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.