code wiki / _hdl_build / nx_rv64_atomic_gate.nx
nx_rv64_atomic_gate.nx source
↩ module page · 363 lines · 19764 B
1// nx_rv64_atomic_gate.nx -- LN34 REFEREE (lang.plan): the IR atomic family on the rv64 lane, from the byte up.
2//
3// ONE FAMILY, THREE LOWERINGS: __atomic_load/store/cas/faa_i64 + __atomic_fence (nx_atom.nx wraps them) lower
4// to OP_ATOMIC_* in the IR; x86-64 lowers those to LOCK instructions (pre-existing), the wasm lane to the 0xFE
5// family (nx_wasm_threads_gate), and -- proven HERE -- the rv64 backend (nx_riscv.nx) to RV64A: amoadd.d.aqrl,
6// an lr.d/sc.d.aqrl compare-and-swap loop, and the ISA manual's sequentially-consistent fence mappings.
7// FOUR LEGS, each composed IN-PROCESS so nothing is trusted by name:
8// A. the sovereign text assembler (nx_rv64_asm) encodes every new mnemonic to the word an independent
9// field-layout encoder predicts (funct5<<27 | aq<<26 | rl<<25 | rs2<<20 | rs1<<15 | funct3<<12 | rd<<7 | 0x2F;
10// fence = pred<<24 | succ<<20 | 0x0F), refuses a malformed fence, and now admits `.L<name>:` labels while
11// still skipping `.globl`-style directives;
12// B. the behavioural sim (rv64im_min_sim, the same instance the A-extension conformance gate drives) executes
13// the assembled program with the hand-computed register and memory results;
14// C. the compiler backend, driven exactly as nx_nxc drives it (lex -> parse -> opt -> regalloc -> emit_function),
15// lowers a fixture carrying every atomic op to those mnemonics and leaves no `# unhandled op`, while a plain
16// fixture carries none of them (the neutrality neg-control);
17// D. END TO END: the backend's emitted `tick` function is assembled by leg A's assembler and RUN on leg B's sim
18// with a0/a1 as arguments and ra pointing at a self-loop sentinel, and the sim's a0 equals the tick count.
19// nx_rv64_atomic_gate
20// license_tier: ORIGINAL No hw writes (Rule 26).
21import "nx_syscalls.nx"
22import "nx_gate_verdict.nx"
23import "nishi_hdl_primitives.nx"
24import "rv64im_min_decoder.nx"
25import "rv64im_min_alu.nx"
26import "rv64im_min_regfile.nx"
27import "rv64im_min_csr.nx"
28import "rv64im_min_clint.nx"
29import "rv64im_min_uart.nx"
30import "rv64im_min_virtio.nx"
31import "rv64im_min_sim.nx"
32import "nx_rv64_asm.nx"
33import "nx_types.nx"
34import "nx_lex_kinds.nx"
35import "nx_outbuf.nx"
36import "nx_ir.nx"
37import "nx_tokenizer.nx"
38import "nx_parse.nx"
39import "nx_opt.nx"
40import "nx_regalloc.nx"
41import "nx_riscv.nx"
42
43const GA_MEMB: i64 = 0x80000000
44const GA_MEMS: i64 = 65536
45const GA_CODE_CAP: i64 = 16384 // assembled words for one fixture function
46const GA_SCR_OFF: i64 = 0x2000 // the atomic cell, inside the sim memory
47const GA_SENTINEL_OFF: i64 = 0x3000 // `j .` the end-to-end run returns into
48const GA_STACK_OFF: i64 = 0xF000 // sp for the end-to-end run (grows down, well above code and cell)
49const GA_STEP_BUDGET: i64 = 200000 // instruction budget for the end-to-end run (a loop that never returns)
50const GA_TICKS: i64 = 1000
51const GA_TOKS: i64 = 262144
52const GA_ASM_CAP: i64 = 1048576
53const GA_FN_STRIDE: i64 = 176 // sizeof(Function), the stride nx_nxc walks with
54const GA_LOCS_STRIDE: i64 = 24
55const GA_FRAME_ALIGN: i64 = 16 // the two frame parameters nx_nxc passes to emit_function
56const GA_RA_SLOT: i64 = 8
57// ABI register numbers the backend's lowering uses
58const GA_X_RA: i64 = 1
59const GA_X_SP: i64 = 2
60const GA_X_A0: i64 = 10
61const GA_X_A1: i64 = 11
62const GA_X_A7: i64 = 17
63const GA_X_T4: i64 = 29
64const GA_X_T5: i64 = 30
65const GA_X_T6: i64 = 31
66// field layout of the RV64A / FENCE words -- the INDEPENDENT encoder leg A is measured against
67const GA_OP_AMO: i64 = 0x2F
68const GA_OP_FENCE: i64 = 0x0F
69const GA_F5_ADD: i64 = 0
70const GA_F5_SWAP: i64 = 1
71const GA_F5_LR: i64 = 2
72const GA_F5_SC: i64 = 3
73const GA_F3_D: i64 = 3
74const GA_F3_W: i64 = 2
75const GA_SH_F5: i64 = 27
76const GA_SH_AQ: i64 = 26
77const GA_SH_RL: i64 = 25
78const GA_SH_RS2: i64 = 20
79const GA_SH_RS1: i64 = 15
80const GA_SH_F3: i64 = 12
81const GA_SH_RD: i64 = 7
82const GA_SH_PRED: i64 = 24
83const GA_SH_SUCC: i64 = 20
84const GA_IORW: i64 = 15
85const GA_RW: i64 = 3
86const GA_R: i64 = 2
87const GA_W: i64 = 1
88// hand-computed values for leg B (see the program text below)
89const GA_CELL_INIT: i64 = 5
90const GA_ADDEND: i64 = 3
91const GA_EXP_T6_AFTER_ADD: i64 = 5
92const GA_EXP_A7_AFTER_LR: i64 = 8
93const GA_EXP_CELL_FINAL: i64 = 8
94const GA_EXP_SC_FAIL: i64 = 1
95const GA_EXP_CAS: i64 = 1009
96const GA_JAL_SELF: i64 = 0x6F // jal x0, 0 -- a self-loop the end-to-end run returns into
97
98const GA_FX_ATOMIC: *u8 = "const MO: i64 = 5\nfunc tick(p: i64, n: i64) -> i64 {\n let c: *i64 = p as *i64\n var i: i64 = 0\n while i < n { __atomic_faa_i64(c, 1, MO); i = i + 1 }\n return __atomic_load_i64(c, MO)\n}\nfunc probe_cas(p: i64) -> i64 {\n let c: *i64 = p as *i64\n let s: i64 = __atomic_store_i64(c, 7, MO)\n let a: i64 = __atomic_cas_i64(c, 7, 9, MO)\n let b: i64 = __atomic_cas_i64(c, 7, 11, MO)\n let f: i64 = __atomic_fence(MO)\n let v: i64 = __atomic_load_i64(c, MO)\n return a * 1000 + b * 100 + v + s + f\n}\n"
99const GA_FX_PLAIN: *u8 = "func plain(a: i64, b: i64) -> i64 { return a + b }\n"
100const GA_PROG_B: *u8 = "amoadd.d.aqrl t6, t5, (t4)\nlr.d.aqrl a7, (t4)\nsc.d.aqrl t5, t6, (t4)\nfence rw, rw\namoswap.d.aqrl t6, a7, (t4)\nsc.d.aqrl t5, t6, (t4)\n"
101const GA_PROG_B_N: i64 = 6
102const GA_PROG_LABELS: *u8 = ".globl top\ntop:\n.Lloop_bb0:\n addi t5, t5, -1\n bne t5, zero, .Lloop_bb0\n ret\n"
103const GA_PROG_LABELS_N: i64 = 3
104const GA_PROG_BADFENCE: *u8 = "fence rw\n"
105
106func ga_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
107func ga_has(buf: *u8, n: i64, needle: *u8) -> i64 {
108 let m: i64 = ga_slen(needle)
109 if m == 0 { return 0 }
110 if (buf as i64) == 0 { return 0 }
111 var i: i64 = 0
112 while i + m <= n {
113 var k: i64 = 0
114 var ok: i64 = 1
115 while k < m { if buf[i + k] != needle[k] { ok = 0; k = m } k = k + 1 }
116 if ok == 1 { return 1 }
117 i = i + 1
118 }
119 return 0
120}
121func ga_word(buf: *u8, off: i64) -> i64 {
122 return (buf[off] as i64) | ((buf[off + 1] as i64) << 8) | ((buf[off + 2] as i64) << 16) | ((buf[off + 3] as i64) << 24)
123}
124func ga_wr64(mem: *u8, off: i64, v: i64) -> i64 { var i: i64 = 0; while i < 8 { mem[off + i] = ((v >> (i * 8)) & 0xff) as u8; i = i + 1 } return 0 }
125func ga_rd64(mem: *u8, off: i64) -> i64 { var v: i64 = 0; var i: i64 = 0; while i < 8 { v = v | ((mem[off + i] as i64) << (i * 8)); i = i + 1 } return v }
126// the independent encoders
127func ga_amo(f5: i64, aq: i64, rl: i64, rs2: i64, rs1: i64, f3: i64, rd: i64) -> i64 {
128 return (f5 << GA_SH_F5) | (aq << GA_SH_AQ) | (rl << GA_SH_RL) | (rs2 << GA_SH_RS2) | (rs1 << GA_SH_RS1) | (f3 << GA_SH_F3) | (rd << GA_SH_RD) | GA_OP_AMO
129}
130func ga_fence(pred: i64, succ: i64) -> i64 { return (pred << GA_SH_PRED) | (succ << GA_SH_SUCC) | GA_OP_FENCE }
131
132// a fresh sim over a fresh memory: code at offset 0, sp/args/ra preset by the caller through the regfile
133struct GaSim {
134 sim: *NxRv64imSim,
135 rf: *NxRv64imRegfile,
136 mem: *u8,
137}
138func ga_sim_new() -> *GaSim {
139 let g: *GaSim = sys_mmap(64) as *GaSim
140 let rf_st: *i64 = sys_mmap(8 * NX_RV64IM_RF_N_REGS) as *i64
141 let csr_st: *i64 = sys_mmap(8 * NX_CSR_SLOT_N) as *i64
142 let clint_st: *i64 = sys_mmap(8 * NX_CLINT_SLOT_N) as *i64
143 let uart_st: *i64 = sys_mmap(8 * NX_UART_SLOT_N) as *i64
144 let tx: *u8 = sys_mmap(64)
145 let mem: *u8 = sys_mmap(GA_MEMS)
146 let rf: *NxRv64imRegfile = sys_mmap(64) as *NxRv64imRegfile
147 let csr: *NxRv64imCsrFile = sys_mmap(64) as *NxRv64imCsrFile
148 let clint: *NxClint = sys_mmap(64) as *NxClint
149 let uart: *NxUart = sys_mmap(64) as *NxUart
150 let sim: *NxRv64imSim = sys_mmap(256) as *NxRv64imSim
151 nx_rv64im_rf_init(rf, rf_st); nx_rv64im_csr_init(csr, csr_st, 0); nx_clint_init(clint, clint_st); nx_uart_init(uart, uart_st, tx, 64)
152 nx_rv64im_sim_init(sim, rf, csr, clint, uart, GA_MEMB, mem, GA_MEMS, 0)
153 g.sim = sim
154 g.rf = rf
155 g.mem = mem
156 return g
157}
158
159// the compiler front half, exactly as nx_nxc drives it, into one asm text buffer
160func ga_compile(src: *u8, o: *OutBuf) -> i64 {
161 let toks: *Tok = lex_source(src, GA_TOKS)
162 if toks == (0 as *Tok) { return 0 - 1 }
163 let m: *Module = parse_module(toks, 0 as *Module)
164 if m == (0 as *Module) { return 0 - 2 }
165 var fi: i64 = 0
166 while fi < m.n_functions {
167 let fn_base: i64 = m.functions as i64
168 let f: *Function = (fn_base + fi * GA_FN_STRIDE) as *Function
169 let fn_name: *u8 = f.name_start as *u8
170 opt_run(f)
171 let locs: *ValueLoc = sys_mmap(f.n_values * GA_LOCS_STRIDE + 64) as *ValueLoc
172 let cs: *i64 = sys_mmap(16) as *i64
173 let cs_fpr: *i64 = sys_mmap(16) as *i64
174 let sb: *i64 = sys_mmap(16) as *i64
175 *cs = 0; *cs_fpr = 0; *sb = 0
176 regalloc_function(f, locs, cs, cs_fpr, sb)
177 emit_function(f, locs, o, fn_name, GA_FRAME_ALIGN, GA_RA_SLOT, *cs, *cs_fpr)
178 fi = fi + 1
179 }
180 return m.n_functions
181}
182// find the byte offset of `<name>:` at line start in an asm text (the function's entry), or -1
183func ga_label_off(txt: *u8, n: i64, name: *u8) -> i64 {
184 let m: i64 = ga_slen(name)
185 var i: i64 = 0
186 while i + m + 1 <= n {
187 var at_line: i64 = 0
188 if i == 0 { at_line = 1 } else { if txt[i - 1] == (10 as u8) { at_line = 1 } }
189 if at_line == 1 {
190 var k: i64 = 0
191 var ok: i64 = 1
192 while k < m { if txt[i + k] != name[k] { ok = 0; k = m } k = k + 1 }
193 if ok == 1 { if txt[i + m] == (58 as u8) { return i } }
194 }
195 i = i + 1
196 }
197 return 0 - 1
198}
199// the code address of a label inside an assembled text: count encodable lines before it (the assembler's own
200// rule -- comment and directive lines take no space, label lines take no space, everything else is 4 bytes)
201func ga_label_addr(txt: *u8, n: i64, name: *u8) -> i64 {
202 let target: i64 = ga_label_off(txt, n, name)
203 if target < 0 { return 0 - 1 }
204 var pos: i64 = 0
205 var abs: i64 = 0
206 while pos < target {
207 // line extent [pos, le)
208 var le: i64 = pos
209 var scanning: i64 = 1
210 while scanning == 1 { if le >= n { scanning = 0 } else { if txt[le] == (10 as u8) { scanning = 0 } else { le = le + 1 } } }
211 // first non-blank byte
212 var fp: i64 = pos
213 var skipping: i64 = 1
214 while skipping == 1 { if fp >= le { skipping = 0 } else { if ra_isws(txt[fp] as i64) == 1 { fp = fp + 1 } else { skipping = 0 } } }
215 if fp < le {
216 let c0: i64 = txt[fp] as i64
217 var counts: i64 = 1
218 if c0 == 35 { counts = 0 }
219 if ra_is_directive(txt, fp, le) == 1 { counts = 0 }
220 // a label line (last non-blank byte ':') takes no space
221 var q: i64 = le - 1
222 var trimming: i64 = 1
223 while trimming == 1 { if q < fp { trimming = 0 } else { if ra_isws(txt[q] as i64) == 1 { q = q - 1 } else { trimming = 0 } } }
224 if q >= fp { if (txt[q] as i64) == 58 { counts = 0 } }
225 if counts == 1 { abs = abs + 4 }
226 }
227 pos = le + 1
228 }
229 return abs
230}
231func ga_puti(v: i64) -> i64 {
232 let b: *u8 = sys_mmap(32)
233 var x: i64 = v
234 var neg: i64 = 0
235 if x < 0 { neg = 1; x = 0 - x }
236 var i: i64 = 31
237 b[i] = 0 as u8
238 if x == 0 { i = i - 1; b[i] = 48 as u8 }
239 while x > 0 { i = i - 1; b[i] = (48 + x - (x / 10) * 10) as u8; x = x / 10 }
240 if neg == 1 { i = i - 1; b[i] = 45 as u8 }
241 sys_write(1, ((b as i64) + i) as *u8, 31 - i)
242 return 0
243}
244
245func main(argc: i64, argv: *i64) -> i64 {
246 let ctr: *i64 = gv_ctr()
247 gv_head("nx_rv64_atomic_gate -- LN34: the IR atomic family lowered to RV64A, assembled sovereign, executed on the sovereign sim" as *u8)
248
249 // ---- A. assembler vs the independent encoder ------------------------------------------------
250 let code: *u8 = sys_mmap(GA_CODE_CAP)
251 let nb: i64 = rvasm_assemble_str(GA_PROG_B, code, GA_CODE_CAP)
252 gv_check("asm-encodes-the-six-atomic-lines-to-six-words" as *u8, nb == GA_PROG_B_N * 4, ctr)
253 gv_check("asm-amoadd-d-aqrl-t6-t5-t4-matches-the-field-layout" as *u8, ga_word(code, 0) == ga_amo(GA_F5_ADD, 1, 1, GA_X_T5, GA_X_T4, GA_F3_D, GA_X_T6), ctr)
254 gv_check("asm-lr-d-aqrl-a7-t4-matches-with-rs2-zero" as *u8, ga_word(code, 4) == ga_amo(GA_F5_LR, 1, 1, 0, GA_X_T4, GA_F3_D, GA_X_A7), ctr)
255 gv_check("asm-sc-d-aqrl-t5-t6-t4-matches" as *u8, ga_word(code, 8) == ga_amo(GA_F5_SC, 1, 1, GA_X_T6, GA_X_T4, GA_F3_D, GA_X_T5), ctr)
256 gv_check("asm-fence-rw-rw-matches-pred-succ-layout" as *u8, ga_word(code, 12) == ga_fence(GA_RW, GA_RW), ctr)
257 gv_check("asm-amoswap-d-aqrl-t6-a7-t4-matches" as *u8, ga_word(code, 16) == ga_amo(GA_F5_SWAP, 1, 1, GA_X_A7, GA_X_T4, GA_F3_D, GA_X_T6), ctr)
258 let code2: *u8 = sys_mmap(GA_CODE_CAP)
259 let nb2: i64 = rvasm_assemble_str("fence\nfence r, rw\nfence rw, w\namoadd.w t6, t5, (t4)\n" as *u8, code2, GA_CODE_CAP)
260 gv_check("asm-bare-fence-is-iorw-iorw-and-the-relaxed-forms-encode" as *u8, (nb2 == 16) * (ga_word(code2, 0) == ga_fence(GA_IORW, GA_IORW)) * (ga_word(code2, 4) == ga_fence(GA_R, GA_RW)) * (ga_word(code2, 8) == ga_fence(GA_RW, GA_W)) * (ga_word(code2, 12) == ga_amo(GA_F5_ADD, 0, 0, GA_X_T5, GA_X_T4, GA_F3_W, GA_X_T6)), ctr)
261 let code3: *u8 = sys_mmap(GA_CODE_CAP)
262 let nb3: i64 = rvasm_assemble_str(GA_PROG_BADFENCE, code3, GA_CODE_CAP)
263 gv_check("neg-control-asm-refuses-a-fence-with-one-set" as *u8, nb3 < 0, ctr)
264 let nb4: i64 = rvasm_assemble_str(GA_PROG_LABELS, code3, GA_CODE_CAP)
265 gv_check("asm-admits-dot-L-labels-and-skips-dot-directives" as *u8, nb4 == GA_PROG_LABELS_N * 4, ctr)
266
267 // ---- B. the sim executes the assembled program with the hand-computed results ----------------
268 let g: *GaSim = ga_sim_new()
269 var i: i64 = 0
270 while i < nb { g.mem[i] = code[i]; i = i + 1 }
271 ga_wr64(g.mem, GA_SCR_OFF, GA_CELL_INIT)
272 nx_rv64im_rf_write(g.rf, GA_X_T4, GA_MEMB + GA_SCR_OFF)
273 nx_rv64im_rf_write(g.rf, GA_X_T5, GA_ADDEND)
274 var st: i64 = 0
275 while st < GA_PROG_B_N { nx_rv64im_sim_step(g.sim); st = st + 1 }
276 let t6: i64 = nx_rv64im_rf_read(g.rf, GA_X_T6)
277 let a7: i64 = nx_rv64im_rf_read(g.rf, GA_X_A7)
278 let t5: i64 = nx_rv64im_rf_read(g.rf, GA_X_T5)
279 let cell: i64 = ga_rd64(g.mem, GA_SCR_OFF)
280 gv_puts(" sim: t6=" as *u8); ga_puti(t6); gv_puts(" a7=" as *u8); ga_puti(a7); gv_puts(" t5=" as *u8); ga_puti(t5); gv_puts(" cell=" as *u8); ga_puti(cell); gv_puts("\n" as *u8)
281 gv_check("sim-lr-reads-the-amoadd-result-8" as *u8, a7 == GA_EXP_A7_AFTER_LR, ctr)
282 gv_check("sim-amoswap-old-value-proves-the-sc-wrote-5" as *u8, t6 == GA_EXP_T6_AFTER_ADD, ctr)
283 gv_check("sim-cell-ends-at-8-after-the-swap" as *u8, cell == GA_EXP_CELL_FINAL, ctr)
284 gv_check("neg-control-sim-sc-without-a-reservation-fails-1" as *u8, t5 == GA_EXP_SC_FAIL, ctr)
285
286 // ---- C. the backend lowers every atomic op ------------------------------------------------------
287 let o: *OutBuf = out_new(GA_ASM_CAP)
288 let nf: i64 = ga_compile(GA_FX_ATOMIC, o)
289 gv_check("backend-compiles-the-atomic-fixture-two-functions" as *u8, nf == 2, ctr)
290 gv_check("backend-faa-lowers-to-amoadd-d-aqrl" as *u8, ga_has(o.buf, o.pos, "amoadd.d.aqrl t6, t5, (t4)" as *u8), ctr)
291 gv_check("backend-cas-lowers-to-an-lr-sc-aqrl-loop" as *u8, ga_has(o.buf, o.pos, "lr.d.aqrl t6, (t4)" as *u8) * ga_has(o.buf, o.pos, "sc.d.aqrl t6, a7, (t4)" as *u8) * ga_has(o.buf, o.pos, "_cas" as *u8), ctr)
292 gv_check("backend-load-and-store-carry-the-seq-cst-fences" as *u8, ga_has(o.buf, o.pos, "fence r, rw" as *u8) * ga_has(o.buf, o.pos, "fence rw, w" as *u8), ctr)
293 gv_check("backend-fence-lowers-to-fence-rw-rw" as *u8, ga_has(o.buf, o.pos, "fence rw, rw" as *u8), ctr)
294 gv_check("backend-leaves-no-unhandled-op" as *u8, 1 - ga_has(o.buf, o.pos, "# unhandled op" as *u8), ctr)
295 let op: *OutBuf = out_new(GA_ASM_CAP)
296 let npf: i64 = ga_compile(GA_FX_PLAIN, op)
297 gv_check("neg-control-plain-fixture-carries-no-atomic-or-fence" as *u8, (npf == 1) * (1 - ga_has(op.buf, op.pos, "amo" as *u8)) * (1 - ga_has(op.buf, op.pos, "lr.d" as *u8)) * (1 - ga_has(op.buf, op.pos, "fence" as *u8)), ctr)
298
299 // ---- D. end to end: assemble the backend's output and run it on the sim ------------------------
300 let ocode: *u8 = sys_mmap(GA_CODE_CAP)
301 o.buf[o.pos] = 0 as u8
302 let onb: i64 = rvasm_assemble(o.buf, o.pos, ocode, GA_CODE_CAP)
303 if onb < 0 {
304 gv_puts(" ASSEMBLE FAILED rc=" as *u8); ga_puti(onb); gv_puts(" on the backend text below:\n" as *u8)
305 sys_write(1, o.buf, o.pos)
306 }
307 gv_check("e2e-backend-text-assembles-on-the-sovereign-assembler" as *u8, onb > 0, ctr)
308 let tick_at: i64 = ga_label_addr(o.buf, o.pos, "tick" as *u8)
309 let cas_at: i64 = ga_label_addr(o.buf, o.pos, "probe_cas" as *u8)
310 gv_check("e2e-both-entry-labels-resolve-to-code-addresses" as *u8, (tick_at >= 0) * (cas_at >= 0), ctr)
311 // run tick(cell, GA_TICKS)
312 let g2: *GaSim = ga_sim_new()
313 i = 0
314 while i < onb { g2.mem[i] = ocode[i]; i = i + 1 }
315 let jself: *u8 = sys_mmap(GA_CODE_CAP)
316 let jn: i64 = rvasm_assemble_str("sent:\n j sent\n" as *u8, jself, GA_CODE_CAP)
317 var jsent: i64 = 0
318 if jn == 4 { if ga_word(jself, 0) == GA_JAL_SELF { jsent = 1; ga_wr64(g2.mem, GA_SENTINEL_OFF, ga_word(jself, 0)) } }
319 gv_check("e2e-sentinel-self-jump-assembles-to-jal-zero-0" as *u8, jsent == 1, ctr)
320 ga_wr64(g2.mem, GA_SCR_OFF, 0)
321 nx_rv64im_rf_write(g2.rf, GA_X_SP, GA_MEMB + GA_STACK_OFF)
322 nx_rv64im_rf_write(g2.rf, GA_X_RA, GA_MEMB + GA_SENTINEL_OFF)
323 nx_rv64im_rf_write(g2.rf, GA_X_A0, GA_MEMB + GA_SCR_OFF)
324 nx_rv64im_rf_write(g2.rf, GA_X_A1, GA_TICKS)
325 g2.sim.pc = GA_MEMB + tick_at
326 var steps: i64 = 0
327 var running: i64 = 1
328 while running == 1 {
329 nx_rv64im_sim_step(g2.sim)
330 steps = steps + 1
331 if g2.sim.pc == GA_MEMB + GA_SENTINEL_OFF { running = 0 }
332 if steps >= GA_STEP_BUDGET { running = 0 }
333 if g2.sim.halted == 1 { running = 0 }
334 }
335 let ret: i64 = nx_rv64im_rf_read(g2.rf, GA_X_A0)
336 let cell2: i64 = ga_rd64(g2.mem, GA_SCR_OFF)
337 gv_puts(" e2e tick: steps=" as *u8); ga_puti(steps); gv_puts(" a0=" as *u8); ga_puti(ret); gv_puts(" cell=" as *u8); ga_puti(cell2); gv_puts(" returned=" as *u8); ga_puti(1 - running); gv_puts("\n" as *u8)
338 gv_check("e2e-tick-returns-through-ra-within-budget" as *u8, (steps < GA_STEP_BUDGET) * (g2.sim.pc == GA_MEMB + GA_SENTINEL_OFF), ctr)
339 gv_check("e2e-tick-1000-amoadd-reads-back-1000-in-a0-and-in-memory" as *u8, (ret == GA_TICKS) * (cell2 == GA_TICKS), ctr)
340 // run probe_cas(cell)
341 let g3: *GaSim = ga_sim_new()
342 i = 0
343 while i < onb { g3.mem[i] = ocode[i]; i = i + 1 }
344 ga_wr64(g3.mem, GA_SENTINEL_OFF, ga_word(jself, 0))
345 nx_rv64im_rf_write(g3.rf, GA_X_SP, GA_MEMB + GA_STACK_OFF)
346 nx_rv64im_rf_write(g3.rf, GA_X_RA, GA_MEMB + GA_SENTINEL_OFF)
347 nx_rv64im_rf_write(g3.rf, GA_X_A0, GA_MEMB + GA_SCR_OFF)
348 g3.sim.pc = GA_MEMB + cas_at
349 steps = 0
350 running = 1
351 while running == 1 {
352 nx_rv64im_sim_step(g3.sim)
353 steps = steps + 1
354 if g3.sim.pc == GA_MEMB + GA_SENTINEL_OFF { running = 0 }
355 if steps >= GA_STEP_BUDGET { running = 0 }
356 if g3.sim.halted == 1 { running = 0 }
357 }
358 let ret3: i64 = nx_rv64im_rf_read(g3.rf, GA_X_A0)
359 gv_puts(" e2e probe_cas: steps=" as *u8); ga_puti(steps); gv_puts(" a0=" as *u8); ga_puti(ret3); gv_puts("\n" as *u8)
360 gv_check("e2e-cas-swaps-once-then-refuses-reads-1009-on-the-sim" as *u8, (g3.sim.pc == GA_MEMB + GA_SENTINEL_OFF) * (ret3 == GA_EXP_CAS), ctr)
361
362 return gv_verdict("rv64_atomic_gate" as *u8, ctr, "assembler, sim, backend and the end-to-end run of the backend's own output; the NAS toolchain canary is the remaining door" as *u8)
363}