code wiki / _hdl_build / nx_driver_shapes.nx

nx_driver_shapes.nx source

↩ module page · 251 lines · 14992 B

1// SUPERSEDED 2026-06-13 (no-sprawl consolidation): the CANONICAL driver-from-spec path is the 2// op-list-driven nx_drv_proto_emit.nx (a new device class = a new op-list SPEC, zero emitter change). 3// This shape library + its consumer nx_drvgen are KEPT only as the byte-identical virtio re-emit 4// regression reference (NVMEGATE stage=B PROOF-1). See nx_drvgen.nx header + nvme_oplist_virt.spec. 5// nx_driver_shapes.nx -- the DEVICE-AGNOSTIC driver shape library (X-DRV-W1 stage A). This is 6// the emitter-of-emitters substrate for driver bring-up: a tiny rv64 mini-encoder plus four 7// SPEC-DATA-DRIVEN emit-op families that, between them, express the structure of ANY register- 8// programmed device-init driver. ZERO virtio (or NVMe) identity is baked here -- every offset 9// and constant an op touches arrives as a PARAMETER, pulled by the caller from a generic field 10// table. The device identity lives entirely in the spec; this library only knows how to turn 11// (offset, value) tuples into bytes. The four shapes: 12// 13// WIRE_TLV verify "reg-at-offset == const" / write "const -> reg-at-offset". The bring-up 14// handshake is a list of WIRE_TLV ops (virtio MagicValue/Version/DeviceID verifies 15// + Status ORs; NVMe would be CAP/VS verifies + AQA/ASQ/ACQ writes). Provided by 16// ds_emit_verify / ds_emit_verify_u / ds_emit_regwrite. 17// COMMAND_QUEUE bind a queue (write base/PFN regs) + kick a doorbell/notify + read an instrument 18// register back and verify it. virtio's QueuePFN+QueueNotify and NVMe's ASQ/ACQ+ 19// SQyTDBL are the SAME op over different spec rows -- the caller threads the bind 20// regs, the kick reg/val, and the result reg/expected. The read-back verify is 21// ds_emit_verify / ds_emit_verify_u; the kick is ds_emit_regwrite. 22// STATE_MACHINE an enable/progress step that is EITHER monotonic-OR-write (virtio Status) OR 23// write-then-poll-until-bit-set (NVMe CC.EN -> CSTS.RDY). ds_emit_regwrite covers 24// the OR-write half; ds_emit_poll_until_set synthesises the poll-loop control flow 25// (a real backward branch) from a spec field -- the structure comes from DATA, not 26// from code (the eoe / X-AUT-006d principle). Virtio uses only the OR-write half; 27// the poll half is exercised by the 2nd device class (stage B). 28// STRUCT_WALK store struct fields at base_reg + memoff with width w, driven by a field table 29// (off, width, value). virtio's 16-byte split-virtqueue descriptor and NVMe's 30// 64-byte SQE are the SAME op over different field tables. Provided by 31// ds_emit_memstore (and ds_li32 / ds_li32u for materialising a struct base addr). 32// 33// Byte-reproducibility: every op keeps the two-pass measure/backfill invariant of the original 34// virtio emitter (instruction COUNT identical across passes; only immediates differ), so a forward 35// fail-branch target is structurally derived, never typed. This file is lifted VERBATIM (byte-for- 36// byte logic) from nx_virtio_hs_emit's encoder so the refactored virtio emitter that now CALLS 37// these ops re-emits its image byte-identically (the stage-A regression guard). 38// Sovereign: pure integer arithmetic, no syscalls beyond what the importer pulls in via 39// nx_syscalls. license_tier: ORIGINAL 40import "nx_syscalls.nx" 41 42// ---- generic target-platform map (qemu-virt rv64; hardware facts, not device identity) ---- 43const DS_UART: i64 = 0x10000000 // NS16550A THR (write a byte = transmit a transcript char) 44const DS_FIN: i64 = 0x100000 // SiFive test finisher (write to exit the machine cleanly) 45const DS_PASS: i64 = 0x5555 // FINISHER_PASS low half -> clean halt 46// rv64 register numbers the shape ops use (a fixed calling convention, device-agnostic) 47const DS_X0: i64 = 0 48const DS_T0: i64 = 5 // UART base 49const DS_T1: i64 = 6 // scratch / transcript byte 50const DS_T2: i64 = 7 // device base 51const DS_T3: i64 = 28 // loaded register value (actual) 52const DS_T4: i64 = 29 // expected constant / poll mask 53const DS_T5: i64 = 30 // finisher base / struct-base scratch 54 55// ---- the rv64 mini-encoder (one form per function; integer-only, struct-free) ---- 56func ds_lui(rd: i64, imm20: i64) -> i64 { return ((imm20 & 0xFFFFF) << 12) | (rd << 7) | 0x37 } 57func ds_addi(rd: i64, rs1: i64, imm: i64) -> i64 { return ((imm & 0xFFF) << 20) | (rs1 << 15) | (rd << 7) | 0x13 } 58// load; f3=2 -> LW (sign-extended 32-bit), f3=6 -> LWU (zero-extended). imm I-type (signed 12-bit). 59func ds_load(rd: i64, rs1: i64, f3: i64, imm: i64) -> i64 { return ((imm & 0xFFF) << 20) | (rs1 << 15) | (f3 << 12) | (rd << 7) | 0x03 } 60// store; f3=0 -> SB, f3=1 -> SH, f3=2 -> SW. imm S-type split (signed 12-bit). 61func ds_store(rs2: i64, rs1: i64, f3: i64, imm: i64) -> i64 { 62 let hi: i64 = ((imm >> 5) & 0x7f) << 25 63 let lo: i64 = (imm & 0x1f) << 7 64 return hi | (rs2 << 20) | (rs1 << 15) | (f3 << 12) | lo | 0x23 65} 66// B-type branch; f3=0 -> BEQ, f3=1 -> BNE. imm in bytes (signed, multiple of 2). 67func ds_branch(rs1: i64, rs2: i64, f3: i64, imm: i64) -> i64 { 68 let b12: i64 = ((imm >> 12) & 0x1) << 31 69 let b11: i64 = ((imm >> 11) & 0x1) << 7 70 let b10_5: i64 = ((imm >> 5) & 0x3f) << 25 71 let b4_1: i64 = ((imm >> 1) & 0xf) << 8 72 return b12 | b10_5 | (rs2 << 20) | (rs1 << 15) | (f3 << 12) | b4_1 | b11 | 0x63 73} 74// J-type jal; imm in bytes (signed, multiple of 2). 75func ds_jal(rd: i64, imm: i64) -> i64 { 76 let b20: i64 = ((imm >> 20) & 0x1) << 31 77 let b19_12: i64 = ((imm >> 12) & 0xff) << 12 78 let b11: i64 = ((imm >> 11) & 0x1) << 20 79 let b10_1: i64 = ((imm >> 1) & 0x3ff) << 21 80 return b20 | b10_1 | b11 | b19_12 | (rd << 7) | 0x6f 81} 82// R-type AND (funct3=7, funct7=0): rd = rs1 & rs2. 83func ds_and(rd: i64, rs1: i64, rs2: i64) -> i64 { return (rs2 << 20) | (rs1 << 15) | (7 << 12) | (rd << 7) | 0x33 } 84// I-type shift-immediate (OP-IMM, opcode 0x13): f3=1 -> SLLI, f3=5 -> SRLI. rv64 shamt is 6 bits. 85func ds_shift(rd: i64, rs1: i64, f3: i64, shamt: i64) -> i64 { return ((shamt & 0x3f) << 20) | (rs1 << 15) | (f3 << 12) | (rd << 7) | 0x13 } 86 87func ds_w32(buf: *u8, off: i64, w: i64) -> i64 { 88 buf[off] = (w & 0xff) as u8 89 buf[off+1] = ((w >> 8) & 0xff) as u8 90 buf[off+2] = ((w >> 16) & 0xff) as u8 91 buf[off+3] = ((w >> 24) & 0xff) as u8 92 return off + 4 93} 94 95// load a full 32-bit constant into rd: lui hi + addi lo (sign-corrected). 2 words. On rv64 lui 96// sign-extends bit 31, so a bit-31-set value leaves rd = 0xFFFFFFFF_xxxxxxxx -- correct for MMIO 97// consts / feature masks (sign-extended compares) but WRONG for a true positive RAM address 98// (use ds_li32u for those). 99func ds_li32(buf: *u8, off: i64, rd: i64, val: i64) -> i64 { 100 var hi: i64 = (val >> 12) & 0xFFFFF 101 var lo: i64 = val & 0xFFF 102 if lo >= 0x800 { lo = lo - 0x1000; hi = (hi + 1) & 0xFFFFF } 103 var o: i64 = ds_w32(buf, off, ds_lui(rd, hi)) 104 o = ds_w32(buf, o, ds_addi(rd, rd, lo)) 105 return o 106} 107 108// load a ZERO-EXTENDED 32-bit constant (a true positive guest address) into rd. lui+addi then 109// slli rd,rd,32 ; srli rd,rd,32 clears the sign-extended upper 32 bits so a bit-31-set address 110// materialises positive. 4 words -- the canonical rv64 idiom for a 32-bit phys addr with bit 31 set. 111func ds_li32u(buf: *u8, off: i64, rd: i64, val: i64) -> i64 { 112 var o: i64 = ds_li32(buf, off, rd, val) 113 o = ds_w32(buf, o, ds_shift(rd, rd, 1, 32)) // slli rd,rd,32 114 o = ds_w32(buf, o, ds_shift(rd, rd, 5, 32)) // srli rd,rd,32 115 return o 116} 117 118// ---- transcript primitive: emit n bytes from s over the UART (t0 = UART base). 2 words/char. ---- 119func ds_emit_str(buf: *u8, off: i64, s: *u8, n: i64) -> i64 { 120 var o: i64 = off 121 var i: i64 = 0 122 while i < n { 123 o = ds_w32(buf, o, ds_addi(DS_T1, DS_X0, s[i] as i64)) 124 o = ds_w32(buf, o, ds_store(DS_T1, DS_T0, 0, 0)) // sb t1,0(t0) 125 i = i + 1 126 } 127 return o 128} 129 130// ==== SHAPE: WIRE_TLV ==== 131// verify reg-at-offset == expected, else branch to fail_off (the finisher block). 132// lw t3, regoff(t2) ; li t4, expected ; bne t3,t4, (fail_off - pc). 4 words. 133func ds_emit_verify(buf: *u8, off: i64, regoff: i64, expected: i64, fail_off: i64) -> i64 { 134 var o: i64 = ds_w32(buf, off, ds_load(DS_T3, DS_T2, 2, regoff)) 135 o = ds_li32(buf, o, DS_T4, expected) 136 let pc: i64 = o 137 o = ds_w32(buf, o, ds_branch(DS_T3, DS_T4, 1, fail_off - pc)) 138 return o 139} 140 141// verify reg-at-offset == expected for an UNSIGNED 32-bit value (e.g. a guest phys addr whose bit 142// 31 is set), else branch to fail_off. Uses LWU (f3=6, zero-extend) so the loaded value matches 143// the lui-built positive expected. 4 words (same shape as ds_emit_verify, only the load f3 differs 144// -> two-pass byte-reproducibility preserved). 145func ds_emit_verify_u(buf: *u8, off: i64, regoff: i64, expected: i64, fail_off: i64) -> i64 { 146 var o: i64 = ds_w32(buf, off, ds_load(DS_T3, DS_T2, 6, regoff)) 147 o = ds_li32(buf, o, DS_T4, expected) 148 let pc: i64 = o 149 o = ds_w32(buf, o, ds_branch(DS_T3, DS_T4, 1, fail_off - pc)) 150 return o 151} 152 153// write a value into a MMIO register (a Status latch ORs writes; a config reg latches the value). 154// li t1,val ; sw t1, regoff(t2). 3 words. 155func ds_emit_regwrite(buf: *u8, off: i64, regoff: i64, val: i64) -> i64 { 156 var o: i64 = ds_li32(buf, off, DS_T1, val) 157 o = ds_w32(buf, o, ds_store(DS_T1, DS_T2, 2, regoff)) 158 return o 159} 160 161// confirm (reg & mask) != 0, else branch to fail_off (a WIRE_TLV "bit stuck" check). 162// lw t3, regoff(t2) ; li t4, mask ; and t3,t3,t4 ; beq t3,x0, fail. 5 words. 163func ds_emit_checkbit(buf: *u8, off: i64, regoff: i64, mask: i64, fail_off: i64) -> i64 { 164 var o: i64 = ds_w32(buf, off, ds_load(DS_T3, DS_T2, 2, regoff)) 165 o = ds_li32(buf, o, DS_T4, mask) 166 o = ds_w32(buf, o, ds_and(DS_T3, DS_T3, DS_T4)) 167 let pc: i64 = o 168 o = ds_w32(buf, o, ds_branch(DS_T3, DS_X0, 0, fail_off - pc)) 169 return o 170} 171 172// write a value into a MMIO register at an ABSOLUTE address (not a 12-bit offset from the 173// device base). Needed when a register lives FAR from the base (NVMe's doorbell page at 174// base+0x1000 and the CqPeek instrument at base+0x1008 are beyond the rv64 sw/lw signed 175// 12-bit immediate range of +/-2047). Materialise the full address into a scratch register 176// (DS_T5) and store at offset 0. li t5,regaddr ; li t1,val ; sw t1,0(t5). 4 words. Generic: 177// the caller threads regaddr = base + regoff (the device identity is data, not code). 178func ds_emit_regwrite_far(buf: *u8, off: i64, regaddr: i64, val: i64) -> i64 { 179 var o: i64 = ds_li32(buf, off, DS_T5, regaddr) 180 o = ds_li32(buf, o, DS_T1, val) 181 o = ds_w32(buf, o, ds_store(DS_T1, DS_T5, 2, 0)) 182 return o 183} 184 185// verify reg-at-ABSOLUTE-address == expected (unsigned, LWU), else branch to fail_off. The FAR 186// twin of ds_emit_verify_u for a register beyond the 12-bit immediate range (NVMe's CqPeek at 187// base+0x1008). li t5,regaddr ; lwu t3,0(t5) ; li t4,expected ; bne t3,t4, fail. 6 words. 188func ds_emit_verify_far(buf: *u8, off: i64, regaddr: i64, expected: i64, fail_off: i64) -> i64 { 189 var o: i64 = ds_li32(buf, off, DS_T5, regaddr) 190 o = ds_w32(buf, o, ds_load(DS_T3, DS_T5, 6, 0)) 191 o = ds_li32(buf, o, DS_T4, expected) 192 let pc: i64 = o 193 o = ds_w32(buf, o, ds_branch(DS_T3, DS_T4, 1, fail_off - pc)) 194 return o 195} 196 197// ==== SHAPE: STRUCT_WALK ==== 198// store a 32-bit immediate into RAM at base_reg + memoff (f3=2 -> SW, f3=1 -> SH, f3=0 -> SB). 199// li t1,val ; sw/sh/sb t1, memoff(base_reg). 3 words. (base_reg already holds the struct addr.) 200func ds_emit_memstore(buf: *u8, off: i64, base_reg: i64, memoff: i64, f3: i64, val: i64) -> i64 { 201 var o: i64 = ds_li32(buf, off, DS_T1, val) 202 o = ds_w32(buf, o, ds_store(DS_T1, base_reg, f3, memoff)) 203 return o 204} 205 206// ==== SHAPE: STATE_MACHINE (poll half) ==== 207// write-then-poll-until-bit-set: the device-enable progress step that virtio does NOT have but 208// NVMe (CC.EN -> CSTS.RDY) does. Emits a REAL backward branch (a poll loop): the read of the 209// status reg, the AND with the ready mask, and a BEQ back to the read while the bit is clear. 210// li t1, enable_val ; sw t1, en_off(t2) -- write the enable 211// L: lw t3, rdy_off(t2) ; li t4, rdy_mask ; and t3,t3,t4 ; beq t3,x0, L (backward branch). 8 words. 212// The backward branch (negative B-type immediate) is the structural fingerprint that distinguishes 213// a polled state machine from virtio's straight-line forward-only handshake -- constant-lifting a 214// forward-only image provably cannot introduce a loop (the eoe / X-AUT-006d argument). 215func ds_emit_poll_until_set(buf: *u8, off: i64, en_off: i64, en_val: i64, rdy_off: i64, rdy_mask: i64) -> i64 { 216 var o: i64 = ds_li32(buf, off, DS_T1, en_val) 217 o = ds_w32(buf, o, ds_store(DS_T1, DS_T2, 2, en_off)) // sw enable -> en reg 218 let loop_pc: i64 = o // L: 219 o = ds_w32(buf, o, ds_load(DS_T3, DS_T2, 2, rdy_off)) // lw t3, rdy(t2) 220 o = ds_li32(buf, o, DS_T4, rdy_mask) // li t4, mask 221 o = ds_w32(buf, o, ds_and(DS_T3, DS_T3, DS_T4)) // and t3,t3,t4 222 let pc: i64 = o 223 o = ds_w32(buf, o, ds_branch(DS_T3, DS_X0, 0, loop_pc - pc)) // beq t3,x0, L (backward: loop_pc - pc < 0) 224 return o 225} 226 227// poll-until-bit-set over a MEMORY location pointed at by base_reg (NOT a device register). 228// virtio has no such loop; NVMe's CQE phase-tag poll (spin on the completion entry in guest RAM 229// until the device sets the phase bit) does. No enable-write half -- the device-side post already 230// happened (the doorbell DMA ran on the kick); this is purely the driver SPINNING on the result. 231// L: lw t3, poll_off(base_reg) ; li t4, mask ; and t3,t3,t4 ; beq t3,x0, L (backward branch). 5 words. 232// Same STATE_MACHINE family as ds_emit_poll_until_set -- a real backward B-type immediate, the 233// structural fingerprint a forward-only image (virtio) provably cannot carry (eoe / X-AUT-006d). 234func ds_emit_poll_mem_until_set(buf: *u8, off: i64, base_reg: i64, poll_off: i64, mask: i64) -> i64 { 235 let loop_pc: i64 = off // L: 236 var o: i64 = ds_w32(buf, off, ds_load(DS_T3, base_reg, 2, poll_off)) // lw t3, poll(base_reg) 237 o = ds_li32(buf, o, DS_T4, mask) // li t4, mask 238 o = ds_w32(buf, o, ds_and(DS_T3, DS_T3, DS_T4)) // and t3,t3,t4 239 let pc: i64 = o 240 o = ds_w32(buf, o, ds_branch(DS_T3, DS_X0, 0, loop_pc - pc)) // beq t3,x0, L (backward) 241 return o 242} 243 244// ==== finisher: li t5,FIN ; li t1,PASS ; sw t1,0(t5) -> clean halt ; jal x0,0 spin. 6 words. ==== 245func ds_emit_finisher(buf: *u8, off: i64) -> i64 { 246 var o: i64 = ds_li32(buf, off, DS_T5, DS_FIN) 247 o = ds_li32(buf, o, DS_T1, DS_PASS) 248 o = ds_w32(buf, o, ds_store(DS_T1, DS_T5, 2, 0)) 249 o = ds_w32(buf, o, ds_jal(DS_X0, 0)) 250 return o 251}