Skip to content

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.

tb/testbench.py
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.

tb/testbench.py
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.

tb/testbench.py
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's timestamp field (a default property of BaseTransaction) from the current simulation time.

  • scoreboard_polling_ns - the frequency with which to check the front of the scoreboard's monitor queue, this defaults to 100 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.

tb/testbench.py
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