Intuity / forastero

Making cocotb testbenches that bit easier
http://forastero.intuity.io/
Apache License 2.0
24 stars 1 forks source link

Proposal for sequences and interleavers #9

Open Intuity opened 7 months ago

Intuity commented 7 months ago

Introduction

Forastero currently provides drivers, monitors, and scoreboards but provides no infrastructure for generating sequences of transactions to push through drivers and monitors.

The proposal is to add two features to the library:

Any number of sequences can interface with an arbiter, this creates an issue where a sequence may expect to have sole control of a DUT but ends up fighting with other sequences for control. The proposed solution for this is to introduce the concept of locking.

A sequence may request to lock any number of drivers and monitors to gain the sole ability to enqueue/dequeue transactions. The sequence may then release locks as it completes various tasks, allowing other sequences to be interleaved.

One issue with locking is that it can lead to deadlock where sequence A first locks driver X then attempts to lock Y, while sequence B first locks driver Y then attempts to lock driver X. The proposed solution to this is to only allow a sequence to request locks if it holds no outstanding locks - i.e. in a single request a sequence may lock any number of drivers/monitors, it can then release these locks at any level of granularity, and may then only request more locks once its number of held locks reaches zero.

The arbiter will be responsible for servicing lock requests and balancing the competing priorities of servicing easy lock requests where few drivers/monitors are locked against difficult lock requests where many drivers/monitors are locked. Not correctly balancing these priorities will either over emphasise or deprioritise difficult lock requests, and hence starve certain sequences.

The diagram below shows how three sequences interface with an arbiter to provide stimulus, in this example:

sequences

Syntax

I would suggest the following syntax for sequences and test cases.

Sequences

# Declare a sequence
@forastero.sequence()
# List the drivers/monitors this test case will use
@forastero.requires("drv_a")
@forastero.requires("drv_b")
@forastero.requires("mon_a")
async def my_sequence(tb: Testbench, drv_a: DriverA, drv_b: DriverB, mon_a: MonitorA, my_option: int=123):
    # Lock some (but not all) of drivers/monitors
    await forastero.lock(drv_a, mon_a)
    # Enqueue transactions into the driver
    drv_a.enqueue(TransactionA(some_value=my_option))
    drv_a.enqueue(TransactionA(...))
    # Release the driver
    forastero.release(drv_a)
    # Wait for a monitor event
    rsp = await mon_a.wait_for(MonitorEvent.CAPTURE)
    # Release monitor
    forastero.release(mon_a)
    # Request all the locks
    await forastero.lock(drv_a, drv_b, mon_a)
    # ...do other stuff...

Testcases

BaseBench will need to be extended to have a built in arbiter for sequences, then test cases can attach sequences onto it:

from .sequences import sequence_a, sequence_b

@Testbench.testcase()
async def my_testcase(tb):
    # Note that there will need to be a wrapper layer hidden here that fills in the drivers/monitors in the `requires` lists
    # before the sequence is started
    tb.add_sequence(sequence_a(opt_a=123, opt_b=234))
    tb.add_sequence(sequence_b(opt_c=453, opt_d=465))
    # Possibly there should be syntax to wait for the sequence to go idle?
    await tb.wait_sequencer_idle()
    # ...then queue up more...?

Compact Wait Conditions

One chunk of code that seems to be repeated a lot is waiting for a given condition to be reached across multiple drivers, monitors, internal probes, etc. For example, we might want to wait for a given transaction to be submitted into the DUT and then the internal FSM state to reach a particular value (i.e. leaving IDLE). It would be nice to have a compact syntax for describing this that the arbiter can then track and satisfy:

@forastero.sequence()
@forastero.requires("drv_a")
@forastero.requires("drv_b")
@forastero.requires("mon_a")
async def my_sequence(tb: Testbench, drv_a: DriverA, drv_b: DriverB, mon_a: MonitorA, **kwds):
    await forastero.lock(drv_a, drv_b, mon_a)
    drv_a.enqueue(tr0 := SomeTransaction())
    await forastero.wait_for(
        drv_a.driven(tr0)
    ).and_then(
        tb.dut.state_q.value == FsmState.IDLE
    ).and_then(
        ...
    )

NOTE The syntax here is entirely up for debate, but the concept of a concise expression that's checked on various conditions is the key - a bit like SVA implication operators I guess? In essence I'm trying to avoid lots of repetitions of:

while True:
    await RisingEdge(tb.clk)
    if tb.dut.state_q.value == FsmState.IDLE:
        break
Kotarski commented 7 months ago

Sequences

Alternative (not necessarily exclusive) syntax with context managers - nice thing is that it could prevent a lock not getting released nicely.

# Declare a sequence
@forastero.sequence()
# List the drivers/monitors this test case will use
@forastero.requires("drv_a")
@forastero.requires("drv_b")
@forastero.requires("mon_a")
async def my_sequence(tb: Testbench, drv_a: DriverA, drv_b: DriverB, mon_a: MonitorA, my_option: int=123):
    # Lock some (but not all) of drivers/monitors
    async with forastero.lock(drv_a, mon_a) as (drv_lk_a, mon_lk_a):
        # Enqueue transactions into the driver
        drv_a.enqueue(TransactionA(some_value=my_option))
        drv_a.enqueue(TransactionA(...))
        # Release the driver early
        drv_lk_a.release()
        # Wait for a monitor event
        rsp = await mon_a.wait_for(MonitorEvent.CAPTURE)
        # Release monitor automatically on context exit
    # Request all the locks
    async with forastero.lock(drv_a, drv_b, mon_a):
        # ...do other stuff...
        # Release all locks on context exit

Compact Wait Conditions

I may be missing something but isn't this: https://docs.python.org/3/library/asyncio-task.html#asyncio.gather or https://docs.python.org/3/library/asyncio-task.html#asyncio.wait ?

Intuity commented 7 months ago

A few more ideas that have arisen: