rtl/gpu.sv — GPU / AIPU Vector ALU (v1)
A 16-lane parallel INT32 vector ALU — the minimum-viable implementation behind the routing slot that had been reserved-but-unwired since v3. Mirrors npu.sv's IDLE/LOAD/COMPUTE/OUTPUT state-machine shape for consistency, but operates on plain INT32 vectors rather than an INT8/INT16 MAC array.
Where it fits
Instantiated once by base_die_top.sv as gpu_inst — directly on the base die, no UCIe chiplet crossing for this minimum-viable version (unlike the NPU's dedicated link). Dispatched through the same noc_router as CPU/NPU, via custom-0 opcode 0x57.
Ports
| Direction | Name | Width | Description |
|---|---|---|---|
| in | clk, rst_n |
1, 1 | Clock / reset |
| in | gpu_valid |
1 | Dispatch instruction valid |
| out | gpu_ready |
1 | 1 only in IDLE |
| in | gpu_instruction |
32 | Encoded GPU instruction (opcode 0x57) |
| out | result_valid |
1 | High during OUTPUT_ST |
| out | result_index |
4 | Current output lane (0–15) |
| out | result_data |
32 | result[result_index], INT32 signed |
| in | result_ready |
1 | Backpressure — hold lane when low |
Instruction encoding (opcode 0x57, custom-0)
[6:0] = 0x57
[8:7] = cmd : 00=LOAD_A 01=LOAD_B 10=COMPUTE 11=NOP
[12:9] = lane (4-bit, LOAD_A/LOAD_B)
[14:13] = alu_op : 00=ADD 01=SUB 10=AND 11=XOR (COMPUTE)
[31:15] = data (17-bit signed, sign-extended to 32; LOAD_A/LOAD_B)
data must stay within [-65536, 65535] to round-trip exactly through the 17-bit field — a real hardware limit of the encoding, the same tradeoff npu.sv makes for its narrower instruction-carried operands.
Functionality
Operand vectors: vec_a[0:15], vec_b[0:15], both INT32, persist across instructions — reloading only vec_b (say) keeps vec_a's prior values, letting a fixed operand be reused across multiple COMPUTEs without reloading it.
Compute: all 16 lanes' alu_out[i] = vec_a[i] <alu_op> vec_b[i] combinationally every cycle; COMPUTE_ST latches all 16 into result[] in one cycle.
FSM: IDLE → LOAD_ST → IDLE (one lane write) or IDLE → COMPUTE_ST → OUTPUT_ST → IDLE (16-lane compute + 16-cycle streamed output), identical shape to npu.sv.
Example — random signed ADD, then reload only one operand
# tb/tb_server_dispatch_unit.py: GPUDriver / GPUMonitor / GPUScoreboard
A = [rng.randint(-60000, 60000) for _ in range(16)]
B1 = [rng.randint(-60000, 60000) for _ in range(16)]
await drv.load_vec_a(A)
await drv.load_vec_b(B1)
await drv.compute(GPU_ALU_ADD)
result1 = await mon.collect() # result1[i] == A[i] + B1[i]
B2 = [rng.randint(-60000, 60000) for _ in range(16)]
await drv.load_vec_b(B2) # vec_a NOT reloaded — persists
await drv.compute(GPU_ALU_ADD)
result2 = await mon.collect() # result2[i] == A[i] + B2[i]
This is run_gpu_random_reload in tb_server_dispatch_unit.md.
Related
- gpu.md (docs) — full state-machine diagram and wiring notes
- npu.md — the sibling co-processor this file's FSM shape mirrors
- base_die_top.md — instantiation site (no UCIe crossing, unlike NPU)