Reference mul_wallace
A proved reference of mul, written or adapted by the maintainers and shipped with the task: its design and its proof mul_wallace.lean (264 code lines). A Wallace tree: carry-save adders reduce the rows three at a time, then a ripple final adder. It is shown with parshown with par, never a player's entry (proved references).
Area
8 966.10µm²
8 966.0992 µm² exactly
Beats 0% · matches 0%
0 of 2 other players beaten, 0 equal.
- vs par
- +4.7% largermul_tree_h · 8 563.21 µm²
- on this page
- 10th of 10 designs
- cells
- 1 502
- flops
- 0
- repair
- 0 buffers, 0 tie cells
- GE
- 2 113.894 extra
Performance · time
5 982ps
the delay: the latest output arrival, p[30] rising
Beats 50% · matches 0%
1 of 2 other players beaten, 0 equal.
- vs par
- −0.2% fastermul_tree_h · 5 992 ps
- on this page
- 4th of 10 designs
- Fmax-eq.
- 167.2 MHz derived
- critical path
- 56 cells
- unit depth
- 62 extra
Score · area × time
53 635µm²·ns
8 966.0992 µm² × 5 982 ps
Beats 0% · matches 0%
0 of 2 other players beaten, 0 equal.
- vs par
- +4.5% worsemul_tree_h · 51 311 µm²·ns
- on this page
- 7th of 10 designs
- board
- a proved reference, shown with par: never ranked
- Pareto
- dominated on this page by late-carry, dadda_ks_16 and dadda_rc_16
On the board, of 2 other players: 0 beaten, 0 level, 1 trade off, 1 ahead.
how these numbers are measured and compared
Area: sky130 A0′, the repaired netlist. Time: NLDM, inputs driven by buf_8, 10 fF per output bit, no wires.
Area, time and score are ranked (the leaderboard orders by the score); a number tagged extra, derived or research is shown, never ranked. Fmax-equivalent = 10⁶ / time in ps, for comparison with sites that quote Fmax.
The field is the ranked records of mul's other players at this revision (2 players, 2 records), never the references, the baselines or a rejected design; each player counts once, at their best, and ties are exact at the recorded unit; advisory, recomputed at every build, never stored in a record and never used by the ranking.
On the board, another player is beaten when this design dominates every record of theirs, level when none of theirs is better and one is identical (the rest dominated), ahead when a record of theirs dominates this design, and a trade off otherwise (one of theirs better on one axis, worse on the other).
“On this page” and “Pareto” compare with the 10 designs of the area-time plot (5 references, 2 submissions and 3 baselines): not a ranking.
Area
133.917µm²
133.91730 µm² exactly
Beats 0% · matches 0%
0 of 2 other players beaten, 0 equal.
- vs par
- +4.7% largermul_tree_h · 127.954 µm²
- on this page
- 10th of 10 designs
- cells
- 1 513
- flops
- 0
- repair
- 0 buffers, 0 tie cells
- GE
- 2 113.894 extra
Performance · time
1 186ps
the delay: the latest output arrival
Beats 50% · matches 0%
1 of 2 other players beaten, 0 equal.
- vs par
- +2.2% slowermul_tree_h · 1 160 ps
- on this page
- 6th of 10 designs
- Fmax-eq.
- 843.2 MHz derived
- unit depth
- 62 extra
Score · area × time
158.8µm²·ns
133.91730 µm² × 1 186 ps
Beats 0% · matches 0%
0 of 2 other players beaten, 0 equal.
- vs par
- +7.0% worsemul_tree_h · 148.4 µm²·ns
- on this page
- 8th of 10 designs
- board
- a proved reference, shown with par: never ranked
- Pareto
- dominated on this page by late-carry, dadda_ks_16, dadda_rc_16, yosys_mul and mul_tree_h
On the board, of 2 other players: 0 beaten, 0 level, 1 trade off, 1 ahead.
how these numbers are measured and compared
Area: ASAP7 A0′ (asap7_a0_v1), the same scored netlist, each gate its least-area ASAP7 build, repaired by buffering. Time: NLDM, inputs driven by BUFx8, 2.5 fF per output bit, no wires; a table read below its grid is read at its floor. ASAP7: a predictive 7 nm library, never fabricated; no wires, like sky130's score (how ASAP7 is scored, and its quirks).
Area, time and score order this view's board (by the score); the records' ranked score is sky130's, the default view; a number tagged extra, derived or research is shown, never ranked. Fmax-equivalent = 10⁶ / time in ps, for comparison with sites that quote Fmax.
The field is the ranked records of mul's other players at this revision (2 players, 2 records), never the references, the baselines or a rejected design; each player counts once, at their best, and ties are exact at the recorded unit; advisory, recomputed at every build, never stored in a record and never used by the ranking.
On the board, another player is beaten when this design dominates every record of theirs, level when none of theirs is better and one is identical (the rest dominated), ahead when a record of theirs dominates this design, and a trade off otherwise (one of theirs better on one axis, worse on the other).
“On this page” and “Pareto” compare with the 10 designs of the area-time plot (5 references, 2 submissions and 3 baselines): not a ranking.
The sources
// svGolf task `mul` -- reference solution 4: a WALLACE-TREE multiplier (tape of rows, 3:2 carry-save
// adder rows), parametric in N.
//
// The tape T is one 2-D packed array of 3N-1 rows of 2N bits. Rows 0 .. N-1 are the N partial
// products (row j = (a & {N{b[j]}}) << j) and row N is an all-zero dummy row, so N+1 rows are live
// at the start. Carry-save adder number g (g = 0 .. N-2) reads the three OLDEST live rows 3g, 3g+1,
// 3g+2 and APPENDS two rows: the sum row x ^ y ^ z at position N+1+2g and the carry row
// maj(x, y, z) << 1 at position N+2+2g. Each adder consumes three live rows and appends two, so after
// the N-1 adders exactly two rows remain, at positions 3N-3 and 3N-2: that is Wallace's reduction
// in its queue form (group the rows by threes, replace each group by its sum and carry row; the
// number of live rows shrinks by a factor 2/3 per level, so there are about log_{3/2}(N) levels).
// A 2N-bit ripple-carry adder adds the last two rows. Every sum is taken modulo 2^(2N), exactly
// the width of the product, so every carry out of bit 2N-1 is dropped without an argument.
//
// There is no localparam, no division, no conditional generate and no stage counter: every index is
// affine in N and the genvars (reads at 3*g+k, writes at N+1+2*g and N+2+2*g). A shifted partial
// product is a GATHER read of `ae`, the zero-padded copy of `a`, so no write leaves its vector.
module mul_n #(parameter int N = 16) (
input logic [N-1:0] a, b,
output logic [2*N-1:0] p
);
logic [4*N-2:0] ae; // ae[x] = a[x-(2N-1)] for 2N-1 <= x < 3N-1, else 0
logic [3*N-2:0][2*N-1:0] T; // the tape of rows
logic [2*N:0] K; // final-adder carries
assign ae[2*N-2:0] = '0;
assign ae[3*N-2:2*N-1] = a;
assign ae[4*N-2:3*N-1] = '0;
assign T[N] = '0;
assign K[0] = 1'b0;
genvar j, g, c;
generate
// the N partial products: row j is a, shifted left by j and gated by b[j] (a gather read of ae)
for (j = 0; j < N; j++) begin : g_leaf
for (c = 0; c < 2*N; c++) begin : g_lc
assign T[j][c] = ae[c + 2*N - 1 - j] & b[j];
end
end
// carry-save adder g: rows 3g, 3g+1, 3g+2 in, rows N+1+2g (sum) and N+2+2g (carry) appended
for (g = 0; g < N-1; g++) begin : g_csa
assign T[N+2+2*g][0] = 1'b0;
for (c = 0; c < 2*N; c++) begin : g_sum
assign T[N+1+2*g][c] = T[3*g][c] ^ T[3*g+1][c] ^ T[3*g+2][c];
end
for (c = 0; c < 2*N-1; c++) begin : g_car
assign T[N+2+2*g][c+1] = (T[3*g][c] & T[3*g+1][c]) | (T[3*g+2][c] & (T[3*g][c] ^ T[3*g+1][c]));
end
end
// final carry-propagate (ripple) adder of the last two rows, positions 3N-3 and 3N-2
for (c = 0; c < 2*N; c++) begin : g_fin
assign p[c] = T[3*N-3][c] ^ T[3*N-2][c] ^ K[c];
assign K[c+1] = (T[3*N-3][c] & T[3*N-2][c]) | (K[c] & (T[3*N-3][c] ^ T[3*N-2][c]));
end
endgenerate
endmodule
mul_wallace.lean (264 code lines)
import Submission.Design
import SvGolf.Comb
import LeanModels.Sv.CombEqTactic
import LeanModels.Sv.CombLib
/-!
# Reference solution 4 for task `mul`: a Wallace-tree multiplier (a tape of rows, 3:2 carry-save adders), proved with the `sv_comb` tactics
This is a submission file like any player's `Solution.lean`. The checker generates the module
`Submission.Design` (`Submission.design`, `Submission.Goal`); this file proves
theorem Submission.solution : Submission.Goal
for EVERY `N ≥ 1`. The design is `reference/mul_wallace.sv`: ONE 2-D packed array `T`, the TAPE, of
`3N-1` rows of `2N` bits. Rows `0 .. N-1` are the partial products (row `j` is `a · b[j] · 2^j`),
row `N` is an all-zero dummy row, so `N+1` rows are live at the start. Carry-save adder number `g`
(`g = 0 .. N-2`) reads the three oldest live rows `3g`, `3g+1`, `3g+2` and APPENDS the sum row
`x ^ y ^ z` at position `N+1+2g` and the carry row `maj(x, y, z) << 1` at position `N+2+2g`; after
the `N-1` adders two rows remain, at positions `3N-3` and `3N-2`, and a `2N`-bit ripple-carry
adder adds them. This is Wallace's reduction by groups of three rows, in its queue form (the live
rows shrink by a factor 2/3 per level). There is no stage loop, no localparam, no division and no
conditional generate: every index is affine in `N` and the genvars (`3*g+k`, `N+1+2*g`, `N+2+2*g`),
so the existing fragment of `sv_structure` / `sv_equations` reads the design as it is.
## Route (the file is in this order)
1. **Arithmetic** (`TapeArith`, plain Lean, no ls-sv): `csa_row` (a row of 3:2 counters with the
carry row shifted by one column preserves the column-weighted sum, up to the dropped top carry),
`lane_gated` (a partial-product row is the shifted `a`, gated by a bit of `b`), and `tape_mul`:
the sum modulo `2^(2N)` of the LIVE window `[3g, N+1+2g)` of the tape (`csum V (3g) (N+1-g)`) is
`a · b` for every `g ≤ N-1`, by induction on `g`: at `g = 0` the window is the partial products
and the dummy row, and adder `g` replaces its first three rows by the two rows appended at its
far end, which `csa_row` shows to have the same sum modulo `2^(2N)`. At `g = N-1` the window is
the two last rows, and the final adder is `ripple_row` of the library.
2. **Structure** (`tape_struct`): `sv_structure` (`CombDesign ∧ Acyclic`); the goals compare 2-D windows and
close by `intros; sv_rowmajor; omega`. The rank is a lexicographic key `(stage, adder)` of each
site identity (`tapeKey`): inputs, constants and the dummy row, the partial products, the adders
in order of `g` (an adder reads only rows below `N+1+2g`, which earlier adders wrote), the ripple
adder column by column. `tape_struct` needs `maxHeartbeats 1000000`.
3. **Ports** (`tape_interface`): `sv_interface`.
4. **Equations, bridge and coverage** (`solution`): the printed equations of `sv_equations` are bridged to
the clean facts of `tape_mul` one `sv_bridge eq_x` per fact (the `Int` index forms of the netlist
against the `Nat` indices of the arithmetic: congruence and `omega`).
5. **Coverage** (`solution`, the same theorem): the coverage goals of `sv_equations` are the reads of the
tape (a row `3g+k` or `3N-3`, `3N-2` at a column) and of the carry chain `K`; the closing
`all_goals … sv_cover` proves them, solving the writer from the read (a partial product, the dummy
row, or a sum or carry row of adder `(r-N-1)/2`).
-/
/-! ## 1. The arithmetic (plain Lean, `CombLib` only)
The Wallace tape multiplier: `tape_mul` proves `p = a * b` from the netlist equations as clean Boolean
facts about bit functions; nothing here mentions a design or a state. -/
namespace TapeArith
open LeanModels.Sv.CombLib
theorem bfVal_false {f : Nat → Bool} {n : Nat} (h : ∀ j < n, f j = false) : bfVal f n = 0 := by
induction n with
| zero => rfl
| succ n ih =>
simp only [bfVal]
rw [ih (fun j hj => h j (by omega)), h n (by omega)]
simp
/-- A row of 3:2 counters, the carry row shifted one column: column `c` of `s`/`cy`, with
`sh (c+1) = cy c`. The top carry `cy (W-1)` is dropped from `sh`. -/
theorem csa_row (n : Nat) (x y z s cy sh : Nat → Bool)
(hs : ∀ c < n + 1, s c = (x c ^^ y c ^^ z c))
(hcy : ∀ c < n + 1, cy c = ((x c && y c) || (z c && (x c ^^ y c))))
(hsh0 : sh 0 = false) (hsh : ∀ c, c + 1 < n + 1 → sh (c + 1) = cy c) :
bfVal s (n + 1) + bfVal sh (n + 1) + 2 ^ (n + 1) * (cy n).toNat =
bfVal x (n + 1) + bfVal y (n + 1) + bfVal z (n + 1) := by
have key : ∀ m, m ≤ n + 1 →
bfVal s m + 2 * bfVal cy m = bfVal x m + bfVal y m + bfVal z m := by
intro m
induction m with
| zero => intro _; simp [bfVal]
| succ m ih =>
intro hm
have ih := ih (by omega)
have hfa := fullAdder_arith (x m) (y m) (z m)
rw [← hs m (by omega), ← hcy m (by omega)] at hfa
simp only [bfVal]
have : 2 ^ m * ((s m).toNat + 2 * (cy m).toNat) =
2 ^ m * ((x m).toNat + (y m).toNat + (z m).toNat) := by rw [hfa]
generalize (s m).toNat = S0 at *
generalize (cy m).toNat = C0 at *
generalize (x m).toNat = X0 at *
generalize (y m).toNat = Y0 at *
generalize (z m).toNat = Z0 at *
generalize 2 ^ m = P at *
grind
have hsh' : bfVal sh (n + 1) = 2 * bfVal cy n := by
rw [bfVal_shift, hsh0]
have : bfVal (fun j => sh (j + 1)) n = bfVal cy n :=
bfVal_congr (fun j hj => hsh j (by omega))
rw [this]; simp
have hk := key (n + 1) (Nat.le_refl _)
rw [hsh']
simp only [bfVal, Nat.pow_succ] at hk ⊢
generalize 2 ^ n = P at *
generalize (cy n).toNat = C0 at *
grind
/-! ### The leaves: a gated, shifted copy of `a` -/
/-- The zero-padded copy `ae` of `a`, read at offset `2N-1-r`, is `a` shifted left by `r`
(for `r ≤ N`: nothing is cut off). -/
theorem lane_val {N : Nat} (hN : 1 ≤ N) (a : BitVec N) (ae : Nat → Bool)
(h1 : ∀ k, k < 2 * N - 1 → ae k = false)
(h2 : ∀ k, k < N → ae (2 * N - 1 + k) = a.getLsbD k)
(h3 : ∀ k, k < N → ae (3 * N - 1 + k) = false)
(r : Nat) (hr : r ≤ N) :
bfVal (fun c => ae (c + (2 * N - 1 - r))) (2 * N) = a.toNat * 2 ^ r := by
have hs1 := bfVal_split (fun c => ae (c + (2 * N - 1 - r))) r (2 * N - r)
rw [show r + (2 * N - r) = 2 * N by omega] at hs1
rw [hs1]
have hlow : bfVal (fun c => ae (c + (2 * N - 1 - r))) r = 0 :=
bfVal_false (fun j hj => h1 _ (by omega))
rw [hlow]
have hs2 := bfVal_split (fun j => ae (r + j + (2 * N - 1 - r))) N (N - r)
rw [show N + (N - r) = 2 * N - r by omega] at hs2
rw [hs2]
have hmid : bfVal (fun j => ae (r + j + (2 * N - 1 - r))) N = a.toNat := by
rw [← bfVal_bv a]
exact bfVal_congr (fun j hj => by
have : r + j + (2 * N - 1 - r) = 2 * N - 1 + j := by omega
rw [this]; exact h2 j hj)
have hhigh : bfVal (fun j => (fun j => ae (r + j + (2 * N - 1 - r))) (N + j)) (N - r) = 0 :=
bfVal_false (fun j hj => by
show ae (r + (N + j) + (2 * N - 1 - r)) = false
have : r + (N + j) + (2 * N - 1 - r) = 3 * N - 1 + j := by omega
rw [this]; exact h3 j (by omega))
rw [hmid, hhigh]
simp only [Nat.zero_add, Nat.mul_zero, Nat.add_zero]
rw [Nat.mul_comm]
/-- The value of a gated lane. -/
theorem lane_gated {N : Nat} (hN : 1 ≤ N) (a : BitVec N) (ae : Nat → Bool)
(h1 : ∀ k, k < 2 * N - 1 → ae k = false)
(h2 : ∀ k, k < N → ae (2 * N - 1 + k) = a.getLsbD k)
(h3 : ∀ k, k < N → ae (3 * N - 1 + k) = false)
(r : Nat) (g : Bool) (hg : g = true → r ≤ N) :
bfVal (fun c => ae (c + (2 * N - 1 - r)) && g) (2 * N) = g.toNat * (a.toNat * 2 ^ r) := by
rw [bfVal_and]
cases g with
| false => simp
| true => simp only [Bool.toNat_true]; rw [lane_val hN a ae h1 h2 h3 r (hg rfl)]
/-! ### Sums over a stretch of tape rows -/
def csum (V : Nat → Nat) : Nat → Nat → Nat
| _, 0 => 0
| s, n + 1 => csum V s n + V (s + n)
theorem csum_front (V : Nat → Nat) (n : Nat) : ∀ s, csum V s (n + 1) = V s + csum V (s + 1) n := by
induction n with
| zero => intro s; simp [csum]
| succ n ih =>
intro s
have e : s + 1 + n = s + (n + 1) := by omega
have e2 : csum V (s + 1) (n + 1) = csum V (s + 1) n + V (s + 1 + n) := rfl
rw [csum, ih s, e2, e]
omega
/-- Three rows off the front. -/
theorem csum_front3 (V : Nat → Nat) (n s : Nat) :
csum V s (n + 3) = V s + V (s + 1) + V (s + 2) + csum V (s + 3) n := by
rw [csum_front, csum_front, csum_front]
simp only [Nat.add_assoc, Nat.reduceAdd]
/-- Leaves: the first `N` rows hold the partial products, one row per bit of `b`. -/
theorem csum_leaves (a : Nat) (g : Nat → Bool) (V : Nat → Nat) (N : Nat)
(hV : ∀ j, j < N → V j = (g j).toNat * (a * 2 ^ j)) :
∀ n, n ≤ N → csum V 0 n = a * bfVal g n := by
intro n
induction n with
| zero => intro _; simp [csum, bfVal]
| succ n ih =>
intro hn
have ih := ih (by omega)
rw [csum, ih, Nat.zero_add, hV n (by omega)]
simp only [bfVal]
generalize 2 ^ n = P
generalize (g n).toNat = B0
generalize bfVal g n = F
grind
/-- **The whole multiplier**, from the equations of the tape as clean Boolean facts. -/
theorem tape_mul {N : Nat} (hN : 1 ≤ N) (a b : BitVec N) (p : BitVec (2 * N))
(ae : Nat → Bool) (T : Nat → Nat → Bool) (K : Nat → Bool)
(hae1 : ∀ k, k < 2 * N - 1 → ae k = false)
(hae2 : ∀ k, k < N → ae (2 * N - 1 + k) = a.getLsbD k)
(hae3 : ∀ k, k < N → ae (3 * N - 1 + k) = false)
(hleaf : ∀ j, j < N → ∀ c, c < 2 * N → T j c = (ae (c + (2 * N - 1 - j)) && b.getLsbD j))
(hdum : ∀ c, c < 2 * N → T N c = false)
(hsum : ∀ g, g < N - 1 → ∀ c, c < 2 * N →
T (N + 1 + 2 * g) c = (T (3 * g) c ^^ T (3 * g + 1) c ^^ T (3 * g + 2) c))
(hcar0 : ∀ g, g < N - 1 → T (N + 2 + 2 * g) 0 = false)
(hcar : ∀ g, g < N - 1 → ∀ c, c < 2 * N - 1 →
T (N + 2 + 2 * g) (c + 1) = ((T (3 * g) c && T (3 * g + 1) c) ||
(T (3 * g + 2) c && (T (3 * g) c ^^ T (3 * g + 1) c))))
(hK0 : K 0 = false)
(hp : ∀ c, c < 2 * N → p.getLsbD c = (T (3 * N - 3) c ^^ T (3 * N - 2) c ^^ K c))
(hK : ∀ c, c < 2 * N → K (c + 1) =
((T (3 * N - 3) c && T (3 * N - 2) c) || (K c && (T (3 * N - 3) c ^^ T (3 * N - 2) c)))) :
p = BitVec.ofNat (2 * N) (a.toNat * b.toNat) := by
obtain ⟨n, hn⟩ : ∃ n, 2 * N = n + 1 := ⟨2 * N - 1, by omega⟩
let V : Nat → Nat := fun m => bfVal (T m) (2 * N)
-- the leaves: one shifted copy of `a` per bit of `b`
have hleafV : ∀ j, j < N → V j = (b.getLsbD j).toNat * (a.toNat * 2 ^ j) := by
intro j hj
show bfVal (T j) (2 * N) = _
have e1 : bfVal (T j) (2 * N) =
bfVal (fun c => ae (c + (2 * N - 1 - j)) && b.getLsbD j) (2 * N) :=
bfVal_congr (fun c hc => hleaf j hj c hc)
rw [e1, lane_gated hN a ae hae1 hae2 hae3 j (b.getLsbD j) (fun _ => by omega)]
have hdumV : V N = 0 := bfVal_false (fun c hc => hdum c hc)
have hbase : csum V 0 (N + 1) = a.toNat * b.toNat := by
show csum V 0 N + V (0 + N) = _
rw [csum_leaves a.toNat b.getLsbD V N hleafV N (Nat.le_refl N), bfVal_bv, Nat.zero_add, hdumV]
rfl
-- one carry-save adder: rows 3g, 3g+1, 3g+2 are replaced by the two appended rows
have hcsa : ∀ g, g < N - 1 → ∃ q, V (N + 1 + 2 * g) + V (N + 2 + 2 * g) + 2 ^ (2 * N) * q =
V (3 * g) + V (3 * g + 1) + V (3 * g + 2) := by
intro g hg
have h := csa_row n (T (3 * g)) (T (3 * g + 1)) (T (3 * g + 2)) (T (N + 1 + 2 * g))
(fun c => (T (3 * g) c && T (3 * g + 1) c) || (T (3 * g + 2) c && (T (3 * g) c ^^ T (3 * g + 1) c)))
(T (N + 2 + 2 * g))
(fun c hc => hsum g hg c (by omega)) (fun _ _ => rfl) (hcar0 g hg)
(fun c hc => hcar g hg c (by omega))
rw [← hn] at h
exact ⟨_, by simp only [V]; omega⟩
-- the invariant: the live window `[3g, N+1+2g)` sums to `a * b` modulo `2^(2N)`
have hwin : ∀ g, g ≤ N - 1 → csum V (3 * g) (N + 1 - g) % 2 ^ (2 * N) =
(a.toNat * b.toNat) % 2 ^ (2 * N) := by
intro g
induction g with
| zero => intro _; simpa using congrArg (· % 2 ^ (2 * N)) hbase
| succ g ih =>
intro hg
have ih := ih (by omega)
obtain ⟨q, hq⟩ := hcsa g (by omega)
have e1 : csum V (3 * g) (N + 1 - g) = V (3 * g) + V (3 * g + 1) + V (3 * g + 2) +
csum V (3 * g + 3) (N - 2 - g) := by
rw [show N + 1 - g = (N - 2 - g) + 3 by omega, csum_front3]
have e2 : csum V (3 * (g + 1)) (N + 1 - (g + 1)) =
csum V (3 * g + 3) (N - 2 - g) + V (N + 1 + 2 * g) + V (N + 2 + 2 * g) := by
rw [show N + 1 - (g + 1) = (N - 2 - g) + 2 by omega, show 3 * (g + 1) = 3 * g + 3 by omega]
show csum V (3 * g + 3) (N - 2 - g + 1) + V (3 * g + 3 + (N - 2 - g + 1)) = _
show csum V (3 * g + 3) (N - 2 - g) + V (3 * g + 3 + (N - 2 - g)) +
V (3 * g + 3 + (N - 2 - g + 1)) = _
rw [show 3 * g + 3 + (N - 2 - g) = N + 1 + 2 * g by omega,
show 3 * g + 3 + (N - 2 - g + 1) = N + 2 + 2 * g by omega]
rw [e1] at ih
rw [e2]
have h3 : (csum V (3 * g + 3) (N - 2 - g) + V (N + 1 + 2 * g) + V (N + 2 + 2 * g) +
2 ^ (2 * N) * q) % 2 ^ (2 * N) =
(V (3 * g) + V (3 * g + 1) + V (3 * g + 2) + csum V (3 * g + 3) (N - 2 - g)) % 2 ^ (2 * N) := by
congr 1; omega
rw [Nat.add_mul_mod_self_left] at h3
rw [h3, ih]
-- the last window: the two remaining rows, at positions `3N-3` and `3N-2`
have hlast := hwin (N - 1) (Nat.le_refl _)
rw [show N + 1 - (N - 1) = 2 by omega] at hlast
have e3 : csum V (3 * (N - 1)) 2 = V (3 * N - 3) + V (3 * N - 2) := by
show csum V (3 * (N - 1)) 0 + V (3 * (N - 1) + 0) + V (3 * (N - 1) + 1) = _
rw [show 3 * (N - 1) + 0 = 3 * N - 3 by omega, show 3 * (N - 1) + 1 = 3 * N - 2 by omega]
simp [csum]
rw [e3] at hlast
-- the final adder
have hrip := ripple_row (2 * N) (T (3 * N - 3)) (T (3 * N - 2))
(fun c => T (3 * N - 3) c ^^ T (3 * N - 2) c) p.getLsbD K
(fun _ _ => rfl) (fun c hc => hp c hc) (fun c hc => hK c hc)
rw [hK0, bfVal_bv] at hrip
simp only [Bool.toNat_false, Nat.add_zero] at hrip
have hlt : p.toNat < 2 ^ (2 * N) := p.isLt
have hpv : p.toNat = (V (3 * N - 3) + V (3 * N - 2)) % 2 ^ (2 * N) := by
have := sum_split hlt (t := V (3 * N - 3) + V (3 * N - 2)) (q := (K (2 * N)).toNat)
(by simp only [V]; omega)
exact this.1
apply BitVec.eq_of_toNat_eq
rw [BitVec.toNat_ofNat, hpv, hlast]
end TapeArith
namespace Submission
open LeanModels.Sv LeanModels.Sv.CombView LeanModels.Sv.CombLib LeanModels.Sv.CombEq SvGolf
/-! ## 2. Structure: `sv_structure` -/
/-- The rank: `(stage, adder, phase)`. -/
def tapeKey : SiteId → Key
| .plain _ => (0, 0, 0)
| .fam "g_lc" _ _ => (1, 0, 0)
| .fam "g_csa" _ [g] => (2, g.toNat, 0)
| .fam "g_sum" _ [g, _] => (2, g.toNat, 0)
| .fam "g_car" _ [g, _] => (2, g.toNat, 0)
| .fam "g_fin" 0 [c] => (3, c.toNat, 1)
| .fam "g_fin" _ [c] => (3, c.toNat, 0)
| .fam _ _ _ => (3, 0, 0)
set_option maxHeartbeats 1000000 in
/-- `CombDesign` and `Acyclic` for every `N ≥ 1`, from the lexicographic key: ONE application of the
soundness theorem and one round of obligations for both. -/
theorem tape_struct (N : Nat) (hN : 1 ≤ N) :
CombDesign (design.sem N) ∧ Acyclic (design.sem N) ∧ SharedObl design.pdesign [N] := by
sv_structure (lexRank := tapeKey)
all_goals (intros; sv_rowmajor; omega)
/-! ## 3. The ports -/
theorem tape_interface (N : Nat) (hN : 1 ≤ N) (hc : CombDesign (design.sem N)) :
SvGolf.Tasks.Mul.task.Interface N (design.sem N) := by
sv_interface
/-! ## 4. Bridge, equations and coverage of the tape reads
Every cell the design reads must have a writer. A read of the tape `T` at row `r`, column `c` is
written by one of five assigns: the partial-product loop (a row `r < N`), the dummy row `r = N`,
the sum row of an adder (`r = N+1+2g`), or the carry row of an adder (`r = N+2+2g`: column 0 by its
own assign, columns `c ≥ 1` by the carry loop). `sv_cover` closes these coverage goals itself: it
solves the adder index from the read's row (`g := (r - N - 1) / 2`, the parity is part of the
condition it hands to `omega`) and walks the disjunction of writers (`CombCover.lean`). -/
set_option maxHeartbeats 500000 in
theorem solution : Goal := by
intro N hN
have hN' : 1 ≤ N := hN.1
obtain ⟨hc, ha, hso⟩ := tape_struct N hN'
refine CombTask.satisfiedAt_of_comb (tape_interface N hN' hc) hc ha ?_
intro st heqs hs v hv
obtain ⟨x, y, ⟨⟩⟩ := v
simp only [SvGolf.Tasks.Mul.task, HoldAll_cons, HoldAll_nil, and_true] at hv ⊢
obtain ⟨hA, hB⟩ := hv
sv_equations
· obtain ⟨⟨vp, hvp, hvpw⟩, -⟩ := known_p_g_fin_0 0 (by omega)
have ba : ∀ i, i < N → st.boolAt "a" i = x.getLsbD i := fun i hi => SvState.boolAt_ofBitVec hA hi
have bb : ∀ i, i < N → st.boolAt "b" i = y.getLsbD i := fun i hi => SvState.boolAt_ofBitVec hB hi
rw [hvp, SvState.lookup_eq_ofBitVec_bv (W := 2 * N) hvp (by omega)
(fun k hk => (known_p_g_fin_0 k (by omega)).2)]
congr 2
refine TapeArith.tape_mul hN' x y (XF.bv (2 * N) (st.boolAt "p")) (st.boolAt "ae")
(st.cell "T" (2 * N)) (st.boolAt "K") ?_ ?_ ?_ ?_ ?_ ?_ ?_ ?_ ?_ ?_ ?_
· sv_bridge eq_ae_plain_0
· sv_bridge eq_ae_plain_1 with ba
· sv_bridge eq_ae_plain_2
· sv_bridge eq_T_g_lc_0 with bb
· sv_bridge eq_T_plain_3
· sv_bridge eq_T_g_sum_0
· sv_bridge eq_T_g_csa_0
· sv_bridge eq_T_g_car_0
· sv_bridge eq_K_plain_4
· sv_bridge eq_p_g_fin_0 with XF.getLsbD_bv
· sv_bridge eq_K_g_fin_1
case inputsKnown_1 => exact inKnown_ofBitVec hA (by omega)
case inputsKnown_2 => exact inKnown_ofBitVec hB (by omega)
all_goals first | (intros; omega) | (intros; rfl) | sv_cover
/--
info: 'Submission.solution' depends on axioms: [propext, Classical.choice, Quot.sound]
-/
#guard_msgs (whitespace := lax) in
#print axioms solution
end Submission
The netlist
The netlist card shows the sky130 weighing (its cells, its critical path): the ASAP7 view changes the overview above, not this card.
Schematic
drawn at N = 4
Drawn at N = 4, a display instance; scored at N = 16. Only this drawing and the notes under it are at the smaller size: the critical path, the cells and every other number on this page are the scored netlist's.
It is the same design through the same pinned flow at a smaller size (ppa.py --param), so that what it does can be seen: 68 cells here, 1 502 at the scoring point. The critical path at N = 4 (12 cells, ending at p[6] rising, 1 837 ps there) is in the accent colour. Drawn by netlistsvg with svGolf's sky130 skin (derived from netlistsvg's default skin, as the drawing is: MIT licence, Copyright (c) 2016 Neil Turley, the notice): one symbol per cell function, the cell above it, the instance on hover; nor2b and nand2b are drawn with the bubble on their inverted input. The cells of the path are listed under Critical path, each a link to the cell here.
The netlist at N = 4: 68 cells, 25 nand2_1, 21 and2_1, 15 xor2_1, 4 xnor2_1, 1 nand2b_1, 1 nor2b_1, 1 or2_1. There (measured, not ranked): 410.39 µm², 1 837 ps. Open the drawing on its own (SVG, 43.7 KB) to pan and zoom it with the browser's own controls.
Critical path
at N = 16 · scored
The path the ranked delay ends on, at the scoring point N = 16: 57 nets, 56 cells, to p[30] rising at 5 982 ps. Each row is a net of the repaired netlist with the arrival and the transition the STA computed there (NLDM tables, the larger transition carried; no wires). The first row is an input: its arrival is the input driver's own delay (buf_8 fed by a 50 ps ramp). From ppa.py --path (critical_path_timing); the instance names are the scored netlist's, not the schematic's (drawn at a smaller size).
In the schematic, drawn at N = 4, the critical path is 12 cells to p[6] rising (1 837 ps there); each cell is a link to it in the drawing:
a[3]_286_and2_1_289_xnor2_1_291_xor2_1_294_xor2_1_296_nand2_1_312_and2_1_313_xnor2_1_314_nand2_1_317_nand2_1_329_nand2_1_331_nand2_1_336_xor2_1p[6]
every stage (57 rows)
| # | net | edge | driven by | cell | arrival (ps) | transition (ps) | stage (ps) |
|---|---|---|---|---|---|---|---|
| 0 | b[6] | fall | input port | – | 129.9 | 35.0 | |
| 1 | _4950_ | fall | _5888_ | and2_1 | 293.0 | 51.7 | 163.1 |
| 2 | _3550_ | rise | _5952_ | xor2_1 | 493.6 | 190.5 | 200.6 |
| 3 | _3623_ | rise | _6018_ | xor2_1 | 710.4 | 190.5 | 216.8 |
| 4 | _3625_ | rise | _6020_ | xor2_1 | 927.2 | 190.5 | 216.8 |
| 5 | _3651_ | rise | _6044_ | xor2_1 | 1 143.7 | 190.2 | 216.5 |
| 6 | _3680_ | rise | _6070_ | xor2_1 | 1 358.8 | 190.5 | 215.1 |
| 7 | _3697_ | rise | _6086_ | xor2_1 | 1 575.3 | 190.2 | 216.5 |
| 8 | _3726_ | rise | _6112_ | xor2_1 | 1 790.4 | 190.5 | 215.1 |
| 9 | _3731_ | rise | _6117_ | xor2_1 | 1 995.5 | 174.4 | 205.1 |
| 10 | _3739_ | rise | _6124_ | xor2_1 | 2 210.0 | 190.2 | 214.5 |
| 11 | _3777_ | rise | _6158_ | xor2_1 | 2 425.1 | 190.5 | 215.1 |
| 12 | _3816_ | rise | _6194_ | xor2_1 | 2 641.9 | 190.5 | 216.8 |
| 13 | _3897_ | rise | _6268_ | xor2_1 | 2 858.7 | 190.5 | 216.8 |
| 14 | _3898_ | fall | _6269_ | nand2_1 | 2 930.4 | 45.9 | 71.7 |
| 15 | _3899_ | rise | _6270_ | nand2_1 | 3 017.6 | 80.1 | 87.2 |
| 16 | _3901_ | fall | _6272_ | nand2_1 | 3 067.2 | 45.9 | 49.6 |
| 17 | _3902_ | rise | _6273_ | nand2_1 | 3 154.4 | 80.1 | 87.2 |
| 18 | _3904_ | fall | _6275_ | nand2_1 | 3 204.0 | 45.9 | 49.6 |
| 19 | _3905_ | rise | _6276_ | nand2_1 | 3 291.2 | 80.1 | 87.2 |
| 20 | _3908_ | fall | _6278_ | nand2_1 | 3 340.8 | 45.9 | 49.6 |
| 21 | _3909_ | rise | _6279_ | nand2_1 | 3 428.0 | 80.1 | 87.2 |
| 22 | _3911_ | fall | _6281_ | nand2_1 | 3 477.5 | 45.9 | 49.5 |
| 23 | _3912_ | rise | _6282_ | nand2_1 | 3 564.7 | 80.1 | 87.2 |
| 24 | _3914_ | fall | _6284_ | nand2_1 | 3 614.3 | 45.9 | 49.6 |
| 25 | _3915_ | rise | _6285_ | nand2_1 | 3 701.5 | 80.1 | 87.2 |
| 26 | _3917_ | fall | _6287_ | nand2_1 | 3 751.1 | 45.9 | 49.6 |
| 27 | _3919_ | rise | _6288_ | nand2_1 | 3 838.3 | 80.1 | 87.2 |
| 28 | _3921_ | fall | _6290_ | nand2_1 | 3 887.9 | 45.9 | 49.6 |
| 29 | _3922_ | rise | _6291_ | nand2_1 | 3 975.1 | 80.1 | 87.2 |
| 30 | _3924_ | fall | _6293_ | nand2_1 | 4 024.7 | 45.9 | 49.6 |
| 31 | _3925_ | rise | _6294_ | nand2_1 | 4 111.8 | 80.1 | 87.1 |
| 32 | _3927_ | fall | _6296_ | nand2_1 | 4 161.4 | 45.9 | 49.6 |
| 33 | _3928_ | rise | _6297_ | nand2_1 | 4 248.6 | 80.1 | 87.2 |
| 34 | _3931_ | fall | _6299_ | nand2_1 | 4 298.2 | 45.9 | 49.6 |
| 35 | _3932_ | rise | _6300_ | nand2_1 | 4 385.4 | 80.1 | 87.2 |
| 36 | _3934_ | fall | _6302_ | nand2_1 | 4 435.0 | 45.9 | 49.6 |
| 37 | _3935_ | rise | _6303_ | nand2_1 | 4 522.2 | 80.1 | 87.2 |
| 38 | _3937_ | fall | _6305_ | nand2_1 | 4 571.8 | 45.9 | 49.6 |
| 39 | _3938_ | rise | _6306_ | nand2_1 | 4 659.0 | 80.1 | 87.2 |
| 40 | _3941_ | fall | _6308_ | nand2_1 | 4 708.5 | 45.9 | 49.5 |
| 41 | _3942_ | rise | _6309_ | nand2_1 | 4 795.7 | 80.1 | 87.2 |
| 42 | _3944_ | fall | _6311_ | nand2_1 | 4 845.3 | 45.9 | 49.6 |
| 43 | _3945_ | rise | _6312_ | nand2_1 | 4 932.5 | 80.1 | 87.2 |
| 44 | _3947_ | fall | _6314_ | nand2_1 | 4 982.1 | 45.9 | 49.6 |
| 45 | _3948_ | rise | _6315_ | nand2_1 | 5 069.3 | 80.1 | 87.2 |
| 46 | _3991_ | fall | _6354_ | nand2_1 | 5 118.9 | 45.9 | 49.6 |
| 47 | _3992_ | rise | _6356_ | nand2_1 | 5 206.1 | 80.1 | 87.2 |
| 48 | _4028_ | fall | _6388_ | nand2_1 | 5 255.6 | 45.9 | 49.5 |
| 49 | _4029_ | rise | _6390_ | nand2_1 | 5 342.8 | 80.1 | 87.2 |
| 50 | _4057_ | fall | _6416_ | nand2_1 | 5 392.4 | 45.9 | 49.6 |
| 51 | _4059_ | rise | _6418_ | nand2_1 | 5 479.6 | 80.1 | 87.2 |
| 52 | _4081_ | fall | _6438_ | nand2_1 | 5 529.2 | 45.9 | 49.6 |
| 53 | _4082_ | rise | _6440_ | nand2_1 | 5 616.4 | 80.1 | 87.2 |
| 54 | _4096_ | fall | _6453_ | nand2_1 | 5 666.2 | 47.7 | 49.8 |
| 55 | _4098_ | rise | _6456_ | nand2_1 | 5 747.0 | 80.1 | 80.8 |
| 56 | _3516_ | rise | _6467_ | xor2_1 | 5 982.3 | 247.2 | 235.3 |
Cells
at N = 16 · scored
| cell | count | µm² each | µm² in all | share |
|---|---|---|---|---|
xor2_1 | 478 | 8.7584 | 4186.5152 | 46.7% |
nand2_1 | 678 | 3.7536 | 2544.9408 | 28.4% |
and2_1 | 301 | 6.2560 | 1883.0560 | 21.0% |
xnor2_1 | 26 | 8.7584 | 227.7184 | 2.5% |
nor2b_1 | 6 | 6.2560 | 37.5360 | 0.4% |
or2_1 | 6 | 6.2560 | 37.5360 | 0.4% |
mux2_1 | 2 | 11.2608 | 22.5216 | 0.3% |
nand2b_1 | 3 | 6.2560 | 18.7680 | 0.2% |
nor2_1 | 2 | 3.7536 | 7.5072 | 0.1% |
| total | 1 502 | 8966.0992 |
Repair (buffer_trees_v1): 0 buffers on 0 nets, 0 tie cells, 1 weighing round; nothing is resized.
Legality: worst load 0.144 × max_capacitance, worst transition 0.178 × max_transition; 0 timing-table reads outside the characterized grid.
the gate mix of the extra GE column (the pinned GE table, score/ge.json)
| cell | count | GE each | GE total |
|---|---|---|---|
NAND | 678 | 1.000 | 678.000 |
XOR | 478 | 2.000 | 956.000 |
AND | 301 | 1.333 | 401.233 |
XNOR | 26 | 2.000 | 52.000 |
ANDNOT | 6 | 1.333 | 7.998 |
OR | 6 | 1.333 | 7.998 |
ORNOT | 3 | 1.333 | 3.999 |
MUX | 2 | 2.333 | 4.666 |
NOR | 2 | 1.000 | 2.000 |
| total | 1502 | 2113.894 |
The reference
- task
mul- design
mul_wallace.svsha2564369d9943d3ff63630a41058184b139e23e061aa2c27b361ebe53d220c00ae09- proof
mul_wallace.lean, 264 code lines- tactics seen
grind,induction,omega,simp,sv_cover,sv_equations,sv_interface,sv_rowmajor,sv_structure- same design as
- –
- recorded
- no: the checker's run details (build time, axioms, the post-synthesis vectors) are kept for recorded designs only
- downloads
mul_wallace.svmul_wallace.lean
Area against time
- submission
- proved reference
- baseline (not proved)
- baseline with a banned operator
- the Pareto front of the entries
- this design
- par's score: area × time = 51 311 µm²·ns
the designs as numbers, by score
| design | kind | area (µm²) | time (ps) | score (µm²·ns) |
|---|---|---|---|---|
| late-carry | submission | 7 961.39 | 5 342 | 42 530 |
dadda_rc_16 | baseline, not proved | 8 230.39 | 5 461 | 44 946 |
dadda_ks_16 | baseline, not proved | 8 937.32 | 5 148 | 46 009 |
| plus-minus-two | submission | 7 978.90 | 6 329 | 50 498 |
mul_tree_h | proved reference · par | 8 563.21 | 5 992 | 51 311 |
mul_tree | proved reference | 8 603.25 | 6 129 | 52 729 |
mul_wallace · this design | proved reference | 8 966.10 | 5 982 | 53 635 |
yosys_mul | baseline, not proved · banned operator | 8 825.96 | 6 261 | 55 259 |
mul_n | proved reference | 8 290.45 | 10 907 | 90 424 |
mul_n_byhand | proved reference | 8 290.45 | 10 907 | 90 424 |
- submission
- proved reference
- baseline (not proved)
- baseline with a banned operator
- the Pareto front of the entries
- this design
- par's score: area × time = 148.4 µm²·ns
the designs as numbers, by score
| design | kind | area (µm²) | time (ps) | score (µm²·ns) |
|---|---|---|---|---|
dadda_ks_16 | baseline, not proved | 133.174 | 902 | 120.1 |
| late-carry | submission | 120.854 | 1 088 | 131.5 |
dadda_rc_16 | baseline, not proved | 123.391 | 1 100 | 135.7 |
mul_tree_h | proved reference · par | 127.954 | 1 160 | 148.4 |
yosys_mul | baseline, not proved · banned operator | 131.789 | 1 139 | 150.1 |
mul_tree | proved reference | 128.552 | 1 191 | 153.1 |
| plus-minus-two | submission | 119.746 | 1 307 | 156.5 |
mul_wallace · this design | proved reference | 133.917 | 1 186 | 158.8 |
mul_n | proved reference | 123.930 | 1 981 | 245.5 |
mul_n_byhand | proved reference | 123.930 | 1 981 | 245.5 |
Reproduce
From svgolf/, with the pinned tools (reproduce a score lists them):
# the ranked numbers, at the scoring point (from svgolf/, with the pinned Yosys and liberty)
python3.12 score/ppa.py tasks/mul/reference/mul_wallace.sv --task tasks/mul --path \
--json out.json --netlist scored.v --repaired-netlist repaired.v
Expect, in out.json:
- ppa.netlist_sha256
d1d74a81134a7ba2a0edb6670629e5b1733ab125d010aa41cde225d086604fd4- ppa.repair.netlist_sha256
b52cf6114bdddf5fe9ff756cce278976543b0c2404bf013d3f65fb3854ea7a39- ppa.area_e4um2_sky130
89660992- ppa.delay_ps_sky130
5982
The schematic and the critical-path data are site/schematics/mul/4369d9943d3ff636.json and its .svg, written by site/gen_schematics.py (the display instance is ppa.py ... --param N=4 --repaired-netlist, drawn by netlistsvg 1.0.2; repaired netlist sha256 c3db23ad732eea22…).
From svgolf/, with the pinned tools (reproduce a score lists them):
# the ASAP7 view's numbers, at the scoring point (from svgolf/, with the pinned Yosys; once:
# python3.12 score/ppa.py --pdk asap7 --fetch-liberty, the five pinned ASAP7 files into score/lib/asap7/)
python3.12 score/ppa.py tasks/mul/reference/mul_wallace.sv --task tasks/mul --pdk asap7 --no-mapped \
--json out.json
Expect, in out.json:
- ppa.netlist_sha256
d1d74a81134a7ba2a0edb6670629e5b1733ab125d010aa41cde225d086604fd4- ppa.repair.netlist_sha256
00792cf1155fc968d66dfdd3574e90f74ae29aee98a4f645ffe1257d376da98f- ppa.area_e5um2_asap7
13391730- ppa.delay_ps_asap7
1186
leaderboard/asap7/mul.json keeps these numbers; python3.12 leaderboard/leaderboard.py asap7 --rescore --task mul weighs every design of the task again and compares, bit for bit.