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 a24c91127116

reference: par, not an entryreference: par, not an entry scored at N = 16

Recorded by the maintainers from the proved reference mul_tree_h: its design and its proof mul_tree_h.lean (307 code lines). A carry-save tree whose leaves each hold two partial-product rows, every node a 4:2 compressor, with a ripple final adder. The record shows that reference passing the whole checker. It is par, the bar to beatpar, the bar to beat, not a player's entry, and it is not ranked among the submissions (proved references).

Area

8 563.21µm²

8 563.2128 µm² exactly

Beats 0% · matches 0%

0 of 2 other players beaten, 0 equal.

vs par
this design is par
on this page
6th of 10 designs
cells
1 450
flops
0
repair
0 buffers, 0 tie cells
GE
2 025.905 extra
A1′
8 134.05 µm² research

Performance · time

5 992ps

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

Beats 50% · matches 0%

1 of 2 other players beaten, 0 equal.

vs par
this design is par
on this page
5th of 10 designs
Fmax-eq.
166.9 MHz derived
critical path
56 cells
unit depth
62 extra
A1′
6 217 ps research

Score · area × time

51 311µm²·ns

8 563.2128 µm² × 5 992 ps

Beats 0% · matches 0%

0 of 2 other players beaten, 0 equal.

vs par
this design is par
on this page
5th of 10 designs
board
par, the bar to beat: never ranked
Pareto
dominated on this page by late-carry 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

127.954µm²

127.95408 µm² exactly

Beats 0% · matches 0%

0 of 2 other players beaten, 0 equal.

vs par
this design is par
on this page
6th of 10 designs
cells
1 451
flops
0
repair
0 buffers, 0 tie cells
GE
2 025.905 extra

Performance · time

1 160ps

the delay: the latest output arrival

Beats 50% · matches 0%

1 of 2 other players beaten, 0 equal.

vs par
this design is par
on this page
5th of 10 designs
Fmax-eq.
862.1 MHz derived
unit depth
62 extra

Score · area × time

148.4µm²·ns

127.95408 µm² × 1 160 ps

Beats 50% · matches 0%

1 of 2 other players beaten, 0 equal.

vs par
this design is par
on this page
4th of 10 designs
board
par, the bar to beat: never ranked
Pareto
dominated on this page by late-carry 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: 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

mul_tree_h.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] = ae[c + 2*N - 1 - 2*j] & bb[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+1][c] ^ C[2*k+1][c] ^ S[2*k+2][c];
        assign S[k][c] = X[k][c] ^ Y[k][c] ^ C[2*k+2][c];
      end
      for (c = 0; c < 2*N-1; c++) begin : g_car
        assign 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]));
        assign C[k][c+1] = (X[k][c] & Y[k][c]) | (C[2*k+2][c] & (X[k][c] ^ Y[k][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] = (S[0][c] & C[0][c]) | (R[c] & (S[0][c] ^ C[0][c]));
    end
  endgenerate
endmodule
mul_tree_h.lean (307 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_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 = (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]

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.

_225_ · sky130_fd_sc_hd__and2_1 and2_1 _226_ · sky130_fd_sc_hd__nand2_1 nand2_1 _227_ · sky130_fd_sc_hd__and2_1 and2_1 _228_ · sky130_fd_sc_hd__and2_1 and2_1 _229_ · sky130_fd_sc_hd__and2_1 and2_1 _230_ · sky130_fd_sc_hd__xnor2_1 xnor2_1 _231_ · sky130_fd_sc_hd__and2_1 and2_1 _232_ · sky130_fd_sc_hd__and2_1 and2_1 _233_ · sky130_fd_sc_hd__and2_1 and2_1 _234_ · sky130_fd_sc_hd__nand2_1 nand2_1 _235_ · sky130_fd_sc_hd__nand2_1 nand2_1 _236_ · sky130_fd_sc_hd__xor2_1 xor2_1 _237_ · sky130_fd_sc_hd__nand2_1 nand2_1 _238_ · sky130_fd_sc_hd__xor2_1 xor2_1 _239_ · sky130_fd_sc_hd__and2_1 and2_1 _240_ · sky130_fd_sc_hd__xor2_1 xor2_1 _241_ · sky130_fd_sc_hd__and2_1 and2_1 _242_ · sky130_fd_sc_hd__nand2_1 nand2_1 _243_ · sky130_fd_sc_hd__and2_1 and2_1 _244_ · sky130_fd_sc_hd__and2_1 and2_1 _245_ · sky130_fd_sc_hd__and2_1 and2_1 _246_ · sky130_fd_sc_hd__nand2_1 nand2_1 _247_ · sky130_fd_sc_hd__xnor2_1 xnor2_1 _248_ · sky130_fd_sc_hd__nand2_1 nand2_1 _249_ · sky130_fd_sc_hd__xor2_1 xor2_1 _250_ · sky130_fd_sc_hd__nand2_1 nand2_1 _251_ · sky130_fd_sc_hd__xor2_1 xor2_1 _252_ · sky130_fd_sc_hd__nand2_1 nand2_1 _253_ · sky130_fd_sc_hd__xor2_1 xor2_1 _254_ · sky130_fd_sc_hd__and2_1 and2_1 _255_ · sky130_fd_sc_hd__xor2_1 xor2_1 _256_ · sky130_fd_sc_hd__nand2_1 nand2_1 _257_ · sky130_fd_sc_hd__and2_1 and2_1 _258_ · sky130_fd_sc_hd__nand2_1 nand2_1 _259_ · sky130_fd_sc_hd__nand2_1 nand2_1 _260_ · sky130_fd_sc_hd__and2_1 and2_1 _261_ · sky130_fd_sc_hd__nand2_1 nand2_1 _262_ · sky130_fd_sc_hd__xnor2_1 xnor2_1 _263_ · sky130_fd_sc_hd__nand2_1 nand2_1 _264_ · sky130_fd_sc_hd__xor2_1 xor2_1 _265_ · sky130_fd_sc_hd__nand2_1 nand2_1 _266_ · sky130_fd_sc_hd__xor2_1 xor2_1 _267_ · sky130_fd_sc_hd__nand2_1 nand2_1 _268_ · sky130_fd_sc_hd__xor2_1 xor2_1 _269_ · sky130_fd_sc_hd__nand2_1 nand2_1 _270_ · sky130_fd_sc_hd__xor2_1 xor2_1 _271_ · sky130_fd_sc_hd__nand2_1 nand2_1 _272_ · sky130_fd_sc_hd__nand2_1 nand2_1 _273_ · sky130_fd_sc_hd__and2_1 and2_1 _274_ · sky130_fd_sc_hd__and2_1 and2_1 _275_ · sky130_fd_sc_hd__nand2_1 nand2_1 _276_ · sky130_fd_sc_hd__xor2_1 xor2_1 _277_ · sky130_fd_sc_hd__nand2_1 nand2_1 _278_ · sky130_fd_sc_hd__xor2_1 xor2_1 _279_ · sky130_fd_sc_hd__nand2_1 nand2_1 _280_ · sky130_fd_sc_hd__xor2_1 xor2_1 _281_ · sky130_fd_sc_hd__nand2_1 nand2_1 _282_ · sky130_fd_sc_hd__nand2_1 nand2_1 _283_ · sky130_fd_sc_hd__and2_1 and2_1 _284_ · sky130_fd_sc_hd__nand2_1 nand2_1 _285_ · sky130_fd_sc_hd__xor2_1 xor2_1 _286_ · sky130_fd_sc_hd__nand2_1 nand2_1 _287_ · sky130_fd_sc_hd__xor2_1 xor2_1 _288_ · sky130_fd_sc_hd__nand2_1 nand2_1 a b p 0 1 2 3 4 5 6 7 0 1 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: 64 cells here, 1 450 at the scoring point. The critical path at N = 4 (11 cells, ending at p[6] rising, 1 777 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: 64 cells, 27 nand2_1, 18 and2_1, 16 xor2_1, 3 xnor2_1. There (measured, not ranked): 380.36 µm², 1 777 ps. Open the drawing on its own (SVG, 41.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 992 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 11 cells to p[6] rising (1 777 ps there); each cell is a link to it in the drawing:

  1. a[3]
  2. _244_ and2_1
  3. _247_ xnor2_1
  4. _249_ xor2_1
  5. _251_ xor2_1
  6. _253_ xor2_1
  7. _254_ and2_1
  8. _269_ nand2_1
  9. _271_ nand2_1
  10. _279_ nand2_1
  11. _281_ nand2_1
  12. _287_ xor2_1
  13. p[6]
every stage (57 rows)
#netedgedriven bycellarrival (ps)transition (ps)stage (ps)
0a[8]fallinput port–128.133.5
1_3974_fall_6077_and2_1290.651.7162.5
2_4027_rise_6131_xor2_1491.3190.5200.7
3_4029_rise_6133_xor2_1708.0190.5216.7
4_4031_rise_6135_xor2_1924.8190.5216.8
5_4033_rise_6137_xor2_11 141.6190.5216.8
6_4035_rise_6139_xor2_11 358.4190.5216.8
7_4037_rise_6141_xor2_11 575.1190.5216.7
8_4039_rise_6143_xor2_11 791.9190.5216.8
9_4041_rise_6145_xor2_12 008.7190.5216.8
10_4043_rise_6147_xor2_12 225.4190.5216.7
11_4045_rise_6149_xor2_12 430.5174.5205.1
12_4047_rise_6151_xor2_12 645.3190.5214.8
13_4049_rise_6153_xor2_12 862.0190.5216.7
14_4050_fall_6154_nand2_12 933.845.971.8
15_4051_rise_6156_nand2_13 021.080.187.2
16_4108_fall_4763_nand2_13 070.645.949.6
17_4109_rise_4765_nand2_13 157.880.187.2
18_4172_fall_4828_nand2_13 207.445.949.6
19_4173_rise_4830_nand2_13 294.680.187.2
20_4242_fall_4899_nand2_13 344.145.949.5
21_4243_rise_4901_nand2_13 431.380.187.2
22_4319_fall_4977_nand2_13 480.945.949.6
23_4320_rise_4979_nand2_13 568.180.187.2
24_4404_fall_5061_nand2_13 617.745.949.6
25_4405_rise_5063_nand2_13 704.980.187.2
26_4500_fall_5150_nand2_13 754.545.949.6
27_4501_rise_5152_nand2_13 841.780.187.2
28_4595_fall_5238_nand2_13 891.245.949.5
29_4596_rise_5240_nand2_13 978.480.187.2
30_4684_fall_5320_nand2_14 028.045.949.6
31_4685_rise_5322_nand2_14 115.280.187.2
32_3353_fall_5400_nand2_14 164.845.949.6
33_3354_rise_5402_nand2_14 252.080.187.2
34_3432_fall_5473_nand2_14 301.645.949.6
35_3433_rise_5475_nand2_14 388.880.187.2
36_3503_fall_5540_nand2_14 438.445.949.6
37_3504_rise_5542_nand2_14 525.580.187.1
38_3568_fall_5600_nand2_14 575.145.949.6
39_3569_rise_5602_nand2_14 662.380.187.2
40_3631_fall_5658_nand2_14 711.945.949.6
41_3632_rise_5660_nand2_14 799.180.187.2
42_3686_fall_5709_nand2_14 848.745.949.6
43_3687_rise_5711_nand2_14 935.980.187.2
44_3734_fall_5754_nand2_14 985.545.949.6
45_3735_rise_5756_nand2_15 072.680.187.1
46_3774_fall_5791_nand2_15 122.245.949.6
47_3775_rise_5793_nand2_15 209.480.187.2
48_3809_fall_5825_nand2_15 259.045.949.6
49_3810_rise_5827_nand2_15 346.280.187.2
50_3838_fall_5852_nand2_15 395.845.949.6
51_3839_rise_5854_nand2_15 483.080.187.2
52_3860_fall_5873_nand2_15 532.645.949.6
53_3861_rise_5875_nand2_15 619.880.187.2
54_3874_fall_5887_nand2_15 669.345.949.5
55_3876_rise_5889_nand2_15 756.580.187.2
56_3332_rise_5899_xor2_15 991.8247.2235.3

Cells

at N = 16 · scored

the cells of the repaired netlist, by area5 bars; the table beside the chart lists the same numbers.xor2_1: 4063.9 µm² (µm² · 464 cells)xor2_14063.9 µm² · 464 cellsnand2_1: 2567.46 µm² (µm² · 684 cells)nand2_12567.46 µm² · 684 cellsand2_1: 1776.7 µm² (µm² · 284 cells)and2_11776.7 µm² · 284 cellsxnor2_1: 148.89 µm² (µm² · 17 cells)xnor2_1148.89 µm² · 17 cellsnand2b_1: 6.26 µm² (µm² · 1 cell)nand2b_16.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_14648.75844063.897647.5%
nand2_16843.75362567.462430.0%
and2_12846.25601776.704020.7%
xnor2_1178.7584148.89281.7%
nand2b_116.25606.25600.1%
total1 4508563.2128

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 netlist5 bars; the table beside the chart lists the same numbers.NAND: 684 cells (684.000 GE)NAND684 684.000 GEXOR: 464 cells (928.000 GE)XOR464 928.000 GEAND: 284 cells (378.572 GE)AND284 378.572 GEXNOR: 17 cells (34.000 GE)XNOR17 34.000 GEORNOT: 1 cell (1.333 GE)ORNOT1 1.333 GE
Cells by type; area is the count weighted by the pinned GE table.
cellcountGE eachGE total
NAND6841.000684.000
XOR4642.000928.000
AND2841.333378.572
XNOR172.00034.000
ORNOT11.3331.333
total14502025.905

The record

task
mul
player
svgolf-reference
assisted by
Claude (Anthropic)
submitted
2026-11-07
notes
reference solution
scoring point
N = 16
design
mul_tree_h.sv sha256 f77fcae9b71e648b5d7e30fa4c429b86f75c5315ddd543d6e875b94aa33c3f65
proof
mul_tree_h.lean sha256 4cdd0e0cea2cdcedbefd01705fc6632421a6a13cef1dbd7e97960a1198e091a6: the proof of the reference mul_tree_h (mul_tree_h.lean, 307 code lines)
canonical text
sha256 9da951fcd81f0c35011f053686751f21337389747292eb955e58c5e9398cd4b9
record hash
a24c91127116e87437b1cbbfcdd5d5607d34eb89f3c0e72c26f7bfe9a87f2fba
downloads
a24c91127116.sv a24c91127116.lean a24c91127116.json

The proof

code lines
307
build time
34.09 s
axioms
Classical.choice, Quot.sound, propext
tactics seen
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
60ebf526fff291b303d5926cece6ff6451e3b1171acd6f8d48b0c944fa8dd6fe
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
c4b1312dcaed2472d21eb03834d60c9b24a5a5c5b862ec4c35eac709e3dbd673
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 · 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 pslate-carry · submission · 2026-11-05 · Score: 42 530 µm²·ns · Area: 7961.4 µm² · Delay: 5342 psthis design · mul_tree_h · proved reference · par · 2026-10-30T12:20:05Z · Score: 51 311 µm²·ns · Area: 8563.2 µm² · Delay: 5992 psthis design · par2 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 · 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 pslate-carry · submission · 2026-11-05 · Score: 42 530 µm²·ns · Area: 7961.4 µm² · Delay: 5342 psthis design · mul_tree_h · proved reference · par · 2026-10-30T12:20:05Z · Score: 51 311 µm²·ns · Area: 8563.2 µm² · Delay: 5992 psthis design · par2 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 submission7 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 · this designproved 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_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 pslate-carry · submission · 2026-11-05 · Score: 131.5 µm²·ns · Area: 120.854 µm² · Delay: 1088 psplus-minus-two · submission · 2026-11-05 · Score: 156.5 µm²·ns · Area: 119.746 µm² · Delay: 1307 psthis design · mul_tree_h · proved reference · par · 2026-10-30T12:20:05Z · Score: 148.4 µm²·ns · Area: 127.954 µm² · Delay: 1160 psthis design · par2 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_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 pslate-carry · submission · 2026-11-05 · Score: 131.5 µm²·ns · Area: 120.854 µm² · Delay: 1088 psplus-minus-two · submission · 2026-11-05 · Score: 156.5 µm²·ns · Area: 119.746 µm² · Delay: 1307 psthis design · mul_tree_h · proved reference · par · 2026-10-30T12:20:05Z · Score: 148.4 µm²·ns · Area: 127.954 µm² · Delay: 1160 psthis design · par2 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 submission120.8541 088131.5
dadda_rc_16 baseline, not proved123.3911 100135.7
mul_tree_h · this designproved 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 tasks/mul/reference/mul_tree_h.sv --task tasks/mul --path \
    --json out.json --netlist scored.v --repaired-netlist repaired.v

Expect, in out.json:

score.rtlil_sha256
60ebf526fff291b303d5926cece6ff6451e3b1171acd6f8d48b0c944fa8dd6fe
ppa.netlist_sha256
127b7cdd2871845693bd4fab68573b2a9e0f50607dfe336196481aab29557bc5
ppa.repair.netlist_sha256
58bb983dfaa1ad4865d275f70b5d27aeb9768134e4fd4ae1f1609025c5f2c3a8
ppa.area_e4um2_sky130
85632128
ppa.delay_ps_sky130
5992

The schematic and the critical-path data are site/schematics/mul/f77fcae9b71e648b.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 2e1b05730680e17e…).

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 tasks/mul/reference/mul_tree_h.sv --task tasks/mul --pdk asap7 --no-mapped \
    --json out.json

Expect, in out.json:

ppa.netlist_sha256
127b7cdd2871845693bd4fab68573b2a9e0f50607dfe336196481aab29557bc5
ppa.repair.netlist_sha256
4ffa243bda30e7f96a2fb3dd760541b437579e6f2e7a265090b62c9bb8b15555
ppa.area_e5um2_asap7
12795408
ppa.delay_ps_asap7
1160

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=a24c91127116e87437b1cbbfcdd5d5607d34eb89f3c0e72c26f7bfe9a87f2fba
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.