|
CocoTB Framework · Verification Infrastructure for RTL Testing GitHub · Documentation Index · MIT License |
wb4_sequence.py¶
WB4Sequence is the traffic axis for Wishbone B4: what transfers a test
performs, independent of who drives them (WB4Master) and when
(FlexRandomizer). It is a builder that produces a list of single-beat
transactions, which you can filter, shuffle, replay and convert to packets.
Why a builder, not parallel lists¶
The older APBSequence shape carries a list per field and walks them with
cycling iterators. WB4Sequence follows AXI4Sequence instead: methods that
append transfers, so a workload reads as what it does. It is also smaller
than either, because Wishbone has less to describe. A B4 transfer is always
one word -- the registered-feedback bursts of chapter 4 are a HINT carried on
each transfer, not a multi-beat transaction, so there is no burst length to
describe. It has no protection bits, so there is no prot. And the
termination status is the slave's answer, never part of the stimulus.
Construction¶
from CocoTBFramework.components.wb4.wb4_sequence import WB4Sequence
seq = WB4Sequence("smoke", addr_width=32, data_width=32, seed=1)
sel_width defaults to data_width // 8. Passing seed makes the sequence
reproducible, and the sequence owns its own random.Random, so building one
never perturbs any other randomisation in the test.
Primitives¶
| Method | Effect |
|---|---|
add_write(addr, data, sel=None, tag="") |
One write; sel defaults to every byte |
add_read(addr, tag="") |
One read; reads carry an all-ones select |
Addresses and data are masked to the configured widths, so a value too wide for the bus is truncated where you can see it rather than inside a driver.
Patterns¶
| Method | Shape |
|---|---|
add_block(base, count, we=, stride=, data=) |
A run of transfers one word apart; data cycles |
add_readback_pairs(base, count) |
Write/read pairs on the same address, so each read has an unambiguous expected value |
add_strobed_writes(base, count) |
Full write, partial write, read back: makes a dropped SEL visible |
add_random_workload(count, ...) |
A random mix, optionally steered at address windows |
Address windows are the Wishbone-flavoured part¶
A B4 slave may answer ERR or RTY, and a test usually provokes those by
aiming at ranges the device under test decodes that way. windows takes
(lo, hi, probability) triples:
seq.add_random_workload(
200, addr_lo=0, addr_hi=0xD000, write_frac=0.6,
windows=[(0xE000, 0xEFFF, 0.1), # the DUT's ERR range
(0xF000, 0xFFFF, 0.1)]) # the DUT's RTY range
Each transfer drawn from a window is tagged "rand:w0", "rand:w1" and so
on, so a scoreboard can tell which range an address came from without
re-deriving the decode. Probabilities are taken in order and must sum to at
most 1.0; the remainder is drawn from [addr_lo, addr_hi).
unique_addrs=True refuses to repeat an address within the call, which is
what a test wants when several transfers are in flight and a per-address
model would otherwise race itself.
Burst hints¶
assign_burst_hints() lays a registered-feedback pattern over the transfers
already built: runs of INCR closed by one EOB, with classic transfers
between them, one BTE held for the length of each run.
seq.add_random_workload(200, addr_hi=0xD000)
seq.assign_burst_hints(burst_frac=0.4, min_len=2, max_len=5)
| Argument | Effect |
|---|---|
burst_frac |
Chance a classic transfer starts a burst (default 0.4) |
min_len, max_len |
Burst length in transfers, inclusive (default 2..5) |
bte_choices |
Which burst types to draw from (default all four) |
A burst that would run off the end of the sequence is closed on the last
transfer, so a sequence never ends mid-burst. clear_burst_hints() puts
every transfer back to CLASSIC/LINEAR, which is what a DUT built without the
hints must show on its bus whatever it was handed -- that pair is how a test
checks a USE_BURST_HINTS parameter both ways.
The hints are advisory. Nothing in the framework or the RTL library acts on
one, so the pattern only has to be varied and self-describing. What it
proves is that each hint arrives WITH its own transfer rather than a
neighbour's, which is why every run holds one BTE and the lengths vary.
Shaping and output¶
| Method | Effect |
|---|---|
filter(predicate) |
A new sequence of the matching transfers; the original is untouched |
shuffle() |
In place, using the sequence's own generator, so a seeded shuffle repeats |
reset() |
Drops every transfer and re-seeds, so the same calls rebuild an identical sequence |
to_packets(master=None) |
WB4Packet objects ready for master.send() |
clear_burst_hints() |
Every transfer back to CLASSIC/LINEAR |
stats |
total, writes, reads, unique_addrs, partial_writes, burst_transfers, burst_ends |
to_packets(master=...) compares the sequence's widths against the BFM's and
raises on a mismatch, so a sequence built for the wrong bus is caught rather
than silently truncating an address.
Full example¶
seq = WB4Sequence("mixed", addr_width=32, data_width=32, seed=7)
seq.add_readback_pairs(0x1000, 8)
seq.add_strobed_writes(0x2000, 4)
seq.add_random_workload(100, addr_hi=0xD000,
windows=[(0xE000, 0xEFFF, 0.1)])
seq.assign_burst_hints()
for pkt in seq.to_packets(master):
await master.send(pkt)
await master.wait_idle()