Skip to main content

Module wprog_engine_rtl

Module wprog_engine_rtl 

Source
Expand description

wprog_engine_rtl — the WHOLE-PROGRAM spatial engine: run an entire control-flow program on the reconfigurable fabric, streamed over the wire, not just one straight-line block. Where the block engine (block_engine_rtl) runs a single straight-line residual, this runs a whole ProgramCfgFabric — multiple basic blocks with branch conditions (Select cells), a register file that survives block reconfiguration, and a block-walk terminator (Jump / Branch / Return) — the RTL of run_program_cfg_on_grid driven by a flat config, so ANY acyclic branchy program runs on ONE physical array by loading its config.

This module is the reference + config layer (pure Rust, provable without a simulator): serialize_program flattens a ProgramCfgFabric into a WProgConfig, and eval_wprog_config interprets that flat config exactly as the RTL will — proven bit-identical to run_program_cfg_on_grid (≡ the VM) by wprog_engine_lock. The RTL emitter + the over-the-wire SoC lower this same flat config to silicon.

Correct-first scope: Arith + Select cells (arithmetic + branch decisions) — the whole acyclic branchy control-flow family. The state plane (Mem cells → heap BRAM), loops (the loop fabric), and function calls (a call stack) are the follow-ons that extend it toward the whole language.

Structs§

WBlock
One basic block: live-in registers, cells, register writes, terminator.
WCell
One cell. kind: 0 = Arith op(ops[0], ops[1]), 1 = Select cmp(ops[0],ops[1]) ? ops[2] : ops[3], 3 = HeapRead heap[ops[0]], 4 = HeapWrite heap[ops[0]] = ops[1] (drives ops[1]), 5 = HeapAlloc (grow the heap by ops[0] words, drive the new base pointer) — the state plane, mirroring eval_grid_with_heap.
WImage
The flat, loadable image of a whole program: parallel per-field arrays the generic engine reads directly. Block b’s fields are blk_*[b]; its cells are cell_*[blk_cell_ptr[b] .. + blk_n_cells[b]]; and so on.
WProgConfig
A whole program flattened for the fabric.
WRun
Everything one engine run produces: the entry’s return value, the block-step count, the final heap, the peak call-frame depth, and the output tape (every Show, in program order).
WShow
One Show the engine executed: the value tag the lowering assigned in the SHOW cell’s code (from its exact per-register type tracking, so a reused register is shown as what it holds now) and the raw 64-bit value. A Text needs the heap to render — see render_show. This is exactly the record the RTL streams over the byte channel (REC_SHOW, tag, payload) the moment the cell runs, so the tape the model builds is the byte stream the host reads.
WSrc
An operand source in the flat config: a live-in register (In), a constant, or an earlier cell’s value.
WWork
What a run did, in the units the engine’s cycle count is made of: every executed cell’s cycles with its data-dependent part priced from the data (a divide’s 64 steps, a power’s multiplies, a text’s or a copy’s length), and the blocks, register writes, calls and returns around them.

Enums§

MemBackend
The MEMORY BACKEND for the config-driven engine’s on-chip memories — a first-class, selectable choice of substrate. The whole-program engine holds two kinds of memory: the STATE PLANE (the shared heap + the recursion sfile frame stack) and the CONFIG memories (the UART-streamed program image: blk_*, cell_*, live-ins, writes, constants). Each can live in LUT flip-flops or in block RAM.
WFault
A hardware fault model injected into the golden interpreter. Running a program through the model under each fault and comparing the result against a value observed on silicon localizes a synthesis/place-and-route divergence to one state memory + one address bit in pure Rust: eval is the ground truth, so a fault whose result equals the observed value characterizes the hardware defect.
WTerm
A block’s terminator in the flat config.

Constants§

FPU_V
The proven bit-exact sequential IEEE-754 f64 FPU (fpu.v beside this module; benchmarks/silicon/fpu.v is its golden copy for the FPGA flows) — fp_addsub/fp_mul/fp_div/ fp_sqrt (start/done handshake) + combinational fp_cmp. Appended verbatim after the engine module so the over-the-wire SoC can instantiate the float units; the engine’s 64-bit register file / heap already carry f64 bit patterns, so the FPU is the only float hardware the engine needs. Bit-exact vs Rust f64 over 41,521 vectors (its own verify-fpu.sh), and the exponent path is timing-safe (see the fp_div pipeline lock).
MUL_FUNC_F
The combinational func_f for the over-the-wire SoC: no integer func code computes here. DivS/ModS/FloorDivS/Pow (codes 0–3) run on the engine’s sequential datapath (wpe_soc_template.v st==6, a magnitude-restoring divider and a repeat-multiply), MulHiU (4) and MulOvfS (12) on its multiplier unit (ST_MU..ST_MUW), and the float lane on the FPU, so no multiply or divide shares a cycle with the cell walk. Shared by all three SoC engines (behavioral/synth/hw) so they agree byte-for-byte; the sequencing and results are proven ≡ FuncUnit::eval (== the VM) under iverilog.
WPE_DEPTH
Frame-stack depth (max recursion).
WPE_HEAP
Heap state-plane depth (words) — a projection builds a weight Seq + activation Seqs.
WPE_HW_DEPTH
WPE_HW_HEAP
WPE_HW_MAXARG
WPE_HW_MAXBLK
WPE_HW_MAXCELL
WPE_HW_MAXK
WPE_HW_MAXLIV
WPE_HW_MAXWR
WPE_HW_NREG
WPE_MAXARG
Max TOTAL call arguments across the whole program — the flat arg_op config memory.
WPE_MAXBLK
Max basic blocks per program (blk_*[0:MAXBLK-1], indexed by the block PC).
WPE_MAXCELL
Max TOTAL cells across the whole program — the flat cell_* config memory (indexed by blk_cell_ptr[b]+ci, an accumulating pointer) AND the per-block value array v[0:MAXCELL-1].
WPE_MAXK
Max distinct constants in the program’s konst table (the flat konst memory).
WPE_MAXLIV
Max TOTAL live-in registers across the whole program — the flat liv_reg config memory.
WPE_MAXWR
Max TOTAL register writes across the whole program — the flat wr_* config memory (accumulating pointer).
WPE_NREG
Register-file depth of the generic config-driven whole-program engine (per-register, file[0:NREG-1]).
WPE_STREAM_CAPS
The WPE_STREAM_* profile as the [usize; 10] caps array emit_wprog_engine_soc_verilog_backend expects (order: NREG, MAXBLK, MAXCELL, MAXWR, MAXARG, MAXLIV, MAXK, HEAP, DEPTH, MAXV).
WPE_STREAM_DEPTH
Call-frame depth. Sized from the MEASURED peak across the benchmark suite (26, driven by fib), rounded to a power of two — not from the transformer, which is call-free and needs none. This was 4 while the smaller XC7A100T profile was 8, and because the wire image carries no record of the profile it was built for, a depth-8 program streamed to the depth-4 engine ran and returned a plausible wrong number: on silicon bench_fib gave 8 instead of 21, wpe_rec8 20 instead of 36, wpe_recursion 11 instead of 15. Both wpe_cap_profile_lock (no dimension may shrink between profiles) and the_stream_engine_profile_covers_every_benchmark_in_the_suite (the suite’s measured ceiling) now pin it.
WPE_STREAM_HEAP
Heap depth (words) for the streamed engine. Sized to 2^15 = 32768 — a POWER OF TWO that maps to exactly ONE RAMB36E1 depth-bank at ×1 width (a single RAMB36 is 32768×1), so the whole 15-bit heap_ra addresses one clean tile with NO depth-cascade. This dodges a yosys 0.33 synth_xilinx mis-map: for a heap whose depth is NOT a clean single-tile fit (e.g. the old 20480), memory_libmap picks a wide tile (×9 → 4096 deep) and then FAILS to depth-cascade — it wires every tile to the SAME 12-bit address (heap_ra[11:0]) and drops the top address bits (heap_ra[14:12]), so on silicon (and in post-synth gate-level sim) any heap read/write to an address
WPE_STREAM_MAXARG
WPE_STREAM_MAXBLK
WPE_STREAM_MAXCELL
WPE_STREAM_MAXK
Const-table depth (konst[0:MAXK-1]). The transformer uses 41; a wider straight-line program (the postsynth high-register lock’s 90-value sum) needs 91 distinct constants, so MAXK=128 covers the family. The unchecked image builder skips the konst.len() <= MAXK guard, so an over-MAXK program would silently read a nonexistent const slot — 91 > the old 64 was one of the four caps the high-register lock overflowed.
WPE_STREAM_MAXLIV
WPE_STREAM_MAXV
Depth of the per-block cell-value scratch array v (v[0:MAXV-1]). v is indexed ONLY by a cell’s position WITHIN ITS BLOCK (mode-2 operands + the v[ci] write), never by the flat blk_cell_ptr + ci, so it needs to hold only the MAX cells in any SINGLE block — not the whole-program MAXCELL total. The streamed transformer’s largest block is 116 cells; a wider straight-line block (the postsynth high-register lock’s 90-value sum lowers to a single 270-cell block) needs more, so MAXV=512 covers the family. Under ConfigBram (the streamed engine
WPE_STREAM_MAXWR
WPE_STREAM_NREG
WPE_SYNTH_DEPTH
WPE_SYNTH_HEAP
WPE_SYNTH_MAXARG
WPE_SYNTH_MAXBLK
WPE_SYNTH_MAXCELL
WPE_SYNTH_MAXK
WPE_SYNTH_MAXLIV
WPE_SYNTH_MAXWR
WPE_SYNTH_NREG

Functions§

emit_wprog_engine_soc_testbench
A self-checking testbench that acts as the OVER-THE-WIRE HOST: it $readmemhs the concatenated wire bytes (wire_<name>.hex), per-vector byte lengths (lens_<name>.hex), and the eval_wprog_config oracle (oracle_<name>.hex), then for each vector bit-bangs its bytes onto uart_rx, decodes the returned frame (0xA5 sync + result[63:0] + cycles[31:0] + 0x5A), and checks the result == the oracle. Proves the WHOLE wire→synthesize→run→wire path ≡ the VM under iverilog.
emit_wprog_engine_soc_verilog
Emit the UNIVERSAL over-the-wire whole-program engine SoC: one fixed netlist that RECEIVES any fitting program’s flat image + params over a UART, fills its on-chip config memories (the loader FSM), seeds the register file from the params (INIT), runs the interpret st-machine (== eval_wprog_config == the VM), and streams the 64-bit result + run-cycle count back over the UART — then loops for the next program. The engine core (block-record wires, resolve/resolve_arg, the case(st) walk) is the SAME logic proven in emit_wprog_engine_verilog; only the config source (wire vs $readmemh) and the result path (UART vs port) differ. Integer functional units (synthesizable — the sequential div/mod/floordiv/pow datapath lives in the template; func_f is the DSP multiplies only). CLKS_PER_BIT sizes the UART bit period.
emit_wprog_engine_soc_verilog_backend
Emit the over-the-wire engine with an EXPLICIT memory backend — the first-class “choose the substrate” entry point. Registers/Bram are the proven paths; ConfigBram additionally maps the config image to block RAM (its registered-read FSM is the in-progress large-capacity build). caps[9] (MAXV) sizes the per-block value array v independently of the whole-program MAXCELL total.
emit_wprog_engine_soc_verilog_backend_debug
Emit the over-the-wire engine with an EXPLICIT memory backend AND an on-chip READBACK debug mode. When debug_readback is true the engine, at the load→run boundary (P_CLR done), FIRST streams a DEBUG FRAME back over the SAME UART tx — a fixed diagnostic slice of each major memory — then hands off to the interpret FSM, so real silicon can be compared against the known config-image/heap values byte-for-byte. The DEBUG FRAME is: sync 0xD8, then for each memory in order {cell_kind, cell_op0, cell_op1, blk_term_kind, heap} entries [0..8), each as a 64-bit BIG-ENDIAN word (8 bytes MSB-first, matching the result frame’s byte order), then end byte 0x8D — 1 + 588 + 1 = 322 bytes. The debug FSM walks each address through a REGISTERED BRAM read (issue → wait the latency → capture) and reuses the existing tx byte-sender. debug_readback=false emits RTL BYTE-IDENTICAL to emit_wprog_engine_soc_verilog_backend (the whole debug region is excised + the parameter is not injected). Readback is only meaningful on the ConfigBram streamed engine — asserted here.
emit_wprog_engine_soc_verilog_backend_rdw
Emit the over-the-wire engine with an EXPLICIT backend AND the SIM-ONLY read-during-write collision detectors active (RDW_DETECT=1). The detectors compare each memory’s registered-read address (heap_ra/sf_ra/ file_ra/v_ra) against every same-cycle write address during the interpret phase and $display + count a RDW-COLLISION whenever they coincide — a cross-port read-during-write that a real RAMB36 returns UNDEFINED for but this behavioral sim idealizes as READ_FIRST (old data). This is a DIAGNOSTIC build only; rdw_detect=false (every other entry point) EXCISES the whole detector region so the shipped netlist is BYTE-IDENTICAL to before this feature (the RDW_DETECT parameter is not even injected into the header).
emit_wprog_engine_soc_verilog_hw
Emit the DEPLOYED hold-safe over-the-wire engine SoC (the real bitstream): same logic as the synth engine, but with the WPE_HW_* cap profile that both synthesizes the loop’s compare correctly under yosys AND stays under the board’s hold-safe FF threshold. Runs loops/branches/writes, recursion (depth ≤3), and small collections over the wire on real silicon; proven ≡ eval_wprog_config (RTL) + loop gate-sim ≡VM.
emit_wprog_engine_soc_verilog_hw_probed
The deployed hold-safe engine (emit_wprog_engine_soc_verilog_hw) with its observer ports: the same logic and caps, plus probe_{running, st, pc, cell, sp, hp, cycles} outputs wired straight from the engine’s own registers, for the Fabric Terminal’s activity panel.
emit_wprog_engine_soc_verilog_synth
Emit the SYNTHESIZABLE over-the-wire engine SoC (real Nexys A7 bitstream): the same universal loader/INIT/ interpret/TX logic as emit_wprog_engine_soc_verilog, but with SYNTH_FUNC_F, the WPE_SYNTH_* cap profile, and (* ram_style="registers" *) forced on every config/state memory so the open flow keeps the UART-streamed config in flip-flops. Proven ≡ eval_wprog_config for fitting programs under iverilog, then synthesized through yosys→nextpnr-xilinx to a bitstream.
emit_wprog_engine_testbench
A self-checking testbench for the generic config-driven engine: it loads the params, pulses reset, runs to done, and compares result to the eval_wprog_config oracle in wpe_{name}_out.hex (params in wpe_{name}_in.hex; the program image in the wpe_{name}_*.hex config memories the engine loads).
emit_wprog_engine_verilog
Emit the GENERIC config-driven whole-program processor: ONE fixed microcoded engine that runs ANY program by loading its WImage into on-chip config memories (via $readmemh) and interpreting it — fetch a block record, evaluate its cells one per clock (operands resolved from the register file / cell-value array / constant pool through the live-in table), commit its register writes, then follow the terminator, with a heap state plane and a frame stack for Call/Return. Proven ≡ eval_wprog_config (≡ the VM) under iverilog for any loaded image. n_params sizes only the input ports; every other bound is a fixed generic capacity, so the SAME netlist runs every program that fits.
emit_wprog_testbench
A self-checking testbench for the whole-program engine: for each param vector, pulse reset, run the FSM to done, and compare result to the eval_wprog_config oracle in wprog_{name}_out.hex (params in wprog_{name}_in.hex). Proves the RTL — register file + block walk + FRAME STACK — matches the model.
emit_wprog_verilog
Emit the WHOLE-PROGRAM ENGINE as synthesizable RTL: a register file + block-PC + a SEQUENTIAL per-cell datapath (one cell per clock into a cell-value array v, so the ordered HEAP STATE PLANE — HeapRead / HeapWrite / HeapAlloc against a heap BRAM — threads exactly as the model does) + a terminator FSM (Jump/Branch/Return/CALL) + a FRAME STACK that saves the caller’s register file on every Call and restores it on Return. So the WHOLE language family — branchy control flow, loops, recursion, division, and collections/strings/structs on the heap — runs on silicon, proven bit-identical to eval_wprog_config (≡ the VM) under iverilog. Cells: Arith / Select / Func (divider) / HeapRead / HeapWrite / HeapAlloc.
eval_wprog_config
Interpret the flat config exactly as the RTL will: a register file seeded with params, a block-walk over the terminator graph, each block computing its cells (Arith/Select) over its live-in registers + prior cell values, committing its register writes, and branching on its terminator. Proven bit-identical to run_program_cfg_on_grid (≡ the VM) — the reference the whole-program RTL is checked against.
eval_wprog_config_counted
Like eval_wprog_config but also returns the number of BLOCK-STEPS executed. Each block is one spatial tile group evaluated in a single fabric cycle, so this count IS the whole-program fabric’s cycle count. ONE authoritative evaluator shared by the result path and any cycle measurement — no drifting hand-copy.
eval_wprog_config_counted_heap
As eval_wprog_config_counted but also returns the final HEAP, so a caller can render a Text result (a char-list [len, byte0..] at the returned pointer) back to a string for verification.
eval_wprog_config_counted_heap_preloaded
As eval_wprog_config_counted_heap but the heap starts PRE-LOADED with heap_init — the DATA-segment model of a chip that receives its weights streamed into memory separately from the kernel. loaded() lowers to heap pointer 0, so item k of loaded() reads heap_init[k] (heap_init[0] = the blob length, heap_init[1..] = the weights). The bump allocator is simply heap.len(), so runtime collections allocate PAST the pre-loaded region automatically — no separate hp seeding needed. This is what lets the streamed transformer run ≡VM on the engine without Push-building its ~3,136 weights into the config.
eval_wprog_config_faulted
The golden interpreter under an injected hardware fault (see WFault). An empty fault set is byte-identical to eval_wprog_config_counted_heap_preloaded.
eval_wprog_config_probed
As eval_wprog_config_counted_heap_preloaded but also returns the PEAK call-frame depth the run reached.
eval_wprog_config_run
THE golden interpreter of the flat config — the one body every result / step / heap / depth / output-tape query is a view of. Returns everything a run produces (WRun), including the Show tape.
func_code_needs_fpu
Does a Func cell with this code run on the FPU lane (WITH_FPU)? The float units (add/sub/mul/div/sqrt/ floor, the IEEE compares, int→float, approx-eq) exist only when the engine is built with the FPU, and so does popcount (it rides the FPU’s combinational lane; the FPU-less MUL_FUNC_F answers 0 for it). An image built WITH_FPU(0) has no datapath for any of these and must not be handed such a program. The integer units — div/mod/floor-div/pow and the DSP multiplies — run on every image.
program_calls_fabric
Lift a WHOLE multi-function program (with function CALLS and recursion) to a WProgConfig carrying Call terminators — the source→config half of recursion (the frame-stack eval_wprog_config is the eval half). fn_name is lifted FIRST so its entry is block 0 (the config’s entry); every function shares one global block index space, and a Call targets the callee function’s entry block. Correct-first scope: integer bodies + calls (LoadConst-int / Move / Add·Sub·Mul / AddAssign / Div·Mod·FloorDiv / comparisons / Call / Return / Jump family) — enough for fib/sumdown/gcd-style recursion; collections-in-recursion is the follow-on (the heap arms would be added the same way program_cfg_fabric has them).
program_main_fabric
Lift a WHOLE LOGOS PROGRAM from its ## Main: the code region the compiler emits before the first function body (terminated by Halt), plus every function it transitively calls. Main takes no parameters and its Halt is a top-level Return 0; its Shows become SHOW cells (kind 10) so the program’s OUTPUT — not just a return value — runs on the engine, ≡ vm_outcome(src).output line for line (program_output).
program_output
A whole program’s OUTPUT on the engine model — every Show rendered, one line each — the form that compares directly against vm_outcome(src).output.
render_show
Render one shown value the way the VM prints it: signed Int, fmt_f64 Float, the Text’s bytes, unsigned Word32, true/false.
serialize_program
Flatten a whole-function CFG fabric into the fabric’s flat config.
wprog_config_fits_engine
Does cfg fit the fixed generic engine? Returns Err naming the first exceeded dimension. A program that overflows any bound CANNOT run on the one-size netlist, so its image must be rejected LOUDLY here rather than producing silently-wrong results (a flat-array overflow corrupts later blocks’ cells/writes; an over-NREG register indexes a nonexistent file slot → 0 — the two failure modes the Mandelbrot kernel first exposed). The cell_*/wr_*/liv_reg/arg_op/konst memories are FLAT with accumulating per-block pointers, so their bounds are program TOTALS, not per-block. Called by wprog_config_image — every image is checked.
wprog_config_fits_hw_engine
Returns true iff cfg fits the DEPLOYED hold-safe hardware engine’s cap profile (WPE_HW_*) — the tighter bounds the real bitstream can hold. Programs must satisfy these to run over the wire on silicon.
wprog_config_fits_stream_engine
Returns Ok iff cfg fits the WPE_STREAM_* profile (the whole streamed transformer’s engine). Every dimension is a program TOTAL (the flat config memories accumulate across blocks). Named-loudly on overflow so a growing model is caught before it silently corrupts the flat config image.
wprog_config_fits_synth_engine
Returns true iff cfg fits the SYNTHESIZABLE engine’s smaller cap profile (a superset check of the generic engine’s fit): every over-the-wire program synthesized to hardware must satisfy these or it would overflow the register-backed config memories. The integer func units — Div/Mod/FloorDiv/Pow (sequential datapath) and MulHiU/MulOvfS (DSP) — all synthesize; only the float/popcount codes are rejected (the FPU follow-on).
wprog_config_image
Flatten a WProgConfig into its loadable image — the exact tables the generic engine walks: block records with base pointers into flat cell / write / arg / live-in pools, and a constant pool for Const operands.
wprog_config_image_unchecked
Build the flat wire image WITHOUT the generic-engine fit check — for streaming into a BIG-cap engine (the backend/scale work: the pipelined BRAM engine sized to run every benchmark) whose caps exceed the generic netlist. The caller guarantees the program fits the target cap profile; only the MAXK konst-table bound (shared by every profile) is still asserted here.
wprog_from_decision_fabric
Wrap a single-block if-converted DECISION FABRIC as a one-block flat config, so its cells run on the CROSSBAR whole-program engine (eval_wprog_config). The crossbar datapath has full interconnect — a cell may read any earlier cell’s value directly — so a COMPUTED comparison predicate needs no spatial routing. This runs constructs the nearest-neighbour placer (route_loop_body) cannot place, e.g. a Set’s dedup scan, on the fabric ≡ the VM (the decision fabric’s lowering is proven; only the placement was the limit).
wprog_nregs
The register-file width for a whole-program config: one 64-bit word per distinct register touched.