logicaffeine_compile/codegen_sva/
block_engine_rtl.rs1use super::fabric_rtl::{AluOp, FabricOp, Src};
19
20pub struct BlockConfig {
24 pub ctrl: Vec<u32>,
25 pub ac: Vec<i64>,
26 pub bc: Vec<i64>,
27 pub result: usize,
28}
29
30fn alu_code(op: AluOp) -> u32 {
31 match op {
32 AluOp::Add => 0,
33 AluOp::Sub => 1,
34 AluOp::Mul => 2,
35 AluOp::And => 3,
36 AluOp::Or => 4,
37 AluOp::Xor => 5,
38 AluOp::Shl => 6,
39 AluOp::Shr => 7,
40 AluOp::UShr => 8,
41 }
42}
43
44fn enc(s: Src) -> (u32, u32, i64) {
46 match s {
47 Src::In(k) => (0, k as u32, 0),
48 Src::Const(c) => (1, 0, c),
49 Src::Val(j) => (2, j as u32, 0),
50 }
51}
52
53pub fn serialize_block(ops: &[FabricOp], result: usize, k: usize) -> BlockConfig {
56 assert!(
57 ops.len() <= k,
58 "block has {} cells > engine capacity {k}",
59 ops.len()
60 );
61 let mut ctrl = vec![0u32; k];
62 let mut ac = vec![0i64; k];
63 let mut bc = vec![0i64; k];
64 for (i, o) in ops.iter().enumerate() {
65 let (am, ar, acv) = enc(o.a);
66 let (bm, br, bcv) = enc(o.b);
67 ctrl[i] = alu_code(o.op) | (am << 4) | (ar << 6) | (bm << 10) | (br << 12);
68 ac[i] = acv;
69 bc[i] = bcv;
70 }
71 BlockConfig {
72 ctrl,
73 ac,
74 bc,
75 result,
76 }
77}
78
79fn in_mux(field: &str, n_in: usize) -> String {
81 let mut acc = "64'sd0".to_string();
82 for k in (0..n_in).rev() {
83 acc = format!("({field}==4'd{k}) ? in{k} : ({acc})");
84 }
85 acc
86}
87
88fn v_mux(field: &str, upto: usize) -> String {
90 let mut acc = "64'sd0".to_string();
91 for j in (0..upto).rev() {
92 acc = format!("({field}==4'd{j}) ? v_{j} : ({acc})");
93 }
94 acc
95}
96
97pub fn emit_block_engine_verilog(name: &str, k: usize, n_in: usize) -> String {
101 let in_ports: String = (0..n_in)
102 .map(|j| format!("input signed [63:0] in{j},\n "))
103 .collect();
104
105 let mut body = String::new();
106 for i in 0..k {
107 body.push_str(&format!(" wire [31:0] ct_{i} = ctrl[{i}];\n"));
108 let ain = in_mux(&format!("ct_{i}[9:6]"), n_in);
109 let bin = in_mux(&format!("ct_{i}[15:12]"), n_in);
110 let av = if i == 0 {
111 "64'sd0".to_string()
112 } else {
113 v_mux(&format!("ct_{i}[9:6]"), i)
114 };
115 let bv = if i == 0 {
116 "64'sd0".to_string()
117 } else {
118 v_mux(&format!("ct_{i}[15:12]"), i)
119 };
120 body.push_str(&format!(
121 " wire signed [63:0] opa_{i} = (ct_{i}[5:4]==2'd0) ? ({ain}) : (ct_{i}[5:4]==2'd1) ? ac[{i}] : ({av});\n"
122 ));
123 body.push_str(&format!(
124 " wire signed [63:0] opb_{i} = (ct_{i}[11:10]==2'd0) ? ({bin}) : (ct_{i}[11:10]==2'd1) ? bc[{i}] : ({bv});\n"
125 ));
126 body.push_str(&format!(
127 " wire signed [63:0] v_{i} = alu_f(ct_{i}[3:0], opa_{i}, opb_{i});\n"
128 ));
129 }
130 let mut res_sel = "64'sd0".to_string();
132 for i in (0..k).rev() {
133 res_sel = format!("(res=={rw}'d{i}) ? v_{i} : ({res_sel})", rw = 16);
134 }
135
136 format!(
137 "// bexec_{name} — a UNIVERSAL {k}-cell block EXECUTE engine over {n_in} inputs. Each cell's op and\n\
138 // operand sources are RUNTIME config ($readmemh); loading a decoded block's op list runs it on ONE\n\
139 // reconfigurable datapath. Feed-forward → combinational. Reproduces eval_fabric; proven ≡ the VM.\n\
140 module bexec_{name}(\n {in_ports}output signed [63:0] result\n);\n\
141 \x20 reg [31:0] ctrl [0:{km1}];\n\
142 \x20 reg signed [63:0] ac [0:{km1}];\n\
143 \x20 reg signed [63:0] bc [0:{km1}];\n\
144 \x20 reg [15:0] res; reg [63:0] rhdr [0:0];\n\
145 \x20 initial begin\n\
146 \x20 $readmemh(\"bexec_{name}_ctrl.hex\", ctrl);\n\
147 \x20 $readmemh(\"bexec_{name}_ac.hex\", ac);\n\
148 \x20 $readmemh(\"bexec_{name}_bc.hex\", bc);\n\
149 \x20 $readmemh(\"bexec_{name}_res.hex\", rhdr);\n\
150 \x20 res = rhdr[0][15:0];\n\
151 \x20 end\n\
152 \x20 function signed [63:0] alu_f(input [3:0] op, input signed [63:0] a, input signed [63:0] b);\n\
153 \x20 case (op)\n\
154 \x20 4'd0: alu_f = a + b; 4'd1: alu_f = a - b; 4'd2: alu_f = a * b;\n\
155 \x20 4'd3: alu_f = a & b; 4'd4: alu_f = a | b; 4'd5: alu_f = a ^ b;\n\
156 \x20 4'd6: alu_f = a <<< b; 4'd7: alu_f = a >>> b; 4'd8: alu_f = $unsigned(a) >> b; default: alu_f = 64'sd0;\n\
157 \x20 endcase\n\
158 \x20 endfunction\n\
159 {body} assign result = {res_sel};\n\
160 endmodule\n",
161 km1 = k - 1,
162 )
163}
164
165pub fn emit_block_engine_testbench(name: &str, n_in: usize, n_vectors: usize) -> String {
168 let decls: String = (0..n_in)
169 .map(|k| format!(" reg signed [63:0] in{k};\n"))
170 .collect();
171 let ports: String = {
172 let mut v: Vec<String> = (0..n_in).map(|k| format!(".in{k}(in{k})")).collect();
173 v.push(".result(result)".to_string());
174 v.join(", ")
175 };
176 let pin = if n_in > 0 {
177 format!(" reg signed [63:0] pin [0:{}];\n", n_in * n_vectors - 1)
178 } else {
179 String::new()
180 };
181 let readpin = if n_in > 0 {
182 format!(" $readmemh(\"bexec_{name}_pin.hex\", pin);\n")
183 } else {
184 String::new()
185 };
186 let drive: String = (0..n_in)
187 .map(|k| format!(" in{k} = pin[i*{n_in}+{k}];\n"))
188 .collect();
189
190 format!(
191 r#"// tb_bexec_{name} — the block execute engine (config = a decoded op list) vs the RUNNING VM.
192`timescale 1ns/1ps
193module tb_bexec_{name};
194{decls}{pin} reg signed [63:0] eout [0:{olast}];
195 wire signed [63:0] result;
196 integer i, errors;
197 bexec_{name} dut({ports});
198 initial begin
199 errors = 0;
200{readpin} $readmemh("bexec_{name}_eout.hex", eout);
201 for (i = 0; i < {n_vectors}; i = i + 1) begin
202{drive} #1;
203 if (result !== eout[i]) begin errors = errors + 1; $display("FAIL case %0d: got %0d exp %0d", i, result, eout[i]); end
204 end
205 if (errors == 0) $display("PASS: {n_vectors} block-engine evaluations match the VM");
206 else $display("FAIL: %0d block-engine mismatches", errors);
207 $finish;
208 end
209endmodule
210"#,
211 olast = n_vectors - 1,
212 )
213}