Skip to main content

logicaffeine_compile/codegen_sva/
bignum_rtl.rs

1//! bignum_rtl — the numeric tower's foundation in silicon: the BigInt datapath.
2//!
3//! `logicaffeine_base::numeric::BigInt` is sign + little-endian u64 limbs; its magnitude
4//! arithmetic (`mag_add`/`mag_sub`/`mag_cmp`/`mag_mul`) is schoolbook limb work. This module
5//! emits that as synthesizable silicon:
6//!   • `bigint_alu` — a 256-bit (4-limb) add / subtract / unsigned-compare datapath (shared
7//!     carry-borrow chain);
8//!   • `bigint_mul` — the 64×64→128 limb multiplier that `mag_mul` iterates.
9//! Each is proven TWO ways: byte-identical to its software oracle under iverilog (random +
10//! edge vectors), AND — the same bar the flagship ALU meets — correct by our OWN certified
11//! SAT (exhaustive algebraic identities: the adder is a group, the comparator a total order,
12//! the multiplier commutative and distributive over add).
13//!
14//! Arbitrary-precision growth and Rational / Decimal / Complex ride the numeric-tower
15//! coprocessor built ON this datapath — roadmap. The full-width 256-bit product is this
16//! `bigint_mul` primitive composed with the (proven) `bigint_alu` accumulate, exactly as
17//! `mag_mul` composes limb products with carry-adds.
18
19pub const LIMBS: usize = 4; // 4 × 64-bit = 256-bit operands
20
21/// The software limb-add — a faithful mirror of `logicaffeine_base::numeric::mag_add`
22/// (little-endian u64 limbs, u128 carry): the low `LIMBS` sum limbs and the carry-out.
23pub fn limb_add(a: &[u64; LIMBS], b: &[u64; LIMBS]) -> ([u64; LIMBS], u64) {
24    let mut sum = [0u64; LIMBS];
25    let mut carry = 0u128;
26    for i in 0..LIMBS {
27        let s = a[i] as u128 + b[i] as u128 + carry;
28        sum[i] = s as u64;
29        carry = s >> 64;
30    }
31    (sum, carry as u64) // carry ∈ {0,1}
32}
33
34/// The software limb-subtract — mirror of `mag_sub`'s borrow chain, but total (defined for
35/// every a, b): the low `LIMBS` difference limbs and the borrow-out (`1` iff a < b, i.e. the
36/// subtraction underflowed). When `borrow == 0` the difference equals `mag_sub(a, b)`.
37pub fn limb_sub(a: &[u64; LIMBS], b: &[u64; LIMBS]) -> ([u64; LIMBS], u64) {
38    let mut diff = [0u64; LIMBS];
39    let mut borrow = 0i128;
40    for i in 0..LIMBS {
41        let d = a[i] as i128 - b[i] as i128 - borrow;
42        if d < 0 {
43            diff[i] = (d + (1i128 << 64)) as u64;
44            borrow = 1;
45        } else {
46            diff[i] = d as u64;
47            borrow = 0;
48        }
49    }
50    (diff, borrow as u64) // borrow ∈ {0,1}
51}
52
53/// Unsigned magnitude compare — mirror of `mag_cmp` (most-significant limb dominates).
54pub fn limb_lt(a: &[u64; LIMBS], b: &[u64; LIMBS]) -> bool {
55    for i in (0..LIMBS).rev() {
56        if a[i] != b[i] {
57            return a[i] < b[i];
58        }
59    }
60    false
61}
62
63/// The limb multiply `mag_mul` iterates: one 64×64→128 partial product.
64pub fn limb_mul(a: u64, b: u64) -> u128 {
65    (a as u128) * (b as u128)
66}
67
68/// The low 256 bits of `a * b` — schoolbook over `LIMBS` limbs, carries past the top limb dropped
69/// (the wraparound a fixed 256-bit datapath produces). This is `bigint_mul` (64×64) composed with
70/// the proven `bigint_alu` carry-adds; the reference for the soft-core's `BigIntWide` Mul.
71pub fn wmul256(a: &[u64; LIMBS], b: &[u64; LIMBS]) -> [u64; LIMBS] {
72    let mut out = [0u64; LIMBS];
73    for i in 0..LIMBS {
74        let mut carry = 0u128;
75        for j in 0..(LIMBS - i) {
76            let idx = i + j;
77            let cur = out[idx] as u128 + (a[i] as u128) * (b[j] as u128) + carry;
78            out[idx] = cur as u64;
79            carry = cur >> 64;
80        }
81    }
82    out
83}
84
85/// The unsigned quotient `a / b` over 256-bit magnitudes — binary long division (b==0 → 0), the
86/// reference for `bigint_div`'s restoring loop; the soft-core's `BigIntWide` Div routes to that block.
87pub fn wdiv256(a: &[u64; LIMBS], b: &[u64; LIMBS]) -> [u64; LIMBS] {
88    if b.iter().all(|&x| x == 0) {
89        return [0u64; LIMBS];
90    }
91    let mut q = [0u64; LIMBS];
92    let mut rem = [0u64; LIMBS];
93    for bit in (0..LIMBS * 64).rev() {
94        // rem <<= 1
95        let mut carry = 0u64;
96        for limb in rem.iter_mut() {
97            let next = *limb >> 63;
98            *limb = (*limb << 1) | carry;
99            carry = next;
100        }
101        rem[0] |= (a[bit / 64] >> (bit % 64)) & 1; // bring down the next dividend bit
102        if !limb_lt(&rem, b) {
103            rem = limb_sub(&rem, b).0;
104            q[bit / 64] |= 1u64 << (bit % 64);
105        }
106    }
107    q
108}
109
110/// Emit the 256-bit add / subtract / compare datapath. The `{cout,sum}` / `{borrow,diff}`
111/// use a 257-bit context so the carry / borrow out of the top limb is captured; `lt` is the
112/// borrow (a < b ⇔ the subtract underflows), `eq` the magnitude equality.
113pub fn emit_bigint_alu_verilog() -> String {
114    r#"// bigint_alu — a 256-bit (4 x 64-bit limb) BigInt datapath: add, subtract, unsigned
115// compare, over one shared carry/borrow chain. Byte-identical to the software limb ops
116// (proven by bigint_alu_matches_the_numeric_oracle) and correct by our certified SAT
117// (the_bigint_alu_is_a_certified_group_and_order). a[63:0] is limb 0 (little-endian).
118module bigint_alu(
119  input  [255:0] a,
120  input  [255:0] b,
121  output [255:0] sum,    output cout,      // a + b  (carry out of the top limb kept)
122  output [255:0] diff,   output borrow,    // a - b  (borrow = 1 iff a < b; magnitude when a>=b)
123  output         eq,     output lt         // unsigned compare: a == b, a < b
124);
125  assign {cout, sum}    = a + b;           // 257-bit LHS ⇒ carry-propagate, carry retained
126  assign {borrow, diff} = {1'b0, a} - {1'b0, b}; // borrow (bit 256) = 1 ⇔ a < b
127  assign eq = (a == b);
128  assign lt = borrow;                      // a < b  ⇔  the subtract underflowed
129endmodule
130"#
131    .to_string()
132}
133
134/// Emit the 64×64→128 limb multiplier (the partial product `mag_mul` accumulates).
135pub fn emit_bigint_mul_verilog() -> String {
136    r#"// bigint_mul — the 64x64 -> 128 limb multiplier mag_mul iterates. The full 256-bit
137// product is this primitive composed with the proven bigint_alu carry-adds (schoolbook).
138// prod = a * b, byte-identical to (a as u128) * (b as u128)
139// (proven by bigint_mul_matches_the_limb_mul_oracle; distributivity by our certified SAT).
140//
141// BUILT FROM FOUR 32x32 SUB-PRODUCTS RATHER THAN ONE 64x64 ARRAY, and that is a
142// MANUFACTURABILITY choice, not an arithmetic one. `assign prod = a * b` lets synthesis build a
143// flat 64x64 array whose partial-product nets are up to 128 bits wide and run the width of the
144// block. On sky130 those long wide runs are an antenna source, and an antenna violation on a net
145// that ALREADY carries a diode cannot be repaired by adding another one — measured on
146// coproc_core, where all 7 surviving violators had a diode on the violating pin and the jog
147// router could not bridge any of them (TritonRoute had patched them with via-enclosure RECTs,
148// whose geometry odb cannot round-trip from Python). Every intermediate below is <= 65 bits,
149// matching the width of every antenna-clean block in the suite.
150//
151// EQUIVALENCE IS NOT ASSUMED. Verified against `a * b` under iverilog over 16 corner cases
152// (0 / 1 / 2^64-1 / the 2^32 boundary, in both operands) and 200,000 random vectors, plus the
153// 64 oracle vectors of tb_bigint_mul.
154module bigint_mul(
155  input  [63:0]  a,
156  input  [63:0]  b,
157  output [127:0] prod
158);
159  wire [31:0] a0 = a[31:0], a1 = a[63:32];
160  wire [31:0] b0 = b[31:0], b1 = b[63:32];
161  wire [63:0] t0 = a0 * b0;                              // 32x32 -> 64, four of them
162  wire [63:0] t1 = a0 * b1;
163  wire [63:0] t2 = a1 * b0;
164  wire [63:0] t3 = a1 * b1;
165  wire [64:0] mid    = {1'b0, t1} + {1'b0, t2};          // 65-bit, cannot overflow
166  wire [64:0] lo_sum = {1'b0, t0} + {mid[31:0], 32'b0};  // t0 + (mid_lo << 32)
167  wire [63:0] plo = lo_sum[63:0];
168  wire [63:0] phi = t3 + mid[64:32] + {63'b0, lo_sum[64]};
169  assign prod = {phi, plo};
170endmodule
171"#
172    .to_string()
173}
174
175/// Self-checking ALU testbench: apply N (a,b) and compare sum/cout, diff/borrow, eq/lt against
176/// the software oracle from `bigint_{a,b}.hex` + `bigint_{sum,sub,cmp}.hex`. Prints PASS/FAIL.
177pub fn emit_bigint_alu_testbench(n: usize) -> String {
178    format!(
179        r#"// tb_bigint_alu — HW 256-bit add/sub/compare vs the software BigInt limb oracle.
180`timescale 1ns/1ps
181module tb_bigint_alu;
182  reg  [255:0] a, b;
183  reg  [255:0] oa   [0:{last}];
184  reg  [255:0] ob   [0:{last}];
185  reg  [256:0] osum [0:{last}];   // {{cout, sum}}
186  reg  [256:0] osub [0:{last}];   // {{borrow, diff}}
187  reg  [1:0]   ocmp [0:{last}];   // {{eq, lt}}
188  wire [255:0] sum, diff;
189  wire         cout, borrow, eq, lt;
190  integer i, errors = 0;
191  bigint_alu dut(.a(a), .b(b), .sum(sum), .cout(cout), .diff(diff),
192                 .borrow(borrow), .eq(eq), .lt(lt));
193  initial begin
194    $readmemh("bigint_a.hex", oa);
195    $readmemh("bigint_b.hex", ob);
196    $readmemh("bigint_sum.hex", osum);
197    $readmemh("bigint_sub.hex", osub);
198    $readmemh("bigint_cmp.hex", ocmp);
199    for (i = 0; i < {n}; i = i + 1) begin
200      a = oa[i]; b = ob[i]; #1;
201      if ({{cout, sum}}    !== osum[i]) begin errors = errors + 1; $display("FAIL add %0d", i); end
202      if ({{borrow, diff}} !== osub[i]) begin errors = errors + 1; $display("FAIL sub %0d", i); end
203      if ({{eq, lt}}       !== ocmp[i]) begin errors = errors + 1; $display("FAIL cmp %0d", i); end
204    end
205    if (errors == 0) $display("PASS: {n} 256-bit BigInt add/sub/compare match the software oracle");
206    else $display("FAIL: %0d BigInt ALU mismatches", errors);
207    $finish;
208  end
209endmodule
210"#,
211        last = n - 1,
212        n = n,
213    )
214}
215
216/// Self-checking multiplier testbench: N (a,b) 64-bit, compare `prod` against `(a*b)` 128-bit.
217pub fn emit_bigint_mul_testbench(n: usize) -> String {
218    format!(
219        r#"// tb_bigint_mul — HW 64x64 limb multiplier vs the software (a as u128)*(b as u128).
220`timescale 1ns/1ps
221module tb_bigint_mul;
222  reg  [63:0]  a, b;
223  reg  [63:0]  oa [0:{last}];
224  reg  [63:0]  ob [0:{last}];
225  reg  [127:0] op [0:{last}];
226  wire [127:0] prod;
227  integer i, errors = 0;
228  bigint_mul dut(.a(a), .b(b), .prod(prod));
229  initial begin
230    $readmemh("bigint_mul_a.hex", oa);
231    $readmemh("bigint_mul_b.hex", ob);
232    $readmemh("bigint_mul_p.hex", op);
233    for (i = 0; i < {n}; i = i + 1) begin
234      a = oa[i]; b = ob[i]; #1;
235      if (prod !== op[i]) begin errors = errors + 1; $display("FAIL mul %0d", i); end
236    end
237    if (errors == 0) $display("PASS: {n} 64x64 limb products match the software oracle");
238    else $display("FAIL: %0d BigInt mul mismatches", errors);
239    $finish;
240  end
241endmodule
242"#,
243        last = n - 1,
244        n = n,
245    )
246}
247
248/// Reproducible ALU vectors: (a, b) + oracle {cout,sum}, {borrow,diff}, {eq,lt}. Seeds a few
249/// carry/borrow/equal edge cases into the random mix.
250fn bigint_alu_vectors(n: usize) -> [String; 5] {
251    let mut st: u64 = 0x00B1_6100_ADDE_D000;
252    let mut next = || {
253        st = st
254            .wrapping_mul(6364136223846793005)
255            .wrapping_add(1442695040888963407);
256        st
257    };
258    let (mut ha, mut hb) = (String::new(), String::new());
259    let (mut hsum, mut hsub, mut hcmp) = (String::new(), String::new(), String::new());
260    for k in 0..n {
261        // edge cases: MAX+1 (carry out), 0-1 (borrow out / wrap), equal operands.
262        let (a, b) = match k {
263            0 => ([u64::MAX; 4], [1, 0, 0, 0]),
264            1 => ([0; 4], [1, 0, 0, 0]),
265            2 => {
266                let v = [next(), next(), next(), next()];
267                (v, v)
268            }
269            _ => (
270                [next(), next(), next(), next()],
271                [next(), next(), next(), next()],
272            ),
273        };
274        let (s, cout) = limb_add(&a, &b);
275        let (d, borrow) = limb_sub(&a, &b);
276        let (eq, lt) = (a == b, limb_lt(&a, &b));
277        ha.push_str(&format!(
278            "{:016x}{:016x}{:016x}{:016x}\n",
279            a[3], a[2], a[1], a[0]
280        ));
281        hb.push_str(&format!(
282            "{:016x}{:016x}{:016x}{:016x}\n",
283            b[3], b[2], b[1], b[0]
284        ));
285        hsum.push_str(&format!(
286            "{:01x}{:016x}{:016x}{:016x}{:016x}\n",
287            cout & 1,
288            s[3],
289            s[2],
290            s[1],
291            s[0]
292        ));
293        hsub.push_str(&format!(
294            "{:01x}{:016x}{:016x}{:016x}{:016x}\n",
295            borrow & 1,
296            d[3],
297            d[2],
298            d[1],
299            d[0]
300        ));
301        hcmp.push_str(&format!("{:01x}\n", ((eq as u8) << 1) | (lt as u8)));
302    }
303    [ha, hb, hsum, hsub, hcmp]
304}
305
306/// Reproducible 64-bit multiplier vectors + 128-bit product oracle.
307fn bigint_mul_vectors(n: usize) -> [String; 3] {
308    let mut st: u64 = 0x6D75_6C00_7000_D0D0;
309    let mut next = || {
310        st = st
311            .wrapping_mul(6364136223846793005)
312            .wrapping_add(1442695040888963407);
313        st
314    };
315    let (mut ha, mut hb, mut hp) = (String::new(), String::new(), String::new());
316    for k in 0..n {
317        let (a, b) = match k {
318            0 => (u64::MAX, u64::MAX), // largest product
319            1 => (0, next()),
320            _ => (next(), next()),
321        };
322        let p = limb_mul(a, b);
323        ha.push_str(&format!("{a:016x}\n"));
324        hb.push_str(&format!("{b:016x}\n"));
325        hp.push_str(&format!("{:016x}{:016x}\n", (p >> 64) as u64, p as u64));
326    }
327    [ha, hb, hp]
328}
329
330/// Emit the BigInt divide block — a sequential 256-bit UNSIGNED long divider (1 bit/cycle restoring,
331/// W cycles) computing q = a / b, r = a % b over magnitudes, b==0 -> q=r=0. The wide analogue of
332/// `shared_divider`'s magnitude core (no signed fixup — BigInt carries its sign separately); it feeds
333/// the numeric-tower BigInt divide the way `shared_divider` feeds the 32-bit ALU. 4,340 LUT / 1,548 FF
334/// on the ECP5-85K. Proven == the reference magnitude division by iverilog (`tb_bigint_div`) and ==
335/// `numeric::mag_div`'s algorithm by the Rust model test below.
336pub fn emit_bigint_div_verilog() -> String {
337    include_str!("bigint_div.v").to_string()
338}
339
340/// Emit the divide block's self-checking testbench: drive `bigint_div`, wait for `done`, and check
341/// quotient+remainder against the native 256-bit unsigned `a / b` / `a % b` reference over edge cases
342/// plus a 2000-pair random sweep. PASS iff all match.
343pub fn emit_bigint_div_testbench() -> String {
344    include_str!("tb_bigint_div.v").to_string()
345}
346
347#[cfg(test)]
348#[path = "../../../logicaffeine_tests/unit/logicaffeine_compile/codegen_sva/bignum_rtl/tests.rs"]
349mod tests;