tb/tb_video_stream_workload.py — Video Streaming Workload Testbench
New. Turns a reported real-world symptom — a phone client's local video playback buffering at a 256 kbps link rate — into a concrete CPU+NPU chiplet workload test. Rather than re-deriving low-level drivers, it imports and reuses tb_server_dispatch_unit.py's NPUDriver/NPUMonitor/NPUScoreboard/send_instruction/reset_dut/generate_clock/dram_model — this file is purely new test scenarios built on the existing verified building blocks, added as its own MODULE rather than edited into either existing suite.
What it models
The scenario: the CPU chiplet keeps doing its own per-chunk stream bookkeeping while the NPU chiplet — on its own UCIe link, independent of CPU stalls (see BUG-007) — runs the per-chunk tile filter that the still-unimplemented "wireless peripheral driver" backlog item (docs/status.md) will eventually stream out to the display. The test asks: can the CPU+NPU chiplet pair sustain the throughput a 256 kbps stream demands, including in the worst case (zero inter-arrival gap), without falling behind ("buffering")?
Bitrate → cycle-budget mapping: one NPU COMPUTE consumes one 16-value (COLS) INT8 activation vector — the natural "chunk" unit for the NPU's native 16×16 array (no im2col/tiling layer exists yet — see docs/todo.md "Convolution support in NPU"). At 256 kbps, a 16-byte chunk affords 16 × 8 × 1e9 / 256000 = 500,000 ns = 50,000 cycles at 100 MHz before the stream would start backing up.
Test list
| # | Test | What it proves |
|---|---|---|
| 1 | test_video_stream_sustained_throughput |
16 stream chunks back-to-back (zero inter-arrival gap — the heavy-load/burst case, strictly harder than a paced 256 kbps trickle), CPU bumping a per-chunk counter between each. Checks every chunk's NPU filter result against a Python scoreboard, every chunk's CPU counter increments (no starvation from interleaved NPU dispatch on the shared NoC router), and that cumulative latency for all 16 chunks stays within the 256 kbps-derived budget |
| 2 | test_cpu_progresses_while_npu_computes |
Starts NPU result collection as a background cocotb task without awaiting it, keeps dispatching CPU work while the NPU is still computing/draining, and asserts the CPU's dispatched ops completed before the NPU finished — proving genuine chiplet-level concurrency, not the CPU stalled behind the NPU on a shared pipeline |
Result when run: TESTS=2 PASS=2 FAIL=0. Headroom reported: 547.8× (16 chunks took 1,460 actual cycles vs. an 800,000-cycle budget) — this pipeline is nowhere near compute-bound at 256 kbps; if a real device buffers at that bitrate, the bottleneck is elsewhere (network stack, the not-yet-implemented wireless peripheral driver, or BUG-006's open MOESI issue), not CPU/NPU throughput.
Key additions (beyond what's imported)
| Component | Purpose |
|---|---|
addi(rd, rs1, imm) |
Local RV32I I-type ADDI encoder — used for the per-chunk CPU counter bump, since this file doesn't need a full .asm fixture |
CHUNK_ARRIVAL_NS / DEADLINE_CYCLES |
The 256 kbps → cycle-budget derivation described above |
cpu_bump_counter(dut) |
Dispatches one addi s1, s1, 1 through the NoC router |
read_counter(dut) |
Reads x9 (s1) via dut.cpu_chiplet_inst.cpu_core_inst.rf_unit.rf[9] |
How to run
docker run --rm -e MODULE=tb_video_stream_workload -e ASM_TEST=cpu_peak_tests.asm \
-v "$(pwd)":/usr/src/server_dispatch riscv_sim:latest
Example — proving concurrency, not just correctness
await drv.compute()
monitor_task = cocotb.start_soon(mon.collect()) # NPU still computing — don't await
for _ in range(5):
await cpu_bump_counter(dut) # CPU keeps working independently
npu_done_early = monitor_task.done() # snapshot: NPU not finished yet
for _ in range(10):
await RisingEdge(dut.clk) # let riscv_core's 5-stage pipeline drain
counter_mid = read_counter(dut)
assert counter_mid == 5 # CPU wasn't starved by the in-flight NPU compute
assert not npu_done_early # and the NPU really was still busy when we checked
Related
- tb_server_dispatch_unit.md — the driver/monitor/scoreboard classes this file imports rather than duplicates
- rtl/npu.md, rtl/npu_chiplet_top.md — the compute path under load
- rtl/cpu_chiplet_top.md — the CPU chiplet whose independence from NPU compute this proves
- chiplets.md — why CPU and NPU are separate dies in the first place