Skip to content

GPU / AIPU v1 — Vector ALU (minimum viable)

The GPU/AIPU (rtl/gpu.sv) is a 16-lane parallel INT32 vector ALU — the first working implementation behind the routing slot that was reserved (but unwired) since v3. It lives directly on the base die (no UCIe chiplet crossing for this minimum-viable version, unlike the NPU) and is dispatched through the same NoC router as the CPU and NPU, via RISC-V custom-0 opcode 0x57.


What It Does

Operation Instruction Result
Load operand A LOAD_A vec_a[lane] = data
Load operand B LOAD_B vec_b[lane] = data
Element-wise compute COMPUTE result[lane] = vec_a[lane] <alu_op> vec_b[lane] for all 16 lanes
No-op NOP

alu_op selects one of ADD, SUB, AND, XOR — the minimum viable op set for a vector ALU; more ops are a natural follow-up (see docs/todo.md).


Instruction Encoding (opcode 0x57, RISC-V custom-0)

 Bit: 31        15 14  13 12    9 8  7 6      0
      +------------+------+------+----+--------+
      |    data    |alu_op| lane | cmd| 0x57   |
      |   [31:15]  |[14:13]|[12:9]|[8:7]| [6:0] |
      +------------+------+------+----+--------+
cmd [8:7] Mnemonic Fields used Action
2'b00 LOAD_A lane[12:9], data[31:15] vec_a[lane] ← data
2'b01 LOAD_B lane[12:9], data[31:15] vec_b[lane] ← data
2'b10 COMPUTE alu_op[14:13] result[i] ← vec_a[i] <alu_op> vec_b[i], all 16 lanes
2'b11 NOP idle
alu_op [14:13] Op
2'b00 ADD
2'b01 SUB
2'b10 AND
2'b11 XOR

data is a 17-bit signed immediate, sign-extended to the 32-bit lane register — the same tradeoff npu.sv makes (narrower instruction-carried operand, wider internal datapath). Values must stay within [-65536, 65535] to round-trip exactly; this is a real hardware limit of the encoding, not just a test constraint.


State Machine

Mirrors npu.sv's IDLE/LOAD/COMPUTE/OUTPUT shape:

        gpu_valid + cmd=00/01
   ┌───────────────────────────► LOAD_ST ──────────────────────► IDLE
   │                                                          (1 cycle)
IDLE      gpu_valid + cmd=10
   ├───────────────────────────► COMPUTE_ST ─────────────────────┐
   │                              (result[i] = alu_out[i], all 16) │
   │                                                                ▼
   │     gpu_valid + cmd=11                                  OUTPUT_ST
   └───────────────────────────► IDLE (NOP)          out_cnt==15 + ready
                                                            └──────────► IDLE
State Cycles Action
IDLE Accept instruction, latch fields, assert gpu_ready
LOAD_ST 1 vec_a[lat_lane] ← lat_data or vec_b[lat_lane] ← lat_data (per lat_cmd)
COMPUTE_ST 1 result[i] ← alu_out[i] for all 16 lanes (combinational per-lane ALU)
OUTPUT_ST 16 Stream result[0..15] one per cycle with backpressure

vec_a and vec_b persist across instructions — reloading only one operand (e.g. vec_b for a new RHS) keeps the other's values, letting a fixed operand be reused across multiple COMPUTEs without reloading it.


Result Bus

Signal Direction Description
gpu_result_valid out High during OUTPUT_ST
gpu_result_index [3:0] out Current lane index (0–15)
gpu_result_data [31:0] out result[gpu_result_index] — INT32 signed
gpu_result_ready in Backpressure — hold gpu_result_index when low

Exposed at system_top, same pattern as the NPU's npu_result_* bus.


Wiring

Unlike the NPU (which crosses a UCIe link to npu_chiplet_top), the GPU is instantiated directly in base_die_top.sv, wired straight to the restored GPU port on noc_router.sv — no chiplet crossing for this minimum-viable version. system_top.sv forwards gpu_result_* unchanged, matching how it already forwards npu_result_*.


Verification

tb/tb_server_dispatch_unit.py: run_gpu_vector_add, run_gpu_vector_sub, run_gpu_vector_bitwise (AND then XOR reusing loaded operands), run_gpu_random_reload (random signed ADD, then reload only vec_b and confirm vec_a persisted). See docs/verification.md.