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

Carry-less multiplier clmul

Multiply two polynomials over GF(2): ANDs and XORs, no carries.

open for submissions parametric scored at N = 16

Par
3821µm²·nsclmul_n · 3429.5 µm² × 1.11 ns
Record
3821µm²·nsclmul_n · 3429.5 µm² × 1.11 ns
Under par
0%
Par
9.901µm²·nsclmul_n · 50.257 µm² × 0.197 ns
Record
9.901µm²·nsclmul_n · 50.257 µm² × 0.197 ns
Under par
0%

Score

ASAP7 · area × time · µm²·ns · lower is better
clmul: the score of each entry by date1 entry by date (x) and score in µm²·ns (y); the line is the best score so far, the dashed line par. The table under the chart lists the same numbers.360038004000420026 Oct2 Nov9 Novdate enteredscore (µm²·ns) · lower is betterpar: clmul_n, 3821 µm²·nsPAR3821 · provedthe best score so farclmul_n · proved reference · par · 2026-11-01T14:52:49Z · Score: 3821 µm²·ns · Area: 3429.5 µm² · Delay: 1114 ps
clmul: the score of each entry by date1 entry by date (x) and score in µm²·ns (y); the line is the best score so far, the dashed line par. The table under the chart lists the same numbers.360038004000420026 Oct2 Nov9 Novdate enteredscore (µm²·ns) · lower is betterpar: clmul_n, 3821 µm²·nsPARthe best score so farclmul_n · proved reference · par · 2026-11-01T14:52:49Z · Score: 3821 µm²·ns · Area: 3429.5 µm² · Delay: 1114 ps
  • proved reference
  • the best score so far
  • par
Score = area × time: the sky130 cell area of the scored netlist in µm² times the critical-path delay in ns, from the pinned flow, so µm²·ns. Lower is better. It is the one ranked number: Par (the best proved reference's score, the bar to beat), Record (the best accepted score so far, par while no submission beats it), Under par (how far the record is below par, as a share of par's score) and the leaderboard use it. The Pareto front, area in GE (NAND2 gate equivalents), depth and the other numbers are for exploring, under compare the designs; reproduce a score says how to get the same numbers. Rated with Yosys 0.53 (git 53c22ab7, the Homebrew build for macOS arm64, clang++ 17, -O3) and its yosys-abc (ABC 1.01 of 6 May 2025): the flow is deterministic on that build (measured on 52 designs). Another build of the same version can break ABC's ties differently (measured: 3 fdivp designs, up to 2.9% in delay), so a number made on another platform may differ slightly: the build and its hashes. Dots are the entries by date; the proved references are the first entries, dated by when they joined the task.
every entry by date
datedesignkindscore (µm²·ns)area × time
2026-11-01clmul_nproved reference · par38213429.5 µm² × 1.11 ns
clmul: the score of each entry by date1 entry by date (x) and score in µm²·ns (y); the line is the best score so far, the dashed line par, and 5 unproved baselines are labelled on the right. The table under the chart lists the same numbers.10121426 Oct2 Nov9 Novdate enteredscore (µm²·ns) · lower is betterkara_full_16 · baseline, not proved · Score: 14.96 µm²·ns · Area: 44.250 µm² · Delay: 338 pskara_b2_16 · baseline, not proved · Score: 14.6 µm²·ns · Area: 43.988 µm² · Delay: 332 pskara_b4_16 · baseline, not proved · Score: 12.27 µm²·ns · Area: 40.620 µm² · Delay: 302 pskara_b8_16 · baseline, not proved · Score: 10.52 µm²·ns · Area: 41.903 µm² · Delay: 251 psschool_tree_16 · baseline, not proved · Score: 8.574 µm²·ns · Area: 50.141 µm² · Delay: 171 pspar: clmul_n, 9.901 µm²·nskara_full_1614.96 · not provedkara_b2_1614.6 · not provedkara_b4_1612.27 · not provedkara_b8_1610.52 · not provedPAR9.901 · provedschool_tree_168.574 · not provedthe best score so farclmul_n · proved reference · par · 2026-11-01T14:52:49Z · Score: 9.901 µm²·ns · Area: 50.257 µm² · Delay: 197 ps
clmul: the score of each entry by date1 entry by date (x) and score in µm²·ns (y); the line is the best score so far, the dashed line par, and 5 unproved baselines are labelled on the right. The table under the chart lists the same numbers.10121426 Oct2 Nov9 Novdate enteredscore (µm²·ns) · lower is betterkara_full_16 · baseline, not proved · Score: 14.96 µm²·ns · Area: 44.250 µm² · Delay: 338 pskara_b2_16 · baseline, not proved · Score: 14.6 µm²·ns · Area: 43.988 µm² · Delay: 332 pskara_b4_16 · baseline, not proved · Score: 12.27 µm²·ns · Area: 40.620 µm² · Delay: 302 pskara_b8_16 · baseline, not proved · Score: 10.52 µm²·ns · Area: 41.903 µm² · Delay: 251 psschool_tree_16 · baseline, not proved · Score: 8.574 µm²·ns · Area: 50.141 µm² · Delay: 171 pspar: clmul_n, 9.901 µm²·nsPARthe best score so farclmul_n · proved reference · par · 2026-11-01T14:52:49Z · Score: 9.901 µm²·ns · Area: 50.257 µm² · Delay: 197 ps
  • proved reference
  • the best score so far
  • par
  • a baseline's score (not proved)
Score = area × time: the ASAP7 cell area of the scored netlist in µm² times the critical-path delay in ns, from the pinned flow, so µm²·ns. Lower is better. In this view Par (the best proved reference's score, the bar to beat), Record (the best accepted score so far, par while no submission beats it), Under par (how far the record is below par, as a share of par's score) and the leaderboard use it; the records' ranked score is sky130's, the default view. The Pareto front, area in GE (NAND2 gate equivalents), depth and the other numbers are for exploring, under compare the designs; reproduce a score says how to get the same numbers. ASAP7 view. These numbers are on ASAP7 (asap7sc7p5t), a predictive 7 nm library made for research: it has never been fabricated, so no chip can confirm them. Like sky130's score they count no wires. sky130, a real 130 nm process, is the default view (how ASAP7 is scored, and its quirks). Rated with Yosys 0.53 (git 53c22ab7, the Homebrew build for macOS arm64, clang++ 17, -O3) and its yosys-abc (ABC 1.01 of 6 May 2025): the flow is deterministic on that build (measured on 52 designs). Another build of the same version can break ABC's ties differently (measured: 3 fdivp designs, up to 2.9% in delay), so a number made on another platform may differ slightly: the build and its hashes. Dots are the entries by date; the proved references are the first entries, dated by when they joined the task. The dotted levels are baselines: scored, never proved.
every entry by date
datedesignkindscore (µm²·ns)area × time
2026-11-01clmul_nproved reference · par9.90150.257 µm² × 0.197 ns
–kara_b4_16baseline, not proved12.2740.620 µm² × 0.302 ns
–kara_b8_16baseline, not proved10.5241.903 µm² × 0.251 ns
–kara_b2_16baseline, not proved14.643.988 µm² × 0.332 ns
–kara_full_16baseline, not proved14.9644.250 µm² × 0.338 ns
–school_tree_16baseline, not proved8.57450.141 µm² × 0.171 ns

This text quotes sky130 numbers and compares the designs on sky130, the default library; the score, the boards and the charts of this page are on ASAP7, where the order of the designs can differ.

Multiply two polynomials over GF(2): bit k of the 2N-1-bit result is the XOR, over all i + j = k, of a[i] AND b[j]. This is multiplication with the carries removed, the operation behind GHASH, CRCs and binary-field cryptography. The proof covers every N ≥ 1; the score is taken at N = 16.

Run-time + - * / % ** and the ordering comparisons < <= > >= are banned. The proved N × N AND/XOR array has 3429.5 µm² and a 1.11 ns critical path on sky130 (772.267 GE, depth 6). It is the first task whose best known designs are recursive: an unproved Karatsuba circuit is about a fifth smaller, 2756.4 µm², but slower, 1.81 ns (621.959 GE, depth 8).

Interface #

Write a module with exactly this header: port names, directions, widths and order matter, and so do the module's name and its parameters.

clmul_n.sv
module clmul_n #(parameter int N = 16) (
  input  logic [N-1:0]   a, b,
  output logic [2*N-2:0] p
);
portdirectionwidth (bits)
ainputN
binputN
poutput2*N-1

The trusted statement #

A proof must establish the task's trusted statement, the constant SvGolf.Tasks.Clmul.Statement of Spec.lean (module tasks.clmul.Spec). Below it is split into the properties a circuit must have, one card each: what the property guarantees in plain words, and its exact Lean text. Every proved reference has all of them. The last tab, what does not count, lists the designs the checker rejects.

Properties

A design meets the trusted statement exactly when, for every width N with 1 ≤ N < 2^30, it has all of the properties below. Lean checks that, together, they say exactly what the statement says, nothing less and nothing more (the theorem statement_iff_properties).

PARAMS_IN_RANGE is the range the statement covers, not a property: every other card must hold at each value in it.

PARAMS_IN_RANGErangeThe widths the statement covers: every N with 1 ≤ N < 2^30 (so that the widths 2N-1 and 2N-2, int expressions of the module, stay below 2^31, the values an int holds).

The statement covers every width N with 1 ≤ N < 2^30 at once (the module's port widths are int expressions of N, such as 2N, and an int holds no value of 2^31 or more): one proof works for all of them, not just for the width the design is scored at (N = 16). Every property below must hold at each width.

ParamsInRange
/-- The widths the statement covers: every `N` with `1 ≤ N < 2^30` (so that the widths `2N-1` and
`2N-2`, `int` expressions of the module, stay below `2^31`, the values an `int` holds). -/
def ParamsInRange (N : Nat) : Prop :=
  1 ≤ N ∧ N < 2 ^ 30
svgolf/tasks/clmul/Properties.lean:17Open in code
INTERFACEExactly these ports, in this order, with these widths.

The module has exactly the input ports a and b (N bits each) and exactly the output port p (2N − 1 bits), with these widths, in this order. Nothing is missing and nothing is extra: an extra input could serve as a hidden mode pin, and the checker and the scoring tools drive and read the design by these names and widths.

Interface
/-- Exactly these ports, in this order, with these widths. -/
def Interface (N : Nat) (d : Design2) : Prop :=
  inputPorts d = [("a", N), ("b", N)] ∧ outputPorts d = [("p", 2 * N - 1)]
svgolf/tasks/clmul/Properties.lean:22Open in code
UNCLOCKEDNo process runs at a clock edge (no always_ff, no always @(posedge …)).

No process of the design runs at a clock edge: no always_ff, no always @(posedge …), no asynchronous-reset block; only continuous assignments and always_comb blocks. The task is combinational, so it never needs one. The rule closes a gap between Lean's model of SystemVerilog, which the proof is about, and real tools: the model runs every clocked block once per cycle whatever its clock does, so a design that registers correct outputs on a clock nothing drives would look right there, while a simulator sees a clock that never ticks and synthesis removes the registers.

Unclocked
/-- No process runs at a clock edge (no `always_ff`, no `always @(posedge …)`). -/
def Unclocked (d : Design2) : Prop :=
  ∀ p ∈ d.processes.toList, p.isEdgePhase = false
svgolf/tasks/clmul/Properties.lean:26Open in code
LOOP_FREENo combinational loop: the design lowers to continuous assigns (Design2.lowerComb) that are ranked (Acyclic): each reads only what lower-ranked ones write.

The design has no combinational loop: written as continuous assignments (each always_comb block becomes one), its processes can be ranked so that each reads only what lower-ranked ones write. A latch (an always_comb block that keeps a value on some path) is refused with it. Lean's model starts every signal at x and settles, so it would read a loop at one state, while a circuit powers up in any 0/1 state the loop can hold and keeps it; the rule removes that question instead of answering it. The ranking is of processes, not bits, so a process that reads a part of a signal it writes itself is refused too, even when no bit depends on itself: write such bits as separate assignments.

LoopFree
/-- No combinational loop: the design lowers to continuous assigns (`Design2.lowerComb`) that are
ranked (`Acyclic`): each reads only what lower-ranked ones write. -/
def LoopFree (d : Design2) : Prop :=
  ∃ d', d.lowerComb = some d' ∧ Acyclic d'
svgolf/tasks/clmul/Properties.lean:30Open in code
TERMINATESUnder every schedule and every stimulus, the run completes.

Whatever order the simulator picks for processes that are ready at the same moment (the schedule), and whatever values the inputs take, simulating the design completes: every cycle settles. A combinational loop that oscillates fails this, and so does a design the tools could not read. It is also what gives the next property its teeth: that one is about completed runs, and a design with no completed run would satisfy it without computing anything.

Terminates
/-- Under every schedule and every stimulus, the run completes. -/
def Terminates (d : Design2) : Prop :=
  ∀ (σ : ScheduleOracle) (stim : List SvState), ∃ tr, Runs2 d σ stim tr
svgolf/tasks/clmul/Properties.lean:35Open in code
COMPUTES_CARRYLESS_PRODUCTIn every completed run, every cycle whose stimulus drives a and b with known values ends with p holding the coefficients of their product as polynomials over GF(2).

In every cycle in which the inputs a and b are driven with known 0/1 values, the output p settles to their carry-less product: reading bit i of a vector as the coefficient of x^i, p is the product of the two polynomials over GF(2), so bit k of p is the XOR of a[i] AND b[j] over all i + j = k. It is a × b with every carry dropped, and its 2N − 1 bits hold the whole product (no reduction modulo a polynomial). The outputs must be fully known 0/1 values in every cycle of every run, whatever the earlier cycles were, so they cannot depend on history.

ComputesCarrylessProduct
/-- In every completed run, every cycle whose stimulus drives `a` and `b` with known values ends
with `p` holding the coefficients of their product as polynomials over GF(2). -/
def ComputesCarrylessProduct (N : Nat) (d : Design2) : Prop :=
  d ⊨₂ spec2 fun stim tr =>
    ∀ (c : Nat) (inputs st : SvState), stim[c]? = some inputs → tr[c]? = some st →
      ∀ (a b : BitVec N),
        HoldAll inputs [("a", N), ("b", N)] (a, b, ()) →
        HoldAll st [("p", 2 * N - 1)] (ofBits (2 * N - 1) (coeff N a b), ())
svgolf/tasks/clmul/Properties.lean:39Open in code

Definitions

The names the properties use, each with its Lean text. Trusted text is what a proof is about; a helper of Properties.lean is not trusted: Lean checks it as part of statement_iff_properties.

This task's trusted text

coeffSpec.leancoeff N a b k: coefficient k of the product polynomial, the XOR over all pairs of bit positions i, j below N with i + j = k of a[i] AND b[j].
coeff
/-- Coefficient `k` of the product polynomial: the XOR, over all pairs `(i, j)` of positions below `N`
with `i + j = k`, of `a_i AND b_j`. -/
def coeff (N : Nat) (a b : BitVec N) (k : Nat) : Bool :=
  (List.range N).foldl (fun acc i =>
    (List.range N).foldl (fun acc j => acc ^^ (decide (i + j = k) && a.getLsbD i && b.getLsbD j)) acc) false
svgolf/tasks/clmul/Spec.lean:48Open in code
ofBitsSpec.leanofBits n f: the n-bit vector whose bit k is f k.
ofBits
/-- The `n`-bit vector whose bit `k` (counted from the least significant end) is `f k`. -/
def ofBits : (n : Nat) → (Nat → Bool) → BitVec n
  | 0, _ => 0#0
  | n + 1, f => ofBits n (fun k => f (k + 1)) ++ BitVec.ofBool (f 0)
svgolf/tasks/clmul/Spec.lean:43Open in code
StatementSpec.leanThe trusted challenge statement that these properties restate, part by part.
Statement
/-- **The challenge statement.** A submission proves `SvGolf.Tasks.Clmul.Statement Submission.design.sem`. -/
abbrev Statement : (Nat → LeanModels.Sv.Design2) → Prop := task.Statement
svgolf/tasks/clmul/Spec.lean:61Open in code
taskSpec.leanThe task as data: the parameter range, the ports at each parameter value, and the function the outputs must compute.
task
/-- The task: inputs `a b : BitVec N`, output `p = a (x) b` on `2N-1` bits, for `1 ≤ N < 2^30`. -/
def task : CombTask where
  inRange N := 1 ≤ N ∧ N < 2 ^ 30
  inputs N := [("a", N), ("b", N)]
  outputs N := [("p", 2 * N - 1)]
  fn N := fun (a, b, ()) => (ofBits (2 * N - 1) (coeff N a b), ())
svgolf/tasks/clmul/Spec.lean:54Open in code

The shared trusted base

CombTask.SatisfiedAtTask.leanThe trusted form of a combinational statement at one parameter value: the interface, then no clock and termination, then the function.
SatisfiedAt
/-- The statement at one parameter value: interface; no clocked process, no
combinational loop and termination; function. Conjunct 2 is the triple
`Unclocked d ∧ LoopFree d ∧ Terminates2 d`. -/
def SatisfiedAt (N : Nat) (d : Design2) : Prop :=
  t.Interface N d ∧ (Unclocked d ∧ LoopFree d ∧ Terminates2 d) ∧ t.Computes N d
svgolf/SvGolf/Task.lean:262Open in code
inputPortsTask.leanThe design's input ports, each with its declared width, in declaration order.
inputPorts
/-- The input ports of `d` with their declared widths, in declaration order.
`instantiate2` lists the ports first, in port-list order. A declaration is
an input port iff its `isInput` flag is set. -/
def inputPorts (d : Design2) : List Sig :=
  d.decls.toList.filterMap fun dc => if dc.isInput then some (dc.name, dc.width) else none
svgolf/SvGolf/Task.lean:178Open in code
outputPortsTask.leanThe design's output ports, each with its declared width, in declaration order.
outputPorts
/-- The output ports of `d` with their declared widths, in declaration order.
A declaration is an output port iff its `isOutput` flag is set. -/
def outputPorts (d : Design2) : List Sig :=
  d.decls.toList.filterMap fun dc => if dc.isOutput then some (dc.name, dc.width) else none
svgolf/SvGolf/Task.lean:184Open in code
HoldAllTask.leanHoldAll st ports values: in the state st, every listed port holds its value as a known bit vector (no x, no z) of exactly the port's width.
HoldAll
/-- `HoldAll st sigs v`: in state `st`, every port `(n, w)` of `sigs` is bound
to the fully known `w`-bit vector that `v` assigns it. -/
def HoldAll (st : SvState) : (sigs : List Sig) → Vals (sigs.map Prod.snd) → Prop
  | [], _ => True
  | (n, _) :: sigs, v => SvState.lookup st n = some (LVec.ofBitVec v.1) ∧ HoldAll st sigs v.2
svgolf/SvGolf/Task.lean:197Open in code
⊨₂Spec2.leand ⊨₂ P: in every completed run of the design d, under every schedule and every stimulus, the stimulus and the trace satisfy P.
Models2
/-- `d ⊨₂ P` — for **every** legal schedule, every stimulus, and every
completed run (`Runs2`), the trace satisfies `P`. Introduce from a raw
∀-fuel fact with `Models2.of_run`; eliminate against a concrete run with
`Models2.run` / `Models2.runs`. Says nothing about whether runs complete —
pair it with `Terminates2`. -/
def Models2 (d : Design2) (P : TraceProp2) : Prop :=
  ∀ (σ : ScheduleOracle) (stim tr : List SvState), Runs2 d σ stim tr → P d stim tr

@[inherit_doc] scoped infix:50 " ⊨₂ " => Models2
LeanModels/Sv/Spec2.lean:141Open in code
spec2Spec2.leanTurns a property of a stimulus (one input entry per cycle) and a trace (one snapshot of every signal per cycle) into the form ⊨₂ takes.
spec2
/-- Lift a design-agnostic stimulus/trace predicate into `⊨₂`
(`d ⊨₂ spec2 fun stim tr => …`). -/
def spec2 (P : List SvState → List SvState → Prop) : TraceProp2 := fun _ stim tr => P stim tr
LeanModels/Sv/Spec2.lean:151Open in code
Runs2Spec2.leanRuns2 d σ stim tr: simulating d under the schedule σ on the stimulus stim completes, with the trace tr.
Runs2
/-- The fuel-abstracted Design2 run judgment: *some* fuel completes the run
of `d` under schedule `σ` and stimulus `stim` with trace `tr`. The only place
fuel appears in a Design2 spec. -/
def Runs2 (d : Design2) (σ : ScheduleOracle) (stim tr : List SvState) : Prop :=
  ∃ fuel, run2 d σ fuel stim = .ok tr
LeanModels/Sv/Spec2.lean:121Open in code
ScheduleOracleSemantics.leanA legal schedule: the order in which the simulator runs the processes that are ready at the same moment. The statement quantifies over all of them.
ScheduleOracle
/-- A legal schedule: at invocation `k`, orders the ready process list.
`choose_perm` is the legality proof — every invocation is a permutation of
the ready list — so `∀ σ : ScheduleOracle` ranges over exactly the legal
schedules. See the module docstring for the invocation-counter protocol. -/
structure ScheduleOracle where
  /-- Invocation counter and ready process indices in, execution order out. -/
  choose : Nat → List Nat → List Nat
  /-- Every invocation yields a permutation of the ready list. -/
  choose_perm : ∀ (k : Nat) (ready : List Nat), (choose k ready).Perm ready
LeanModels/Sv/Semantics.lean:151Open in code
Process2.isEdgePhaseSem2.leanTrue for the processes that run at the clock edge: always_ff, always @(posedge …) and asynchronous-reset blocks.
isEdgePhase
/-- Edge-phase processes: every posedge-clocked block, INCLUDING the
async-reset ones (the posedge fires with reset asserted too — the body's
reset branch is what holds the state, pin3 CYCLE 4). -/
def Process2.isEdgePhase : Process2 → Bool
  | .alwaysFF _ _ | .alwaysPlain _ _ | .alwaysFFR _ _ _ _ => true
  | _ => false
LeanModels/Sv/Sem2.lean:650Open in code

What does not count

Designs that try to score without meeting the statement (negatives), or that exploit a place where the proof's reading and the scored netlist could differ (attacks). The checker stops at the first stage that refuses; each design is listed with that stage.

Refused before any proof is tried

The checker's static stages read the text itself and refuse it: an operator the task bans, a construct that two tools read differently, an unknown (x or z) value that could arise, or a program the language forbids.

  • wide_out.sv (negative, stopped at extract): The output p is 2N bits wide instead of 2N - 1.

    the design, with its comments left out
    module clmul_n #(parameter int N = 16) (
      input  logic [N-1:0]   a, b,
      output logic [2*N-1:0] p
    );
      logic [N-1:0][N-1:0] r;
    
      assign p[2*N-1] = 1'b0;
      genvar i, j, m;
      generate
        for (j = 0; j < N; j++) begin : g_row0
          assign r[0][j] = a[0] & b[j];
        end
        for (i = 1; i < N; i++) begin : g_row
          for (j = 0; j < N-1; j++) begin : g_bit
            assign r[i][j] = r[i-1][j+1] ^ (a[i] & b[j]);
          end
          assign r[i][N-1] = a[i] & b[N-1];
        end
        for (i = 0; i < N; i++) begin : g_lo
          assign p[i] = r[i][0];
        end
        for (m = 0; m < N-1; m++) begin : g_hi
          assign p[N+m] = r[N-1][m+1];
        end
      endgenerate
    endmodule
    
  • div_zero_x.sv (attack, stopped at banlist): The right product, plus a run-time division whose divisor is zero on one input pair in 2^32: the proof sees an unknown value there and the synthesized circuit a different, defined one. Run-time / % ** are banned in every task.

    the design, with its comments left out
    module clmul_n #(parameter int N = 16) (
      input  logic [N-1:0]   a, b,
      output logic [2*N-2:0] p
    );
      logic [N-1:0][N-1:0] r;
      logic [2*N-2:0] pp;
      genvar i, j, m;
      generate
        for (j = 0; j < N; j++) begin : g_row0
          assign r[0][j] = a[0] & b[j];
        end
        for (i = 1; i < N; i++) begin : g_row
          for (j = 0; j < N-1; j++) begin : g_bit
            assign r[i][j] = r[i-1][j+1] ^ (a[i] & b[j]);
          end
          assign r[i][N-1] = a[i] & b[N-1];
        end
        for (i = 0; i < N; i++) begin : g_lo
          assign pp[i] = r[i][0];
        end
        for (m = 0; m < N-1; m++) begin : g_hi
          assign pp[N+m] = r[N-1][m+1];
        end
      endgenerate
      logic [2*N-1:0] wa;
      logic [3*N-1:0] wb;
      logic [N-1:0]   pa, pb;
      logic           rare;
      logic           q;
      assign wa   = {N{2'b10}};
      assign wb   = {N{3'b011}};
      assign pa   = wa[N-1:0];
      assign pb   = wb[N-1:0];
      assign rare = (&(~(a ^ pa))) & (&(~(b ^ pb)));
      assign q    = 1'b1 / ~rare;
      always_comb
        if (q) p = pp ^ {{(2*N-2){1'b0}}, rare};
        else      p = pp;
    endmodule
    
  • kronecker_mul.sv (negative, stopped at banlist): The right function through the integer multiplier on bit-spread operands, using the banned run-time *.

    the design, with its comments left out
    module clmul_n #(parameter int N = 16) (
      input  logic [N-1:0]   a, b,
      output logic [2*N-2:0] p
    );
      localparam int K = 8;
      logic [K*N-1:0]     sa, sb;
      logic [2*K*N-1:0]   sp;
      genvar i, k;
      generate
        for (i = 0; i < K*N; i++) begin : g_spread
          if (i % K == 0) begin : g_bit
            assign sa[i] = a[i/K];
            assign sb[i] = b[i/K];
          end else begin : g_zero
            assign sa[i] = 1'b0;
            assign sb[i] = 1'b0;
          end
        end
      endgenerate
      assign sp = sa * sb;
      generate
        for (k = 0; k < 2*N-1; k++) begin : g_parity
          assign p[k] = sp[K*k];
        end
      endgenerate
    endmodule
    
  • plus_xor.sv (negative, stopped at banlist): The right function, with each row's XOR written as a one-bit run-time +, which is banned.

    the design, with its comments left out
    module clmul_n #(parameter int N = 16) (
      input  logic [N-1:0]   a, b,
      output logic [2*N-2:0] p
    );
      logic [N-1:0][N-1:0] r;
    
      genvar i, j, m;
      generate
        for (j = 0; j < N; j++) begin : g_row0
          assign r[0][j] = a[0] & b[j];
        end
        for (i = 1; i < N; i++) begin : g_row
          for (j = 0; j < N-1; j++) begin : g_bit
            assign r[i][j] = r[i-1][j+1] + (a[i] & b[j]);
          end
          assign r[i][N-1] = a[i] & b[N-1];
        end
        for (i = 0; i < N; i++) begin : g_lo
          assign p[i] = r[i][0];
        end
        for (m = 0; m < N-1; m++) begin : g_hi
          assign p[N+m] = r[N-1][m+1];
        end
      endgenerate
    endmodule
    
Rejected at the proof

These pass every static check. The proof that comes with each one does not compile, or does not prove the statement: a design that is wrong somewhere in the task's range (or has other ports) has no proof at all, and a proof that leaves a goal unproved is never accepted.

  • drop_top.sv (negative, stopped at solution): Off by one: every row after the first drops its top term.

    the design, with its comments left out
    module clmul_n #(parameter int N = 16) (
      input  logic [N-1:0]   a, b,
      output logic [2*N-2:0] p
    );
      logic [N-1:0][N-1:0] r;
    
      genvar i, j, m;
      generate
        for (j = 0; j < N; j++) begin : g_row0
          assign r[0][j] = a[0] & b[j];
        end
        for (i = 1; i < N; i++) begin : g_row
          for (j = 0; j < N-1; j++) begin : g_bit
            assign r[i][j] = r[i-1][j+1] ^ (a[i] & b[j]);
          end
          assign r[i][N-1] = 1'b0;
        end
        for (i = 0; i < N; i++) begin : g_lo
          assign p[i] = r[i][0];
        end
        for (m = 0; m < N-1; m++) begin : g_hi
          assign p[N+m] = r[N-1][m+1];
        end
      endgenerate
    endmodule
    
  • only_n16.sv (negative, stopped at solution): Correct only at the scoring width N = 16, zero elsewhere.

    the design, with its comments left out
    module clmul_n #(parameter int N = 16) (
      input  logic [N-1:0]   a, b,
      output logic [2*N-2:0] p
    );
      logic [N-1:0][N-1:0] r;
    
      genvar i, j, m;
      generate
        if (N == 16) begin : g_scored
          for (j = 0; j < N; j++) begin : g_row0
            assign r[0][j] = a[0] & b[j];
          end
          for (i = 1; i < N; i++) begin : g_row
            for (j = 0; j < N-1; j++) begin : g_bit
              assign r[i][j] = r[i-1][j+1] ^ (a[i] & b[j]);
            end
            assign r[i][N-1] = a[i] & b[N-1];
          end
          for (i = 0; i < N; i++) begin : g_lo
            assign p[i] = r[i][0];
          end
          for (m = 0; m < N-1; m++) begin : g_hi
            assign p[N+m] = r[N-1][m+1];
          end
        end else begin : g_other
          assign p = '0;
        end
      endgenerate
    endmodule
    
  • or_accumulate.sv (negative, stopped at solution): Accumulates the rows with OR instead of XOR.

    the design, with its comments left out
    module clmul_n #(parameter int N = 16) (
      input  logic [N-1:0]   a, b,
      output logic [2*N-2:0] p
    );
      logic [N-1:0][N-1:0] r;
    
      genvar i, j, m;
      generate
        for (j = 0; j < N; j++) begin : g_row0
          assign r[0][j] = a[0] & b[j];
        end
        for (i = 1; i < N; i++) begin : g_row
          for (j = 0; j < N-1; j++) begin : g_bit
            assign r[i][j] = r[i-1][j+1] | (a[i] & b[j]);
          end
          assign r[i][N-1] = a[i] & b[N-1];
        end
        for (i = 0; i < N; i++) begin : g_lo
          assign p[i] = r[i][0];
        end
        for (m = 0; m < N-1; m++) begin : g_hi
          assign p[N+m] = r[N-1][m+1];
        end
      endgenerate
    endmodule
    
  • reversed_p.sv (negative, stopped at solution): Delivers the result bits in reverse order (coefficient k at p[2N-2-k]).

    the design, with its comments left out
    module clmul_n #(parameter int N = 16) (
      input  logic [N-1:0]   a, b,
      output logic [2*N-2:0] p
    );
      logic [N-1:0][N-1:0] r;
    
      genvar i, j, m;
      generate
        for (j = 0; j < N; j++) begin : g_row0
          assign r[0][j] = a[0] & b[j];
        end
        for (i = 1; i < N; i++) begin : g_row
          for (j = 0; j < N-1; j++) begin : g_bit
            assign r[i][j] = r[i-1][j+1] ^ (a[i] & b[j]);
          end
          assign r[i][N-1] = a[i] & b[N-1];
        end
        for (i = 0; i < N; i++) begin : g_lo
          assign p[2*N-2-i] = r[i][0];
        end
        for (m = 0; m < N-1; m++) begin : g_hi
          assign p[N-2-m] = r[N-1][m+1];
        end
      endgenerate
    endmodule
    
  • top_shift.sv (negative, stopped at solution): The top N - 1 output bits are read from the wrong position.

    the design, with its comments left out
    module clmul_n #(parameter int N = 16) (
      input  logic [N-1:0]   a, b,
      output logic [2*N-2:0] p
    );
      logic [N-1:0][N-1:0] r;
    
      genvar i, j, m;
      generate
        for (j = 0; j < N; j++) begin : g_row0
          assign r[0][j] = a[0] & b[j];
        end
        for (i = 1; i < N; i++) begin : g_row
          for (j = 0; j < N-1; j++) begin : g_bit
            assign r[i][j] = r[i-1][j+1] ^ (a[i] & b[j]);
          end
          assign r[i][N-1] = a[i] & b[N-1];
        end
        for (i = 0; i < N; i++) begin : g_lo
          assign p[i] = r[i][0];
        end
        for (m = 0; m < N-1; m++) begin : g_hi
          assign p[N+m] = r[N-1][m];
        end
      endgenerate
    endmodule
    
Spec.lean, verbatim (98 lines)
import SvGolf.Task

/-!
# Task `clmul`: N-bit carry-less multiplier, the product of polynomials over GF(2) (TRUSTED)

`mul` with the carries removed: AND for the partial products, XOR for the sums, no adder. Module (port
order as written; `N` is the module's only parameter):

```systemverilog
module clmul_n #(parameter int N = 16) (
  input  logic [N-1:0]   a, b,
  output logic [2*N-2:0] p
);
```

Statement: for every `N` with `1 ≤ N < 2^30` (so that the widths `2N-1` and `2N-2`, `int` expressions
of the module, stay below `2^31`, the values an `int` holds), the design has
exactly the ports `a`, `b` (inputs, `N` bits) and `p` (output, `2N-1` bits), no process is clocked, there
is no combinational loop, every run terminates, and in every cycle whose stimulus
drives `a` and `b` with known values the snapshot's `p` is the coefficient vector of the product of the
two polynomials over GF(2) that `a` and `b` encode (bit `i` of a vector is the coefficient of `x^i`):

    p_k = XOR over i + j = k, 0 ≤ i, j < N, of (a_i AND b_j),   for 0 ≤ k ≤ 2N-2.

The product of two polynomials of degree below `N` has degree at most `2N-2`, so `p` loses nothing (and
there is no constant-zero top bit). The same bits are the unsigned product `a * b` with every carry
dropped (`PCLMULQDQ`, the GHASH core, the polynomial multiplication of every binary-field and CRC/BCH
construction); this task is the UNREDUCED product, no modulus. The bit orientation is the usual
(little-endian) one, the opposite of GHASH's reflected convention.

The function is DEFINED by the double sum above, as `coeff` below, without natural-number subtraction or
any reliance on out-of-range bit reads. Sources used to cross-check (none trusted): `oracle.py`
(shift-and-XOR in Python), an implementation by Kronecker substitution (spread the bits `k = bit_length(N)
+ 1` apart, multiply as integers, read the parity of each group; `tests/checker/test_advisories.py`),
FIPS 197 Section 4.2's two products (`examples` below), and exhaustive agreement with the Lean spec
(`tests/clmul/validate/validate.py`).
-/

namespace SvGolf.Tasks.Clmul

open SvGolf

/-- The `n`-bit vector whose bit `k` (counted from the least significant end) is `f k`. -/
def ofBits : (n : Nat) → (Nat → Bool) → BitVec n
  | 0, _ => 0#0
  | n + 1, f => ofBits n (fun k => f (k + 1)) ++ BitVec.ofBool (f 0)

/-- Coefficient `k` of the product polynomial: the XOR, over all pairs `(i, j)` of positions below `N`
with `i + j = k`, of `a_i AND b_j`. -/
def coeff (N : Nat) (a b : BitVec N) (k : Nat) : Bool :=
  (List.range N).foldl (fun acc i =>
    (List.range N).foldl (fun acc j => acc ^^ (decide (i + j = k) && a.getLsbD i && b.getLsbD j)) acc) false

/-- The task: inputs `a b : BitVec N`, output `p = a (x) b` on `2N-1` bits, for `1 ≤ N < 2^30`. -/
def task : CombTask where
  inRange N := 1 ≤ N ∧ N < 2 ^ 30
  inputs N := [("a", N), ("b", N)]
  outputs N := [("p", 2 * N - 1)]
  fn N := fun (a, b, ()) => (ofBits (2 * N - 1) (coeff N a b), ())

/-- **The challenge statement.** A submission proves `SvGolf.Tasks.Clmul.Statement Submission.design.sem`. -/
abbrev Statement : (Nat → LeanModels.Sv.Design2) → Prop := task.Statement

/-! ## Sanity checks (closed; `decide +kernel` is kernel evaluation, no axiom) -/

/-- The scoring point `N = 16` (and the reported points 8 and 32) are in range. -/
example : task.inRange 16 ∧ task.inRange 8 ∧ task.inRange 32 := by simp [task]

/-- The range ends below `2^30`: the output's bound `2*N-2` (the port's `[2*N-2:0]`, evaluated as an
`int`) and the product `2*N` it is computed from stay below `2^31`, the values an `int` holds. -/
example : task.inRange (2 ^ 30 - 1) ∧ ¬ task.inRange (2 ^ 30) := by simp [task]

/-- At the top of the range `2*N` is still an `int`. -/
example : 2 * (2 ^ 30 - 1) < 2 ^ 31 := by decide

/-- The input names are distinct (the non-vacuity theorem `CombTask.SatisfiedAt.exists_run` needs it),
at every `N`. -/
example (N : Nat) : ((task.inputs N).map Prod.fst).Nodup := by simp [task]

/-- `(x + 1)^2 = x^2 + 1` over GF(2), at `N = 2`. -/
example : (task.fn 2 (0b11#2, 0b11#2, ())).1 = 0b101#3 := by decide

/-- `(x^2 + 1)(x + 1) = x^3 + x^2 + x + 1`, at `N = 3` (five output bits). -/
example : (task.fn 3 (0b101#3, 0b011#3, ())).1 = 0b01111#5 := by decide

/-- `N = 1` is the AND of two bits, on ONE output bit. -/
example : (task.fn 1 (1#1, 1#1, ())).1 = 1#1 ∧ (task.fn 1 (1#1, 0#1, ())).1 = 0#1 := by decide

/-- `(1 + x + ... + x^15)^2 = 1 + x^2 + ... + x^30` over GF(2), at `N = 16` (31 output bits). -/
example : (task.fn 16 (0xffff#16, 0xffff#16, ())).1 = 0x55555555#31 := by decide +kernel

/-- FIPS 197 Section 4.2 (2001 edition: `{57} * {83} = {c1}`; 2023 edition, Eq. (4.7): `{57} * {13} = {fe}`):
the unreduced products are `0x2b79` and `0x0589`; reduced modulo `x^8+x^4+x^3+x+1` (`0x11b`) they give
those bytes (`tests/clmul/validate/validate.py` does the reduction). -/
example : (task.fn 8 (0x57#8, 0x83#8, ())).1 = 0x2b79#15 := by decide +kernel
example : (task.fn 8 (0x57#8, 0x13#8, ())).1 = 0x0589#15 := by decide +kernel

end SvGolf.Tasks.Clmul

Its trusted text

The modules specific to this task. Like every task, it also rests on the shared trusted base (the ls-sv semantics of SystemVerilog and svGolf's statement helpers), and a proof may import any module of the ls-sv library. Nothing else is trusted: proof helpers are kernel-checked like any submission.

modulerolelinessha256
tasks.clmul.Specsvgolf/tasks/clmul/Spec.leantask statement987f8461a613cd

How to participate #

  1. Write a circuit with the task's exact interface.
  2. Prove in Lean 4 that it meets the trusted statement; Start a new design, below, gives the route, and the proving guide shows how.
  3. Check and record it (how to submit): the checker accepts the pair or says which stage refused it, as it does for each design under what does not count.
  4. Beat par: an accepted design whose score (area × time) is below that of the best proved reference. The leaderboard ranks by it.

How to submit

You hand in two files: solution.sv, a module with the exact header under Interface, and Solution.lean, a Lean 4 proof that it meets the trusted statement.

Step 0, once per machine. Install:

  • elan, which installs the Lean toolchain that lean-toolchain pins (leanprover/lean4:v4.33.0-rc1) on first use;
  • Yosys 0.53 with its yosys-abc (every pinned tool and file: Reproduce a score);
  • Verilator 5, a C++ compiler and make, for the post-synthesis check (it compiles Verilator's model of the netlist);
  • Python 3.12 with python3.12 -m pip install -r requirements.txt (pyslang==11.0.0, the extractor's SystemVerilog frontend).
step 0
git clone https://github.com/thomasnormal/svgolf
cd svgolf
python3.12 -m pip install -r requirements.txt
# the trusted Lean and the checker's evaluator
lake build
# the pinned sky130 liberty, checked by its hash
python3.12 score/ppa.py --fetch-liberty

lake build takes about 6 minutes and 1 GB of disk the first time, on a laptop that already had the Lean toolchain (README.md's Step 0 says what was measured where; elan's first download of the toolchain comes on top): Lake clones proglib (formerly lean-surfaces) and its packages from github.com, so it needs network access there, and builds the LeanModels.Sv modules svGolf uses; Mathlib is cloned, never built.

Start from the reference pair under Files, clmul_n, a complete and accepted one: copy it, from the same directory:

shell
cp tasks/clmul/reference/clmul_n.sv solution.sv
cp tasks/clmul/reference/clmul_n.lean Solution.lean

Then, for each design, from the same directory: find it without Lean and check it once without a proof (the golfing loop and step 1), write its proof (Start a new design, below, gives the route), then check the pair and record it (steps 2 and 3):

shell
# the golfing loop, no Lean: area, delay and the critical
#    path, about a second
python3.12 score/ppa.py solution.sv --task tasks/clmul \
    --no-mapped --path
# 1. no proof needed; never accepts; exits 0 when every
#    stage passed, ending with area x time and par
python3.12 check.py tasks/clmul solution.sv --unproved \
    --tactics --stage-dir stage
# 2. the real check: prints "accepted": true; its
#    post-synthesis check is LIGHT by default (a subset of
#    the vectors, for local feedback), so stderr also says
#    "accepted, but the check was INCOMPLETE ... rerun with
#    --postsynth full": expected, since step 3 runs the
#    full one itself
python3.12 check.py tasks/clmul solution.sv Solution.lean
# 3. record it: the whole check again, full and sandboxed
python3.12 leaderboard/leaderboard.py add --task clmul \
    --player YOUR_HANDLE --assisted-by "TOOL OR MODEL" \
    --sv solution.sv --lean Solution.lean \
    --notes "YOUR DESIGN IN ONE LINE"

Done: step 3 prints added clmul/HASH: YOUR_HANDLE, … once the pair has passed the whole check, full and sandboxed, and its record is written (what it writes, and where: below); otherwise it prints REFUSED with the reasons and records nothing. Step 2's "accepted": true, with its INCOMPLETE line, is feedback for you, not a record.

The golfing loop and step 1, the first two commands above. The second command runs every stage that needs no proof, the task's oracle included (a wrong circuit is named there, not deep in a proof), says whether the proof tactics read the design (tactics.in_fragment) and leaves it staged in stage/ for probe files (PROVING.md, section 0).

score/ppa.py is the inner loop of golfing; its ranked numbers are area_um2_sky130 and delay_ps_sky130 in what it prints. It runs only the source gate and the pinned flow: it does not check the banlist, the 4-state rules, whether ls-sv can read the design, the post-synthesis simulation or the task's function. Step 1 does, still without a proof.

The Lean file. The checker extracts your .sv itself and writes the trusted module Submission.Design; Goal unfolds to SvGolf.Tasks.Clmul.Statement Submission.design.sem, the task's statement of your design:

Submission.Design (generated: you never write it)
import LeanModels.Sv.Ingest2
import tasks.clmul.Spec

namespace Submission
load_design_sv2 design from "<the envelope of your .sv>"
abbrev Goal : Prop := SvGolf.Tasks.Clmul.Statement design.sem
end Submission

Your Solution.lean imports it and proves Goal, as the skeleton in Start a new design (The proof in detail) shows. File names and paths are free (here solution.sv and Solution.lean); the module name Submission, the import Submission.Design header and the theorem solution : Goal (in namespace Submission, theorem Submission.solution : Submission.Goal) are fixed.

Step 3 writes your record's three files (.json, .sv and .lean, named by the record's hash) to leaderboard/data/clmul/, the directory a pull request adds. Then open a pull request against https://github.com/thomasnormal/svgolf that adds them; CI re-verifies it. There is no submission server: nothing is sent anywhere by these commands. For a private trial, add --data-dir DIR: the record then goes to DIR and the board stays as it is. Repeat --assisted-by for each tool or AI system that helped, or leave it out.

YOUR_HANDLE is your public name: the board and the record's page show it. It is 1 to 32 characters, letters, digits, ., _ or -, the first a letter or a digit ([A-Za-z0-9][A-Za-z0-9._-]{0,31}). --notes is optional: one line on the design (at most 2000 characters), shown on the record's page. In a private trial (--data-dir DIR), add --from-check RESULT.json records the JSON that a check.py … --postsynth full --ppa require run printed instead of checking again; the board's own leaderboard/data/ refuses a stored result.

the sandbox, and machines without one

leaderboard.py add runs the check with --sandbox require: the proof's Lean, the extractor, Yosys and Verilator must run inside the operating system's sandbox (Seatbelt on macOS, bubblewrap on Linux). Where that sandbox is missing or fails its self-test, the check stops with an infrastructure error and nothing is recorded. On such a machine, --sandbox auto runs the check without it (and says so on stderr), and --sandbox off skips it: your proof file then runs with your own privileges, and the record is for your own use, since an official record is made with the sandbox or not at all. add also weighs the ranked sky130 numbers, which need the pinned liberty (Step 0). On a shared machine whose Lean calls go through a wrapper, set SVGOLF_LEAN_WRAP; check.py and leaderboard.py both read it, and SVGOLF_LEAN_WRAP_ONCE=1 takes the wrapper once for a whole run.

Leaderboard

by score · ASAP7

No submissions yet. Par, the bar to beat, is the proved reference clmul_n: 3821 µm²·ns (3429.5 µm² × 1.11 ns).

  1. ⚑
    par proved reference
    38213429.5 µm² × 1.11 ns

No submissions yet. Par, the bar to beat, is the proved reference clmul_n: 9.901 µm²·ns (50.257 µm² × 0.197 ns).

  1. ⚑
    par proved reference
    9.90150.257 µm² × 0.197 ns
score: area × time, µm²·nspar

At a glance

top module
clmul_n
parameters
N in range 1 <= N < 2^30
scoring point
N = 16
depth budget
none
banned run-time operators
+ - * / % **, and the run-time ordering comparisons < <= > >= (Yosys builds each as its own adder, an $alu; equality stays allowed)

The task is to build a carry-less product from XOR/AND networks, so the arithmetic that would build the integer product (or the Kronecker trick: spread the bits, multiply as integers, read the parities) is banned at run time: * and the + - that Yosys merges into the same $macc/$alu cells. Run-time / % ** are banned in every task (since 2026-11-06, under the 2026-11-05 ruling that svGolf disallows a construct where Yosys departs from the standard): a zero divisor or a negative exponent makes an x from known operands, which ls-sv carries into decisions and Yosys builds as a defined value (score/banlist.py ALWAYS_BANNED). Constant arithmetic (parameter expressions, genvar indices) stays allowed.

allowed axioms
propext, Classical.choice, Quot.sound
proof time limit
300 s of CPU time (a wall-clock backstop of 1500 s stops a proof that waits)

Start a new design

The route: the sv_comb tactics, then the XOR arithmetic of your rows (PROVING.md, section 1; reference/clmul_n.lean is a complete proof, 238 code lines):

  1. sv_structure (lexRank := key N): one driver per bit and no combinational loop (CombDesign ∧ Acyclic), ordered by your key, a lexicographic triple per site (clmulKey: the row first, the output bits last).
  2. sv_interface: the ports, one line.
  3. sv_equations: each assign as an equation over Bool and every driven bit known; the 2-D signal r is read as st.cell "r" N i j, and each field of your arithmetic lemma is one sv_bridge eq_… line.
  4. The arithmetic, in plain Lean: the equations give the Spec's product. The reference's is a structure of five fields (View), a row invariant by induction on the row (view_row: row i, column j is the XOR over t ≤ i of a t & b (i + j - t)) and the collapse of the Spec's double sum (view_spec).
The proof in detailLay the design out so the tactics take it · The skeleton · Library lemmas that exist today

Lay the design out so the tactics take it (what they refuse, with the messages: PROVING.md, section 4; the styles of real code and where each stops: "How people write clmul" below):

  • continuous assigns of & and ^ in at most two nested generate for loops that start at an integer literal, every index and loop bound affine in N and the genvars;
  • partial sums as the rows of a 2-D array, r[i][j]; sv_cover finds the assign that writes a read by solving one unknown per index equation, so a read whose writer needs a quotient and a remainder of the index is not covered;
  • every declared bit driven at N = 16;
  • no module instances: the tactics cannot summarize them yet, so a Karatsuba multiplier (the smaller design) is written flat, as one module; no procedural for loop (fourstate refuses it).

The skeleton (reference/clmul_n.lean is it, filled in for the array):

import Submission.Design
import tasks.clmul.Spec
import SvGolf.Comb
import LeanModels.Sv.CombEqTactic
import LeanModels.Sv.CombLib

namespace Submission
open LeanModels.Sv LeanModels.Sv.CombView LeanModels.Sv.CombLib LeanModels.Sv.CombEq SvGolf
open SvGolf.Tasks.Clmul

set_option maxHeartbeats 400000 in
theorem my_struct (N : Nat) (hN : 1 ≤ N) :
    CombDesign (design.sem N) ∧ Acyclic (design.sem N) ∧ SharedObl design.pdesign [N] := by
  sv_structure (lexRank := myKey N)           -- your key, a `def` above (clmul_n.lean's `clmulKey`)
  all_goals (intros; sv_rowmajor; omega)

theorem my_interface (N : Nat) (hN : 1 ≤ N) (hc : CombDesign (design.sem N)) :
    SvGolf.Tasks.Clmul.task.Interface N (design.sem N) := by
  sv_interface

-- your arithmetic: the rows' equations in, the product's coefficients out (clmul_n.lean's `ClmulArith`)

set_option maxHeartbeats 400000 in
theorem solution : Goal := by
  intro N hN
  have hN' : 1 ≤ N := hN.1
  obtain ⟨hc, ha, hso⟩ := my_struct N hN'
  refine CombTask.satisfiedAt_of_comb (my_interface N hN' hc) hc ha ?_
  intro st heqs hs v hv
  obtain ⟨x, y, ⟨⟩⟩ := v
  simp only [SvGolf.Tasks.Clmul.task, HoldAll_cons, HoldAll_nil, and_true] at hv ⊢
  obtain ⟨hA, hB⟩ := hv
  sv_equations
  · -- the main goal: your arithmetic lemma, one `sv_bridge eq_…` per field (clmul_n.lean)
  case inputsKnown_1 => exact inKnown_ofBitVec hA (by omega)
  case inputsKnown_2 => exact inKnown_ofBitVec hB (by omega)
  all_goals first | (intros; omega) | sv_cover

end Submission

Library lemmas that exist today (the tables: PROVING.md, section 6):

  • the XOR arithmetic is in the reference (a task ships no library: copy it): bx (the XOR of f 0, …, f (n-1)) with bx_congr, bx_trunc, bx_pair, foldl_xor; coeff_eq (the Spec's double sum as a nested bx), getLsbD_ofBits, view_row, view_spec;
  • LeanModels.Sv.CombLib: the bit-function value bfVal and its lemmas, rowMajor_inj, lexLt;
  • SvState.cell, sv_bridge and sv_rowmajor; rmN and Key (LeanModels/Sv/CombViewArith.lean) and LeanModels.Sv.Lib.RowMajor; the port bridges SvState.lookup_eq_ofBitVec_bv, SvState.boolAt_ofBitVec, inKnown_ofBitVec.

Compare the designs

ASAP7 · any two numbers · lower is better

N = 16, the scored point: the ranked numbers. The same designs are also measured at N = 4, N = 8 and N = 32: measured, not ranked, the same proved designs through the same pinned flow at other parameters (the tables below, and how the designs scale).

  • proved reference
  • baseline (not proved)
  • the Pareto front of the proved designs
Lower is better on both axes. The step line joins the proved designs that no other proved design beats on both; baselines are shown for scale. Choose any two numbers to find the best design for your own trade-off. 6 of 6 designs have both numbers.
every design with all its numbers, by score (at N = 16, ranked)
designScore (µm²·ns)Area (µm²)Delay (ps)Energy (fJ/op)Leakage (nW)Area (GE)Depth (gates)GatesProof (lines)
clmul_n par38213429.5111448311772.2676481238
kara_b4_16 not proved–––––621.9598371–
kara_b8_16 not proved–––––643.2837393–
kara_b2_16 not proved–––––672.63510390–
kara_full_16 not proved–––––677.6389389–
school_tree_16 not proved–––––770.9355481–
at N = 4 (measured, not ranked): every design with all its numbers, by score
designScore (µm²·ns)Area (µm²)Delay (ps)Area (GE)Depth (gates)GatesProof (lines)
clmul_n reference110.6171.464538.329325238
at N = 8 (measured, not ranked): every design with all its numbers, by score
designScore (µm²·ns)Area (µm²)Delay (ps)Area (GE)Depth (gates)GatesProof (lines)
school_tree_8 not proved645.6802.0805179.6494113–
clmul_n reference645.6802.0805179.6494113238
kara_b4_8 not proved825.6744.51109166.9865101–
at N = 32 (measured, not ranked): every design with all its numbers, by score
designScore (µm²·ns)Area (µm²)Delay (ps)Area (GE)Depth (gates)GatesProof (lines)
school_tree_32 not proved16 36214 315.011433219.39361985–
clmul_n reference18 29314 279.912813214.73171985238
kara_b4_32 not proved23 5029162.525652068.877111203–
  • proved reference
  • baseline (not proved)
  • the Pareto front of the proved designs
Lower is better on both axes. The step line joins the proved designs that no other proved design beats on both; baselines are shown for scale. Choose any two numbers to find the best design for your own trade-off. 6 of 6 designs have both numbers.
every design with all its numbers, by score
designScore (µm²·ns)Area (µm²)Delay (ps)Area (GE)Depth (gates)GatesProof (lines)
school_tree_16 not proved8.57450.141171770.9355481–
clmul_n par9.90150.257197772.2676481238
kara_b8_16 not proved10.5241.903251643.2837393–
kara_b4_16 not proved12.2740.620302621.9598371–
kara_b2_16 not proved14.643.988332672.63510390–
kara_full_16 not proved14.9644.250338677.6389389–

How the designs scale #

On sky130 in either view: the designs are measured at the other points on sky130 alone (score/measured/), and the ASAP7 view weighs each design at the scoring point only.

clmul: score over par against N, one line per design3 designs, Score ÷ par's (y) against N (x), one line each, named on the right; lower is better. The table under the chart lists the same numbers.0.911.11.21.3481632N, log scaleScore ÷ par's · lower is betterpar: clmul_nkara_b4 · baseline, not proved · measured, not ranked · 3 sizes of one generator: kara_b4_16, kara_b4_32, kara_b4_8kara_b4 · N = 8 · measured, not ranked · Score: 825.6 µm²·ns (×1.28 par)kara_b4 · N = 16 · the scored point · Score: 4981 µm²·ns (×1.30 par)kara_b4 · N = 32 · measured, not ranked · Score: 23 502 µm²·ns (×1.28 par)kara_b4school_tree · baseline, not proved · measured, not ranked · 3 sizes of one generator: school_tree_16, school_tree_32, school_tree_8school_tree · N = 8 · measured, not ranked · Score: 645.6 µm²·ns (×1.00 par)school_tree · N = 16 · the scored point · Score: 3314 µm²·ns (×0.87 par)school_tree · N = 32 · measured, not ranked · Score: 16 362 µm²·ns (×0.89 par)school_treeclmul_n · proved reference · measured, not rankedclmul_n · N = 4 · measured, not ranked · Score: 110.6 µm²·ns (×1.00 par)clmul_n · N = 8 · measured, not ranked · Score: 645.6 µm²·ns (×1.00 par)clmul_n · N = 16 · the scored point · Score: 3821 µm²·ns (×1.00 par)clmul_n · N = 32 · measured, not ranked · Score: 18 293 µm²·ns (×1.00 par)clmul_n
clmul: score over par against N, one line per design3 designs, Score ÷ par's (y) against N (x), one line each, named on the right; lower is better. The table under the chart lists the same numbers.11.2481632N, log scaleScore ÷ par's · lower is betterpar: clmul_nkara_b4 · baseline, not proved · measured, not ranked · 3 sizes of one generator: kara_b4_16, kara_b4_32, kara_b4_8kara_b4 · N = 8 · measured, not ranked · Score: 825.6 µm²·ns (×1.28 par)kara_b4 · N = 16 · the scored point · Score: 4981 µm²·ns (×1.30 par)kara_b4 · N = 32 · measured, not ranked · Score: 23 502 µm²·ns (×1.28 par)kara_b4school_tree · baseline, not proved · measured, not ranked · 3 sizes of one generator: school_tree_16, school_tree_32, school_tree_8school_tree · N = 8 · measured, not ranked · Score: 645.6 µm²·ns (×1.00 par)school_tree · N = 16 · the scored point · Score: 3314 µm²·ns (×0.87 par)school_tree · N = 32 · measured, not ranked · Score: 16 362 µm²·ns (×0.89 par)school_treeclmul_n · proved reference · measured, not rankedclmul_n · N = 4 · measured, not ranked · Score: 110.6 µm²·ns (×1.00 par)clmul_n · N = 8 · measured, not ranked · Score: 645.6 µm²·ns (×1.00 par)clmul_n · N = 16 · the scored point · Score: 3821 µm²·ns (×1.00 par)clmul_n · N = 32 · measured, not ranked · Score: 18 293 µm²·ns (×1.00 par)clmul_n
  • proved reference
  • baseline (not proved)
  • par
Score of each design over par's (clmul_n) at the same point, against N: 1 is par, and a line under it beats par at that size, so where two lines cross one design starts to pay off against the other. One line per design, lower is better: the same proved design at each point, through the same pinned flow. Measured, not ranked: the board ranks at N = 16 only. A dashed line is a baseline (not proved). kara_b4 and school_tree are families of fixed-size baselines, one file per size, each drawn as one line. Measured at one point only, so not drawn: kara_b2_16, kara_b8_16 and kara_full_16 (in the table).
every design at every point: Score (µm²·ns)
designN = 4N = 8N = 16 (ranked)N = 32
clmul_n110.6645.6382118 293
kara_b2_16––6083–
kara_b4 kara_b4_16, kara_b4_32, kara_b4_8–825.6498123 502
kara_b8_16––3916–
kara_full_16––6223–
school_tree school_tree_16, school_tree_32, school_tree_8–645.6331416 362

Proved references

Proved and accepted designs that ship with the task, written or adapted by the maintainers. They are the first entries, and par, the bar to beat, is the one with the best score (area × time, µm²·ns, from the sky130ASAP7 area and time; a design without a sky130an ASAP7 weighing has no score and comes after, by its extra GE columns). A reference that was also recorded links its record under its name: the record holds that reference's design and proof, matched by the sha256 of both files.

designscore (µm²·ns)area (µm²)time (ps)GE · depthvs parproof linesfiles
clmul_n parparThe N × N array: AND partial products summed by rows of XORs, the multiplier's shape without carries.tactics: decide, induction, omega, simp, sv_cover, sv_equations, sv_interface, sv_rowmajor, sv_structurerecord: bfb3c58778b638219.9013429.550.2571114197772.267 · 6parpar238.sv .lean

Baselines

Not entries. Designs scored by the pinned flow (with the banlist lifted) but never accepted: unproved, simulation-only, or using an operator the task bans. They show how much room there is.

designscore (µm²·ns)area (µm²)time (ps)GE · depthvs parfile
kara_b4_16 Karatsuba recursion down to a 4-bit schoolbook base, generated for N = 16 only and checked by simulation: the smallest design on this page.–12.27–40.620–302621.959 · 8–+23.9%.sv
kara_b8_16 Karatsuba recursion down to an 8-bit schoolbook base, generated for N = 16 only and checked by simulation.–10.52–41.903–251643.283 · 7–+6.2%.sv
kara_b2_16 Karatsuba recursion down to a 2-bit schoolbook base, generated for N = 16 only and checked by simulation.–14.6–43.988–332672.635 · 10–+47.5%.sv
kara_full_16 Karatsuba recursion all the way down to single bits, generated for N = 16 only and checked by simulation.–14.96–44.250–338677.638 · 9–+51.1%.sv
school_tree_16 Schoolbook: all N² partial products and a balanced XOR tree per output bit, generated for N = 16 only and checked by simulation.–8.574–50.141–171770.935 · 5–−13.4%.sv
kara_b4_32 Karatsuba recursion down to a 4-bit schoolbook base, generated for N = 32 only and checked by simulation.––––––scored at N=32 only––.sv
kara_b4_8 Karatsuba recursion down to a 4-bit schoolbook base, generated for N = 8 only and checked by simulation.––––––scored at N=8 only––.sv
school_tree_32 Schoolbook: all N² partial products and a balanced XOR tree per output bit, generated for N = 32 only and checked by simulation.––––––scored at N=32 only––.sv
school_tree_8 Schoolbook: all N² partial products and a balanced XOR tree per output bit, generated for N = 8 only and checked by simulation.––––––scored at N=8 only––.sv