tb/tb_server_dispatch_unit.py — Regression Testbench
The main cocotb regression suite for system_top — 15 tests covering the NPU, GPU, and full RISC-V CPU programs. This is the Makefile default (MODULE ?= tb_server_dispatch_unit), and the file every other testbench in this repo imports shared helpers from (generate_clock, dram_model, reset_dut, send_instruction, NPUDriver/NPUMonitor/NPUScoreboard, etc.) rather than re-deriving them.
What it drives
system_top's top-level ports directly (clk/rst_n, net_valid/net_ready/net_instruction, npu_result_*, gpu_result_*, npu_dma_*, dram_*, halt) — see rtl/system_top.md for the full port list.
Shared infrastructure (reused by other testbenches)
| Component | Purpose |
|---|---|
generate_clock(dut) |
100 MHz clock (10ns period — Timer(5, unit="ns") each half) |
dram_model(dut) |
1-cycle-ack DRAM stub; dict-backed memory |
reset_dut(dut, cycles=6) |
Asserts rst_n=0, zeroes all inputs and the CPU register file, releases reset |
send_instruction(dut, raw) |
Drives one instruction through the net_valid/net_ready/net_instruction handshake — the single primitive every test (and every other tb file) dispatches instructions with |
NPUDriver / NPUMonitor / NPUScoreboard |
Encode+send NPU instructions (load_weights handles the ping-pong SWAP protocol), collect the 16-lane result stream, and predict expected values in Python |
GPUDriver / GPUMonitor / GPUScoreboard |
Same shape, for the 16-lane INT32 vector ALU |
npu_instr_int8/npu_instr_int16/gpu_instr |
Raw instruction-word encoders matching rtl/npu.md and rtl/gpu.md's bit layouts |
Test list
| # | Test | Exercises |
|---|---|---|
| 1 | run_npu_gemv |
Basic 16×16 GEMV, W[r][c]=r+1, A=1, with ping-pong SWAP |
| 2 | run_npu_identity |
Identity weight matrix — output must equal A |
| 3 | run_npu_all_ones |
W=1, A=1 → every lane = 16 |
| 4 | run_npu_random_signed |
Random signed INT8, Python scoreboard cross-check |
| 5 | run_npu_negative |
Negative weights — signed arithmetic coverage |
| 6 | run_npu_max_values |
W=127, A=127 → max INT8 product, no overflow |
| 7 | run_npu_zero_act |
A=0 → all outputs 0 regardless of weights |
| 8 | run_npu_tiled_gemm |
K=32 via two tiles, COMPUTE then COMPUTE_ACC |
| 9 | run_npu_int16_mode |
INT16 mixed-precision (SET_PREC, data at [31:18]) |
| 10 | run_npu_ping_pong |
Ping-pong bank isolation across two independent inferences |
| 11 | run_gpu_vector_add |
GPU: A+B element-wise, 16 INT32 lanes |
| 12 | run_gpu_vector_sub |
GPU: A-B, including negative results |
| 13 | run_gpu_vector_bitwise |
GPU: AND then XOR, reusing loaded operands |
| 14 | run_gpu_random_reload |
GPU: random ADD, reload only vec_b — vec_a must persist |
| 15 | run_riscv_program |
Full RV32IM program (parsed from an .asm fixture): MUL/DIV chains, branches, loads/stores, register assertions |
How to run
# Default program (cpu_peak_tests.asm)
docker run --rm -e MODULE=tb_server_dispatch_unit -e ASM_TEST=cpu_peak_tests.asm \
-v "$(pwd)":/usr/src/server_dispatch riscv_sim:latest
# A single test
COCOTB_TESTCASE=run_npu_gemv source scripts/run
# Without Docker
make results.xml
Example — the send_instruction primitive every test builds on
async def send_instruction(dut, raw):
dut.net_valid.value = 1
dut.net_instruction.value = raw
await RisingEdge(dut.clk)
while not int(dut.net_ready.value):
await RisingEdge(dut.clk)
dut.net_valid.value = 0
Every instruction — CPU, GPU, or NPU-bound — goes through exactly this handshake; the noc_router decodes the opcode and routes it, so the testbench never needs to know or care which engine an instruction targets.
Related
- tb/index.md — testbench reference index
- verification.md — full current pass/fail table
- rtl/npu.md, rtl/gpu.md — instruction encodings this file's driver classes implement
- BUG-001, BUG-002, BUG-004, BUG-005 — bugs found via this suite