Scoreboards
Simple Channels
When self.register(...)
is called it will register the monitor against the
scoreboard and queue captured transactions into a dedicated channel (unless
scoreboard=False
is set). If no other parameters are provided then the
scoreboard will create a simple channel.
A simple channel will expect all transactions submitted to the reference queue to appear in the same order in the monitor's queue, and whenever a mismatch is detected it will flag an error.
from forastero import BaseBench, IORole
from .stream import StreamIO, StreamMonitor
class Testbench(BaseBench):
def __init__(self, dut) -> None:
super().__init__(dut, clk=dut.i_clk, rst=dut.i_rst)
stream_io = StreamIO(dut, "stream", IORole.INITIATOR)
self.register("stream_mon",
StreamMonitor(self, stream_io, self.clk, self.rst))
def model_stream(self):
self.scoreboard.channels["stream_mon"].push_reference(StreamTransaction(...))
Match Window Channels
Sometimes it is necessary to relax the precise ordering matches between captured and reference transactions - for example if a black-box component is performing an arbitration between different upstream initiators onto a single downstream port then the testbench may want to just check for transaction consistency without modelling the exact ordering function implemented by the hardware.
To support this, the scoreboard offers matching windows - allowing a captured
transaction to match against any of the first N
entries of the reference queue.
This can be configured using the scoreboard_match_window
argument when registering
a monitor.
In the example shown below the matching window is set to a value of 4, this
means that each captured transaction can be matched against entries 0
, 1
,
2
, or 3
of the reference queue.
from forastero import BaseBench, IORole
from .stream import StreamIO, StreamMonitor
class Testbench(BaseBench):
def __init__(self, dut) -> None:
super().__init__(dut, clk=dut.i_clk, rst=dut.i_rst)
ds_io = dsIO(dut, "ds", IORole.INITIATOR)
self.register("ds_mon",
StreamMonitor(self, ds_io, self.clk, self.rst),
scoreboard_match_window=4)
def model_stream(self):
self.scoreboard.channels["ds_mon"].push_reference(StreamTransaction(...))
Funnel Channels
Another option is to register the monitor with multiple named scoreboard queues, thus creating a "funnel" channel. In this case each named queue of the channel must maintain order, but the scoreboard can pop entries from different queus in any order - this is great for blackbox verification of components like arbiters where the decision on which transaction is going to be taken may be influenced by many factors internal to the device.
from forastero import BaseBench, IORole
from .stream import StreamIO, StreamMonitor
class Testbench(BaseBench):
def __init__(self, dut) -> None:
super().__init__(dut, clk=dut.i_clk, rst=dut.i_rst)
stream_io = StreamIO(dut, "stream", IORole.INITIATOR)
self.register("arb_output_mon",
StreamMonitor(self, stream_io, self.clk, self.rst),
scoreboard_queues=("a", "b", "c"))
def model_arbiter_src_b(self):
self.scoreboard.channels["arb_output_mon"].push_reference(
"b", StreamTransaction(...)
)
Scoreboard Channel Timeouts
While each registered testcase can provide a timeout, this in many cases may be
set at a very high time to cope with complex, long running testcases. To provide
a finer granularity of timeout control, timeouts may be configured specifically
to scoreboard channels that specify how long it is acceptable for a transaction
to sit at the front of the monitor's queue before the scoreboard matches it to
a reference transaction. There are two key parameters to self.register(...)
:
-
scoreboard_timeout_ns
- the maximum acceptable age a transaction may be at the front of the monitor's channel. The age is determined by substracting the transaction'stimestamp
field (a default property ofBaseTransaction
) from the current simulation time. -
scoreboard_polling_ns
- the frequency with which to check the front of the scoreboard's monitor queue, this defaults to100 ns
.
Due to the interaction of the polling timeout and polling period, transactions may live longer than the timeout in certain cases but this is bounded by a maximum delay of one polling interval.
from forastero import BaseBench, IORole
from .stream import StreamIO, StreamMonitor
class Testbench(BaseBench):
def __init__(self, dut) -> None:
super().__init__(dut, clk=dut.i_clk, rst=dut.i_rst)
stream_io = StreamIO(dut, "stream", IORole.INITIATOR)
self.register("stream_mon",
StreamMonitor(self, stream_io, self.clk, self.rst),
scoreboard_timeout_ns=10)
forastero.scoreboard.Scoreboard
Scoreboard for comparing captured and reference transactions. The scoreboard is divided into different 'channels', one per monitor, that handle the comparison of captured and reference objects and reporting of any differences.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
tb
|
Reference to the testbench |
required | |
fail_fast
|
bool
|
Stop the test as soon as a mismatch is reported (default: False) |
False
|
postmortem
|
bool
|
Enter debug on a mismatch (default: False) |
False
|
result
property
If no mismatches recorded return True, else return False
attach(monitor, filter_fn=None, queues=None, timeout_ns=None, polling_ns=100, match_window=1)
Attach a monitor to the scoreboard, creating and scheduling a new channel in the process.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
monitor
|
BaseMonitor
|
The monitor to attach |
required |
queues
|
list[str] | tuple[str] | None
|
List of reference queue names, this causes a funnel type scoreboard channel to be used |
None
|
filter_fn
|
Callable | None
|
A filter function that can either drop or modify items recorded by the monitor prior to scoreboarding |
None
|
timeout_ns
|
int | None
|
Optional timeout to allow for a object sat at the front of the monitor queue to remain unmatched (in nanoseconds, a value of None disables the timeout mechanism) |
None
|
polling_ns
|
int
|
How frequently to poll to check for unmatched items stuck in the monitor queue in nanoseconds (defaults to 100 ns) |
100
|
match_window
|
int
|
Where precise ordering of expected transactions is not known, a positive integer matching window can be used to match any of the next N transactions in the reference queue (where N is set by match_window) |
1
|
drain()
async
Block until all chains of the scoreboard have been drained