code wiki / _hdl_build / nx_drv_proto_emit.nx
nx_drv_proto_emit.nx source
↩ module page · 470 lines · 25537 B
1// nx_drv_proto_emit.nx -- DRIVER-PROTOCOL-FROM-SPEC (X-DRV-W1), the GENERIC driver emitter.
2//
3// The thesis (driver-as-shape-composition): a driver SPEC carries the device register map
4// (base + offsets) AND -- crucially -- the PROTOCOL STEP-SEQUENCE itself as DATA: an op-list
5// (the STATE_MACHINE shape). This organ WALKS that op-list and SYNTHESIZES a bare-metal rv64
6// driver image whose control flow IS the spec's sequence -- so a DIFFERENT protocol sequence is
7// a DIFFERENT spec -> a DIFFERENT driver, with ZERO emitter changes. That is the emitter-of-
8// emitters keystone (X-AUT-006c/e) COMPOSED into the device-driver layer (X-DRV-W0 HWMAP gives
9// the bind side; this gives the protocol side).
10//
11// CONTRAST with nx_virtio_hs_emit: that emitter parameterises the virtio register offsets/values
12// from a spec but HARD-CODES the protocol sequence (identity->ACK->FEAT->VQ->DESC->USED->...).
13// Here the sequence is DATA, so virtio-blk is merely ONE spec instance (the last-mile/benchmark
14// transport per the nishi-ecosystem-only law); a virtio-net handshake, or a future Nishi-native
15// device protocol, is simply a different op-list. The gate proves this by emitting TWO distinct
16// op-lists from the ONE binary and running BOTH against the real devices on the sovereign emu.
17//
18// SPEC FORMAT (line-based, '#'=comment):
19// out <path> where to write the flat image (and <path>.gold)
20// base <hex> device MMIO base (0x1000-aligned -> lui t2)
21// op verify <regoff> <const> LW reg ; li const ; bne -> fail (signed read-back)
22// op verifyu <regoff> <const> LWU reg ; li const ; bne -> fail (unsigned/address read-back)
23// op write <regoff> <val> li val ; SW reg (also: notify = a write)
24// op notify <regoff> <val> alias for write (device kick)
25// op read <regoff> LW reg (exercise a RO register, value discarded)
26// op checkbit <regoff> <mask> LW reg ; li mask ; and ; beq x0 -> fail (bit-stuck proof)
27// op setbase <addr> li32u t5 = addr (membase for the following memstore ops)
28// op memstore <memoff> <f3> <val> li val ; S[f3] val, memoff(t5) (f3: 1=SH 2=SW; ring/desc lay)
29// op emit <literal-to-eol> write the literal bytes over the UART (the transcript token)
30// The image always ends with the SiFive finisher (clean halt). Any verify/checkbit that fails
31// BRANCHES PAST the rest of the op-list to the finisher, so the transcript loses its tail --
32// that is the tamper handle the gate exploits.
33//
34// nx_drv_proto_emit <specpath> -> the spec's `out` (flat rv64 image) + <out>.gold (golden
35// transcript = the concatenation of the emit literals, in op order).
36// VERDICT log -> knowledge/status/driver_spec.log. Sovereign: syscalls only, no gcc/.sh.
37// license_tier: ORIGINAL
38import "nx_syscalls.nx"
39const DP_MAGIC_16384: i64 = 16384
40
41// ---- qemu-virt platform map (device tree as data, not magic) ----
42const DP_UART: i64 = 0x10000000 // NS16550A THR (write a byte = transmit)
43const DP_FIN: i64 = 0x100000 // SiFive test finisher (write to exit)
44const DP_PASS: i64 = 0x5555 // FINISHER_PASS low half -> clean halt
45// rv64 register numbers used (mirror nx_virtio_hs_emit so the encoder forms are identical)
46const RV_X0: i64 = 0
47const RV_T0: i64 = 5 // UART base
48const RV_T1: i64 = 6 // scratch / transcript byte / write value
49const RV_T2: i64 = 7 // device MMIO base
50const RV_T3: i64 = 28 // loaded register value (actual)
51const RV_T4: i64 = 29 // expected constant
52const RV_T5: i64 = 30 // finisher base / membase (set by setbase)
53
54// ---- op-list opcodes (the STATE_MACHINE shape, parsed from the spec) ----
55const OP_VERIFY: i64 = 1 // a0=regoff a1=const (signed LW)
56const OP_VERIFYU: i64 = 2 // a0=regoff a1=const (unsigned LWU)
57const OP_WRITE: i64 = 3 // a0=regoff a1=val
58const OP_READ: i64 = 4 // a0=regoff
59const OP_CHECKBIT: i64 = 5 // a0=regoff a1=mask
60const OP_SETBASE: i64 = 6 // a0=addr (li32u t5)
61const OP_MEMSTORE: i64 = 7 // a0=memoff a1=f3 a2=val
62const OP_EMIT: i64 = 8 // a0=tail-offset a1=len
63
64const DP_MAXOP: i64 = 256
65const DP_TAILCAP: i64 = 2048
66
67// ---- the rv64 mini-encoder (one form per function; integer-only) ----
68func dp_lui(rd: i64, imm20: i64) -> i64 { return ((imm20 & 0xFFFFF) << 12) | (rd << 7) | 0x37 }
69func dp_addi(rd: i64, rs1: i64, imm: i64) -> i64 { return ((imm & 0xFFF) << 20) | (rs1 << 15) | (rd << 7) | 0x13 }
70// load; f3=2 -> LW (sign-extended 32-bit), f3=6 -> LWU (zero-extended 32-bit).
71func dp_load(rd: i64, rs1: i64, f3: i64, imm: i64) -> i64 { return ((imm & 0xFFF) << 20) | (rs1 << 15) | (f3 << 12) | (rd << 7) | 0x03 }
72// store; f3=0 -> SB, f3=1 -> SH, f3=2 -> SW. imm S-type split (signed 12-bit).
73func dp_store(rs2: i64, rs1: i64, f3: i64, imm: i64) -> i64 {
74 let hi: i64 = ((imm >> 5) & 0x7f) << 25
75 let lo: i64 = (imm & 0x1f) << 7
76 return hi | (rs2 << 20) | (rs1 << 15) | (f3 << 12) | lo | 0x23
77}
78// B-type branch; f3=0 -> BEQ, f3=1 -> BNE. imm in bytes (signed, multiple of 2).
79func dp_branch(rs1: i64, rs2: i64, f3: i64, imm: i64) -> i64 {
80 let b12: i64 = ((imm >> 12) & 0x1) << 31
81 let b11: i64 = ((imm >> 11) & 0x1) << 7
82 let b10_5: i64 = ((imm >> 5) & 0x3f) << 25
83 let b4_1: i64 = ((imm >> 1) & 0xf) << 8
84 return b12 | b10_5 | (rs2 << 20) | (rs1 << 15) | (f3 << 12) | b4_1 | b11 | 0x63
85}
86// J-type jal; imm in bytes (signed, multiple of 2).
87func dp_jal(rd: i64, imm: i64) -> i64 {
88 let b20: i64 = ((imm >> 20) & 0x1) << 31
89 let b19_12: i64 = ((imm >> 12) & 0xff) << 12
90 let b11: i64 = ((imm >> 11) & 0x1) << 20
91 let b10_1: i64 = ((imm >> 1) & 0x3ff) << 21
92 return b20 | b10_1 | b11 | b19_12 | (rd << 7) | 0x6f
93}
94// R-type AND (funct3=7, funct7=0): rd = rs1 & rs2.
95func dp_and(rd: i64, rs1: i64, rs2: i64) -> i64 { return (rs2 << 20) | (rs1 << 15) | (7 << 12) | (rd << 7) | 0x33 }
96// I-type shift-immediate (OP-IMM 0x13): f3=1 -> SLLI, f3=5 -> SRLI. rv64 shamt = imm[5:0].
97func dp_shift(rd: i64, rs1: i64, f3: i64, shamt: i64) -> i64 { return ((shamt & 0x3f) << 20) | (rs1 << 15) | (f3 << 12) | (rd << 7) | 0x13 }
98
99func dp_w32(buf: *u8, off: i64, w: i64) -> i64 {
100 buf[off] = (w & 0xff) as u8
101 buf[off+1] = ((w >> 8) & 0xff) as u8
102 buf[off+2] = ((w >> 16) & 0xff) as u8
103 buf[off+3] = ((w >> 24) & 0xff) as u8
104 return off + 4
105}
106
107// load a 32-bit constant into rd: lui hi + addi lo (sign-corrected). 2 words (value-independent
108// size -> two-pass byte-stability). RV64 LUI SIGN-EXTENDS from bit 31 -- and the sovereign sim does
109// too, since the 2026-07-09 nx_rv64im_imm_u fix -- so a bit-31-set constant materialises NEGATIVE
110// here. A caller comparing against a ZERO-extended read-back (LWU / `verifyu`) MUST use dp_li32u
111// instead; see dp_op_verify. (The comment that used to sit here claimed the sim did NOT sign-extend.
112// That became false on 2026-07-09 and the code depending on it broke silently for three weeks.)
113func dp_li32(buf: *u8, off: i64, rd: i64, val: i64) -> i64 {
114 var hi: i64 = (val >> 12) & 0xFFFFF
115 var lo: i64 = val & 0xFFF
116 if lo >= 0x800 { lo = lo - 0x1000; hi = (hi + 1) & 0xFFFFF }
117 var o: i64 = dp_w32(buf, off, dp_lui(rd, hi))
118 o = dp_w32(buf, o, dp_addi(rd, rd, lo))
119 return o
120}
121
122// load a ZERO-EXTENDED 32-bit address into rd: li32 then slli 32 ; srli 32. 4 words. The
123// canonical rv64 idiom for a positive physical address with bit 31 set (ring/data buffers).
124func dp_li32u(buf: *u8, off: i64, rd: i64, val: i64) -> i64 {
125 var o: i64 = dp_li32(buf, off, rd, val)
126 o = dp_w32(buf, o, dp_shift(rd, rd, 1, 32)) // slli rd,rd,32
127 o = dp_w32(buf, o, dp_shift(rd, rd, 5, 32)) // srli rd,rd,32
128 return o
129}
130
131// emit n transcript bytes from s over the UART (t0 = UART base). 2 words/char.
132func dp_emit_bytes(buf: *u8, off: i64, s: *u8, n: i64) -> i64 {
133 var o: i64 = off
134 var i: i64 = 0
135 while i < n {
136 o = dp_w32(buf, o, dp_addi(RV_T1, RV_X0, s[i] as i64))
137 o = dp_w32(buf, o, dp_store(RV_T1, RV_T0, 0, 0)) // sb t1,0(t0)
138 i = i + 1
139 }
140 return o
141}
142
143// verify reg-at-offset == expected, else jump to fail_off. f3sel picks LW (2, signed) or LWU
144// (6, unsigned). lw/lwu t3, regoff(t2) ; li t4, expected ; bne t3,t4,(fail_off-pc). 4 words.
145func dp_op_verify(buf: *u8, off: i64, regoff: i64, expected: i64, f3sel: i64, fail_off: i64) -> i64 {
146 var o: i64 = dp_w32(buf, off, dp_load(RV_T3, RV_T2, f3sel, regoff))
147 // THE EXPECTED CONSTANT MUST BE MATERIALISED IN THE SAME WIDTH THE LOAD PRODUCES.
148 // f3sel 6 = LWU -> the read-back is ZERO-extended, so the constant must be zero-extended too
149 // (dp_li32u = li32 ; slli 32 ; srli 32). f3sel 2 = LW -> sign-extended, so dp_li32 is correct.
150 // ROOT CAUSE 2026-08-07: this used dp_li32 UNCONDITIONALLY. That was sound only while the sim's
151 // LUI zero-extended. LUI was CORRECTED to sign-extend on 2026-07-09 (rv64im_min_decoder.nx
152 // nx_rv64im_imm_u, caught by the QEMU differential fuzzer), which silently made every `verifyu`
153 // against a bit-31-set constant compare a ZERO-extended load with a NEGATIVE constant -- a bne
154 // that can never be equal. drv_proto_blk_virt.spec stage 6 (verifyu 0x060 0x80003000) died there
155 // and the transcript truncated at VQ. It stayed INVISIBLE until the emu grew a golden-transcript
156 // check (2026-08-01); before that a clean finisher halt ALONE scored GREEN.
157 if f3sel == 6 {
158 o = dp_li32u(buf, o, RV_T4, expected)
159 } else {
160 o = dp_li32(buf, o, RV_T4, expected)
161 }
162 let pc: i64 = o
163 o = dp_w32(buf, o, dp_branch(RV_T3, RV_T4, 1, fail_off - pc))
164 return o
165}
166
167// write a value into a MMIO register: li t1,val ; sw t1, regoff(t2). 3 words.
168func dp_op_write(buf: *u8, off: i64, regoff: i64, val: i64) -> i64 {
169 var o: i64 = dp_li32(buf, off, RV_T1, val)
170 o = dp_w32(buf, o, dp_store(RV_T1, RV_T2, 2, regoff))
171 return o
172}
173
174// read (exercise) a MMIO register into t3, value discarded: lw t3, regoff(t2). 1 word.
175func dp_op_read(buf: *u8, off: i64, regoff: i64) -> i64 {
176 return dp_w32(buf, off, dp_load(RV_T3, RV_T2, 2, regoff))
177}
178
179// confirm (reg & mask) != 0, else jump to fail_off: lw t3 ; li t4,mask ; and ; beq t3,x0,fail. 5 words.
180func dp_op_checkbit(buf: *u8, off: i64, regoff: i64, mask: i64, fail_off: i64) -> i64 {
181 var o: i64 = dp_w32(buf, off, dp_load(RV_T3, RV_T2, 2, regoff))
182 o = dp_li32(buf, o, RV_T4, mask)
183 o = dp_w32(buf, o, dp_and(RV_T3, RV_T3, RV_T4))
184 let pc: i64 = o
185 o = dp_w32(buf, o, dp_branch(RV_T3, RV_X0, 0, fail_off - pc))
186 return o
187}
188
189// store a value into guest RAM at t5 + memoff (f3: 1=SH 2=SW): li t1,val ; s[f3] t1, memoff(t5). 3 words.
190func dp_op_memstore(buf: *u8, off: i64, memoff: i64, f3: i64, val: i64) -> i64 {
191 var o: i64 = dp_li32(buf, off, RV_T1, val)
192 o = dp_w32(buf, o, dp_store(RV_T1, RV_T5, f3, memoff))
193 return o
194}
195
196// finisher block: li t5,FIN ; li t1,PASS ; sw t1,0(t5) -> halt ; jal x0,0 spin. 6 words = 24 bytes.
197func dp_emit_finisher(buf: *u8, off: i64) -> i64 {
198 var o: i64 = dp_li32(buf, off, RV_T5, DP_FIN)
199 o = dp_li32(buf, o, RV_T1, DP_PASS)
200 o = dp_w32(buf, o, dp_store(RV_T1, RV_T5, 2, 0))
201 o = dp_w32(buf, o, dp_jal(RV_X0, 0))
202 return o
203}
204
205// author the WHOLE driver image into buf by WALKING the op-list (the STATE_MACHINE shape).
206// cfg-free: every byte derives from base + the parsed ops. fail_off = absolute byte offset of
207// the finisher (measure pass passes 0; real pass passes the fixed value). Returns byte length.
208func dp_emit_image(buf: *u8, base: i64, opc: *i64, oa0: *i64, oa1: *i64, oa2: *i64, nop: i64, tail: *u8, fail_off: i64) -> i64 {
209 var o: i64 = 0
210 // boot preamble: t0 = UART base, t2 = device MMIO base (both 0x1000-aligned -> lui-only).
211 o = dp_w32(buf, o, dp_lui(RV_T0, DP_UART >> 12))
212 o = dp_w32(buf, o, dp_lui(RV_T2, base >> 12))
213 var i: i64 = 0
214 while i < nop {
215 let op: i64 = opc[i]
216 if op == OP_VERIFY { o = dp_op_verify(buf, o, oa0[i], oa1[i], 2, fail_off) }
217 if op == OP_VERIFYU { o = dp_op_verify(buf, o, oa0[i], oa1[i], 6, fail_off) }
218 if op == OP_WRITE { o = dp_op_write(buf, o, oa0[i], oa1[i]) }
219 if op == OP_READ { o = dp_op_read(buf, o, oa0[i]) }
220 if op == OP_CHECKBIT { o = dp_op_checkbit(buf, o, oa0[i], oa1[i], fail_off) }
221 if op == OP_SETBASE { o = dp_li32u(buf, o, RV_T5, oa0[i]) }
222 if op == OP_MEMSTORE { o = dp_op_memstore(buf, o, oa0[i], oa1[i], oa2[i]) }
223 if op == OP_EMIT { o = dp_emit_bytes(buf, o, (tail as i64 + oa0[i]) as *u8, oa1[i]) }
224 i = i + 1
225 }
226 o = dp_emit_finisher(buf, o)
227 return o
228}
229
230func dp_p(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 }
231func dp_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 }
232func dp_fn(fd: i64, v: i64) -> i64 { let bb: *u8=sys_mmap(28); var m: i64=v; if m<0{m=0-m;sys_write(fd,"-" as *u8,1)}; let t: *u8=sys_mmap(28); var k: i64=0; if m==0{t[0]=48;k=1}; while m>0{t[k]=(48+(m%10)) as u8;m=m/10;k=k+1}; var i: i64=0; while i<k{bb[i]=t[k-1-i];i=i+1}; sys_write(fd,bb,k); return 0 }
233
234// ---- spec parse helpers ----
235// parse a hex (0x..) or decimal token at p in [.,le); returns value; *endp = next index.
236func dp_parse_num(buf: *u8, p: i64, le: i64, endp: *i64) -> i64 {
237 var q: i64 = p
238 var val: i64 = 0
239 if q + 1 < le { if buf[q] == (48 as u8) { if buf[q+1] == (120 as u8) {
240 q = q + 2
241 var go: i64 = 1
242 while go == 1 {
243 if q >= le { go = 0 } else {
244 let c: i64 = buf[q] as i64
245 var d: i64 = 0 - 1
246 if c >= 48 { if c <= 57 { d = c - 48 } }
247 if c >= 97 { if c <= 102 { d = c - 87 } }
248 if c >= 65 { if c <= 70 { d = c - 55 } }
249 if d < 0 { go = 0 } else { val = (val * 16) + d; q = q + 1 }
250 }
251 }
252 endp[0] = q
253 return val
254 }}}
255 var go2: i64 = 1
256 while go2 == 1 {
257 if q >= le { go2 = 0 } else {
258 let c: i64 = buf[q] as i64
259 if c >= 48 { if c <= 57 { val = (val * 10) + (c - 48); q = q + 1 } else { go2 = 0 } } else { go2 = 0 }
260 }
261 }
262 endp[0] = q
263 return val
264}
265
266// if line [ls,le) begins with key (a "name " prefix), parse the trailing number into out[0] and
267// return 1; else 0. (number fields: base.)
268func dp_num_field(buf: *u8, ls: i64, le: i64, key: *u8, out: *i64) -> i64 {
269 var k: i64 = 0
270 while key[k] != (0 as u8) {
271 if ls + k >= le { return 0 }
272 if buf[ls + k] != key[k] { return 0 }
273 k = k + 1
274 }
275 let endp: *i64 = sys_mmap(16) as *i64
276 out[0] = dp_parse_num(buf, ls + k, le, endp)
277 return 1
278}
279
280// if line begins with key, copy the rest (minus CR) into out (NUL-terminated); return length, else -1.
281func dp_str_field(buf: *u8, ls: i64, le: i64, key: *u8, out: *u8) -> i64 {
282 var k: i64 = 0
283 while key[k] != (0 as u8) {
284 if ls + k >= le { return 0 - 1 }
285 if buf[ls + k] != key[k] { return 0 - 1 }
286 k = k + 1
287 }
288 var o: i64 = 0
289 var q: i64 = ls + k
290 while q < le { if buf[q] == (13 as u8) { q = le } else { out[o] = buf[q]; o = o + 1; q = q + 1 } }
291 out[o] = 0 as u8
292 return o
293}
294
295// does [p..] match keyword kw FOLLOWED by a space/tab? returns index past "kw " if so, else -1.
296func dp_kw(buf: *u8, p: i64, le: i64, kw: *u8) -> i64 {
297 var k: i64 = 0
298 while kw[k] != (0 as u8) {
299 if p + k >= le { return 0 - 1 }
300 if buf[p + k] != kw[k] { return 0 - 1 }
301 k = k + 1
302 }
303 if p + k >= le { return 0 - 1 }
304 if buf[p + k] != (32 as u8) { if buf[p + k] != (9 as u8) { return 0 - 1 } }
305 return p + k + 1
306}
307
308func dp_log(name: *u8, bytes: i64, golden: *u8, verdict: *u8) -> i64 {
309 let lfd: i64 = sys_openat_append("knowledge/status/driver_spec.log" as *u8, 0x1a4)
310 if lfd < 0 { return 0 - 1 }
311 dp_fp(lfd, "DRVPROTOEMIT name=" as *u8); dp_fp(lfd, name)
312 dp_fp(lfd, " keystone=driver-protocol-from-spec composes=emitter-of-emitters+HWMAP bytes=" as *u8); dp_fn(lfd, bytes)
313 dp_fp(lfd, " golden=" as *u8); dp_fp(lfd, golden)
314 dp_fp(lfd, " verdict=" as *u8); dp_fp(lfd, verdict); dp_fp(lfd, "\n" as *u8)
315 sys_close(lfd)
316 return 0
317}
318
319func main(argc: i64, argv: *i64) -> i64 {
320 if argc < 2 { dp_p("usage: nx_drv_proto_emit <specpath>\n" as *u8); sys_exit(2); return 2 }
321 let sp: *u8 = argv[1] as *u8
322 let lenp: *i64 = sys_mmap(16) as *i64
323 let spec: *u8 = sys_read_file(sp, lenp)
324 let sn: i64 = lenp[0]
325 if sn <= 0 { dp_p("DRVPROTO REFUSED: spec missing\n" as *u8); dp_log("(missing)" as *u8, 0, "-" as *u8, "REFUSED" as *u8); sys_exit(2); return 2 }
326
327 var base: i64 = 0 - 1
328 let outp: *u8 = sys_mmap(256)
329 outp[0] = 0 as u8
330 let tmp: *i64 = sys_mmap(16) as *i64
331
332 // op-list arrays (the STATE_MACHINE shape) + the transcript-literal tail buffer.
333 let opc: *i64 = sys_mmap(8 * DP_MAXOP) as *i64
334 let oa0: *i64 = sys_mmap(8 * DP_MAXOP) as *i64
335 let oa1: *i64 = sys_mmap(8 * DP_MAXOP) as *i64
336 let oa2: *i64 = sys_mmap(8 * DP_MAXOP) as *i64
337 let tail: *u8 = sys_mmap(DP_TAILCAP)
338 var nop: i64 = 0
339 var tn: i64 = 0
340 let endp: *i64 = sys_mmap(16) as *i64
341
342 var ls: i64 = 0
343 while ls < sn {
344 var le: i64 = ls
345 var scan: i64 = 1
346 while scan == 1 { if le >= sn { scan = 0 } else { if spec[le] == (10 as u8) { scan = 0 } else { le = le + 1 } } }
347 if spec[ls] != (35 as u8) {
348 // non-op header fields
349 if dp_num_field(spec, ls, le, "base " as *u8, tmp) == 1 { base = tmp[0] }
350 dp_str_field(spec, ls, le, "out " as *u8, outp)
351 // op lines
352 let ostart: i64 = dp_kw(spec, ls, le, "op" as *u8)
353 if ostart >= 0 {
354 if nop >= DP_MAXOP { dp_p("DRVPROTO REFUSED: too many ops\n" as *u8); dp_log("(too-many-ops)" as *u8, 0, "-" as *u8, "REFUSED" as *u8); sys_exit(2); return 2 }
355 var q: i64 = ostart
356 var matched: i64 = 0
357 // check VERIFYU before VERIFY (prefix), and the rest.
358 var nq: i64 = dp_kw(spec, q, le, "verifyu" as *u8)
359 if nq >= 0 { opc[nop]=OP_VERIFYU; oa0[nop]=dp_parse_num(spec,nq,le,endp); nq=endp[0]+1; oa1[nop]=dp_parse_num(spec,nq,le,endp); oa2[nop]=0; matched=1 }
360 if matched == 0 { nq = dp_kw(spec, q, le, "verify" as *u8)
361 if nq >= 0 { opc[nop]=OP_VERIFY; oa0[nop]=dp_parse_num(spec,nq,le,endp); nq=endp[0]+1; oa1[nop]=dp_parse_num(spec,nq,le,endp); oa2[nop]=0; matched=1 } }
362 if matched == 0 { nq = dp_kw(spec, q, le, "write" as *u8)
363 if nq >= 0 { opc[nop]=OP_WRITE; oa0[nop]=dp_parse_num(spec,nq,le,endp); nq=endp[0]+1; oa1[nop]=dp_parse_num(spec,nq,le,endp); oa2[nop]=0; matched=1 } }
364 if matched == 0 { nq = dp_kw(spec, q, le, "notify" as *u8)
365 if nq >= 0 { opc[nop]=OP_WRITE; oa0[nop]=dp_parse_num(spec,nq,le,endp); nq=endp[0]+1; oa1[nop]=dp_parse_num(spec,nq,le,endp); oa2[nop]=0; matched=1 } }
366 if matched == 0 { nq = dp_kw(spec, q, le, "read" as *u8)
367 if nq >= 0 { opc[nop]=OP_READ; oa0[nop]=dp_parse_num(spec,nq,le,endp); oa1[nop]=0; oa2[nop]=0; matched=1 } }
368 if matched == 0 { nq = dp_kw(spec, q, le, "checkbit" as *u8)
369 if nq >= 0 { opc[nop]=OP_CHECKBIT; oa0[nop]=dp_parse_num(spec,nq,le,endp); nq=endp[0]+1; oa1[nop]=dp_parse_num(spec,nq,le,endp); oa2[nop]=0; matched=1 } }
370 if matched == 0 { nq = dp_kw(spec, q, le, "setbase" as *u8)
371 if nq >= 0 { opc[nop]=OP_SETBASE; oa0[nop]=dp_parse_num(spec,nq,le,endp); oa1[nop]=0; oa2[nop]=0; matched=1 } }
372 if matched == 0 { nq = dp_kw(spec, q, le, "memstore" as *u8)
373 if nq >= 0 { opc[nop]=OP_MEMSTORE; oa0[nop]=dp_parse_num(spec,nq,le,endp); nq=endp[0]+1; oa1[nop]=dp_parse_num(spec,nq,le,endp); nq=endp[0]+1; oa2[nop]=dp_parse_num(spec,nq,le,endp); matched=1 } }
374 if matched == 0 { nq = dp_kw(spec, q, le, "emit" as *u8)
375 if nq >= 0 {
376 // capture the rest of the line verbatim (minus CR) as the transcript token.
377 var elen: i64 = 0
378 var ep: i64 = nq
379 while ep < le { if spec[ep] == (13 as u8) { ep = le } else { tail[tn + elen] = spec[ep]; elen = elen + 1; ep = ep + 1 } }
380 if elen <= 0 { dp_p("DRVPROTO REFUSED: empty emit literal\n" as *u8); dp_log("(empty-emit)" as *u8, 0, "-" as *u8, "REFUSED" as *u8); sys_exit(2); return 2 }
381 opc[nop]=OP_EMIT; oa0[nop]=tn; oa1[nop]=elen; oa2[nop]=0
382 tn = tn + elen
383 matched = 1
384 } }
385 if matched == 0 { dp_p("DRVPROTO REFUSED: unknown op\n" as *u8); dp_log("(unknown-op)" as *u8, 0, "-" as *u8, "REFUSED" as *u8); sys_exit(2); return 2 }
386 nop = nop + 1
387 }
388 }
389 ls = le + 1
390 }
391
392 // optional CLI overrides (X-DRV-W2 autonomous bind): argv[2] = device base discovered by the
393 // probe (0 = keep the spec's base), argv[3] = output path. The base override is what lets the
394 // driver registry BIND a spec to wherever the device was actually found -- the same blk spec
395 // brings up a blk device at any address, proving "bring up ANY iron" is data-driven, not a
396 // per-address hand-edit. (Absent args = the plain X-DRV-W1 single-spec emit, unchanged.)
397 if argc >= 3 {
398 let ov: *u8 = argv[2] as *u8
399 var ovl: i64 = 0
400 while ov[ovl] != (0 as u8) { ovl = ovl + 1 }
401 let oendp: *i64 = sys_mmap(16) as *i64
402 let base_ov: i64 = dp_parse_num(ov, 0, ovl, oendp)
403 if base_ov != 0 { base = base_ov }
404 }
405 if argc >= 4 {
406 let outov: *u8 = argv[3] as *u8
407 var oi: i64 = 0
408 while outov[oi] != (0 as u8) { outp[oi] = outov[oi]; oi = oi + 1 }
409 outp[oi] = 0 as u8
410 }
411
412 // validate (defensive at the spec boundary -- a malformed spec REFUSES, never false-greens).
413 if base == (0 - 1) { dp_p("DRVPROTO REFUSED: no base\n" as *u8); dp_log("(no-base)" as *u8, 0, "-" as *u8, "REFUSED" as *u8); sys_exit(2); return 2 }
414 if (base & 0xFFF) != 0 { dp_p("DRVPROTO REFUSED: base not 0x1000-aligned\n" as *u8); dp_log("(base-align)" as *u8, 0, "-" as *u8, "REFUSED" as *u8); sys_exit(2); return 2 }
415 if outp[0] == (0 as u8) { dp_p("DRVPROTO REFUSED: no out\n" as *u8); dp_log("(no-out)" as *u8, 0, "-" as *u8, "REFUSED" as *u8); sys_exit(2); return 2 }
416 if nop <= 0 { dp_p("DRVPROTO REFUSED: empty op-list\n" as *u8); dp_log("(empty-ops)" as *u8, 0, "-" as *u8, "REFUSED" as *u8); sys_exit(2); return 2 }
417
418 // golden transcript = concatenation of the emit literals, in op order.
419 let golden: *u8 = sys_mmap(DP_TAILCAP)
420 var gn: i64 = 0
421 var gi: i64 = 0
422 while gi < nop {
423 if opc[gi] == OP_EMIT {
424 var c: i64 = 0
425 while c < oa1[gi] { golden[gn] = tail[oa0[gi] + c]; gn = gn + 1; c = c + 1 }
426 }
427 gi = gi + 1
428 }
429 golden[gn] = 0 as u8
430 if gn <= 0 { dp_p("DRVPROTO REFUSED: no emit op (empty golden)\n" as *u8); dp_log("(no-emit)" as *u8, 0, "-" as *u8, "REFUSED" as *u8); sys_exit(2); return 2 }
431
432 // ---- TWO-PASS authoring: pass 1 measures the finisher byte-offset (the fail target); pass 2
433 // re-emits with that offset baked into every fail branch. Instruction COUNT is identical
434 // across passes (every li32 is 2 words regardless of value, every branch is fixed-width), so
435 // the measured offset is stable -- byte-reproducible synthesis of the spec's state machine. ----
436 let scratch: *u8 = sys_mmap(DP_MAGIC_16384)
437 let sz0: i64 = dp_emit_image(scratch, base, opc, oa0, oa1, oa2, nop, tail, 0)
438 let fin_len: i64 = 24 // finisher = li(2)+li(2)+sw(1)+jal(1) = 6 words
439 let fail_off: i64 = sz0 - fin_len
440
441 let bin: *u8 = sys_mmap(DP_MAGIC_16384)
442 let sz: i64 = dp_emit_image(bin, base, opc, oa0, oa1, oa2, nop, tail, fail_off)
443 if sz != sz0 { dp_p("DRVPROTO RED: pass size mismatch\n" as *u8); dp_log(outp, sz, golden, "RED" as *u8); sys_exit(1); return 1 }
444
445 let ofd: i64 = sys_openat_wr(outp, 0x1a4)
446 if ofd < 0 { dp_p("DRVPROTO RED: cannot open out\n" as *u8); dp_log(outp, sz, golden, "RED" as *u8); sys_exit(1); return 1 }
447 sys_write(ofd, bin, sz)
448 sys_close(ofd)
449
450 // write the golden next to the image (<out>.gold) for the gate.
451 let gp: *u8 = sys_mmap(512)
452 var gpi: i64 = 0
453 while outp[gpi] != (0 as u8) { gp[gpi] = outp[gpi]; gpi = gpi + 1 }
454 gp[gpi] = 46 as u8; gpi = gpi + 1 // '.'
455 gp[gpi] = 103 as u8; gpi = gpi + 1 // 'g'
456 gp[gpi] = 111 as u8; gpi = gpi + 1 // 'o'
457 gp[gpi] = 108 as u8; gpi = gpi + 1 // 'l'
458 gp[gpi] = 100 as u8; gpi = gpi + 1 // 'd'
459 gp[gpi] = 0 as u8
460 let gfd: i64 = sys_openat_wr(gp, 0x1a4)
461 if gfd >= 0 { sys_write(gfd, golden, gn); sys_close(gfd) }
462
463 dp_p("DRVPROTO GREEN: authored " as *u8); dp_p(outp); dp_p(" bytes=" as *u8); dp_fn(1, sz)
464 dp_p(" ops=" as *u8); dp_fn(1, nop)
465 dp_p(" fail_off=" as *u8); dp_fn(1, fail_off)
466 dp_p(" golden=" as *u8); dp_p(golden); dp_p(" (driver SPEC op-list in, bootable rv64 driver image out -- protocol state machine synthesized from the spec)\n" as *u8)
467 dp_log(outp, sz, golden, "GREEN")
468 sys_exit(0)
469 return 0
470}