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}