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}