logicaffeine_compile/codegen_sva/
coproc_core.rs1use super::bignum_rtl::{limb_add, limb_mul, limb_sub};
17use super::crdt_rtl::merge_lane;
18
19pub type Instr = (u8, u8, u8, u8);
21
22pub const COPROC_A: [u64; 4] = [0xFFFF_FFFF_FFFF_FFFF, 0x1111_1111_1111_1111, 0x2, 0x3];
25pub const COPROC_B: [u64; 4] = [1, 1, 1, 1];
26
27pub const COPROC_PROGRAM: &[Instr] = &[
29 (1, 2, 0, 1), (2, 3, 0, 1), (3, 4, 0, 1), (4, 5, 0, 1), (0, 0, 0, 0), ];
35
36fn merge_low128(a: &[u64; 4], b: &[u64; 4]) -> [u64; 2] {
38 let lanes = |x: &[u64; 4]| {
39 [
40 x[0] as u32,
41 (x[0] >> 32) as u32,
42 x[1] as u32,
43 (x[1] >> 32) as u32,
44 ]
45 };
46 let (la, lb) = (lanes(a), lanes(b));
47 let m: Vec<u32> = (0..4).map(|i| merge_lane(la[i], lb[i])).collect();
48 [
49 (m[0] as u64) | ((m[1] as u64) << 32),
50 (m[2] as u64) | ((m[3] as u64) << 32),
51 ]
52}
53
54pub fn run_core(a: &[u64; 4], b: &[u64; 4], program: &[Instr]) -> [[u64; 4]; 8] {
58 let mut r = [[0u64; 4]; 8];
59 r[0] = *a;
60 r[1] = *b;
61 for &(op, rd, rs, rt) in program {
62 let (rd, rs, rt) = (rd as usize, rs as usize, rt as usize);
63 match op {
64 0 => break, 1 => r[rd] = limb_add(&r[rs], &r[rt]).0, 2 => r[rd] = limb_sub(&r[rs], &r[rt]).0, 3 => {
68 let p = limb_mul(r[rs][0], r[rt][0]); r[rd] = [p as u64, (p >> 64) as u64, 0, 0];
70 }
71 4 => {
72 let m = merge_low128(&r[rs], &r[rt]); r[rd] = [m[0], m[1], 0, 0];
74 }
75 _ => panic!("coproc_core: unknown op {op}"),
76 }
77 }
78 r
79}
80
81fn hex256(x: &[u64; 4]) -> String {
82 format!("256'h{:016x}{:016x}{:016x}{:016x}", x[3], x[2], x[1], x[0])
83}
84
85pub fn emit_coproc_core_verilog(a: &[u64; 4], b: &[u64; 4], program: &[Instr]) -> String {
88 let mut rom = String::new();
89 for (i, &(op, rd, rs, rt)) in program.iter().enumerate() {
90 rom.push_str(&format!(
91 " 4'd{i}: instr = {{4'd{op}, 3'd{rd}, 3'd{rs}, 3'd{rt}}};\n"
92 ));
93 }
94 format!(
95 r#"// coproc_core — a minimal core that ISSUES coprocessor instructions to the real
96// synthesized blocks (bigint_alu / bigint_mul / crdt_merge) and RETIRES results to a register
97// file: fetch (pc->rom) -> decode (op/rd/rs/rt) -> route R[rs]/R[rt] to the coprocessor ->
98// latch to R[rd] -> advance pc. Proven == the software oracle by iverilog. op: 0=HALT 1=BADD
99// 2=BSUB 3=BMUL 4=CMERGE. Read back the register file via rsel/rout after halt.
100module coproc_core(input clk, input rst, input [2:0] rsel, output [255:0] rout, output halted);
101 reg [255:0] R [0:7];
102 reg [3:0] pc;
103 reg done;
104 integer j;
105
106 // Combinational instruction ROM + decode.
107 reg [12:0] instr;
108 always @(*) begin
109 case (pc)
110{rom} default: instr = {{4'd0, 9'd0}}; // HALT
111 endcase
112 end
113 wire [3:0] op = instr[12:9];
114 wire [2:0] rd = instr[8:6];
115 wire [2:0] rs = instr[5:3];
116 wire [2:0] rt = instr[2:0];
117
118 // The coprocessor functional units, fed the decoded source registers.
119 wire [255:0] alu_sum, alu_diff;
120 wire alu_cout, alu_borrow, alu_eq, alu_lt;
121 bigint_alu u_alu(.a(R[rs]), .b(R[rt]), .sum(alu_sum), .cout(alu_cout),
122 .diff(alu_diff), .borrow(alu_borrow), .eq(alu_eq), .lt(alu_lt));
123 wire [127:0] mul_prod;
124 bigint_mul u_mul(.a(R[rs][63:0]), .b(R[rt][63:0]), .prod(mul_prod));
125 wire [127:0] cm_merged;
126 wire [33:0] cm_value;
127 crdt_merge u_cm(.a(R[rs][127:0]), .b(R[rt][127:0]), .merged(cm_merged), .value(cm_value));
128
129 assign rout = R[rsel];
130 assign halted = done;
131
132 always @(posedge clk) begin
133 if (rst) begin
134 pc <= 4'd0; done <= 1'b0;
135 R[0] <= {a}; R[1] <= {b};
136 for (j = 2; j < 8; j = j + 1) R[j] <= 256'd0;
137 end else if (!done) begin
138 case (op)
139 4'd0: done <= 1'b1; // HALT
140 4'd1: R[rd] <= alu_sum; // BADD
141 4'd2: R[rd] <= alu_diff; // BSUB
142 4'd3: R[rd] <= {{128'd0, mul_prod}}; // BMUL
143 4'd4: R[rd] <= {{128'd0, cm_merged}}; // CMERGE
144 default: ;
145 endcase
146 if (op != 4'd0) pc <= pc + 4'd1;
147 end
148 end
149endmodule
150"#,
151 rom = rom,
152 a = hex256(a),
153 b = hex256(b),
154 )
155}
156
157pub fn emit_coproc_core_testbench() -> String {
160 r#"// tb_coproc_core — the issuing core vs the software oracle's retired register file.
161`timescale 1ns/1ps
162module tb_coproc_core;
163 reg clk = 0, rst = 1;
164 reg [2:0] rsel = 0;
165 wire [255:0] rout;
166 wire halted;
167 reg [255:0] exp [0:7];
168 integer errors = 0, i;
169 coproc_core dut(.clk(clk), .rst(rst), .rsel(rsel), .rout(rout), .halted(halted));
170 always #5 clk = ~clk;
171 initial begin
172 $readmemh("coproc_exp.hex", exp);
173 #12 rst = 0;
174 for (i = 0; i < 64 && !halted; i = i + 1) @(posedge clk);
175 if (!halted) begin $display("FAIL: core never halted"); $finish; end
176 for (i = 2; i <= 5; i = i + 1) begin
177 rsel = i[2:0]; #1;
178 if (rout !== exp[i]) begin errors = errors + 1; $display("FAIL R%0d", i); end
179 end
180 if (errors == 0)
181 $display("PASS: coproc_core issued BADD/BSUB/BMUL/CMERGE and retired results matching the oracle");
182 else
183 $display("FAIL: %0d retired-register mismatches", errors);
184 $finish;
185 end
186endmodule
187"#
188 .to_string()
189}
190
191fn coproc_exp_hex(a: &[u64; 4], b: &[u64; 4], program: &[Instr]) -> String {
193 let r = run_core(a, b, program);
194 let mut s = String::new();
195 for reg in &r {
196 s.push_str(&format!(
197 "{:016x}{:016x}{:016x}{:016x}\n",
198 reg[3], reg[2], reg[1], reg[0]
199 ));
200 }
201 s
202}
203
204#[cfg(test)]
205#[path = "../../../logicaffeine_tests/unit/logicaffeine_compile/codegen_sva/coproc_core/tests.rs"]
206mod tests;