Golf scores on ASAP7, a predictive 7 nm library: never fabricated, and like sky130's score it counts no wires. sky130, a real 130 nm process, is the default. How ASAP7 is scored, and its quirks.

Tasks / mul / record

Record 5b30f35c283b

on the Pareto frontieron the Pareto frontier scored at N = 16

Area

7 961.39µm²

7 961.3856 µm² exactly

Beats 100% · matches 0%

1 of 1 other player beaten, 0 equal.

vs par
−7.0% smallermul_tree_h · 8 563.21 µm²
on this page
smallest of 10 designs
cells
1 431
flops
0
repair
0 buffers, 0 tie cells
GE
1 930.3 extra
A1′
8 075.24 µm² research

Performance · time

5 342ps

the delay: the latest output arrival, p[30] rising

Beats 100% · matches 0%

1 of 1 other player beaten, 0 equal.

vs par
−10.8% fastermul_tree_h · 5 992 ps
on this page
2nd of 10 designs
Fmax-eq.
187.2 MHz derived
critical path
57 cells
unit depth
60 extra
A1′
6 138 ps research

Score · area × time

42 530µm²·ns

7 961.3856 µm² × 5 342 ps

Beats 100% · matches 0%

1 of 1 other player beaten, 0 equal.

vs par
−17.1% bettermul_tree_h · 51 311 µm²·ns
on this page
best of 10 designs
board
layer 1, rank 1 of 2 by score
Pareto
on the front

On the board, of 1 other player: 1 beaten, 0 level, 0 trade off, 0 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 (1 player, 1 record), 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

120.854µm²

120.85362 µm² exactly

Beats 0% · matches 0%

0 of 1 other player beaten, 0 equal.

vs par
−5.5% smallermul_tree_h · 127.954 µm²
on this page
2nd of 10 designs
cells
1 463
flops
0
repair
0 buffers, 0 tie cells
GE
1 930.3 extra

Performance · time

1 088ps

the delay: the latest output arrival

Beats 100% · matches 0%

1 of 1 other player beaten, 0 equal.

vs par
−6.2% fastermul_tree_h · 1 160 ps
on this page
2nd of 10 designs
Fmax-eq.
919.1 MHz derived
unit depth
60 extra

Score · area × time

131.5µm²·ns

120.85362 µm² × 1 088 ps

Beats 100% · matches 0%

1 of 1 other player beaten, 0 equal.

vs par
−11.4% bettermul_tree_h · 148.4 µm²·ns
on this page
2nd of 10 designs
board
layer 1, rank 1 of 2 by score
Pareto
on the front

On the board, of 1 other player: 0 beaten, 0 level, 1 trade off, 0 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 (1 player, 1 record), 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

solution.svdownload
// svGolf task `mul` -- reference solution 5: a log-depth carry-save TREE multiplier whose leaves each hold TWO
// partial-product rows, parametric in N, written with a LOCALPARAM.
//
// The N partial-product rows pp[j] (the shifted copies of a, gated by the bits of b) are taken two at a time:
// leaf j of H = ceil(N/2) holds the carry-save pair (S, C) = (pp[2j], pp[2j+1]), two rows being already a
// pair. The H leaves are those of one balanced binary tree in a binary-HEAP numbering: the root is node 0,
// node k has the children 2k+1 and 2k+2, the H-1 internal nodes are 0 .. H-2 and the H leaves H-1 .. 2H-2.
// EVERY internal node is a 4:2 compressor on its two children's pairs: two stacked rows of 3:2 counters
// (full adders), the first row X/Y, the second S/C; the two rows of the root go through a ripple-carry
// final adder. There is no pairing node and no constant select: a node never has to be told apart from
// its siblings, which is what the single-row tree (mul_tree.sv) needs th/nth for.
//
// The one non-affine quantity, H = ceil(N/2), is a `localparam integer` defined by a division by a literal:
// the sv_comb tactics read it as one more named constant of N (PROVING.md, section 4), so every index and
// bound stays an integer constant expression and `omega` decides the obligations (H = (N + 1) / 2 is
// known to it). `bb` is `b` zero-extended to 2H+1 bits, so the last leaf of an odd N has a zero row.
// A shifted partial product is a GATHER read of `ae`, the zero-padded copy of `a`, so no write ever
// leaves its vector. All sums are taken modulo 2^(2N), exactly the width of the product.
module mul_n #(parameter int N = 16) (
  input  logic [N-1:0]   a, b,
  output logic [2*N-1:0] p
);
  localparam integer H = (N + 1) / 2;   // leaves: ceil(N/2) pairs of partial-product rows

  logic [4*N-2:0]          ae;   // ae[x] = a[x-(2N-1)] for 2N-1 <= x < 3N-1, else 0
  logic [2*H:0]            bb;   // b zero-extended
  logic [2*H-2:0][2*N-1:0] S, C; // sum / carry row of each heap node
  logic [H-1:0][2*N-1:0]   X, Y; // first-row sum / carry of node k (row H-1 unused)
  logic [2*N:0]            R;    // 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 bb[N-1:0]       = b;
  assign bb[2*H:N]       = '0;
  assign X[H-1]          = '0;
  assign Y[H-1]          = '0;
  assign R[0]            = 1'b0;

  genvar j, k, c;
  generate
    // leaves: node H-1+j is the pair of partial-product rows 2j and 2j+1 (gather reads of ae)
    for (j = 0; j < H; j++) begin : g_leaf
      for (c = 0; c < 2*N; c++) begin : g_lc
        assign S[H-1+j][c] = bb[2*j] & ae[c + 2*N - 1 - 2*j];
        assign C[H-1+j][c] = ae[c + 2*N - 2 - 2*j] & bb[2*j+1];
      end
    end
    // internal nodes: a 4:2 compressor on the children 2k+1, 2k+2
    for (k = 0; k < H-1; k++) begin : g_node
      assign Y[k][0] = 1'b0;
      assign C[k][0] = 1'b0;
      for (c = 0; c < 2*N; c++) begin : g_col
        assign X[k][c] = (S[2*k+2][c] ^ C[2*k+1][c]) ^ S[2*k+1][c];
        assign S[k][c] = (X[k][c] ^ C[2*k+2][c]) ^ Y[k][c];
      end
      for (c = 0; c < 2*N-1; c++) begin : g_car
        assign Y[k][c+1] = (S[2*k+2][c] ^ C[2*k+1][c]) ? S[2*k+1][c] : C[2*k+1][c];
        assign C[k][c+1] = (Y[k][c] ^ C[2*k+2][c]) ? X[k][c] : C[2*k+2][c];
      end
    end
    // final carry-propagate (ripple) adder of the root's two rows
    for (c = 0; c < 2*N; c++) begin : g_fin
      assign p[c]   = S[0][c] ^ (C[0][c] ^ R[c]);
      assign R[c+1] = (C[0][c] ^ S[0][c]) ? R[c] : S[0][c];
    end
  endgenerate
endmodule
Solution.lean (360 code lines)
import Submission.Design
import SvGolf.Comb
import LeanModels.Sv.CombEqTactic
import LeanModels.Sv.CombLib

/-!
# Reference solution 5 for task `mul`: a carry-save tree with TWO-ROW leaves, 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_tree_h.sv`: the `N` partial-product rows are taken
two at a time, `H = ceil(N/2)` leaves (nodes `H-1 .. 2H-2` of a binary HEAP, node `k` having the
children `2k+1`, `2k+2`, the root node 0), each leaf a carry-save pair `(S, C) = (pp[2j], pp[2j+1])`;
EVERY internal node is a 4:2 compressor, two stacked rows of full adders, on its children's pairs; the
root's two rows go through a ripple-carry adder.

The new thing is the **localparam**: `localparam integer H = (N + 1) / 2;` (the number of leaves, not
an affine function of `N`). The `sv_comb` tactics read a localparam as one more named constant of `N`
(PROVING.md, section 4); its definition is inlined into every obligation as `(↑N + 1) / 2`, which
`omega` knows (floor division by a literal). That one line replaces what the single-row heap of
`mul_tree.sv` needs four extra loops and a constant select (`th`/`nth`) for.

## Route (the file is in this order)

1. **Arithmetic** (`HeapArith`, plain Lean, no ls-sv): `csa_row`, `node_sum`, `lane_gated` as in
   `mul_tree.lean`, and `heap_mul`: the sums over the CUT `{t, …, 2t}` of the heap are invariant
   modulo `2^(2N)` while `t` runs from `H-1` (the leaves: `a · b` exactly, two rows per leaf, `b`
   zero-extended) down to `0` (the root). There is no pairing case: every internal node compresses.
2. **Structure** (`tree_struct`): `sv_structure` (`CombDesign ∧ Acyclic`); the goals compare 2-D windows and close
   by `intros; sv_rowmajor; omega`, with `H` inlined. The rank is a lexicographic key `(stage, node
   order, phase)` (`treeKey`).
3. **Ports** (`tree_interface`): `sv_interface`; the valuation of a design that has localparams
   (`Summary.val`) is the tactic's business.
4. **Equations, bridge and coverage** (`solution`): the printed equations of `sv_equations` (with
   `((N : Int) + 1) / 2` in the place of `H`) are bridged to the clean facts of `heap_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 heap reads;
   the closing `all_goals … sv_cover` proves them, solving the writer from the read.
-/

/-! ## 1. The arithmetic (plain Lean, `CombLib` only)

The heap-indexed 4:2 carry-save tree multiplier with two-row leaves: `heap_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 HeapArith
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

/-- A 4:2 compressor node: two stacked rows of counters, on a window of `n+1` columns, preserves
the sum exactly up to the two dropped top carries. -/
theorem node_sum (n : Nat) (SL CL SR CR S C X Y : Nat → Bool)
    (hX : ∀ c < n + 1, X c = (SL c ^^ CL c ^^ SR c))
    (hY0 : Y 0 = false)
    (hY : ∀ c, c + 1 < n + 1 → Y (c + 1) = ((SL c && CL c) || (SR c && (SL c ^^ CL c))))
    (hS : ∀ c < n + 1, S c = (X c ^^ Y c ^^ CR c))
    (hC0 : C 0 = false)
    (hC : ∀ c, c + 1 < n + 1 → C (c + 1) = ((X c && Y c) || (CR c && (X c ^^ Y c)))) :
    ∃ q, bfVal S (n + 1) + bfVal C (n + 1) + 2 ^ (n + 1) * q =
      (bfVal SL (n + 1) + bfVal CL (n + 1)) + (bfVal SR (n + 1) + bfVal CR (n + 1)) := by
  have h1 := csa_row n SL CL SR X (fun c => (SL c && CL c) || (SR c && (SL c ^^ CL c))) Y
    hX (fun _ _ => rfl) hY0 hY
  have h2 := csa_row n X Y CR S (fun c => (X c && Y c) || (CR c && (X c ^^ Y c))) C
    hS (fun _ _ => rfl) hC0 hC
  refine ⟨(((SL n && CL n) || (SR n && (SL n ^^ CL n))).toNat) +
    ((X n && Y n) || (CR n && (X n ^^ Y n))).toNat, ?_⟩
  simp only [Nat.mul_add] at h1 h2 ⊢
  generalize 2 ^ (n + 1) = P at *
  omega


/-! ### 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 heap nodes -/

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

/-- Leaves: `csum` of the leaf values is `a · b`, two rows (two bits of `b`) per leaf. -/
theorem csum_leaves2 (H : Nat) (a : Nat) (g : Nat → Bool) (V : Nat → Nat)
    (hV : ∀ j, j < H → V (H - 1 + j) =
      (g (2 * j)).toNat * (a * 2 ^ (2 * j)) + (g (2 * j + 1)).toNat * (a * 2 ^ (2 * j + 1))) :
    ∀ n, n ≤ H → csum V (H - 1) n = a * bfVal g (2 * 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, hV n (by omega), show 2 * (n + 1) = 2 * n + 1 + 1 by omega]
    simp only [bfVal, Nat.pow_succ]
    generalize 2 ^ (2 * n) = P
    generalize (g (2 * n)).toNat = B0
    generalize (g (2 * n + 1)).toNat = B1
    generalize bfVal g (2 * n) = F
    grind

/-- **The whole multiplier**, from the equations of the netlist as clean Boolean facts. `H` is the
number of leaves, `N ≤ 2H ≤ N + 1`: leaf `j` of the heap (node `H - 1 + j`) holds the rows `2j`, `2j+1`
of the partial products. -/
theorem heap_mul0 {N H : Nat} (hN : 1 ≤ N) (hH1 : N ≤ 2 * H) (hH2 : 2 * H ≤ N + 1)
    (a b : BitVec N) (p : BitVec (2 * N))
    (ae bb : Nat → Bool) (S C X Y : Nat → Nat → Bool) (R : 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)
    (hbb1 : ∀ k, k < N → bb k = b.getLsbD k)
    (hbb2 : ∀ k, N ≤ k → k < 2 * H → bb k = false)
    (hleafS : ∀ j, j < H → ∀ c, c < 2 * N →
      S (H - 1 + j) c = (ae (c + (2 * N - 1 - 2 * j)) && bb (2 * j)))
    (hleafC : ∀ j, j < H → ∀ c, c < 2 * N →
      C (H - 1 + j) c = (ae (c + (2 * N - 1 - (2 * j + 1))) && bb (2 * j + 1)))
    (hY0 : ∀ k, k < H - 1 → Y k 0 = false)
    (hC0 : ∀ k, k < H - 1 → C k 0 = false)
    (hX : ∀ k, k < H - 1 → ∀ c, c < 2 * N →
      X k c = (S (2 * k + 1) c ^^ C (2 * k + 1) c ^^ S (2 * k + 2) c))
    (hS : ∀ k, k < H - 1 → ∀ c, c < 2 * N → S k c = (X k c ^^ Y k c ^^ C (2 * k + 2) c))
    (hY : ∀ k, k < H - 1 → ∀ c, c < 2 * N - 1 →
      Y k (c + 1) = ((S (2 * k + 1) c && C (2 * k + 1) c) ||
        (S (2 * k + 2) c && (S (2 * k + 1) c ^^ C (2 * k + 1) c))))
    (hC : ∀ k, k < H - 1 → ∀ c, c < 2 * N - 1 →
      C k (c + 1) = ((X k c && Y k c) || (C (2 * k + 2) c && (X k c ^^ Y k c))))
    (hR0 : R 0 = false)
    (hp : ∀ c, c < 2 * N → p.getLsbD c = (S 0 c ^^ C 0 c ^^ R c))
    (hR : ∀ c, c < 2 * N → R (c + 1) =
      ((S 0 c && C 0 c) || (R c && (S 0 c ^^ C 0 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 (S m) (2 * N) + bfVal (C m) (2 * N)
  -- the leaves: two shifted, gated copies of `a` per leaf
  have hleaf : ∀ j, j < H → V (H - 1 + j) =
      (bb (2 * j)).toNat * (a.toNat * 2 ^ (2 * j)) +
        (bb (2 * j + 1)).toNat * (a.toNat * 2 ^ (2 * j + 1)) := by
    intro j hj
    have e1 : bfVal (S (H - 1 + j)) (2 * N) =
        bfVal (fun c => ae (c + (2 * N - 1 - 2 * j)) && bb (2 * j)) (2 * N) :=
      bfVal_congr (fun c hc => hleafS j hj c hc)
    have e2 : bfVal (C (H - 1 + j)) (2 * N) =
        bfVal (fun c => ae (c + (2 * N - 1 - (2 * j + 1))) && bb (2 * j + 1)) (2 * N) :=
      bfVal_congr (fun c hc => hleafC j hj c hc)
    show bfVal (S (H - 1 + j)) (2 * N) + bfVal (C (H - 1 + j)) (2 * N) = _
    rw [e1, e2, lane_gated hN a ae hae1 hae2 hae3 (2 * j) (bb (2 * j)) (fun hg => by
        by_cases h : N ≤ 2 * j
        · rw [hbb2 (2 * j) h (by omega)] at hg; exact absurd hg (by simp)
        · omega),
      lane_gated hN a ae hae1 hae2 hae3 (2 * j + 1) (bb (2 * j + 1)) (fun hg => by
        by_cases h : N ≤ 2 * j + 1
        · rw [hbb2 (2 * j + 1) h (by omega)] at hg; exact absurd hg (by simp)
        · omega)]
  -- `b`, zero-extended, is the gate bits
  have hbv : bfVal bb (2 * H) = b.toNat := by
    have hs := bfVal_split bb N (2 * H - N)
    rw [show N + (2 * H - N) = 2 * H by omega] at hs
    rw [hs]
    have h1 : bfVal bb N = b.toNat := by
      rw [← bfVal_bv b]; exact bfVal_congr (fun j hj => hbb1 j hj)
    have h2 : bfVal (fun j => bb (N + j)) (2 * H - N) = 0 :=
      bfVal_false (fun j hj => hbb2 (N + j) (by omega) (by omega))
    rw [h1, h2]; simp
  have hleaves : csum V (H - 1) H = a.toNat * b.toNat := by
    rw [csum_leaves2 H a.toNat bb V hleaf H (Nat.le_refl H), hbv]
  -- the nodes: every internal node is a 4:2 compressor
  have hnode : ∀ k, k < H - 1 → ∃ q, V k + 2 ^ (2 * N) * q = V (2 * k + 1) + V (2 * k + 2) := by
    intro k hk
    obtain ⟨q, hq⟩ := node_sum n (S (2 * k + 1)) (C (2 * k + 1)) (S (2 * k + 2)) (C (2 * k + 2))
      (S k) (C k) (X k) (Y k)
      (fun c hc => hX k hk c (by omega)) (hY0 k hk)
      (fun c hc => hY k hk c (by omega))
      (fun c hc => hS k hk c (by omega))
      (hC0 k hk)
      (fun c hc => hC k hk c (by omega))
    rw [← hn] at hq
    exact ⟨q, by simp only [V]; omega⟩
  -- the cut sums
  have hcut : ∀ t, t < H - 1 → ∃ q, csum V t (t + 1) + 2 ^ (2 * N) * q = csum V (t + 1) (t + 2) := by
    intro t ht
    obtain ⟨q, hq⟩ := hnode t ht
    refine ⟨q, ?_⟩
    have f1 : csum V t (t + 1) = V t + csum V (t + 1) t := csum_front V t t
    have f2 : csum V (t + 1) (t + 2) = csum V (t + 1) t + V (t + 1 + t) + V (t + 1 + (t + 1)) := rfl
    rw [f1, f2, show t + 1 + t = 2 * t + 1 by omega, show t + 1 + (t + 1) = 2 * t + 2 by omega]
    omega
  have hdown : ∀ d, d ≤ H - 1 → csum V (H - 1 - d) (H - 1 - d + 1) % 2 ^ (2 * N) =
      (a.toNat * b.toNat) % 2 ^ (2 * N) := by
    intro d
    induction d with
    | zero =>
      intro _
      rw [show H - 1 - 0 + 1 = H by omega, Nat.sub_zero, hleaves]
    | succ d ih =>
      intro hd
      have ih := ih (by omega)
      obtain ⟨q, hq⟩ := hcut (H - 1 - (d + 1)) (by omega)
      rw [show H - 1 - (d + 1) + 1 = H - 1 - d by omega,
        show H - 1 - (d + 1) + 2 = H - 1 - d + 1 by omega] at hq
      rw [← ih, ← hq, Nat.add_mul_mod_self_left, show H - 1 - (d + 1) + 1 = H - 1 - d by omega]
  have hroot := hdown (H - 1) (Nat.le_refl _)
  rw [Nat.sub_self, show (0 : Nat) + 1 = 1 from rfl] at hroot
  have hV0 : csum V 0 1 = V 0 := by simp [csum]
  rw [hV0] at hroot
  -- the final adder
  have hrip := ripple_row (2 * N) (S 0) (C 0) (fun c => S 0 c ^^ C 0 c) p.getLsbD R
    (fun _ _ => rfl) (fun c hc => hp c hc) (fun c hc => hR c hc)
  rw [hR0, 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 0 % 2 ^ (2 * N) := by
    have := sum_split hlt (t := V 0) (q := (R (2 * N)).toNat) (by simp only [V]; omega)
    exact this.1
  apply BitVec.eq_of_toNat_eq
  rw [BitVec.toNat_ofNat, hpv, hroot]


/-! ### The player's forms of the gates

`heap_mul` is `heap_mul0` with the cells written the way THIS design writes them (the printed
equations of `sv_equations`); each is the canonical full-adder cell by a case split on its Booleans. -/

theorem heap_mul {N H : Nat} (hN : 1 ≤ N) (hH1 : N ≤ 2 * H) (hH2 : 2 * H ≤ N + 1)
    (a b : BitVec N) (p : BitVec (2 * N))
    (ae bb : Nat → Bool) (S C X Y : Nat → Nat → Bool) (R : 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)
    (hbb1 : ∀ k, k < N → bb k = b.getLsbD k)
    (hbb2 : ∀ k, N ≤ k → k < 2 * H → bb k = false)
    (hleafS : ∀ j, j < H → ∀ c, c < 2 * N →
      S (H - 1 + j) c = (bb (2 * j) && ae (c + (2 * N - 1 - 2 * j))))
    (hleafC : ∀ j, j < H → ∀ c, c < 2 * N →
      C (H - 1 + j) c = (ae (c + (2 * N - 1 - (2 * j + 1))) && bb (2 * j + 1)))
    (hY0 : ∀ k, k < H - 1 → Y k 0 = false)
    (hC0 : ∀ k, k < H - 1 → C k 0 = false)
    (hX : ∀ k, k < H - 1 → ∀ c, c < 2 * N →
      X k c = ((S (2 * k + 2) c ^^ C (2 * k + 1) c) ^^ S (2 * k + 1) c))
    (hS : ∀ k, k < H - 1 → ∀ c, c < 2 * N → S k c = ((X k c ^^ C (2 * k + 2) c) ^^ Y k c))
    (hY : ∀ k, k < H - 1 → ∀ c, c < 2 * N - 1 →
      Y k (c + 1) = (if (S (2 * k + 2) c ^^ C (2 * k + 1) c) = true then S (2 * k + 1) c else C (2 * k + 1) c))
    (hC : ∀ k, k < H - 1 → ∀ c, c < 2 * N - 1 →
      C k (c + 1) = (if (Y k c ^^ C (2 * k + 2) c) = true then X k c else C (2 * k + 2) c))
    (hR0 : R 0 = false)
    (hp : ∀ c, c < 2 * N → p.getLsbD c = (S 0 c ^^ (C 0 c ^^ R c)))
    (hR : ∀ c, c < 2 * N → R (c + 1) = (if (C 0 c ^^ S 0 c) = true then R c else S 0 c)) :
    p = BitVec.ofNat (2 * N) (a.toNat * b.toNat) := by
  refine heap_mul0 hN hH1 hH2 a b p ae bb S C X Y R hae1 hae2 hae3 hbb1 hbb2 ?_ hleafC hY0 hC0
    ?_ ?_ ?_ ?_ hR0 ?_ ?_
  · intro j hj c hc
    rw [hleafS j hj c hc]; exact Bool.and_comm _ _
  · intro k hk c hc
    rw [hX k hk c hc]
    generalize S (2 * k + 1) c = s1; generalize C (2 * k + 1) c = c1; generalize S (2 * k + 2) c = s2
    cases s1 <;> cases c1 <;> cases s2 <;> decide
  · intro k hk c hc
    rw [hS k hk c hc]
    generalize X k c = x; generalize Y k c = y; generalize C (2 * k + 2) c = c2
    cases x <;> cases y <;> cases c2 <;> decide
  · intro k hk c hc
    rw [hY k hk c hc]
    generalize S (2 * k + 1) c = s1; generalize C (2 * k + 1) c = c1; generalize S (2 * k + 2) c = s2
    cases s1 <;> cases c1 <;> cases s2 <;> decide
  · intro k hk c hc
    rw [hC k hk c hc]
    generalize X k c = x; generalize Y k c = y; generalize C (2 * k + 2) c = c2
    cases x <;> cases y <;> cases c2 <;> decide
  · intro c hc
    rw [hp c hc]
    generalize S 0 c = s0; generalize C 0 c = c0; generalize R c = r
    cases s0 <;> cases c0 <;> cases r <;> decide
  · intro c hc
    rw [hR c hc]
    generalize S 0 c = s0; generalize C 0 c = c0; generalize R c = r
    cases s0 <;> cases c0 <;> cases r <;> decide

end HeapArith

namespace Submission

open LeanModels.Sv LeanModels.Sv.CombView LeanModels.Sv.CombLib LeanModels.Sv.CombEq SvGolf


/-! ## 2. Structure: `sv_structure` -/

/-- The rank: `(stage, node order, phase)`. Inputs and constants first, then the leaves, then the
internal nodes from the last (deepest, highest heap index) to the root, in two phases (the first row of
counters, then the second), then the ripple final adder column by column. `H = (N + 1) / 2` is the
number of leaves. -/
def treeKey (N : Nat) : SiteId → Key
  | .plain _ => (0, 0, 0)
  | .fam "g_lc" _ _ => (1, 0, 0)
  | .fam "g_node" _ [k] => (2, (N + 1) / 2 - 1 - k.toNat, 0)
  | .fam "g_col" 0 [k, _] => (2, (N + 1) / 2 - 1 - k.toNat, 1)
  | .fam "g_col" _ [k, _] => (2, (N + 1) / 2 - 1 - k.toNat, 2)
  | .fam "g_car" 0 [k, _] => (2, (N + 1) / 2 - 1 - k.toNat, 1)
  | .fam "g_car" _ [k, _] => (2, (N + 1) / 2 - 1 - k.toNat, 2)
  | .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 tree_struct (N : Nat) (hN : 1 ≤ N) :
    CombDesign (design.sem N) ∧ Acyclic (design.sem N) ∧ SharedObl design.pdesign [N] := by
  sv_structure (lexRank := treeKey N)
  all_goals (intros; sv_rowmajor; omega)

/-! ## 3. The ports (the task's `Interface`)

`sv_interface`, for a design with a localparam: the valuation is `Summary.val` (the parameters zipped
with the arguments, then the localparams evaluated over them), which the tactic unfolds itself. -/

theorem tree_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 heap reads

Every cell the tree reads must have a writer. The heap rows `[0, H-1)` are written by the node loop,
the rows `[H-1, 2H-1)` by the leaf loop; a carry row is also written in column 0 by its own assign.
`sv_cover` closes these coverage goals itself (it solves the writer's loop indices from the read's row
and column and walks the disjunction of writers, `CombCover.lean`); the number of leaves `H` is
`((N : Int) + 1) / 2`, which `omega` reads. -/

set_option maxHeartbeats 600000 in
theorem solution : Goal := by
  intro N hN
  have hN' : 1 ≤ N := hN.1
  obtain ⟨hc, ha, hso⟩ := tree_struct N hN'
  refine CombTask.satisfiedAt_of_comb (tree_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 HeapArith.heap_mul (H := (N + 1) / 2) hN' (by omega) (by omega) x y
      (XF.bv (2 * N) (st.boolAt "p")) (st.boolAt "ae") (st.boolAt "bb") (st.cell "S" (2 * N))
      (st.cell "C" (2 * N)) (st.cell "X" (2 * N)) (st.cell "Y" (2 * N)) (st.boolAt "R")
      ?_ ?_ ?_ ?_ ?_ ?_ ?_ ?_ ?_ ?_ ?_ ?_ ?_ ?_ ?_ ?_
    · sv_bridge eq_ae_plain_0
    · sv_bridge eq_ae_plain_1 with ba
    · sv_bridge eq_ae_plain_2
    · sv_bridge eq_bb_plain_3 with bb
    · intro k hk1 hk2
      sv_bridge (eq_bb_plain_4 (k - N) (by omega))
    · sv_bridge eq_S_g_lc_0
    · sv_bridge eq_C_g_lc_1
    · sv_bridge eq_Y_g_node_0
    · sv_bridge eq_C_g_node_1
    · sv_bridge eq_X_g_col_0
    · sv_bridge eq_S_g_col_1
    · sv_bridge eq_Y_g_car_0
    · sv_bridge eq_C_g_car_1
    · sv_bridge eq_R_plain_7
    · sv_bridge eq_p_g_fin_0 with XF.getLsbD_bv
    · sv_bridge eq_R_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.

_189_ · sky130_fd_sc_hd__nand2_1 nand2_1 _190_ · sky130_fd_sc_hd__and2_1 and2_1 _191_ · sky130_fd_sc_hd__and2_1 and2_1 _192_ · sky130_fd_sc_hd__and2_1 and2_1 _193_ · sky130_fd_sc_hd__and2_1 and2_1 _194_ · sky130_fd_sc_hd__nand2_1 nand2_1 _195_ · sky130_fd_sc_hd__nand2_1 nand2_1 _196_ · sky130_fd_sc_hd__nand2b_1 nand2b_1 _197_ · sky130_fd_sc_hd__xnor2_1 xnor2_1 _198_ · sky130_fd_sc_hd__nand2_1 nand2_1 _199_ · sky130_fd_sc_hd__xnor2_1 xnor2_1 _200_ · sky130_fd_sc_hd__and2_1 and2_1 _201_ · sky130_fd_sc_hd__nand2_1 nand2_1 _202_ · sky130_fd_sc_hd__nand2_1 nand2_1 _203_ · sky130_fd_sc_hd__nand2_1 nand2_1 _204_ · sky130_fd_sc_hd__nand2_1 nand2_1 _205_ · sky130_fd_sc_hd__xor2_1 xor2_1 _206_ · sky130_fd_sc_hd__nand2_1 nand2_1 _207_ · sky130_fd_sc_hd__xor2_1 xor2_1 _208_ · sky130_fd_sc_hd__nand2_1 nand2_1 _209_ · sky130_fd_sc_hd__nand2_1 nand2_1 _210_ · sky130_fd_sc_hd__nand2_1 nand2_1 _211_ · sky130_fd_sc_hd__xor2_1 xor2_1 _212_ · sky130_fd_sc_hd__nand2_1 nand2_1 _213_ · sky130_fd_sc_hd__xnor2_1 xnor2_1 _214_ · sky130_fd_sc_hd__nand2_1 nand2_1 _215_ · sky130_fd_sc_hd__xor2_1 xor2_1 _216_ · sky130_fd_sc_hd__and2_1 and2_1 _217_ · sky130_fd_sc_hd__nand2_1 nand2_1 _218_ · sky130_fd_sc_hd__and2_1 and2_1 _219_ · sky130_fd_sc_hd__and2_1 and2_1 _220_ · sky130_fd_sc_hd__nand2_1 nand2_1 _221_ · sky130_fd_sc_hd__or2_1 or2_1 _222_ · sky130_fd_sc_hd__nand2_1 nand2_1 _223_ · sky130_fd_sc_hd__nand2_1 nand2_1 _224_ · sky130_fd_sc_hd__and2_1 and2_1 _225_ · sky130_fd_sc_hd__nand2_1 nand2_1 _226_ · sky130_fd_sc_hd__nand2_1 nand2_1 _227_ · sky130_fd_sc_hd__or2_1 or2_1 _228_ · sky130_fd_sc_hd__nand2_1 nand2_1 _229_ · sky130_fd_sc_hd__nand2_1 nand2_1 _230_ · sky130_fd_sc_hd__xnor2_1 xnor2_1 _231_ · sky130_fd_sc_hd__nand2_1 nand2_1 _232_ · sky130_fd_sc_hd__or2_1 or2_1 _233_ · sky130_fd_sc_hd__and2_1 and2_1 _234_ · sky130_fd_sc_hd__xnor2_1 xnor2_1 _235_ · sky130_fd_sc_hd__nand2_1 nand2_1 _236_ · sky130_fd_sc_hd__nand2_1 nand2_1 _237_ · sky130_fd_sc_hd__and2_1 and2_1 _238_ · sky130_fd_sc_hd__nand2_1 nand2_1 _239_ · sky130_fd_sc_hd__and2_1 and2_1 _240_ · sky130_fd_sc_hd__inv_1 inv_1 _241_ · sky130_fd_sc_hd__or2_1 or2_1 _242_ · sky130_fd_sc_hd__nand2_1 nand2_1 _243_ · sky130_fd_sc_hd__nand2_1 nand2_1 _244_ · sky130_fd_sc_hd__and2_1 and2_1 _245_ · sky130_fd_sc_hd__or2_1 or2_1 _246_ · sky130_fd_sc_hd__nand2_1 nand2_1 _247_ · sky130_fd_sc_hd__and2_1 and2_1 _248_ · sky130_fd_sc_hd__nand2_1 nand2_1 _249_ · sky130_fd_sc_hd__nand2_1 nand2_1 _250_ · sky130_fd_sc_hd__nand2_1 nand2_1 _251_ · sky130_fd_sc_hd__and2_1 and2_1 _252_ · sky130_fd_sc_hd__xnor2_1 xnor2_1 _253_ · sky130_fd_sc_hd__xor2_1 xor2_1 _254_ · sky130_fd_sc_hd__xnor2_1 xnor2_1 _255_ · sky130_fd_sc_hd__and2_1 and2_1 _256_ · sky130_fd_sc_hd__xnor2_1 xnor2_1 _257_ · sky130_fd_sc_hd__xor2_1 xor2_1 _258_ · sky130_fd_sc_hd__xnor2_1 xnor2_1 a b p 0 1 2 3 4 5 6 7 1 0 2 3 0 1 2 3

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: 70 cells here, 1 431 at the scoring point. The critical path at N = 4 (14 cells, ending at p[6] rising, 2 206 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: 70 cells, 32 nand2_1, 16 and2_1, 9 xnor2_1, 6 xor2_1, 5 or2_1, 1 inv_1, 1 nand2b_1. There (measured, not ranked): 392.88 µm², 2 206 ps. Open the drawing on its own (SVG, 45.1 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: 58 nets, 57 cells, to p[30] rising at 5 342 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 14 cells to p[6] rising (2 206 ps there); each cell is a link to it in the drawing:

  1. b[1]
  2. _191_ and2_1
  3. _197_ xnor2_1
  4. _198_ nand2_1
  5. _208_ nand2_1
  6. _211_ xor2_1
  7. _213_ xnor2_1
  8. _214_ nand2_1
  9. _227_ or2_1
  10. _237_ and2_1
  11. _241_ or2_1
  12. _248_ nand2_1
  13. _249_ nand2_1
  14. _257_ xor2_1
  15. _258_ xnor2_1
  16. p[6]
every stage (58 rows)
#netedgedriven bycellarrival (ps)transition (ps)stage (ps)
0b[5]fallinput port–136.440.7
1_3710_fall_3834_and2_1305.054.5168.6
2_3753_rise_3838_xnor2_1515.9202.4210.9
3_3764_fall_3839_nand2_1589.247.573.3
4_3775_rise_3840_nand2_1677.280.188.0
5_2409_rise_3843_xor2_1870.6190.5193.4
6_2430_rise_3845_xor2_11 087.4190.5216.8
7_2452_rise_3847_xor2_11 304.0190.2216.6
8_2604_fall_3861_nand2_11 370.953.766.9
9_3036_rise_3996_nand2_11 461.680.290.7
10_3039_rise_3999_xor2_11 655.1190.5193.5
11_3041_rise_4001_xor2_11 873.8193.2218.7
12_3042_fall_4002_nand2_11 938.850.265.0
13_3045_fall_4006_and2_12 095.839.9157.0
14_3094_fall_4055_or2_12 314.549.4218.7
15_3150_fall_4112_and2_12 465.051.4150.5
16_3151_rise_4113_nand2_12 526.641.461.6
17_3152_fall_4115_nand2_12 592.455.065.8
18_3213_rise_4176_nand2_12 647.947.055.5
19_3214_fall_4178_nand2_12 715.555.167.6
20_3281_rise_4245_nand2_12 771.047.055.5
21_3282_fall_4247_nand2_12 838.655.167.6
22_3357_rise_4322_nand2_12 923.483.484.8
23_3441_fall_4405_nand2_12 973.746.350.3
24_3442_rise_4407_nand2_13 061.981.388.2
25_3538_fall_4494_nand2_13 111.746.349.8
26_3539_rise_4496_nand2_13 199.981.388.2
27_3632_fall_4581_nand2_13 249.846.349.9
28_3634_rise_4583_nand2_13 337.981.388.1
29_3722_fall_4663_nand2_13 387.846.349.9
30_3723_rise_4665_nand2_13 476.081.388.2
31_2410_fall_4743_nand2_13 525.846.349.8
32_2411_rise_4745_nand2_13 614.081.388.2
33_2489_fall_4816_nand2_13 663.846.349.8
34_2490_rise_4818_nand2_13 752.081.388.2
35_2563_fall_4884_nand2_13 801.946.349.9
36_2564_rise_4886_nand2_13 890.081.388.1
37_2629_fall_4945_nand2_13 939.946.349.9
38_2630_rise_4947_nand2_14 028.181.388.2
39_2687_fall_4999_nand2_14 077.946.349.8
40_2688_rise_5001_nand2_14 166.181.388.2
41_2742_fall_5050_nand2_14 215.946.349.8
42_2743_rise_5052_nand2_14 304.181.388.2
43_2790_fall_5094_nand2_14 354.046.349.9
44_2791_rise_5096_nand2_14 442.181.388.1
45_2829_fall_5131_nand2_14 492.046.349.9
46_2831_rise_5133_nand2_14 580.281.388.2
47_2866_fall_5165_nand2_14 630.046.349.8
48_2867_rise_5167_nand2_14 689.341.459.3
49_2868_fall_5168_nand2_14 755.155.065.8
50_2893_rise_5191_nand2_14 810.647.055.5
51_2895_fall_5193_nand2_14 878.255.167.6
52_2914_rise_5211_nand2_14 933.746.655.5
53_2916_fall_5213_nand2_15 001.155.167.4
54_2928_rise_5224_nand2_15 065.557.164.4
55_2935_fall_5232_nand2_15 117.637.552.1
56_2937_fall_5233_nand2b_15 245.839.3128.2
57_2404_rise_5235_nand2_15 342.5107.096.7

Cells

at N = 16 · scored

the cells of the repaired netlist, by area10 bars; the table beside the chart lists the same numbers.xor2_1: 3529.64 µm² (µm² · 403 cells)xor2_13529.64 µm² · 403 cellsnand2_1: 3205.57 µm² (µm² · 854 cells)nand2_13205.57 µm² · 854 cellsxnor2_1: 543.02 µm² (µm² · 62 cells)xnor2_1543.02 µm² · 62 cellsand2_1: 425.41 µm² (µm² · 68 cells)and2_1425.41 µm² · 68 cellsnand2b_1: 131.38 µm² (µm² · 21 cells)nand2b_1131.38 µm² · 21 cellsnor2b_1: 62.56 µm² (µm² · 10 cells)nor2b_162.56 µm² · 10 cellsnor2_1: 22.52 µm² (µm² · 6 cells)nor2_122.52 µm² · 6 cellsor2_1: 18.77 µm² (µm² · 3 cells)or2_118.77 µm² · 3 cellsinv_1: 11.26 µm² (µm² · 3 cells)inv_111.26 µm² · 3 cellsmux2_1: 11.26 µm² (µm² · 1 cell)mux2_111.26 µm² · 1 cell
The area of each cell type in the repaired A0′ netlist, at the scoring point: the count times the cell's area in the pinned liberty. Their sum is the ranked area.
cellcountµm² eachµm² in allshare
xor2_14038.75843529.635244.3%
nand2_18543.75363205.574440.3%
xnor2_1628.7584543.02086.8%
and2_1686.2560425.40805.3%
nand2b_1216.2560131.37601.7%
nor2b_1106.256062.56000.8%
nor2_163.753622.52160.3%
or2_136.256018.76800.2%
inv_133.753611.26080.1%
mux2_1111.260811.26080.1%
total1 4317961.3856

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)
gate mix: cells of each type in the scored netlist10 bars; the table beside the chart lists the same numbers.NAND: 854 cells (854.000 GE)NAND854 854.000 GEXOR: 403 cells (806.000 GE)XOR403 806.000 GEAND: 68 cells (90.644 GE)AND68 90.644 GEXNOR: 62 cells (124.000 GE)XNOR62 124.000 GEORNOT: 21 cells (27.993 GE)ORNOT21 27.993 GEANDNOT: 10 cells (13.330 GE)ANDNOT10 13.330 GENOR: 6 cells (6.000 GE)NOR6 6.000 GENOT: 3 cells (2.001 GE)NOT3 2.001 GEOR: 3 cells (3.999 GE)OR3 3.999 GEMUX: 1 cell (2.333 GE)MUX1 2.333 GE
Cells by type; area is the count weighted by the pinned GE table.
cellcountGE eachGE total
NAND8541.000854.000
XOR4032.000806.000
AND681.33390.644
XNOR622.000124.000
ORNOT211.33327.993
ANDNOT101.33313.330
NOR61.0006.000
NOT30.6672.001
OR31.3333.999
MUX12.3332.333
total14311930.300

The record

task
mul
player
late-carry
assisted by
Claude Sonnet 5.5 (Anthropic)
submitted
2026-11-05
notes
Made by an AI agent (Claude Sonnet, Anthropic) in the maintainers' newcomer study on 2026-11-05; the agent chose the player name. The design is the reference mul_tree_h (a carry-save tree of 4:2 compressors with two-row leaves and a ripple-carry final adder) with the same dataflow and generate-block labels; only the Boolean form of the cell assigns changed (XOR association, carries written as muxes, which compressor input arrives late), chosen by a search over those forms scored with score/ppa.py. The proof is the reference proof with one added lemma that restates its arithmetic theorem for these gate forms by a Boolean case split. The header comments of both files are the reference's, unchanged. Proof ported by the maintainers for the 2026-11-06 range change; design unchanged.
scoring point
N = 16
design
solution.sv sha256 9f7eb149ac237cd89d414f5de7f9e021bae46f339b13f5db8726e6f5a635bab5
proof
Solution.lean sha256 c63b53572d1bc3d6e96ca83ca95c175743054d32d495fea945c83216917870b2
canonical text
sha256 566ad1e6139884603cd2a557aaba814a9aef7d2cb0cb33d98e941ba8266b541e
record hash
5b30f35c283bff377e4a02300c0248f4ee89ec2ea61441000da0b896cbc64b34
downloads
5b30f35c283b.sv 5b30f35c283b.lean 5b30f35c283b.json
previous frontier
none: no submission of this task is dated before this one; 1 other submission is dated the same day (2026-11-05), which does not order it

The proof

code lines
360
build time
59.63 s
axioms
Classical.choice, Quot.sound, propext
tactics seen
decide, grind, induction, omega, simp, sv_cover, sv_equations, sv_interface, sv_rowmajor, sv_structure
imports
Submission.Design, SvGolf.Comb, LeanModels.Sv.CombEqTactic, LeanModels.Sv.CombLib

Checker and flow

Checked under the pinned flow of this site's boards.

Yosys
Yosys 0.53 (git sha1 53c22ab7c0ced80861c7536c5dae682c30fb5834, clang++ 17.0.0 -fPIC -O3)
Verilator
Verilator 5.042 2025-11-02 rev UNKNOWN.REV
Lean toolchain
leanprover/lean4:v4.33.0-rc1
svGolf revision
d1f3cae6f01a088007a57b31ff8baa6b839d6f74
lean-surfaces revision
d1f3cae6f01a088007a57b31ff8baa6b839d6f74
canonicalization script
bbd11227d45dc1365f27597fd45c565fe721ba2648450588fc4fd983d00b367b
scoring script
ea58219d8ef14c3bad265218ee266ccdb709ac9d4dc5258edc34d51ee83a0445
GE table
6a29196a3f4b58241f4c9af650c9141e4632b737214d563a11ba142a725c4bea
extractor
4930696021c588c75dc890034c534c95560ad9a28a4955b87d1f8d68b5edbc0c
canonical RTLIL
a07aff68c4817022e892973d23f7c11d90be5d164adfadf08dc04141bec0937e
the checker's files (sha256)
filesha256
check.pye708084f72f955b0ae1aaabe469dba3eacc5a91484a1ed81bcc073b60f3c0b6d
check/Comparator.lean3145eb578ca71e452eff05a398e8ad04673e6c72ca5343b234cb22e2cefe65a6
check/SvEval.leanfb51d72510b515460866c2798aad3c6f55295536e627b92854c3580af215bb91
check/clocked_rules.py73856a45905724544247ad29042bdc2f1778271ec07b15cdb3a5f214685165c1
check/envelope.py076db01a9791bcc25ee84e9b0033604a4b675f00b212eab0c864078e6a4748b7
check/postsynth.py4e1ee2110c63f24e033dd689c929ea686ddf10f4db23b9057c6fb711394c7558
lakefile.tomlf35a88404367d22906effc14d644aac9a9cad47dfd3b704ea8fec88acfaf9c4d
lean-toolchain62c2d9c0fc1ec4c67e151c11eff41ca004ef38e179cf9476c230406e6defedef
score/banlist.pyf18c14c23a14d55db63e202f42cc424dc84c1dd1c337f132ad60859dc9d5d33a
score/canon.ysbbd11227d45dc1365f27597fd45c565fe721ba2648450588fc4fd983d00b367b
score/fourstate.py95332451ae3754226fb87b46027385b46a91121e7eb9ae25b302bce756d90475
score/ge.json6a29196a3f4b58241f4c9af650c9141e4632b737214d563a11ba142a725c4bea
score/ge_seq.json54b93ca63888009d0671c8978e8ab7bf4fb8ce303bc78d3b4e1f83d946cc3201
score/ppa.py912031912c3e3f6790725161753e95415df34846bd8c27dde3eb707747ddbcbe
score/ppa_mapped.ys93547b343dbac135737e98516b1004a780e1cf2f579e1bd7fed82c908c6d4125
score/ppa_mapped_pins.json4b8688ba3f4d67681658e5944afe878592adf588cd416a8b47597ebe06deb045
score/ppa_pins.jsond66a6086a37a97f5985266057051979fb6a8a3c557d3ef9c42acc1b5e41502d8
score/score.py71d2e3b82f8813ec64a58f0a46909ca23dfab377db44eda6f294b713f45ae781
score/srcgate.py11952f61058051c9e8de91a801b672d07e66a29308e978156c04ffd9f0dbef45
score/svgolf.ysea58219d8ef14c3bad265218ee266ccdb709ac9d4dc5258edc34d51ee83a0445
score/svgolf_seq.ysc3874dbfc85921a87df6eacdfc6b2c9a0e2b34edd5738951e0d8bdb51f4558b2

Post-synthesis check

ls-sv's reading of the design and the scored netlist, simulated on the same vectors, agreed on every one. The vectors are a sample of the inputs: a guard against the two tools disagreeing, not a proof.

mode
full
vectors
330
full set
330
mismatches
0
point
N=16
seed
70067cf91f3a9f9aab05172b97108564a08a3daf2873d6f5ffa9152a05378815
salted
no

Area against time

µm² × ps · lower left is better · ASAP7
mul: this design's sky130 area against time, among the task's designs10 designs by sky130 area (µm²) (x) and time (ps) (y), this one ringed; lower left is better; 2 beyond the view, counted at its edges. The table under the chart lists the same numbers.500055006000650070007500800082008400860088009000sky130 area (µm²)time (ps) · lower is betterpar's score: area × time = 51 311 µm²·nsthe Pareto front of the entriesdadda_ks_16 · baseline, not proved · Score: 46 009 µm²·ns · Area: 8937.3 µm² · Delay: 5148 psdadda_rc_16 · baseline, not proved · Score: 44 946 µm²·ns · Area: 8230.4 µm² · Delay: 5461 psyosys_mul · baseline, not proved · banned operator · Score: 55 259 µm²·ns · Area: 8826.0 µm² · Delay: 6261 psmul_wallace · proved reference · 2026-10-30T05:26:59Z · Score: 53 635 µm²·ns · Area: 8966.1 µm² · Delay: 5982 psmul_tree_h · proved reference · par · 2026-10-30T12:20:05Z · Score: 51 311 µm²·ns · Area: 8563.2 µm² · Delay: 5992 psmul_tree · proved reference · 2026-10-30T03:37:11Z · Score: 52 729 µm²·ns · Area: 8603.3 µm² · Delay: 6129 psplus-minus-two · submission · 2026-11-05 · Score: 50 498 µm²·ns · Area: 7978.9 µm² · Delay: 6329 psthis design · late-carry · submission · 2026-11-05 · Score: 42 530 µm²·ns · Area: 7961.4 µm² · Delay: 5342 psparthis design2 designs beyond the view (above)▴ 2
mul: this design's sky130 area against time, among the task's designs10 designs by sky130 area (µm²) (x) and time (ps) (y), this one ringed; lower left is better; 2 beyond the view, counted at its edges. The table under the chart lists the same numbers.500060007000800085009000sky130 area (µm²)time (ps) · lower is betterpar's score: area × time = 51 311 µm²·nsthe Pareto front of the entriesdadda_ks_16 · baseline, not proved · Score: 46 009 µm²·ns · Area: 8937.3 µm² · Delay: 5148 psdadda_rc_16 · baseline, not proved · Score: 44 946 µm²·ns · Area: 8230.4 µm² · Delay: 5461 psyosys_mul · baseline, not proved · banned operator · Score: 55 259 µm²·ns · Area: 8826.0 µm² · Delay: 6261 psmul_wallace · proved reference · 2026-10-30T05:26:59Z · Score: 53 635 µm²·ns · Area: 8966.1 µm² · Delay: 5982 psmul_tree_h · proved reference · par · 2026-10-30T12:20:05Z · Score: 51 311 µm²·ns · Area: 8563.2 µm² · Delay: 5992 psmul_tree · proved reference · 2026-10-30T03:37:11Z · Score: 52 729 µm²·ns · Area: 8603.3 µm² · Delay: 6129 psplus-minus-two · submission · 2026-11-05 · Score: 50 498 µm²·ns · Area: 7978.9 µm² · Delay: 6329 psthis design · late-carry · submission · 2026-11-05 · Score: 42 530 µm²·ns · Area: 7961.4 µm² · Delay: 5342 psparthis design2 designs beyond the view (above)▴ 2
  • 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
sky130 area (x) against the ranked time (y); lower left is better on both. The step line is the Pareto front of the entries (the proved references and the players' ranked records); a design under par's score curve has a better score than par. Baselines are drawn for scale: scored, never proved. Fitted to the cluster: 2 designs beyond the view, counted at its edge (◂ ▸ ▴ ▾); “All designs” shows them.
the designs as numbers, by score
designkindarea (µm²)time (ps)score (µm²·ns)
late-carry · this designsubmission7 961.395 34242 530
dadda_rc_16 baseline, not proved8 230.395 46144 946
dadda_ks_16 baseline, not proved8 937.325 14846 009
plus-minus-two submission7 978.906 32950 498
mul_tree_h proved reference · par8 563.215 99251 311
mul_tree proved reference8 603.256 12952 729
mul_wallace proved reference8 966.105 98253 635
yosys_mul baseline, not proved · banned operator8 825.966 26155 259
mul_n proved reference8 290.4510 90790 424
mul_n_byhand proved reference8 290.4510 90790 424
mul: this design's ASAP7 area against time, among the task's designs10 designs by ASAP7 area (µm²) (x) and time (ps) (y), this one ringed; lower left is better; 2 beyond the view, counted at its edges. The table under the chart lists the same numbers.100012001400120125130ASAP7 area (µm²)time (ps) · lower is betterpar's score: area × time = 148.4 µm²·nsthe Pareto front of the entriesdadda_ks_16 · baseline, not proved · Score: 120.1 µm²·ns · Area: 133.174 µm² · Delay: 902 psdadda_rc_16 · baseline, not proved · Score: 135.7 µm²·ns · Area: 123.391 µm² · Delay: 1100 psyosys_mul · baseline, not proved · banned operator · Score: 150.1 µm²·ns · Area: 131.789 µm² · Delay: 1139 psmul_tree_h · proved reference · par · 2026-10-30T12:20:05Z · Score: 148.4 µm²·ns · Area: 127.954 µm² · Delay: 1160 psmul_wallace · proved reference · 2026-10-30T05:26:59Z · Score: 158.8 µm²·ns · Area: 133.917 µm² · Delay: 1186 psmul_tree · proved reference · 2026-10-30T03:37:11Z · Score: 153.1 µm²·ns · Area: 128.552 µm² · Delay: 1191 psplus-minus-two · submission · 2026-11-05 · Score: 156.5 µm²·ns · Area: 119.746 µm² · Delay: 1307 psthis design · late-carry · submission · 2026-11-05 · Score: 131.5 µm²·ns · Area: 120.854 µm² · Delay: 1088 psparthis design2 designs beyond the view (above)▴ 2
mul: this design's ASAP7 area against time, among the task's designs10 designs by ASAP7 area (µm²) (x) and time (ps) (y), this one ringed; lower left is better; 2 beyond the view, counted at its edges. The table under the chart lists the same numbers.100012001400120125130ASAP7 area (µm²)time (ps) · lower is betterpar's score: area × time = 148.4 µm²·nsthe Pareto front of the entriesdadda_ks_16 · baseline, not proved · Score: 120.1 µm²·ns · Area: 133.174 µm² · Delay: 902 psdadda_rc_16 · baseline, not proved · Score: 135.7 µm²·ns · Area: 123.391 µm² · Delay: 1100 psyosys_mul · baseline, not proved · banned operator · Score: 150.1 µm²·ns · Area: 131.789 µm² · Delay: 1139 psmul_tree_h · proved reference · par · 2026-10-30T12:20:05Z · Score: 148.4 µm²·ns · Area: 127.954 µm² · Delay: 1160 psmul_wallace · proved reference · 2026-10-30T05:26:59Z · Score: 158.8 µm²·ns · Area: 133.917 µm² · Delay: 1186 psmul_tree · proved reference · 2026-10-30T03:37:11Z · Score: 153.1 µm²·ns · Area: 128.552 µm² · Delay: 1191 psplus-minus-two · submission · 2026-11-05 · Score: 156.5 µm²·ns · Area: 119.746 µm² · Delay: 1307 psthis design · late-carry · submission · 2026-11-05 · Score: 131.5 µm²·ns · Area: 120.854 µm² · Delay: 1088 psparthis design2 designs beyond the view (above)▴ 2
  • 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
ASAP7 area (x) against the ASAP7 time (y); lower left is better on both. The step line is the Pareto front of the entries (the proved references and the players' ranked records); a design under par's score curve has a better score than par. Baselines are drawn for scale: scored, never proved. Fitted to the cluster: 2 designs beyond the view, counted at its edge (◂ ▸ ▴ ▾); “All designs” shows them.
the designs as numbers, by score
designkindarea (µm²)time (ps)score (µm²·ns)
dadda_ks_16 baseline, not proved133.174902120.1
late-carry · this designsubmission120.8541 088131.5
dadda_rc_16 baseline, not proved123.3911 100135.7
mul_tree_h proved reference · par127.9541 160148.4
yosys_mul baseline, not proved · banned operator131.7891 139150.1
mul_tree proved reference128.5521 191153.1
plus-minus-two submission119.7461 307156.5
mul_wallace proved reference133.9171 186158.8
mul_n proved reference123.9301 981245.5
mul_n_byhand proved reference123.9301 981245.5

Reproduce and verify

From svgolf/, with the pinned tools (reproduce a score lists them):

shell
# the ranked numbers, at the scoring point (from svgolf/, with the pinned Yosys and liberty)
python3.12 score/ppa.py 5b30f35c283bff377e4a02300c0248f4ee89ec2ea61441000da0b896cbc64b34.sv --task tasks/mul --path \
    --json out.json --netlist scored.v --repaired-netlist repaired.v

Expect, in out.json:

score.rtlil_sha256
a07aff68c4817022e892973d23f7c11d90be5d164adfadf08dc04141bec0937e
ppa.netlist_sha256
543b374acc78d6623eee062b9bb8cebcae8c0c517eed5e224ec57cb6b1042247
ppa.repair.netlist_sha256
993dd1dc69f9bbe8ec3d273856040e9774940d890592395b8d204067104dc600
ppa.area_e4um2_sky130
79613856
ppa.delay_ps_sky130
5342

The schematic and the critical-path data are site/schematics/mul/9f7eb149ac237cd8.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 c18c43c33b117d18…).

From svgolf/, with the pinned tools (reproduce a score lists them):

shell
# 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 5b30f35c283bff377e4a02300c0248f4ee89ec2ea61441000da0b896cbc64b34.sv --task tasks/mul --pdk asap7 --no-mapped \
    --json out.json

Expect, in out.json:

ppa.netlist_sha256
543b374acc78d6623eee062b9bb8cebcae8c0c517eed5e224ec57cb6b1042247
ppa.repair.netlist_sha256
540f9ff763d63b371f98e7f1ee6d30ec774af971d78e9c0cb11cf91d360b1095
ppa.area_e5um2_asap7
12085362
ppa.delay_ps_asap7
1088

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.

Verify the record

Download the three files, then from svgolf/:

shell
# re-run the whole check on this record's files (the cost of the original check)
H=5b30f35c283bff377e4a02300c0248f4ee89ec2ea61441000da0b896cbc64b34
mkdir -p rec/mul && cp "$H.json" "$H.sv" "$H.lean" rec/mul/
python3.12 leaderboard/leaderboard.py verify --data-dir rec --task mul --full

verify --offline checks only the hashes and the arithmetic; plain verify also rescores the design (no Lean; seconds for most designs, a minute or more for the largest), on sky130 and on ASAP7.