|
4 | 4 | # Daniel Kondys <kondys@cesnet.cz> |
5 | 5 |
|
6 | 6 |
|
| 7 | +# importing required modules |
7 | 8 | import itertools |
8 | 9 |
|
9 | 10 | import cocotb |
|
18 | 19 | from cocotbext.ofm.base.generators import ItemRateLimiter |
19 | 20 | from cocotbext.ofm.mvb.transaction import MvbTrClassic |
20 | 21 |
|
21 | | - |
| 22 | +# definition of the class encapsulating components of the test |
22 | 23 | class testbench(): |
| 24 | + # dut = device tree of the tested component |
23 | 25 | def __init__(self, dut, debug=False): |
24 | 26 | self.dut = dut |
| 27 | + |
| 28 | + # setting up the input driver and connecting it to signals beginning with "RX" |
25 | 29 | self.stream_in = MVBDriver(dut, "RX", dut.CLK) |
26 | | - self.backpressure = BitDriver(dut.TX_DST_RDY, dut.CLK) |
| 30 | + |
| 31 | + # setting up the output monitor and connecting it to signals beginning with "TX" |
27 | 32 | self.stream_out = MVBMonitor(dut, "TX", dut.CLK, tr_type=MvbTrClassic) |
28 | 33 |
|
| 34 | + # setting up driver of the DST_RDY so it randomly fluctuates between 0 and 1 |
| 35 | + self.backpressure = BitDriver(dut.TX_DST_RDY, dut.CLK) |
| 36 | + |
| 37 | + # setting up the probe measuring throughput |
29 | 38 | self.throughput_probe = ThroughputProbe(ThroughputProbeMvbInterface(self.stream_out), throughput_units="items") |
30 | 39 | self.throughput_probe.set_log_period(10) |
31 | 40 | self.throughput_probe.add_log_interval(0, None) |
32 | 41 |
|
33 | | - # Create a scoreboard on the stream_out bus |
| 42 | + # counter of sent transactions |
34 | 43 | self.pkts_sent = 0 |
| 44 | + |
| 45 | + # list of the transactions that are expected to be received |
35 | 46 | self.expected_output = [] |
| 47 | + |
| 48 | + # setting up a scoreboard which compares received transactions with the expected transactions |
36 | 49 | self.scoreboard = Scoreboard(dut) |
| 50 | + |
| 51 | + # linking a monitor with its expected output |
37 | 52 | self.scoreboard.add_interface(self.stream_out, self.expected_output) |
38 | 53 |
|
| 54 | + # setting up the logging level |
39 | 55 | if debug: |
40 | 56 | self.stream_in.log.setLevel(cocotb.logging.DEBUG) |
41 | 57 | self.stream_out.log.setLevel(cocotb.logging.DEBUG) |
42 | 58 |
|
| 59 | + # method for adding transactions to the expected output |
43 | 60 | def model(self, transaction): |
44 | 61 | """Model the DUT based on the input transaction""" |
45 | 62 | self.expected_output.append(transaction) |
46 | 63 | self.pkts_sent += 1 |
47 | 64 |
|
| 65 | + # method preforming a hardware reset |
48 | 66 | async def reset(self): |
49 | 67 | self.dut.RESET.value = 1 |
50 | 68 | await ClockCycles(self.dut.CLK, 10) |
51 | 69 | self.dut.RESET.value = 0 |
52 | 70 | await RisingEdge(self.dut.CLK) |
53 | 71 |
|
54 | 72 |
|
| 73 | +# defining a test - functions with "@cocotb.test()" decorator will be automatically found and run |
55 | 74 | @cocotb.test() |
56 | 75 | async def run_test(dut, pkt_count=10000): |
57 | | - # Start clock generator |
| 76 | + # start a clock generator |
58 | 77 | cocotb.start_soon(Clock(dut.CLK, 5, units="ns").start()) |
| 78 | + |
| 79 | + # initialization of the test bench |
59 | 80 | tb = testbench(dut, debug=False) |
60 | | - # Change MVB drivers IdleGenerator to ItemRateLimiter |
61 | | - # Note: the RateLimiter's rate is affected by backpressure (DST_RDY). |
62 | | - # Eventhough it takes into account cycles with DST_RDY=0, the desired rate might not be achievable. |
| 81 | + |
| 82 | + # change MVB driver's IdleGenerator to ItemRateLimiter |
| 83 | + # note: the RateLimiter's rate is affected by backpressure (DST_RDY). |
| 84 | + # Even though it takes into account cycles with DST_RDY=0, the desired rate might not be achievable. |
63 | 85 | idle_gen_conf = dict(random_idles=True, max_idles=5, zero_idles_chance=50) |
64 | 86 | tb.stream_in.set_idle_generator(ItemRateLimiter(rate_percentage=30, **idle_gen_conf)) |
| 87 | + |
| 88 | + # running simulated reset |
65 | 89 | await tb.reset() |
| 90 | + |
| 91 | + # starting the BitDriver (randomized DST_RDY) |
66 | 92 | tb.backpressure.start((1, i % 5) for i in itertools.count()) |
67 | 93 |
|
| 94 | + # dynamically getting the width of the data signal that will be set (useful if the width of the signal may change) |
68 | 95 | data_width = tb.stream_in.item_widths["data"] |
| 96 | + |
| 97 | + # generating MVB items as random integers between the minimum and maximum unsigned value of the item |
69 | 98 | for transaction in random_integers(0, 2**data_width-1, pkt_count): |
| 99 | + |
| 100 | + # logging the generated transaction |
70 | 101 | cocotb.log.debug(f"generated transaction: {hex(transaction)}") |
| 102 | + |
| 103 | + # initializing MVB transaction object |
71 | 104 | mvb_tr = MvbTrClassic() |
| 105 | + |
| 106 | + # setting data of MVB transaction to the generated integer |
72 | 107 | mvb_tr.data = transaction |
| 108 | + |
| 109 | + # appending the transaction to tb.expected_output |
73 | 110 | tb.model(mvb_tr) |
| 111 | + |
| 112 | + # passing the transaction to the driver which then writes it to the bus |
74 | 113 | tb.stream_in.append(mvb_tr) |
75 | 114 |
|
76 | 115 | last_num = 0 |
77 | 116 |
|
| 117 | + # checking if all the expected packets have been received |
78 | 118 | while (tb.stream_out.item_cnt < pkt_count): |
| 119 | + |
| 120 | + # logging number of received packets after every 1000 packets |
79 | 121 | if (tb.stream_out.item_cnt // 1000) > last_num: |
80 | 122 | last_num = tb.stream_out.item_cnt // 1000 |
81 | 123 | cocotb.log.info(f"Number of transactions processed: {tb.stream_out.item_cnt}/{pkt_count}") |
| 124 | + |
| 125 | + # if not all packets have been received yet, waiting 100 cycles so the simulation doesn't stop prematurely |
82 | 126 | await ClockCycles(dut.CLK, 100) |
83 | 127 |
|
| 128 | + # logging values measured by throughput probe |
84 | 129 | tb.throughput_probe.log_max_throughput() |
85 | 130 | tb.throughput_probe.log_average_throughput() |
86 | 131 |
|
| 132 | + # displaying result of the test |
87 | 133 | raise tb.scoreboard.result |
0 commit comments