logicaffeine_compile/codegen_sva/
xpu_soc_rtl.rs1use super::block_engine_rtl::BlockConfig;
18
19const CELL_BYTES: usize = 20;
21
22pub fn block_stream_bytes(cfg: &BlockConfig, inputs: &[i64], k: usize, n_in: usize) -> Vec<u8> {
24 assert_eq!(cfg.ctrl.len(), k, "config must be padded to k cells");
25 let mut out = Vec::with_capacity(k * CELL_BYTES + 2 + n_in * 8);
26 for i in 0..k {
27 out.extend_from_slice(&cfg.ctrl[i].to_be_bytes()); out.extend_from_slice(&(cfg.ac[i] as u64).to_be_bytes()); out.extend_from_slice(&(cfg.bc[i] as u64).to_be_bytes()); }
31 out.extend_from_slice(&(cfg.result as u16).to_be_bytes()); for j in 0..n_in {
33 let v = if j < inputs.len() { inputs[j] } else { 0 };
34 out.extend_from_slice(&(v as u64).to_be_bytes()); }
36 out
37}
38
39fn be_bytes(base: usize, n: usize) -> String {
41 let parts: Vec<String> = (0..n).map(|b| format!("mem[{}]", base + b)).collect();
42 format!("{{{}}}", parts.join(", "))
43}
44
45fn in_mux(field: &str, n_in: usize) -> String {
46 let mut acc = "64'sd0".to_string();
47 for j in (0..n_in).rev() {
48 acc = format!("({field}==4'd{j}) ? iv_{j} : ({acc})");
49 }
50 acc
51}
52
53fn v_mux(field: &str, upto: usize) -> String {
54 let mut acc = "64'sd0".to_string();
55 for j in (0..upto).rev() {
56 acc = format!("({field}==4'd{j}) ? v_{j} : ({acc})");
57 }
58 acc
59}
60
61pub fn emit_xpu_soc_verilog(name: &str, k: usize, n_in: usize) -> String {
65 let total = k * CELL_BYTES + 2 + n_in * 8;
66 let res_base = k * CELL_BYTES;
67 let in_base = res_base + 2;
68
69 let mut asm = String::new();
70 for i in 0..k {
72 let base = i * CELL_BYTES;
73 asm.push_str(&format!(" wire [31:0] ct_{i} = {};\n", be_bytes(base, 4)));
74 asm.push_str(&format!(
75 " wire signed [63:0] ac_{i} = {};\n",
76 be_bytes(base + 4, 8)
77 ));
78 asm.push_str(&format!(
79 " wire signed [63:0] bc_{i} = {};\n",
80 be_bytes(base + 12, 8)
81 ));
82 }
83 asm.push_str(&format!(" wire [15:0] res = {};\n", be_bytes(res_base, 2)));
84 for j in 0..n_in {
85 asm.push_str(&format!(
86 " wire signed [63:0] iv_{j} = {};\n",
87 be_bytes(in_base + j * 8, 8)
88 ));
89 }
90
91 let mut comp = String::new();
93 for i in 0..k {
94 let ain = in_mux(&format!("ct_{i}[9:6]"), n_in);
95 let bin = in_mux(&format!("ct_{i}[15:12]"), n_in);
96 let av = if i == 0 {
97 "64'sd0".to_string()
98 } else {
99 v_mux(&format!("ct_{i}[9:6]"), i)
100 };
101 let bv = if i == 0 {
102 "64'sd0".to_string()
103 } else {
104 v_mux(&format!("ct_{i}[15:12]"), i)
105 };
106 comp.push_str(&format!(
107 " wire signed [63:0] opa_{i} = (ct_{i}[5:4]==2'd0) ? ({ain}) : (ct_{i}[5:4]==2'd1) ? ac_{i} : ({av});\n"
108 ));
109 comp.push_str(&format!(
110 " wire signed [63:0] opb_{i} = (ct_{i}[11:10]==2'd0) ? ({bin}) : (ct_{i}[11:10]==2'd1) ? bc_{i} : ({bv});\n"
111 ));
112 comp.push_str(&format!(
113 " wire signed [63:0] v_{i} = alu_f(ct_{i}[3:0], opa_{i}, opb_{i});\n"
114 ));
115 }
116 let mut res_sel = "64'sd0".to_string();
117 for i in (0..k).rev() {
118 res_sel = format!("(res==16'd{i}) ? v_{i} : ({res_sel})");
119 }
120
121 format!(
122 "// xpu_{name} — the self-synthesizing bytecode XPU SoC. A block's bytecode is streamed in byte-by-byte\n\
123 // (byte_in/byte_valid), buffered on-chip, assembled into the reconfigurable engine's config + inputs,\n\
124 // and RUN — bytecode over the wire → result, no host help. (nexys_soc's load-then-run, payload = the\n\
125 // spatial execute engine's config instead of soft-core opcodes.) Proven ≡ VM by xpu_soc_lock.\n\
126 module xpu_{name}(input clk, input rst, input [7:0] byte_in, input byte_valid,\n\
127 \x20 output signed [63:0] result, output done);\n\
128 \x20 localparam integer TOTAL = {total};\n\
129 \x20 reg [7:0] mem [0:{tm1}];\n\
130 \x20 reg [15:0] cnt;\n\
131 \x20 reg loaded;\n\
132 \x20 always @(posedge clk) begin\n\
133 \x20 if (rst) begin cnt <= 16'd0; loaded <= 1'b0; end\n\
134 \x20 else if (!loaded && byte_valid) begin\n\
135 \x20 mem[cnt] <= byte_in;\n\
136 \x20 if (cnt == TOTAL-1) loaded <= 1'b1;\n\
137 \x20 cnt <= cnt + 16'd1;\n\
138 \x20 end\n\
139 \x20 end\n\
140 \x20 assign done = loaded;\n\
141 \x20 function signed [63:0] alu_f(input [3:0] op, input signed [63:0] a, input signed [63:0] b);\n\
142 \x20 case (op)\n\
143 \x20 4'd0: alu_f = a + b; 4'd1: alu_f = a - b; 4'd2: alu_f = a * b;\n\
144 \x20 4'd3: alu_f = a & b; 4'd4: alu_f = a | b; 4'd5: alu_f = a ^ b;\n\
145 \x20 4'd6: alu_f = a <<< b; 4'd7: alu_f = a >>> b; default: alu_f = 64'sd0;\n\
146 \x20 endcase\n\
147 \x20 endfunction\n\
148 {asm}{comp} assign result = {res_sel};\n\
149 endmodule\n",
150 tm1 = total - 1,
151 )
152}
153
154pub fn emit_xpu_soc_testbench(name: &str, n_cases: usize, frame_len: usize) -> String {
158 format!(
159 r#"// tb_xpu_{name} — stream each program's bytecode frame in over the wire; result must equal the VM.
160`timescale 1ns/1ps
161module tb_xpu_{name};
162 reg clk = 1'b0;
163 reg rst;
164 reg [7:0] byte_in;
165 reg byte_valid;
166 wire signed [63:0] result;
167 wire done;
168 reg [7:0] frames [0:{flast}]; // n_cases frames concatenated, {frame_len} bytes each
169 reg signed [63:0] eout [0:{clast}];
170 integer i, b, errors, guard;
171 xpu_{name} dut(.clk(clk), .rst(rst), .byte_in(byte_in), .byte_valid(byte_valid), .result(result), .done(done));
172 always #5 clk = ~clk;
173 initial begin
174 errors = 0;
175 $readmemh("xpu_{name}_frames.hex", frames);
176 $readmemh("xpu_{name}_eout.hex", eout);
177 for (i = 0; i < {n_cases}; i = i + 1) begin
178 rst = 1'b1; byte_valid = 1'b0; @(posedge clk); #1;
179 rst = 1'b0;
180 for (b = 0; b < {frame_len}; b = b + 1) begin
181 byte_in = frames[i*{frame_len}+b]; byte_valid = 1'b1; @(posedge clk); #1;
182 end
183 byte_valid = 1'b0;
184 guard = 0;
185 while (done !== 1'b1 && guard < 100000) begin @(posedge clk); #1; guard = guard + 1; end
186 #1;
187 if (result !== eout[i]) begin errors = errors + 1; $display("FAIL case %0d: got %0d exp %0d", i, result, eout[i]); end
188 end
189 if (errors == 0) $display("PASS: {n_cases} bytecode frames run on the XPU SoC match the VM");
190 else $display("FAIL: %0d XPU SoC mismatches", errors);
191 $finish;
192 end
193endmodule
194"#,
195 flast = n_cases * frame_len - 1,
196 clast = n_cases - 1,
197 )
198}