code wiki / _hdl_build / nx_fpga_lut.nx

nx_fpga_lut.nx source

↩ module page · 94 lines · 4720 B

1// nx_fpga_lut.nx -- LIB: RUNG 4 of the sovereign FPGA-boot SIMULATOR (operator 2026-06-22: "for the fpga if 2// there is a way to simulate it lets build that from the hardware rung up on specs"). The FPGA's ATOM is the 3// 4-input Look-Up Table (LUT4): 16 SRAM config bits that, loaded by the bitstream, make ONE cell compute ANY 4// boolean function of 4 inputs. This lib models the LUT4 + DFF fabric primitives PER SPEC and the TECH-MAP that 5// compiles the team's gate-netlist primitives (nishi_synth_gates NX_GATE_KIND_*) into LUT4 init bits = the 6// per-LUT bitstream. This is the bedrock the higher rungs (place -> route -> bitstream -> fabric-config-sim -> 7// boot) stack on. Built on the VERIFIED lower rungs: rv64im_min_sim (behavioral), synth_emit_alu_gates (netlist), 8// nx_nxgate_sim (gate eval). 9// 10// NEVER-BRICK (#26) BY CONSTRUCTION: pure integer, NO float, NO syscall that writes any real/persistent hardware 11// state -- a LUT is evaluated in memory. Bounded (a LUT4 has exactly 16 input combinations), total (every input 12// yields 0/1), deterministic (same input -> same bit). A simulator cannot brick anything; the gate asserts these. 13// 14// Kind codes MIRROR nishi_synth_gates.nx (sealed enum) so the tech-map is over the SAME primitives the netlist 15// emitter uses (local consts avoid dragging that file's heavy hdl-primitive deps; codes are a stable sealed set). 16// license_tier: ORIGINAL 17import "nx_syscalls.nx" 18 19// --- gate kinds (mirror nishi_synth_gates.nx NX_GATE_KIND_*) --- 20const FL_AND: i64 = 0 21const FL_OR: i64 = 1 22const FL_NOT: i64 = 2 23const FL_XOR: i64 = 3 24const FL_NAND: i64 = 4 25const FL_NOR: i64 = 5 26const FL_XNOR: i64 = 6 27const FL_MUX: i64 = 10 // (sel=a, in0=b, in1=c) 28const FL_DFF: i64 = 23 // sequential 29 30// ===================== LUT4 primitive (the FPGA atom) ===================== 31// A LUT4 is 16 config bits. With inputs a,b,c,d in {0,1}, the cell outputs bit 32// (a | b<<1 | c<<2 | d<<3) of `init`. `init` IS the bitstream for this LUT. 33func lut4_index(a: i64, b: i64, c: i64, d: i64) -> i64 { 34 return (a & 1) | ((b & 1) << 1) | ((c & 1) << 2) | ((d & 1) << 3) 35} 36func lut4_eval(init: i64, a: i64, b: i64, c: i64, d: i64) -> i64 { 37 return (init >> lut4_index(a, b, c, d)) & 1 38} 39 40// ===================== reference boolean primitives ===================== 41// The INDEPENDENT golden model (computed directly, not via the LUT) the tech-map is proven against. 42// Inputs are bits a,b,c,d; each gate uses only the inputs its kind defines. 43func fl_gate_ref(kind: i64, a: i64, b: i64, c: i64, d: i64) -> i64 { 44 let x: i64 = a & 1 45 let y: i64 = b & 1 46 let z: i64 = c & 1 47 if kind == FL_AND { return x & y } 48 if kind == FL_OR { return x | y } 49 if kind == FL_NOT { if x == 0 { return 1 } return 0 } 50 if kind == FL_XOR { return x ^ y } 51 if kind == FL_NAND { if (x & y) == 0 { return 1 } return 0 } 52 if kind == FL_NOR { if (x | y) == 0 { return 1 } return 0 } 53 if kind == FL_XNOR { if (x ^ y) == 0 { return 1 } return 0 } 54 if kind == FL_MUX { if x == 1 { return z } return y } // sel? in1 : in0 55 return 0 // unknown kind -> defined safe 0 (never-brick: total) 56} 57 58// is this a kind the single-LUT4 tech-map covers? (bit-level boolean + mux fit ONE LUT4; wide arithmetic = LUT networks, higher rungs) 59func fl_kind_lutmappable(kind: i64) -> i64 { 60 if kind == FL_AND { return 1 } 61 if kind == FL_OR { return 1 } 62 if kind == FL_NOT { return 1 } 63 if kind == FL_XOR { return 1 } 64 if kind == FL_NAND { return 1 } 65 if kind == FL_NOR { return 1 } 66 if kind == FL_XNOR { return 1 } 67 if kind == FL_MUX { return 1 } 68 return 0 69} 70 71// ===================== the TECH-MAP: gate primitive -> LUT4 init bits (its bitstream) ===================== 72// init = OR over all 16 input combos of (gate_ref(combo) << combo_index). This is exactly how a synthesizer 73// computes a LUT's SRAM contents: enumerate the function's truth table. 74func fl_gate_to_lut4(kind: i64) -> i64 { 75 var init: i64 = 0 76 var i: i64 = 0 77 while i < 16 { 78 let a: i64 = i & 1 79 let b: i64 = (i >> 1) & 1 80 let c: i64 = (i >> 2) & 1 81 let d: i64 = (i >> 3) & 1 82 if fl_gate_ref(kind, a, b, c, d) == 1 { init = init | (1 << lut4_index(a, b, c, d)) } 83 i = i + 1 84 } 85 return init 86} 87 88// ===================== DFF primitive (the FPGA's sequential atom) ===================== 89// A D-flip-flop: Q holds its value until a clock TICK, then latches D. Modeled as: next-Q on tick = D; 90// with no tick, Q is unchanged. (The fabric clock drives all DFFs simultaneously -- nx_nxgate_sim semantics.) 91func fl_dff_next(q_prev: i64, d: i64, tick: i64) -> i64 { 92 if tick == 1 { return d & 1 } 93 return q_prev & 1 94}