logicaffeine_compile/codegen_sva/
sequencer_rtl.rs1use super::fabric_grid::{emit_grid_verilog, eval_grid_all_with_heap, route_loop_body, Grid};
22use super::fabric_rtl::{ProgramCfgFabric, Src, Term};
23
24pub fn eval_sequencer(pf: &ProgramCfgFabric, params: &[i64]) -> (i64, u64) {
30 use std::collections::HashMap;
31 let mut file: HashMap<u16, i64> = (0..pf.n_params).map(|p| (p as u16, params[p])).collect();
32 let mut heap: Vec<i64> = Vec::new();
34 let mut blk = 0usize;
35 let mut steps = 0u64;
36 for _ in 0..10_000_000 {
37 steps += 1;
38 let b = &pf.blocks[blk];
39 let inputs: Vec<i64> = b
40 .inputs
41 .iter()
42 .map(|r| *file.get(r).unwrap_or(&0))
43 .collect();
44 let mut needed: Vec<Src> = b.writes.iter().map(|(_, s)| *s).collect();
45 let term_off = needed.len();
46 match &b.term {
47 Term::Jump(_) => {}
48 Term::Branch { cond, .. } => needed.push(*cond),
49 Term::Return(s) => needed.push(*s),
50 }
51 let g = route_loop_body(&b.body, b.inputs.len(), &needed);
52 let outs = eval_grid_all_with_heap(&g, &inputs, &mut heap);
53 for (i, (r, _)) in b.writes.iter().enumerate() {
54 file.insert(*r, outs[i]);
55 }
56 match &b.term {
57 Term::Jump(t) => blk = *t,
58 Term::Branch {
59 then_blk, else_blk, ..
60 } => {
61 blk = if outs[term_off] != 0 {
62 *then_blk
63 } else {
64 *else_blk
65 }
66 }
67 Term::Return(_) => return (outs[term_off], steps),
68 }
69 }
70 panic!("sequencer: controller did not return within the step budget (a back-edge slipped through?)")
71}
72
73fn n_regs(pf: &ProgramCfgFabric) -> usize {
76 let mut max_reg = 0u16;
77 for b in &pf.blocks {
78 for r in &b.inputs {
79 max_reg = max_reg.max(*r);
80 }
81 for (r, _) in &b.writes {
82 max_reg = max_reg.max(*r);
83 }
84 }
85 (max_reg as usize + 1).max(pf.n_params).max(1)
86}
87
88fn clog2(n: usize) -> usize {
90 let mut b = 1;
91 while (1usize << b) < n {
92 b += 1;
93 }
94 b
95}
96
97fn block_needed(b: &super::fabric_rtl::CfgBlock) -> (Vec<Src>, usize) {
101 let mut needed: Vec<Src> = b.writes.iter().map(|(_, s)| *s).collect();
102 let term_off = needed.len();
103 match &b.term {
104 Term::Jump(_) => {}
105 Term::Branch { cond, .. } => needed.push(*cond),
106 Term::Return(s) => needed.push(*s),
107 }
108 (needed, term_off)
109}
110
111pub fn emit_sequencer_verilog(name: &str, pf: &ProgramCfgFabric) -> String {
116 let nb = pf.blocks.len();
117 assert!(nb >= 1, "sequencer: a program needs at least one block");
118 let nreg = n_regs(pf);
119 let pcw = clog2(nb);
120
121 let mut grids: Vec<(Grid, usize, usize)> = Vec::with_capacity(nb); let mut modules = String::new();
124 for (k, b) in pf.blocks.iter().enumerate() {
125 let (needed, term_off) = block_needed(b);
126 assert!(
127 !needed.is_empty(),
128 "sequencer: block {k} of `{name}` has no outputs (no register writes and an unconditional Jump) — \
129 the honest bound (route_loop_body needs ≥1 output tap); a real block writes a register or ends in \
130 a conditional/return terminator"
131 );
132 let g = route_loop_body(&b.body, b.inputs.len(), &needed);
133 let n_out = g.all_outs().len();
134 modules.push_str(&emit_grid_verilog(&format!("seq_{name}_b{k}"), &g));
135 modules.push('\n');
136 grids.push((g, term_off, n_out));
137 }
138
139 let mut ports: Vec<String> = vec!["input clk".into(), "input rst".into()];
141 for p in 0..pf.n_params {
142 ports.push(format!("input [63:0] p{p}"));
143 }
144 ports.push("output [63:0] result".into());
145 ports.push("output done".into());
146 ports.push("output [63:0] cycles".into());
147
148 let mut body = String::new();
149 body.push_str(&format!(" reg [63:0] file [0:{}];\n", nreg - 1));
150 body.push_str(&format!(" reg [{}:0] pc;\n", pcw - 1));
151 body.push_str(" reg done_r;\n reg [63:0] result_r;\n reg [63:0] cyc_r;\n");
152 body.push_str(
153 " assign done = done_r;\n assign result = result_r;\n assign cycles = cyc_r;\n\n",
154 );
155
156 for r in 0..nreg {
158 body.push_str(&format!(" wire [63:0] fr_{r} = file[{r}];\n"));
159 }
160 body.push('\n');
161
162 for (k, b) in pf.blocks.iter().enumerate() {
164 let (_g, _term_off, n_out) = &grids[k];
165 for o in 0..*n_out {
166 body.push_str(&format!(" wire [63:0] b{k}_o{o};\n"));
167 }
168 let mut conns: Vec<String> = Vec::new();
169 for (j, r) in b.inputs.iter().enumerate() {
170 conns.push(format!(".in{j}(fr_{r})"));
171 }
172 if *n_out == 1 {
173 conns.push(format!(".result(b{k}_o0)"));
174 } else {
175 for o in 0..*n_out {
176 conns.push(format!(".out{o}(b{k}_o{o})"));
177 }
178 }
179 body.push_str(&format!(
180 " grid_seq_{name}_b{k} u_b{k} ({});\n\n",
181 conns.join(", ")
182 ));
183 }
184
185 body.push_str(" always @(posedge clk) begin\n");
187 body.push_str(" if (rst) begin\n");
188 body.push_str(&format!(" pc <= {pcw}'d0;\n done_r <= 1'b0;\n result_r <= 64'd0;\n cyc_r <= 64'd0;\n"));
189 for r in 0..nreg {
190 if r < pf.n_params {
191 body.push_str(&format!(" file[{r}] <= p{r};\n"));
192 } else {
193 body.push_str(&format!(" file[{r}] <= 64'd0;\n"));
194 }
195 }
196 body.push_str(" end else if (!done_r) begin\n");
197 body.push_str(" cyc_r <= cyc_r + 64'd1;\n");
198 body.push_str(" case (pc)\n");
199 for (k, b) in pf.blocks.iter().enumerate() {
200 let (_g, term_off, _n_out) = &grids[k];
201 body.push_str(&format!(" {pcw}'d{k}: begin\n"));
202 for (i, (r, _)) in b.writes.iter().enumerate() {
203 body.push_str(&format!(" file[{r}] <= b{k}_o{i};\n"));
204 }
205 match &b.term {
206 Term::Jump(t) => {
207 body.push_str(&format!(" pc <= {pcw}'d{t};\n"));
208 }
209 Term::Branch {
210 then_blk, else_blk, ..
211 } => {
212 body.push_str(&format!(
213 " pc <= (b{k}_o{term_off} != 64'd0) ? {pcw}'d{then_blk} : {pcw}'d{else_blk};\n"
214 ));
215 }
216 Term::Return(_) => {
217 body.push_str(&format!(
218 " result_r <= b{k}_o{term_off};\n done_r <= 1'b1;\n"
219 ));
220 }
221 }
222 body.push_str(" end\n");
223 }
224 body.push_str(" default: done_r <= 1'b1;\n");
225 body.push_str(" endcase\n");
226 body.push_str(" end\n end\n");
227
228 format!(
229 "// seq_{name} — the AUTONOMOUS block-walk sequencer: a register file + block program-counter +\n\
230 // terminator FSM (Jump/Branch/Return) + cycle counter around {nb} per-block placed grids. Walks the\n\
231 // whole function itself, one block per clock, ≡ run_program_cfg_on_grid ≡ the VM.\n\
232 {modules}\
233 module seq_{name}(\n {ports}\n);\n{body}endmodule\n",
234 ports = ports.join(",\n "),
235 )
236}
237
238pub fn emit_sequencer_testbench(name: &str, n_params: usize, n_vectors: usize) -> String {
242 let mut decls = String::new();
243 if n_params > 0 {
244 decls.push_str(&format!(
245 " reg [63:0] pin [0:{}];\n",
246 n_params * n_vectors - 1
247 ));
248 }
249 decls.push_str(&format!(" reg [63:0] oout [0:{}];\n", n_vectors - 1));
250 decls.push_str(&format!(" reg [63:0] ocyc [0:{}];\n", n_vectors - 1));
251 for p in 0..n_params {
252 decls.push_str(&format!(" reg [63:0] p{p};\n"));
253 }
254
255 let mut dut_ports: Vec<String> = vec![".clk(clk)".into(), ".rst(rst)".into()];
256 for p in 0..n_params {
257 dut_ports.push(format!(".p{p}(p{p})"));
258 }
259 dut_ports.push(".result(result)".into());
260 dut_ports.push(".done(done)".into());
261 dut_ports.push(".cycles(cycles)".into());
262
263 let readin = if n_params > 0 {
264 format!(" $readmemh(\"seq_{name}_in.hex\", pin);\n")
265 } else {
266 String::new()
267 };
268 let mut load = String::new();
269 for p in 0..n_params {
270 load.push_str(&format!(" p{p} = pin[i*{n_params}+{p}];\n"));
271 }
272
273 format!(
274 r#"// tb_seq_{name} — the autonomous sequencer vs the RUNNING VM (result + exact block-step count).
275`timescale 1ns/1ps
276module tb_seq_{name};
277 reg clk = 1'b0;
278 reg rst;
279{decls} wire [63:0] result;
280 wire done;
281 wire [63:0] cycles;
282 integer i, errors, guard;
283 seq_{name} dut({ports});
284 always #5 clk = ~clk;
285 initial begin
286 errors = 0;
287{readin} $readmemh("seq_{name}_out.hex", oout);
288 $readmemh("seq_{name}_cyc.hex", ocyc);
289 for (i = 0; i < {n_vectors}; i = i + 1) begin
290{load} rst = 1'b1; @(posedge clk); #1;
291 rst = 1'b0;
292 guard = 0;
293 while (done !== 1'b1 && guard < 100000) begin @(posedge clk); #1; guard = guard + 1; end
294 if (result !== oout[i]) begin errors = errors + 1; $display("FAIL case %0d: result got %0d exp %0d", i, result, oout[i]); end
295 if (cycles !== ocyc[i]) begin errors = errors + 1; $display("FAIL case %0d: cycles got %0d exp %0d", i, cycles, ocyc[i]); end
296 end
297 if (errors == 0) $display("PASS: {n_vectors} sequencer runs match the VM (result + cycle count)");
298 else $display("FAIL: %0d sequencer mismatches", errors);
299 $finish;
300 end
301endmodule
302"#,
303 ports = dut_ports.join(", "),
304 )
305}