This task
Take N unsigned W-bit values, one per cycle, and give them back in ascending order, one per cycle,
duplicates kept, behind a stream handshake: start, then in_valid and in_data in, out_valid and out_data
out. out_data is claimed only at the cycles where out_valid = 1, which show the sorted values in order;
out_valid is known at every cycle from the release and 0 until the first start. The proof covers every N, W ≥ 1 with N·W < 2^31 and proves
the latency the submission declares; the score is taken at N = 8, W = 8.
The design is clocked by clk alone, on its rising edge, with the synchronous reset rst: no asynchronous
process, and clk is never read as data. No output may depend combinationally on an input; the claims hold from every power-up value of every register,
the first reset included; no construct may create an unknown value, and a register that is never loaded is
refused. No operator is banned but run-time / % **, as in every task: comparisons are the task. Any schedule is
allowed, and the score prices it: an insertion array, a sorting network behind serial-parallel buffers,
a merge structure. No design can finish before 2N - 1 cycles, because the smallest value
depends on the last input; the proved reference, an insertion-cell array, takes 2N (16 cycles at the
scoring point) for 1288.819 GE.
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.
module sorter #(parameter int N = 8, parameter int W = 8) (
input logic clk, rst,
input logic start, in_valid,
input logic [W-1:0] in_data,
output logic out_valid,
output logic [W-1:0] out_data
);| port | direction | width (bits) |
|---|---|---|
clk | input | 1 |
rst | input | 1 |
start | input | 1 |
in_valid | input | 1 |
in_data | input | W |
out_valid | output | 1 |
out_data | output | W |
The trusted statement #
A proof must establish the task's trusted statement, the constant SvGolf.Tasks.Sorter.Statement of Spec.lean (module tasks.sorter.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 and its declared latency meet the trusted statement exactly when, for every (N, W) in range, they have all of the properties below. The last four are guarantees the design owes an environment that follows the protocol: after a reset is released, start is raised only while the design is idle, and in_valid is 1, with one input value on in_data, in exactly the N cycles after each start. Lean checks that, together, the properties 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 parameters the statement covers: N, W ≥ 1 and N·W below 2^31.
The statement covers every count N ≥ 1 of values of every width W ≥ 1, as long as N·W stays below 2^31: the parameters are 32-bit ints, and a design that buffers the whole stream declares N·W bits. One proof covers all of them; the design is scored at N = 8, W = 8.
/-- The parameters the statement covers: `N, W ≥ 1` and `N·W` below `2^31`. -/
def ParamsInRange (N W : Nat) : Prop :=
1 ≤ N ∧ 1 ≤ W ∧ N * W < 2 ^ 31INTERFACEExactly these ports, in this order, with these widths; no two declarations share a name.
The module has exactly the input ports clk, rst, start, in_valid (1 bit each) and in_data (W bits) and exactly the output ports out_valid (1 bit) and out_data (W 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. No two declarations of the module (ports or internal signals) share a name, so every port has exactly one declared width.
/-- Exactly these ports, in this order, with these widths; no two declarations share a name. -/
def Interface (W : Nat) (d : Design2) : Prop :=
inputPorts d = [("clk", 1), ("rst", 1), ("start", 1), ("in_valid", 1), ("in_data", W)] ∧
outputPorts d = [("out_valid", 1), ("out_data", W)] ∧ (d.decls.toList.map Decl.name).NodupCLOCK_DISCIPLINEclk is an input; every process that runs at a clock edge is clocked by clk; no process has an asynchronous reset (rst is synchronous); no process reads clk as data.
The clock is the input clk: every register is clocked by it, no register has an asynchronous reset (rst is synchronous, read inside the clocked processes), and no logic reads clk as a data signal. Together these make one cycle of Lean's model exactly one rising edge of clk in the real circuit: a clock that is gated, derived from logic, tied off or never driven is ruled out. One consequence to know: the registers have to sit in the top module, because connecting clk to a child instance copies it as data.
/-- `clk` is an input; every process that runs at a clock edge is clocked by `clk`; no process
has an asynchronous reset (`rst` is synchronous); no process reads `clk` as data. -/
def ClockDiscipline (d : Design2) : Prop :=
"clk" ∈ d.inputNames.toList ∧
∀ p ∈ d.processes.toList,
(p.isEdgePhase = true → edgeClock p = some "clk") ∧
asyncReset p = none ∧
"clk" ∉ processDataNames pTERMINATESFrom every power-up state, under every schedule and every known stimulus, the run completes.
From every power-up state (every register and variable, 2-state ones included, holding some 0 or 1), whatever order the simulator picks for processes that are ready at the same moment (the schedule), and whatever known values the environment drives on the inputs, simulating the design completes: every clock 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 guarantees below their teeth: they are about completed runs, and a design with no completed run would satisfy them without doing anything.
/-- From every power-up state, under every schedule and every known stimulus, the run completes. -/
def Terminates (d : Design2) : Prop :=
∀ st0, PowerUp d st0 → ∀ (σ : ScheduleOracle) (stim : List SvState), KnownStim d stim →
∃ tr, RunsAt d σ st0 stim trLATENCY_AT_LEAST_ONEThe declared latency is at least one cycle.
Besides the design, a submission declares its latency: the number of cycles it may take to answer a start, as a function of the parameters. It must be at least one cycle. The checker pins its value at the scoring point, and the score uses it (time = latency × clock period), so declaring a larger latency than needed only costs score.
/-- The declared latency is at least one cycle. -/
def LatencyAtLeastOne (lat : Nat) : Prop :=
1 ≤ latMOORE_OUTPUTSNo combinational path leads from an input port, rst included, to an output port (MooreOutDep with nothing exempt, txtask-v2 rule 1): out_valid and out_data are functions of the registers, so every snapshot is what the wires show for the whole next period.
No combinational path leads from any input, rst included, to out_valid and out_data: the outputs are functions of the registers only. The proof reads the outputs in the snapshot just after a clock edge, with that cycle's inputs still applied; on the wire the inputs change before the next edge. Without this rule an output that reads an input directly would be proved on one value and seen on another; an output gated by rst could hide a transaction's last value from a consumer when a reset follows (txtask-v2 rule 1).
/-- No combinational path leads from an input port, `rst` included, to an output port
(`MooreOutDep` with nothing exempt, txtask-v2 rule 1): `out_valid` and `out_data` are functions of
the registers, so every snapshot is what the wires show for the whole next period. -/
def MooreOutputs (d : Design2) : Prop :=
MooreOutDep d []NO_X_CREATINGNo construct of the design can create an x or z from known operands (XSafe, txtask-v2 rule 3): with the power-up rule, every value of every run is known.
No construct of the design can make an unknown value out of known ones: no run-time / or % except by a nonzero constant, no ** that could be x, no run-time index that can leave its range, no slice beyond its operand, no bitwise or equality operator on operands of different widths, no x or z literal outside a casez/casex pattern. Together with the power-up rule, every value the design computes in Lean's model is then a known 0 or 1, so every if, case and ?: decides on a known value, where the hardware decides too (txtask-v2 rule 3).
/-- No construct of the design can create an `x` or `z` from known operands (`XSafe`, txtask-v2
rule 3): with the power-up rule, every value of every run is known. -/
def NoXCreating (d : Design2) : Prop :=
XSafe dOUT_VALID_KNOWNout_valid is a known bit at every cycle from the release.
As long as the environment follows the protocol, out_valid is a known bit, 0 or 1, in every cycle from the release of the reset on; never x.
/-- `out_valid` is a known bit at every cycle from the release. -/
def OutValidKnown (N W : Nat) (d : Design2) : Prop :=
UnderProtocol N W d fun _ tr c1 h =>
∀ c, c1 ≤ c → c < h → ∃ b, TraceBit tr c "out_valid" bSILENT_BEFORE_FIRST_STARTout_valid is 0 at every cycle from the release up to the first start.
As long as the environment follows the protocol, out_valid is 0 in every cycle after the release of the reset and before the first start: nothing comes out before there is anything to sort, and nothing left over from before the reset comes out.
/-- `out_valid` is 0 at every cycle from the release up to the first start. -/
def SilentBeforeFirstStart (N W : Nat) (d : Design2) : Prop :=
UnderProtocol N W d fun stim tr c1 h =>
∀ c, c1 ≤ c → c < h → (∀ s, c1 ≤ s → s ≤ c → ¬ StimBit stim s "start" true) →
TraceBit tr c "out_valid" falseEMITS_SORTEDFrom each start to the next, the values shown on out_data while out_valid is 1 are, in order, the transaction's input values sorted ascending.
As long as the environment follows the protocol, for every start whose latency window has passed, the values on out_data in the cycles where out_valid is 1, from that start up to the next one, are, in order, exactly the N input values of the transaction sorted ascending as unsigned numbers, duplicates kept. So exactly N values come out; gaps between them are allowed, and out_data may hold anything while out_valid is 0.
/-- From each start to the next, the values shown on `out_data` while `out_valid` is 1 are, in
order, the transaction's input values sorted ascending. -/
def EmitsSorted (N W lat : Nat) (d : Design2) : Prop :=
UnderProtocol N W d fun stim tr c1 h =>
∀ s xs e, s + lat < h → Transaction N W stim c1 h s xs e →
∃ obs : List (Bool × BitVec W), obs.length = e - s ∧ task.Shows (N, W) tr s obs ∧
task.emitted (N, W) obs = sortAsc xsSILENT_AFTER_LATENCYFrom each start to the next, out_valid is 0 at every cycle more than lat cycles after the start.
As long as the environment follows the protocol, after every start, out_valid is 0 in every cycle more than lat cycles later, up to the next start (the cycle in which start is sampled is cycle 0). With EMITS_SORTED, this says that all N values come out within the declared latency.
/-- From each start to the next, `out_valid` is 0 at every cycle more than `lat` cycles after
the start. -/
def SilentAfterLatency (N W lat : Nat) (d : Design2) : Prop :=
UnderProtocol N W d fun stim tr c1 h =>
∀ s xs e, s + lat < h → Transaction N W stim c1 h s xs e →
∀ c, s + lat < c → c < e → TraceBit tr c "out_valid" falseDefinitions
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
sortAscSpec.leanInsertion sort, ascending as unsigned numbers, duplicates kept: the function of a transaction.
/-- Insertion sort, ascending, unsigned: `List.insertionSort (· ≤ ·)` (C8), written out here
because the trusted text imports no Mathlib (structural recursion: the kernel evaluates it). -/
def sortAsc {W : Nat} : List (BitVec W) → List (BitVec W)
| [] => []
| a :: as => insertAsc a (sortAsc as)insertAscSpec.leanInserts a value into an ascending list, before the first element it is at most.
/-- Insert `a` into an ascending list (unsigned order), before the first element it is `≤` to. -/
def insertAsc {W : Nat} (a : BitVec W) : List (BitVec W) → List (BitVec W)
| [] => [a]
| b :: bs => if a.toNat ≤ b.toNat then a :: b :: bs else b :: insertAsc a bsStatementSpec.leanThe trusted challenge statement that these properties restate, part by part.
/-- **The challenge statement**, for the two-parameter design family `sem N W` and the latency
function `lat N W`. -/
abbrev Statement (sem : Nat → Nat → LeanModels.Sv.Design2) (lat : Nat → Nat → Nat) : Prop :=
task.StatementV2 (fun p => sem p.1 p.2) (fun p => lat p.1 p.2)taskSpec.leanThe task as data: the parameter range, the port names and widths, and the function of a transaction.
/-- **The task** (C8). -/
def task : StreamTask Params where
inRange p := 1 ≤ p.1 ∧ 1 ≤ p.2 ∧ p.1 * p.2 < 2 ^ 31
clk := "clk"
rst := "rst"
start := "start"
inValid := "in_valid"
inData := "in_data"
outValid := "out_valid"
outData := "out_data"
width p := p.2
count p := p.1
spec _ xs := sortAsc xsHelpers in Properties.lean (proved, not trusted)
TransactionProperties.leanTransaction N W stim c1 h s xs e: s is a start after the release c1, xs are the N values on in_data in the cycles after it, and e is the next start, or the horizon h if there is none before it.
/-- `Transaction N W stim c1 h s xs e`: `s` is a start after the release `c1`, `xs` are the `N`
values on `in_data` in the cycles after it, and `e` is the next start, or the horizon `h` if
there is none before it. -/
def Transaction (N W : Nat) (stim : List SvState) (c1 h s : Nat) (xs : List (BitVec W))
(e : Nat) : Prop :=
c1 ≤ s ∧ StimBit stim s "start" true ∧ xs.length = N ∧
(∀ j v, xs[j]? = some v → ∃ en, stim[s + 1 + j]? = some en ∧
SvState.lookup en "in_data" = some (LVec.ofBitVec v)) ∧
s < e ∧ e ≤ h ∧ (∀ s', s < s' → s' < e → ¬ StimBit stim s' "start" true) ∧
(e < h → StimBit stim e "start" true)UnderProtocolProperties.leanUnderProtocol d G: in every completed run from every power-up state on every known stimulus, from every release of the reset, up to every later cycle h before which the environment followed the protocol (the task's Rely), G holds up to h. Every guarantee is stated in this frame, so a protocol violation can only void claims about later cycles.
/-- `G` holds in every completed run from every power-up state on every known stimulus, from every
release `c1` of the reset, up to every horizon `h` before which the environment kept the protocol
(`StreamTask.Rely`). -/
def UnderProtocol (N W : Nat) (d : Design2)
(G : List SvState → List SvState → Nat → Nat → Prop) : Prop :=
ModelsPU d fun stim tr =>
∀ c1 h, Released "rst" stim c1 h → task.Rely (N, W) stim tr c1 h → G stim tr c1 hThe shared trusted base
StreamTask.RelyTaskSeq.leanThe environment's side of the protocol, up to a horizon: start, in_valid and in_data are driven with known values in every cycle, start is 1 only while the design is idle, and in_valid is 1 exactly in the N cycles after each start.
/-- **The rely, up to the horizon `h`**: every entry from the release drives
`start`, `in_valid` and `in_data` with known values; `start` only when `Idle`;
`in_valid` is 1 exactly at the `count p` entries after each start. -/
def Rely (p : P) (stim tr : List SvState) (c1 h : Nat) : Prop :=
∀ c, c1 ≤ c → c < h →
(∃ b, StimBit stim c t.start b) ∧
(∃ e v, stim[c]? = some e ∧ t.DataIs p e t.inData v) ∧
(StimBit stim c t.start true → t.Idle p stim tr c1 c) ∧
(∀ s, c1 ≤ s → s < c → c ≤ s + t.count p → StimBit stim s t.start true →
StimBit stim c t.inValid true) ∧
((∀ s, c1 ≤ s → s < c → c ≤ s + t.count p → ¬ StimBit stim s t.start true) →
StimBit stim c t.inValid false)StreamTask.IdleTaskSeq.leanThe design is idle: every start since the release has had all N of its values emitted.
/-- **C2 for streams, `Idle`**: from the release `c1`, every start before `c`
has had all its `count p` outputs emitted before `c`. -/
def Idle (p : P) (stim tr : List SvState) (c1 c : Nat) : Prop :=
∀ s, c1 ≤ s → s < c → StimBit stim s t.start true →
∃ obs : List (t.Obs p), s + obs.length ≤ c ∧ t.Shows p tr s obs ∧
(t.emitted p obs).length = t.count pStreamTask.ShowsTaskSeq.leanShows tr s obs: the snapshots from cycle s on show the observations obs, one per cycle: out_valid as a known bit, and out_data as a known value where out_valid is 1.
/-- The snapshots `a, a+1, ...` show the observations `obs`: `out_valid` known,
and `out_data` known where valid. -/
def Shows (p : P) (tr : List SvState) (a : Nat) (obs : List (t.Obs p)) : Prop :=
∀ k o, obs[k]? = some o → ∃ st, tr[a + k]? = some st ∧ BitIs st t.outValid o.1 ∧
(o.1 = true → t.DataIs p st t.outData o.2)StreamTask.emittedTaskSeq.leanThe values emitted in a list of observations: the out_data of the cycles with out_valid = 1, in order.
/-- The values emitted in a list of observations, in order. -/
def emitted (p : P) (obs : List (t.Obs p)) : List (BitVec (t.width p)) :=
(obs.filter (·.1)).map (·.2)StreamTask.GuarTaskSeq.leanThe trusted guarantee, one conjunction; OUT_VALID_KNOWN, SILENT_BEFORE_FIRST_START, EMITS_SORTED and SILENT_AFTER_LATENCY are its parts.
/-- **The guarantee, up to the horizon `h`**: `out_valid` is known at every
cycle from the release, and 0 before the first start; for every start `s`
whose latency window ends before `h`, with input values `xs` (the `in_data`
of the `count p` entries after it), the observations from `s` to the next
start (or to `h`) emit exactly `spec xs`, none after `s + lat`. -/
def Guar (p : P) (lat : Nat) (stim tr : List SvState) (c1 h : Nat) : Prop :=
(∀ c, c1 ≤ c → c < h → ∃ b, TraceBit tr c t.outValid b) ∧
(∀ c, c1 ≤ c → c < h → (∀ s, c1 ≤ s → s ≤ c → ¬ StimBit stim s t.start true) →
TraceBit tr c t.outValid false) ∧
∀ s (xs : List (BitVec (t.width p))), c1 ≤ s → s + lat < h → StimBit stim s t.start true →
xs.length = t.count p →
(∀ j v, xs[j]? = some v → ∃ e, stim[s + 1 + j]? = some e ∧ t.DataIs p e t.inData v) →
∀ e, s < e → e ≤ h → (∀ s', s < s' → s' < e → ¬ StimBit stim s' t.start true) →
(e < h → StimBit stim e t.start true) →
∃ obs : List (t.Obs p), obs.length = e - s ∧ t.Shows p tr s obs ∧
t.emitted p obs = t.spec p xs ∧ ∀ k o, obs[k]? = some o → lat < k → o.1 = falseReleasedTaskSeq.leanReleased rst stim c1 h: rst was 1 in the cycle before c1 and is 0 in every cycle from c1 up to h. Claims start at every release, so nothing may survive a reset.
/-- **C1**: `c1` is a release cycle of the synchronous reset `rst` within the
first `h` entries: the entry before `c1` asserts it, every entry in `[c1, h)`
deasserts it. -/
def Released (rst : String) (stim : List SvState) (c1 h : Nat) : Prop :=
0 < c1 ∧ StimBit stim (c1 - 1) rst true ∧ ∀ c, c1 ≤ c → c < h → StimBit stim c rst falseTraceBitTaskSeq.leanTraceBit tr c n b: in cycle c the design shows the 1-bit signal n as the known bit b.
/-- The snapshot `c` exists and binds `n` to `b`. -/
def TraceBit (tr : List SvState) (c : Nat) (n : String) (b : Bool) : Prop :=
∃ st, tr[c]? = some st ∧ BitIs st n bStimBitTaskSeq.leanStimBit stim c n b: in cycle c the environment drives the 1-bit input n with the known bit b.
/-- The stimulus entry `c` exists and binds `n` to `b`. -/
def StimBit (stim : List SvState) (c : Nat) (n : String) (b : Bool) : Prop :=
∃ e, stim[c]? = some e ∧ BitIs e n bClockedByTaskSeq.leanThe trusted clock discipline that CLOCK_DISCIPLINE restates.
/-- **The clock discipline** (replaces `Unclocked` for a task with a clock):
`k` is an input port of `d`; every edge-phase process (the processes ls-sv
runs at the clock edge, `Process2.isEdgePhase`) is clocked by `k`; an edge
process with an asynchronous reset uses the reset port `rst`; and no process
reads `k` as data. Under it, "one stimulus entry is one rising edge of `k`"
is the protocol, so an undriven, tied, derived or gated clock cannot occur. -/
def ClockedBy (k : String) (rst : Option String) (d : Design2) : Prop :=
k ∈ d.inputNames.toList ∧
∀ p ∈ d.processes.toList,
(p.isEdgePhase = true → edgeClock p = some k) ∧
(∀ r, asyncReset p = some r → rst = some r) ∧
k ∉ processDataNames pedgeClockTaskSeq.leanThe clock of a clocked process (always_ff @(posedge c) and the like); none for any other process.
/-- The clock of an edge process (`always_ff @(posedge c)`, `always
@(posedge c)`, `always_ff @(posedge c or negedge rn)`, `always_ff @(posedge c
or posedge r)`); `none` for every other process. -/
def edgeClock : Process2 → Option String
| .alwaysFF c _ | .alwaysPlain c _ | .alwaysFFR c _ _ _ => some c
| _ => noneasyncResetTaskSeq.leanThe asynchronous reset of a clocked process (the rn of posedge c or negedge rn), if it has one.
/-- The asynchronous reset of an edge process (the `rn` of `posedge c or
negedge rn`, or the `r` of `posedge c or posedge r`), if it has one. Both
polarities name their reset, so the clock discipline below holds an
active-high reset to the task's reset port too. -/
def asyncReset : Process2 → Option String
| .alwaysFFR _ r _ _ => some r
| _ => noneprocessDataNamesTaskSeq.leanEvery signal a process reads or writes as data: its body, or its assignment. The clock and reset in the event control do not count.
/-- The names a process mentions as DATA: everything in its body (or its
target and value). The clock and reset names in an edge process's event
control are not data. -/
def processDataNames : Process2 → List String
| .alwaysFF _ b | .alwaysPlain _ b | .alwaysFFR _ _ b _ | .alwaysComb b => stmtNames b
| .assign t e => lhsNames t ++ exprNames e
| .unsupported _ _ => []inputPortsTask.leanThe design's input ports, each with its declared width, in declaration order.
/-- 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 noneoutputPortsTask.leanThe design's output ports, each with its declared width, in declaration order.
/-- 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 noneHoldAllTask.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 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.2ScheduleOracleSemantics.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.
/-- 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 readyProcess2.isEdgePhaseSem2.leanTrue for the processes that run at the clock edge: always_ff, always @(posedge …) and asynchronous-reset blocks.
/-- 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
| _ => falseMooreOutDepTaskSeq.leanThe trusted rule 1 of txtask-v2 that MOORE_OUTPUTS restates: a set of signals that holds every input port outside the exempt list (here the list is empty: every input port, rst included), is closed along every combinational dependency and holds no output port.
/-- **Rule 1, Moore outputs**: no combinational path leads from an input port outside `exempt`
to an output port. `S` is a set of signals that contains every such input, is closed along the
combinational dependencies (if an assignment depends on a signal in `S`, every signal it writes
is in `S`) and contains no output port. Every v2 statement exempts nothing (`SeqTask`'s from the
start; `TxTask`'s and `StreamTask`'s since 2026-11-09, see `TxTask.SatisfiedAtV2`). -/
def MooreOutDep (d : Design2) (exempt : List String) : Prop :=
∃ S : String → Prop,
(∀ n ∈ d.inputNames.toList, n ∉ exempt → S n) ∧
(∀ e ∈ combDeps d, (∃ r ∈ e.2, S r) → S e.1) ∧
∀ o ∈ d.outputNames.toList, ¬ S ocombDepsTaskSeq.leanThe combinational dependencies of a design, by assignment: for each always_comb or continuous assign, every signal a write targets and every signal its value, its indices or an enclosing condition reads. A clocked process contributes none: its writes end a combinational path at the clock edge.
/-- Every combinational dependency of a design, by assignment. -/
def combDeps (d : Design2) : List DepEdge := d.processes.toList.flatMap processDepsModelsPUTaskSeq.leanModelsPU d P: from every power-up state, under every schedule, on every known stimulus, every completed run of d satisfies P. It replaces txtask-v1's runs from Lean's own start state, where an unreset register is x: an unreset register is a 0 or 1 nobody knows, so the claims must hold for every value it can have.
/-- **Rule 2's judgment**: from every power-up state, under every schedule, on every known
stimulus, every completed run satisfies `P` (v1's `⊨₂`, whose runs start at `initState2 d`, with
the start quantified over the power-up states). -/
def ModelsPU (d : Design2) (P : List SvState → List SvState → Prop) : Prop :=
∀ st0, PowerUp d st0 → ∀ (σ : ScheduleOracle) (stim tr : List SvState), KnownStim d stim →
RunsAt d σ st0 stim tr → P stim trPowerUpTaskSeq.leanPowerUp d st: st binds every declared signal of d, in declaration order, to a value of its declared width whose every bit is 0 or 1 (2-state declarations included, initializers ignored).
/-- **A power-up state** of `d`: every declaration, in declaration order, bound to a value of its
declared width whose every bit is `0` or `1`. 2-state declarations are included (ls-sv's
`initState2` starts them at `0`, the standard's 2-state initial value; a flip-flop powers up 0 or
1), and declaration initializers play no part (the clocked source gate refuses them). -/
def PowerUp (d : Design2) (st : SvState) : Prop :=
st.map Prod.fst = d.decls.toList.map Decl.name ∧
∀ b ∈ st.zip d.decls.toList, b.1.2.width = b.2.width ∧ b.1.2.allKnown = trueKnownStimTaskSeq.leanKnownStim d stim: every stimulus entry binds each input port it drives to a value of the port's width whose every bit is 0 or 1. An x on an input is a value the environment does not know; each of its resolutions is a known entry.
/-- Every entry of a stimulus is known. -/
def KnownStim (d : Design2) (stim : List SvState) : Prop := ∀ e ∈ stim, KnownEntry d eKnownEntryTaskSeq.leanKnownEntry d e: every input port the entry e binds holds a known value of its declared width.
/-- **A known stimulus entry**: every input port it binds holds a value of the port's declared
width whose every bit is `0` or `1`. An `x` on an input is a value the environment does not
know, and each of its resolutions is a known entry, so quantifying over known entries is the
formal reading of an `x` input. An input the entry does not bind keeps its value
(`applyInputs2`). -/
def KnownEntry (d : Design2) (e : SvState) : Prop :=
∀ n ∈ d.inputNames.toList,
((SvState.lookup e n).all fun v => v.allKnown && sigWidth d n == some v.width) = truesigWidthTaskSeq.leanThe declared width of a signal (its first declaration's).
/-- The declared width of the signal `n` (its first declaration's; `Design2.declWidth` of ls-sv's
comb layer, restated here so that this file imports no proof layer). -/
def sigWidth (d : Design2) (n : String) : Option Nat :=
(d.decls.toList.find? fun dc => dc.name == n).map Decl.widthRunsAtTaskSeq.leanRunsAt d σ st0 stim tr: simulating d from the state st0 under the schedule σ on the stimulus stim completes, with the trace tr.
/-- Some fuel completes the run of `d` from `st0` under `σ` on `stim` with the trace `tr` (the
`Runs2` of `run2At`). -/
def RunsAt (d : Design2) (σ : ScheduleOracle) (st0 : SvState) (stim tr : List SvState) : Prop :=
∃ fuel, run2At d σ fuel st0 stim = .ok trrun2AtTaskSeq.leanLean's run of a design (ls-sv's run2: the same clock cycles and checks) started from a given state instead of its own start state.
/-- **`run2` from the state `st0`** in place of `initState2 d`: the same cycles (`runFrom2`), the
same qualified-case validation (`run2_eq_run2At`: `run2` is `run2At` from `initState2 d`). -/
def run2At (d : Design2) (σ : ScheduleOracle) (fuel : Nat) (st0 : SvState)
(stim : List SvState) : Res (List SvState) := do
let tr ← runFrom2 d σ fuel st0 0 stim
if d.processes.toList.any Process2.hasQualifiedCase then
validateTraceChecks2 fuel d tr
return trTerminatesPUTaskSeq.leanThe trusted termination conjunct of txtask-v2 that TERMINATES restates: from every power-up state, under every schedule, every known stimulus completes.
/-- **Non-vacuity of `ModelsPU`**: from every power-up state, under every schedule, every known
stimulus completes (v1's `Terminates2` from the power-up states: a design that settles from the
x start but oscillates from a concrete one fails it). -/
def TerminatesPU (d : Design2) : Prop :=
∀ st0, PowerUp d st0 → ∀ (σ : ScheduleOracle) (stim : List SvState), KnownStim d stim →
∃ tr, RunsAt d σ st0 stim trXSafeTaskSeq.leanThe trusted rule 3 of txtask-v2 that NO_X_CREATING restates: every process passes the structural check xsafeB.
/-- **Rule 3**: no construct of `d` can create an `x` or a `z` from known operands. -/
def XSafe (d : Design2) : Prop := xsafeB d = truexsafeBTaskSeq.leanThe structural check of rule 3, a Boolean function of the design's text and declared widths: every expression of every process has a rule-3 width (xsWidth), and every case pattern is an admitted expression or a casez/casex literal.
/-- **Rule 3, decided**: no process of `d` has a construct that can create an `x` from known
operands. -/
def xsafeB (d : Design2) : Bool := d.processes.toList.all (xsProc d)xsWidthTaskSeq.leanThe width of an expression's value when no construct in it can create an x from known operands, and none otherwise: literals without x/z digits, declared signals, slices in range, run-time indices whose every value is in range, equal widths for bitwise and equality operators and for the two arms of ?:, and the operator rules of binW.
/-- **Rule 3 for an expression**: `some w` when no construct in `e` can create an `x` from known
operands, `w` being the width of its value on a state that binds every signal at its declared
width; `none` otherwise. -/
def xsWidth (d : Design2) : Expr2 → Option Nat
| .lit v => if v.allKnown then some v.width else none
| .ident n => sigWidth d n
| .unary .lnot a => (xsWidth d a).map fun _ => 1
| .unary _ a => xsWidth d a
| .resizeSigned w a => (xsWidth d a).map fun _ => w
| .squash2 a => xsWidth d a
| .binary op l r => binW op l r (xsWidth d l) (xsWidth d r)
| .scmp op l r => if scmpOp op then (do let _ ← xsWidth d l; let _ ← xsWidth d r; pure 1) else none
| .logical _ l r => do let _ ← xsWidth d l; let _ ← xsWidth d r; pure 1
| .ternary c t f => do
let _ ← xsWidth d c; let a ← xsWidth d t; let b ← xsWidth d f; if a = b then pure a else none
| .concat parts => xsWidthList d parts.toList
| .slice b lo w => do let a ← xsWidth d b; if lo + w ≤ a then pure w else none
| .index b i ew => do
let a ← xsWidth d b; let k ← xsWidth d i
if unsignedIdx d i = true ∧ 2 ^ k * ew ≤ a then pure ew else none
| .repl n a => (xsWidth d a).map (· * n)
| .reduce _ a => (xsWidth d a).map fun _ => 1
| .resize w a => (xsWidth d a).map fun _ => w
| .unsupported _ _ => none
termination_by structural e => ebinWTaskSeq.leanRule 3 at a binary operator: / and % only by a nonzero constant, ** with a signed exponent only on a nonzero constant base, &, |, ^, ==, !=, ==?, !=? only on equal widths; every other operator is known on known operands.
/-- Rule 3 at a binary operator, from its operands `l`, `r` and their rule-3 widths `wl`, `wr`:
the width of the result, or `none` if the operator can create an `x` from known operands. -/
def binW (op : BinOp) (l r : Expr2) (wl wr : Option Nat) : Option Nat :=
match op with
| .add | .sub | .mul => do let a ← wl; let b ← wr; pure (max a b)
| .div | .sdiv | .rem | .srem => do
let a ← wl; let b ← wr; if constNZ r then pure (max a b) else none
| .pow _ se => do let a ← wl; let _ ← wr; if !se || constNZ l then pure a else none
| .shl _ | .shr _ | .ashl _ | .ashr _ => do let a ← wl; let _ ← wr; pure a
| .and | .or | .xor => do let a ← wl; let b ← wr; if a = b then pure a else none
| .eq | .ne => do let a ← wl; let b ← wr; if a = b then pure 1 else none
| .wildEq | .wildNe => do let a ← wl; let b ← wr; if a = b then pure 1 else none
| .caseEq | .caseNe | .casez | .casex => do let _ ← wl; let _ ← wr; pure 1
| .land | .lor | .lt | .le | .gt | .ge | .slt | .sle | .sgt | .sge => do
let _ ← wl; let _ ← wr; pure 1What 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.
- Rejected at the proof: 8 designs (by stage:
solution8)
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.
early_valid.sv(attack, stopped atsolution): The valid token enters withstart, one cycle before the first value: one output too many (the padding 0).the design, with its licence and provenance notices
// Attack for task sorter (tests/expected.json, `attacks`; PLAN4.md Gate 5, the sorter): the proved // reference tasks/sorter/reference/sorter_ins.sv with ONE change (1 site), a correct-looking design // that is wrong for some N, W: the valid token enters with the start (`tok[N-1] <= in_valid | start`), one cycle before the first value. // Wrong: out_valid rises one cycle early, at the snapshot N after the start, and stays up N + 1 cycles: one value too many (the padding 0 in cell 0), every N. // Every check up to the proof passes it; the reference's proof (reference/sorter_ins.lean) must FAIL on it. // Derived from andrewpeck/cell-sort (MIT, cfc46709; the reference's header has the provenance and changes): // // MIT License // // Copyright (c) 2024-2025 Andrew Peck // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. `timescale 1ns / 1ps `default_nettype none module sorter #(parameter int N = 8, parameter int W = 8) ( input logic clk, rst, input logic start, in_valid, input logic [W-1:0] in_data, output logic out_valid, output logic [W-1:0] out_data ); logic [N*W-1:0] data; // cell k's value: data[k*W +: W]; cell N-1 is the top ("best") cell logic [N-1:0] tok; // cell k's valid token // cells 0 .. N-2: upstream's unit_cell; the upper neighbour is cell k+1 for (genvar k = 0; k < N-1; k++) begin : gen_cell always_ff @(posedge clk) begin if (in_valid) begin if (in_data > data[(k+1)*W +: W]) // the neighbour is pushed: take its value data[k*W +: W] <= data[(k+1)*W +: W]; else if (in_data > data[k*W +: W]) // the input beats this cell's value data[k*W +: W] <= in_data; end else data[k*W +: W] <= data[(k+1)*W +: W]; // drain: shift down tok[k] <= tok[k+1]; if (rst) begin data[k*W +: W] <= '0; tok[k] <= 1'b0; end end end // the top cell N-1: never pushed into (upstream's push[DEPTH] = 0); drains in the empty value 0 always_ff @(posedge clk) begin if (in_valid) begin if (in_data > data[(N-1)*W +: W]) data[(N-1)*W +: W] <= in_data; end else data[(N-1)*W +: W] <= '0; tok[N-1] <= in_valid | start; if (rst) begin data[(N-1)*W +: W] <= '0; tok[N-1] <= 1'b0; end end // the streaming output: cell 0 and its token, registered always_ff @(posedge clk) begin out_valid <= tok[0]; out_data <= data[0 +: W]; if (rst) out_valid <= 1'b0; end endmodulemissing_else.sv(attack, stopped atsolution): A cell's second test lost itselse: when the input beats both its neighbour and its own value, it is copied into several cells and the neighbour's value is lost.the design, with its licence and provenance notices
// Attack for task sorter (tests/expected.json, `attacks`; PLAN4.md Gate 5, the sorter): the proved // reference tasks/sorter/reference/sorter_ins.sv with ONE change (1 site), a correct-looking design // that is wrong for some N, W: the cell's second test lost its `else`: when the input beats both the upper neighbour and the cell's own value, both writes fire and the later one (the input) wins. // Wrong: a value larger than two stored ones is copied into several cells and the neighbour's value is lost (N >= 2); right at N = 1. // Every check up to the proof passes it; the reference's proof (reference/sorter_ins.lean) must FAIL on it. // Derived from andrewpeck/cell-sort (MIT, cfc46709; the reference's header has the provenance and changes): // // MIT License // // Copyright (c) 2024-2025 Andrew Peck // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. `timescale 1ns / 1ps `default_nettype none module sorter #(parameter int N = 8, parameter int W = 8) ( input logic clk, rst, input logic start, in_valid, input logic [W-1:0] in_data, output logic out_valid, output logic [W-1:0] out_data ); logic [N*W-1:0] data; // cell k's value: data[k*W +: W]; cell N-1 is the top ("best") cell logic [N-1:0] tok; // cell k's valid token // cells 0 .. N-2: upstream's unit_cell; the upper neighbour is cell k+1 for (genvar k = 0; k < N-1; k++) begin : gen_cell always_ff @(posedge clk) begin if (in_valid) begin if (in_data > data[(k+1)*W +: W]) // the neighbour is pushed: take its value data[k*W +: W] <= data[(k+1)*W +: W]; if (in_data > data[k*W +: W]) // the input beats this cell's value data[k*W +: W] <= in_data; end else data[k*W +: W] <= data[(k+1)*W +: W]; // drain: shift down tok[k] <= tok[k+1]; if (rst) begin data[k*W +: W] <= '0; tok[k] <= 1'b0; end end end // the top cell N-1: never pushed into (upstream's push[DEPTH] = 0); drains in the empty value 0 always_ff @(posedge clk) begin if (in_valid) begin if (in_data > data[(N-1)*W +: W]) data[(N-1)*W +: W] <= in_data; end else data[(N-1)*W +: W] <= '0; tok[N-1] <= in_valid; if (rst) begin data[(N-1)*W +: W] <= '0; tok[N-1] <= 1'b0; end end // the streaming output: cell 0 and its token, registered always_ff @(posedge clk) begin out_valid <= tok[0]; out_data <= data[0 +: W]; if (rst) out_valid <= 1'b0; end endmodulepush_own_value.sv(attack, stopped atsolution): The neighbour-push test compares the input with the cell's own value instead of its upper neighbour's, so the input is never stored below the top cell.the design, with its licence and provenance notices
// Attack for task sorter (tests/expected.json, `attacks`; PLAN4.md Gate 5, the sorter): the proved // reference tasks/sorter/reference/sorter_ins.sv with ONE change (1 site), a correct-looking design // that is wrong for some N, W: the neighbour-push test compares the input with the cell's OWN value instead of its upper neighbour's. // Wrong: a cell takes its neighbour's value whenever the input beats its own value, so the input itself is never stored below the top cell (N >= 2); right at N = 1. // Every check up to the proof passes it; the reference's proof (reference/sorter_ins.lean) must FAIL on it. // Derived from andrewpeck/cell-sort (MIT, cfc46709; the reference's header has the provenance and changes): // // MIT License // // Copyright (c) 2024-2025 Andrew Peck // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. `timescale 1ns / 1ps `default_nettype none module sorter #(parameter int N = 8, parameter int W = 8) ( input logic clk, rst, input logic start, in_valid, input logic [W-1:0] in_data, output logic out_valid, output logic [W-1:0] out_data ); logic [N*W-1:0] data; // cell k's value: data[k*W +: W]; cell N-1 is the top ("best") cell logic [N-1:0] tok; // cell k's valid token // cells 0 .. N-2: upstream's unit_cell; the upper neighbour is cell k+1 for (genvar k = 0; k < N-1; k++) begin : gen_cell always_ff @(posedge clk) begin if (in_valid) begin if (in_data > data[k*W +: W]) // the neighbour is pushed: take its value data[k*W +: W] <= data[(k+1)*W +: W]; else if (in_data > data[k*W +: W]) // the input beats this cell's value data[k*W +: W] <= in_data; end else data[k*W +: W] <= data[(k+1)*W +: W]; // drain: shift down tok[k] <= tok[k+1]; if (rst) begin data[k*W +: W] <= '0; tok[k] <= 1'b0; end end end // the top cell N-1: never pushed into (upstream's push[DEPTH] = 0); drains in the empty value 0 always_ff @(posedge clk) begin if (in_valid) begin if (in_data > data[(N-1)*W +: W]) data[(N-1)*W +: W] <= in_data; end else data[(N-1)*W +: W] <= '0; tok[N-1] <= in_valid; if (rst) begin data[(N-1)*W +: W] <= '0; tok[N-1] <= 1'b0; end end // the streaming output: cell 0 and its token, registered always_ff @(posedge clk) begin out_valid <= tok[0]; out_data <= data[0 +: W]; if (rst) out_valid <= 1'b0; end endmodulereset_keeps_cells.sv(attack, stopped atsolution): The reset clears the tokens and the top cell but not the other cells: the first transaction after a reset sorts in whatever they held.the design, with its licence and provenance notices
// Attack for task sorter (tests/expected.json, `attacks`; PLAN4.md Gate 5, the sorter): the proved // reference tasks/sorter/reference/sorter_ins.sv with ONE change (1 site), a correct-looking design // that is wrong for some N, W: the reset clears the tokens and the top cell but not cells 0 .. N-2. // Wrong: the first transaction after a reset sorts the input together with whatever the cells held (N >= 2; the drain clears them, so later transactions are right); right at N = 1. // Every check up to the proof passes it; the reference's proof (reference/sorter_ins.lean) must FAIL on it. // Derived from andrewpeck/cell-sort (MIT, cfc46709; the reference's header has the provenance and changes): // // MIT License // // Copyright (c) 2024-2025 Andrew Peck // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. `timescale 1ns / 1ps `default_nettype none module sorter #(parameter int N = 8, parameter int W = 8) ( input logic clk, rst, input logic start, in_valid, input logic [W-1:0] in_data, output logic out_valid, output logic [W-1:0] out_data ); logic [N*W-1:0] data; // cell k's value: data[k*W +: W]; cell N-1 is the top ("best") cell logic [N-1:0] tok; // cell k's valid token // cells 0 .. N-2: upstream's unit_cell; the upper neighbour is cell k+1 for (genvar k = 0; k < N-1; k++) begin : gen_cell always_ff @(posedge clk) begin if (in_valid) begin if (in_data > data[(k+1)*W +: W]) // the neighbour is pushed: take its value data[k*W +: W] <= data[(k+1)*W +: W]; else if (in_data > data[k*W +: W]) // the input beats this cell's value data[k*W +: W] <= in_data; end else data[k*W +: W] <= data[(k+1)*W +: W]; // drain: shift down tok[k] <= tok[k+1]; if (rst) begin tok[k] <= 1'b0; end end end // the top cell N-1: never pushed into (upstream's push[DEPTH] = 0); drains in the empty value 0 always_ff @(posedge clk) begin if (in_valid) begin if (in_data > data[(N-1)*W +: W]) data[(N-1)*W +: W] <= in_data; end else data[(N-1)*W +: W] <= '0; tok[N-1] <= in_valid; if (rst) begin data[(N-1)*W +: W] <= '0; tok[N-1] <= 1'b0; end end // the streaming output: cell 0 and its token, registered always_ff @(posedge clk) begin out_valid <= tok[0]; out_data <= data[0 +: W]; if (rst) out_valid <= 1'b0; end endmodulerst_gate.sv(attack, stopped atsolution): The outputs are registered, butout_validis gated by the reset (assign out_valid = ov_q & ~rst): no output may read an input combinationally, the reset included, so the last output before a reset cannot vanish from the wire.the design, with its licence and provenance notices
// Attack for task sorter (tests/expected.json, `attacks`; review L1 of 2026-11-08, the rule-1 decision of // 2026-11-09): the proved reference tasks/sorter/reference/sorter_ins.sv with ONE change (the output stage), // a design whose every claimed snapshot is the reference's: the outputs are registered as ov_q and od_q, and // out_valid is ov_q gated by the reset (`assign out_valid = ov_q & ~rst`). // Wrong on the wire: in the cycle of a reset entry the snapshot before it is still claimed, but the wire shows // out_valid = 0, so a transaction's last output is hidden from a consumer when a reset follows it. Rule 1 with // nothing exempt (`MooreOutDep d []`, since 2026-11-09) refuses the path from rst to out_valid, for every N, W. // Every check that needs no proof passes it; the reference's proof (reference/sorter_ins.lean) must FAIL on it. // Derived from andrewpeck/cell-sort (MIT, cfc46709; the reference's header has the provenance and changes): // // MIT License // // Copyright (c) 2024-2025 Andrew Peck // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. `timescale 1ns / 1ps `default_nettype none module sorter #(parameter int N = 8, parameter int W = 8) ( input logic clk, rst, input logic start, in_valid, input logic [W-1:0] in_data, output logic out_valid, output logic [W-1:0] out_data ); logic [N*W-1:0] data; // cell k's value: data[k*W +: W]; cell N-1 is the top ("best") cell logic [N-1:0] tok; // cell k's valid token // cells 0 .. N-2: upstream's unit_cell; the upper neighbour is cell k+1 for (genvar k = 0; k < N-1; k++) begin : gen_cell always_ff @(posedge clk) begin if (in_valid) begin if (in_data > data[(k+1)*W +: W]) // the neighbour is pushed: take its value data[k*W +: W] <= data[(k+1)*W +: W]; else if (in_data > data[k*W +: W]) // the input beats this cell's value data[k*W +: W] <= in_data; end else data[k*W +: W] <= data[(k+1)*W +: W]; // drain: shift down tok[k] <= tok[k+1]; if (rst) begin data[k*W +: W] <= '0; tok[k] <= 1'b0; end end end // the top cell N-1: never pushed into (upstream's push[DEPTH] = 0); drains in the empty value 0 always_ff @(posedge clk) begin if (in_valid) begin if (in_data > data[(N-1)*W +: W]) data[(N-1)*W +: W] <= in_data; end else data[(N-1)*W +: W] <= '0; tok[N-1] <= in_valid; if (rst) begin data[(N-1)*W +: W] <= '0; tok[N-1] <= 1'b0; end end // the streaming output: cell 0 and its token, registered logic ov_q; logic [W-1:0] od_q; always_ff @(posedge clk) begin ov_q <= tok[0]; od_q <= data[0 +: W]; if (rst) ov_q <= 1'b0; end assign out_valid = ov_q & ~rst; assign out_data = od_q; endmodulesigned_order.sv(attack, stopped atsolution): The comparisons are signed, so values with the top bit set sort below the padding 0 and are lost.the design, with its licence and provenance notices
// Attack for task sorter (tests/expected.json, `attacks`; PLAN4.md Gate 5, the sorter): the proved // reference tasks/sorter/reference/sorter_ins.sv with ONE change (3 sites), a correct-looking design // that is wrong for some N, W: the comparisons are signed (`$signed(in_data) > $signed(data[...])`) instead of unsigned. // Wrong: values with the top bit set sort below the others (and below the padding 0, so they are lost), every N and W. // Every check up to the proof passes it; the reference's proof (reference/sorter_ins.lean) must FAIL on it. // Derived from andrewpeck/cell-sort (MIT, cfc46709; the reference's header has the provenance and changes): // // MIT License // // Copyright (c) 2024-2025 Andrew Peck // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. `timescale 1ns / 1ps `default_nettype none module sorter #(parameter int N = 8, parameter int W = 8) ( input logic clk, rst, input logic start, in_valid, input logic [W-1:0] in_data, output logic out_valid, output logic [W-1:0] out_data ); logic [N*W-1:0] data; // cell k's value: data[k*W +: W]; cell N-1 is the top ("best") cell logic [N-1:0] tok; // cell k's valid token // cells 0 .. N-2: upstream's unit_cell; the upper neighbour is cell k+1 for (genvar k = 0; k < N-1; k++) begin : gen_cell always_ff @(posedge clk) begin if (in_valid) begin if ($signed(in_data) > $signed(data[(k+1)*W +: W])) // the neighbour is pushed: take its value data[k*W +: W] <= data[(k+1)*W +: W]; else if ($signed(in_data) > $signed(data[k*W +: W])) // the input beats this cell's value data[k*W +: W] <= in_data; end else data[k*W +: W] <= data[(k+1)*W +: W]; // drain: shift down tok[k] <= tok[k+1]; if (rst) begin data[k*W +: W] <= '0; tok[k] <= 1'b0; end end end // the top cell N-1: never pushed into (upstream's push[DEPTH] = 0); drains in the empty value 0 always_ff @(posedge clk) begin if (in_valid) begin if ($signed(in_data) > $signed(data[(N-1)*W +: W])) data[(N-1)*W +: W] <= in_data; end else data[(N-1)*W +: W] <= '0; tok[N-1] <= in_valid; if (rst) begin data[(N-1)*W +: W] <= '0; tok[N-1] <= 1'b0; end end // the streaming output: cell 0 and its token, registered always_ff @(posedge clk) begin out_valid <= tok[0]; out_data <= data[0 +: W]; if (rst) out_valid <= 1'b0; end endmoduletoken_self.sv(attack, stopped atsolution): Each cell's token shift reads its own token instead of its neighbour's: the tokens never move and no value is emitted.the design, with its licence and provenance notices
// Attack for task sorter (tests/expected.json, `attacks`; PLAN4.md Gate 5, the sorter): the proved // reference tasks/sorter/reference/sorter_ins.sv with ONE change (1 site), a correct-looking design // that is wrong for some N, W: the token shift of cells 0 .. N-2 reads the cell's own token (`tok[k] <= tok[k]`) instead of its upper neighbour's. // Wrong: the tokens below the top never move, so out_valid stays 0 and no value is emitted (N >= 2); right at N = 1, where the family is empty and out_valid reads the top token. // Every check up to the proof passes it; the reference's proof (reference/sorter_ins.lean) must FAIL on it. // Derived from andrewpeck/cell-sort (MIT, cfc46709; the reference's header has the provenance and changes): // // MIT License // // Copyright (c) 2024-2025 Andrew Peck // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. `timescale 1ns / 1ps `default_nettype none module sorter #(parameter int N = 8, parameter int W = 8) ( input logic clk, rst, input logic start, in_valid, input logic [W-1:0] in_data, output logic out_valid, output logic [W-1:0] out_data ); logic [N*W-1:0] data; // cell k's value: data[k*W +: W]; cell N-1 is the top ("best") cell logic [N-1:0] tok; // cell k's valid token // cells 0 .. N-2: upstream's unit_cell; the upper neighbour is cell k+1 for (genvar k = 0; k < N-1; k++) begin : gen_cell always_ff @(posedge clk) begin if (in_valid) begin if (in_data > data[(k+1)*W +: W]) // the neighbour is pushed: take its value data[k*W +: W] <= data[(k+1)*W +: W]; else if (in_data > data[k*W +: W]) // the input beats this cell's value data[k*W +: W] <= in_data; end else data[k*W +: W] <= data[(k+1)*W +: W]; // drain: shift down tok[k] <= tok[k]; if (rst) begin data[k*W +: W] <= '0; tok[k] <= 1'b0; end end end // the top cell N-1: never pushed into (upstream's push[DEPTH] = 0); drains in the empty value 0 always_ff @(posedge clk) begin if (in_valid) begin if (in_data > data[(N-1)*W +: W]) data[(N-1)*W +: W] <= in_data; end else data[(N-1)*W +: W] <= '0; tok[N-1] <= in_valid; if (rst) begin data[(N-1)*W +: W] <= '0; tok[N-1] <= 1'b0; end end // the streaming output: cell 0 and its token, registered always_ff @(posedge clk) begin out_valid <= tok[0]; out_data <= data[0 +: W]; if (rst) out_valid <= 1'b0; end endmoduletop_not_drained.sv(attack, stopped atsolution): The drain does not clear the top cell: it keeps the transaction's maximum, and the next transaction's values never reach the top.the design, with its licence and provenance notices
// Attack for task sorter (tests/expected.json, `attacks`; PLAN4.md Gate 5, the sorter): the proved // reference tasks/sorter/reference/sorter_ins.sv with ONE change (1 site), a correct-looking design // that is wrong for some N, W: the top cell is not cleared by the drain (its `else` branch is gone): it keeps the transaction's maximum. // Wrong: the stale maximum shifts down behind the sorted values and is still in the top cell for the next transaction, whose values below it never reach the top: wrong from the second transaction on, every N. // Every check up to the proof passes it; the reference's proof (reference/sorter_ins.lean) must FAIL on it. // Derived from andrewpeck/cell-sort (MIT, cfc46709; the reference's header has the provenance and changes): // // MIT License // // Copyright (c) 2024-2025 Andrew Peck // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. `timescale 1ns / 1ps `default_nettype none module sorter #(parameter int N = 8, parameter int W = 8) ( input logic clk, rst, input logic start, in_valid, input logic [W-1:0] in_data, output logic out_valid, output logic [W-1:0] out_data ); logic [N*W-1:0] data; // cell k's value: data[k*W +: W]; cell N-1 is the top ("best") cell logic [N-1:0] tok; // cell k's valid token // cells 0 .. N-2: upstream's unit_cell; the upper neighbour is cell k+1 for (genvar k = 0; k < N-1; k++) begin : gen_cell always_ff @(posedge clk) begin if (in_valid) begin if (in_data > data[(k+1)*W +: W]) // the neighbour is pushed: take its value data[k*W +: W] <= data[(k+1)*W +: W]; else if (in_data > data[k*W +: W]) // the input beats this cell's value data[k*W +: W] <= in_data; end else data[k*W +: W] <= data[(k+1)*W +: W]; // drain: shift down tok[k] <= tok[k+1]; if (rst) begin data[k*W +: W] <= '0; tok[k] <= 1'b0; end end end // the top cell N-1: never pushed into (upstream's push[DEPTH] = 0); drains in the empty value 0 always_ff @(posedge clk) begin if (in_valid) begin if (in_data > data[(N-1)*W +: W]) data[(N-1)*W +: W] <= in_data; end tok[N-1] <= in_valid; if (rst) begin data[(N-1)*W +: W] <= '0; tok[N-1] <= 1'b0; end end // the streaming output: cell 0 and its token, registered always_ff @(posedge clk) begin out_valid <= tok[0]; out_data <= data[0 +: W]; if (rst) out_valid <= 1'b0; end endmodule
Spec.lean, verbatim (97 lines)
import SvGolf.TaskSeq
/-!
# Task `sorter`: a streaming sorter of N unsigned W-bit values
# (TRUSTED text, final, decided 2026-11-08, amended 2026-11-09; written 2026-11-04; contract C8 of `design/sequential/CONTRACT.md`)
Module (port order as written; `N`, `W` are the module's parameters, in this order):
```systemverilog
module sorter #(parameter int N = 8, parameter int W = 8) (
input logic clk, rst,
input logic start, in_valid,
input logic [W-1:0] in_data,
output logic out_valid,
output logic [W-1:0] out_data
);
```
**Protocol** (`StreamTask`, `SvGolf/TaskSeq.lean`; C1, C2, C8): synchronous active-high reset `rst`;
claims from the cycle after its release; `start` a one-cycle pulse raised only when the design is
idle (no transaction yet, or the previous one's `N` outputs emitted); after a `start`, `in_valid`
is 1 for exactly the `N` cycles beginning the cycle after it (no gaps), carrying the values
`x[0] .. x[N-1]` on `in_data`, and 0 otherwise. There is no output backpressure.
**Guarantee.** `out_valid` is KNOWN at every cycle after the release, and 0 before the first
`start`. From each `start` to the next, `out_valid` is 1 on exactly `N` cycles, all at most `lat`
cycles after the edge that samples `start`, and the `out_data` values on those cycles, in order,
are the input values sorted ascending as unsigned numbers, duplicates kept. `out_data` is cared
about only where `out_valid` = 1; the valid cycles may have gaps.
The declared latency `lat` is the submission's; the checker pins its value at the scoring point and
the score uses it. Run-time `/ % **` are banned, as in every task (`task.json`).
**Range.** `N, W ≥ 1` and `N·W < 2^31`: the parameters are `int`, and a design that buffers the whole
stream declares `N·W` bits; every such width must stay in the 32-bit range, where a real elaboration
and ls-sv's unbounded constant tier agree (`PDesign.int32Agrees`; Task v2 decision 1).
**txtask-v2 (2026-11-05; final, decided 2026-11-08, amended 2026-11-09).** `Statement` is the v2
statement `StreamTask.StatementV2`: txtask-v1's interface, clock discipline and latency bound; its
claims and termination from EVERY power-up state on every known stimulus (rule 2); Moore outputs
(`MooreOutDep`, rule 1, nothing exempt: no combinational path from `rst`, `start`, `in_valid` or
`in_data` to `out_valid` or `out_data`); and no construct that creates an `x` from known operands
(`XSafe`, rule 3). `SvGolf/TaskSeq.lean`, last section, and `design/soundness/TXTASK-V2-REVIEW.md`.
-/
namespace SvGolf.Tasks.Sorter
open SvGolf
/-- The parameters `(N, W)`. -/
abbrev Params := Nat × Nat
/-- Insert `a` into an ascending list (unsigned order), before the first element it is `≤` to. -/
def insertAsc {W : Nat} (a : BitVec W) : List (BitVec W) → List (BitVec W)
| [] => [a]
| b :: bs => if a.toNat ≤ b.toNat then a :: b :: bs else b :: insertAsc a bs
/-- Insertion sort, ascending, unsigned: `List.insertionSort (· ≤ ·)` (C8), written out here
because the trusted text imports no Mathlib (structural recursion: the kernel evaluates it). -/
def sortAsc {W : Nat} : List (BitVec W) → List (BitVec W)
| [] => []
| a :: as => insertAsc a (sortAsc as)
/-- **The task** (C8). -/
def task : StreamTask Params where
inRange p := 1 ≤ p.1 ∧ 1 ≤ p.2 ∧ p.1 * p.2 < 2 ^ 31
clk := "clk"
rst := "rst"
start := "start"
inValid := "in_valid"
inData := "in_data"
outValid := "out_valid"
outData := "out_data"
width p := p.2
count p := p.1
spec _ xs := sortAsc xs
/-- **The challenge statement**, for the two-parameter design family `sem N W` and the latency
function `lat N W`. -/
abbrev Statement (sem : Nat → Nat → LeanModels.Sv.Design2) (lat : Nat → Nat → Nat) : Prop :=
task.StatementV2 (fun p => sem p.1 p.2) (fun p => lat p.1 p.2)
/-! ## Sanity checks on the statement's data (proved; they add nothing to the trusted base) -/
/-- Duplicates kept, unsigned order. -/
example : sortAsc [3#4, 15#4, 0#4, 3#4, 8#4] = [0#4, 3#4, 3#4, 8#4, 15#4] := by decide
/-- A typical point is in range; the 32-bit bound excludes `N·W ≥ 2^31`. -/
example : task.inRange (8, 8) ∧ ¬ task.inRange (2 ^ 16, 2 ^ 15) := by
simp [task]
/-- The port names are distinct. -/
example : ["clk", "rst", "start", "in_valid", "in_data"].Nodup ∧
["out_valid", "out_data"].Nodup := by
decide
end SvGolf.Tasks.Sorter
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.
| module | role | lines | sha256 |
|---|---|---|---|
tasks.sorter.Spec | task statement | 97 | 6243e0673747 |
How to participate #
- Write a circuit with the task's exact interface.
- Prove in Lean 4 that it meets the trusted statement; Start a new design, below, gives the route, and the proving guide shows how.
- 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.
- Beat par: an accepted design whose score (area × time) is below that of the best proved reference. The leaderboard ranks by it.