Prove a circuit correct, then golf its gates.
Each task fixes what a circuit must do in a trusted Lean statement: a function, or on a clocked task a property of every cycle. You write the circuit in SystemVerilog, prove in Lean that it meets the statement for every width the task allows, and the checker scores the circuit by area × time (on a clocked task, the time is the clock period, times the latency in cycles where the task has one). Lower is better.
The course
Tasks
sky130: SkyWater's open 130 nm process, the default.ASAP7: a predictive 7 nm library, never fabricated. How it is scored
- 00parametric ·
_exampleBitwise AND
A toy task that runs the whole pipeline: y = a & b on N bits.
- Par
- 28.03µm²·ns
- Record
- 28.03µm²·ns
- Under par
- 0%
- Par
- 0.0784µm²·ns
- Record
- 0.0784µm²·ns
- Under par
- 0%
- 01parametric ·
addN-bit adder
Add two N-bit numbers and a carry in, with logic gates only.
- Par
- 1202µm²·ns
- Record
- 1176µm²·ns
- Under par
- −2%
- Par
- 3.94µm²·ns
- Record
- 3.94µm²·ns
- Under par
- 0%
- 02parametric ·
mulN-bit multiplier
The full 2N-bit product of two unsigned N-bit numbers, with logic gates only.
- Par
- 51 311µm²·ns
- Record
- 42 530µm²·ns
- Under par
- −17%
- Par
- 148.4µm²·ns
- Record
- 131.5µm²·ns
- Under par
- −11%
- 03parametric ·
clmulCarry-less multiplier
Multiply two polynomials over GF(2): ANDs and XORs, no carries.
- Par
- 3821µm²·ns
- Record
- 3821µm²·ns
- Under par
- 0%
- Par
- 9.901µm²·ns
- Record
- 9.901µm²·ns
- Under par
- 0%
- 04fixed size ·
aes_sboxAES S-box
The byte substitution of AES (FIPS 197): 8 bits in, 8 bits out.
- Par
- 3833µm²·ns
- Record
- 2858µm²·ns
- Under par
- −25%
- Par
- 9.402µm²·ns
- Record
- 7.458µm²·ns
- Under par
- −21%
- 05parametric ·
fdivpIEEE 754 division, every format
One divider for every exponent width and precision, rounded to nearest even.
- Par
- 340 950µm²·ns
- Record
- 340 950µm²·ns
- Under par
- 0%
- Par
- 974.2µm²·ns
- Record
- 974.2µm²·ns
- Under par
- 0%
- 06clocked, reactive ·
arbiterFair N-client arbiter, clocked and reactive
At most one grant per cycle, only to a requester, someone whenever anyone requests, and a requester that asks N cycles in a row granted within them.
- Par
- 2175µm²·ns
- Record
- 2175µm²·ns
- Under par
- 0%
- Par
- 5.642µm²·ns
- Record
- 5.642µm²·ns
- Under par
- 0%
- 07clocked, transaction ·
matmulN × N matrix multiplication, clocked
Multiply two signed N × N matrices, wrapping at AccW bits, behind a start/done handshake.
- Par
- 3 729 636µm²·ns
- Record
- 2 276 474µm²·ns
- Under par
- −39%
- Par
- 8217µm²·ns
- Record
- 5935µm²·ns
- Under par
- −28%
- 08clocked, stream ·
sorterStreaming sorter, clocked
N unsigned W-bit values in, one per cycle; the same values out in ascending order, one per cycle.
- Par
- 222 540µm²·ns
- Record
- 157 015µm²·ns
- Under par
- −29%
- Par
- 701.9µm²·ns
- Record
- 538.9µm²·ns
- Under par
- −23%
Par is the best proved reference that ships with the task: a design the maintainers wrote or adapted and proved, the bar to beat, never an entry. Record is the best accepted design so far; with no submissions it is par. Both are the one ranked number, area × time on the sky130 cell library (µm²·ns), lower is better; “–” marks a task whose designs are not weighed on sky130 yet. Under par is how far the record is below par, as a share of par's score (0% while par is the record).Par is the best proved reference that ships with the task: a design the maintainers wrote or adapted and proved, the bar to beat, never an entry. Record is the best accepted design so far; with no submissions it is par. In this view both are area × time, the number it orders by, on the ASAP7 cell library (µm²·ns), lower is better; “–” marks a task whose designs are not weighed on ASAP7 yet. Under par is how far the record is below par, as a share of par's score (0% while par is the record). ASAP7: a predictive 7 nm library, never fabricated; no wires, like sky130's score (how ASAP7 is scored, and its quirks).
How it works, on the adder #
Write
solution.sv, with the task's module header. The adder bans the arithmetic operators, so the sum is built from gates. The adder's ripple-carry reference,tasks/add/reference/add_n.sv: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]; endmoduleProve
Solution.lean: one theorem. The checker turns your.svinto the Lean constantdesign, the circuit as ls-sv, the formal semantics of SystemVerilog in Lean, runs it.Goalis the task's trusted statement about it (trusted: you read it, and Lean's kernel checks your proof against it): for everyNwith1 ≤ N < 2^31, whenever the inputs are known,{cout, s} = a + b + cin.import Submission.Design -- written by the checker: `design`, `Goal` namespace Submission theorem solution : Goal := by -- your proof (add_n.lean: a complete one) end SubmissionCheck both files with one command. It accepts the pair or names the stage that refused it. Its last line is the score: the pinned Yosys and ABC synthesize your circuit, each gate becomes a sky130 cell, and the area in µm² times the critical-path delay in ns is the one ranked number. Par is the best proved reference; beat it.
$ python3.12 check.py tasks/add solution.sv Solution.lean ... "accepted": true, ... ranked (sky130 A0'): 460.4416 um^2 x 2611 ps = 1202.21 um2*ns; par 1202.21 (add_n)
You submit two files, solution.sv and Solution.lean (any names). Checking and recording them run
on your own machine and upload nothing. Golf without Lean:
python3.12 score/ppa.py solution.sv --task tasks/add --no-mapped prints the same area and delay in a
second or two, and the same file always scores the same (reproduce a score).
Install once: Step 0.
How a design becomes Lean #
You never write the Lean model of your circuit. The checker reads your .sv through a source gate that
makes one canonical text for every tool, and the pinned extractor of proglib (formerly lean-surfaces), built on the slang
SystemVerilog frontend, turns that text into an envelope: the elaborated design, cross-checked at several
parameter values. The checker then writes the trusted module Submission.Design, whose
load_design_sv2 design from "<envelope>" makes the Lean constant design: design.sem N is your circuit
at parameter N as ls-sv runs it, and Goal is the task's statement of design.sem. Your
Solution.lean imports that module and proves theorem solution : Goal in namespace Submission. The
proving guide shows the loop: stage the design, print its equations, prove.
Each task page's "Start a new design" says which tactics prove a new circuit of that task, how to lay the
circuit out so that they take it, and which library lemmas exist.
The proof and the score #
The proof is about the design as ls-sv reads it, the score about the netlist Yosys builds from the same text. Before it accepts, the checker simulates the scored netlist against the proved design on a sample of input vectors and rejects any disagreement: fixed patterns and the task's corner values, inputs built from the constants the design compares against, and seeded random vectors, salted in competition runs so that nobody can predict them (every input where the input space is small, as for the AES S-box's 256). On a clocked task both run long seeded input traces from several power-up states, and the checker also holds the netlist's outputs to the task's property on every cycle. That is sampling, not a proof: a guard against the two tools disagreeing, behind the static rules that refuse every known disagreement before any proof is tried. Read the rules and the proving guide, or start with the tutorial task.