code wiki / _hdl_build / rv64im_min_gpu.nx
rv64im_min_gpu.nx source
↩ module page · 280 lines · 14440 B
1// rv64im_min_gpu.nx -- a SOVEREIGN GPU-class command-submission controller device model (the 4th
2// device class for the driver-from-spec arc, toward the GPU/CUDA/DirectX trajectory). Twin of
3// rv64im_min_nvme.nx but for the GPU SUBMISSION model: a command RING in guest RAM, a write-to-
4// submit DOORBELL, a monotonic FENCE/seqno the driver polls for completion, and a RESULTPEEK
5// binding-proof register. It COMPOSES with the sovereign SPIR-V lane: a command packet MUST carry
6// the SPIR-V magic 0x07230203 (a compute-shader dispatch) or the device refuses to execute -- so a
7// GPU driver authored from an op-list spec drives a real SPIR-V-validated dispatch.
8//
9// The device models ONE controller window [0x10007000, 0x10008000) -- free of the virtio (0x10001/
10// 0x10002), nvme (0x10003-0x10005), nndev (0x10005), and mmu (0x10006) windows (additive). KEY
11// behaviour: on a DOORBELL write the device fetches the command packet from RING_BASE (STRUCT_WALK:
12// opcode + spirv_magic + operand + seqno), validates the SPIR-V magic, "executes" the dispatch, bumps
13// FENCE to the submitted seqno, and latches RESULTPEEK = (operand<<16)|seqno.
14//
15// GPU/Vulkan/SPIR-V here is the last-mile interop PROBE + a measuring stick to EXCEED -- the emitted
16// driver + the op-list shapes are Nishi-native (the device identity lives ENTIRELY in the spec; the
17// canonical op-list emitter nx_drv_proto_emit knows zero GPU).
18// Status: SEED. 2026-06-13. Enable + command-ring doorbell dispatch + SPIR-V-magic-gated fence/result.
19// license_tier: ORIGINAL
20
21import "nx_syscalls.nx"
22import "nishi_hdl_primitives.nx"
23
24// ===== MMIO addresses =================================================
25const NX_GPU_BASE: i64 = 0x10007000
26const NX_GPU_END: i64 = 0x10008000
27
28// ===== GPU controller register offsets (all small so the generic op-list emitter reaches them) =====
29// 0x00 ID (RO) identity const
30// 0x04 CTRL (RW) control; EN = bit0
31// 0x08 STATUS (RO) status; READY = bit0 (set when CTRL.EN written 1)
32// 0x10 RING_LO / 0x14 RING_HI (RW) command-ring base address (64-bit split lo/hi)
33// 0x20 DOORBELL (WO) submit -- the kick that runs the dispatch DMA
34// 0x24 FENCE (RO) completion seqno (set to the submitted seqno on dispatch; the driver polls it)
35// 0x28 RESULTPEEK (RO) sovereign-only instrument = (operand<<16)|seqno, the binding-proof word
36const NX_GPU_OFF_ID: i64 = 0x00
37const NX_GPU_OFF_CTRL: i64 = 0x04
38const NX_GPU_OFF_STATUS: i64 = 0x08
39const NX_GPU_OFF_RING_LO: i64 = 0x10
40const NX_GPU_OFF_RING_HI: i64 = 0x14
41const NX_GPU_OFF_DOORBELL: i64 = 0x20
42const NX_GPU_OFF_FENCE: i64 = 0x24
43const NX_GPU_OFF_RESULTPEEK: i64 = 0x28
44
45// ===== Device identity + constants =================================================
46const NX_GPU_ID: i64 = 0x00475055 // RO identity const
47const NX_GPU_CTRL_EN: i64 = 1 // CTRL.EN = bit0
48const NX_GPU_STATUS_READY: i64 = 1 // STATUS.READY = bit0
49const NX_GPU_SPIRV_MAGIC: i64 = 0x07230203 // SPIR-V module magic -- the dispatch must carry it
50
51// ===== Command-packet layout in guest RAM (at RING base) =================================================
52// pkt.opcode @ byte 0 (16-bit; 1 = compute dispatch)
53// pkt.spirv_magic @ byte 4 (32-bit; MUST == 0x07230203 or the device refuses to execute)
54// pkt.operand @ byte 8 (16-bit; the compute input the device transforms)
55// pkt.seqno @ byte 12 (16-bit; the fence value the device signals on completion)
56const NX_GPU_PKT_OFF_OPCODE: i64 = 0
57const NX_GPU_PKT_OFF_MAGIC: i64 = 4
58const NX_GPU_PKT_OFF_OPERAND:i64 = 8
59const NX_GPU_PKT_OFF_SEQNO: i64 = 12
60// ---- compute (DOT8) packet extension (opcode 2): a 4-elem int8 dot product = the matmul primitive,
61// the sovereign GPU-compute / CUDA-moat-escape kernel (V-RAM-005, bit-identical CPU-vs-GPU) ----
62const NX_GPU_OPCODE_ECHO: i64 = 1 // (operand<<16)|seqno binding echo (the base round-trip)
63const NX_GPU_OPCODE_DOT8: i64 = 2 // int8 4-elem dot product -> RESULTPEEK
64const NX_GPU_OPCODE_MATMUL2: i64 = 3 // 2x2 int8 matmul tile (the V-RAM-005 primitive) -> packed RESULTPEEK
65const NX_GPU_PKT_OFF_CSEQ: i64 = 8 // DOT8: seqno @+8 (operand slot reused for the compute opcode)
66const NX_GPU_PKT_OFF_A: i64 = 16 // DOT8: 4 int8 a-values @+16..19
67const NX_GPU_PKT_OFF_B: i64 = 20 // DOT8: 4 int8 b-values @+20..23
68
69// ===== Verdicts =================================================
70const NX_GPU_OK: i64 = 0
71const NX_GPU_ADDR_OUT_OF_RANGE: i64 = 1
72
73// ===== Storage (caller allocates an NX_GPU_SLOT_N-i64 backing buffer) =====
74const NX_GPU_SLOT_CTRL: i64 = 0
75const NX_GPU_SLOT_STATUS: i64 = 1
76const NX_GPU_SLOT_RING_LO: i64 = 2
77const NX_GPU_SLOT_RING_HI: i64 = 3
78const NX_GPU_SLOT_FENCE: i64 = 4
79const NX_GPU_SLOT_RESULTPEEK: i64 = 5
80const NX_GPU_SLOT_DBELL: i64 = 6
81const NX_GPU_SLOT_N: i64 = 7
82
83struct NxGpu {
84 storage: *i64 // NX_GPU_SLOT_N i64s
85 valid: i64
86 base: i64 // this instance's MMIO base (0x10007000)
87}
88
89func nx_gpu_reset_storage(storage: *i64) -> i64 {
90 storage[NX_GPU_SLOT_CTRL] = 0
91 storage[NX_GPU_SLOT_STATUS] = 0
92 storage[NX_GPU_SLOT_RING_LO] = 0
93 storage[NX_GPU_SLOT_RING_HI] = 0
94 storage[NX_GPU_SLOT_FENCE] = 0
95 storage[NX_GPU_SLOT_RESULTPEEK] = 0
96 storage[NX_GPU_SLOT_DBELL] = 0
97 return NX_GPU_OK
98}
99
100func nx_gpu_init(c: *NxGpu, storage: *i64) -> i64 {
101 if (c as i64) == 0 { return 0 - NX_HDL_BAD_KIND }
102 if (storage as i64) == 0 { return 0 - NX_HDL_BAD_KIND }
103 c.storage = storage
104 c.valid = 1
105 c.base = NX_GPU_BASE
106 nx_gpu_reset_storage(storage)
107 return NX_GPU_OK
108}
109
110func nx_gpu_addr_in_range(c: *NxGpu, addr: i64) -> i64 {
111 if addr < c.base { return 0 }
112 if addr >= c.base + 0x1000 { return 0 }
113 return 1
114}
115
116// ===== little-endian DMA helpers over the sim's flat guest RAM (DRY: same bounds-checked contract) =====
117func nx_gpu_dma_rd16(mem_buf: *u8, off: i64) -> i64 {
118 let b0: i64 = mem_buf[off] as i64
119 let b1: i64 = mem_buf[off + 1] as i64
120 return b0 | (b1 << 8)
121}
122func nx_gpu_dma_rd32(mem_buf: *u8, off: i64) -> i64 {
123 let b0: i64 = mem_buf[off] as i64
124 let b1: i64 = mem_buf[off + 1] as i64
125 let b2: i64 = mem_buf[off + 2] as i64
126 let b3: i64 = mem_buf[off + 3] as i64
127 return b0 | (b1 << 8) | (b2 << 16) | (b3 << 24)
128}
129func nx_gpu_dma_inrange(off: i64, w: i64, mem_size: i64) -> i64 {
130 if off < 0 { return 0 }
131 if off > mem_size - w { return 0 }
132 return 1
133}
134func nx_gpu_dma_rd8(mem_buf: *u8, off: i64) -> i64 { return mem_buf[off] as i64 }
135// sign-extend an 8-bit value to 64 bits (int8 semantics for the dot product).
136func nx_gpu_sext8(v: i64) -> i64 { let low: i64 = v & 0xff; if (low & 0x80) != 0 { return low | (0 - 256) } return low }
137
138// ===== MMIO read (32-bit) =================================================
139func nx_gpu_read32(c: *NxGpu, addr: i64, value_out: *i64) -> i64 {
140 if c.valid != 1 { return 0 - NX_GPU_ADDR_OUT_OF_RANGE }
141 if (value_out as i64) == 0 { return 0 - NX_HDL_BAD_KIND }
142 if nx_gpu_addr_in_range(c, addr) != 1 { return 0 - NX_GPU_ADDR_OUT_OF_RANGE }
143 let off: i64 = addr - c.base
144 if off == NX_GPU_OFF_ID {
145 value_out[0] = NX_GPU_ID
146 return NX_GPU_OK
147 }
148 if off == NX_GPU_OFF_CTRL {
149 value_out[0] = c.storage[NX_GPU_SLOT_CTRL]
150 return NX_GPU_OK
151 }
152 if off == NX_GPU_OFF_STATUS {
153 value_out[0] = c.storage[NX_GPU_SLOT_STATUS]
154 return NX_GPU_OK
155 }
156 if off == NX_GPU_OFF_RING_LO {
157 value_out[0] = c.storage[NX_GPU_SLOT_RING_LO]
158 return NX_GPU_OK
159 }
160 if off == NX_GPU_OFF_RING_HI {
161 value_out[0] = c.storage[NX_GPU_SLOT_RING_HI]
162 return NX_GPU_OK
163 }
164 if off == NX_GPU_OFF_FENCE {
165 value_out[0] = c.storage[NX_GPU_SLOT_FENCE] & 0xffffffff
166 return NX_GPU_OK
167 }
168 if off == NX_GPU_OFF_RESULTPEEK {
169 value_out[0] = c.storage[NX_GPU_SLOT_RESULTPEEK] & 0xffffffff
170 return NX_GPU_OK
171 }
172 value_out[0] = 0
173 return NX_GPU_OK
174}
175
176// ===== DOORBELL dispatch DMA (the GPU-class round-trip) =====================
177// On a DOORBELL write the device: (1) fetches the command packet from RING_LO (opcode @+0,
178// spirv_magic @+4, operand @+8, seqno @+12), (2) validates spirv_magic == 0x07230203 -- if it
179// does NOT match, the device REFUSES to execute (fence/resultpeek unchanged), so a packet with no
180// SPIR-V kernel makes no progress (a real validation that composes with the sovereign SPIR-V lane),
181// (3) on a valid dispatch sets FENCE = seqno and latches RESULTPEEK = (operand<<16)|seqno.
182// Bounds-checked at the device/memory boundary -- an out-of-range ring base makes the DMA a no-op.
183func nx_gpu_doorbell_dma(c: *NxGpu, mem_buf: *u8, mem_base: i64, mem_size: i64) -> i64 {
184 if c.valid != 1 { return 0 - NX_GPU_ADDR_OUT_OF_RANGE }
185 if (mem_buf as i64) == 0 { return 0 - NX_GPU_ADDR_OUT_OF_RANGE }
186 c.storage[NX_GPU_SLOT_DBELL] = c.storage[NX_GPU_SLOT_DBELL] + 1
187 // the device only services the doorbell once enabled + ready.
188 if (c.storage[NX_GPU_SLOT_STATUS] & NX_GPU_STATUS_READY) == 0 { return NX_GPU_OK }
189 let ring_phys: i64 = c.storage[NX_GPU_SLOT_RING_LO]
190 if ring_phys < mem_base { return 0 - NX_GPU_ADDR_OUT_OF_RANGE }
191 let ring_off: i64 = ring_phys - mem_base
192 // bound the whole packet incl. the DOT8 b-vector tail (@+20..23); covers the echo packet too.
193 if nx_gpu_dma_inrange(ring_off + NX_GPU_PKT_OFF_B + 4, 0, mem_size) != 1 { return 0 - NX_GPU_ADDR_OUT_OF_RANGE }
194 let magic: i64 = nx_gpu_dma_rd32(mem_buf, ring_off + NX_GPU_PKT_OFF_MAGIC)
195 // SPIR-V validation: a dispatch without the SPIR-V module magic makes NO progress.
196 if magic != NX_GPU_SPIRV_MAGIC { return NX_GPU_OK }
197 let opcode: i64 = nx_gpu_dma_rd16(mem_buf, ring_off + NX_GPU_PKT_OFF_OPCODE)
198 if opcode == NX_GPU_OPCODE_DOT8 {
199 // int8 4-elem dot product (the matmul primitive) over a[+16..19] x b[+20..23] in guest RAM:
200 // signed int8*int8 accumulated -> RESULTPEEK. Bit-identical to the CPU reference the gate
201 // computes the same way -- the sovereign GPU-compute / CUDA-moat-escape proof (V-RAM-005).
202 let cseq: i64 = nx_gpu_dma_rd16(mem_buf, ring_off + NX_GPU_PKT_OFF_CSEQ)
203 var dot: i64 = 0
204 var i: i64 = 0
205 while i < 4 {
206 let av: i64 = nx_gpu_sext8(nx_gpu_dma_rd8(mem_buf, ring_off + NX_GPU_PKT_OFF_A + i))
207 let bv: i64 = nx_gpu_sext8(nx_gpu_dma_rd8(mem_buf, ring_off + NX_GPU_PKT_OFF_B + i))
208 dot = dot + (av * bv)
209 i = i + 1
210 }
211 c.storage[NX_GPU_SLOT_FENCE] = cseq & 0xffffffff
212 c.storage[NX_GPU_SLOT_RESULTPEEK] = dot & 0xffffffff
213 return NX_GPU_OK
214 }
215 if opcode == NX_GPU_OPCODE_MATMUL2 {
216 // 2x2 int8 matmul tile (the V-RAM-005 primitive): C = A*B with A/B row-major int8 @+16/+20.
217 // Packs the four int8 outputs into RESULTPEEK (C00 | C01<<8 | C10<<16 | C11<<24), bit-identical
218 // to the CPU reference the gate computes the same way (correctness; throughput-vs-cuBLAS is a
219 // separate real-RTX measurement, NOT claimed on the emu -- no-wave).
220 let mseq: i64 = nx_gpu_dma_rd16(mem_buf, ring_off + NX_GPU_PKT_OFF_CSEQ)
221 let a00: i64 = nx_gpu_sext8(nx_gpu_dma_rd8(mem_buf, ring_off + NX_GPU_PKT_OFF_A + 0))
222 let a01: i64 = nx_gpu_sext8(nx_gpu_dma_rd8(mem_buf, ring_off + NX_GPU_PKT_OFF_A + 1))
223 let a10: i64 = nx_gpu_sext8(nx_gpu_dma_rd8(mem_buf, ring_off + NX_GPU_PKT_OFF_A + 2))
224 let a11: i64 = nx_gpu_sext8(nx_gpu_dma_rd8(mem_buf, ring_off + NX_GPU_PKT_OFF_A + 3))
225 let b00: i64 = nx_gpu_sext8(nx_gpu_dma_rd8(mem_buf, ring_off + NX_GPU_PKT_OFF_B + 0))
226 let b01: i64 = nx_gpu_sext8(nx_gpu_dma_rd8(mem_buf, ring_off + NX_GPU_PKT_OFF_B + 1))
227 let b10: i64 = nx_gpu_sext8(nx_gpu_dma_rd8(mem_buf, ring_off + NX_GPU_PKT_OFF_B + 2))
228 let b11: i64 = nx_gpu_sext8(nx_gpu_dma_rd8(mem_buf, ring_off + NX_GPU_PKT_OFF_B + 3))
229 let c00: i64 = (a00 * b00) + (a01 * b10)
230 let c01: i64 = (a00 * b01) + (a01 * b11)
231 let c10: i64 = (a10 * b00) + (a11 * b10)
232 let c11: i64 = (a10 * b01) + (a11 * b11)
233 c.storage[NX_GPU_SLOT_FENCE] = mseq & 0xffffffff
234 let packed: i64 = (c00 & 0xff) | ((c01 & 0xff) << 8) | ((c10 & 0xff) << 16) | ((c11 & 0xff) << 24)
235 c.storage[NX_GPU_SLOT_RESULTPEEK] = packed & 0xffffffff
236 return NX_GPU_OK
237 }
238 // opcode 1 (ECHO): the base binding round-trip -- signal the fence + latch (operand<<16)|seqno.
239 let operand: i64 = nx_gpu_dma_rd16(mem_buf, ring_off + NX_GPU_PKT_OFF_OPERAND)
240 let seqno: i64 = nx_gpu_dma_rd16(mem_buf, ring_off + NX_GPU_PKT_OFF_SEQNO)
241 c.storage[NX_GPU_SLOT_FENCE] = seqno & 0xffffffff
242 let result: i64 = ((operand & 0xffff) << 16) | (seqno & 0xffff)
243 c.storage[NX_GPU_SLOT_RESULTPEEK] = result & 0xffffffff
244 return NX_GPU_OK
245}
246
247// ===== MMIO write (32-bit) =================================================
248// CTRL latches; CTRL.EN=1 sets STATUS.READY (enable), CTRL.EN=0 clears it (reset, ring forgotten).
249// RING_LO/HI latch. The DOORBELL runs the dispatch DMA (via the sim's store32 dispatch, which has
250// guest RAM in hand). RO regs (ID/STATUS/FENCE/RESULTPEEK) drop writes.
251func nx_gpu_write32(c: *NxGpu, addr: i64, value: i64) -> i64 {
252 if c.valid != 1 { return 0 - NX_GPU_ADDR_OUT_OF_RANGE }
253 if nx_gpu_addr_in_range(c, addr) != 1 { return 0 - NX_GPU_ADDR_OUT_OF_RANGE }
254 let off: i64 = addr - c.base
255 if off == NX_GPU_OFF_CTRL {
256 c.storage[NX_GPU_SLOT_CTRL] = value & 0xffffffff
257 if (value & NX_GPU_CTRL_EN) != 0 {
258 c.storage[NX_GPU_SLOT_STATUS] = c.storage[NX_GPU_SLOT_STATUS] | NX_GPU_STATUS_READY
259 } else {
260 nx_gpu_reset_storage(c.storage)
261 }
262 return NX_GPU_OK
263 }
264 if off == NX_GPU_OFF_RING_LO {
265 c.storage[NX_GPU_SLOT_RING_LO] = value & 0xffffffff
266 return NX_GPU_OK
267 }
268 if off == NX_GPU_OFF_RING_HI {
269 c.storage[NX_GPU_SLOT_RING_HI] = value & 0xffffffff
270 return NX_GPU_OK
271 }
272 // ID/STATUS/FENCE/RESULTPEEK read-only; DOORBELL handled by the sim dispatch. Drop.
273 return NX_GPU_OK
274}
275
276// ===== Status accessor (harness helper) =================================================
277func nx_gpu_fence(c: *NxGpu) -> i64 {
278 if c.valid != 1 { return 0 }
279 return c.storage[NX_GPU_SLOT_FENCE]
280}