Skip to content

Commit b89fbe1

Browse files
committed
Merge branch 'schwarz-modelling_mi_transactions_with_object' into 'devel'
Modelování, posílání a přijímání transakcí v MI driveru, monitoru a testu přes objekt See merge request ndk/ndk-fpga!70
2 parents e1e975f + a82696f commit b89fbe1

File tree

10 files changed

+620
-311
lines changed

10 files changed

+620
-311
lines changed

comp/mi_tools/pipe/cocotb/cocotb_test.py

Lines changed: 72 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -7,45 +7,48 @@
77
import cocotb
88
from cocotb.clock import Clock
99
from cocotb.triggers import RisingEdge, ClockCycles
10-
from drivers import MIMasterDriverTB as MIMasterDriver
11-
from drivers import MISlaveDriverTB as MISlaveDriver
12-
from cocotbext.ofm.mi.monitors import MIMasterMonitor, MISlaveMonitor
10+
from cocotbext.ofm.mi.drivers import MIRequestDriverAgent, MIResponseDriverAgent
11+
from cocotbext.ofm.mi.proxymonitor import MIProxyMonitor
12+
from cocotbext.ofm.mi.monitors import MIMonitor
1313
from cocotbext.ofm.ver.generators import random_packets
14+
from cocotbext.ofm.utils.math import ceildiv
1415
from cocotb_bus.drivers import BitDriver
15-
from cocotb_bus.scoreboard import Scoreboard
16+
from scoreboard import Scoreboard
17+
from cocotbext.ofm.mi.transaction import MiRequestTransaction, MiResponseTransaction, MiTransaction, MiTransactionType
18+
from cocotb.binary import BinaryValue
19+
from cocotbext.ofm.utils.signals import filter_bytes_by_bitmask
1620

1721
import itertools
22+
from random import choice, randint
1823

1924

2025
class testbench():
2126
def __init__(self, dut, debug=False):
2227
self.dut = dut
23-
self.master_stream_in = MIMasterDriver(dut, "IN", dut.CLK)
24-
self.master_stream_out = MIMasterMonitor(dut, "OUT", dut.CLK)
25-
self.slave_stream_in = MISlaveDriver(dut, "OUT", dut.CLK)
26-
self.slave_stream_out = MISlaveMonitor(dut, "IN", dut.CLK)
28+
self.request_stream_in = MIRequestDriverAgent(dut, "IN", dut.CLK)
29+
self.response_stream_out = MIMonitor(dut, "OUT", dut.CLK)
30+
self.response_proxy = MIProxyMonitor(self.response_stream_out, MiTransactionType.Request)
31+
self.response_stream_in = MIResponseDriverAgent(dut, "OUT", dut.CLK)
32+
self.request_stream_out = MIMonitor(dut, "IN", dut.CLK)
33+
self.request_proxy = MIProxyMonitor(self.request_stream_out, MiTransactionType.Response)
2734

2835
self.backpressure = BitDriver(dut.OUT_ARDY, dut.CLK)
2936

30-
# Create a scoreboard on the master_stream_out bus
37+
# Create a scoreboard on the response_stream_out bus
3138
self.pkts_sent = 0
32-
self.expected_output_master = []
33-
self.expected_output_slave = []
39+
self.expected_output = []
3440
self.scoreboard = Scoreboard(dut)
35-
self.scoreboard.add_interface(self.master_stream_out, self.expected_output_master)
36-
self.scoreboard.add_interface(self.slave_stream_out, self.expected_output_slave)
41+
self.scoreboard.add_interface(self.response_proxy, self.expected_output)
42+
self.scoreboard.add_interface(self.request_proxy, self.expected_output)
3743

3844
if debug:
39-
self.master_stream_in.log.setLevel(cocotb.logging.DEBUG)
40-
self.master_stream_out.log.setLevel(cocotb.logging.DEBUG)
45+
self.request_stream_in.log.setLevel(cocotb.logging.DEBUG)
46+
self.response_stream_out.log.setLevel(cocotb.logging.DEBUG)
4147

42-
def model(self, transaction: bytes, testcase: int):
48+
def model(self, test_trans: MiTransaction):
4349
"""Model the DUT based on the input transaction"""
44-
if testcase == 0:
45-
self.expected_output_slave.append(transaction)
46-
else:
47-
self.expected_output_master.append(transaction)
4850

51+
self.expected_output.append(test_trans)
4952
self.pkts_sent += 1
5053

5154
async def reset(self):
@@ -59,58 +62,72 @@ async def reset(self):
5962
async def run_test(dut, pkt_count: int = 1000, item_width_min: int = 1, item_width_max: int = 32):
6063
# Start clock generator
6164
cocotb.start_soon(Clock(dut.CLK, 5, units='ns').start())
62-
tb = testbench(dut)
65+
tb = testbench(dut, debug=False)
6366
await tb.reset()
6467

6568
tb.backpressure.start((1, i % 5) for i in itertools.count())
6669

67-
for testcase in range(2):
68-
"""two test cases - read = 0, write = 1"""
70+
item_count = 0
71+
trans_cntr = 0
6972

70-
item_count = 0
73+
for transaction in random_packets(item_width_min, item_width_max, pkt_count):
74+
trans_cntr += 1
75+
request_type = choice([MiTransactionType.Request, MiTransactionType.Response])
76+
start_addr = randint(0, 2**(tb.request_stream_in.addr_width*8)-1)
7177

72-
for transaction in random_packets(item_width_min, item_width_max, pkt_count):
73-
left_to_enable = len(transaction)
78+
addr = start_addr
79+
offset_transaction = transaction
80+
byte_enable = BinaryValue(2**len(transaction) - 1)
7481

75-
cycles = (len(transaction) + (tb.master_stream_in.data_width - 1)) // tb.master_stream_in.data_width
82+
start_offset = addr % tb.request_stream_in.data_width
83+
end_offset = -(addr + len(offset_transaction)) % tb.request_stream_in.data_width
7684

77-
item_count += cycles
85+
offset_transaction = bytes(start_offset) + offset_transaction + bytes(end_offset)
86+
byte_enable = BinaryValue(("0" * start_offset) + byte_enable.binstr + ("0" * end_offset))
87+
addr = addr - start_offset
7888

79-
for i in range(cycles):
80-
if left_to_enable > tb.master_stream_in.data_width:
81-
byte_enable = 2**tb.master_stream_in.data_width - 1
82-
"""setting all bytes of byte enable to 1"""
89+
cycles = ceildiv(bus_width=tb.request_stream_in.data_width, transaction_len=len(offset_transaction))
8390

84-
left_to_enable -= tb.master_stream_in.data_width
91+
for i in range(cycles):
92+
data = offset_transaction[i*tb.request_stream_in.data_width:(i+1)*tb.request_stream_in.data_width]
8593

86-
else:
87-
byte_enable = 2**left_to_enable - 1
94+
be_slice = BinaryValue(byte_enable.binstr[i*tb.request_stream_in.data_width:(i+1)*tb.request_stream_in.data_width][::-1], bigEndian=False).integer
95+
enabled_data = filter_bytes_by_bitmask(data, be_slice)
8896

89-
byte_enable = 15 if testcase == 0 else byte_enable
97+
if len(enabled_data) == 0:
98+
continue
9099

91-
tb.model((int.from_bytes(transaction[0:tb.master_stream_in.addr_width], 'little') + i * tb.master_stream_in.addr_width, int.from_bytes(transaction[i * tb.master_stream_in.data_width:(i + 1) * tb.master_stream_in.data_width], 'little'), byte_enable), testcase)
100+
test_trans = MiTransaction()
101+
test_trans.trans_type = request_type
102+
test_trans.addr = addr + i*tb.request_stream_in.addr_width
103+
test_trans.data = int.from_bytes(enabled_data, 'little')
104+
test_trans.be = be_slice
105+
tb.model(test_trans)
106+
item_count += 1
92107

93-
if testcase == 0:
94-
"""read test"""
95-
cocotb.log.debug(f"generated transaction: {transaction.hex()}")
96-
tb.master_stream_in.append(("rd", transaction))
97-
tb.slave_stream_in.append(("rd", transaction))
108+
request_trans = MiRequestTransaction()
109+
request_trans.trans_type = request_type
110+
request_trans.addr = start_addr
111+
request_trans.data = transaction
112+
request_trans.data_len = len(transaction)
98113

99-
else:
100-
"""write test"""
101-
cocotb.log.debug(f"generated transaction: {transaction.hex()}")
102-
tb.master_stream_in.append(("wr", transaction))
103-
tb.slave_stream_in.append(("wr", transaction))
114+
response_trans = MiResponseTransaction()
115+
response_trans.trans_type = request_type
116+
response_trans.data = offset_transaction
117+
response_trans.be = byte_enable.integer
104118

105-
last_num = 0
119+
cocotb.log.debug(f"generated transaction: {transaction.hex()}")
120+
tb.request_stream_in.append(request_trans)
121+
tb.response_stream_in.append(response_trans)
106122

107-
stream_out = tb.slave_stream_out if testcase == 0 else tb.master_stream_out
108-
transaction_type = "read" if testcase == 0 else "write"
123+
last_num = 0
124+
stream_out_item_cnt = tb.response_proxy.item_cnt + tb.request_proxy.item_cnt
109125

110-
while stream_out.item_cnt < item_count:
111-
if stream_out.item_cnt // 1000 > last_num:
112-
last_num = stream_out.item_cnt // 1000
113-
cocotb.log.info(f"Number of {transaction_type} transactions processed: {stream_out.item_cnt}/{item_count}")
114-
await ClockCycles(dut.CLK, 100)
126+
while stream_out_item_cnt < item_count:
127+
if stream_out_item_cnt // 1000 > last_num:
128+
last_num = stream_out_item_cnt // 1000
129+
cocotb.log.info(f"Number of transactions processed: {stream_out_item_cnt}/{item_count}")
130+
await ClockCycles(dut.CLK, 100)
131+
stream_out_item_cnt = tb.response_proxy.item_cnt + tb.request_proxy.item_cnt
115132

116133
raise tb.scoreboard.result
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# scoreboard.py: Custom scoreboard for MI Pipe Test
2+
# Copyright (C) 2024 CESNET z. s. p. o.
3+
# Author(s): Ondřej Schwarz <[email protected]>
4+
#
5+
# SPDX-License-Identifier: BSD-3-Clause
6+
7+
from cocotb_bus.scoreboard import Scoreboard as BaseScoreboard
8+
from cocotbext.ofm.mi.transaction import MiTransaction
9+
from cocotb.utils import hexdiffs
10+
from cocotb.result import TestFailure
11+
12+
13+
class Scoreboard(BaseScoreboard):
14+
"""Custom scoreboard for MI Pipe Test"""
15+
16+
def __init__(self, dut, reorder_depth=0, fail_immediately=True):
17+
super().__init__(dut, reorder_depth=reorder_depth, fail_immediately=fail_immediately)
18+
19+
def compare(self, got: MiTransaction, exp: MiTransaction, log, strict_type=True):
20+
"""Custom compare function for MI Pipe Test
21+
22+
Args:
23+
got: object from MiTransaction class passed by the monitor
24+
exp: object from MiTransaction class passed by the test
25+
log: logging object
26+
strict_type: if type of the transaction should be compared
27+
"""
28+
29+
# Type comparison
30+
if strict_type and type(got) != type(exp):
31+
self.errors += 1
32+
log.error("Received transaction type is different than expected")
33+
log.info("Received: %s but expected %s" %
34+
(str(type(got)), str(type(exp))))
35+
if self._imm:
36+
raise TestFailure("Received transaction of wrong type. "
37+
"Set strict_type=False to avoid this.")
38+
return
39+
40+
elif not strict_type:
41+
raise NotImplementedError("Non-strict type not implemented for MI Pipe Test.")
42+
43+
# Comparing modeled and received values
44+
strgot, strexp = [], []
45+
46+
if got.trans_type != exp.trans_type:
47+
return
48+
49+
for i in ["addr", "data", ("be", "byte_enable")]:
50+
item, text = i if isinstance(i, tuple) else (i, i)
51+
if getattr(got, item) != getattr(exp, item):
52+
self.errors += 1
53+
strgot.append(f"{text}: {hex(getattr(got, item))}")
54+
strexp.append(f"{text}: {hex(getattr(exp, item))}")
55+
56+
if self.errors > 0:
57+
log.error("Received transaction differed from expected output")
58+
log.info(f"Expected: {'; '.join(strexp)}")
59+
log.info(f"Received: {'; '.join(strgot)}")
60+
61+
log.warning("Difference:\n%s" % hexdiffs('; '.join(strexp), '; '.join(strgot)))
62+
if self._imm:
63+
raise TestFailure("Received transaction differed from expected "
64+
"transaction")
65+
else:
66+
try:
67+
log.debug("Received expected transaction %d bytes" %
68+
(len(got)))
69+
log.debug(repr(got))
70+
except Exception:
71+
pass

comp/mi_tools/test_space/cocotb/cocotb_test.py

Lines changed: 3 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import cocotb
88
from cocotb.clock import Clock
99
from cocotb.triggers import RisingEdge, ClockCycles
10-
from cocotbext.ofm.mi.drivers import MIMasterDriver as MIDriver
10+
from cocotbext.ofm.mi.drivers import MIRequestDriver as MIDriver
1111
from cocotbext.ofm.ver.generators import random_packets
1212
from cocotb_bus.scoreboard import Scoreboard
1313

@@ -38,43 +38,19 @@ async def reset(self):
3838

3939

4040
@cocotb.test()
41-
async def run_test(dut, pkt_count=1000, item_width_min=1, item_width_max=32):
41+
async def run_test(dut, pkt_count=5000, item_width_min=1, item_width_max=32):
4242
# Start clock generator
4343
cocotb.start_soon(Clock(dut.CLK, 5, units='ns').start())
4444
tb = testbench(dut)
4545
await tb.reset()
4646

47-
cocotb.log.info("\nREAD32 AND WRITE32 TEST\n")
48-
49-
for transaction in random_packets(4, 4, pkt_count):
50-
cocotb.log.debug(f"generated transaction: {transaction.hex()}")
51-
await tb.stream_in.write32(int.from_bytes(transaction, 'little'), transaction)
52-
output = await tb.stream_in.read32(int.from_bytes(transaction, 'little'))
53-
cocotb.log.debug(f"recieved transaction: {output.hex()}")
54-
55-
assert output == transaction
56-
57-
cocotb.log.info("DONE")
58-
5947
cocotb.log.info("\nREAD AND WRITE TEST\n")
6048

6149
for transaction in random_packets(item_width_min, item_width_max, pkt_count):
6250
cocotb.log.debug(f"generated transaction: {transaction.hex()}")
6351
await tb.stream_in.write(int.from_bytes(transaction[0:4], 'little'), transaction)
6452
output = await tb.stream_in.read(int.from_bytes(transaction[0:4], 'little'), len(transaction))
65-
cocotb.log.debug(f"recieved transaction: {output.hex()}")
66-
67-
assert output == transaction
68-
69-
cocotb.log.info("DONE")
70-
71-
cocotb.log.info("\nREAD64 AND WRITE64 TEST\n")
72-
73-
for transaction in random_packets(8, 8, pkt_count):
74-
cocotb.log.debug(f"generated transaction: {transaction.hex()}")
75-
await tb.stream_in.write64(int.from_bytes(transaction[0:4], 'little'), transaction)
76-
output = await tb.stream_in.read64(int.from_bytes(transaction[0:4], 'little'))
77-
cocotb.log.debug(f"recieved transaction: {output.hex()}")
53+
cocotb.log.debug(f"received transaction: {output.hex()}")
7854

7955
assert output == transaction
8056

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# proxymonitor.py: ProxyMonitor
2+
# Copyright (C) 2024 CESNET z. s. p. o.
3+
# Author(s): Ondřej Schwarz <[email protected]>
4+
#
5+
# SPDX-License-Identifier: BSD-3-Clause
6+
7+
from cocotb_bus.monitors import BusMonitor
8+
from abc import ABC, abstractmethod
9+
10+
11+
class ProxyMonitor(BusMonitor, ABC):
12+
"""
13+
Generic Proxy Monitor used for redirecting traffic from cocotb BusMonitor
14+
and running it through a filter. It automatically connects to the _recv
15+
function of the passed BusMonitor.
16+
17+
Attributes:
18+
_item_cnt(int): number of items which successfully passed through the filter.
19+
"""
20+
21+
def __init__(self, monitor: BusMonitor):
22+
"""
23+
Args:
24+
monitor: BusMonitor which this class is to be the proxy for.
25+
"""
26+
super().__init__(monitor.entity, monitor.name, monitor.clock)
27+
monitor.add_callback(self.monitor_callback)
28+
self._item_cnt = 0
29+
30+
@property
31+
def item_cnt(self):
32+
return self._item_cnt
33+
34+
def monitor_callback(self, *args, **kwargs) -> None:
35+
"""
36+
Callback connected to the passed BusMonitor's _recv function.
37+
The received transaction is run through a filter and then
38+
passed on through the _recv function.
39+
"""
40+
filtered_transaction = self._filter_transaction(*args, **kwargs)
41+
42+
if filtered_transaction is None:
43+
return
44+
45+
self._recv(filtered_transaction)
46+
self._item_cnt += 1
47+
48+
@abstractmethod
49+
def _filter_transaction(self, transaction, *args, **kwargs) -> any:
50+
"""
51+
Filter function used in function monitor_callback. It is ment to be redefined
52+
for specific usecases.
53+
The placeholder filter implemented here lets all the transactions through.
54+
"""
55+
return transaction
56+
57+
async def _monitor_recv(self):
58+
"""
59+
This function must be implemented in every BusMonitor child, however, here it has
60+
no use. However, it may be redifined for specific usecases.
61+
"""
62+
pass

0 commit comments

Comments
 (0)