code wiki / _hdl_build / nx_drvgen.nx
nx_drvgen.nx source
↩ module page · 349 lines · 20331 B
1// SUPERSEDED 2026-06-13 (no-sprawl consolidation): the CANONICAL driver-from-spec emitter is the
2// op-list-driven nx_drv_proto_emit.nx -- there a NEW device class is a NEW op-list SPEC with ZERO
3// emitter change (the protocol step-sequence is DATA). nx_drvgen hardcodes the NVMe stage ORDERING
4// in code (a per-class emitter), which the X-DRV-W1 council flagged as a generality dilution. NVMe
5// is now carried as an op-list spec: knowledge/specs/nvme_oplist_virt.spec, gate-proven by
6// _nvme_oplist_gate (knowledge/status/driver_spec.log::NVMEOPGATE verdict=GREEN). nx_drvgen +
7// nx_driver_shapes are KEPT as the regression reference (NVMEGATE stage=B), not extended. New work
8// -> add an op-list spec to nx_drv_proto_emit, NOT a new bespoke emitter.
9// nx_drvgen.nx -- the GENERIC driver-image author for the 2nd device class (X-DRV-W1 stage B).
10// A NEW device class IS A SPEC FILE: nx_drvgen reads an NVMe-class controller spec
11// (knowledge/specs/nvme_admin_enable_virt.spec) and AUTHORS a bare-metal rv64 flat image whose
12// driver performs the REAL NVMe controller bring-up by routing EVERY byte through the SAME
13// device-agnostic shape library (nx_driver_shapes: WIRE_TLV / COMMAND_QUEUE / STATE_MACHINE /
14// STRUCT_WALK) that re-emits the virtio image byte-identically in PROOF-1. The shape library
15// knows ZERO NVMe -- the controller identity, offsets, queue bases, SQE/CQE layout, and the
16// phase-tag KAT all arrive from the spec. So the NVMe driver's distinctness comes from spec
17// DATA, not from any virtio code path.
18//
19// The driver the image runs (NVMe 1.4 base spec bring-up subset):
20// verify VS == 0x00010400 (1.4.0) -> emit "VIO-NVME "
21// write CC.EN=1, POLL CSTS until RDY set (STATE_MACHINE poll loop) -> emit "EN " <-- a REAL backward branch (the structural fingerprint virtio's straight-line handshake lacks)
22// bind AQA + ASQ/ACQ base-address regs, read ASQ_LO back + verify -> emit "RDY "
23// lay the 64-byte SQE (opcode/CID/CDW10) into ASQ_base (STRUCT_WALK) -> emit "ASQ "
24// ring the SQ-Tail doorbell (the device posts the phase-tagged CQE) -> emit "SQE "
25// POLL the CQE phase bit in ACQ RAM, read NvmeCqPeek back + verify -> emit "CQ\n" <-- a SECOND backward branch (the CQE phase-tag poll); the verify is the tamper canary
26// write the SiFive finisher -> clean halt.
27// Any verify that fails BRANCHES PAST the rest of the bring-up straight to the finisher, so the
28// transcript loses its tail (the " CQ" canary, etc.) -- the gate's tamper test exploits this.
29// nx_drvgen <specpath> -> writes the flat image to the spec's `out` + the golden to <out>.gold
30// VERDICT log -> knowledge/status/virtio_blk.log (DRVGEN rows). Sovereign: syscalls only,
31// no gcc/.sh. NVMe/PCI is a last-mile interop PROBE + measuring stick to EXCEED, never a
32// substrate -- the emitted driver + shapes are Nishi-native. license_tier: ORIGINAL
33import "nx_syscalls.nx"
34import "nx_itoa_lib.nx" // shared MSB-first emitter (zero-alloc)
35import "nx_driver_shapes.nx"
36const NV_MAGIC_8192: i64 = 8192
37
38// rv64 register numbers (mirror the shape library's calling convention).
39const NV_X0: i64 = 0
40const NV_T2: i64 = 7 // device base
41const NV_T3: i64 = 28 // loaded register value (actual)
42const NV_T5: i64 = 30 // struct-base scratch (ASQ / ACQ guest addr)
43
44// ---- cfg array layout (spec-derived; filled in main, threaded to the emitter) ----
45const NC_BASE: i64 = 0
46const NC_OFF_CAP: i64 = 1
47const NC_OFF_VS: i64 = 2
48const NC_OFF_CC: i64 = 3
49const NC_OFF_CSTS: i64 = 4
50const NC_OFF_AQA: i64 = 5
51const NC_OFF_ASQ_LO: i64 = 6
52const NC_OFF_ASQ_HI: i64 = 7
53const NC_OFF_ACQ_LO: i64 = 8
54const NC_OFF_ACQ_HI: i64 = 9
55const NC_OFF_SQ0TDBL: i64 = 10
56const NC_OFF_CQ0HDBL: i64 = 11
57const NC_OFF_CQPEEK: i64 = 12
58const NC_VS_EXPECTED: i64 = 13
59const NC_CC_EN: i64 = 14
60const NC_CSTS_RDY_MASK: i64 = 15
61const NC_ASQ_ADDR: i64 = 16
62const NC_ACQ_ADDR: i64 = 17
63const NC_AQA_VAL: i64 = 18
64const NC_SQE_OPCODE: i64 = 19
65const NC_SQE_CID: i64 = 20
66const NC_SQE_OFF_OPCODE:i64 = 21
67const NC_SQE_OFF_CID: i64 = 22
68const NC_SQE_OFF_CDW10: i64 = 23
69const NC_SQE_CDW10: i64 = 24
70const NC_CQE_OFF_CID: i64 = 25
71const NC_CQE_OFF_STATUS:i64 = 26
72const NC_CQE_PHASE_BIT: i64 = 27
73const NC_CQE_CID_EXP: i64 = 28
74const NC_CQE_STATUS_EXP:i64 = 29
75const NC_CQPEEK_EXP: i64 = 30
76const NC_N: i64 = 31
77
78func nv_p(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 }
79func nv_fp(fd: i64, s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(fd,s,n); return 0 }
80// MIGRATED to the shared emitter (debt 1785563586). The old body mmapped a scratch buffer
81// per call and never freed it. At PAGE granularity that is 4096B leaked PER CALL -- the
82// defect that took 28.5GB of a 36GB host in nx_ts_lumadiff (2MB input, ~3.66M calls).
83// nxi_* is MSB-first, allocates NOTHING, and emits identical bytes including the sign.
84func nv_fn(fd: i64, v: i64) -> i64 { nxi_fd(fd, v); return 0 }
85func nv_hex(fd: i64, v: i64) -> i64 { let d: *u8=sys_mmap(16); var i: i64=0; while i<8{let nib: i64=(v >> ((7-i)*4)) & 0xf; if nib<10{d[i]=(48+nib) as u8} else {d[i]=(87+nib) as u8} i=i+1}; sys_write(fd,d,8); return 0 }
86
87// ---- author the whole NVMe bring-up image into buf; return byte length. cfg holds the
88// spec-derived offsets/constants; segs[6*2]=(start,len) for the 6 transcript segments; tail =
89// the segment bytes back-to-back. fail_off = the absolute byte offset where the finisher block
90// begins (the measure pass passes 0; the real pass passes the fixed value). EVERY emit goes
91// through the device-agnostic shape library -- the only NVMe knowledge is in cfg[] (spec data). ----
92func nv_emit_image(buf: *u8, cfg: *i64, tail: *u8, segs: *i64, fail_off: i64) -> i64 {
93 let base: i64 = cfg[NC_BASE]
94 var o: i64 = 0
95 // boot: t0 = UART base, t2 = NVMe device base (both exact * 0x1000 -> lui-only).
96 o = ds_w32(buf, o, ds_lui(DS_T0, DS_UART >> 12))
97 o = ds_w32(buf, o, ds_lui(DS_T2, base >> 12))
98 // stage 0 (WIRE_TLV): verify VS == 1.4.0 (controller identity).
99 o = ds_emit_verify(buf, o, cfg[NC_OFF_VS], cfg[NC_VS_EXPECTED], fail_off)
100 o = ds_emit_str(buf, o, tail + segs[0], segs[1]) // "VIO-NVME "
101 // stage 1 (STATE_MACHINE poll): write CC.EN=1, poll CSTS until RDY set. This emits a REAL
102 // backward branch (a poll loop) -- the structural distinctness from virtio's straight-line
103 // forward-only handshake (constant-lifting a forward-only image cannot add a loop).
104 o = ds_emit_poll_until_set(buf, o, cfg[NC_OFF_CC], cfg[NC_CC_EN], cfg[NC_OFF_CSTS], cfg[NC_CSTS_RDY_MASK])
105 o = ds_emit_str(buf, o, tail + segs[2], segs[3]) // "EN "
106 // stage 2 (COMMAND_QUEUE): bind AQA + the ASQ/ACQ 64-bit base-address registers, then read
107 // ASQ_LO back and verify it == asq_addr (the queue-binding proof, unsigned read-back).
108 o = ds_emit_regwrite(buf, o, cfg[NC_OFF_AQA], cfg[NC_AQA_VAL])
109 o = ds_emit_regwrite(buf, o, cfg[NC_OFF_ASQ_LO], cfg[NC_ASQ_ADDR])
110 o = ds_emit_regwrite(buf, o, cfg[NC_OFF_ASQ_HI], 0)
111 o = ds_emit_regwrite(buf, o, cfg[NC_OFF_ACQ_LO], cfg[NC_ACQ_ADDR])
112 o = ds_emit_regwrite(buf, o, cfg[NC_OFF_ACQ_HI], 0)
113 o = ds_emit_verify_u(buf, o, cfg[NC_OFF_ASQ_LO], cfg[NC_ASQ_ADDR], fail_off) // ASQ_LO read-back
114 o = ds_emit_str(buf, o, tail + segs[4], segs[5]) // "RDY "
115 // stage 3 (STRUCT_WALK): lay the 64-byte SQE into ASQ_base. t5 = ASQ guest address
116 // (zero-extended positive); store opcode(sb @+0), CID(sh @+2), CDW10(sw @+40).
117 o = ds_li32u(buf, o, NV_T5, cfg[NC_ASQ_ADDR])
118 o = ds_emit_memstore(buf, o, NV_T5, cfg[NC_SQE_OFF_OPCODE], 0, cfg[NC_SQE_OPCODE]) // sb opcode
119 o = ds_emit_memstore(buf, o, NV_T5, cfg[NC_SQE_OFF_CID], 1, cfg[NC_SQE_CID]) // sh cid
120 o = ds_emit_memstore(buf, o, NV_T5, cfg[NC_SQE_OFF_CDW10], 2, cfg[NC_SQE_CDW10]) // sw cdw10
121 o = ds_emit_str(buf, o, tail + segs[6], segs[7]) // "ASQ "
122 // stage 4 (COMMAND_QUEUE kick): ring the SQ-Tail doorbell. The doorbell lives at base+0x1000
123 // (FAR -- beyond the 12-bit sw immediate), so the driver materialises the absolute address and
124 // stores at offset 0 (ds_emit_regwrite_far). The device fetches the SQE, executes the admin
125 // command, posts the phase-tagged CQE into ACQ, and latches NvmeCqPeek.
126 o = ds_emit_regwrite_far(buf, o, base + cfg[NC_OFF_SQ0TDBL], 1)
127 o = ds_emit_str(buf, o, tail + segs[8], segs[9]) // "SQE "
128 // stage 5 (STATE_MACHINE poll + WIRE_TLV verify): poll the CQE phase bit in ACQ guest RAM
129 // (a SECOND backward branch), then read NvmeCqPeek back (at base+0x1008, FAR) and verify ==
130 // cqpeek_expected (the 2nd-class round-trip binding proof). A mismatch branches to the
131 // finisher, dropping ONLY the " CQ" canary (the "...ASQ SQE" tail survives) -- CqPeek tamper.
132 o = ds_li32u(buf, o, NV_T5, cfg[NC_ACQ_ADDR])
133 o = ds_emit_poll_mem_until_set(buf, o, NV_T5, cfg[NC_CQE_OFF_STATUS], cfg[NC_CQE_PHASE_BIT])
134 o = ds_emit_verify_far(buf, o, base + cfg[NC_OFF_CQPEEK], cfg[NC_CQPEEK_EXP], fail_off) // CqPeek read-back
135 o = ds_emit_str(buf, o, tail + segs[10], segs[11]) // "CQ\n"
136 o = ds_emit_finisher(buf, o)
137 return o
138}
139
140// ---- spec parse helpers (mirror nx_virtio_hs_emit's field parser) ----
141func nv_parse_num(buf: *u8, p: i64, le: i64, endp: *i64) -> i64 {
142 var q: i64 = p
143 var val: i64 = 0
144 if q + 1 < le { if buf[q] == (48 as u8) { if buf[q+1] == (120 as u8) {
145 q = q + 2
146 var go: i64 = 1
147 while go == 1 {
148 if q >= le { go = 0 } else {
149 let c: i64 = buf[q] as i64
150 var d: i64 = 0 - 1
151 if c >= 48 { if c <= 57 { d = c - 48 } }
152 if c >= 97 { if c <= 102 { d = c - 87 } }
153 if c >= 65 { if c <= 70 { d = c - 55 } }
154 if d < 0 { go = 0 } else { val = (val * 16) + d; q = q + 1 }
155 }
156 }
157 endp[0] = q
158 return val
159 }}}
160 var go2: i64 = 1
161 while go2 == 1 {
162 if q >= le { go2 = 0 } else {
163 let c: i64 = buf[q] as i64
164 if c >= 48 { if c <= 57 { val = (val * 10) + (c - 48); q = q + 1 } else { go2 = 0 } } else { go2 = 0 }
165 }
166 }
167 endp[0] = q
168 return val
169}
170
171func nv_num_field(buf: *u8, ls: i64, le: i64, key: *u8, out: *i64) -> i64 {
172 var k: i64 = 0
173 while key[k] != (0 as u8) {
174 if ls + k >= le { return 0 }
175 if buf[ls + k] != key[k] { return 0 }
176 k = k + 1
177 }
178 let endp: *i64 = sys_mmap(16) as *i64
179 out[0] = nv_parse_num(buf, ls + k, le, endp)
180 return 1
181}
182
183func nv_str_field(buf: *u8, ls: i64, le: i64, key: *u8, out: *u8) -> i64 {
184 var k: i64 = 0
185 while key[k] != (0 as u8) {
186 if ls + k >= le { return 0 - 1 }
187 if buf[ls + k] != key[k] { return 0 - 1 }
188 k = k + 1
189 }
190 var o: i64 = 0
191 var q: i64 = ls + k
192 while q < le { if buf[q] == (13 as u8) { q = le } else { out[o] = buf[q]; o = o + 1; q = q + 1 } }
193 out[o] = 0 as u8
194 return o
195}
196
197func nv_log(name: *u8, bytes: i64, golden: *u8, verdict: *u8) -> i64 {
198 let lfd: i64 = sys_openat_append("knowledge/status/virtio_blk.log" as *u8, 0x1a4)
199 if lfd < 0 { return 0 - 1 }
200 nv_fp(lfd, "DRVGEN name=" as *u8); nv_fp(lfd, name)
201 nv_fp(lfd, " class=nvme transport=pci-mmio bytes=" as *u8); nv_fn(lfd, bytes)
202 nv_fp(lfd, " golden=" as *u8); nv_fp(lfd, golden)
203 nv_fp(lfd, " verdict=" as *u8); nv_fp(lfd, verdict); nv_fp(lfd, "\n" as *u8)
204 sys_close(lfd)
205 return 0
206}
207
208func main(argc: i64, argv: *i64) -> i64 {
209 if argc < 2 { nv_p("usage: nx_drvgen <specpath>\n" as *u8); sys_exit(2); return 2 }
210 let sp: *u8 = argv[1] as *u8
211 let lenp: *i64 = sys_mmap(16) as *i64
212 let spec: *u8 = sys_read_file(sp, lenp)
213 let sn: i64 = lenp[0]
214 if sn <= 0 { nv_p("DRVGEN REFUSED: spec missing\n" as *u8); nv_log("(missing)" as *u8, 0, "-" as *u8, "REFUSED" as *u8); sys_exit(2); return 2 }
215
216 let cfg: *i64 = sys_mmap(8 * NC_N) as *i64
217 var ci: i64 = 0
218 while ci < NC_N { cfg[ci] = 0 - 1; ci = ci + 1 } // -1 = unset sentinel
219 let banner: *u8 = sys_mmap(256)
220 let outp: *u8 = sys_mmap(256)
221 banner[0] = 0 as u8
222 outp[0] = 0 as u8
223 let tmp: *i64 = sys_mmap(16) as *i64
224
225 var ls: i64 = 0
226 while ls < sn {
227 var le: i64 = ls
228 var scan: i64 = 1
229 while scan == 1 { if le >= sn { scan = 0 } else { if spec[le] == (10 as u8) { scan = 0 } else { le = le + 1 } } }
230 if spec[ls] != (35 as u8) {
231 if nv_num_field(spec, ls, le, "base " as *u8, tmp) == 1 { cfg[NC_BASE] = tmp[0] }
232 if nv_num_field(spec, ls, le, "off_cap " as *u8, tmp) == 1 { cfg[NC_OFF_CAP] = tmp[0] }
233 if nv_num_field(spec, ls, le, "off_vs " as *u8, tmp) == 1 { cfg[NC_OFF_VS] = tmp[0] }
234 if nv_num_field(spec, ls, le, "off_cc " as *u8, tmp) == 1 { cfg[NC_OFF_CC] = tmp[0] }
235 if nv_num_field(spec, ls, le, "off_csts " as *u8, tmp) == 1 { cfg[NC_OFF_CSTS] = tmp[0] }
236 if nv_num_field(spec, ls, le, "off_aqa " as *u8, tmp) == 1 { cfg[NC_OFF_AQA] = tmp[0] }
237 if nv_num_field(spec, ls, le, "off_asq_lo " as *u8, tmp) == 1 { cfg[NC_OFF_ASQ_LO] = tmp[0] }
238 if nv_num_field(spec, ls, le, "off_asq_hi " as *u8, tmp) == 1 { cfg[NC_OFF_ASQ_HI] = tmp[0] }
239 if nv_num_field(spec, ls, le, "off_acq_lo " as *u8, tmp) == 1 { cfg[NC_OFF_ACQ_LO] = tmp[0] }
240 if nv_num_field(spec, ls, le, "off_acq_hi " as *u8, tmp) == 1 { cfg[NC_OFF_ACQ_HI] = tmp[0] }
241 if nv_num_field(spec, ls, le, "off_sq0tdbl " as *u8, tmp) == 1 { cfg[NC_OFF_SQ0TDBL] = tmp[0] }
242 if nv_num_field(spec, ls, le, "off_cq0hdbl " as *u8, tmp) == 1 { cfg[NC_OFF_CQ0HDBL] = tmp[0] }
243 if nv_num_field(spec, ls, le, "off_cqpeek " as *u8, tmp) == 1 { cfg[NC_OFF_CQPEEK] = tmp[0] }
244 if nv_num_field(spec, ls, le, "vs_expected " as *u8, tmp) == 1 { cfg[NC_VS_EXPECTED] = tmp[0] }
245 if nv_num_field(spec, ls, le, "cc_en " as *u8, tmp) == 1 { cfg[NC_CC_EN] = tmp[0] }
246 if nv_num_field(spec, ls, le, "csts_rdy_mask " as *u8, tmp) == 1 { cfg[NC_CSTS_RDY_MASK] = tmp[0] }
247 if nv_num_field(spec, ls, le, "asq_addr " as *u8, tmp) == 1 { cfg[NC_ASQ_ADDR] = tmp[0] }
248 if nv_num_field(spec, ls, le, "acq_addr " as *u8, tmp) == 1 { cfg[NC_ACQ_ADDR] = tmp[0] }
249 if nv_num_field(spec, ls, le, "aqa_val " as *u8, tmp) == 1 { cfg[NC_AQA_VAL] = tmp[0] }
250 if nv_num_field(spec, ls, le, "sqe_opcode " as *u8, tmp) == 1 { cfg[NC_SQE_OPCODE] = tmp[0] }
251 if nv_num_field(spec, ls, le, "sqe_cid " as *u8, tmp) == 1 { cfg[NC_SQE_CID] = tmp[0] }
252 if nv_num_field(spec, ls, le, "sqe_off_opcode " as *u8, tmp) == 1 { cfg[NC_SQE_OFF_OPCODE] = tmp[0] }
253 if nv_num_field(spec, ls, le, "sqe_off_cid " as *u8, tmp) == 1 { cfg[NC_SQE_OFF_CID] = tmp[0] }
254 if nv_num_field(spec, ls, le, "sqe_off_cdw10 " as *u8, tmp) == 1 { cfg[NC_SQE_OFF_CDW10] = tmp[0] }
255 if nv_num_field(spec, ls, le, "sqe_cdw10 " as *u8, tmp) == 1 { cfg[NC_SQE_CDW10] = tmp[0] }
256 if nv_num_field(spec, ls, le, "cqe_off_cid " as *u8, tmp) == 1 { cfg[NC_CQE_OFF_CID] = tmp[0] }
257 if nv_num_field(spec, ls, le, "cqe_off_status " as *u8, tmp) == 1 { cfg[NC_CQE_OFF_STATUS] = tmp[0] }
258 if nv_num_field(spec, ls, le, "cqe_phase_bit " as *u8, tmp) == 1 { cfg[NC_CQE_PHASE_BIT] = tmp[0] }
259 if nv_num_field(spec, ls, le, "cqe_cid_expected " as *u8, tmp) == 1 { cfg[NC_CQE_CID_EXP] = tmp[0] }
260 if nv_num_field(spec, ls, le, "cqe_status_expected " as *u8, tmp) == 1 { cfg[NC_CQE_STATUS_EXP] = tmp[0] }
261 if nv_num_field(spec, ls, le, "cqpeek_expected " as *u8, tmp) == 1 { cfg[NC_CQPEEK_EXP] = tmp[0] }
262 nv_str_field(spec, ls, le, "banner " as *u8, banner)
263 nv_str_field(spec, ls, le, "out " as *u8, outp)
264 }
265 ls = le + 1
266 }
267
268 // validate: every cfg field must have parsed (the spec is a single complete source).
269 var bad: i64 = 0
270 var vi: i64 = 0
271 while vi < NC_N { if cfg[vi] == (0 - 1) { bad = 1 } vi = vi + 1 }
272 if bad == 1 { nv_p("DRVGEN REFUSED: spec missing a required field\n" as *u8); nv_log("(bad-spec)" as *u8, 0, "-" as *u8, "REFUSED" as *u8); sys_exit(2); return 2 }
273 var blen: i64 = 0
274 while banner[blen] != (0 as u8) { blen = blen + 1 }
275 if blen <= 0 { nv_p("DRVGEN REFUSED: no banner row\n" as *u8); nv_log("(no-banner)" as *u8, 0, "-" as *u8, "REFUSED" as *u8); sys_exit(2); return 2 }
276 if outp[0] == (0 as u8) { nv_p("DRVGEN REFUSED: no out row\n" as *u8); nv_log("(no-out)" as *u8, 0, "-" as *u8, "REFUSED" as *u8); sys_exit(2); return 2 }
277
278 // ---- table-compute the 6 transcript SEGMENTS from the banner (split on spaces; each token
279 // gets a trailing space except the last, which gets a newline) -- golden = banner + '\n'. ----
280 let NV_EXPECT_SEGS: i64 = 6
281 let tail: *u8 = sys_mmap(256)
282 let segs: *i64 = sys_mmap(8 * 12) as *i64
283 var tn: i64 = 0
284 var seg_idx: i64 = 0
285 var seg_start: i64 = 0
286 var bp: i64 = 0
287 while bp < blen {
288 if banner[bp] == (32 as u8) {
289 tail[tn] = 32 as u8; tn = tn + 1
290 segs[seg_idx * 2] = seg_start
291 segs[seg_idx * 2 + 1] = tn - seg_start
292 seg_idx = seg_idx + 1
293 seg_start = tn
294 bp = bp + 1
295 } else {
296 tail[tn] = banner[bp]; tn = tn + 1
297 bp = bp + 1
298 }
299 }
300 tail[tn] = 10 as u8; tn = tn + 1
301 segs[seg_idx * 2] = seg_start
302 segs[seg_idx * 2 + 1] = tn - seg_start
303 seg_idx = seg_idx + 1
304 tail[tn] = 0 as u8
305 if seg_idx != NV_EXPECT_SEGS { nv_p("DRVGEN REFUSED: banner token count mismatch, expected " as *u8); nv_fn(1, NV_EXPECT_SEGS); nv_p(" got " as *u8); nv_fn(1, seg_idx); nv_p("\n" as *u8); nv_log("(bad-banner)" as *u8, 0, "-" as *u8, "REFUSED" as *u8); sys_exit(2); return 2 }
306
307 let golden: *u8 = sys_mmap(256)
308 var gi: i64 = 0
309 while gi < tn { golden[gi] = tail[gi]; gi = gi + 1 }
310 golden[gi] = 0 as u8
311
312 // ---- TWO-PASS authoring: pass 1 measures the finisher byte-offset (the fail target); pass
313 // 2 re-emits with that offset baked into every fail branch. Instruction COUNT is identical
314 // across passes (only immediates differ), so the measured offset is stable -- byte-reproducible. ----
315 let scratch: *u8 = sys_mmap(NV_MAGIC_8192)
316 let sz0: i64 = nv_emit_image(scratch, cfg, tail, segs, 0)
317 let fin_len: i64 = 24 // ds_emit_finisher = li(2)+li(2)+sw(1)+jal(1) = 6 words = 24 bytes
318 let fail_off: i64 = sz0 - fin_len
319
320 let bin: *u8 = sys_mmap(NV_MAGIC_8192)
321 let sz: i64 = nv_emit_image(bin, cfg, tail, segs, fail_off)
322 if sz != sz0 { nv_p("DRVGEN RED: pass size mismatch\n" as *u8); nv_log(outp, sz, golden, "RED" as *u8); sys_exit(1); return 1 }
323
324 let ofd: i64 = sys_openat_wr(outp, 0x1a4)
325 if ofd < 0 { nv_p("DRVGEN RED: cannot open out\n" as *u8); nv_log(outp, sz, golden, "RED" as *u8); sys_exit(1); return 1 }
326 sys_write(ofd, bin, sz)
327 sys_close(ofd)
328
329 // write the golden transcript next to the image (<out>.gold) for the gate.
330 let gp: *u8 = sys_mmap(512)
331 var gpi: i64 = 0
332 while outp[gpi] != (0 as u8) { gp[gpi] = outp[gpi]; gpi = gpi + 1 }
333 gp[gpi] = 46 as u8; gpi = gpi + 1 // '.'
334 gp[gpi] = 103 as u8; gpi = gpi + 1 // 'g'
335 gp[gpi] = 111 as u8; gpi = gpi + 1 // 'o'
336 gp[gpi] = 108 as u8; gpi = gpi + 1 // 'l'
337 gp[gpi] = 100 as u8; gpi = gpi + 1 // 'd'
338 gp[gpi] = 0 as u8
339 let gfd: i64 = sys_openat_wr(gp, 0x1a4)
340 if gfd >= 0 { sys_write(gfd, golden, gi); sys_close(gfd) }
341
342 nv_p("DRVGEN GREEN: authored " as *u8); nv_p(outp); nv_p(" bytes=" as *u8); nv_fn(1, sz)
343 nv_p(" base=0x" as *u8); nv_hex(1, cfg[NC_BASE])
344 nv_p(" fail_off=" as *u8); nv_fn(1, fail_off)
345 nv_p(" golden=" as *u8); nv_p(golden); nv_p(" (spec in, bootable rv64 NVMe-class bring-up image out, all bytes through nx_driver_shapes)\n" as *u8)
346 nv_log(outp, sz, golden, "GREEN" as *u8)
347 sys_exit(0)
348 return 0
349}