Skip to main content

logicaffeine_compile/codegen_sva/
coproc_core.rs

1//! coproc_core — the coprocessors, ISSUED as CPU instructions.
2//!
3//! The `crdt_merge` / `bigint_alu` / `bigint_mul` blocks are real synthesized silicon, but on
4//! their own they are standalone datapaths. This module wires them into a genuine (if minimal)
5//! core: a program counter, a combinational instruction ROM, an 8×256-bit register file, and a
6//! decode → issue → retire pipeline. Each cycle it fetches `rom[pc]`, decodes `{op,rd,rs,rt}`,
7//! ROUTES `R[rs]`/`R[rt]` into the addressed coprocessor, and RETIRES the result to `R[rd]` —
8//! exactly a CPU issuing a coprocessor instruction. Proven == the software oracle under
9//! iverilog, tied to the shipped `BigInt`/`GCounter` at the model level, and synthesized.
10//!
11//! This is a DEMONSTRATOR core (five ops); the full 113-op LOGOS soft-core with these
12//! coprocessors on its issue port is the in-progress integration (SILICON_CAPABILITY_MAP).
13//!
14//! ISA: `op` ∈ {0=HALT, 1=BADD, 2=BSUB, 3=BMUL, 4=CMERGE}; `rd/rs/rt` are 3-bit reg indices.
15
16use super::bignum_rtl::{limb_add, limb_mul, limb_sub};
17use super::crdt_rtl::merge_lane;
18
19/// One decoded instruction: `(op, rd, rs, rt)`.
20pub type Instr = (u8, u8, u8, u8);
21
22/// Initial register-file operands (R0, R1); the rest reset to zero. Chosen with A > B (so the
23/// subtract has no underflow) and full-width carry/borrow activity.
24pub 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
27/// The demo program: issue each coprocessor op once, retire to a distinct register, then halt.
28pub const COPROC_PROGRAM: &[Instr] = &[
29    (1, 2, 0, 1), // BADD   R2 = R0 + R1
30    (2, 3, 0, 1), // BSUB   R3 = R0 - R1
31    (3, 4, 0, 1), // BMUL   R4 = R0.lo64 * R1.lo64  (128-bit product in low limbs)
32    (4, 5, 0, 1), // CMERGE R5 = per-lane max of R0.lo128, R1.lo128
33    (0, 0, 0, 0), // HALT
34];
35
36/// The per-lane-max merge the RTL applies to a register's low 128 bits (four 32-bit lanes).
37fn 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
54/// A cycle-faithful Rust MODEL of the core: fetch → decode → issue → retire, over the same
55/// coprocessor semantics the RTL instantiates. Returns the final 8×256-bit register file. The
56/// iverilog testbench proves the RTL reproduces exactly this.
57pub 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,                              // HALT
65            1 => r[rd] = limb_add(&r[rs], &r[rt]).0, // BADD (sum; carry-out dropped, 256-bit reg)
66            2 => r[rd] = limb_sub(&r[rs], &r[rt]).0, // BSUB (difference)
67            3 => {
68                let p = limb_mul(r[rs][0], r[rt][0]); // BMUL (64×64→128 in low two limbs)
69                r[rd] = [p as u64, (p >> 64) as u64, 0, 0];
70            }
71            4 => {
72                let m = merge_low128(&r[rs], &r[rt]); // CMERGE (128-bit merged into low limbs)
73                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
85/// Emit the issuing core: PC + combinational ROM + 8×256-bit register file + decode/issue/retire,
86/// instantiating the real `bigint_alu` / `bigint_mul` / `crdt_merge` blocks as functional units.
87pub 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
157/// Self-checking testbench: run the core to halt, read back R2..R5, compare to the oracle from
158/// `coproc_exp.hex` (the Rust model's final register file). Prints PASS/FAIL.
159pub 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
191/// The oracle hex: the model's final 8×256-bit register file, one 64-hex-digit word per line.
192fn 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;