Skip to content

Commit ab8cc38

Browse files
committed
Merge branch 'schwarz-feat-reimplement_mfb_monitor' into 'devel'
Reimplement MFB monitor using binary to improve readability See merge request ndk/ndk-fpga!250
2 parents 2d88ce4 + ee923e0 commit ab8cc38

File tree

5 files changed

+250
-128
lines changed

5 files changed

+250
-128
lines changed

comp/mfb_tools/storage/fifox/cocotb/cocotb_test.py

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,33 @@
1515
from cocotb_bus.drivers import BitDriver
1616
from cocotb_bus.scoreboard import Scoreboard
1717
from cocotbext.ofm.utils.throughput_probe import ThroughputProbe, ThroughputProbeMfbInterface
18+
from cocotbext.ofm.mfb.transaction import MfbTransaction, MfbTransactionWithMeta
19+
from random import randint
1820

1921

2022
# definition of the class encapsulating components of the test
2123
class testbench():
2224
# dut = device tree to the tested component
2325
def __init__(self, dut, debug=False):
2426
self.dut = dut
27+
28+
# setting MFB params based on generics
29+
mfb_params = {
30+
"regions" : dut.REGIONS.value,
31+
"region_size" : dut.REGION_SIZE.value,
32+
"block_size" : dut.BLOCK_SIZE.value,
33+
"item_width" : dut.ITEM_WIDTH.value,
34+
"meta_width" : dut.META_WIDTH.value
35+
}
36+
2537
# setting up the input driver and connecting it to signals begging with "RX"
26-
self.stream_in = MFBDriver(dut, "RX", dut.CLK)
38+
self.stream_in = MFBDriver(dut, "RX", dut.CLK, mfb_params=mfb_params)
39+
40+
# choosing the right transaction type based on legth of the meta signal
41+
self.trans_type = MfbTransactionWithMeta if len(self.stream_in.bus.meta) > 0 else MfbTransaction
42+
2743
# setting up the output monitor and connecting it to signals begging with "TX"
28-
self.stream_out = MFBMonitor(dut, "TX", dut.CLK)
44+
self.stream_out = MFBMonitor(dut, "TX", dut.CLK, mfb_params=mfb_params, trans_type=self.trans_type)
2945
# setting up driver of the DST_RDY so it randomly fluctuates between 0 and 1
3046
self.backpressure = BitDriver(dut.TX_DST_RDY, dut.CLK)
3147

@@ -77,12 +93,23 @@ async def run_test(dut, pkt_count=10000, frame_size_min=60, frame_size_max=512):
7793
# staring the BitDriver (randomized DST_RDY)
7894
tb.backpressure.start((1, i % 5) for i in itertools.count())
7995

96+
# calculating width of the meta signal for a region
97+
meta_width = len(tb.stream_in.bus.meta) // len(tb.stream_in.bus.sof)
98+
8099
# generating random packets
81-
for transaction in random_packets(frame_size_min, frame_size_max, pkt_count):
100+
for packet in random_packets(frame_size_min, frame_size_max, pkt_count):
101+
# creating a transaction object and adding data to it
102+
transaction = tb.trans_type()
103+
transaction.data = packet
104+
105+
# setting meta signal if it's present
106+
if hasattr(transaction, "meta"):
107+
transaction.meta = randint(0, 2**meta_width-1)
108+
82109
# adding generated packet to tb.expected_output
83110
tb.model(transaction)
84111
# logging the generated packet
85-
cocotb.log.debug("generated transaction: " + transaction.hex())
112+
cocotb.log.debug(f"generated transaction: {transaction}")
86113
# passing generated packet to the driver to be sent to the bus
87114
tb.stream_in.append(transaction)
88115

python/cocotbext/cocotbext/ofm/mfb/drivers.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
# SPDX-License-Identifier: BSD-3-Clause
66

77
from cocotb_bus.drivers import BusDriver
8+
from cocotbext.ofm.mfb.transaction import MfbTransaction
89
from cocotb.triggers import RisingEdge
910
from cocotbext.ofm.mfb.utils import get_mfb_params
1011

@@ -13,13 +14,14 @@
1314

1415
class MFBDriver(BusDriver):
1516
_signals = ["data", "sof_pos", "eof_pos", "sof", "eof", "src_rdy", "dst_rdy"]
17+
_optional_signals = ["meta"]
1618

1719
def __init__(self, entity, name, clock, array_idx=None, mfb_params=None):
1820
BusDriver.__init__(self, entity, name, clock, array_idx=array_idx)
1921
self.clock = clock
2022
self.frame_cnt = 0
21-
self._regions, self._region_size, self._block_size, self._item_width = get_mfb_params(
22-
self.bus.data, self.bus.sof_pos, self.bus.eof_pos, self.bus.sof, mfb_params
23+
self._regions, self._region_size, self._block_size, self._item_width, self._meta_width = get_mfb_params(
24+
self.bus, mfb_params
2325
)
2426
self._items = self._regions * self._region_size * self._block_size
2527
self._region_items = self._region_size * self._block_size
@@ -156,6 +158,10 @@ async def _send_thread(self):
156158

157159
while self._sendQ:
158160
transaction, callback, event, kwargs = self._sendQ.popleft()
161+
162+
if isinstance(transaction, MfbTransaction):
163+
transaction: bytes = transaction.data
164+
159165
await self._write_frame(transaction)
160166
self.frame_cnt += 1
161167
# Notify the world that this transaction is complete
Lines changed: 133 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -1,125 +1,170 @@
1-
# monitors.py: MFBMonitor
2-
# Copyright (C) 2024 CESNET z. s. p. o.
3-
# Author(s): Jakub Cabal <[email protected]>
4-
#
51
# SPDX-License-Identifier: BSD-3-Clause
2+
# Copyright (C) 2025 CESNET z. s. p. o.
3+
# Author(s): Jakub Cabal <[email protected]>
4+
# Ondrej Schwarz <[email protected]>
65

76
from cocotb_bus.monitors import BusMonitor
87
from cocotb.triggers import RisingEdge
9-
from cocotbext.ofm.mfb.utils import get_mfb_params, signal_unpack
8+
from cocotbext.ofm.utils.binary import Binary, BinaryVector
9+
from cocotbext.ofm.mfb.utils import get_mfb_params
10+
from cocotbext.ofm.mfb.transaction import MfbTransaction
11+
from math import log2
12+
from copy import copy
13+
14+
# NOTE remove line 46 while/after reimplementing MFB driver !!!
1015

1116

1217
class MFBProtocolError(Exception):
1318
pass
1419

1520

1621
class MFBMonitor(BusMonitor):
22+
"""
23+
Monitor for the MFB bus.
24+
25+
Args:
26+
trans_type: The desired type for the transactions returned by the monitor.
27+
Defaults to `bytes` for backward compatibility.
28+
Consider using a child class of `MfbTransaction` for more structured data.
29+
30+
meta_valid_with: Specifies which signal indicates the validity of metadata
31+
if the 'meta' signal is present. Must be either "sof" (start of frame)
32+
or "eof" (end of frame).
33+
"""
34+
1735
_signals = ["data", "sof_pos", "eof_pos", "sof", "eof", "src_rdy", "dst_rdy"]
36+
_optional_signals = ["meta"]
1837

19-
def __init__(self, entity, name, clock, array_idx=None, mfb_params=None):
20-
BusMonitor.__init__(self, entity, name, clock, array_idx=array_idx)
21-
self.frame_cnt = 0
22-
self.item_cnt = 0
23-
self._regions, self._region_size, self._block_size, self._item_width = get_mfb_params(
24-
self.bus.data, self.bus.sof_pos, self.bus.eof_pos, self.bus.sof, mfb_params
38+
def __init__(self, entity, name, clock, array_idx=None, mfb_params=None, trans_type: MfbTransaction | bytes = bytes, meta_vld_with: str = "sof"):
39+
super().__init__(entity, name, clock, array_idx=array_idx)
40+
41+
self._regions, self._region_size, self._block_size, self._item_width, self._meta_width = get_mfb_params(
42+
self.bus, mfb_params
2543
)
2644
self._region_items = self._region_size * self._block_size
27-
self._sof_arr = [0] * self._regions
28-
self._eof_arr = [0] * self._regions
29-
self._sof_pos_arr = [0] * self._regions
30-
self._eof_pos_arr = [0] * self._regions
45+
46+
self._item_width = 8 # remove this line while/after reimplementing MFB driver!!!
47+
48+
self._trans_type = trans_type
49+
self._transaction = MfbTransaction() if self._trans_type is bytes else trans_type()
50+
51+
self._data = BinaryVector(item_count=self._regions, item_bits=self._region_items*self._item_width, endian="little")
52+
self._sof_pos = BinaryVector(item_count=self._regions, item_bits=int(log2(self._region_size)))
53+
self._eof_pos = BinaryVector(item_count=self._regions, item_bits=int(log2(self._region_size*self._block_size)))
54+
self._sof = Binary(bits=self._regions)
55+
self._eof = Binary(bits=self._regions)
56+
57+
if self._meta_width > 0:
58+
self._meta = BinaryVector(item_count=self._regions, item_bits=self._meta_width)
59+
60+
if meta_vld_with not in ["sof", "eof"]:
61+
raise ValueError(f"Invalid value of {meta_vld_with} of 'meta_vld_with'. Supported values are: \"sof\", \"eof\".")
62+
63+
self._meta_vld_with = meta_vld_with
64+
65+
self.frame_cnt = 0
66+
self.item_cnt = 0
3167

3268
def _is_valid_word(self, signal_src_rdy, signal_dst_rdy):
3369
if signal_dst_rdy is None:
3470
return (signal_src_rdy.value == 1)
3571
else:
3672
return (signal_src_rdy.value == 1) and (signal_dst_rdy.value == 1)
3773

74+
def _read_control_signals(self):
75+
self._data.value = self.bus.data.value.integer
76+
self._sof_pos.value = self.bus.sof_pos.value.integer
77+
self._eof_pos.value = self.bus.eof_pos.value.integer
78+
self._sof.value = self.bus.sof.value.integer
79+
self._eof.value = self.bus.eof.value.integer
80+
81+
if self._meta_width > 0:
82+
self._meta.value = self.bus.meta.value.integer
83+
84+
def _recv_trans(self):
85+
self.log.debug(f"received transaction: {self._transaction}")
86+
87+
if self._trans_type is bytes:
88+
self._recv(self._transaction.data)
89+
else:
90+
self._recv(copy(self._transaction))
91+
3892
async def _monitor_recv(self):
39-
"""Watch the pins and reconstruct transactions."""
40-
# Avoid spurious object creation by recycling
41-
clkedge = RisingEdge(self.clock)
42-
frame = b""
93+
clk_re = RisingEdge(self.clock)
94+
4395
in_frame = False
4496

4597
while True:
46-
await clkedge
98+
await clk_re
4799

48100
if self.in_reset:
49101
continue
50102

51103
if self._is_valid_word(self.bus.src_rdy, self.bus.dst_rdy):
52-
self.log.debug("valid MFB word")
53-
54-
data_val = self.bus.data.value
55-
data_val.big_endian = False
56-
data_bytes = data_val.buff
57-
58-
self._sof_arr = signal_unpack(self._regions, self.bus.sof)
59-
self._eof_arr = signal_unpack(self._regions, self.bus.eof)
60-
self._sof_pos_arr = signal_unpack(self._regions, self.bus.sof_pos)
61-
self._eof_pos_arr = signal_unpack(self._regions, self.bus.eof_pos)
62-
63-
self.log.debug(f"sof_arr {str(self._sof_arr)}")
64-
self.log.debug(f"eof_arr {str(self._eof_arr)}")
65-
self.log.debug(f"sof_pos_arr {str(self._sof_pos_arr)}")
66-
self.log.debug(f"eof_pos_arr {str(self._eof_pos_arr)}")
67-
68-
for rr in range(self._regions):
69-
# Iterating through the regions.
70-
eof_done = False
71-
rs_inx = (rr * self._region_items)
72-
re_inx = (rr * self._region_items + self._region_items)
73-
ee_idx = (rr * self._region_items + self._eof_pos_arr[rr] + 1)
74-
ss_idx = (rr * self._region_items + (self._sof_pos_arr[rr] * self._block_size))
75-
76-
self.log.debug(f"rs_inx {str(rs_inx)}")
77-
self.log.debug(f"re_inx {str(re_inx)}")
78-
self.log.debug(f"ee_idx {str(ee_idx)}")
79-
self.log.debug(f"ss_idx {str(ss_idx)}")
80-
81-
if self._eof_arr[rr] == 1:
82-
if in_frame:
83-
# Checks if there is a packet that is being processed and if it ends in this region.
84-
self.log.debug("Frame End")
85-
in_frame = False
86-
eof_done = True
87-
frame += data_bytes[rs_inx:ee_idx]
88-
self.item_cnt += len(data_bytes[rs_inx:ee_idx])*8 // self._item_width
89-
self.log.debug(f"frame done {frame.hex()}")
90-
self._recv(frame)
91-
self.frame_cnt += 1
92-
elif self._sof_arr[rr] == 1 and (self._eof_pos_arr[rr] < self._sof_pos_arr[rr]):
93-
raise MFBProtocolError("MFB error: an end-of-frame received before a start-of-frame!")
104+
self._read_control_signals()
105+
106+
for r in range(self._regions):
107+
sof = self._sof[r].int
108+
eof = self._eof[r].int
109+
110+
sof_pos = self._sof_pos[r].int if sof else 0
111+
eof_pos = self._eof_pos[r].int if eof else 0
112+
113+
pkt_start = sof_pos * self._block_size * self._item_width
114+
pkt_end = (eof_pos + 1) * self._item_width
115+
116+
# receive metadata (if present)
117+
if self._meta_width > 0 and hasattr(self._transaction, "meta"):
118+
if sof and self._meta_vld_with == "sof":
119+
self._transaction.meta = self._meta[r].int
120+
elif eof and self._meta_vld_with == "eof":
121+
self._transaction.meta = self._meta[r].int
94122

95123
if in_frame:
96-
# Region with a valid 'middle of packet'.
97-
frame += data_bytes[rs_inx:re_inx]
98-
self.item_cnt += len(data_bytes[rs_inx:re_inx])*8 // self._item_width
99-
self.log.debug(f"frame middle {frame.hex()}")
100-
101-
if self._sof_arr[rr] == 1:
102-
# Checking for beginning of a packet.
103-
self.log.debug("Frame Start")
104-
if in_frame:
105-
raise MFBProtocolError("MFB error: a start-of-frame received without an end-of-frame!")
106-
in_frame = True
107-
frame = b""
108-
109-
if (self._eof_arr[rr] == 1) and (not eof_done):
110-
# Checking if the packet ends in the same regions where it began.
111-
self.log.debug("Frame End in single region")
112-
if not in_frame:
113-
raise MFBProtocolError("MFB error: an end-of-frame received without a start-of-frame!")
124+
if sof and eof:
125+
# if sof appears before eof
126+
if self._region_size > 1:
127+
if pkt_end > pkt_start:
128+
raise MFBProtocolError(f"MFB error: a start-of-frame received without an end-of-frame! ({sof_pos=}, {eof_pos=})")
129+
130+
# end of one packet
131+
self._transaction.data += self._data[r][:pkt_end].bytes
132+
self._recv_trans()
133+
self.frame_cnt += 1
134+
self.item_cnt += len(self._transaction.data) * 8 // self._item_width
135+
136+
# start of another packet, in_frame stays True
137+
self._transaction.data = self._data[r][pkt_start:].bytes
138+
139+
elif sof:
140+
# sof when the previous packet hasn't ended
141+
raise MFBProtocolError(f"MFB error: a start-of-frame received without an end-of-frame! ({sof_pos=})")
142+
143+
elif eof:
144+
# packet ends in this region and new one doesn't start
145+
self._transaction.data += self._data[r][:pkt_end].bytes
146+
self._recv_trans()
114147
in_frame = False
115-
frame += data_bytes[ss_idx:ee_idx]
116-
self.item_cnt += len(data_bytes[ss_idx:ee_idx])*8 // self._item_width
117-
self.log.debug(f"frame done single {frame.hex()}")
118-
self._recv(frame)
119148
self.frame_cnt += 1
149+
self.item_cnt += len(self._transaction.data) * 8 // self._item_width
120150

121151
else:
122-
# Packet continues into another region.
123-
frame += data_bytes[ss_idx:re_inx]
124-
self.item_cnt += len(data_bytes[ss_idx:re_inx])*8 // self._item_width
125-
self.log.debug(f"frame start {frame.hex()}")
152+
# packet starts and ends in another region, in_frame stays True
153+
self._transaction.data += self._data[r].bytes
154+
155+
else:
156+
if sof and eof:
157+
# packet starts and ends in this region, in_frame stays False
158+
self._transaction.data = self._data[r][pkt_start : pkt_end].bytes
159+
self._recv_trans()
160+
self.frame_cnt += 1
161+
self.item_cnt += len(self._transaction.data) * 8 // self._item_width
162+
163+
elif sof:
164+
# packet starts in this regions and ends in another one
165+
self._transaction.data = self._data[r][pkt_start:].bytes
166+
in_frame = True
167+
168+
elif eof:
169+
# eof when not in frame
170+
raise MFBProtocolError("MFB error: an end-of-frame received before a start-of-frame!")

0 commit comments

Comments
 (0)