Tutorial: verify a video-packet endianness converter¶
This tutorial builds the stream_pipeline example shipped with the repository.
The RTL is a one-entry elastic stage that changes byte order only in VIDEO
payload beats, and the tutorial follows its complete verification path.
The observable contract¶
The component has one input and one output Intel Avalon-ST Video packet stream. Its contract is:
- preserve packet order and packet type;
- preserve every packet identifier beat;
- preserve CONTROL and USER packets exactly;
- reverse the valid bytes in each VIDEO payload beat;
- retain valid output while the sink applies backpressure;
- discard in-flight state on reset.
The default interface carries four 8-bit symbols per beat. A complete VIDEO
payload beat therefore changes from [0x10, 0x11, 0x12, 0x13] to
[0x13, 0x12, 0x11, 0x10]. If the final beat contains only two valid bytes,
[0x20, 0x21] becomes [0x21, 0x20]; empty lanes remain empty.
The verification path is:
VIPSequence -> source driver -> endianness converter -> sink monitor
| |
source monitor --------------------> scoreboard
|
behavior model
Share one configuration¶
config.py
declares the 8-bit symbol width, four symbols per beat, and runtime-only frame
dimensions. The runner passes cfg.to_parameters() to the RTL compiler and
serialises the complete object for cocotb. The running test restores it with
load_runtime_config(TestConfig).
The frame contains 21 bytes, so the test covers both complete beats and a final partial beat. Keeping these values in one object prevents the testbench from interpreting the stream differently from the compiled RTL.
Derive the interface layout once¶
layout.py
converts the configuration into one VideoFormat and one FrameSize. The agent,
codec and test share those objects. No later layer recalculates bus width or
frame geometry.
Model the byte-order conversion as a pure operation¶
The functional model receives a VIDEO payload without its identifier beat. It splits that payload into bus-width groups and reverses each group independently:
class StreamFunctionalModel:
def __init__(self, bytes_per_beat):
self.bytes_per_beat = bytes_per_beat
def process_payload(self, payload):
result = list(payload)
for start in range(0, len(result), self.bytes_per_beat):
stop = min(start + self.bytes_per_beat, len(result))
result[start:stop] = reversed(result[start:stop])
return result
The last slice can be shorter than one beat, which models empty correctly
without adding padding to the expected packet. The behavior model wraps the
result in VideoPacketResult.exact() and exposes reset() even though this
component retains no model state.
tests/test_models.py
checks complete and partial groups as ordinary Python, without compiling RTL.
Apply the model only to VIDEO packets¶
scoreboard.py
is the protocol boundary. It queues CONTROL and USER packets unchanged. For a
VIPVideoPacket, it sends only packet.payload to the behavior model and wraps
the transformed bytes in a new VIPVideoPacket expectation. The VIDEO
identifier beat is generated by the packet codec and is never passed through
the endianness model.
The base scoreboard owns output ordering, comparison, sticky failures, reset epochs and completion. The custom scoreboard owns only the mapping specific to this component contract.
Compose and connect in the environment¶
env.py
creates the clock, buses, VIPAgent, behavior model and scoreboard. Its
connect_phase() wires both analysis paths:
self.data_agent.source_analysis_port.connect(self.scoreboard.data_in_export)
self.data_agent.sink_analysis_port.connect(self.scoreboard.data_out_export)
Semantic helpers keep tests independent from signal names: reset(),
send_packets(), drain() and stop_tasks().
State the scenario in the test¶
test_pyuvm.py
contains a base lifecycle and one contract-focused scenario. The scenario sends
CONTROL, USER and VIDEO packets as one ordered sequence. Distinct USER payload
bytes catch accidental conversion of a non-video packet, while the generated
frame exercises the VIDEO conversion.
The finally block always cancels BFMs and the clock. A failed comparison must
not leave simulator tasks running.
Launch through the runner¶
run_test.py
selects the source directory, top-level module, test module and resolved
configuration. It contains no stimulus and no expected data.
After the example passes, useful extensions are:
- enable source and sink timing randomisation;
- send several VIDEO packets between CONTROL packets;
- assert reset while a packet is pending;
- parameterise a different number of bytes per beat;
- add an independently modelled transformation after the byte-order converter.