code wiki / _hdl_build / nx_driver_shapes.nx
nx_driver_shapes.nx
buildroot/runtime/_hdl_build/nx_driver_shapes.nx
about
SUPERSEDED 2026-06-13 (no-sprawl consolidation): the CANONICAL driver-from-spec path is the
op-list-driven nx_drv_proto_emit.nx (a new device class = a new op-list SPEC, zero emitter change).
This shape library + its consumer nx_drvgen are KEPT only as the byte-identical virtio re-emit
regression reference (NVMEGATE stage=B PROOF-1). See nx_drvgen.nx header + nvme_oplist_virt.spec.
nx_driver_shapes.nx -- the DEVICE-AGNOSTIC driver shape library (X-DRV-W1 stage A). This is
the emitter-of-emitters substrate for driver bring-up: a tiny rv64 mini-encoder plus four
SPEC-DATA-DRIVEN emit-op families that, between them, express the structure of ANY register-
programmed device-init driver. ZERO virtio (or NVMe) identity is baked here -- every offset
and constant an op touches arrives as a PARAMETER, pulled by the caller from a generic field
table. The device identity lives entirely in the spec; this library only knows how to turn
(offset, value) tuples into bytes. The four shapes:
WIRE_TLV verify "reg-at-offset == const" / write "const -> reg-at-offset". The bring-up
handshake is a list of WIRE_TLV ops (virtio MagicValue/Version/DeviceID verifies
+ Status ORs; NVMe would be CAP/VS verifies + AQA/ASQ/ACQ writes). Provided by
ds_emit_verify / ds_emit_verify_u / ds_emit_regwrite.
COMMAND_QUEUE bind a queue (write base/PFN regs) + kick a doorbell/notify + read an instrument
register back and verify it. virtio's QueuePFN+QueueNotify and NVMe's ASQ/ACQ+
SQyTDBL are the SAME op over different spec rows -- the caller threads the bind
regs, the kick reg/val, and the result reg/expected. The read-back verify is
ds_emit_verify / ds_emit_verify_u; the kick is ds_emit_regwrite.
STATE_MACHINE an enable/progress step that is EITHER monotonic-OR-write (virtio Status) OR
write-then-poll-until-bit-set (NVMe CC.EN -> CSTS.RDY). ds_emit_regwrite covers
the OR-write half; ds_emit_poll_until_set synthesises the poll-loop control flow
(a real backward branch) from a spec field -- the structure comes from DATA, not
from code (the eoe / X-AUT-006d principle). Virtio uses only the OR-write half;
the poll half is exercised by the 2nd device class (stage B).
STRUCT_WALK store struct fields at base_reg + memoff with width w, driven by a field table
(off, width, value). virtio's 16-byte split-virtqueue descriptor and NVMe's
64-byte SQE are the SAME op over different field tables. Provided by
ds_emit_memstore (and ds_li32 / ds_li32u for materialising a struct base addr).
Byte-reproducibility: every op keeps the two-pass measure/backfill invariant of the original
virtio emitter (instruction COUNT identical across passes; only immediates differ), so a forward
fail-branch target is structurally derived, never typed. This file is lifted VERBATIM (byte-for-
byte logic) from nx_virtio_hs_emit's encoder so the refactored virtio emitter that now CALLS
these ops re-emits its image byte-identically (the stage-A regression guard).
Sovereign: pure integer arithmetic, no syscalls beyond what the importer pulls in via
nx_syscalls. license_tier: ORIGINAL
dependencies 1 imports · 3 importers
imports: nx_syscalls.nx
imported by: _ds_smoke.nxnx_drvgen.nxnx_virtio_hs_emit.nx
structs
| none |
consts
| 43 | const DS_UART: i64 = 0x10000000 // NS16550A THR (write a byte = transmit a transcript char) |
| 44 | const DS_FIN: i64 = 0x100000 // SiFive test finisher (write to exit the machine cleanly) |
| 45 | const DS_PASS: i64 = 0x5555 // FINISHER_PASS low half -> clean halt |
| 47 | const DS_X0: i64 = 0 |
| 48 | const DS_T0: i64 = 5 // UART base |
| 49 | const DS_T1: i64 = 6 // scratch / transcript byte |
| 50 | const DS_T2: i64 = 7 // device base |
| 51 | const DS_T3: i64 = 28 // loaded register value (actual) |
| 52 | const DS_T4: i64 = 29 // expected constant / poll mask |
| 53 | const DS_T5: i64 = 30 // finisher base / struct-base scratch |
functions
| 56 | func ds_lui(rd: i64, imm20: i64) -> i64 { return ((imm20 & 0xFFFFF) << 12) | (rd << 7) | 0x37 } |
| 57 | func ds_addi(rd: i64, rs1: i64, imm: i64) -> i64 { return ((imm & 0xFFF) << 20) | (rs1 << 15) | (rd << 7) | 0x13 } |
| 59 | func ds_load(rd: i64, rs1: i64, f3: i64, imm: i64) -> i64 { return ((imm & 0xFFF) << 20) | (rs1 << 15) | (f3 << 12) | (rd << 7) | 0x03 } |
| 61 | func ds_store(rs2: i64, rs1: i64, f3: i64, imm: i64) -> i64 |
| 67 | func ds_branch(rs1: i64, rs2: i64, f3: i64, imm: i64) -> i64 |
| 75 | func ds_jal(rd: i64, imm: i64) -> i64 called by 1: ds_emit_finisher |
| 83 | func ds_and(rd: i64, rs1: i64, rs2: i64) -> i64 { return (rs2 << 20) | (rs1 << 15) | (7 << 12) | (rd << 7) | 0x33 } |
| 85 | func ds_shift(rd: i64, rs1: i64, f3: i64, shamt: i64) -> i64 { return ((shamt & 0x3f) << 20) | (rs1 << 15) | (f3 << 12) | (rd << 7) | 0x13 } called by 1: ds_li32u |
| 87 | func ds_w32(buf: *u8, off: i64, w: i64) -> i64 |
| 99 | func ds_li32(buf: *u8, off: i64, rd: i64, val: i64) -> i64 |
| 111 | func ds_li32u(buf: *u8, off: i64, rd: i64, val: i64) -> i64 |
| 119 | func ds_emit_str(buf: *u8, off: i64, s: *u8, n: i64) -> i64 |
| 133 | func ds_emit_verify(buf: *u8, off: i64, regoff: i64, expected: i64, fail_off: i64) -> i64 |
| 145 | func ds_emit_verify_u(buf: *u8, off: i64, regoff: i64, expected: i64, fail_off: i64) -> i64 |
| 155 | func ds_emit_regwrite(buf: *u8, off: i64, regoff: i64, val: i64) -> i64 |
| 163 | func ds_emit_checkbit(buf: *u8, off: i64, regoff: i64, mask: i64, fail_off: i64) -> i64 |
| 178 | func ds_emit_regwrite_far(buf: *u8, off: i64, regaddr: i64, val: i64) -> i64 |
| 188 | func ds_emit_verify_far(buf: *u8, off: i64, regaddr: i64, expected: i64, fail_off: i64) -> i64 |
| 200 | func ds_emit_memstore(buf: *u8, off: i64, base_reg: i64, memoff: i64, f3: i64, val: i64) -> i64 |
| 215 | func ds_emit_poll_until_set(buf: *u8, off: i64, en_off: i64, en_val: i64, rdy_off: i64, rdy_mask: i64) -> i64 |
| 234 | func ds_emit_poll_mem_until_set(buf: *u8, off: i64, base_reg: i64, poll_off: i64, mask: i64) -> i64 |
| 245 | func ds_emit_finisher(buf: *u8, off: i64) -> i64 |