Skip to content

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