This task
Multiply two N × N matrices of signed W-bit entries: C = A · B, every sum wrapping at AccW bits.
A handshake frames each product: the design reads A and B when start is pulsed, and after each start raises
done once C holds the product. C is claimed only from the first done = 1 after a start until the next
start, where it must hold the product; at every other cycle it may show anything, while done is known at every
cycle from the release and 0 until the first start. The proof covers every N, W, AccW ≥ 1 with N·N·W < 2^31 and N·N·AccW < 2^31 and proves the
latency the submission declares; the score is taken at N = 4, W = 8, AccW = 32.
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. Every operator but run-time / % ** (banned in every task) is allowed, * included, so the task is the
schedule, which the score prices: how many multipliers, how many cycles and how short a cycle. Any
schedule is allowed: N² multiply-accumulate units at once, fewer units over more cycles, a systolic array.
The proved reference, N² multiply-accumulate units stepping through k, takes N cycles (4 at the
scoring point) for 20783.15 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 matmul #(parameter int N = 4, parameter int W = 8, parameter int AccW = 32) (
input logic clk, rst,
input logic start,
input logic [N*N*W-1:0] A, B,
output logic done,
output logic [N*N*AccW-1:0] C
);| port | direction | width (bits) |
|---|---|---|
clk | input | 1 |
rst | input | 1 |
start | input | 1 |
A | input | N*N*W |
B | input | N*N*W |
done | output | 1 |
C | output | N*N*AccW |
The trusted statement #
A proof must establish the task's trusted statement, the constant SvGolf.Tasks.Matmul.Statement of Spec.lean (module tasks.matmul.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, AccW) in range, they have all of the properties below. The last five 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 A and B are held from start until done rises. 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, AccW ≥ 1, and both port widths below 2^31.
The statement covers every matrix size N ≥ 1, entry width W ≥ 1 and accumulator width AccW ≥ 1, as long as both port widths, N·N·W and N·N·AccW bits, stay below 2^31: the parameters are 32-bit ints, and in that range a real SystemVerilog tool and Lean's model agree on every width and index. One proof covers all of them; the design is scored at N = 4, W = 8, AccW = 32.
/-- The parameters the statement covers: `N, W, AccW ≥ 1`, and both port widths below `2^31`. -/
def ParamsInRange (N W AccW : Nat) : Prop :=
1 ≤ N ∧ 1 ≤ W ∧ 1 ≤ AccW ∧ N * N * W < 2 ^ 31 ∧ N * N * AccW < 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 (1 bit each), A and B (N·N·W bits each) and exactly the output ports done (1 bit) and C (N·N·AccW 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. Matrices are flat, row-major, least significant entry first: entry (i, k) of A is A[(i·N+k)·W +: W].
/-- Exactly these ports, in this order, with these widths; no two declarations share a name. -/
def Interface (N W AccW : Nat) (d : Design2) : Prop :=
inputPorts d = [("clk", 1), ("rst", 1), ("start", 1), ("A", N * N * W), ("B", N * N * W)] ∧
outputPorts d = [("done", 1), ("C", N * N * AccW)] ∧ (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): done and C 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 done and C: 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): `done` and `C` 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 dDONE_KNOWNdone is a known bit at every cycle from the release.
As long as the environment follows the protocol, done is a known bit, 0 or 1, in every cycle from the release of the reset on. The environment decides when to start the next transaction by watching done, so done must mean something in every cycle; never x.
/-- `done` is a known bit at every cycle from the release. -/
def DoneKnown (N W AccW : Nat) (d : Design2) : Prop :=
UnderProtocol N W AccW d fun _ tr c1 h =>
∀ c, c1 ≤ c → c < h → ∃ b, TraceBit tr c "done" bDONE_LOW_BEFORE_FIRST_STARTdone is 0 at every cycle from the release up to the first start.
As long as the environment follows the protocol, done is 0 in every cycle after the release of the reset and before the first start. done = 1 claims a result, and before the first start there is none: a design that raises done too soon fails here, whatever C shows.
/-- `done` is 0 at every cycle from the release up to the first start. -/
def DoneLowBeforeFirstStart (N W AccW : Nat) (d : Design2) : Prop :=
UnderProtocol N W AccW d fun stim tr c1 h =>
∀ c, c1 ≤ c → c < h → (∀ s, c1 ≤ s → s ≤ c → ¬ StimBit stim s "start" true) →
TraceBit tr c "done" falseDONE_WITHIN_LATENCYAfter every start s, done is 1 at some cycle at most lat cycles later.
As long as the environment follows the protocol, after every start, done is 1 in some cycle at most lat cycles later, counting the cycle in which start is sampled as cycle 0. Finishing earlier is fine, and the number of cycles may depend on the data, as long as it never exceeds the declared latency.
/-- After every start `s`, `done` is 1 at some cycle at most `lat` cycles later. -/
def DoneWithinLatency (N W AccW lat : Nat) (d : Design2) : Prop :=
UnderProtocol N W AccW d fun stim tr c1 h =>
∀ s, c1 ≤ s → StimBit stim s "start" true → s + lat < h →
∃ c, s ≤ c ∧ c ≤ s + lat ∧ TraceBit tr c "done" trueDONE_HELDOnce done is 1 after a start, it stays 1 until the next start.
As long as the environment follows the protocol, once done has been 1 after a start, it stays 1 in every cycle until the next start. The result does not vanish: the environment may read it at any time before it starts the next transaction.
/-- Once `done` is 1 after a start, it stays 1 until the next start. -/
def DoneHeld (N W AccW : Nat) (d : Design2) : Prop :=
UnderProtocol N W AccW d fun stim tr c1 h =>
∀ s c, c < h → Finished stim tr c1 s c → TraceBit tr c "done" trueRESULT_IS_PRODUCTOnce done is 1 after a start with operands A and B, C holds the product A·B until the next start.
As long as the environment follows the protocol, in every cycle from the first done = 1 after a start until the next start, C holds the matrix product A·B of the operands driven when start was sampled. Entries of A and B are signed W-bit numbers, and each C[i][j] = Σ_k A[i][k]·B[k][j] is computed exactly and then wrapped to AccW bits (modulo 2^AccW, as a GPU accumulator does). Once done rises the environment may change A and B, so the design must keep the result, not recompute it from its inputs.
/-- Once `done` is 1 after a start with operands `A` and `B`, `C` holds the product `A·B` until
the next start. -/
def ResultIsProduct (N W AccW : Nat) (d : Design2) : Prop :=
UnderProtocol N W AccW d fun stim tr c1 h =>
∀ s c (A B : BitVec (N * N * W)), c < h → Finished stim tr c1 s c →
(∃ e, stim[s]? = some e ∧ HoldAll e [("A", N * N * W), ("B", N * N * W)] (A, B, ())) →
∃ st, tr[c]? = some st ∧ HoldAll st [("C", N * N * AccW)] (product N W AccW A B, ())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
productSpec.leanproduct N W AccW A B: the matrix product, each entry Σ_k A[i][k]·B[k][j] over signed W-bit entries, reduced modulo 2^AccW, packed flat and row-major.
/-- The product `C = A·B`, each entry reduced mod `2^AccW`, packed flat row-major. -/
def product (N W AccW : Nat) {n : Nat} (a b : BitVec n) : BitVec (N * N * AccW) :=
BitVec.ofNat (N * N * AccW)
((List.range (N * N)).foldl
(fun acc ij =>
acc + (BitVec.ofInt AccW (dot N W a b (ij / N) (ij % N))).toNat * 2 ^ (ij * AccW))
0)dotSpec.leandot N W A B i j: the exact integer Σ_{k<N} A[i][k]·B[k][j].
/-- `Σ_{k < N} A[i][k] · B[k][j]` in the integers. -/
def dot (N W : Nat) {n : Nat} (a b : BitVec n) (i j : Nat) : Int :=
(List.range N).foldl (fun acc k => acc + entry W a (i * N + k) * entry W b (k * N + j)) 0entrySpec.leanentry W x idx: entry idx of a flat vector of W-bit signed entries, as an integer.
/-- Entry `idx` of a flat vector of `W`-bit signed entries, as an integer. -/
def entry (W : Nat) {n : Nat} (x : BitVec n) (idx : Nat) : Int :=
(x.extractLsb' (idx * W) W).toIntStatementSpec.leanThe trusted challenge statement that these properties restate, part by part.
/-- **The challenge statement**, for the three-parameter design family `sem N W AccW` and the
latency function `lat N W AccW`. The checker's goal is `Statement design.sem lat ∧ lat 4 8 32 = n`
with the latency `lat` the proof file defines and the declared number `n` (C3). -/
abbrev Statement (sem : Nat → Nat → Nat → LeanModels.Sv.Design2) (lat : Nat → Nat → Nat → Nat) :
Prop :=
task.StatementV2 (fun p => sem p.1 p.2.1 p.2.2) (fun p => lat p.1 p.2.1 p.2.2)taskSpec.leanThe task as data: the parameter range, the port names and widths, and the function of a transaction.
/-- **The task** (C6). -/
def task : TxTask Params where
inRange p := 1 ≤ p.1 ∧ 1 ≤ p.2.1 ∧ 1 ≤ p.2.2 ∧
p.1 * p.1 * p.2.1 < 2 ^ 31 ∧ p.1 * p.1 * p.2.2 < 2 ^ 31
clk := "clk"
rst := "rst"
start := "start"
operands p := [("A", p.1 * p.1 * p.2.1), ("B", p.1 * p.1 * p.2.1)]
done := "done"
payload p := [("C", p.1 * p.1 * p.2.2)]
result p := fun (a, b, ()) => (product p.1 p.2.1 p.2.2 a b, ())Helpers in Properties.lean (proved, not trusted)
FinishedProperties.leanFinished stim tr c1 s c: s is a start after the release c1, done has been 1 in some cycle from s to c, and there is no later start up to c. These are the cycles in which the result must be shown.
/-- `Finished stim tr c1 s c`: `s` is a start after the release `c1`, `done` has been 1 at some
cycle from `s` to `c`, and there is no later start up to `c`. -/
def Finished (stim tr : List SvState) (c1 s c : Nat) : Prop :=
c1 ≤ s ∧ StimBit stim s "start" true ∧
(∃ c', s ≤ c' ∧ c' ≤ c ∧ TraceBit tr c' "done" true) ∧
∀ s', s < s' → s' ≤ c → ¬ StimBit stim s' "start" trueUnderProtocolProperties.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
(`TxTask.Rely`). -/
def UnderProtocol (N W AccW : 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, AccW) stim tr c1 h → G stim tr c1 hThe shared trusted base
TxTask.RelyTaskSeq.leanThe environment's side of the protocol, up to a horizon: start and the operands are driven with known values in every cycle, start is 1 only while the design is idle, and the operands of a start are held until the cycle in which done first rises.
/-- **The rely, up to the horizon `h`**: from the release `c1`, every entry
before `h` drives `start` and the operands with known values; `start` is 1
only when `Idle`; and the operands of a start are held until the cycle in
which `done` first rises. -/
def Rely (p : P) (stim tr : List SvState) (c1 h : Nat) : Prop :=
∀ c, c1 ≤ c → c < h →
(∃ b, StimBit stim c t.start b) ∧ (∃ x, t.OpsAt p stim c x) ∧
(StimBit stim c t.start true → t.Idle stim tr c1 c) ∧
∀ s x, c1 ≤ s → s ≤ c → StimBit stim s t.start true → t.OpsAt p stim s x →
(∀ c', s ≤ c' → c' < c → ¬ TraceBit tr c' t.done true) → t.OpsAt p stim c xTxTask.IdleTaskSeq.leanThe design is idle: every start since the release has already been answered by a cycle with done = 1.
/-- **C2, `Idle`**: from the release `c1`, every start before cycle `c` has
been followed, before `c`, by a cycle showing `done` = 1. -/
def Idle (stim tr : List SvState) (c1 c : Nat) : Prop :=
∀ s, c1 ≤ s → s < c → StimBit stim s t.start true →
∃ c', s ≤ c' ∧ c' < c ∧ TraceBit tr c' t.done trueTxTask.GuarTaskSeq.leanThe trusted guarantee, one conjunction; DONE_KNOWN, DONE_LOW_BEFORE_FIRST_START, DONE_WITHIN_LATENCY, DONE_HELD and RESULT_IS_PRODUCT are its parts.
/-- **The guarantee, up to the horizon `h`** (C3, C4, C5): `done` is known at
every cycle from the release. `done` = 1 claims a result; before the first start
there is none, so `done` is 0 at every cycle from the release up to the first
start (as `out_valid` in `StreamTask.Guar`; decided 2026-11-09). For every start
`s` with operands `x`: `done` rises at a cycle `≤ s + lat` (when that cycle is
before `h`), and from the first cycle it is 1 until the next start it stays 1
with the payload equal to `result x`. -/
def Guar (p : P) (lat : Nat) (stim tr : List SvState) (c1 h : Nat) : Prop :=
(∀ c, c1 ≤ c → c < h → ∃ b, TraceBit tr c t.done b) ∧
(∀ c, c1 ≤ c → c < h → (∀ s, c1 ≤ s → s ≤ c → ¬ StimBit stim s t.start true) →
TraceBit tr c t.done false) ∧
∀ s x, c1 ≤ s → s < h → StimBit stim s t.start true → t.OpsAt p stim s x →
(s + lat < h → ∃ c, s ≤ c ∧ c ≤ s + lat ∧ TraceBit tr c t.done true) ∧
∀ c, s ≤ c → c < h → (∃ c', s ≤ c' ∧ c' ≤ c ∧ TraceBit tr c' t.done true) →
(∀ s', s < s' → s' ≤ c → ¬ StimBit stim s' t.start true) →
∃ st, tr[c]? = some st ∧ BitIs st t.done true ∧ HoldAll st (t.payload p) (t.result p x)ReleasedTaskSeq.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.
- Refused before any proof is tried: 1 design (by stage:
source1) - Rejected at the proof: 6 designs (by stage:
solution6)
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.
async_reset.sv(attack, stopped atsource): The control process has an asynchronous reset: the reset is synchronous on this task, the statement refuses an asynchronous one, and the source check refuses the second event first.the design, with its comments left out
module matmul #(parameter int N = 4, parameter int W = 8, parameter int AccW = 32) ( input logic clk, rst, input logic start, input logic [N*N*W-1:0] A, B, output logic done, output logic [N*N*AccW-1:0] C ); logic [31:0] k; logic busy; logic [N*N*W-1:0] a_sh, b_sh; always_ff @(posedge clk or posedge rst) if (rst) begin busy <= 1'b0; done <= 1'b0; k <= 32'd0; end else if (start) begin busy <= 1'b1; done <= 1'b0; k <= 32'd0; a_sh <= A; b_sh <= B; end else if (busy) begin k <= k + 32'd1; a_sh <= a_sh >> W; b_sh <= b_sh >> (N*W); if (k == N - 1) begin busy <= 1'b0; done <= 1'b1; end end for (genvar i = 0; i < N; i++) begin : g_row for (genvar j = 0; j < N; j++) begin : g_col always_ff @(posedge clk) if (start) C[(i*N+j)*AccW +: AccW] <= '0; else if (busy) C[(i*N+j)*AccW +: AccW] <= C[(i*N+j)*AccW +: AccW] + {{AccW{a_sh[i*N*W + W - 1]}}, a_sh[i*N*W +: W]} * {{AccW{b_sh[j*W + W - 1]}}, b_sh[j*W +: W]}; end end 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.
acc_2w.sv(attack, stopped atsolution): The accumulator is only 2W bits wide and sign-extended to AccW bits: wrong when AccW > 2W and a dot product leaves the 2W-bit range.the design, with its comments left out
module matmul #(parameter int N = 4, parameter int W = 8, parameter int AccW = 32) ( input logic clk, rst, input logic start, input logic [N*N*W-1:0] A, B, output logic done, output logic [N*N*AccW-1:0] C ); logic [31:0] k; logic busy; logic [N*N*W-1:0] a_sh, b_sh; logic [N*N*2*W-1:0] acc; always_ff @(posedge clk) if (rst) begin busy <= 1'b0; done <= 1'b0; k <= 32'd0; end else if (start) begin busy <= 1'b1; done <= 1'b0; k <= 32'd0; a_sh <= A; b_sh <= B; end else if (busy) begin k <= k + 32'd1; a_sh <= a_sh >> W; b_sh <= b_sh >> (N*W); if (k == N - 1) begin busy <= 1'b0; done <= 1'b1; end end for (genvar i = 0; i < N; i++) begin : g_row for (genvar j = 0; j < N; j++) begin : g_col always_ff @(posedge clk) if (start) acc[(i*N+j)*2*W +: 2*W] <= '0; else if (busy) acc[(i*N+j)*2*W +: 2*W] <= acc[(i*N+j)*2*W +: 2*W] + {{2*W{a_sh[i*N*W + W - 1]}}, a_sh[i*N*W +: W]} * {{2*W{b_sh[j*W + W - 1]}}, b_sh[j*W +: W]}; assign C[(i*N+j)*AccW +: AccW] = {{AccW{acc[(i*N+j)*2*W + 2*W - 1]}}, acc[(i*N+j)*2*W +: 2*W]}; end end endmoduledone_early.sv(attack, stopped atsolution):donerises one step early (k == N - 2), whileCstill lacks the last product.the design, with its comments left out
module matmul #(parameter int N = 4, parameter int W = 8, parameter int AccW = 32) ( input logic clk, rst, input logic start, input logic [N*N*W-1:0] A, B, output logic done, output logic [N*N*AccW-1:0] C ); logic [31:0] k; logic busy; logic [N*N*W-1:0] a_sh, b_sh; always_ff @(posedge clk) if (rst) begin busy <= 1'b0; done <= 1'b0; k <= 32'd0; end else if (start) begin busy <= 1'b1; done <= 1'b0; k <= 32'd0; a_sh <= A; b_sh <= B; end else if (busy) begin k <= k + 32'd1; a_sh <= a_sh >> W; b_sh <= b_sh >> (N*W); if (k == N - 2) done <= 1'b1; if (k == N - 1) busy <= 1'b0; end for (genvar i = 0; i < N; i++) begin : g_row for (genvar j = 0; j < N; j++) begin : g_col always_ff @(posedge clk) if (start) C[(i*N+j)*AccW +: AccW] <= '0; else if (busy) C[(i*N+j)*AccW +: AccW] <= C[(i*N+j)*AccW +: AccW] + {{AccW{a_sh[i*N*W + W - 1]}}, a_sh[i*N*W +: W]} * {{AccW{b_sh[j*W + W - 1]}}, b_sh[j*W +: W]}; end end endmodulemissing_sign_ext.sv(attack, stopped atsolution): Each W × W product is sign-extended to 2W bits only, then zero-extended into the accumulator: wrong for a negative product when AccW > 2W.the design, with its comments left out
module matmul #(parameter int N = 4, parameter int W = 8, parameter int AccW = 32) ( input logic clk, rst, input logic start, input logic [N*N*W-1:0] A, B, output logic done, output logic [N*N*AccW-1:0] C ); logic [31:0] k; logic busy; logic [N*N*W-1:0] a_sh, b_sh; logic [N*N*2*W-1:0] p; always_ff @(posedge clk) if (rst) begin busy <= 1'b0; done <= 1'b0; k <= 32'd0; end else if (start) begin busy <= 1'b1; done <= 1'b0; k <= 32'd0; a_sh <= A; b_sh <= B; end else if (busy) begin k <= k + 32'd1; a_sh <= a_sh >> W; b_sh <= b_sh >> (N*W); if (k == N - 1) begin busy <= 1'b0; done <= 1'b1; end end for (genvar i = 0; i < N; i++) begin : g_row for (genvar j = 0; j < N; j++) begin : g_col assign p[(i*N+j)*2*W +: 2*W] = {{W{a_sh[i*N*W + W - 1]}}, a_sh[i*N*W +: W]} * {{W{b_sh[j*W + W - 1]}}, b_sh[j*W +: W]}; always_ff @(posedge clk) if (start) C[(i*N+j)*AccW +: AccW] <= '0; else if (busy) C[(i*N+j)*AccW +: AccW] <= C[(i*N+j)*AccW +: AccW] + p[(i*N+j)*2*W +: 2*W]; end end endmoduleno_clear_on_start.sv(attack, stopped atsolution): The accumulators are cleared by the reset but not bystart, so every transaction after the first adds onto the previous result.the design, with its comments left out
module matmul #(parameter int N = 4, parameter int W = 8, parameter int AccW = 32) ( input logic clk, rst, input logic start, input logic [N*N*W-1:0] A, B, output logic done, output logic [N*N*AccW-1:0] C ); logic [31:0] k; logic busy; logic [N*N*W-1:0] a_sh, b_sh; always_ff @(posedge clk) if (rst) begin busy <= 1'b0; done <= 1'b0; k <= 32'd0; end else if (start) begin busy <= 1'b1; done <= 1'b0; k <= 32'd0; a_sh <= A; b_sh <= B; end else if (busy) begin k <= k + 32'd1; a_sh <= a_sh >> W; b_sh <= b_sh >> (N*W); if (k == N - 1) begin busy <= 1'b0; done <= 1'b1; end end for (genvar i = 0; i < N; i++) begin : g_row for (genvar j = 0; j < N; j++) begin : g_col always_ff @(posedge clk) if (rst) C[(i*N+j)*AccW +: AccW] <= '0; else if (busy) C[(i*N+j)*AccW +: AccW] <= C[(i*N+j)*AccW +: AccW] + {{AccW{a_sh[i*N*W + W - 1]}}, a_sh[i*N*W +: W]} * {{AccW{b_sh[j*W + W - 1]}}, b_sh[j*W +: W]}; end end endmoduleoff_by_one_k.sv(attack, stopped atsolution): The step counter starts at 1, so the last product never reachesCanddonerises a cycle early.the design, with its comments left out
module matmul #(parameter int N = 4, parameter int W = 8, parameter int AccW = 32) ( input logic clk, rst, input logic start, input logic [N*N*W-1:0] A, B, output logic done, output logic [N*N*AccW-1:0] C ); logic [31:0] k; logic busy; logic [N*N*W-1:0] a_sh, b_sh; always_ff @(posedge clk) if (rst) begin busy <= 1'b0; done <= 1'b0; k <= 32'd0; end else if (start) begin busy <= 1'b1; done <= 1'b0; k <= 32'd1; a_sh <= A; b_sh <= B; end else if (busy) begin k <= k + 32'd1; a_sh <= a_sh >> W; b_sh <= b_sh >> (N*W); if (k == N - 1) begin busy <= 1'b0; done <= 1'b1; end end for (genvar i = 0; i < N; i++) begin : g_row for (genvar j = 0; j < N; j++) begin : g_col always_ff @(posedge clk) if (start) C[(i*N+j)*AccW +: AccW] <= '0; else if (busy) C[(i*N+j)*AccW +: AccW] <= C[(i*N+j)*AccW +: AccW] + {{AccW{a_sh[i*N*W + W - 1]}}, a_sh[i*N*W +: W]} * {{AccW{b_sh[j*W + W - 1]}}, b_sh[j*W +: W]}; end end endmoduleunsigned_product.sv(attack, stopped atsolution): The entries are multiplied as unsigned numbers (zero-extended part-selects): wrong for a negative entry when AccW > W ≥ 2.the design, with its comments left out
module matmul #(parameter int N = 4, parameter int W = 8, parameter int AccW = 32) ( input logic clk, rst, input logic start, input logic [N*N*W-1:0] A, B, output logic done, output logic [N*N*AccW-1:0] C ); logic [31:0] k; logic busy; logic [N*N*W-1:0] a_sh, b_sh; always_ff @(posedge clk) if (rst) begin busy <= 1'b0; done <= 1'b0; k <= 32'd0; end else if (start) begin busy <= 1'b1; done <= 1'b0; k <= 32'd0; a_sh <= A; b_sh <= B; end else if (busy) begin k <= k + 32'd1; a_sh <= a_sh >> W; b_sh <= b_sh >> (N*W); if (k == N - 1) begin busy <= 1'b0; done <= 1'b1; end end for (genvar i = 0; i < N; i++) begin : g_row for (genvar j = 0; j < N; j++) begin : g_col always_ff @(posedge clk) if (start) C[(i*N+j)*AccW +: AccW] <= '0; else if (busy) C[(i*N+j)*AccW +: AccW] <= C[(i*N+j)*AccW +: AccW] + a_sh[i*N*W +: W] * b_sh[j*W +: W]; end end endmodule
Spec.lean, verbatim (114 lines)
import SvGolf.TaskSeq
/-!
# Task `matmul`: N×N signed matrix multiplication, wrapping at the accumulator width
# (TRUSTED text, final, decided 2026-11-08, amended 2026-11-09; written 2026-11-04; contract C6 of `design/sequential/CONTRACT.md`)
Module (port order as written; `N`, `W`, `AccW` are the module's parameters, in this order):
```systemverilog
module matmul #(parameter int N = 4, parameter int W = 8, parameter int AccW = 32) (
input logic clk, rst,
input logic start,
input logic [N*N*W-1:0] A, B,
output logic done,
output logic [N*N*AccW-1:0] C
);
```
**Layout.** Flat, row-major, least significant entry first: entry `(i, k)` of `A` is
`A[(i*N+k)*W +: W]`, likewise `B`; entry `(i, j)` of `C` is `C[(i*N+j)*AccW +: AccW]`.
**Arithmetic (ruled 11/03).** The entries of `A` and `B` are SIGNED (two's complement,
`W` bits), products sign-extended; `C[i][j] = Σ_k A[i][k]·B[k][j]`, computed exactly in the
integers and then reduced mod `2^AccW` (wrap, as GPUs accumulate; order-independent, so every
summation order is legal). `C`'s entries are two's complement `AccW`-bit values.
**Protocol** (`TxTask`, `SvGolf/TaskSeq.lean`; C1 to C5): synchronous active-high reset `rst`;
claims from the cycle after its release; `start` a one-cycle pulse raised only when the design is
idle; `A` and `B` held from `start` until `done` rises; `done` rises within the declared latency
`lat` (cycles after the edge that samples `start`), then stays 1 with `C` = the product until the
next `start`. `done` is known in every cycle after the release, and 0 up to the first start
(`done` = 1 claims a result, and there is none before a start); `C` only where `done` = 1.
The declared latency `lat` is the submission's; the checker pins its value at the scoring point
`(4, 8, 32)` and the score uses it (C3, C7). Run-time `/ % **` are banned, as in every task (`task.json`).
**Range.** `N, W, AccW ≥ 1`, and the port widths `N·N·W` and `N·N·AccW` below `2^31`: the parameters
are `int`, and every width and index expression of the interface (`N*N*W-1`, `(i*N+k)*W`) 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, the `clmul` precedent).
**txtask-v2 (2026-11-05; final, decided 2026-11-08, amended 2026-11-09).** `Statement` is the v2
statement `TxTask.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: an unreset register is an
unknown 0/1 value, not ls-sv's `x` start); Moore outputs (`MooreOutDep`, rule 1, nothing exempt: no
combinational path from `rst`, `start`, `A` or `B` to `done` or `C`); 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.Matmul
open SvGolf
/-- The parameters `(N, W, AccW)`. -/
abbrev Params := Nat × Nat × Nat
/-- Entry `idx` of a flat vector of `W`-bit signed entries, as an integer. -/
def entry (W : Nat) {n : Nat} (x : BitVec n) (idx : Nat) : Int :=
(x.extractLsb' (idx * W) W).toInt
/-- `Σ_{k < N} A[i][k] · B[k][j]` in the integers. -/
def dot (N W : Nat) {n : Nat} (a b : BitVec n) (i j : Nat) : Int :=
(List.range N).foldl (fun acc k => acc + entry W a (i * N + k) * entry W b (k * N + j)) 0
/-- The product `C = A·B`, each entry reduced mod `2^AccW`, packed flat row-major. -/
def product (N W AccW : Nat) {n : Nat} (a b : BitVec n) : BitVec (N * N * AccW) :=
BitVec.ofNat (N * N * AccW)
((List.range (N * N)).foldl
(fun acc ij =>
acc + (BitVec.ofInt AccW (dot N W a b (ij / N) (ij % N))).toNat * 2 ^ (ij * AccW))
0)
/-- **The task** (C6). -/
def task : TxTask Params where
inRange p := 1 ≤ p.1 ∧ 1 ≤ p.2.1 ∧ 1 ≤ p.2.2 ∧
p.1 * p.1 * p.2.1 < 2 ^ 31 ∧ p.1 * p.1 * p.2.2 < 2 ^ 31
clk := "clk"
rst := "rst"
start := "start"
operands p := [("A", p.1 * p.1 * p.2.1), ("B", p.1 * p.1 * p.2.1)]
done := "done"
payload p := [("C", p.1 * p.1 * p.2.2)]
result p := fun (a, b, ()) => (product p.1 p.2.1 p.2.2 a b, ())
/-- **The challenge statement**, for the three-parameter design family `sem N W AccW` and the
latency function `lat N W AccW`. The checker's goal is `Statement design.sem lat ∧ lat 4 8 32 = n`
with the latency `lat` the proof file defines and the declared number `n` (C3). -/
abbrev Statement (sem : Nat → Nat → Nat → LeanModels.Sv.Design2) (lat : Nat → Nat → Nat → Nat) :
Prop :=
task.StatementV2 (fun p => sem p.1 p.2.1 p.2.2) (fun p => lat p.1 p.2.1 p.2.2)
/-! ## Sanity checks on the statement's data (proved; they add nothing to the trusted base) -/
/-- A 2×2 product with a negative entry: `A = [[-1, 2], [3, 4]]`, `B = [[5, 6], [7, 0]]` (W = 4)
gives `C = [[9, -6], [43, 18]]` (AccW = 8; `-6` is `0xFA`). -/
example : product 2 4 8 (0x432F#16) (0x0765#16) = 0x122BFA09#32 := by decide
/-- Wrap: `[[-8]]·[[-8]] = 64`, which is `0x40` in 8 bits and `0` in 6 bits (W = 4). -/
example : product 1 4 8 (0x8#4) (0x8#4) = 0x40#8 ∧ product 1 4 6 (0x8#4) (0x8#4) = 0#6 := by
decide
/-- The off-points the extractor probes and the scoring point are in range (C6). -/
example : task.inRange (1, 1, 1) ∧ task.inRange (2, 3, 5) ∧ task.inRange (3, 4, 8) ∧
task.inRange (4, 8, 32) := by
simp [task]
/-- The port names are distinct. -/
example (p : Params) :
(["clk", "rst", "start"] ++ (task.operands p).map Prod.fst).Nodup ∧
task.done ∉ (task.payload p).map Prod.fst := by
simp [task]
end SvGolf.Tasks.Matmul
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.matmul.Spec | task statement | 114 | 2e8ec32ea060 |
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.