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 / add / record

Record 8c1f662e6f8c

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

Recorded by the maintainers from the proved reference add_n_byhand: its design and its proof add_n_byhand.lean (509 code lines). The same design as add_n, proved by hand without the tactics: the by-hand proof. The record shows that reference passing the whole checker. It is shown with parshown with par, not a player's entry, and it is not ranked among the submissions (proved references).

Area

460.44µm²

460.4416 µm² exactly

Beats 100% · matches 0%

1 of 1 other player beaten, 0 equal.

vs par
±0% equaladd_n · 460.44 µm²
on this page
1st of 10 designs (tied with 4)
cells
80
flops
0
repair
0 buffers, 0 tie cells
GE
112 extra
A1′
440.42 µm² research

Performance · time

2 611ps

the delay: the latest output arrival, s[15] rising

Beats 0% · matches 0%

0 of 1 other player beaten, 0 equal.

vs par
±0% equaladd_n · 2 611 ps
on this page
5th of 10 designs (tied with 4)
Fmax-eq.
383.0 MHz derived
critical path
32 cells
unit depth
33 extra
A1′
6 279 ps research

Score · area × time

1202µm²·ns

460.4416 µm² × 2 611 ps

Beats 0% · matches 0%

0 of 1 other player beaten, 0 equal.

vs par
±0% equaladd_n · 1202 µm²·ns
on this page
2nd of 10 designs (tied with 4)
board
a proved reference, shown with par: never ranked
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: 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 add'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, 1 submission and 4 baselines): not a ranking.

Area

6.998µm²

6.99840 µm² exactly

Beats 100% · matches 0%

1 of 1 other player beaten, 0 equal.

vs par
±0% equaladd_n · 6.998 µm²
on this page
1st of 10 designs (tied with 4)
cells
80
flops
0
repair
0 buffers, 0 tie cells
GE
112 extra

Performance · time

563ps

the delay: the latest output arrival

Beats 0% · matches 0%

0 of 1 other player beaten, 0 equal.

vs par
±0% equaladd_n · 563 ps
on this page
5th of 10 designs (tied with 4)
Fmax-eq.
1776.2 MHz derived
unit depth
33 extra

Score · area × time

3.94µm²·ns

6.99840 µm² × 563 ps

Beats 100% · matches 0%

1 of 1 other player beaten, 0 equal.

vs par
±0% equaladd_n · 3.94 µm²·ns
on this page
2nd of 10 designs (tied with 4)
board
a proved reference, shown with par: never ranked
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 add'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, 1 submission and 4 baselines): not a ranking.

The sources

add_n_byhand.svdownload
// svGolf task `add`, reference solution: an N-bit ripple-carry adder built
// from gates in one generate loop. c[i] is the carry INTO bit i; c[0] = cin,
// cout = c[N]. Per bit: the sum is a three-input XOR and the carry out is the
// majority of a[i], b[i], c[i], written as generate | (carry & propagate).
module add_n #(parameter int N = 16) (
  input  logic [N-1:0] a, b,
  input  logic         cin,
  output logic [N-1:0] s,
  output logic         cout
);
  logic [N:0] c;
  assign c[0] = cin;
  genvar i;
  generate
    for (i = 0; i < N; i++) begin : g_bit
      assign s[i]   = a[i] ^ b[i] ^ c[i];
      assign c[i+1] = (a[i] & b[i]) | (c[i] & (a[i] ^ b[i]));
    end
  endgenerate
  assign cout = c[N];
endmodule
add_n_byhand.lean (509 code lines)
import Submission.Design
import SvGolf.Comb
import LeanModels.Sv.Gen2
import LeanModels.Sv.Hier2Gen
import LeanModels.Sv.Known2

/-!
# Reference solution for task `add` (N-bit adder with carry): the proof

This is a submission file like any player's `Solution.lean`. The checker
generates the module `Submission.Design`, which loads the player's envelope as
`Submission.design` and names the statement to prove `Submission.Goal`
(`SvGolf.Tasks.Add.Statement Submission.design.sem`). This file proves

    theorem Submission.solution : Submission.Goal

for EVERY `N ≥ 1`: `N` stays a variable from the envelope to the conclusion,
nothing is unrolled at a fixed width and nothing is bit-blasted. The design
(`reference/add_n.sv`) is a ripple-carry adder written with one `generate for`
over the bit index.

The proof is lean-surfaces' `Examples/system-verilog/ripple_add` acceptance
proof (`gen.lean` + `proof.lean`), COPIED here with `ripple_add` renamed to
`design` (svGolf never imports `Examples`), plus the last step that connects
its settled-state lemma to the task's `fn`.

## Route

1. **The design at symbolic `N`** (`ripple_sem_processes`, `ripple_sem_decls`):
   `instantiate2` split into its stages by `PDesign.instantiate2_of_ok`; the
   generate-for expanded by `GenItem.expand2_genFor_ascending_processes`; the
   one-driver guard of `name.sem` discharged for every `N`
   (`ripple_driversOk`). The instantiated design is

       0:      c[0]   = cin
       1:      cout   = c[N]
       2i+2:   s[i]   = a[i] ^ b[i] ^ c[i]                       (i < N)
       2i+3:   c[i+1] = (a[i] & b[i]) | (c[i] & (a[i] ^ b[i]))   (i < N)

2. **The comb profile and acyclicity** (`ripple_comb`, `ripple_acyclic`, rank
   certificate `rippleRank`: carry-in 0, bit `i`'s cells `i + 1`, carry-out
   `N + 1`).
3. **The settled state** (`ripple_settled`): in every solution of the
   design's equations, the carry chain holds core's `BitVec.carry` window by
   window (induction on the bit index, one process equation per step), so `s`
   is `x + y + ci` and `cout` is `2^N ≤ x + y + ci`.
4. **The statement** (`solution`): `CombTask.satisfiedAt_of_comb`
   (`SvGolf/Comb.lean`) reduces it to the interface and step 3;
   `sum_eq_ofNat` and `carry_eq_ofNat` turn the `BitVec` sum and carry into
   the task's `Nat` form.

## Adapting it to another design

Steps 1a-1e pin THIS envelope: `ripple_generates` and `ripple_static_source`
are `rfl` statements of its generate loop and static assigns (the loop's
`resolved_count`, `some 16`, is the slang count at the default `N = 16`, so
even changing the default changes it), and the instantiation lemmas follow
from them. A different design restates those and re-proves steps 2-3; the
reduction in step 4 and its `Nat` bridges carry over unchanged.
-/

namespace Submission

open LeanModels.Sv SvGolf

/-! ## 1a. The envelope's shape (pinned by `rfl`) -/

/-- The sum-bit template `assign s[i] = a[i] ^ b[i] ^ c[i];`. -/
def sumT : PProcess :=
  .assignL (.bitSel "s" (.genvarRef "i"))
    (.binary (.m0 .xor)
      (.binary (.m0 .xor) (.bitSel (.ident "a") (.genvarRef "i"))
        (.bitSel (.ident "b") (.genvarRef "i")))
      (.bitSel (.ident "c") (.genvarRef "i")))

/-- The carry template `assign c[i+1] = (a[i] & b[i]) | (c[i] & (a[i] ^ b[i]));`. -/
def carryT : PProcess :=
  .assignL (.bitSel "c" (.binary (.m0 .add) (.genvarRef "i") (.int 1)))
    (.binary (.m0 .or)
      (.binary (.m0 .and) (.bitSel (.ident "a") (.genvarRef "i"))
        (.bitSel (.ident "b") (.genvarRef "i")))
      (.binary (.m0 .and) (.bitSel (.ident "c") (.genvarRef "i"))
        (.binary (.m0 .xor) (.bitSel (.ident "a") (.genvarRef "i"))
          (.bitSel (.ident "b") (.genvarRef "i")))))

/-- The one generate item: `for (i = 0; i s< N; i++)` over the two
templates (`resolved_count` 4 is the defaults metadata). -/
theorem ripple_generates :
    design.pdesign.generates =
      [.genFor (some "g_bit") "i" (.int 0) (.binary .slt (.genvarRef "i") (.paramRef "N" none))
        (.unary .inc (.genvarRef "i")) (some 16) ([sumT, carryT].map GenItem.process)] := rfl

/-- The two static continuous assigns, in source order. Their labels are
static (`0`, `N`) over the parameter-dependent `[N:0]`, so both selects are
`ArraySel` paths, normalized against the declared range at every point
(2026-10-28; the legacy `BitSel` read a label as an offset from bit 0). -/
theorem ripple_static_source :
    design.pdesign.processes =
      [.assignL (.arrayElem "c" [.int 0]) (.ident "cin"),
       .assign "cout" (.arrayElem "c" [.paramRef "N" none])] := rfl

/-! ## 1b. The instantiated vocabulary -/

/-- `c[0] = cin`. -/
def cinProc : Process2 := .assign (.slice "c" 0 1) (.ident "cin")

/-- `cout = c[N]`. -/
def coutProc (N : Nat) : Process2 := .assign (.ident "cout") (.slice (.ident "c") N 1)

/-- `s[i] = a[i] ^ b[i] ^ c[i]` at bit `i`. -/
def sumProc (i : Nat) : Process2 :=
  .assign (.slice "s" i 1)
    (.binary .xor (.binary .xor (.slice (.ident "a") i 1) (.slice (.ident "b") i 1))
      (.slice (.ident "c") i 1))

/-- `c[i+1] = (a[i] & b[i]) | (c[i] & (a[i] ^ b[i]))` at bit `i`. -/
def carryProc (i : Nat) : Process2 :=
  .assign (.slice "c" (i + 1) 1)
    (.binary .or (.binary .and (.slice (.ident "a") i 1) (.slice (.ident "b") i 1))
      (.binary .and (.slice (.ident "c") i 1)
        (.binary .xor (.slice (.ident "a") i 1) (.slice (.ident "b") i 1))))

/-- The whole instantiated process list, in `instantiate2`'s order: static
processes, then the generate family bit by bit (sum before carry). -/
def rippleProcs (N : Nat) : List Process2 :=
  cinProc :: coutProc N :: (List.range N).flatMap fun i => [sumProc i, carryProc i]

/-- The instantiated declarations (ports first, then `c`), for `N ≥ 1`. -/
def rippleDecls (N : Nat) : List Decl :=
  [{ name := "a", width := N, isInput := true },
   { name := "b", width := N, isInput := true },
   { name := "cin", width := 1, isInput := true },
   { name := "s", width := N, isOutput := true },
   { name := "cout", width := 1, isOutput := true },
   { name := "c", width := N + 1 }]

/-! ## 1c. The stages of `instantiate2` at symbolic `N` -/

/-- The declaration scope: `N` bound, its declared type `int` 32 bits. -/
def rippleScope (N : Int) : IEnv := { vals := [("N", N)], pwidths := [("N", 32)] }

/-- The parameter environment: the declaration scope plus the packed-array
geometry table evaluated in it (`dimTable` — every `instantiate2`
environment carries it). -/
def rippleEnv (N : Int) : IEnv :=
  { rippleScope N with dims := dimTable (rippleScope N) design.pdesign.decls }

/-- Declarations at raw widths: `wv` = `|N-1| + 1` (the `[N-1:0]` ports),
`wc` = `|N| + 1` (`c[N:0]`) — the width rule is total in `N`, which is why
the process-list theorems below need no `1 ≤ N`. -/
def rippleDeclsRaw (wv wc : Nat) : List Decl :=
  [{ name := "a", width := wv, isInput := true },
   { name := "b", width := wv, isInput := true },
   { name := "cin", width := 1, isInput := true },
   { name := "s", width := wv, isOutput := true },
   { name := "cout", width := 1, isOutput := true },
   { name := "c", width := wc }]

/-- Every declaration is 1-D: element width 1. -/
def rippleElems : List (String × Nat) :=
  [("a", 1), ("b", 1), ("cin", 1), ("s", 1), ("cout", 1), ("c", 1)]

/-- The parameter stage binds `N` (by `rfl`). -/
theorem ripple_paramEnv (N : Int) : design.pdesign.paramEnv2 [N] = .ok (rippleEnv N) := by
  rfl

/-- The declaration stage at symbolic `N`: the raw widths `|N-1| + 1` and
`|N| + 1`. -/
theorem ripple_decls (N : Int) :
    buildDecls (rippleEnv N) design.pdesign.decls =
      .ok (rippleDeclsRaw ((N - 1).natAbs + 1) (N.natAbs + 1)) := by
  simp [design.pdesign, buildDecls, instDecl, PType.width_packed_cons, PDim.width, evalInt,
    rippleEnv, rippleScope, List.lookup, rippleDeclsRaw, Gen2.except_ok_bind]

/-- The element-width stage: every declaration has element width 1. -/
theorem ripple_elems (N : Int) :
    buildElems (rippleEnv N) design.pdesign.decls = .ok rippleElems := by
  simp [design.pdesign, buildElems, PType.width_packed_cons, PDim.width, evalInt, rippleEnv,
    rippleScope, List.lookup, rippleElems, Gen2.except_ok_bind]

/-- The sum template at a `Nat`-cast genvar (any surrounding environment:
the template reads only element widths and the genvar). -/
theorem ripple_sum_inst (env : IEnv) (i : Nat) :
    instProcess2 (env.bindVal "i" i) rippleElems sumT = sumProc i := by
  sv2_inst_simp [rippleElems, sumT, sumProc]

/-- The carry template at a `Nat`-cast genvar (`c[i+1]`: offset `i + 1`). -/
theorem ripple_carry_inst (env : IEnv) (i : Nat) :
    instProcess2 (env.bindVal "i" i) rippleElems carryT = carryProc i := by
  sv2_inst_simp [rippleElems, carryT, carryProc]

/-- The static processes (`cout = c[N]` reads the PARAMETER-indexed bit). -/
theorem ripple_static (N : Nat) (wv wc : Nat) :
    design.pdesign.processes.map
        (instProcess2 ((rippleEnv N).withDecls (rippleDeclsRaw wv wc)) rippleElems) =
      [cinProc, coutProc N] := by
  sv2_inst_simp [design.pdesign, rippleEnv, rippleScope, rippleDeclsRaw, rippleElems, cinProc,
    coutProc, dimTable, dimTableStep, PType.width_packed_cons, PDim.width, Int.lt_add_one_iff]

/-- The generate expansion at the surface fuel: the bit-indexed family. -/
theorem ripple_gens (N : Nat) (wv wc : Nat) :
    expandList2 ((rippleEnv N).withDecls (rippleDeclsRaw wv wc)) rippleElems
      (PDesign.surfaceFuel [(N : Int)]) design.pdesign.generates =
      .ok ((List.range N).flatMap fun i => [sumProc i, carryProc i]) := by
  rw [ripple_generates]
  apply expandList2_singleton_ok
  rw [GenItem.expand2_genFor_ascending_processes (N := (N : Int)) (evalInt_int 0)
    (fun i => by simp [rippleEnv, rippleScope, List.lookup])
    (fun i => by simp [evalInt, rippleEnv, rippleScope, List.lookup])
    (by
      have := Nat.le_mul_self (N + 2)
      simp [PDesign.surfaceFuel]
      omega)]
  simp only [Int.sub_zero, Int.toNat_natCast, intRange_zero, List.flatMap_map, List.map_cons,
    List.map_nil, ripple_sum_inst, ripple_carry_inst]

/-- **`instantiate2` at symbolic `N`**: succeeds, and is the assembled
design of the stages above. -/
theorem ripple_instantiate2 (N : Nat) :
    design.pdesign.instantiate2 [(N : Int)] (PDesign.surfaceFuel [(N : Int)]) =
      .ok (design.pdesign.assemble2 (rippleEnv N)
        (rippleDeclsRaw (((N : Int) - 1).natAbs + 1) ((N : Int).natAbs + 1)) rippleElems
        ((List.range N).flatMap fun i => [sumProc i, carryProc i])) :=
  PDesign.instantiate2_of_ok rfl (ripple_paramEnv N) (ripple_decls N) (ripple_elems N)
    (ripple_gens N _ _)

/-! ## 1d. The instantiated design, for every `N` -/

/-- **The instantiated process list, for every `N`** (no unrolling): `c[0] =
cin`, `cout = c[N]`, then `s[i]`, `c[i+1]` for each `i < N`. -/
theorem ripple_processes (N : Nat) :
    (design.pdesign.instantiateD2 [(N : Int)]).processes.toList = rippleProcs N := by
  rw [PDesign.instantiateD2_of_ok (ripple_instantiate2 N), PDesign.assemble2_processes,
    ripple_static]
  simp [rippleProcs, design.pdesign]

/-- **Membership characterization, both directions**:
a process of the instantiated adder is the carry-in, the carry-out, or the
sum/carry cell of some bit `i < N`. -/
theorem mem_ripple_processes (N : Nat) (p : Process2) :
    p ∈ (design.pdesign.instantiateD2 [(N : Int)]).processes.toList ↔
      p = cinProc ∨ p = coutProc N ∨ ∃ i < N, p = sumProc i ∨ p = carryProc i := by
  rw [ripple_processes]
  simp [rippleProcs]

/-- The process count: `2N + 2`. -/
theorem ripple_processes_length (N : Nat) :
    (design.pdesign.instantiateD2 [(N : Int)]).processes.toList.length = 2 * N + 2 := by
  rw [ripple_processes]
  simp only [rippleProcs, List.length_cons, List.length_flatMap, List.length_nil,
    List.map_const', List.sum_replicate_nat, List.length_range]
  omega

/-- **The instantiated declarations** for `N ≥ 1`: `a`, `b`, `s` are `N`
bits, `cin`/`cout` 1 bit, the carry chain `c` is `N + 1` bits. -/
theorem ripple_decls_instantiated (N : Nat) (hN : 1 ≤ N) :
    (design.pdesign.instantiateD2 [(N : Int)]).decls.toList = rippleDecls N := by
  rw [PDesign.instantiateD2_of_ok (ripple_instantiate2 N), PDesign.assemble2_decls]
  have hv : ((N : Int) - 1).natAbs + 1 = N := by omega
  have hc : (N : Int).natAbs = N := Int.natAbs_natCast N
  rw [hv, hc]
  rfl

/-! ## 1e. The one-driver rule, for every `N`

`design.sem N` is `instantiateGD2`: the instantiated design under the
one-driver rule (`Hier2.lean`, "Drivers" — a flat top is checked at every
parameter point since 2026-10-28). The rule holds for every `N`, by the
process-list recipe of `Hier2Gen.lean`: compute the driver windows of
`rippleProcs N` (`ripple_driverWins`), split the generate family with
`pairwise_flatMap_range`, close each pair with `omega`. -/

/-- The envelope's declared names and input ports (parameter-free). -/
theorem ripple_declNames : design.pdesign.declNames = ["a", "b", "cin", "s", "cout", "c"] :=
  rfl

theorem ripple_inputNames_pd : design.pdesign.inputNames = ["a", "b", "cin"] := rfl

/-- The windows bit `i` drives: `s[i]` and `c[i+1]`. -/
def rippleWins (i : Nat) : List DrvWin := [("s", some (i, 1)), ("c", some (i + 1, 1))]

/-- The continuous-driver windows of the instantiated adder, for every `N`. -/
theorem ripple_driverWins (N : Nat) :
    driverWins ["a", "b", "cin", "s", "cout", "c"] (rippleProcs N) =
      [("c", some (0, 1)), ("cout", none)] ++ (List.range N).flatMap rippleWins := by
  have hs : ["a", "b", "cin", "s", "cout", "c"].contains "s" = true := by decide
  have hc : ["a", "b", "cin", "s", "cout", "c"].contains "c" = true := by decide
  have ho : ["a", "b", "cin", "s", "cout", "c"].contains "cout" = true := by decide
  simp only [rippleProcs, driverWins_cons, driverWins_nil, driverWins_flatMap, cinProc, coutProc,
    sumProc, carryProc, Process2.drvWins, Lhs2.drvWins, List.filter_cons, List.filter_nil, hs, hc,
    ho, ↓reduceIte, List.cons_append, List.nil_append, List.append_nil]
  rfl

/-- **The one-driver rule holds for every `N`**: no procedural process;
`c[0]`, the whole `cout`, and per bit `s[i]`, `c[i+1]` are pairwise
disjoint; none is an input port. -/
theorem ripple_driversOk (N : Nat) :
    driversOk design.pdesign.declNames design.pdesign.inputNames (rippleProcs N) = true := by
  rw [ripple_declNames, ripple_inputNames_pd]
  apply driversOk_of_continuous
  · rw [List.flatMap_eq_nil_iff]
    intro p hp
    simp only [rippleProcs, List.mem_cons, List.mem_flatMap, List.mem_range, List.not_mem_nil,
      or_false] at hp
    rcases hp with rfl | rfl | ⟨k, _, rfl | rfl⟩ <;> rfl
  · rw [ripple_driverWins, List.pairwise_append]
    refine ⟨by decide, pairwise_flatMap_range (fun k _ => ?_) (fun j k hjk _ a ha b hb => ?_), ?_⟩
    · simp [rippleWins]
    · simp only [rippleWins, List.mem_cons, List.not_mem_nil, or_false] at ha hb
      rcases ha with rfl | rfl <;> rcases hb with rfl | rfl <;> simp [DrvWin.overlaps] <;> omega
    · intro a ha b hb
      simp only [List.mem_cons, List.not_mem_nil, or_false] at ha
      simp only [List.mem_flatMap, List.mem_range, rippleWins, List.mem_cons, List.not_mem_nil,
        or_false] at hb
      obtain ⟨k, _, hb⟩ := hb
      rcases ha with rfl | rfl <;> rcases hb with rfl | rfl <;> simp [DrvWin.overlaps]
  · intro w hw
    rw [ripple_driverWins] at hw
    simp only [List.mem_append, List.mem_cons, List.not_mem_nil, or_false, List.mem_flatMap,
      List.mem_range, rippleWins] at hw
    rcases hw with (rfl | rfl) | ⟨k, _, (rfl | rfl)⟩ <;> rfl

/-- **`design.sem N` is the instantiated design**, for every `N` (the
guard of `instantiateGD2` passes: `ripple_driversOk`). -/
theorem ripple_sem_eq (N : Nat) :
    design.sem N = design.pdesign.instantiateD2 [(N : Int)] := by
  show design.pdesign.instantiateGD2 [(N : Int)] = _
  apply PDesign.instantiateGD2_of_driversOk
  rw [ripple_processes]
  exact ripple_driversOk N

/-- The `load_design_sv2` family member `design.sem N` is the same
design (`Int.ofNat N` is the binder encoding of `(N : Int)`; the one-driver
guard passes, `ripple_sem_eq`). -/
theorem ripple_sem_processes (N : Nat) :
    (design.sem N).processes.toList = rippleProcs N := by
  rw [ripple_sem_eq]
  exact ripple_processes N

/-- `mem_ripple_processes` for the family member `design.sem N`. -/
theorem mem_ripple_sem_processes (N : Nat) (p : Process2) :
    p ∈ (design.sem N).processes.toList ↔
      p = cinProc ∨ p = coutProc N ∨ ∃ i < N, p = sumProc i ∨ p = carryProc i := by
  rw [ripple_sem_eq]
  exact mem_ripple_processes N p

/-- `ripple_decls_instantiated` for the family member `design.sem N`. -/
theorem ripple_sem_decls (N : Nat) (hN : 1 ≤ N) :
    (design.sem N).decls.toList = rippleDecls N := by
  rw [ripple_sem_eq]
  exact ripple_decls_instantiated N hN

/-! ## 2a. The process table -/

/-- Indexing the bit-interleaved family `[f 0, g 0, f 1, g 1, …]`: entry `m`
is `f (m / 2)` at even `m`, `g (m / 2)` at odd `m`, below `2N`. -/
theorem getElem?_flatMap_pair {α : Type} (N : Nat) : ∀ (f g : Nat → α) (m : Nat),
    ((List.range N).flatMap fun i => [f i, g i])[m]? =
      if m < 2 * N then some (if m % 2 = 0 then f (m / 2) else g (m / 2)) else none := by
  induction N with
  | zero => intro f g m; simp
  | succ N ih =>
    intro f g m
    rw [List.range_succ_eq_map, List.flatMap_cons, List.flatMap_map]
    match m with
    | 0 => simp
    | 1 => simp; omega
    | m + 2 =>
      rw [List.cons_append, List.cons_append, List.nil_append, List.getElem?_cons_succ,
        List.getElem?_cons_succ]
      rw [ih (fun i => f (i + 1)) (fun i => g (i + 1)) m]
      have h1 : (m + 2) / 2 = m / 2 + 1 := by omega
      have h2 : (m + 2) % 2 = m % 2 := by omega
      rw [h1, h2]
      by_cases h : m < 2 * N
      · have h' : m + 2 < 2 * (N + 1) := by omega
        simp only [h, h', if_true]
      · have h' : ¬ (m + 2 < 2 * (N + 1)) := by omega
        simp only [h, h', if_false]

/-- The process array of `design.sem N` is `rippleProcs N` (`ripple_sem_processes`). -/
theorem ripple_getElem? (N j : Nat) :
    (design.sem N).processes[j]? = (rippleProcs N)[j]? := by
  rw [← Array.getElem?_toList, ripple_sem_processes]

/-- **The process table, by index** (every `N`): carry-in at 0, carry-out at
1, then bit `i`'s sum cell at `2i + 2` and carry cell at `2i + 3`. -/
theorem ripple_proc {N j : Nat} {p : Process2} :
    (design.sem N).processes[j]? = some p ↔
      (j = 0 ∧ p = cinProc) ∨ (j = 1 ∧ p = coutProc N) ∨
      ∃ i < N, (j = 2 * i + 2 ∧ p = sumProc i) ∨ (j = 2 * i + 3 ∧ p = carryProc i) := by
  rw [ripple_getElem?]
  match j with
  | 0 => simp [rippleProcs, eq_comm]
  | 1 => simp [rippleProcs, eq_comm]
  | m + 2 =>
    simp only [rippleProcs, List.getElem?_cons_succ, getElem?_flatMap_pair]
    constructor
    · intro h
      by_cases hm : m < 2 * N
      · rw [if_pos hm] at h
        right; right
        refine ⟨m / 2, by omega, ?_⟩
        by_cases h2 : m % 2 = 0
        · rw [if_pos h2, Option.some.injEq] at h
          exact Or.inl ⟨by omega, h.symm⟩
        · rw [if_neg h2, Option.some.injEq] at h
          exact Or.inr ⟨by omega, h.symm⟩
      · rw [if_neg hm] at h
        cases h
    · rintro (⟨h, -⟩ | ⟨h, -⟩ | ⟨i, hi, ⟨h, rfl⟩ | ⟨h, rfl⟩⟩)
      · omega
      · omega
      · obtain rfl : m = 2 * i := by omega
        rw [if_pos (by omega), if_pos (by omega), Nat.mul_div_cancel_left _ (by omega)]
      · obtain rfl : m = 2 * i + 1 := by omega
        rw [if_pos (by omega), if_neg (by omega)]
        congr 2
        omega

/-- Process 0 is the carry-in `c[0] = cin`. -/
theorem ripple_proc_cin (N : Nat) : (design.sem N).processes[0]? = some cinProc :=
  ripple_proc.mpr (Or.inl ⟨rfl, rfl⟩)

/-- Process 1 is the carry-out `cout = c[N]`. -/
theorem ripple_proc_cout (N : Nat) : (design.sem N).processes[1]? = some (coutProc N) :=
  ripple_proc.mpr (Or.inr (Or.inl ⟨rfl, rfl⟩))

/-- Process `2i + 2` is bit `i`'s sum cell. -/
theorem ripple_proc_sum {N i : Nat} (hi : i < N) :
    (design.sem N).processes[2 * i + 2]? = some (sumProc i) :=
  ripple_proc.mpr (Or.inr (Or.inr ⟨i, hi, Or.inl ⟨rfl, rfl⟩⟩))

/-- Process `2i + 3` is bit `i`'s carry cell. -/
theorem ripple_proc_carry {N i : Nat} (hi : i < N) :
    (design.sem N).processes[2 * i + 3]? = some (carryProc i) :=
  ripple_proc.mpr (Or.inr (Or.inr ⟨i, hi, Or.inr ⟨rfl, rfl⟩⟩))

/-! ## 2b. Declarations, windows, driven signals (`1 ≤ N`) -/

section Decls

variable {N : Nat} (hN : 1 ≤ N)
include hN

/-- Declared widths are looked up in `rippleDecls N`. -/
theorem ripple_declWidth (n : String) :
    (design.sem N).declWidth n =
      ((rippleDecls N).find? fun dc => dc.name == n).map Decl.width := by
  simp only [Design2.declWidth, ripple_sem_decls N hN]

/-- `s` is `N` bits. -/
theorem ripple_declWidth_s : (design.sem N).declWidth "s" = some N := by
  simp [ripple_declWidth hN, rippleDecls]

/-- `cout` is 1 bit. -/
theorem ripple_declWidth_cout : (design.sem N).declWidth "cout" = some 1 := by
  simp [ripple_declWidth hN, rippleDecls]

/-- The carry chain `c` is `N + 1` bits. -/
theorem ripple_declWidth_c : (design.sem N).declWidth "c" = some (N + 1) := by
  simp [ripple_declWidth hN, rippleDecls]

/-- The input ports, in declaration order. -/
theorem ripple_inputNames : (design.sem N).inputNames.toList = ["a", "b", "cin"] := by
  simp [Design2.inputNames, ripple_sem_decls N hN, rippleDecls]

/-- **The write windows**: carry-in writes `c[0]`, carry-out the whole 1-bit
`cout`, bit `i`'s sum cell `s[i]` and its carry cell `c[i+1]`. -/
theorem ripple_writes {j : Nat} {wn : Win} : (design.sem N).Writes j wn ↔
    (j = 0 ∧ wn = ("c", 0, 1)) ∨ (j = 1 ∧ wn = ("cout", 0, 1)) ∨
    ∃ i < N, (j = 2 * i + 2 ∧ wn = ("s", i, 1)) ∨
      (j = 2 * i + 3 ∧ wn = ("c", i + 1, 1)) := by
  constructor
  · rintro ⟨t, e, hp, hw⟩
    rcases ripple_proc.mp hp with ⟨rfl, h⟩ | ⟨rfl, h⟩ | ⟨i, hi, ⟨rfl, h⟩ | ⟨rfl, h⟩⟩ <;>
      simp only [cinProc, coutProc, sumProc, carryProc, Process2.assign.injEq] at h <;>
      obtain ⟨rfl, rfl⟩ := h <;>
      simp [ripple_declWidth_cout hN] at hw <;> subst hw
    · exact Or.inl ⟨rfl, rfl⟩
    · exact Or.inr (Or.inl ⟨rfl, rfl⟩)
    · exact Or.inr (Or.inr ⟨i, hi, Or.inl ⟨rfl, rfl⟩⟩)
    · exact Or.inr (Or.inr ⟨i, hi, Or.inr ⟨rfl, rfl⟩⟩)
  · rintro (⟨rfl, rfl⟩ | ⟨rfl, rfl⟩ | ⟨i, hi, ⟨rfl, rfl⟩ | ⟨rfl, rfl⟩⟩)
    · exact ⟨_, _, ripple_proc_cin N, by simp⟩
    · exact ⟨_, _, ripple_proc_cout N, by simp [ripple_declWidth_cout hN]⟩
    · exact ⟨_, _, ripple_proc_sum hi, by simp⟩
    · exact ⟨_, _, ripple_proc_carry hi, by simp⟩

/-- The driven signals: the carry chain, the carry-out, the sum. -/
theorem ripple_driven {n : String} : Driven (design.sem N) n ↔
    n = "c" ∨ n = "cout" ∨ n = "s" := by
  constructor
  · rintro ⟨j, lo, w, h⟩
    rcases (ripple_writes hN).mp h with ⟨-, h⟩ | ⟨-, h⟩ | ⟨i, -, ⟨-, h⟩ | ⟨-, h⟩⟩ <;>
      simp only [Prod.mk.injEq] at h <;> simp [h.1]
  · rintro (rfl | rfl | rfl)
    · exact ⟨0, 0, 1, (ripple_writes hN).mpr (Or.inl ⟨rfl, rfl⟩)⟩
    · exact ⟨1, 0, 1, (ripple_writes hN).mpr (Or.inr (Or.inl ⟨rfl, rfl⟩))⟩
    · exact ⟨2, 0, 1,
        (ripple_writes hN).mpr (Or.inr (Or.inr ⟨0, hN, Or.inl ⟨rfl, rfl⟩⟩))⟩

end Decls

/-! ## 2c. The comb profile and the acyclicity certificate -/

/-- **The ripple adder is in the comb profile** (every `N ≥ 1`): continuous
assigns over 1-bit windows, in range, one driver per bit, no input driven,
no loud node, every read declared, no initializers. -/
theorem ripple_comb (N : Nat) (hN : 1 ≤ N) : CombDesign (design.sem N) where
  nodup := by
    rw [ripple_sem_decls N hN]
    simp only [rippleDecls, List.map_cons, List.map_nil]
    decide
  assign := by
    intro j p hp
    rcases ripple_proc.mp hp with ⟨rfl, rfl⟩ | ⟨rfl, rfl⟩ | ⟨i, hi, ⟨rfl, rfl⟩ | ⟨rfl, rfl⟩⟩
    · exact ⟨_, _, _, rfl, rfl⟩
    · exact ⟨_, _, [("cout", 0, 1)], rfl, by simp [ripple_declWidth_cout hN]⟩
    · exact ⟨_, _, _, rfl, rfl⟩
    · exact ⟨_, _, _, rfl, rfl⟩
  inWidth := by
    intro j n lo w h
    rcases (ripple_writes hN).mp h with ⟨-, h⟩ | ⟨-, h⟩ | ⟨i, hi, ⟨-, h⟩ | ⟨-, h⟩⟩ <;>
      simp only [Prod.mk.injEq] at h <;> obtain ⟨rfl, rfl, rfl⟩ := h
    · exact ⟨_, ripple_declWidth_c hN, by omega⟩
    · exact ⟨_, ripple_declWidth_cout hN, by omega⟩
    · exact ⟨_, ripple_declWidth_s hN, by omega⟩
    · exact ⟨_, ripple_declWidth_c hN, by omega⟩
  notInput := by
    intro n hn
    rw [ripple_inputNames hN]
    rcases (ripple_driven hN).mp hn with rfl | rfl | rfl <;> decide
  disjoint := by
    intro j k wj wk hjk hj hk
    rcases (ripple_writes hN).mp hj with
      ⟨rfl, rfl⟩ | ⟨rfl, rfl⟩ | ⟨i, hi, ⟨rfl, rfl⟩ | ⟨rfl, rfl⟩⟩ <;>
    rcases (ripple_writes hN).mp hk with
      ⟨rfl, rfl⟩ | ⟨rfl, rfl⟩ | ⟨i', hi', ⟨rfl, rfl⟩ | ⟨rfl, rfl⟩⟩ <;>
      rw [Win.overlaps_eq_false] <;> first | (left; decide) | (right; omega) | omega
  selfDisjoint := by
    intro j t e hp
    rcases ripple_proc.mp hp with ⟨-, h⟩ | ⟨-, h⟩ | ⟨i, -, ⟨-, h⟩ | ⟨-, h⟩⟩ <;>
      simp only [cinProc, coutProc, sumProc, carryProc, Process2.assign.injEq] at h <;>
      obtain ⟨rfl, -⟩ := h <;> exact Lhs2.wins_pairwise_of_leaf (fun _ h => by cases h) _
  rhsFree := by
    intro j t e hp
    rcases ripple_proc.mp hp with ⟨-, h⟩ | ⟨-, h⟩ | ⟨i, -, ⟨-, h⟩ | ⟨-, h⟩⟩ <;>
      simp only [cinProc, coutProc, sumProc, carryProc, Process2.assign.injEq] at h <;>
      obtain ⟨-, rfl⟩ := h <;> rfl
  rhsDeclared := by
    intro j t e hp
    rw [ripple_sem_decls N hN]
    rcases ripple_proc.mp hp with ⟨-, h⟩ | ⟨-, h⟩ | ⟨i, -, ⟨-, h⟩ | ⟨-, h⟩⟩ <;>
      simp only [cinProc, coutProc, sumProc, carryProc, Process2.assign.injEq] at h <;>
      obtain ⟨-, rfl⟩ := h <;> simp [Expr2.idents, rippleDecls]
  initWidth := by
    intro n _ dc hdc _ v hv
    rw [ripple_sem_decls N hN] at hdc
    simp [rippleDecls] at hdc
    rcases hdc with rfl | rfl | rfl | rfl | rfl | rfl <;> cases hv
  inFourState := by
    intro dc hdc _
    rw [ripple_sem_decls N hN] at hdc
    simp [rippleDecls] at hdc
    rcases hdc with rfl | rfl | rfl | rfl | rfl | rfl <;> rfl

/-- The rank certificate: carry-in 0, bit `i`'s sum and carry cells (indices
`2i + 2`, `2i + 3`) both `i + 1`, carry-out `N + 1`. -/
def rippleRank (N : Nat) (j : Nat) : Nat :=
  if j = 0 then 0 else if j = 1 then N + 1 else (j - 2) / 2 + 1

/-- **The ripple adder is acyclic**: bit `i`'s cells read `a[i]`, `b[i]` (inputs,
never written) and `c[i]`, written by the carry-in (`i = 0`, rank 0) or by bit
`i - 1`'s carry cell (rank `i`); the carry-out reads `c[N]`, written by bit
`N - 1`'s carry cell (rank `N`). -/
theorem ripple_acyclic (N : Nat) (hN : 1 ≤ N) : Acyclic (design.sem N) := by
  refine ⟨rippleRank N, ?_⟩
  intro j k tj ej tk ek wk hj hk hw hex
  have hWk := (ripple_writes hN).mp ⟨tk, ek, hk, hw⟩
  rcases ripple_proc.mp hj with ⟨rfl, h⟩ | ⟨rfl, h⟩ | ⟨i, hi, ⟨rfl, h⟩ | ⟨rfl, h⟩⟩ <;>
    simp only [cinProc, coutProc, sumProc, carryProc, Process2.assign.injEq] at h <;>
    obtain ⟨-, rfl⟩ := h <;>
    rcases hWk with ⟨rfl, rfl⟩ | ⟨rfl, rfl⟩ | ⟨i', hi', ⟨rfl, rfl⟩ | ⟨rfl, rfl⟩⟩ <;>
    simp [Expr2.reads, Expr2.sliceReads, ReadWin.overlaps, Win.overlaps_eq_true] at hex <;>
    unfold rippleRank <;> (repeat' split) <;> omega

/-! ## 3a. Gate algebra on known 1-bit values -/

section Gates

variable {st : SvState}

/-- A bit select `n[i]` of a known signal (in range). -/
theorem eval_bit {n : String} {w : Nat} {x : BitVec w} {i : Nat}
    (h : SvState.lookup st n = some (LVec.ofBitVec x)) (hi : i < w) :
    evalPure2 st (.slice (.ident n) i 1) = .ok (LVec.ofBitVec (BitVec.ofBool (x.getLsbD i))) :=
  evalPure2_bit_ofBitVec (evalPure2_ident_ofBitVec h) hi

/-- A 1-bit window `n[k]` of a signal whose OTHER bits may be unknown: only the
window's own value matters (the carry chain is known one bit at a time). -/
theorem eval_win {n : String} {u : LVec} {k : Nat} {b : Bool}
    (h : SvState.lookup st n = some u) (hk : u.extract k 1 = LVec.ofBitVec (BitVec.ofBool b)) :
    evalPure2 st (.slice (.ident n) k 1) = .ok (LVec.ofBitVec (BitVec.ofBool b)) := by
  simp only [evalPure2_slice, evalPure2_ident_of_lookup h, Pure2.res_ok_bind, Pure2.res_pure_eq,
    hk]

/-- The sum cell `l ^ m ^ r` over known bits. -/
theorem eval_xor3 {l m r : Expr2} {p q b : Bool}
    (hl : evalPure2 st l = .ok (LVec.ofBitVec (BitVec.ofBool p)))
    (hm : evalPure2 st m = .ok (LVec.ofBitVec (BitVec.ofBool q)))
    (hr : evalPure2 st r = .ok (LVec.ofBitVec (BitVec.ofBool b))) :
    evalPure2 st (.binary .xor (.binary .xor l m) r) =
      .ok (LVec.ofBitVec (BitVec.ofBool (p ^^ q ^^ b))) := by
  rw [evalPure2_xor_ofBitVec (evalPure2_xor_ofBitVec hl hm) hr, BitVec.ofBool_xor_ofBool,
    BitVec.ofBool_xor_ofBool]

/-- The carry cell `(l & m) | (r & (l ^ m))` over known bits is core's
majority `Bool.atLeastTwo` (the gate of `BitVec.carry_succ`). -/
theorem eval_carry {l m r : Expr2} {p q b : Bool}
    (hl : evalPure2 st l = .ok (LVec.ofBitVec (BitVec.ofBool p)))
    (hm : evalPure2 st m = .ok (LVec.ofBitVec (BitVec.ofBool q)))
    (hr : evalPure2 st r = .ok (LVec.ofBitVec (BitVec.ofBool b))) :
    evalPure2 st (.binary .or (.binary .and l m) (.binary .and r (.binary .xor l m))) =
      .ok (LVec.ofBitVec (BitVec.ofBool (Bool.atLeastTwo p q b))) := by
  rw [evalPure2_or_ofBitVec (evalPure2_and_ofBitVec hl hm)
    (evalPure2_and_ofBitVec hr (evalPure2_xor_ofBitVec hl hm)), BitVec.ofBool_xor_ofBool,
    BitVec.ofBool_and_ofBool, BitVec.ofBool_and_ofBool, BitVec.ofBool_or_ofBool]
  cases p <;> cases q <;> cases b <;> rfl

end Gates

/-! ## 3b. The settled state -/

/-- **The ripple adder, one settled state.** In any state that solves the
design's equations and has the comb shape, known inputs `x y : BitVec N`,
`ci : Bool` on `a b cin` give `x + y + ci` (mod `2^N`) on `s` and the carry-out
`2^N ≤ x + y + ci` on `cout`. -/
theorem ripple_settled {N : Nat} (hN : 1 ≤ N) {st : SvState}
    (heqs : CombEqs (design.sem N) st) (hs : StateShape (design.sem N) st)
    {x y : BitVec N} {ci : Bool}
    (ha : SvState.lookup st "a" = some (LVec.ofBitVec x))
    (hb : SvState.lookup st "b" = some (LVec.ofBitVec y))
    (hc : SvState.lookup st "cin" = some (LVec.ofBitVec (BitVec.ofBool ci))) :
    SvState.lookup st "s" = some (LVec.ofBitVec (x + y + (BitVec.ofBool ci).setWidth N)) ∧
      SvState.lookup st "cout" =
        some (LVec.ofBitVec (BitVec.ofBool (2 ^ N ≤ x.toNat + y.toNat + ci.toNat))) := by
  obtain ⟨u, hu, -⟩ := hs.driven "c" ((ripple_driven hN).mpr (Or.inl rfl))
  -- the carry chain, window by window: `c[k]` holds core's `carry k`
  have hcarry : ∀ k, k ≤ N →
      u.extract k 1 = LVec.ofBitVec (BitVec.ofBool (BitVec.carry k x y ci)) := by
    intro k
    induction k with
    | zero =>
      intro _
      obtain ⟨old, hold, hext⟩ :=
        CombEqs.extract_slice heqs (ripple_proc_cin N) (evalPure2_ident_ofBitVec hc)
      rw [hu, Option.some.injEq] at hold
      subst hold
      rw [hext, LVec.resizeU_ofBitVec, BitVec.setWidth_eq, BitVec.carry_zero]
    | succ k ih =>
      intro hk
      have hkN : k < N := by omega
      obtain ⟨old, hold, hext⟩ := CombEqs.extract_slice heqs (ripple_proc_carry hkN)
        (eval_carry (eval_bit ha hkN) (eval_bit hb hkN) (eval_win hu (ih (by omega))))
      rw [hu, Option.some.injEq] at hold
      subst hold
      rw [hext, LVec.resizeU_ofBitVec, BitVec.setWidth_eq, BitVec.carry_succ]
  -- the sum, from its 1-bit windows
  obtain ⟨sv, hsv, hsw⟩ := hs.driven "s" ((ripple_driven hN).mpr (Or.inr (Or.inr rfl)))
  rw [ripple_declWidth_s hN, Option.some.injEq] at hsw
  have hS : sv = LVec.ofBitVec (x + y + (BitVec.ofBool ci).setWidth N) := by
    apply LVec.eq_ofBitVec_of_extract_one hsw.symm
    intro i hi
    obtain ⟨old, hold, hext⟩ := CombEqs.extract_slice heqs (ripple_proc_sum hi)
      (eval_xor3 (eval_bit ha hi) (eval_bit hb hi) (eval_win hu (hcarry i (by omega))))
    rw [hsv, Option.some.injEq] at hold
    subst hold
    rw [hext, LVec.resizeU_ofBitVec, BitVec.setWidth_eq, BitVec.getLsbD_add_add_bool hi,
      Bool.xor_assoc]
  -- the carry-out: `c[N]` is the final carry
  have hco := CombEqs.lookup_ident (ripple_comb N hN) heqs hs (ripple_proc_cout N)
    (eval_win hu (hcarry N (Nat.le_refl N))) (ripple_declWidth_cout hN)
  rw [LVec.resizeU_ofBitVec, BitVec.setWidth_eq, BitVec.carry_width] at hco
  exact ⟨hS ▸ hsv, hco⟩

/-- **The arithmetic reading** of the two outputs: carry-out times `2^N` plus
the truncated sum is the full sum `x + y + ci`. -/
theorem ripple_arith {N : Nat} (hN : 1 ≤ N) (x y : BitVec N) (ci : Bool) :
    (decide (2 ^ N ≤ x.toNat + y.toNat + ci.toNat)).toNat * 2 ^ N +
      (x + y + (BitVec.ofBool ci).setWidth N).toNat = x.toNat + y.toNat + ci.toNat := by
  have hx := x.isLt
  have hy := y.isLt
  have hc : ci.toNat ≤ 1 := Bool.toNat_le ci
  have h2 : 2 ≤ 2 ^ N := Nat.pow_le_pow_right (n := 2) (by decide) hN
  have hcM : ci.toNat < 2 ^ N := by omega
  simp only [BitVec.toNat_add, BitVec.toNat_setWidth, BitVec.toNat_ofBool]
  rw [Nat.mod_eq_of_lt hcM, Nat.mod_add_mod]
  generalize 2 ^ N = M at *
  by_cases h : M ≤ x.toNat + y.toNat + ci.toNat
  · rw [decide_eq_true h, Nat.mod_eq_sub_mod h, Nat.mod_eq_of_lt (by omega)]
    simp only [Bool.toNat_true]
    omega
  · rw [decide_eq_false h, Nat.mod_eq_of_lt (by omega)]
    simp only [Bool.toNat_false]
    omega

/-! ## 4. From the settled state to the task's function -/

/-- A 1-bit vector is the `ofBool` of its only bit. -/
theorem bv1_eq_ofBool (v : BitVec 1) : v = BitVec.ofBool (v.getLsbD 0) := by
  apply BitVec.eq_of_getLsbD_eq
  intro i hi
  obtain rfl : i = 0 := by omega
  simp

/-- The sum the adder computes is the task's `s`: `(x + y + ci) mod 2^N`. -/
theorem sum_eq_ofNat {N : Nat} (hN : 1 ≤ N) (x y : BitVec N) (ci : Bool) :
    x + y + (BitVec.ofBool ci).setWidth N =
      BitVec.ofNat N (x.toNat + y.toNat + (BitVec.ofBool ci).toNat) := by
  apply BitVec.eq_of_toNat_eq
  have h2 : 2 ≤ 2 ^ N := Nat.pow_le_pow_right (n := 2) (by decide) hN
  have hc : ci.toNat < 2 ^ N := by have := Bool.toNat_le ci; omega
  simp only [BitVec.toNat_add, BitVec.toNat_setWidth, BitVec.toNat_ofBool, BitVec.toNat_ofNat]
  rw [Nat.mod_eq_of_lt hc, Nat.mod_add_mod]

/-- The carry-out the adder computes is the task's `cout`: `t / 2^N`, for
any `t < 2 · 2^N`. -/
theorem carry_eq_ofNat {N : Nat} (t : Nat) (ht : t < 2 * 2 ^ N) :
    BitVec.ofBool (decide (2 ^ N ≤ t)) = BitVec.ofNat 1 (t / 2 ^ N) := by
  by_cases h : 2 ^ N ≤ t
  · have hq : t / 2 ^ N = 1 := Nat.div_eq_of_lt_le (by omega) (by omega)
    simp [h, hq]
  · have hq : t / 2 ^ N = 0 := Nat.div_eq_of_lt (by omega)
    simp [h, hq]

/-- The interface: inputs `a b : N`, `cin : 1`; outputs `s : N`, `cout : 1`. -/
theorem add_interface (N : Nat) (hN : 1 ≤ N) :
    SvGolf.Tasks.Add.task.Interface N (design.sem N) := by
  simp [CombTask.Interface, inputPorts, outputPorts, ripple_sem_decls N hN, rippleDecls,
    SvGolf.Tasks.Add.task]

/-- **The reference solution**: the `add` statement, for every `N ≥ 1`. -/
theorem solution : Goal := by
  intro N hN
  have hN' : 1 ≤ N := hN.1
  refine CombTask.satisfiedAt_of_comb (add_interface N hN') (ripple_comb N hN')
    (ripple_acyclic N hN') ?_
  intro st heqs hs v hv
  obtain ⟨x, y, ci, ⟨⟩⟩ := v
  obtain ⟨c0, rfl⟩ : ∃ c0, ci = BitVec.ofBool c0 := ⟨_, bv1_eq_ofBool ci⟩
  simp only [SvGolf.Tasks.Add.task, HoldAll_cons, HoldAll_nil, and_true] at hv ⊢
  obtain ⟨ha, hb, hc⟩ := hv
  obtain ⟨hs', hco⟩ := ripple_settled hN' heqs hs ha hb hc
  have ht : x.toNat + y.toNat + c0.toNat < 2 * 2 ^ N := by
    have : x.toNat < 2 ^ N := x.isLt
    have : y.toNat < 2 ^ N := y.isLt
    have := Bool.toNat_le c0
    have h2 : 2 ≤ 2 ^ N := Nat.pow_le_pow_right (n := 2) (by decide) hN'
    omega
  refine ⟨?_, ?_⟩
  · rw [hs', sum_eq_ofNat hN']
  · rw [hco, carry_eq_ofNat _ ht, BitVec.toNat_ofBool]

/-! ## Axiom pins

Standard axioms only: no `sorry`, no `native_decide`, no `bv_decide`. -/

/--
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.

_36_ · sky130_fd_sc_hd__nand2_1 nand2_1 _37_ · sky130_fd_sc_hd__xor2_1 xor2_1 _38_ · sky130_fd_sc_hd__nand2_1 nand2_1 _39_ · sky130_fd_sc_hd__xor2_1 xor2_1 _40_ · sky130_fd_sc_hd__nand2_1 nand2_1 _41_ · sky130_fd_sc_hd__nand2_1 nand2_1 _42_ · sky130_fd_sc_hd__xor2_1 xor2_1 _43_ · sky130_fd_sc_hd__nand2_1 nand2_1 _44_ · sky130_fd_sc_hd__xor2_1 xor2_1 _45_ · sky130_fd_sc_hd__nand2_1 nand2_1 _46_ · sky130_fd_sc_hd__nand2_1 nand2_1 _47_ · sky130_fd_sc_hd__xor2_1 xor2_1 _48_ · sky130_fd_sc_hd__nand2_1 nand2_1 _49_ · sky130_fd_sc_hd__xor2_1 xor2_1 _50_ · sky130_fd_sc_hd__nand2_1 nand2_1 _51_ · sky130_fd_sc_hd__nand2_1 nand2_1 _52_ · sky130_fd_sc_hd__xor2_1 xor2_1 _53_ · sky130_fd_sc_hd__nand2_1 nand2_1 _54_ · sky130_fd_sc_hd__xor2_1 xor2_1 _55_ · sky130_fd_sc_hd__nand2_1 nand2_1 a b cin cout s 0 1 2 3 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: 20 cells here, 80 at the scoring point. The critical path at N = 4 (8 cells, ending at s[3] rising, 970 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: 20 cells, 12 nand2_1, 8 xor2_1. There (measured, not ranked): 115.11 µm², 970 ps. Open the drawing on its own (SVG, 14.9 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: 33 nets, 32 cells, to s[15] rising at 2 611 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 8 cells to s[3] rising (970 ps there); each cell is a link to it in the drawing:

  1. b[0]
  2. _37_ xor2_1
  3. _38_ nand2_1
  4. _40_ nand2_1
  5. _43_ nand2_1
  6. _45_ nand2_1
  7. _48_ nand2_1
  8. _50_ nand2_1
  9. _54_ xor2_1
  10. s[3]
#netedgedriven bycellarrival (ps)transition (ps)stage (ps)
0b[0]fallinput port–114.422.5
1_117_rise_157_xor2_1302.4190.5188.0
2_118_fall_158_nand2_1374.245.971.8
3_119_rise_159_nand2_1461.480.187.2
4_122_fall_162_nand2_1511.045.949.6
5_123_rise_164_nand2_1598.280.187.2
6_126_fall_167_nand2_1647.745.949.5
7_127_rise_169_nand2_1734.980.187.2
8_130_fall_172_nand2_1784.545.949.6
9_131_rise_174_nand2_1871.780.187.2
10_134_fall_177_nand2_1921.345.949.6
11_135_rise_179_nand2_11 008.580.187.2
12_138_fall_182_nand2_11 058.145.949.6
13_139_rise_184_nand2_11 145.380.187.2
14_142_fall_187_nand2_11 194.845.949.5
15_143_rise_189_nand2_11 282.080.187.2
16_146_fall_192_nand2_11 331.645.949.6
17_147_rise_194_nand2_11 418.880.187.2
18_150_fall_197_nand2_11 468.445.949.6
19_151_rise_199_nand2_11 555.680.187.2
20_154_fall_202_nand2_11 605.245.949.6
21_155_rise_204_nand2_11 692.480.187.2
22_095_fall_207_nand2_11 741.945.949.5
23_096_rise_209_nand2_11 829.180.187.2
24_099_fall_212_nand2_11 878.745.949.6
25_100_rise_214_nand2_11 965.980.187.2
26_103_fall_217_nand2_12 015.545.949.6
27_104_rise_219_nand2_12 102.780.187.2
28_107_fall_222_nand2_12 152.345.949.6
29_108_rise_224_nand2_12 239.580.187.2
30_111_fall_227_nand2_12 289.045.949.5
31_112_rise_229_nand2_12 376.280.187.2
32_92_rise_233_xor2_12 611.5247.2235.3

Cells

at N = 16 · scored

the cells of the repaired netlist, by area2 bars; the table beside the chart lists the same numbers.xor2_1: 280.27 µm² (µm² · 32 cells)xor2_1280.27 µm² · 32 cellsnand2_1: 180.17 µm² (µm² · 48 cells)nand2_1180.17 µm² · 48 cells
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_1328.7584280.268860.9%
nand2_1483.7536180.172839.1%
total80460.4416

Repair (buffer_trees_v1): 0 buffers on 0 nets, 0 tie cells, 1 weighing round; nothing is resized.

Legality: worst load 0.13 × max_capacitance, worst transition 0.165 × 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 netlist2 bars; the table beside the chart lists the same numbers.NAND: 48 cells (48.000 GE)NAND48 48.000 GEXOR: 32 cells (64.000 GE)XOR32 64.000 GE
Cells by type; area is the count weighted by the pinned GE table.
cellcountGE eachGE total
NAND481.00048.000
XOR322.00064.000
total80112.000

The record

task
add
player
svgolf-reference
assisted by
Claude (Anthropic)
submitted
2026-11-07
notes
reference solution
scoring point
N = 16
design
add_n_byhand.sv sha256 6579283e86a83926c68dbaa616514d26e83f7ede393aad333cafb7af3cc47ec5
proof
add_n_byhand.lean sha256 7c977b74da6f96b7023e9bd41214d81dcf907246aea220b8ae3d4743eaf04b13: the proof of the reference add_n_byhand (add_n_byhand.lean, 509 code lines)
canonical text
sha256 e0e932a3ae8a04ac79f407e839b2394287dc32d493b7f699d3001872b580dde0
record hash
8c1f662e6f8cb040b8527e08c0c7f32f64731a1e83e8b78994b433e10f9b2310
downloads
8c1f662e6f8c.sv 8c1f662e6f8c.lean 8c1f662e6f8c.json

The proof

code lines
509
build time
7.25 s
axioms
Classical.choice, Quot.sound, propext
tactics seen
decide, induction, omega, simp
imports
Submission.Design, SvGolf.Comb, LeanModels.Sv.Gen2, LeanModels.Sv.Hier2Gen, LeanModels.Sv.Known2

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
0699af350c19ae28f07e4abd195e746ea9888b6ce94feeb0a43f52c15a0a6622
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
4132
full set
4132
mismatches
0
point
N=16
seed
efb7fed387c86cbde7b7cbe59b98c582ced6221ee1d114f9cea95c25f5dcd877
salted
no

Area against time

µm² × ps · lower left is better · ASAP7
add: 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; 3 beyond the view, counted at its edges. The table under the chart lists the same numbers.2200240026002800450500550600sky130 area (µm²)time (ps) · lower is betterpar's score: area × time = 1202 µm²·nsthe Pareto front of the entriesadd_n_sklansky · baseline, not proved · Score: 1328 µm²·ns · Area: 541.8 µm² · Delay: 2451 ps5 designs at this point · add_mux · add_n · this design · add_proc · add_proc_m5 designs at this point · add_mux · add_n · this design · add_proc · add_proc_m5 designs at this point · add_mux · add_n · this design · add_proc · add_proc_m5 designs at this point · add_mux · add_n · this design · add_proc · add_proc_mselect-six · submission · 2026-11-05 · Score: 1176 µm²·ns · Area: 474.2 µm² · Delay: 2479 ps5 designs at this point · add_mux · add_n · this design · add_proc · add_proc_mthis design · par's numbers1 design beyond the view (right)1 ▸1 design beyond the view (above)▴ 12 designs beyond the view (below)▾ 2
add: 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; 3 beyond the view, counted at its edges. The table under the chart lists the same numbers.2200240026002800500600sky130 area (µm²)time (ps) · lower is betterpar's score: area × time = 1202 µm²·nsthe Pareto front of the entriesadd_n_sklansky · baseline, not proved · Score: 1328 µm²·ns · Area: 541.8 µm² · Delay: 2451 ps5 designs at this point · add_mux · add_n · this design · add_proc · add_proc_m5 designs at this point · add_mux · add_n · this design · add_proc · add_proc_m5 designs at this point · add_mux · add_n · this design · add_proc · add_proc_m5 designs at this point · add_mux · add_n · this design · add_proc · add_proc_mselect-six · submission · 2026-11-05 · Score: 1176 µm²·ns · Area: 474.2 µm² · Delay: 2479 ps5 designs at this point · add_mux · add_n · this design · add_proc · add_proc_mthis design · par's numbers1 design beyond the view (right)1 ▸1 design beyond the view (above)▴ 12 designs beyond the view (below)▾ 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 = 1202 µ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: 3 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)
select-six submission474.202 4791176
add_n proved reference · par460.442 6111202
add_n_byhand · this designproved reference460.442 6111202
add_proc proved reference460.442 6111202
add_mux proved reference460.442 6111202
add_proc_m proved reference460.442 6111202
add_n_kogge_stone baseline, not proved641.872 0641325
add_n_sklansky baseline, not proved541.772 4511328
plus baseline, not proved · banned operator497.983 1431565
cmp_carry baseline, not proved · banned operator857.071 9651684
add: 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.45050055077.588.59ASAP7 area (µm²)time (ps) · lower is betterpar's score: area × time = 3.94 µm²·nsthe Pareto front of the entriesadd_n_sklansky · baseline, not proved · Score: 3.919 µm²·ns · Area: 8.048 µm² · Delay: 487 psplus · baseline, not proved · banned operator · Score: 4.224 µm²·ns · Area: 7.436 µm² · Delay: 568 ps5 designs at this point · add_mux · add_n · this design · add_proc · add_proc_m5 designs at this point · add_mux · add_n · this design · add_proc · add_proc_m5 designs at this point · add_mux · add_n · this design · add_proc · add_proc_m5 designs at this point · add_mux · add_n · this design · add_proc · add_proc_mselect-six · submission · 2026-11-05 · Score: 4.032 µm²·ns · Area: 7.188 µm² · Delay: 561 ps5 designs at this point · add_mux · add_n · this design · add_proc · add_proc_mthis design · par's numbers2 designs beyond the view (right)2 ▸1 design beyond the view (below)▾ 1
add: 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.450500550789ASAP7 area (µm²)time (ps) · lower is betterpar's score: area × time = 3.94 µm²·nsthe Pareto front of the entriesadd_n_sklansky · baseline, not proved · Score: 3.919 µm²·ns · Area: 8.048 µm² · Delay: 487 psplus · baseline, not proved · banned operator · Score: 4.224 µm²·ns · Area: 7.436 µm² · Delay: 568 ps5 designs at this point · add_mux · add_n · this design · add_proc · add_proc_m5 designs at this point · add_mux · add_n · this design · add_proc · add_proc_m5 designs at this point · add_mux · add_n · this design · add_proc · add_proc_m5 designs at this point · add_mux · add_n · this design · add_proc · add_proc_mselect-six · submission · 2026-11-05 · Score: 4.032 µm²·ns · Area: 7.188 µm² · Delay: 561 ps5 designs at this point · add_mux · add_n · this design · add_proc · add_proc_mthis design · par's numbers2 designs beyond the view (right)2 ▸1 design beyond the view (below)▾ 1
  • submission
  • proved reference
  • baseline (not proved)
  • baseline with a banned operator
  • the Pareto front of the entries
  • this design
  • par's score: area × time = 3.94 µ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)
add_n_sklansky baseline, not proved8.0484873.919
add_n proved reference · par6.9985633.94
add_n_byhand · this designproved reference6.9985633.94
add_proc proved reference6.9985633.94
add_mux proved reference6.9985633.94
add_proc_m proved reference6.9985633.94
select-six submission7.1885614.032
add_n_kogge_stone baseline, not proved9.4774324.094
cmp_carry baseline, not proved · banned operator12.5533364.218
plus baseline, not proved · banned operator7.4365684.224

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/add/reference/add_n_byhand.sv --task tasks/add --path \
    --json out.json --netlist scored.v --repaired-netlist repaired.v

Expect, in out.json:

score.rtlil_sha256
0699af350c19ae28f07e4abd195e746ea9888b6ce94feeb0a43f52c15a0a6622
ppa.netlist_sha256
3b7da549a07e36ecd2615bea0205e996b239e7d12cb3917bc04666d5ac4b4cd3
ppa.repair.netlist_sha256
ac214a9f04d7e6d97447f0cf81c012f4905f276361278a247a31b0c089a2dab3
ppa.area_e4um2_sky130
4604416
ppa.delay_ps_sky130
2611

The schematic and the critical-path data are site/schematics/add/6579283e86a83926.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 3d58d970c9866e0f…).

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

Expect, in out.json:

ppa.netlist_sha256
3b7da549a07e36ecd2615bea0205e996b239e7d12cb3917bc04666d5ac4b4cd3
ppa.repair.netlist_sha256
3f31f3f295487ce12bd327899184cd8808d69916e3fc0426de4d1b512cf5a1bf
ppa.area_e5um2_asap7
699840
ppa.delay_ps_asap7
563

leaderboard/asap7/add.json keeps these numbers; python3.12 leaderboard/leaderboard.py asap7 --rescore --task add 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=8c1f662e6f8cb040b8527e08c0c7f32f64731a1e83e8b78994b433e10f9b2310
mkdir -p rec/add && cp "$H.json" "$H.sv" "$H.lean" rec/add/
python3.12 leaderboard/leaderboard.py verify --data-dir rec --task add --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.