code wiki / _hdl_build / nx_rv64_atomic_gate.nx
nx_rv64_atomic_gate.nx
buildroot/runtime/_hdl_build/nx_rv64_atomic_gate.nx
about
nx_rv64_atomic_gate.nx -- LN34 REFEREE (lang.plan): the IR atomic family on the rv64 lane, from the byte up.
ONE FAMILY, THREE LOWERINGS: __atomic_load/store/cas/faa_i64 + __atomic_fence (nx_atom.nx wraps them) lower
to OP_ATOMIC_* in the IR; x86-64 lowers those to LOCK instructions (pre-existing), the wasm lane to the 0xFE
family (nx_wasm_threads_gate), and -- proven HERE -- the rv64 backend (nx_riscv.nx) to RV64A: amoadd.d.aqrl,
an lr.d/sc.d.aqrl compare-and-swap loop, and the ISA manual's sequentially-consistent fence mappings.
FOUR LEGS, each composed IN-PROCESS so nothing is trusted by name:
A. the sovereign text assembler (nx_rv64_asm) encodes every new mnemonic to the word an independent
field-layout encoder predicts (funct5<<27 | aq<<26 | rl<<25 | rs2<<20 | rs1<<15 | funct3<<12 | rd<<7 | 0x2F;
fence = pred<<24 | succ<<20 | 0x0F), refuses a malformed fence, and now admits `.L<name>:` labels while
still skipping `.globl`-style directives;
B. the behavioural sim (rv64im_min_sim, the same instance the A-extension conformance gate drives) executes
the assembled program with the hand-computed register and memory results;
C. the compiler backend, driven exactly as nx_nxc drives it (lex -> parse -> opt -> regalloc -> emit_function),
lowers a fixture carrying every atomic op to those mnemonics and leaves no `# unhandled op`, while a plain
fixture carries none of them (the neutrality neg-control);
D. END TO END: the backend's emitted `tick` function is assembled by leg A's assembler and RUN on leg B's sim
with a0/a1 as arguments and ra pointing at a self-loop sentinel, and the sim's a0 equals the tick count.
nx_rv64_atomic_gate
license_tier: ORIGINAL No hw writes (Rule 26).
dependencies 21 imports · 0 importers
diagram shows first 10 each side; +11 more imports, +0 more importers in the complete lists below.
imports: nx_syscalls.nxnx_gate_verdict.nxnishi_hdl_primitives.nxrv64im_min_decoder.nxrv64im_min_alu.nxrv64im_min_regfile.nxrv64im_min_csr.nxrv64im_min_clint.nxrv64im_min_uart.nxrv64im_min_virtio.nxrv64im_min_sim.nxnx_rv64_asm.nxnx_types.nxnx_lex_kinds.nxnx_outbuf.nxnx_ir.nxnx_tokenizer.nxnx_parse.nxnx_opt.nxnx_regalloc.nxnx_riscv.nx
imported by: nobody (leaf or entry point)
call flow from main pre-order; caps 40 nodes / depth 6 declared; ↻ = already shown
structs
| 133 | struct GaSim |
consts
| 43 | const GA_MEMB: i64 = 0x80000000 |
| 44 | const GA_MEMS: i64 = 65536 |
| 45 | const GA_CODE_CAP: i64 = 16384 // assembled words for one fixture function |
| 46 | const GA_SCR_OFF: i64 = 0x2000 // the atomic cell, inside the sim memory |
| 47 | const GA_SENTINEL_OFF: i64 = 0x3000 // `j .` the end-to-end run returns into |
| 48 | const GA_STACK_OFF: i64 = 0xF000 // sp for the end-to-end run (grows down, well above code and cell) |
| 49 | const GA_STEP_BUDGET: i64 = 200000 // instruction budget for the end-to-end run (a loop that never returns) |
| 50 | const GA_TICKS: i64 = 1000 |
| 51 | const GA_TOKS: i64 = 262144 |
| 52 | const GA_ASM_CAP: i64 = 1048576 |
| 53 | const GA_FN_STRIDE: i64 = 176 // sizeof(Function), the stride nx_nxc walks with |
| 54 | const GA_LOCS_STRIDE: i64 = 24 |
| 55 | const GA_FRAME_ALIGN: i64 = 16 // the two frame parameters nx_nxc passes to emit_function |
| 56 | const GA_RA_SLOT: i64 = 8 |
| 58 | const GA_X_RA: i64 = 1 |
| 59 | const GA_X_SP: i64 = 2 |
| 60 | const GA_X_A0: i64 = 10 |
| 61 | const GA_X_A1: i64 = 11 |
| 62 | const GA_X_A7: i64 = 17 |
| 63 | const GA_X_T4: i64 = 29 |
| 64 | const GA_X_T5: i64 = 30 |
| 65 | const GA_X_T6: i64 = 31 |
| 67 | const GA_OP_AMO: i64 = 0x2F |
| 68 | const GA_OP_FENCE: i64 = 0x0F |
| 69 | const GA_F5_ADD: i64 = 0 |
| 70 | const GA_F5_SWAP: i64 = 1 |
| 71 | const GA_F5_LR: i64 = 2 |
| 72 | const GA_F5_SC: i64 = 3 |
| 73 | const GA_F3_D: i64 = 3 |
| 74 | const GA_F3_W: i64 = 2 |
| 75 | const GA_SH_F5: i64 = 27 |
| 76 | const GA_SH_AQ: i64 = 26 |
| 77 | const GA_SH_RL: i64 = 25 |
| 78 | const GA_SH_RS2: i64 = 20 |
| 79 | const GA_SH_RS1: i64 = 15 |
| 80 | const GA_SH_F3: i64 = 12 |
| 81 | const GA_SH_RD: i64 = 7 |
| 82 | const GA_SH_PRED: i64 = 24 |
| 83 | const GA_SH_SUCC: i64 = 20 |
| 84 | const GA_IORW: i64 = 15 |
| 85 | const GA_RW: i64 = 3 |
| 86 | const GA_R: i64 = 2 |
| 87 | const GA_W: i64 = 1 |
| 89 | const GA_CELL_INIT: i64 = 5 |
| 90 | const GA_ADDEND: i64 = 3 |
| 91 | const GA_EXP_T6_AFTER_ADD: i64 = 5 |
| 92 | const GA_EXP_A7_AFTER_LR: i64 = 8 |
| 93 | const GA_EXP_CELL_FINAL: i64 = 8 |
| 94 | const GA_EXP_SC_FAIL: i64 = 1 |
| 95 | const GA_EXP_CAS: i64 = 1009 |
| 96 | const GA_JAL_SELF: i64 = 0x6F // jal x0, 0 -- a self-loop the end-to-end run returns into |
| 98 | const 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" |
| 99 | const GA_FX_PLAIN: *u8 = "func plain(a: i64, b: i64) -> i64 { return a + b }\n" |
| 100 | const 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" |
| 101 | const GA_PROG_B_N: i64 = 6 |
| 102 | const GA_PROG_LABELS: *u8 = ".globl top\ntop:\n.Lloop_bb0:\n addi t5, t5, -1\n bne t5, zero, .Lloop_bb0\n ret\n" |
| 103 | const GA_PROG_LABELS_N: i64 = 3 |
| 104 | const GA_PROG_BADFENCE: *u8 = "fence rw\n" |
functions
| 106 | func ga_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } |
| 107 | func ga_has(buf: *u8, n: i64, needle: *u8) -> i64 |
| 121 | func ga_word(buf: *u8, off: i64) -> i64 called by 1: main |
| 124 | func 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 } called by 1: main |
| 125 | func 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 } called by 1: main |
| 127 | func ga_amo(f5: i64, aq: i64, rl: i64, rs2: i64, rs1: i64, f3: i64, rd: i64) -> i64 called by 1: main |
| 130 | func ga_fence(pred: i64, succ: i64) -> i64 { return (pred << GA_SH_PRED) | (succ << GA_SH_SUCC) | GA_OP_FENCE } called by 1: main |
| 138 | func ga_sim_new() -> *GaSim |
| 160 | func ga_compile(src: *u8, o: *OutBuf) -> i64 |
| 183 | func ga_label_off(txt: *u8, n: i64, name: *u8) -> i64 |
| 201 | func ga_label_addr(txt: *u8, n: i64, name: *u8) -> i64 |
| 231 | func ga_puti(v: i64) -> i64 |
| 245 | func main(argc: i64, argv: *i64) -> i64 |