Skip to content

Commit 7956205

Browse files
committed
Merge branch 'schwarz-feat-connect_probe_to_driver' into 'devel'
Support for probing MVB and MFB drivers using Throughput Probe and improve wording in comments and hints See merge request ndk/ndk-fpga!370
2 parents d176c6d + 602f25e commit 7956205

File tree

6 files changed

+156
-18
lines changed

6 files changed

+156
-18
lines changed

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

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,14 @@ def __init__(self, dut, debug=False):
4545
# setting up driver of the DST_RDY so it randomly fluctuates between 0 and 1
4646
self.backpressure = BitDriver(dut.TX_DST_RDY, dut.CLK)
4747

48-
# setting up the probe measuring throughput
49-
self.throughput_probe = ThroughputProbe(ThroughputProbeMfbInterface(self.stream_out), throughput_units="bits")
50-
self.throughput_probe.add_log_interval(0, None)
51-
self.throughput_probe.set_log_period(10)
48+
# setting up the probes measuring throughput
49+
self.in_throughput_probe = ThroughputProbe(ThroughputProbeMfbInterface(self.stream_in), throughput_units="bits", name="ThroughputProbe - IN")
50+
self.in_throughput_probe.add_log_interval(0, None)
51+
self.in_throughput_probe.set_log_period(10)
52+
53+
self.out_throughput_probe = ThroughputProbe(ThroughputProbeMfbInterface(self.stream_out), throughput_units="bits", name="ThroughputProbe - OUT")
54+
self.out_throughput_probe.add_log_interval(0, None)
55+
self.out_throughput_probe.set_log_period(10)
5256

5357
# counter of sent transactions
5458
self.pkts_sent = 0
@@ -123,9 +127,12 @@ async def run_test(dut, pkt_count=10000, frame_size_min=60, frame_size_max=512):
123127
# if not all packets have been received yet, waiting 100 cycles so the simulation doesn't stop prematurelly
124128
await ClockCycles(dut.CLK, 100)
125129

126-
# logging values measured by throughput probe
127-
tb.throughput_probe.log_max_throughput()
128-
tb.throughput_probe.log_average_throughput()
130+
# logging values measured by throughput probes
131+
tb.in_throughput_probe.log_max_throughput()
132+
tb.in_throughput_probe.log_average_throughput()
133+
134+
tb.out_throughput_probe.log_max_throughput()
135+
tb.out_throughput_probe.log_average_throughput()
129136

130137
# displaying result of the test
131138
raise tb.scoreboard.result
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
====================
2+
Cocotb tips & tricks
3+
====================
4+
5+
This section consists of simple problems you may encounter when creating testbenches
6+
using ``cocotb/cocotbext-ndk`` frameworks and the solutions to those problems.
7+
8+
Using probes
9+
============
10+
11+
``cocotbext-ndk`` framework includes a base class for creating probes for various purposes.
12+
It can be found at ``ndk-fpga/python/cocotbext/cocotbext/ofm/base/probe.py``. The
13+
probes typically read attributes of their connected agents, usually a monitor or a driver,
14+
perform calculations with them, and display the results to the terminal either at a specific
15+
time, periodically, or on request.
16+
17+
The base ``Probe`` class offers a couple of methods to make this possible.
18+
19+
To log a specific time interval, use the ``add_log_interval`` method. If you want the time interval
20+
to go to infinity, set the ``stop_time`` argument to ``None``; however, don’t forget to also set a period,
21+
otherwise the probe will never log.
22+
23+
Periodic logging can be achieved by setting a period using the ``set_log_period`` method.
24+
25+
For cases where full control over when the probe starts and stops logging is needed, the ``start_log``
26+
and ``stop_log`` methods are implemented.
27+
28+
When it comes to specific implementations of probes, at the time of writing, there is only one,
29+
and that is ``ThroughputProbe`` for measuring throughput and efficiency.
30+
Check out ``ndk-fpga/python/cocotbext/cocotbext/ofm/utils/throughput_probe.py`` for the implementation.
31+
32+
Class ``ThroughputProbe`` further adds the ``log_average_throughput`` and ``log_max_throughput`` methods
33+
to log the average throughput and efficiency and the maximal possible average throughput,
34+
respectively. It is recommended to call these methods at the end of the test when all the transactions
35+
have been processed.
36+
37+
To perform its task, a probe typically needs to read attributes from the probed agent synchronously.
38+
However, the names of the attributes of the agent may differ from the names of the attributes of the probe,
39+
or they may not be present at all; in that case, they need to be either calculated or set to a fixed value.
40+
41+
To resolve this, it is recommended to use interfaces derived from the ``ProbeInterface`` class. This class
42+
offers two approaches for passing a value to the probe – a translation dictionary ``interface_dict`` and
43+
properties.
44+
45+
The translation dictionary ``interface_dict`` should contain all needed attributes as its keys.
46+
The values linked to the keys should be either the names of the equivalent attributes of the agent,
47+
or ``None`` to indicate that the value is returned by a property of the interface.
48+
49+
If the value cannot be simply read from the agent object because some logic needs to be performed
50+
(e.g., the value must be calculated from multiple attributes, or the behavior depends on the type of the
51+
agent), a property with the same name as the key in ``interface_dict`` can be created, which overrides the
52+
dictionary entry.
53+
54+
.. note:: The interface always looks for a property first. Only after it is not found does it try to get the value
55+
from the connected agent using ``interface_dict``.
56+
57+
For a specific implementation of a probe interface, check out the different interfaces for
58+
``ThroughputProbe`` in ``ndk-fpga/python/cocotbext/cocotbext/ofm/utils/throughput_probe.py``.
59+
60+
The setup of a probe can, for example, look like this:
61+
62+
.. code-block:: python
63+
64+
self.out_throughput_probe = ThroughputProbe(
65+
ThroughputProbeMfbInterface(self.stream_out),
66+
throughput_units="bits",
67+
name="ThroughputProbe - OUT"
68+
)
69+
self.out_throughput_probe.add_log_interval(0, None)
70+
self.out_throughput_probe.set_log_period(10)
71+
72+
Which then produces output like this:
73+
74+
.. code-block::
75+
76+
# 10000.00ns INFO cocotb.ThroughputProbe - OUT Immediate throughput at 10.0 us: 134.5104 Gb/s, Immediate efficiency: 32.8395%
77+
# 20000.00ns INFO cocotb.ThroughputProbe - OUT Immediate throughput at 20.0 us: 134.8456 Gb/s, Immediate efficiency: 32.9213%
78+
# 30000.00ns INFO cocotb.ThroughputProbe - OUT Immediate throughput at 30.0 us: 134.9 Gb/s, Immediate efficiency: 32.9346%

doc/source/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ it also provides converters:
9898

9999
basic_cocotb_test
100100
cocotbext
101+
cocotb_tips_and_tricks
101102

102103
.. toctree::
103104
:caption: GitLab CI/CD Tools

python/cocotbext/cocotbext/ofm/base/probe.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
from cocotb.triggers import RisingEdge
99
from cocotb.log import SimLog
1010
from cocotb.utils import get_sim_time
11+
from cocotb_bus.drivers import BusDriver
12+
from cocotb_bus.monitors import BusMonitor
1113
from ofm.utils.units import convert_units
1214
from abc import ABC, abstractmethod
1315
from typing import Any
@@ -20,16 +22,19 @@ class ProbeInterface:
2022
Atributes:
2123
interface_dict(dict): keys are names of parameters that the probe wants to retrieve and items are tuples,
2224
where the first item is the conversion function and second is tuple with arguments
23-
for the convertion function.
24-
_monitor(BusMonitor): monitor object including the attributes required for probing
25+
for the conversion function. It is also possible to override a value in the interface
26+
dict by creating a class property with the same name as the key being overriden in the
27+
interface dict. This can be useful if it's necessary to calculate the value of the attribute
28+
or to implement different behavior based on the type of the agent.
29+
_agent(BusMonitor | BusDriver): monitor or driver object including the attributes required for probing
2530
"""
2631
interface_dict = {}
2732

28-
def __init__(self, monitor):
29-
self._monitor = monitor
33+
def __init__(self, agent: BusMonitor | BusDriver):
34+
self._agent = agent
3035

3136
def __getattr__(self, name: str) -> Any:
32-
return getattr(self._monitor, self.interface_dict.get(name, None))
37+
return getattr(self._agent, self.interface_dict.get(name, None))
3338

3439

3540
class Probe(ABC):

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ def __init__(self, entity, name, clock, array_idx=None, mfb_params=None):
2020
super().__init__(entity, name, clock, array_idx=array_idx)
2121
self.clock = clock
2222
self.frame_cnt = 0
23+
self.item_cnt = 0
2324
self._regions, self._region_size, self._block_size, self._item_width, self._meta_width, self._os_valid_with = get_mfb_params(
2425
self.bus, mfb_params
2526
)
@@ -201,6 +202,7 @@ async def _send_thread(self):
201202

202203
await self._write_frame(transaction)
203204
self.frame_cnt += 1
205+
self.item_cnt += (len(transaction.data) * 8) // self._item_width
204206
# Notify the world that this transaction is complete
205207
if event:
206208
event.set()

python/cocotbext/cocotbext/ofm/utils/throughput_probe.py

Lines changed: 51 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
from ofm.utils import convert_units
1010
from cocotbext.ofm.mvb.monitors import MVBMonitor
1111
from cocotbext.ofm.mfb.monitors import MFBMonitor
12+
from cocotbext.ofm.mvb.drivers import MVBDriver
13+
from cocotbext.ofm.mfb.drivers import MFBDriver
1214

1315

1416
class ThroughputProbeInterface(ProbeInterface):
@@ -35,12 +37,33 @@ class ThroughputProbeMvbInterface(ThroughputProbeInterface):
3537
"item_cnt" : "item_cnt"
3638
}
3739

38-
def __init__(self, monitor: MVBMonitor):
39-
super().__init__(monitor)
40+
def __init__(self, agent: MVBMonitor | MVBDriver):
41+
super().__init__(agent)
42+
43+
@property
44+
def in_reset(self):
45+
"""
46+
Overrides 'in_reset' in interface_dict.
47+
48+
Implements different behavior of in_reset based on
49+
the type of the agent.
50+
"""
51+
in_reset = self.interface_dict.get("in_reset", None)
52+
53+
# check if in_reset has a valid translation in
54+
# the interface dict and if the agent has a
55+
# variable with this name. If yes, return
56+
# it's value
57+
if in_reset is not None:
58+
if hasattr(self._agent, in_reset):
59+
return getattr(self._agent, in_reset)
60+
61+
# if no, return False as default
62+
return False
4063

4164

4265
class ThroughputProbeMfbInterface(ThroughputProbeInterface):
43-
"""Throughput probe interface for the MFB monitor."""
66+
"""Throughput probe interface for the MFB monitor or driver."""
4467
interface_dict = {
4568
"clock" : "clock",
4669
"in_reset" : "in_reset",
@@ -49,12 +72,34 @@ class ThroughputProbeMfbInterface(ThroughputProbeInterface):
4972
"item_cnt" : "item_cnt"
5073
}
5174

52-
def __init__(self, monitor: MFBMonitor):
53-
super().__init__(monitor)
75+
def __init__(self, agent: MFBMonitor | MFBDriver):
76+
super().__init__(agent)
77+
78+
@property
79+
def in_reset(self):
80+
"""
81+
Overrides 'in_reset' in interface_dict.
82+
83+
Implements different behavior of in_reset based on
84+
the type of the agent.
85+
"""
86+
in_reset = self.interface_dict.get("in_reset", None)
87+
88+
if in_reset is not None:
89+
if hasattr(self._agent, in_reset):
90+
return getattr(self._agent, in_reset)
91+
92+
return False
5493

5594
@property
5695
def items(self):
57-
return self._monitor._regions * self._monitor._region_items
96+
"""
97+
Overrides 'items' in interface_dict.
98+
99+
Calculates number of items from number of regions
100+
and items per region on the connected MFB bus.
101+
"""
102+
return self._agent._regions * self._agent._region_items
58103

59104

60105
class ThroughputProbe(Probe):

0 commit comments

Comments
 (0)