Record 1ffe71156e90
Area
241 920.77µm²
241 920.7712 µm² exactly
Beats 0% · matches 0%
0 of 1 other player beaten, 0 equal.
Performance · time
9 410ps
1 cycle × 9 410 ps; the period is the latest arrival at an output, or at a flip-flop's D plus setup, ideal clock
Beats 100% · matches 0%
1 of 1 other player beaten, 0 equal.
Score · area × time
2 276 474µm²·ns
241 920.7712 µm² × 9 410 ps
Beats 100% · matches 0%
1 of 1 other player beaten, 0 equal.
- vs par
- −39.0% bettermatmul_g1 · 3 729 636 µm²·ns
- on this page
- best of 5 designs
- board
- layer 1, rank 1 of 4 by score
- Pareto
- on the front
On the board, of 1 other player: 0 beaten, 0 level, 1 trade off, 0 ahead.
how these numbers are measured and compared
Area: sky130 A0′, the repaired netlist.
Area, time and score are ranked (the leaderboard orders by the score); a number tagged extra, derived or research is shown, never ranked. Fmax-equivalent = 10⁶ / time in ps, for comparison with sites that quote Fmax.
The field is the ranked records of matmul's other players at this revision (1 player, 2 records), never the references, the baselines or a rejected design; each player counts once, at their best, and ties are exact at the recorded unit; advisory, recomputed at every build, never stored in a record and never used by the ranking.
On the board, another player is beaten when this design dominates every record of theirs, level when none of theirs is better and one is identical (the rest dominated), ahead when a record of theirs dominates this design, and a trade off otherwise (one of theirs better on one axis, worse on the other).
“On this page” and “Pareto” compare with the 5 designs of the area-time plot (1 reference and 4 submissions): not a ranking.
Area
3 661.548µm²
3 661.54830 µm² exactly
Beats 0% · matches 0%
0 of 1 other player beaten, 0 equal.
Performance · time
1 621ps
1 cycle × 1 621 ps; the period is the latest arrival at an output, or at a flip-flop's D plus setup, ideal clock
Beats 100% · matches 0%
1 of 1 other player beaten, 0 equal.
Score · area × time
5935µm²·ns
3 661.54830 µm² × 1 621 ps
Beats 100% · matches 0%
1 of 1 other player beaten, 0 equal.
- vs par
- −27.8% bettermatmul_g1 · 8217 µm²·ns
- on this page
- best of 5 designs
- board
- layer 1, rank 1 of 4 by score
- Pareto
- on the front
On the board, of 1 other player: 0 beaten, 0 level, 1 trade off, 0 ahead.
how these numbers are measured and compared
Area: ASAP7 A0′ (asap7_a0_v1), the same scored netlist, each gate its least-area ASAP7 build, repaired by buffering. ASAP7: a predictive 7 nm library, never fabricated; no wires, like sky130's score (how ASAP7 is scored, and its quirks).
Area, time and score order this view's board (by the score); the records' ranked score is sky130's, the default view; a number tagged extra, derived or research is shown, never ranked. Fmax-equivalent = 10⁶ / time in ps, for comparison with sites that quote Fmax.
The field is the ranked records of matmul's other players at this revision (1 player, 2 records), never the references, the baselines or a rejected design; each player counts once, at their best, and ties are exact at the recorded unit; advisory, recomputed at every build, never stored in a record and never used by the ranking.
On the board, another player is beaten when this design dominates every record of theirs, level when none of theirs is better and one is identical (the rest dominated), ahead when a record of theirs dominates this design, and a trade off otherwise (one of theirs better on one axis, worse on the other).
“On this page” and “Pareto” compare with the 5 designs of the area-time plot (1 reference and 4 submissions): not a ranking.
The sources
// matmul, G4: four multiply-accumulate steps per PE per cycle.
// `start` captures A and B (zero-extended into registers with three spare entries / rows, so the
// four static windows below stay in range for every N); every busy cycle adds
// A[i][k..k+3] . B[k..k+3][j] (entries past N read as 0)
// to C[i][j] and shifts the captured operands by four entries (A) / four rows (B). `done` rises
// after ceil(N/4) busy cycles: latency (N+3)/4 = 1 at the scoring point N = 4.
// The shifts are written as two shifts of half the amount so that no shift amount can wrap at 32 bits.
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+3)*W-1:0] a_sh;
logic [(N*N+3*N)*W-1:0] 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'd4;
a_sh <= (a_sh >> (2*W)) >> (2*W);
b_sh <= (b_sh >> (2*N*W)) >> (2*N*W);
if (k + 32'd4 >= N) 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]}
+ {{AccW{a_sh[(i*N+1)*W + W - 1]}}, a_sh[(i*N+1)*W +: W]}
* {{AccW{b_sh[(N+j)*W + W - 1]}}, b_sh[(N+j)*W +: W]}
+ {{AccW{a_sh[(i*N+2)*W + W - 1]}}, a_sh[(i*N+2)*W +: W]}
* {{AccW{b_sh[(2*N+j)*W + W - 1]}}, b_sh[(2*N+j)*W +: W]}
+ {{AccW{a_sh[(i*N+3)*W + W - 1]}}, a_sh[(i*N+3)*W +: W]}
* {{AccW{b_sh[(3*N+j)*W + W - 1]}}, b_sh[(3*N+j)*W +: W]};
end
end
endmodule
Sol.lean (855 code lines)
-- svgolf-latency: 1
import Submission.Design
import LeanModels.Sv.Gen2
import LeanModels.Sv.Hier2Gen
import LeanModels.Sv.SeqProfile
import LeanModels.Sv.SeqFamily
import LeanModels.Sv.Known2
import LeanModels.Sv.Lib.BitVecArith
import LeanModels.Sv.CombViewArith
import LeanModels.Sv.Mono2
import SvGolf.SeqV2
/-!
# Reference solution for task `matmul`, G1: `N²` multiply-accumulate units and one step counter
On `start`, `reference/matmul_g1.sv` captures `A`, `B` into shift registers and clears `C`; for `N`
busy cycles PE `(i, j)` adds `A[i][k]·B[k][j]` (sign-extended, mod `2^AccW`); `done` shows in the
snapshot `N` cycles after the start's (`lat = N`). Proved at every in-range `(N, W, AccW)`:
1. **Agreement** (`mm_sem_eq`): the loaded family `design.sem N W AccW` is the hand-built `mmD`.
2. **G0's clock step** (`mm_profile`, `mm_cycle`): one cycle of the loaded design, from any state
of its shape, any schedule, is the evaluated commit `mmNext`.
3. **The invariant on `k`** (`pe_step`, `next_step`): busy cycle `t` turns every accumulator's
`Σ_{k' < t} A[i][k']·B[k'][j]` into the sum to `t + 1`, mod `2^AccW` (`accC`).
4. **The spec's arithmetic** (`product_eq`, `accC_N`): `product` is `accC` at `t = N`.
5. **The protocol** (`model`): the phases of `TxTask.satisfiedAt_of_phases`.
6. **txtask-v2** (section 8): rule 2 (`mm_powerUp`: every power-up state has the model's shape, so
the claim and termination hold from every power-up), rule 1 (`mm_moore`: every process an
`always_ff`, so no combinational dependency) and rule 3 (`mm_xsafe`), then `solution`.
The design-independent lemmas are in the library: the packed view and its commit (`packBV`,
`LVec.insertEntries_ofBitVec`) in `LeanModels.Sv.SeqFamily`, the clock step (`ffDisjB_of_pairwise`,
`flatMap_ffUpd`) in `LeanModels.Sv.SeqProfile`, the window arithmetic and the datapath's `BitVec` facts in
`LeanModels.Sv.Lib.NatArith` and `LeanModels.Sv.Lib.BitVecArith`.
-/
namespace Submission
open LeanModels.Sv SvGolf
/-- The latency: `done` shows `ceil(N/4)` snapshots after the start's (1 at the scoring point). -/
def lat (N _W _AccW : Nat) : Nat := (N + 3) / 4
/-! ## 0. Small `BitVec` facts for the four-step datapath -/
theorem setWidth_signExtend_mul4 {m n w : Nat} (h : m ≤ n) (a b : BitVec w) :
(a.signExtend n * b.signExtend n).setWidth m = BitVec.ofInt m (a.toInt * b.toInt) := by
rw [BitVec.setWidth_mul _ _ h, Lib.setWidth_signExtend_of_le _ h, Lib.setWidth_signExtend_of_le _ h,
BitVec.ofInt_mul]
rfl
/-- Four multiply-accumulates at a wider width, truncated back (`Lib.setWidth_add_mul_signExtend` four times). -/
theorem setWidth_add4_mul_signExtend {m n w : Nat} (h : m ≤ n) (x : BitVec m)
(a0 b0 a1 b1 a2 b2 a3 b3 : BitVec w) :
(x.setWidth n + a0.signExtend n * b0.signExtend n + a1.signExtend n * b1.signExtend n
+ a2.signExtend n * b2.signExtend n + a3.signExtend n * b3.signExtend n).setWidth m =
x + BitVec.ofInt m (a0.toInt * b0.toInt) + BitVec.ofInt m (a1.toInt * b1.toInt)
+ BitVec.ofInt m (a2.toInt * b2.toInt) + BitVec.ofInt m (a3.toInt * b3.toInt) := by
rw [BitVec.setWidth_add _ _ h, BitVec.setWidth_add _ _ h, BitVec.setWidth_add _ _ h,
BitVec.setWidth_add _ _ h, BitVec.setWidth_setWidth_of_le _ h, BitVec.setWidth_eq,
setWidth_signExtend_mul4 h, setWidth_signExtend_mul4 h, setWidth_signExtend_mul4 h,
setWidth_signExtend_mul4 h]
/-- Zero-extending first changes no part select. -/
theorem extractLsb'_setWidth_of_le {n n' : Nat} (h : n ≤ n') (x : BitVec n) (s l : Nat) :
(x.setWidth n').extractLsb' s l = x.extractLsb' s l := by
apply BitVec.eq_of_getLsbD_eq; intro i hi
by_cases hs : s + i < n
· simp [BitVec.getLsbD_extractLsb', BitVec.getLsbD_setWidth, hi, hs, show s + i < n' by omega]
· simp [BitVec.getLsbD_extractLsb', BitVec.getLsbD_setWidth, hi, hs, BitVec.getLsbD_of_ge x (s + i) (by omega)]
/-- The four-step counter test: `t < N < 2^31`, `t + 4` at 32 bits against `N`. -/
theorem ge_four_iff {t N : Nat} (ht : t < N) (hN : N < 2 ^ 31) :
BitVec.ofNat 32 N ≤ BitVec.ofNat 32 t + 4#32 ↔ N ≤ t + 4 := by
rw [BitVec.le_def, BitVec.toNat_add, BitVec.toNat_ofNat, BitVec.toNat_ofNat, BitVec.toNat_ofNat]
simp only [Nat.reducePow] at hN ⊢
omega
/-- The four `a_sh` windows `[(i·N + m)·W +: W]`, `m ≤ 3`, lie inside the `(N·N + 3)·W`-bit register. -/
theorem winA_le {N W i : Nat} (m : Nat) (hm : m ≤ 3) (hi : i < N) : (i * N + m) * W + W ≤ (N * N + 3) * W := by
have h1 : (i + 1) * N ≤ N * N := Nat.mul_le_mul_right N (by omega)
rw [Nat.add_mul, Nat.one_mul] at h1
have h : i * N + m + 1 ≤ N * N + 3 := by omega
have h2 := Nat.mul_le_mul_right W h
rw [Nat.add_mul (i * N + m) 1 W, Nat.one_mul] at h2
exact h2
/-- The four `b_sh` windows `[(m·N + j)·W +: W]`, `m ≤ 3`, lie inside the `(N·N + 3·N)·W`-bit register. -/
theorem winB_le {N W j : Nat} (m : Nat) (hm : m ≤ 3) (hj : j < N) :
(m * N + j) * W + W ≤ (N * N + 3 * N) * W := by
have h1 : m * N ≤ 3 * N := Nat.mul_le_mul_right N hm
have h3 : N ≤ N * N := Nat.le_mul_self N
have h : m * N + j + 1 ≤ N * N + 3 * N := by omega
have h2 := Nat.mul_le_mul_right W h
rw [Nat.add_mul (m * N + j) 1 W, Nat.one_mul] at h2
exact h2
/-! ## 1. Agreement: the stages of `instantiate2` -/
/-- A 32-bit constant. -/
abbrev c32 (v : Int) : Expr2 := .lit (LVec.ofInt 32 v)
/-- `{{AccW{x[lo+W-1]}}, x[lo +: W]}`: entry `x[lo +: W]` sign-extended by `AccW` bits. -/
def sext (x : String) (AccW lo W : Nat) : Expr2 :=
.concat #[.repl AccW (.concat #[.slice (.ident x) (lo + W - 1) 1]), .slice (.ident x) lo W]
/-- One product of the busy sum: `a_sh[la +: W]` times `b_sh[lb +: W]`, both sign-extended by `AccW` bits. -/
def mulT (AccW W la lb : Nat) : Expr2 :=
.binary .mul (sext "a_sh" AccW la W) (sext "b_sh" AccW lb W)
/-- The PE at `(i, j)`: clear on `start`, else add four products `A[i][k+m]·B[k+m][j]` (`m < 4`) while `busy`. -/
def mmPE (N W AccW i j : Nat) : Process2 :=
.alwaysFF "clk" (.ifStmt (.ident "start")
(.nbaAssign (.slice "C" ((i * N + j) * AccW) AccW) (.lit (LVec.replicate AccW .l0)))
(some (.ifStmt (.ident "busy")
(.nbaAssign (.slice "C" ((i * N + j) * AccW) AccW)
(.resize AccW (.binary .add (.binary .add (.binary .add (.binary .add
(.resize (AccW + W) (.slice (.ident "C") ((i * N + j) * AccW) AccW))
(mulT AccW W (i * N * W) (j * W)))
(mulT AccW W ((i * N + 1) * W) ((N + j) * W)))
(mulT AccW W ((i * N + 2) * W) ((2 * N + j) * W)))
(mulT AccW W ((i * N + 3) * W) ((3 * N + j) * W)))))
none)))
/-- A nonblocking write of a constant. -/
abbrev nb (n : String) (v : LVec) : Stmt2 := .nbaAssign (.ident n) (.lit v)
/-- A shift amount `2·N·W` / `2·W`, as the instantiation writes it. -/
abbrev amtA (W : Nat) : Expr2 := .binary .mul (c32 2) (c32 W)
abbrev amtB (N W : Nat) : Expr2 := .binary .mul (.binary .mul (c32 2) (c32 N)) (c32 W)
/-- The control: `busy`, `done`, the step counter `k` (steps of four), the operand shift registers. -/
def mmCtrl (N W : Nat) : Process2 :=
.alwaysFF "clk" (.ifStmt (.ident "rst")
(.block #[nb "busy" ⟨#[.l0]⟩, nb "done" ⟨#[.l0]⟩, nb "k" (LVec.replicate 32 .l0)])
(some (.ifStmt (.ident "start")
(.block #[nb "busy" ⟨#[.l1]⟩, nb "done" ⟨#[.l0]⟩, nb "k" (LVec.replicate 32 .l0),
.nbaAssign (.ident "a_sh") (.resize ((N * N + 3) * W) (.ident "A")),
.nbaAssign (.ident "b_sh") (.resize ((N * N + 3 * N) * W) (.ident "B"))])
(some (.ifStmt (.ident "busy")
(.block #[.nbaAssign (.ident "k") (.binary .add (.ident "k") (c32 4)),
.nbaAssign (.ident "a_sh")
(.binary (.shr false) (.binary (.shr false) (.ident "a_sh") (amtA W)) (amtA W)),
.nbaAssign (.ident "b_sh")
(.binary (.shr false) (.binary (.shr false) (.ident "b_sh") (amtB N W)) (amtB N W)),
.ifStmt (.binary .ge (.binary .add (.ident "k") (c32 4)) (c32 N))
(.block #[nb "busy" ⟨#[.l0]⟩, nb "done" ⟨#[.l1]⟩]) none])
none)))))
/-- The declarations: `A`/`B` at `wa` bits, `a_sh` at `wp`, `b_sh` at `wq`, `C` at `wc`. -/
def mmDecls (wa wp wq wc : Nat) : List Decl :=
[{ name := "clk", width := 1, isInput := true }, { name := "rst", width := 1, isInput := true },
{ name := "start", width := 1, isInput := true }, { name := "A", width := wa, isInput := true },
{ name := "B", width := wa, isInput := true }, { name := "done", width := 1, isOutput := true },
{ name := "C", width := wc, isOutput := true }, { name := "k", width := 32 },
{ name := "busy", width := 1 }, { name := "a_sh", width := wp }, { name := "b_sh", width := wq }]
/-- The PEs, row-major. -/
def mmPEs (N W AccW : Nat) : List Process2 :=
(List.range N).flatMap fun i => (List.range N).map fun j => mmPE N W AccW i j
def mmD (N W AccW : Nat) : Design2 where
name := "matmul"
decls := (mmDecls (N * N * W) ((N * N + 3) * W) ((N * N + 3 * N) * W) (N * N * AccW)).toArray
processes := (mmCtrl N W :: mmPEs N W AccW).toArray
/-! ### The stages of `instantiate2` -/
def mmScope (N W AccW : Int) : IEnv :=
{ vals := [("N", N), ("W", W), ("AccW", AccW)], pwidths := [("N", 32), ("W", 32), ("AccW", 32)] }
def mmEnv (N W AccW : Int) : IEnv :=
{ mmScope N W AccW with dims := dimTable (mmScope N W AccW) design.pdesign.decls }
theorem mm_paramEnv (N W AccW : Int) :
design.pdesign.paramEnv2 [N, W, AccW] = .ok (mmEnv N W AccW) := rfl
def mmElems : List (String × Nat) :=
["clk", "rst", "start", "A", "B", "done", "C", "k", "busy", "a_sh", "b_sh"].map (·, 1)
theorem mm_decls (N W AccW : Int) :
buildDecls (mmEnv N W AccW) design.pdesign.decls =
.ok (mmDecls ((N * N * W - 1).natAbs + 1) (((N * N + 3) * W - 1).natAbs + 1)
(((N * N + 3 * N) * W - 1).natAbs + 1) ((N * N * AccW - 1).natAbs + 1)) := by
simp [design.pdesign, buildDecls, instDecl, PType.width_packed_cons, PDim.width, evalInt,
mmEnv, mmScope, List.lookup, mmDecls, Gen2.except_ok_bind]
theorem mm_elems (N W AccW : Int) : buildElems (mmEnv N W AccW) design.pdesign.decls = .ok mmElems := by
simp [design.pdesign, buildElems, PType.width_packed_cons, PDim.width, evalInt, mmEnv,
mmScope, List.lookup, mmElems, Gen2.except_ok_bind]
/-- The instantiation environment. -/
abbrev mmE (N W AccW : Nat) : IEnv :=
(mmEnv N W AccW).withDecls (mmDecls (N * N * W) ((N * N + 3) * W) ((N * N + 3 * N) * W) (N * N * AccW))
theorem mm_ctrl (N W AccW : Nat) :
design.pdesign.processes.map (instProcess2 (mmE N W AccW) mmElems) = [mmCtrl N W] := by
sv2_inst_simp [instStmt2, instStmts2, design.pdesign, mmEnv, mmScope, mmDecls, mmElems, mmCtrl,
dimTable, dimTableStep, PType.width_packed_cons, PDim.width, ctxOpWidth2, rigidMax2,
PExpr.ctxPropagates, PExpr.constLeaf, PExpr.negGenvarWidened, LVec.width, divOperandWidth2,
instDivOperand2, fitDivOperand2]
try and_intros
all_goals first | rfl | decide
/-- The PE template (the process inside `g_row`/`g_col`). -/
def peT : PProcess :=
match design.pdesign.generates with
| [.genFor _ _ _ _ _ _ [.genFor _ _ _ _ _ _ [.process p]]] => p
| _ => .alwaysFF "" (.block [])
/-- The `sv2_inst_simp` set of the PE template (with the window facts in scope). `instExpr2` tries
to constant-fold the `*` of the two sign-extending concatenations, and the fold must reduce to its
error; `sv2_inst_simp`'s default set carries the constant folder (`ctype`/`evalAt` and their
workers), so of the evaluators only `evalSized`, which is outside that set, is named here. -/
macro "pe_simp" : tactic => `(tactic| sv2_inst_simp [peT, evalSized, Gen2.except_pure,
Lib.min_add_sub_one_self, Lib.max_add_sub_one_self, Lib.add_sub_one_sub_add_one, instStmt2, design.pdesign,
mmEnv, mmScope, mmDecls, mmElems, mmPE, mulT, sext,
dimTable, dimTableStep, PType.width_packed_cons, PDim.width, ctxOpWidth2, rigidMax2,
PExpr.constLeaf, PExpr.negGenvarWidened, LVec.width, *])
theorem pe_inst (N W AccW i j : Nat) (hN : 1 ≤ N) (hW : 1 ≤ W) (hA : 1 ≤ AccW) (hi : i < N)
(hj : j < N) :
instProcess2 (((mmE N W AccW).bindVal "i" i).bindVal "j" j) mmElems peT =
mmPE N W AccW i j := by
have pW := Nat.mul_pos (Nat.mul_pos hN hN) hW
have pA := Nat.mul_pos (Nat.mul_pos hN hN) hA
have hW0 : (0 : Int) < W := by omega
have hA0 : (0 : Int) < AccW := by omega
have hNW : (1 : Int) ≤ N * N * W := by rw [← Int.natCast_mul, ← Int.natCast_mul]; omega
have hNA : (1 : Int) ≤ N * N * AccW := by rw [← Int.natCast_mul, ← Int.natCast_mul]; omega
have hcA : ((N : Int) * N * W - 1).natAbs + 1 = N * N * W := by
rw [← Int.natCast_mul, ← Int.natCast_mul]; exact Lib.natAbs_sub_one_add_one pW
have hcC : ((N : Int) * N * AccW - 1).natAbs + 1 = N * N * AccW := by
rw [← Int.natCast_mul, ← Int.natCast_mul]; exact Lib.natAbs_sub_one_add_one pA
have pP : 0 < (N * N + 3) * W := Nat.mul_pos (by omega) hW
have pQ : 0 < (N * N + 3 * N) * W := Nat.mul_pos (by omega) hW
have hcP : (((N : Int) * N + 3) * W - 1).natAbs + 1 = (N * N + 3) * W := by
have := Lib.natAbs_sub_one_add_one pP
first | (push_cast at this; exact this) | simpa using this | exact_mod_cast this
have hcQ : (((N : Int) * N + 3 * N) * W - 1).natAbs + 1 = (N * N + 3 * N) * W := by
have := Lib.natAbs_sub_one_add_one pQ
first | (push_cast at this; exact this) | simpa using this | exact_mod_cast this
have hNP : (1 : Int) ≤ (N * N + 3) * W := by
have h : 1 ≤ (N * N + 3) * W := pP
exact_mod_cast h
have hNQ : (1 : Int) ≤ (N * N + 3 * N) * W := by
have h : 1 ≤ (N * N + 3 * N) * W := pQ
exact_mod_cast h
have hC := Lib.window_bounds_of_natCast (((i : Int) * N + j) * AccW) (N * N * AccW)
(Lib.rowMajor_window_le (w := AccW) hi hj) hA (by simp) (by simp)
have ha0 := Lib.window_bounds_of_natCast ((i : Int) * N * W) ((N * N + 3) * W)
(winA_le (N := N) (W := W) 0 (by omega) hi) hW (by simp) (by simp)
have ha1 := Lib.window_bounds_of_natCast (((i : Int) * N + 1) * W) ((N * N + 3) * W)
(winA_le (N := N) (W := W) 1 (by omega) hi) hW (by simp) (by simp)
have ha2 := Lib.window_bounds_of_natCast (((i : Int) * N + 2) * W) ((N * N + 3) * W)
(winA_le (N := N) (W := W) 2 (by omega) hi) hW (by simp) (by simp)
have ha3 := Lib.window_bounds_of_natCast (((i : Int) * N + 3) * W) ((N * N + 3) * W)
(winA_le (N := N) (W := W) 3 (by omega) hi) hW (by simp) (by simp)
have hb0 := Lib.window_bounds_of_natCast ((j : Int) * W) ((N * N + 3 * N) * W)
(winB_le (N := N) (W := W) 0 (by omega) hj) hW (by simp) (by simp)
have hb1 := Lib.window_bounds_of_natCast (((N : Int) + j) * W) ((N * N + 3 * N) * W)
(winB_le (N := N) (W := W) 1 (by omega) hj) hW (by simp) (by simp)
have hb2 := Lib.window_bounds_of_natCast ((2 * (N : Int) + j) * W) ((N * N + 3 * N) * W)
(winB_le (N := N) (W := W) 2 (by omega) hj) hW (by simp) (by simp)
have hb3 := Lib.window_bounds_of_natCast ((3 * (N : Int) + j) * W) ((N * N + 3 * N) * W)
(winB_le (N := N) (W := W) 3 (by omega) hj) hW (by simp) (by simp)
have hA1 : 0 < AccW := hA
have hW1 : 0 < W := hW
have hWne : ¬ W = 0 := by omega
have hmode : (PartMode.up == PartMode.range) = false := rfl
have hlt1 : ¬ AccW + W < AccW := by omega
have hle1 : ¬ AccW + W ≤ AccW := by omega
pe_simp
-- `simp` cannot use `instExpr2`'s `resize` equation (measured): unfold it by hand.
repeat (unfold instExpr2; pe_simp)
/-- `for (g = 0; g < N; g++) body`. -/
def genN (lbl g : String) (body : List GenItem) : GenItem :=
.genFor (some lbl) g (.int 0) (.binary .slt (.genvarRef g) (.paramRef "N" none))
(.unary .inc (.genvarRef g)) (some 4) body
theorem mm_gens_src : design.pdesign.generates = [genN "g_row" "i" [genN "g_col" "j" ([peT].map .process)]] :=
rfl
/-- **The nested generate expands to the row-major PEs** (`mul2d_pp_expand`'s route). -/
theorem mm_gens (N W AccW F : Nat) (hN : 1 ≤ N) (hW : 1 ≤ W) (hA : 1 ≤ AccW) (hF : N < F) :
expandList2 (mmE N W AccW) mmElems F design.pdesign.generates = .ok (mmPEs N W AccW) := by
have hNv : (mmE N W AccW).vals.lookup "N" = some (N : Int) := rfl
rw [mm_gens_src]
apply expandList2_singleton_ok
rw [genN, GenItem.expand2_genFor_ascending_flatMap (N := (N : Int))
(f := fun v => (intRange 0 N).flatMap fun w =>
[peT].map (instProcess2 (((mmE N W AccW).bindVal "i" v).bindVal "j" w) mmElems))
(evalInt_int 0) (fun _ => isInt_paramRef_of (by rw [vals_lookup_bindVal_ne _ (by decide), hNv]))
(fun _ => evalInt_paramRef_of (by rw [vals_lookup_bindVal_ne _ (by decide), hNv])) (by simpa using hF)]
· simp only [intRange_zero, List.flatMap_map, Int.sub_zero, Int.toNat_natCast, List.map_cons,
List.map_nil, Gen2.flatMap_singleton_map, mmPEs]
congr 1
simp only [List.flatMap_def]
congr 1
refine List.map_congr_left fun i hi => List.map_congr_left fun j hj => ?_
exact pe_inst N W AccW i j hN hW hA (List.mem_range.mp hi) (List.mem_range.mp hj)
· intro v _ _
apply expandList2_singleton_ok
have hv : ((mmE N W AccW).bindVal "i" v).vals.lookup "N" = some (N : Int) := by
simp [IEnv.bindVal, List.lookup, hNv]
rw [genN, GenItem.expand2_genFor_ascending_processes (N := (N : Int)) (evalInt_int 0)
(fun _ => isInt_paramRef_of (by rw [vals_lookup_bindVal_ne _ (by decide), hv]))
(fun _ => evalInt_paramRef_of (by rw [vals_lookup_bindVal_ne _ (by decide), hv]))
(by simpa using hF)]
simp
theorem mm_instantiate2 (N W AccW : Nat) (hN : 1 ≤ N) (hW : 1 ≤ W) (hA : 1 ≤ AccW) :
design.pdesign.instantiate2 [(N : Int), W, AccW] (PDesign.surfaceFuel [(N : Int), W, AccW]) =
.ok (design.pdesign.assemble2 (mmEnv N W AccW)
(mmDecls (N * N * W) ((N * N + 3) * W) ((N * N + 3 * N) * W) (N * N * AccW)) mmElems
(mmPEs N W AccW)) := by
have hd := mm_decls N W AccW
have pP : 0 < (N * N + 3) * W := Nat.mul_pos (by omega) hW
have pQ : 0 < (N * N + 3 * N) * W := Nat.mul_pos (by omega) hW
have eP : (((N : Int) * N + 3) * W - 1).natAbs + 1 = (N * N + 3) * W := by
have := Lib.natAbs_sub_one_add_one pP
first | (push_cast at this; exact this) | simpa using this | exact_mod_cast this
have eQ : (((N : Int) * N + 3 * N) * W - 1).natAbs + 1 = (N * N + 3 * N) * W := by
have := Lib.natAbs_sub_one_add_one pQ
first | (push_cast at this; exact this) | simpa using this | exact_mod_cast this
rw [eP, eQ] at hd
rw [← Int.natCast_mul N N, ← Int.natCast_mul (N * N) W,
Lib.natAbs_sub_one_add_one (Nat.mul_pos (Nat.mul_pos hN hN) hW), ← Int.natCast_mul (N * N) AccW,
Lib.natAbs_sub_one_add_one (Nat.mul_pos (Nat.mul_pos hN hN) hA)] at hd
refine PDesign.instantiate2_of_ok rfl (mm_paramEnv _ _ _) hd (mm_elems _ _ _)
(mm_gens N W AccW _ hN hW hA ?_)
simp only [PDesign.surfaceFuel, List.map, List.sum_cons, List.sum_nil, Int.natAbs_natCast]
exact Nat.lt_of_lt_of_le (Nat.lt_of_lt_of_le (by omega) (Nat.le_mul_self _)) (Nat.le_add_right _ _)
theorem mm_processes (N W AccW : Nat) (hN : 1 ≤ N) (hW : 1 ≤ W) (hA : 1 ≤ AccW) :
(design.pdesign.instantiateD2 [(N : Int), W, AccW]).processes.toList =
mmCtrl N W :: mmPEs N W AccW := by
rw [PDesign.instantiateD2_of_ok (mm_instantiate2 N W AccW hN hW hA), PDesign.assemble2_processes,
mm_ctrl]
simp [design.pdesign]
/-- Two PEs write disjoint windows of `C`. -/
theorem pe_disj {N W AccW i j i' j' : Nat} (h : i * N + j < i' * N + j') :
∀ a ∈ (mmPE N W AccW i j).procWins, ∀ b ∈ (mmPE N W AccW i' j').procWins,
a.overlaps b = false := by
have := rowMajor_le (K := AccW) h
simp [mmPE, Process2.procWins, Stmt2.drvWins, Stmt2.drvWinsOpt, Lhs2.drvWins]
omega
theorem mm_pe_mem {N W AccW : Nat} {p : Process2} (hp : p ∈ mmPEs N W AccW) :
∃ i j, p = mmPE N W AccW i j := by
simp only [mmPEs, List.mem_flatMap, List.mem_map, List.mem_range] at hp
obtain ⟨i, -, j, -, rfl⟩ := hp
exact ⟨i, j, rfl⟩
/-- The control and the PEs drive pairwise disjoint windows. -/
theorem mm_pairwise (N W AccW : Nat) : (mmCtrl N W :: mmPEs N W AccW).Pairwise
fun p q => ∀ a ∈ p.procWins, ∀ b ∈ q.procWins, a.overlaps b = false := by
refine List.Pairwise.cons (fun p hp => ?_) (pairwise_flatMap_range (fun i _ => ?_) ?_)
· obtain ⟨i, j, rfl⟩ := mm_pe_mem hp
simp [mmCtrl, mmPE, Process2.procWins, Stmt2.drvWins, Stmt2.drvWinsOpt, Stmt2.drvWinsList,
Lhs2.drvWins]
· exact List.pairwise_map.mpr (List.pairwise_lt_range.imp_of_mem fun _ _ h => pe_disj (by omega))
· intro i i' hii _ a ha b hb
simp only [List.mem_map, List.mem_range] at ha hb
obtain ⟨j, hj, rfl⟩ := ha
obtain ⟨j', -, rfl⟩ := hb
exact pe_disj (by have := rowMajor_le (K := N) hii; omega)
theorem mm_driversOk (N W AccW : Nat) :
driversOk design.pdesign.declNames design.pdesign.inputNames (mmCtrl N W :: mmPEs N W AccW) = true := by
apply driversOk_of_ff ?_ ?_ (mm_pairwise N W AccW)
· intro p hp
rcases List.mem_cons.mp hp with rfl | hp
· exact ⟨_, _, rfl⟩
· obtain ⟨i, j, rfl⟩ := mm_pe_mem hp; exact ⟨_, _, rfl⟩
· simp only [List.mem_flatMap, List.mem_cons]
rintro n ⟨p, hp | hp, hn⟩
all_goals first | subst hp | obtain ⟨i, j, rfl⟩ := mm_pe_mem hp
all_goals revert n; simp [mmCtrl, mmPE, Process2.procWrites, Process2.procWins, Stmt2.drvWins,
Stmt2.drvWinsOpt, Stmt2.drvWinsList, Lhs2.drvWins]; all_goals decide
/-- **The loaded family is `mmD`**, at every `(N, W, AccW)` with positive parameters. -/
theorem mm_sem_eq (N W AccW : Nat) (hN : 1 ≤ N) (hW : 1 ≤ W) (hA : 1 ≤ AccW) :
design.sem N W AccW = mmD N W AccW := by
have h1 : design.sem N W AccW = design.pdesign.instantiateD2 [(N : Int), W, AccW] := by
show design.pdesign.instantiateGD2 [(N : Int), W, AccW] = _
exact PDesign.instantiateGD2_of_driversOk (by rw [mm_processes N W AccW hN hW hA]; exact mm_driversOk N W AccW)
rw [h1, PDesign.instantiateD2_of_ok (mm_instantiate2 N W AccW hN hW hA)]
simp only [PDesign.assemble2, mm_ctrl, mmD]
simp [design.pdesign]
/-! ## 2. G0's clock step for the loaded family, at every `(N, W, AccW)` -/
/-- **`mmD` is in G0's decidable FF-only profile at every `(N, W, AccW)`** (symbolic, not `rfl`). -/
theorem mm_profile (N W AccW : Nat) : (mmD N W AccW).ffOnlyProfileB = true := by
simp only [Design2.ffOnlyProfileB, Bool.and_eq_true]
refine ⟨?_, ffDisjB_of_pairwise (by simpa [mmD] using mm_pairwise N W AccW)⟩
simp [mmD, mmCtrl, mmPEs, mmPE, nb, List.all_flatMap, List.all_map, Stmt2.nbaOnly, Stmt2.optNbaOnly,
Stmt2.listNbaOnly, Stmt2.nbaStaticB, Stmt2.optNbaStaticB, Stmt2.listNbaStaticB, Lhs2.window?,
Design2.declWidth, Design2.inputNames, Stmt2.drvWins, Stmt2.drvWinsOpt, Stmt2.drvWinsList,
Lhs2.drvWins, mmDecls]
/-- The 11 signals of `mmD`, in declaration order. -/
def MS (ck r s a b dn c k bz ash bsh : LVec) : SvState :=
[("clk", ck), ("rst", r), ("start", s), ("A", a), ("B", b), ("done", dn), ("C", c), ("k", k),
("busy", bz), ("a_sh", ash), ("b_sh", bsh)]
theorem apply_mm (N W AccW : Nat) (e : SvState) (ck r s a b dn c k bz ash bsh : LVec) :
applyInputs2 (mmD N W AccW) e (MS ck r s a b dn c k bz ash bsh) =
MS (appIn e "clk" ck) (appIn e "rst" r) (appIn e "start" s) (appIn e "A" a) (appIn e "B" b)
dn c k bz ash bsh := by
unfold applyInputs2
rw [← Array.foldl_toList]
cases h1 : SvState.lookup e "clk" <;> cases h2 : SvState.lookup e "rst" <;>
cases h3 : SvState.lookup e "start" <;> cases h4 : SvState.lookup e "A" <;>
cases h5 : SvState.lookup e "B" <;>
simp [mmD, mmDecls, MS, appIn, h1, h2, h3, h4, h5, SvState.set]
/-- A nonblocking write of `v` to the whole of `n` (old value `old`). -/
abbrev wr (n : String) (old v : LVec) : String × Nat × LVec := (n, 0, v.resizeU old.width)
/-- `v >> (2·W) >> (2·W)`, evaluated. -/
def shA (W : Nat) (v : LVec) : LVec :=
(v.shr ((LVec.ofInt 32 2).mul (LVec.ofInt 32 (W : Int)))).shr ((LVec.ofInt 32 2).mul (LVec.ofInt 32 (W : Int)))
/-- `v >> (2·N·W) >> (2·N·W)`, evaluated. -/
def shB (N W : Nat) (v : LVec) : LVec :=
(v.shr (((LVec.ofInt 32 2).mul (LVec.ofInt 32 (N : Int))).mul (LVec.ofInt 32 (W : Int)))).shr
(((LVec.ofInt 32 2).mul (LVec.ofInt 32 (N : Int))).mul (LVec.ofInt 32 (W : Int)))
/-- The control's updates, from the pre-edge values. -/
def ctrlQ (N W : Nat) (r s a b dn k bz ash bsh : LVec) : NbaQueue2 :=
if r.condTrue then [wr "busy" bz ⟨#[.l0]⟩, wr "done" dn ⟨#[.l0]⟩, wr "k" k (LVec.replicate 32 .l0)]
else if s.condTrue then [wr "busy" bz ⟨#[.l1]⟩, wr "done" dn ⟨#[.l0]⟩,
wr "k" k (LVec.replicate 32 .l0), wr "a_sh" ash (a.resizeU ((N * N + 3) * W)),
wr "b_sh" bsh (b.resizeU ((N * N + 3 * N) * W))]
else if bz.condTrue then
if (LVec.ofLogic ((k.add (LVec.ofInt 32 4)).ge (LVec.ofInt 32 (N : Int)))).condTrue then
[wr "k" k (k.add (LVec.ofInt 32 4)), wr "a_sh" ash (shA W ash), wr "b_sh" bsh (shB N W bsh),
wr "busy" bz ⟨#[.l0]⟩, wr "done" dn ⟨#[.l1]⟩]
else [wr "k" k (k.add (LVec.ofInt 32 4)), wr "a_sh" ash (shA W ash), wr "b_sh" bsh (shB N W bsh)]
else []
/-- `{{AccW{v[lo+W-1]}}, v[lo +: W]}`, evaluated. -/
def sxv (v : LVec) (AccW lo W : Nat) : LVec :=
LVec.concatMany #[LVec.replVec AccW (LVec.concatMany #[v.extract (lo + W - 1) 1]), v.extract lo W]
/-- One product of the busy sum, evaluated. -/
def mulV (AccW W : Nat) (ash bsh : LVec) (la lb : Nat) : LVec :=
(sxv ash AccW la W).mul (sxv bsh AccW lb W)
/-- The new accumulator `(i, j)` of a busy cycle (four products), evaluated. -/
def peV (N W AccW i j : Nat) (c ash bsh : LVec) : LVec :=
let r0 := (c.extract ((i * N + j) * AccW) AccW).resizeU (AccW + W)
let r1 := r0.add (mulV AccW W ash bsh (i * N * W) (j * W))
let r2 := r1.add (mulV AccW W ash bsh ((i * N + 1) * W) ((N + j) * W))
let r3 := r2.add (mulV AccW W ash bsh ((i * N + 2) * W) ((2 * N + j) * W))
let r4 := r3.add (mulV AccW W ash bsh ((i * N + 3) * W) ((3 * N + j) * W))
r4.resizeU AccW
/-- The PE's update, from the pre-edge values. -/
def peQ (N W AccW : Nat) (s bz c ash bsh : LVec) (i j : Nat) : NbaQueue2 :=
if s.condTrue then [("C", (i * N + j) * AccW, (LVec.replicate AccW .l0).resizeU AccW)]
else if bz.condTrue then [("C", (i * N + j) * AccW, (peV N W AccW i j c ash bsh).resizeU AccW)]
else []
theorem ctrl_run (N W F : Nat) (hF : 30 ≤ F) (ck r s a b dn c k bz ash bsh : LVec) :
runEdgeProcess2 F (MS ck r s a b dn c k bz ash bsh) [] (mmCtrl N W) =
.ok (MS ck r s a b dn c k bz ash bsh, ctrlQ N W r s a b dn k bz ash bsh) := by
obtain ⟨f, rfl⟩ : ∃ f, F = f + 30 := ⟨F - 30, by omega⟩
unfold ctrlQ
by_cases h1 : r.condTrue = true <;> by_cases h2 : s.condTrue = true <;>
by_cases h3 : bz.condTrue = true <;>
by_cases h4 : (LVec.ofLogic ((k.add (LVec.ofInt 32 4)).ge (LVec.ofInt 32 (N : Int)))).condTrue = true <;>
simp [mmCtrl, nb, c32, amtA, amtB, shA, shB, runEdgeProcess2, execStmt2, execStmts2, evalExpr2, resolveLhs2,
readSignal, MS, SvState.lookup, evalBinOp, wr, h1, h2, h3, h4]
theorem pe_run (N W AccW i j F : Nat) (hF : 30 ≤ F) (ck r s a b dn c k bz ash bsh : LVec) :
runEdgeProcess2 F (MS ck r s a b dn c k bz ash bsh) [] (mmPE N W AccW i j) =
.ok (MS ck r s a b dn c k bz ash bsh, peQ N W AccW s bz c ash bsh i j) := by
obtain ⟨f, rfl⟩ : ∃ f, F = f + 30 := ⟨F - 30, by omega⟩
unfold peQ
by_cases h1 : s.condTrue = true <;> by_cases h2 : bz.condTrue = true <;>
simp [mmPE, mulT, mulV, sext, runEdgeProcess2, execStmt2, evalExpr2, evalExprs2, resolveLhs2, readSignal, MS,
SvState.lookup, evalBinOp, peV, sxv, h1, h2]
/-- One cycle's next state, from the pre-edge (inputs applied) values. -/
def mmNext (N W AccW : Nat) (ck r s a b dn c k bz ash bsh : LVec) : SvState :=
commitNba2 (MS ck r s a b dn c k bz ash bsh) (ctrlQ N W r s a b dn k bz ash bsh ++
(List.range N).flatMap fun i => (List.range N).flatMap (peQ N W AccW s bz c ash bsh i))
/-- **One cycle of the loaded design is G0's clock step**, evaluated (any schedule, any stimulus
entry, any state of the design's shape, every fuel from 30). -/
theorem mm_cycle (N W AccW : Nat) (hN : 1 ≤ N) (hW : 1 ≤ W) (hA : 1 ≤ AccW) (σ : ScheduleOracle)
(e : SvState) (ck r s a b dn c k bz ash bsh : LVec) (kk F : Nat) (hF : 30 ≤ F) :
cycleStep2 (design.sem N W AccW) σ F e (MS ck r s a b dn c k bz ash bsh) kk =
.ok (mmNext N W AccW (appIn e "clk" ck) (appIn e "rst" r) (appIn e "start" s) (appIn e "A" a)
(appIn e "B" b) dn c k bz ash bsh, kk + 3) := by
have hedge : ∀ p ∈ (mmD N W AccW).processes.toList, p.isEdgePhase = true := by
intro p hp
have hm : p ∈ mmCtrl N W :: mmPEs N W AccW := by simpa [mmD] using hp
rcases List.mem_cons.mp hm with rfl | hm
· rfl
· obtain ⟨i, j, rfl⟩ := mm_pe_mem hm; rfl
rw [mm_sem_eq N W AccW hN hW hA, cycleStep2_profile (mm_profile N W AccW) σ e _ kk ?_ ?_ (by omega),
apply_mm, clockStep, flatMap_ffUpd F _ hedge]
· simp only [mmD, List.toList_toArray, List.flatMap_cons, ctrl_run N W F hF, mmPEs, List.flatMap_assoc,
List.flatMap_map, pe_run N W AccW _ _ F hF, mmNext]
· intro i p hp
rw [apply_mm]
have hm : p ∈ mmCtrl N W :: mmPEs N W AccW :=
List.mem_of_getElem? (by simpa [mmD] using hp : (mmCtrl N W :: mmPEs N W AccW)[i]? = some p)
rcases List.mem_cons.mp hm with rfl | hm
· exact ⟨_, ctrl_run N W F hF _ _ _ _ _ _ _ _ _ _ _⟩
· obtain ⟨i, j, rfl⟩ := mm_pe_mem hm
exact ⟨_, pe_run N W AccW i j F hF _ _ _ _ _ _ _ _ _ _ _⟩
· rw [apply_mm]; simp [MS]
/-! ## 3. The busy cycle on known values: the invariant on `k` -/
attribute [local simp] condTrue_knownBit
/-- `Σ_{k' < t} A[i][k']·B[k'][j]` in the integers (`Tasks.Matmul.dot` at `t = N`). -/
def psum (N W : Nat) {n : Nat} (A B : BitVec n) (t i j : Nat) : Int :=
(List.range t).foldl (fun acc k =>
acc + Tasks.Matmul.entry W A (i * N + k) * Tasks.Matmul.entry W B (k * N + j)) 0
/-- The accumulators after `t` busy cycles: entry `(i, j)` is `psum t i j` mod `2^AccW`. -/
def accC (N W AccW : Nat) {n : Nat} (A B : BitVec n) (t : Nat) : LVec :=
LVec.ofBitVec (packBV AccW (N * N) fun r => BitVec.ofInt AccW (psum N W A B t (r / N) (r % N)))
theorem psum_succ (N W : Nat) {n : Nat} (A B : BitVec n) (t i j : Nat) : psum N W A B (t + 1) i j =
psum N W A B t i j + Tasks.Matmul.entry W A (i * N + t) * Tasks.Matmul.entry W B (t * N + j) := by
simp [psum, List.range_succ, List.foldl_append]
theorem psum_add4 (N W : Nat) {n : Nat} (A B : BitVec n) (t i j : Nat) : psum N W A B (t + 4) i j =
psum N W A B t i j + Tasks.Matmul.entry W A (i * N + t) * Tasks.Matmul.entry W B (t * N + j)
+ Tasks.Matmul.entry W A (i * N + (t + 1)) * Tasks.Matmul.entry W B ((t + 1) * N + j)
+ Tasks.Matmul.entry W A (i * N + (t + 2)) * Tasks.Matmul.entry W B ((t + 2) * N + j)
+ Tasks.Matmul.entry W A (i * N + (t + 3)) * Tasks.Matmul.entry W B ((t + 3) * N + j) := by
have h1 : psum N W A B (t + 1) i j = _ := psum_succ N W A B t i j
have h2 : psum N W A B (t + 2) i j = _ := psum_succ N W A B (t + 1) i j
have h3 : psum N W A B (t + 3) i j = _ := psum_succ N W A B (t + 2) i j
have h4 : psum N W A B (t + 4) i j = _ := psum_succ N W A B (t + 3) i j
rw [h4, h3, h2, h1]
/-- `A` zero-extended to the `a_sh` register's width. -/
def padA (N W : Nat) (A : BitVec (N * N * W)) : BitVec ((N * N + 3) * W) := A.setWidth ((N * N + 3) * W)
/-- `B` zero-extended to the `b_sh` register's width. -/
def padB (N W : Nat) (B : BitVec (N * N * W)) : BitVec ((N * N + 3 * N) * W) := B.setWidth ((N * N + 3 * N) * W)
/-- The `A` window of step `t + m`, as the shifted register shows it. -/
theorem eA {t i N W : Nat} (m : Nat) : t * W + (i * N + m) * W = (i * N + (t + m)) * W := by
rw [← Nat.add_mul]; congr 1; omega
/-- The `B` window of step `t + m`, as the shifted register shows it. -/
theorem eB {t j N W : Nat} (m : Nat) : t * (N * W) + (m * N + j) * W = ((t + m) * N + j) * W := by
simp only [Nat.add_mul, Nat.mul_add, Nat.mul_assoc]; omega
/-- **PE `(i, j)` in a busy cycle** adds `Σ_{m<4} A[i][t+m]·B[t+m][j]` to its accumulator. -/
theorem pe_step {N W AccW : Nat} (hW : 1 ≤ W) (A B : BitVec (N * N * W)) {t i j : Nat} (hi : i < N)
(hj : j < N) : peQ N W AccW (knownBit false) (knownBit true) (accC N W AccW A B t)
(.ofBitVec (padA N W A >>> (t * W))) (.ofBitVec (padB N W B >>> (t * (N * W)))) i j =
[("C", (i * N + j) * AccW,
.ofBitVec (BitVec.ofInt AccW (psum N W A B (t + 4) ((i * N + j) / N) ((i * N + j) % N))))] := by
have hd : (i * N + j) / N = i := by
rw [Nat.add_comm, Nat.add_mul_div_right _ _ (by omega), Nat.div_eq_of_lt hj, Nat.zero_add]
have hm : (i * N + j) % N = j := by rw [Nat.add_comm, Nat.add_mul_mod_self_right, Nat.mod_eq_of_lt hj]
have hc := Lib.rowMajor_window_le (w := AccW) hi hj
have hr : i * N + j < N * N := by
have := Lib.rowMajor_window_le (w := 1) hi hj
simp at this
omega
have wa0 : i * N * W + W ≤ (N * N + 3) * W := by
simpa using winA_le (N := N) (W := W) (i := i) 0 (by omega) hi
have wa1 := winA_le (N := N) (W := W) (i := i) 1 (by omega) hi
have wa2 := winA_le (N := N) (W := W) (i := i) 2 (by omega) hi
have wa3 := winA_le (N := N) (W := W) (i := i) 3 (by omega) hi
have wb0 : j * W + W ≤ (N * N + 3 * N) * W := by
simpa using winB_le (N := N) (W := W) (j := j) 0 (by omega) hj
have wb1 : (N + j) * W + W ≤ (N * N + 3 * N) * W := by
simpa using winB_le (N := N) (W := W) (j := j) 1 (by omega) hj
have wb2 := winB_le (N := N) (W := W) (j := j) 2 (by omega) hj
have wb3 := winB_le (N := N) (W := W) (j := j) 3 (by omega) hj
have hpA : N * N * W ≤ (N * N + 3) * W := Nat.mul_le_mul_right W (by omega)
have hpB : N * N * W ≤ (N * N + 3 * N) * W := Nat.mul_le_mul_right W (by omega)
have e1 : t * W + i * N * W = (i * N + t) * W := by rw [Nat.add_mul, Nat.add_comm]
have e2 : t * (N * W) + j * W = (t * N + j) * W := by rw [Nat.add_mul, Nat.mul_assoc]
have eA1 := eA (t := t) (i := i) (N := N) (W := W) 1
have eA2 := eA (t := t) (i := i) (N := N) (W := W) 2
have eA3 := eA (t := t) (i := i) (N := N) (W := W) 3
have eB1 : t * (N * W) + (N + j) * W = ((t + 1) * N + j) * W := by
simp only [Nat.add_mul, Nat.mul_assoc, Nat.one_mul]; omega
have eB2 := eB (t := t) (j := j) (N := N) (W := W) 2
have eB3 := eB (t := t) (j := j) (N := N) (W := W) 3
simp (disch := omega) only [peQ, condTrue_knownBit, Bool.false_eq_true, ↓reduceIte, peV, mulV, sxv, accC, padA,
padB, LVec.extract_ofBitVec, extractLsb'_packBV, hd, hm, LVec.concatMany_singleton,
LVec.concatMany_cons_cons, LVec.replVec_ofBitVec, LVec.concat_ofBitVec,
LVec.ofBitVec_replicate_append_eq_signExtend hW, LVec.mul_ofBitVec, LVec.add_ofBitVec, LVec.resizeU_ofBitVec,
BitVec.setWidth_eq, setWidth_add4_mul_signExtend, Lib.extractLsb'_ushiftRight, extractLsb'_setWidth_of_le,
e1, e2, eA1, eA2, eA3, eB1, eB2, eB3, psum_add4, BitVec.ofInt_add, Tasks.Matmul.entry]
/-! ## 4. The spec's arithmetic: `product` is the packed vector of the wrapped dot products -/
theorem product_eq (N W AccW : Nat) {n : Nat} (a b : BitVec n) : Tasks.Matmul.product N W AccW a b =
packBV AccW (N * N) fun r => BitVec.ofInt AccW (Tasks.Matmul.dot N W a b (r / N) (r % N)) :=
(packBV_eq_ofNat ..).symm
/-- After `N` busy cycles the accumulators hold the product (`psum N` is `dot`). -/
theorem accC_N (N W AccW : Nat) {n : Nat} (A B : BitVec n) :
accC N W AccW A B N = LVec.ofBitVec (Tasks.Matmul.product N W AccW A B) := by
rw [product_eq]; rfl
/-! ## 5. The registers through a cycle: the commit, then the invariant on `k` -/
section commit
variable {ck r s a b dn c k bz ash bsh : LVec} {lo : Nat} {v : LVec}
theorem nb_busy : nbaStep (MS ck r s a b dn c k bz ash bsh) ("busy", lo, v) =
MS ck r s a b dn c k (bz.insertAt lo v) ash bsh := by simp [nbaStep, MS, SvState.lookup, SvState.set]
theorem nb_done : nbaStep (MS ck r s a b dn c k bz ash bsh) ("done", lo, v) =
MS ck r s a b (dn.insertAt lo v) c k bz ash bsh := by simp [nbaStep, MS, SvState.lookup, SvState.set]
theorem nb_k : nbaStep (MS ck r s a b dn c k bz ash bsh) ("k", lo, v) =
MS ck r s a b dn c (k.insertAt lo v) bz ash bsh := by simp [nbaStep, MS, SvState.lookup, SvState.set]
theorem nb_ash : nbaStep (MS ck r s a b dn c k bz ash bsh) ("a_sh", lo, v) =
MS ck r s a b dn c k bz (ash.insertAt lo v) bsh := by simp [nbaStep, MS, SvState.lookup, SvState.set]
theorem nb_bsh : nbaStep (MS ck r s a b dn c k bz ash bsh) ("b_sh", lo, v) =
MS ck r s a b dn c k bz ash (bsh.insertAt lo v) := by simp [nbaStep, MS, SvState.lookup, SvState.set]
end commit
/-- Committing writes of `C` only. -/
theorem commit_C {ck r s a b dn k bz ash bsh : LVec} : ∀ {q : NbaQueue2}, (∀ u ∈ q, u.1 = "C") → ∀ c,
commitNba2 (MS ck r s a b dn c k bz ash bsh) q =
MS ck r s a b dn (q.foldl (fun acc u => acc.insertAt u.2.1 u.2.2) c) k bz ash bsh
| [], _, _ => rfl
| ⟨n, lo, v⟩ :: q, h, c => by
obtain rfl : n = "C" := h _ List.mem_cons_self
rw [commitNba2_cons, List.foldl_cons, ← commit_C (fun u hu => h u (List.mem_cons_of_mem _ hu))]
congr 1
/-- The PEs' updates, row-major, when PE `(i, j)` writes window `i·N + j` with `g (i·N + j)`. -/
theorem rowMajor_map {N AccW : Nat} {q : Nat → Nat → NbaQueue2} (g : Nat → LVec)
(h : ∀ i j, i < N → j < N → q i j = [("C", (i * N + j) * AccW, g (i * N + j))]) :
((List.range N).flatMap fun i => (List.range N).flatMap (q i)) =
(List.range (N * N)).map fun r => ("C", r * AccW, g r) := by
suffices ∀ M, M ≤ N → ((List.range M).flatMap fun i => (List.range N).flatMap (q i)) =
(List.range (M * N)).map (fun r => ("C", r * AccW, g r)) from this N (Nat.le_refl _)
intro M; induction M with
| zero => intro _; simp
| succ M ih =>
intro hM
rw [List.range_succ, List.flatMap_append, ih (by omega), Nat.succ_mul, List.range_add,
List.map_append, List.map_map]
simp only [List.flatMap_cons, List.flatMap_nil, List.append_nil]
rw [CombView.flatMap_congr' fun j hj => h M j (by omega) (List.mem_range.mp hj)]
congr 1
induction (List.range N) with
| nil => rfl
| cons a l ih' => rw [List.flatMap_cons, ih']; rfl
theorem commit_map {ck r s a b dn k bz ash bsh : LVec} (AccW : Nat) (g : Nat → LVec) (n : Nat) (c : LVec) :
commitNba2 (MS ck r s a b dn c k bz ash bsh) ((List.range n).map fun r => ("C", r * AccW, g r)) =
MS ck r s a b dn (LVec.insertEntries AccW g n c) k bz ash bsh := by
rw [commit_C (by simp), List.foldl_map]; rfl
theorem toNat_mul_lt {w a b : Nat} (ha : a < 2 ^ w) (hb : b < 2 ^ w) (hab : a * b < 2 ^ w) :
(BitVec.ofNat w a * BitVec.ofNat w b).toNat = a * b := by
rw [BitVec.toNat_mul, BitVec.toNat_ofNat, BitVec.toNat_ofNat, Nat.mod_eq_of_lt ha, Nat.mod_eq_of_lt hb,
Nat.mod_eq_of_lt hab]
theorem toNat_mul3_lt {w a b c : Nat} (ha : a < 2 ^ w) (hb : b < 2 ^ w) (hc : c < 2 ^ w)
(hab : a * b < 2 ^ w) (habc : a * b * c < 2 ^ w) :
(BitVec.ofNat w a * BitVec.ofNat w b * BitVec.ofNat w c).toNat = a * b * c := by
rw [BitVec.toNat_mul, toNat_mul_lt ha hb hab, BitVec.toNat_ofNat, Nat.mod_eq_of_lt hc, Nat.mod_eq_of_lt habc]
/-- The control in a busy cycle `t < N`: `k` steps by four, the shift registers shift by four entries / rows;
the cycle that reaches `N` ends. -/
theorem ctrl_busy {N W : Nat} (hN : 1 ≤ N) (hW : 1 ≤ W) (hr : N * N * W < 2 ^ 31) (A B : BitVec (N * N * W))
{t : Nat} (ht : t < N) (ck a b c : LVec) :
commitNba2 (MS ck (knownBit false) (knownBit false) a b (knownBit false) c (.ofBitVec (BitVec.ofNat 32 t))
(knownBit true) (.ofBitVec (padA N W A >>> (t * W))) (.ofBitVec (padB N W B >>> (t * (N * W)))))
(ctrlQ N W (knownBit false) (knownBit false) a b (knownBit false) (.ofBitVec (BitVec.ofNat 32 t)) (knownBit true)
(.ofBitVec (padA N W A >>> (t * W))) (.ofBitVec (padB N W B >>> (t * (N * W))))) =
MS ck (knownBit false) (knownBit false) a b (knownBit (decide (N ≤ t + 4))) c
(.ofBitVec (BitVec.ofNat 32 (t + 4))) (knownBit (decide (¬ N ≤ t + 4)))
(.ofBitVec (padA N W A >>> (t * W + 2 * W + 2 * W)))
(.ofBitVec (padB N W B >>> (t * (N * W) + 2 * N * W + 2 * N * W))) := by
simp only [Nat.reducePow] at hr
have h1 : N * W ≤ N * N * W := by rw [Nat.mul_assoc N N W]; exact Nat.le_mul_of_pos_left _ hN
have hNW : N * W < 2 ^ 31 := by simp only [Nat.reducePow]; omega
have hN31 : N < 2 ^ 31 := by
have : N ≤ N * W := Nat.le_mul_of_pos_right N hW
simp only [Nat.reducePow]; omega
have hW31 : W < 2 ^ 31 := by
have : W ≤ N * W := Nat.le_mul_of_pos_left W hN
simp only [Nat.reducePow]; omega
have hN' : N < 2 ^ 32 := by simp only [Nat.reducePow]; omega
have hW' : W < 2 ^ 32 := by simp only [Nat.reducePow]; omega
have h2 : 2 < 2 ^ 32 := by decide
have h2W : 2 * W < 2 ^ 32 := by simp only [Nat.reducePow]; omega
have h2N : 2 * N < 2 ^ 32 := by simp only [Nat.reducePow]; omega
have h2NW : 2 * N * W < 2 ^ 32 := by
rw [Nat.mul_assoc]; simp only [Nat.reducePow]; omega
have hyW : (BitVec.ofNat 32 2 * BitVec.ofNat 32 W).toNat = 2 * W := toNat_mul_lt h2 hW' h2W
have hyNW : (BitVec.ofNat 32 2 * BitVec.ofNat 32 N * BitVec.ofNat 32 W).toNat = 2 * N * W :=
toNat_mul3_lt h2 hN' hW' h2N h2NW
have h2' : LVec.ofInt 32 2 = LVec.ofBitVec 2#32 := by decide
have h4' : LVec.ofInt 32 4 = LVec.ofBitVec 4#32 := by decide
simp only [ctrlQ, shA, shB, wr, condTrue_knownBit, Bool.false_eq_true, ↓reduceIte, h2', h4',
LVec.ofInt_eq_ofBitVec, BitVec.ofInt_natCast, BitVec.ofInt_ofNat, LVec.mul_ofBitVec,
LVec.add_ofBitVec, LVec.ge_ofBitVec, LVec.ofLogic_ofBool, decide_eq_true_eq, ge_four_iff ht hN31,
LVec.width_ofBitVec, LVec.shr_ofBitVec, LVec.resizeU_ofBitVec, BitVec.setWidth_eq, hyW, hyNW,
← BitVec.shiftRight_add]
by_cases hf : N ≤ t + 4 <;>
simp [hf, commitNba2_cons, commitNba2_nil, nb_k, nb_ash, nb_bsh, nb_busy, nb_done,
LVec.insertAt_zero_of_width_eq, LVec.resizeU_one_l0, LVec.resizeU_one_l1, BitVec.ofNat_add_ofNat]
/-- **The invariant on `k`, one step**: from the state `t < N` steps after a start with operands `A`, `B`
(`acc[i][j] = Σ_{k' < t} A[i][k']·B[k'][j] mod 2^AccW`, `k = t`, the operands shifted by `t` entries), one cycle
without `start` reaches the state at `t + 4`; `done` rises (and `busy` falls) exactly when `N ≤ t + 4`. -/
theorem next_step {N W AccW : Nat} (hN : 1 ≤ N) (hW : 1 ≤ W) (hr : N * N * W < 2 ^ 31)
(A B : BitVec (N * N * W)) {t : Nat} (ht : t < N) (ck a b : LVec) :
mmNext N W AccW ck (knownBit false) (knownBit false) a b (knownBit false) (accC N W AccW A B t)
(.ofBitVec (BitVec.ofNat 32 t)) (knownBit true) (.ofBitVec (padA N W A >>> (t * W)))
(.ofBitVec (padB N W B >>> (t * (N * W)))) =
MS ck (knownBit false) (knownBit false) a b (knownBit (decide (N ≤ t + 4))) (accC N W AccW A B (t + 4))
(.ofBitVec (BitVec.ofNat 32 (t + 4))) (knownBit (decide (¬ N ≤ t + 4)))
(.ofBitVec (padA N W A >>> ((t + 4) * W))) (.ofBitVec (padB N W B >>> ((t + 4) * (N * W)))) := by
have hq := rowMajor_map (N := N) (AccW := AccW) (q := peQ N W AccW (knownBit false) (knownBit true)
(accC N W AccW A B t) (.ofBitVec (padA N W A >>> (t * W))) (.ofBitVec (padB N W B >>> (t * (N * W)))))
(fun r => LVec.ofBitVec (BitVec.ofInt AccW (psum N W A B (t + 4) (r / N) (r % N))))
(fun i j hi hj => pe_step hW A B hi hj)
have eA4 : t * W + 2 * W + 2 * W = (t + 4) * W := by rw [Nat.add_mul]; omega
have eB4 : t * (N * W) + 2 * N * W + 2 * N * W = (t + 4) * (N * W) := by
simp only [Nat.add_mul, Nat.mul_assoc]; omega
rw [mmNext, commitNba2_append, ctrl_busy hN hW hr A B ht, hq, commit_map,
LVec.insertEntries_ofBitVec _ _ (by simp [accC]), eA4, eB4]
rfl
/-! ## 6. Shape and termination -/
/-- The register widths are the declared ones. -/
def Wd (N W AccW : Nat) (dn c k bz ash bsh : LVec) : Prop :=
dn.width = 1 ∧ c.width = N * N * AccW ∧ k.width = 32 ∧ bz.width = 1 ∧ ash.width = (N * N + 3) * W ∧
bsh.width = (N * N + 3 * N) * W
/-- A snapshot of the design's shape. -/
def I (N W AccW : Nat) (st : SvState) : Prop :=
∃ ck r s a b dn c k bz ash bsh, st = MS ck r s a b dn c k bz ash bsh ∧ Wd N W AccW dn c k bz ash bsh
theorem peQ_C (N W AccW : Nat) (s bz c ash bsh : LVec) :
∀ u ∈ (List.range N).flatMap fun i => (List.range N).flatMap (peQ N W AccW s bz c ash bsh i), u.1 = "C" := by
intro u hu
simp only [List.mem_flatMap] at hu
obtain ⟨_, _, _, _, hu⟩ := hu
unfold peQ at hu
by_cases h1 : s.condTrue = true <;> by_cases h2 : bz.condTrue = true <;> simp_all
/-- **Every cycle keeps the design's shape** (any values, `x` included). -/
theorem mmNext_I (N W AccW : Nat) (ck r s a b dn c k bz ash bsh : LVec) (hw : Wd N W AccW dn c k bz ash bsh) :
I N W AccW (mmNext N W AccW ck r s a b dn c k bz ash bsh) := by
obtain ⟨h1, h2, h3, h4, h5, h6⟩ := hw
simp only [mmNext, commitNba2_append, ctrlQ]
by_cases c1 : r.condTrue = true <;> by_cases c2 : s.condTrue = true <;> by_cases c3 : bz.condTrue = true <;>
by_cases c4 : (LVec.ofLogic ((k.add (LVec.ofInt 32 4)).ge (LVec.ofInt 32 (N : Int)))).condTrue = true <;>
simp only [c1, c2, c3, c4, Bool.false_eq_true, ↓reduceIte, commitNba2_cons, commitNba2_nil, nb_busy, nb_done, nb_k,
nb_ash, nb_bsh, wr] <;>
rw [commit_C (peQ_C N W AccW _ _ _ _ _)] <;>
exact ⟨_, _, _, _, _, _, _, _, _, _, _, rfl, by simp [Wd, width_foldl_insertAt, *]⟩
theorem mm_noq (N W AccW : Nat) : (mmD N W AccW).processes.toList.any Process2.hasQualifiedCase = false := by
simp [mmD, mmCtrl, mmPEs, mmPE, mulT, nb, Process2.hasQualifiedCase, Stmt2.hasQualifiedCase,
Stmt2.listHasQualifiedCase]
theorem mm_init (N W AccW : Nat) (hN : 1 ≤ N) (hW : 1 ≤ W) (hA : 1 ≤ AccW) :
I N W AccW (initState2 (design.sem N W AccW)) := by
rw [mm_sem_eq N W AccW hN hW hA]
refine ⟨.xVec 1, .xVec 1, .xVec 1, .xVec (N * N * W), .xVec (N * N * W), .xVec 1, .xVec (N * N * AccW),
.xVec 32, .xVec 1, .xVec ((N * N + 3) * W), .xVec ((N * N + 3 * N) * W), ?_, by simp [Wd]⟩
simp [initState2, mmD, mmDecls, MS]
/-- **Termination**: every schedule and every stimulus completes. -/
theorem mm_terminates (N W AccW : Nat) (hN : 1 ≤ N) (hW : 1 ≤ W) (hA : 1 ≤ AccW) :
Terminates2 (design.sem N W AccW) := by
refine Terminates2.of_invariant (I N W AccW) (mm_init N W AccW hN hW hA) (fun σ => ⟨30, ?_⟩)
(by rw [mm_sem_eq N W AccW hN hW hA]; exact mm_noq N W AccW)
rintro e _ kk ⟨ck, r, s, a, b, dn, c, k, bz, ash, bsh, rfl, hw⟩
exact ⟨_, _, mm_cycle N W AccW hN hW hA σ e ck r s a b dn c k bz ash bsh kk 30 (Nat.le_refl _),
mmNext_I N W AccW _ _ _ _ _ dn c k bz ash bsh hw⟩
/-! ## 7. The protocol: the phases of `TxTask.satisfiedAt_of_phases`, and the statement -/
theorem MS_inj {ck r s a b dn c k bz ash bsh ck' r' s' a' b' dn' c' k' bz' ash' bsh' : LVec}
(h : MS ck r s a b dn c k bz ash bsh = MS ck' r' s' a' b' dn' c' k' bz' ash' bsh') :
dn = dn' ∧ c = c' ∧ k = k' ∧ bz = bz' ∧ ash = ash' ∧ bsh = bsh' := by
simp only [MS, List.cons.injEq, Prod.mk.injEq, true_and, and_true] at h
exact h.2.2.2.2.2
/-- A cycle without `rst` and `start` from an idle state (`busy` = 0) changes no register. -/
theorem next_idle (N W AccW : Nat) (ck a b dn c k ash bsh : LVec) :
mmNext N W AccW ck (knownBit false) (knownBit false) a b dn c k (knownBit false) ash bsh =
MS ck (knownBit false) (knownBit false) a b dn c k (knownBit false) ash bsh := by
have h : ((List.range N).flatMap fun i => (List.range N).flatMap
(peQ N W AccW (knownBit false) (knownBit false) c ash bsh i)) = [] := by simp [List.flatMap_eq_nil_iff, peQ]
rw [mmNext, h]; simp [ctrlQ]; rfl
/-- A reset cycle clears `busy` and `done`. -/
theorem next_reset {N W AccW : Nat} (ck s a b dn c k bz ash bsh : LVec) (hw : Wd N W AccW dn c k bz ash bsh) :
∃ c' k', mmNext N W AccW ck (knownBit true) s a b dn c k bz ash bsh =
MS ck (knownBit true) s a b (knownBit false) c' k' (knownBit false) ash bsh := by
obtain ⟨h1, -, -, h4, -, -⟩ := hw
simp only [mmNext, commitNba2_append, ctrlQ, condTrue_knownBit, ↓reduceIte, commitNba2_cons, commitNba2_nil, nb_busy,
nb_done, nb_k, wr, h1, h4, LVec.resizeU_one_l0]
rw [commit_C (peQ_C N W AccW _ _ _ _ _)]
exact ⟨_, _, by rw [LVec.insertAt_zero_of_width_eq (v := dn) (p := knownBit false) (by simp [h1]),
LVec.insertAt_zero_of_width_eq (v := bz) (p := knownBit false) (by simp [h4])]⟩
/-- A start cycle captures the operands and clears the accumulators: the state at `t = 0`. -/
theorem next_start {N W AccW : Nat} (A B : BitVec (N * N * W)) (ck dn c k bz ash bsh : LVec)
(hw : Wd N W AccW dn c k bz ash bsh) :
mmNext N W AccW ck (knownBit false) (knownBit true) (.ofBitVec A) (.ofBitVec B) dn c k bz ash bsh =
MS ck (knownBit false) (knownBit true) (.ofBitVec A) (.ofBitVec B) (knownBit false) (accC N W AccW A B 0)
(.ofBitVec (BitVec.ofNat 32 0)) (knownBit true) (.ofBitVec (padA N W A >>> (0 * W)))
(.ofBitVec (padB N W B >>> (0 * (N * W)))) := by
obtain ⟨h1, h2, h3, h4, h5, h6⟩ := hw
have hq := rowMajor_map (N := N) (AccW := AccW) (q := peQ N W AccW (knownBit true) bz c ash bsh)
(fun r => LVec.ofBitVec (BitVec.ofInt AccW (psum N W A B 0 (r / N) (r % N))))
(fun i j _ _ => by simp [peQ, psum])
rw [mmNext, commitNba2_append, hq]
simp only [ctrlQ, condTrue_knownBit, Bool.false_eq_true, ↓reduceIte, commitNba2_cons, commitNba2_nil, nb_busy,
nb_done, nb_k, nb_ash, nb_bsh, wr, h1, h3, h4, h5, h6, LVec.resizeU_one_l0, LVec.resizeU_one_l1, LVec.replicate_l0,
LVec.resizeU_ofBitVec, BitVec.setWidth_eq]
rw [commit_map, LVec.insertEntries_ofBitVec _ _ h2, LVec.insertAt_zero_of_width_eq (by simp [h1]),
LVec.insertAt_zero_of_width_eq (by simp [h3]), LVec.insertAt_zero_of_width_eq (by simp [h4]),
LVec.insertAt_zero_of_width_eq (by simp [h5]), LVec.insertAt_zero_of_width_eq (by simp [h6])]
simp [accC, padA, padB]
/-- The state `t` busy cycles after a start with operands `A`, `B` (the invariant on `k`). -/
def BusyAt (N W AccW : Nat) (A B : BitVec (N * N * W)) (t : Nat) (st : SvState) : Prop :=
∃ ck r s a b, st = MS ck r s a b (knownBit false) (accC N W AccW A B t) (.ofBitVec (BitVec.ofNat 32 t))
(knownBit true) (.ofBitVec (padA N W A >>> (t * W))) (.ofBitVec (padB N W B >>> (t * (N * W))))
/-- Past the last real step the accumulators stay: terms with `k ≥ N` read `B` out of range, as 0. -/
theorem entry_B_zero {N W : Nat} (B : BitVec (N * N * W)) {k j : Nat} (hk : N ≤ k) :
Tasks.Matmul.entry W B (k * N + j) = 0 := by
have h : N * N * W ≤ (k * N + j) * W := by
have h1 : N * N ≤ k * N + j := by
have := Nat.mul_le_mul_right N hk
omega
exact Nat.mul_le_mul_right W h1
have e : B.extractLsb' ((k * N + j) * W) W = 0#W := by
apply BitVec.eq_of_getLsbD_eq; intro i hi
simp [BitVec.getLsbD_extractLsb', BitVec.getLsbD_of_ge B ((k * N + j) * W + i) (by omega)]
simp [Tasks.Matmul.entry, e]
theorem psum_ge (N W : Nat) (A B : BitVec (N * N * W)) (i j : Nat) :
∀ d, psum N W A B (N + d) i j = psum N W A B N i j
| 0 => rfl
| d + 1 => by
rw [← Nat.add_assoc, psum_succ, psum_ge N W A B i j d, entry_B_zero B (by omega), Int.mul_zero, Int.add_zero]
theorem accC_ge (N W AccW : Nat) (A B : BitVec (N * N * W)) {t : Nat} (ht : N ≤ t) :
accC N W AccW A B t = LVec.ofBitVec (Tasks.Matmul.product N W AccW A B) := by
obtain ⟨d, rfl⟩ : ∃ d, t = N + d := ⟨t - N, by omega⟩
rw [← accC_N]
simp only [accC, psum_ge]
/-- Finished with `A`, `B`: `done` = 1 and `C` the product. -/
def FinAt (N W AccW : Nat) (A B : BitVec (N * N * W)) (st : SvState) : Prop :=
∃ ck r s a b k ash bsh, st = MS ck r s a b (knownBit true) (.ofBitVec (Tasks.Matmul.product N W AccW A B)) k
(knownBit false) ash bsh
/-- One cycle from a state of the shape, evaluated (`mm_cycle`). -/
def Stp (N W AccW : Nat) (e prev st : SvState) : Prop :=
∃ ck r s a b dn c k bz ash bsh, prev = MS ck r s a b dn c k bz ash bsh ∧ Wd N W AccW dn c k bz ash bsh ∧
st = mmNext N W AccW (appIn e "clk" ck) (appIn e "rst" r) (appIn e "start" s) (appIn e "A" a)
(appIn e "B" b) dn c k bz ash bsh
theorem ops_appIn {N W AccW : Nat} {e : SvState} {x : Tasks.Matmul.task.Ops (N, W, AccW)}
(h : HoldAll e (Tasks.Matmul.task.operands (N, W, AccW)) x) (a b : LVec) :
appIn e "A" a = .ofBitVec x.1 ∧ appIn e "B" b = .ofBitVec x.2.1 := by
simp only [Tasks.Matmul.task, HoldAll] at h; simp [appIn, h.1, h.2.1]
/-- **The phase model** at `(N, W, AccW)`, latency `N`: `Ready` after a reset, `Busy x m` the state
`t = N - 1 - m` busy cycles after a start, `Fin x` the product shown. -/
def model (N W AccW : Nat) (hN : 1 ≤ N) (hW : 1 ≤ W) (hA : 1 ≤ AccW) (hNW : N * N * W < 2 ^ 31) :
Tasks.Matmul.task.PhaseModel (N, W, AccW) ((N + 3) / 4) (design.sem N W AccW) where
Shape := I N W AccW
Step := Stp N W AccW
init := mm_init N W AccW hN hW hA
cycle := by
rintro σ F e prev k st k' ⟨ck, r, s, a, b, dn, c, kk, bz, ash, bsh, rfl, hw⟩ hc
exact ⟨ck, r, s, a, b, dn, c, kk, bz, ash, bsh, rfl, hw, (Prod.mk.inj (cycleStep2_det hc
(mm_cycle N W AccW hN hW hA σ e ck r s a b dn c kk bz ash bsh k 30 (Nat.le_refl _)))).1⟩
shape := by
rintro e prev st - ⟨ck, r, s, a, b, dn, c, k, bz, ash, bsh, -, hw, rfl⟩
exact mmNext_I N W AccW _ _ _ _ _ dn c k bz ash bsh hw
Ready st := ∃ ck r s a b c k ash bsh, st = MS ck r s a b (knownBit false) c k (knownBit false) ash bsh
Busy x m st := ∃ t j, t = 4 * j ∧ j + m + 1 = (N + 3) / 4 ∧ BusyAt N W AccW x.1 x.2.1 t st
Fin x st := FinAt N W AccW x.1 x.2.1 st
ready_done := by
rintro st ⟨ck, r, s, a, b, c, k, ash, bsh, rfl⟩
simp [BitIs, Tasks.Matmul.task, MS, SvState.lookup]
busy_done := by
rintro x m st ⟨t, j, -, -, ck, r, s, a, b, rfl⟩
simp [BitIs, Tasks.Matmul.task, MS, SvState.lookup]
fin_done := by
rintro ⟨A, B, ⟨⟩⟩ st ⟨ck, r, s, a, b, k, ash, bsh, rfl⟩
simp [BitIs, HoldAll, Tasks.Matmul.task, MS, SvState.lookup]
reset := by
rintro e prev st - ⟨ck, r, s, a, b, dn, c, k, bz, ash, bsh, -, hw, rfl⟩ hr
rw [show appIn e "rst" r = knownBit true from BitIs.appIn_eq hr r]
obtain ⟨c', k', h⟩ := next_reset (appIn e "clk" ck) (appIn e "start" s) (appIn e "A" a) (appIn e "B" b)
dn c k bz ash bsh hw
rw [h]; exact ⟨_, _, _, _, _, _, _, _, _, rfl⟩
start := by
rintro e prev st x - ⟨ck, r, s, a, b, dn, c, k, bz, ash, bsh, -, hw, rfl⟩ - hr hs hx
obtain ⟨ha, hb⟩ := ops_appIn hx a b
rw [show appIn e "rst" r = knownBit false from BitIs.appIn_eq hr r,
show appIn e "start" s = knownBit true from BitIs.appIn_eq hs s, ha, hb, next_start _ _ _ dn c k bz ash bsh hw]
exact .inr ⟨(N + 3) / 4 - 1, by omega, 0, 0, by omega, by omega, _, _, _, _, _, rfl⟩
busy := by
rintro e prev st x m - ⟨ck, r, s, a, b, dn, c, k, bz, ash, bsh, rfl, -, rfl⟩ ⟨t, j, htj, hj, ck', r', s', a', b', hb⟩
hr hs -
obtain ⟨rfl, rfl, rfl, rfl, rfl, rfl⟩ := MS_inj hb
have htN : t < N := by omega
rw [show appIn e "rst" r = knownBit false from BitIs.appIn_eq hr r,
show appIn e "start" s = knownBit false from BitIs.appIn_eq hs s, next_step hN hW hNW _ _ htN]
by_cases hf : N ≤ t + 4
· left
simp only [hf, accC_ge N W AccW _ _ hf, decide_true, ne_eq, not_true_eq_false, decide_false]
exact ⟨_, _, _, _, _, _, _, _, rfl⟩
· right
simp only [hf, ne_eq, not_false_eq_true, decide_true, decide_false]
exact ⟨m - 1, by omega, t + 4, j + 1, by omega, by omega, _, _, _, _, _, rfl⟩
hold_ready := by
rintro e prev st - ⟨ck, r, s, a, b, dn, c, k, bz, ash, bsh, rfl, -, rfl⟩ ⟨ck', r', s', a', b', c', k', ash',
bsh', hb⟩ hr hs
obtain ⟨rfl, rfl, rfl, rfl, rfl, rfl⟩ := MS_inj hb
rw [show appIn e "rst" r = knownBit false from BitIs.appIn_eq hr r,
show appIn e "start" s = knownBit false from BitIs.appIn_eq hs s, next_idle]
exact ⟨_, _, _, _, _, _, _, _, _, rfl⟩
hold_fin := by
rintro e prev st x - ⟨ck, r, s, a, b, dn, c, k, bz, ash, bsh, rfl, -, rfl⟩ ⟨ck', r', s', a', b', k', ash',
bsh', hb⟩ hr hs
obtain ⟨rfl, rfl, rfl, rfl, rfl, rfl⟩ := MS_inj hb
rw [show appIn e "rst" r = knownBit false from BitIs.appIn_eq hr r,
show appIn e "start" s = knownBit false from BitIs.appIn_eq hs s, next_idle]
exact ⟨_, _, _, _, _, _, _, _, rfl⟩
theorem mm_interface (N W AccW : Nat) (hN : 1 ≤ N) (hW : 1 ≤ W) (hA : 1 ≤ AccW) :
Tasks.Matmul.task.Interface (N, W, AccW) (design.sem N W AccW) := by
rw [mm_sem_eq N W AccW hN hW hA]
exact ⟨rfl, rfl, by
show (["clk", "rst", "start", "A", "B", "done", "C", "k", "busy", "a_sh", "b_sh"] : List String).Nodup
decide⟩
theorem mm_disciplined (N W AccW : Nat) (hN : 1 ≤ N) (hW : 1 ≤ W) (hA : 1 ≤ AccW) :
Tasks.Matmul.task.Disciplined (design.sem N W AccW) := by
rw [mm_sem_eq N W AccW hN hW hA]
refine ⟨by simp [Tasks.Matmul.task, mmD, mmDecls, Design2.inputNames], fun p hp => ?_⟩
have hm : p ∈ mmCtrl N W :: mmPEs N W AccW := by simpa [mmD] using hp
rcases List.mem_cons.mp hm with rfl | hm
· simp [Tasks.Matmul.task, mmCtrl, nb, c32, amtA, amtB, edgeClock, asyncReset, processDataNames, stmtNames,
stmtOptNames, stmtListNames, lhsNames, exprNames]
· obtain ⟨i, j, rfl⟩ := mm_pe_mem hm
simp [Tasks.Matmul.task, mmPE, mulT, sext, edgeClock, asyncReset, processDataNames, stmtNames, stmtOptNames,
lhsNames, exprNames, exprListNames]
/-! ## 8. txtask-v2: every power-up (rule 2), Moore outputs (rule 1), no x-creating construct (rule 3)
Rule 2: the phase model's shape `I` (the register widths, any values) holds at every power-up state
(`mm_powerUp`), and every obligation of the model holds from any state of the shape, so the claim holds
from every power-up (`TxTask.satisfiedAtV2_of_phases`, `SvGolf.SeqV2`); the same cycle gives
termination from every power-up (`mm_terminatesPU`). Rule 1: every process is an `always_ff`
(`mm_profile`), so the design has no combinational dependency at all (`combDeps_of_ffOnly`). Rule 3
(`mm_xsafe`): every expression is a known literal, a declared signal, an in-range constant slice
(`C`'s windows, the operand entries), a concatenation, a replication, a width conversion or `+`, `-`,
`*`, `>>`, `==` on operands of matching widths. -/
/-- **Rule 1.** -/
theorem mm_moore (N W AccW : Nat) (hN : 1 ≤ N) (hW : 1 ≤ W) (hA : 1 ≤ AccW) :
MooreOutDep (design.sem N W AccW) [] := by
rw [mm_sem_eq N W AccW hN hW hA]
exact MooreOutDep.of_noComb (combDeps_of_ffOnly (FFOnly.ofProfile (mm_profile N W AccW)))
(by simp [mmD, mmDecls, Design2.outputNames, Design2.inputNames])
/-- Every power-up state of the design has its shape. -/
theorem mm_powerUp (N W AccW : Nat) (hN : 1 ≤ N) (hW : 1 ≤ W) (hA : 1 ≤ AccW) (st : SvState)
(h : PowerUp (design.sem N W AccW) st) : I N W AccW st := by
rw [mm_sem_eq N W AccW hN hW hA] at h
obtain ⟨ck, r1, rfl, -, -, h1⟩ := powerUp_cons.mp h
obtain ⟨r, r2, rfl, -, -, h2⟩ := powerUp_cons.mp h1
obtain ⟨s, r3, rfl, -, -, h3⟩ := powerUp_cons.mp h2
obtain ⟨a, r4, rfl, -, -, h4⟩ := powerUp_cons.mp h3
obtain ⟨b, r5, rfl, -, -, h5⟩ := powerUp_cons.mp h4
obtain ⟨dn, r6, rfl, w6, -, h6⟩ := powerUp_cons.mp h5
obtain ⟨c, r7, rfl, w7, -, h7⟩ := powerUp_cons.mp h6
obtain ⟨k, r8, rfl, w8, -, h8⟩ := powerUp_cons.mp h7
obtain ⟨bz, r9, rfl, w9, -, h9⟩ := powerUp_cons.mp h8
obtain ⟨ash, r10, rfl, w10, -, h10⟩ := powerUp_cons.mp h9
obtain ⟨bsh, r11, rfl, w11, -, h11⟩ := powerUp_cons.mp h10
obtain rfl := powerUp_nil.mp h11
exact ⟨ck, r, s, a, b, dn, c, k, bz, ash, bsh, rfl, w6, w7, w8, w9, w10, w11⟩
/-- **Termination from every power-up** (the cycle of `mm_terminates`). -/
theorem mm_terminatesPU (N W AccW : Nat) (hN : 1 ≤ N) (hW : 1 ≤ W) (hA : 1 ≤ AccW) :
TerminatesPU (design.sem N W AccW) := by
refine TerminatesPU.of_invariant (I N W AccW) (mm_powerUp N W AccW hN hW hA) (fun σ => ⟨30, ?_⟩)
(by rw [mm_sem_eq N W AccW hN hW hA]; exact mm_noq N W AccW)
rintro e _ kk ⟨ck, r, s, a, b, dn, c, k, bz, ash, bsh, rfl, hw⟩
exact ⟨_, _, mm_cycle N W AccW hN hW hA σ e ck r s a b dn c k bz ash bsh kk 30 (Nat.le_refl _),
mmNext_I N W AccW _ _ _ _ _ dn c k bz ash bsh hw⟩
theorem lvec_l0_known : (⟨#[.l0]⟩ : LVec).allKnown = true := by
show (LVec.ofLogic .l0).allKnown = true
rw [LVec.ofLogic_l0]; exact LVec.allKnown_ofBitVec _
theorem lvec_l1_known : (⟨#[.l1]⟩ : LVec).allKnown = true := by
show (LVec.ofLogic .l1).allKnown = true
rw [LVec.ofLogic_l1]; exact LVec.allKnown_ofBitVec _
/-- The control passes rule 3. -/
theorem ctrl_xsafe (N W AccW : Nat) : xsProc (mmD N W AccW) (mmCtrl N W) = true := by
simp [xsProc, xsStmt, xsStmts, xsOpt, xsLhs, xsWidth, binW, sigWidth, mmCtrl, nb, c32, amtA, amtB, mmD, mmDecls,
lvec_l0_known, lvec_l1_known, LVec.replicate_l0, LVec.ofInt]
/-- A PE passes rule 3: its windows of `C` and of the operand registers are in range. -/
theorem pe_xsafe {N W AccW i j : Nat} (hW : 1 ≤ W) (hi : i < N) (hj : j < N) :
xsProc (mmD N W AccW) (mmPE N W AccW i j) = true := by
have hN : 0 < N := by omega
have hC := Lib.rowMajor_window_le (M := N) (N := N) (w := AccW) hi hj
have wa0 : i * N * W + W ≤ (N * N + 3) * W := by
simpa using winA_le (N := N) (W := W) (i := i) 0 (by omega) hi
have wa1 := winA_le (N := N) (W := W) (i := i) 1 (by omega) hi
have wa2 := winA_le (N := N) (W := W) (i := i) 2 (by omega) hi
have wa3 := winA_le (N := N) (W := W) (i := i) 3 (by omega) hi
have wb0 : j * W + W ≤ (N * N + 3 * N) * W := by
simpa using winB_le (N := N) (W := W) (j := j) 0 (by omega) hj
have wb1 := winB_le (N := N) (W := W) (j := j) 1 (by omega) hj
have wb2 := winB_le (N := N) (W := W) (j := j) 2 (by omega) hj
have wb3 := winB_le (N := N) (W := W) (j := j) 3 (by omega) hj
have sa0 : i * N * W + W - 1 + 1 ≤ (N * N + 3) * W := by omega
have sa1 : (i * N + 1) * W + W - 1 + 1 ≤ (N * N + 3) * W := by omega
have sa2 : (i * N + 2) * W + W - 1 + 1 ≤ (N * N + 3) * W := by omega
have sa3 : (i * N + 3) * W + W - 1 + 1 ≤ (N * N + 3) * W := by omega
have wb1' : (N + j) * W + W ≤ (N * N + 3 * N) * W := by
simpa using winB_le (N := N) (W := W) (j := j) 1 (by omega) hj
have wb2' : (2 * N + j) * W + W ≤ (N * N + 3 * N) * W := winB_le (N := N) (W := W) (j := j) 2 (by omega) hj
have wb3' : (3 * N + j) * W + W ≤ (N * N + 3 * N) * W := winB_le (N := N) (W := W) (j := j) 3 (by omega) hj
have sb0 : j * W + W - 1 + 1 ≤ (N * N + 3 * N) * W := by omega
have sb1 : (N + j) * W + W - 1 + 1 ≤ (N * N + 3 * N) * W := by omega
have sb2 : (2 * N + j) * W + W - 1 + 1 ≤ (N * N + 3 * N) * W := by omega
have sb3 : (3 * N + j) * W + W - 1 + 1 ≤ (N * N + 3 * N) * W := by omega
have sa0 : i * N * W + W - 1 + 1 ≤ (N * N + 3) * W := by omega
have sa1 : (i * N + 1) * W + W - 1 + 1 ≤ (N * N + 3) * W := by omega
have sa2 : (i * N + 2) * W + W - 1 + 1 ≤ (N * N + 3) * W := by omega
have sa3 : (i * N + 3) * W + W - 1 + 1 ≤ (N * N + 3) * W := by omega
simp [xsProc, xsStmt, xsOpt, xsLhs, xsWidth, xsWidthList, binW, sigWidth, mmPE, mulT, sext, mmD, mmDecls,
LVec.replicate_l0, hC, wa0, wa1, wa2, wa3, wb0, wb1', wb2', wb3', sa0, sa1, sa2, sa3, sb0, sb1, sb2, sb3]
theorem mm_pe_mem' {N W AccW : Nat} {p : Process2} (hp : p ∈ mmPEs N W AccW) :
∃ i j, i < N ∧ j < N ∧ p = mmPE N W AccW i j := by
simp only [mmPEs, List.mem_flatMap, List.mem_map, List.mem_range] at hp
obtain ⟨i, hi, j, hj, rfl⟩ := hp
exact ⟨i, j, hi, hj, rfl⟩
/-- **Rule 3.** -/
theorem mm_xsafe (N W AccW : Nat) (hN : 1 ≤ N) (hW : 1 ≤ W) (hA : 1 ≤ AccW) :
XSafe (design.sem N W AccW) := by
rw [mm_sem_eq N W AccW hN hW hA]
refine List.all_eq_true.mpr fun p hp => ?_
have hm : p ∈ mmCtrl N W :: mmPEs N W AccW := by simpa [mmD] using hp
rcases List.mem_cons.mp hm with rfl | hm
· exact ctrl_xsafe N W AccW
· obtain ⟨i, j, hi, hj, rfl⟩ := mm_pe_mem' hm
exact pe_xsafe hW hi hj
/-- **The reference satisfies the matmul statement (txtask-v2) at latency `N`** (`4` at the scoring
point). -/
theorem solution : Goal lat := by
refine ⟨fun p hp => ?_, rfl⟩
obtain ⟨N, W, AccW⟩ := p
obtain ⟨hN, hW, hA, hNW, -⟩ := hp
exact Tasks.Matmul.task.satisfiedAtV2_of_phases (mm_interface N W AccW hN hW hA)
(mm_disciplined N W AccW hN hW hA) (mm_terminatesPU N W AccW hN hW hA)
(show 1 ≤ (N + 3) / 4 by omega) (model N W AccW hN hW hA hNW) (mm_powerUp N W AccW hN hW hA) (mm_moore N W AccW hN hW hA)
(mm_xsafe N W AccW hN hW hA)
end Submission
The netlist
The netlist card shows the sky130 weighing (its cells, its critical path): the ASAP7 view changes the overview above, not this card.
Schematic
drawn at N = 2, W = 2, AccW = 4
Drawn at N = 2, W = 2, AccW = 4, a display instance; scored at N = 4, W = 8, AccW = 32. Only this drawing and the notes under it are at the smaller size: the critical path, the cells and every other number on this page are the scored netlist's.
It is the same design through the same pinned flow at a smaller size (ppa.py --param), so that what it does can be seen: 597 cells here, 38 735 at the scoring point. The register-to-register critical path at N = 2, W = 2, AccW = 4 (14 cells, ending at the flip-flop input _0104_ falling; a period of 2 817 ps there) is in the accent colour. Drawn by netlistsvg with svGolf's sky130 skin (derived from netlistsvg's default skin, as the drawing is: MIT licence, Copyright (c) 2016 Neil Turley, the notice): one symbol per cell function, the cell above it, the instance on hover; nor2b and nand2b are drawn with the bubble on their inverted input. The cells of the path are listed under Critical path, each a link to the cell here.
The netlist at N = 2, W = 2, AccW = 4: 597 cells, 157 and2_1, 156 nand2_1, 64 dfxtp_1, 62 or2_1, 53 xnor2_1, 47 nor2b_1, 36 xor2_1, 12 mux2_1, 8 nand2b_1, 2 nor2_1. There (measured, not ranked): 4 503.07 µm², 2 817 ps. Open the drawing on its own (SVG, 396.4 KB) to pan and zoom it with the browser's own controls.
Critical path
at N = 4, W = 8, AccW = 32 · scored
The path the ranked register-to-register period ends on, at the scoring point N = 4, W = 8, AccW = 32: 67 nets, 66 cells, to the flip-flop input _00345_ falling; the period, 9 410 ps, is the last arrival plus that flip-flop's setup time (ideal clock). Each row is a net of the repaired netlist with the arrival and the transition the STA computed there (NLDM tables, the larger transition carried; no wires). The first row is a flip-flop's Q: its arrival is the flop's clock-to-Q delay after the clock edge. From ppa.py --path (critical_path_timing); the instance names are the scored netlist's, not the schematic's (drawn at a smaller size).
In the schematic, drawn at N = 2, W = 2, AccW = 4, the critical path is 14 cells to the flip-flop input _0104_ falling (a period of 2 817 ps there); each cell is a link to it in the drawing:
\_52_[7]_0847_nor2b_1_0849_xor2_1_0851_xnor2_1_0853_xnor2_1_0854_and2_1_0870_or2_1_0880_xor2_1_0882_xor2_1_0884_xor2_1_0885_nand2_1_0889_and2_1_0899_xnor2_1_0900_nand2_1_0901_and2_1_0104_flip-flop D
every stage (67 rows)
| # | net | edge | driven by | cell | arrival (ps) | transition (ps) | stage (ps) |
|---|---|---|---|---|---|---|---|
| 0 | \_99_[39] | rise | flop Q | – | 718.6 | 631.6 | |
| 1 | _07424_ | rise | _52788_ | and2_1 | 1 000.7 | 137.3 | 282.1 |
| 2 | _07620_ | fall | _52985_ | mux2_1 | 1 389.1 | 99.3 | 388.4 |
| 3 | _07622_ | rise | _52987_ | xor2_1 | 1 589.7 | 190.8 | 200.6 |
| 4 | _07624_ | rise | _52989_ | xor2_1 | 1 806.5 | 190.5 | 216.8 |
| 5 | _07626_ | rise | _52991_ | xor2_1 | 2 023.3 | 190.5 | 216.8 |
| 6 | _07628_ | rise | _52993_ | xor2_1 | 2 240.0 | 190.5 | 216.7 |
| 7 | _07630_ | rise | _52995_ | xor2_1 | 2 456.8 | 190.5 | 216.8 |
| 8 | _07632_ | rise | _52997_ | xor2_1 | 2 673.6 | 190.5 | 216.8 |
| 9 | _07634_ | rise | _52999_ | xor2_1 | 2 890.4 | 190.5 | 216.8 |
| 10 | _07636_ | fall | _53001_ | xor2_1 | 3 005.7 | 85.7 | 115.3 |
| 11 | _07637_ | rise | _53002_ | nand2_1 | 3 082.2 | 46.6 | 76.5 |
| 12 | _07781_ | fall | _53147_ | nand2_1 | 3 149.7 | 55.1 | 67.5 |
| 13 | _07820_ | rise | _53186_ | nand2_1 | 3 205.2 | 46.6 | 55.5 |
| 14 | _07961_ | fall | _53328_ | nand2_1 | 3 272.6 | 55.1 | 67.4 |
| 15 | _07997_ | rise | _53364_ | nand2_1 | 3 328.1 | 46.6 | 55.5 |
| 16 | _08120_ | fall | _53488_ | nand2_1 | 3 395.6 | 55.1 | 67.5 |
| 17 | _08151_ | rise | _53519_ | nand2_1 | 3 451.1 | 46.6 | 55.5 |
| 18 | _08268_ | fall | _53637_ | nand2_1 | 3 518.6 | 55.1 | 67.5 |
| 19 | _08297_ | rise | _53666_ | nand2_1 | 3 574.1 | 46.6 | 55.5 |
| 20 | _08397_ | fall | _53767_ | nand2_1 | 3 641.5 | 55.1 | 67.4 |
| 21 | _08421_ | rise | _53791_ | nand2_1 | 3 697.1 | 46.6 | 55.6 |
| 22 | _08504_ | fall | _53875_ | nand2_1 | 3 764.5 | 55.1 | 67.4 |
| 23 | _08521_ | rise | _53892_ | nand2_1 | 3 814.1 | 39.8 | 49.6 |
| 24 | _08578_ | rise | _53950_ | and2_1 | 4 629.8 | 1051.4 | 815.7 |
| 25 | _08580_ | fall | _53952_ | xnor2_1 | 4 815.5 | 174.8 | 185.7 |
| 26 | _08582_ | rise | _53954_ | xnor2_1 | 5 057.5 | 212.4 | 242.0 |
| 27 | _08584_ | rise | _53956_ | xor2_1 | 5 277.0 | 190.5 | 219.5 |
| 28 | _08586_ | rise | _53958_ | xor2_1 | 5 493.8 | 219.1 | 216.8 |
| 29 | _08588_ | rise | _53960_ | xor2_1 | 5 714.2 | 190.5 | 220.4 |
| 30 | _08590_ | rise | _53962_ | xor2_1 | 5 930.9 | 219.0 | 216.7 |
| 31 | _08592_ | rise | _53964_ | xor2_1 | 6 153.2 | 193.2 | 222.3 |
| 32 | _08593_ | fall | _53965_ | nand2_1 | 6 218.2 | 41.2 | 65.0 |
| 33 | _08597_ | fall | _53970_ | and2_1 | 6 371.3 | 39.9 | 153.1 |
| 34 | _08616_ | fall | _53989_ | or2_1 | 6 600.5 | 57.5 | 229.2 |
| 35 | _08620_ | rise | _53994_ | nand2_1 | 6 693.8 | 81.6 | 93.3 |
| 36 | _08638_ | fall | _54012_ | nand2_1 | 6 743.7 | 46.3 | 49.9 |
| 37 | _08642_ | rise | _54017_ | nand2_1 | 6 831.9 | 81.3 | 88.2 |
| 38 | _08660_ | fall | _54035_ | nand2_1 | 6 881.7 | 46.3 | 49.8 |
| 39 | _08664_ | rise | _54040_ | nand2_1 | 6 969.9 | 81.3 | 88.2 |
| 40 | _08682_ | fall | _54058_ | nand2_1 | 7 014.4 | 41.2 | 44.5 |
| 41 | _08686_ | fall | _54063_ | and2_1 | 7 167.5 | 39.9 | 153.1 |
| 42 | _08705_ | fall | _54082_ | or2_1 | 7 396.8 | 57.5 | 229.3 |
| 43 | _08708_ | rise | _54086_ | nand2_1 | 7 490.0 | 81.6 | 93.2 |
| 44 | _08749_ | fall | _54128_ | nand2_1 | 7 539.9 | 34.9 | 49.9 |
| 45 | _08750_ | rise | _54129_ | nand2_1 | 7 622.3 | 80.3 | 82.4 |
| 46 | _08751_ | fall | _54130_ | nand2_1 | 7 675.6 | 53.7 | 53.3 |
| 47 | _08754_ | rise | _54134_ | nand2_1 | 7 767.1 | 81.3 | 91.5 |
| 48 | _08795_ | fall | _54176_ | nand2_1 | 7 817.0 | 34.8 | 49.9 |
| 49 | _08796_ | rise | _54177_ | nand2_1 | 7 900.2 | 81.5 | 83.2 |
| 50 | _08797_ | fall | _54178_ | nand2_1 | 7 953.8 | 54.1 | 53.6 |
| 51 | _08801_ | rise | _54183_ | nand2_1 | 8 045.5 | 81.4 | 91.7 |
| 52 | _08819_ | fall | _54201_ | nand2_1 | 8 095.4 | 46.3 | 49.9 |
| 53 | _08823_ | rise | _54206_ | nand2_1 | 8 183.5 | 81.3 | 88.1 |
| 54 | _08841_ | fall | _54224_ | nand2_1 | 8 233.4 | 46.3 | 49.9 |
| 55 | _08844_ | rise | _54228_ | nand2_1 | 8 321.6 | 81.3 | 88.2 |
| 56 | _08885_ | fall | _54270_ | nand2_1 | 8 371.4 | 34.8 | 49.8 |
| 57 | _08886_ | rise | _54271_ | nand2_1 | 8 453.8 | 80.3 | 82.4 |
| 58 | _08887_ | fall | _54272_ | nand2_1 | 8 507.1 | 53.7 | 53.3 |
| 59 | _08891_ | rise | _54277_ | nand2_1 | 8 598.6 | 81.3 | 91.5 |
| 60 | _08909_ | fall | _54295_ | nand2_1 | 8 648.5 | 46.3 | 49.9 |
| 61 | _08913_ | rise | _54300_ | nand2_1 | 8 736.6 | 81.3 | 88.1 |
| 62 | _08931_ | fall | _54318_ | nand2_1 | 8 781.1 | 41.2 | 44.5 |
| 63 | _08934_ | fall | _54322_ | and2_1 | 8 937.3 | 42.8 | 156.2 |
| 64 | _08946_ | rise | _54334_ | xnor2_1 | 9 074.7 | 110.8 | 137.4 |
| 65 | _08947_ | fall | _54335_ | nand2_1 | 9 129.5 | 119.4 | 54.8 |
| 66 | _00345_ | fall | _54336_ | and2_1 | 9 306.9 | 31.0 | 177.4 |
Cells
at N = 4, W = 8, AccW = 32 · scored
| cell | count | µm² each | µm² in all | share |
|---|---|---|---|---|
xor2_1 | 9 562 | 8.7584 | 83747.8208 | 34.6% |
nand2_1 | 17 596 | 3.7536 | 66048.3456 | 27.3% |
and2_1 | 4 955 | 6.2560 | 30998.4800 | 12.8% |
xnor2_1 | 2 792 | 8.7584 | 24453.4528 | 10.1% |
dfxtp_1 | 800 | 20.0192 | 16015.3600 | 6.6% |
or2_1 | 1 464 | 6.2560 | 9158.7840 | 3.8% |
nor2b_1 | 694 | 6.2560 | 4341.6640 | 1.8% |
mux2_1 | 373 | 11.2608 | 4200.2784 | 1.7% |
nand2b_1 | 423 | 6.2560 | 2646.2880 | 1.1% |
nor2_1 | 45 | 3.7536 | 168.9120 | 0.1% |
inv_1 | 25 | 3.7536 | 93.8400 | 0.0% |
buf_6 | 2 | 11.2608 | 22.5216 | 0.0% |
buf_4 | 2 | 7.5072 | 15.0144 | 0.0% |
buf_2 | 2 | 5.0048 | 10.0096 | 0.0% |
| total | 38 735 | 241920.7712 |
Repair (buffer_trees_v1): 6 buffers on 5 nets, 0 tie cells, 2 weighing rounds; nothing is resized.
Legality: worst load 0.985 × max_capacitance, worst transition 0.99 × max_transition; 0 timing-table reads outside the characterized grid.
the gate mix of the extra GE column (the pinned GE table, score/ge_seq.json)
| cell | count | GE each | GE total |
|---|---|---|---|
NAND | 17596 | 1.000 | 17596.000 |
XOR | 9562 | 2.000 | 19124.000 |
AND | 4955 | 1.333 | 6605.015 |
XNOR | 2792 | 2.000 | 5584.000 |
OR | 1464 | 1.333 | 1951.512 |
DFF_P | 800 | 5.333 | 4266.400 |
ANDNOT | 694 | 1.333 | 925.102 |
ORNOT | 423 | 1.333 | 563.859 |
MUX | 373 | 2.333 | 870.209 |
NOR | 45 | 1.000 | 45.000 |
NOT | 25 | 0.667 | 16.675 |
| total | 38729 | 57547.772 |
The record
- task
matmul- player
- fanout-ferret
- assisted by
- Claude Sonnet 5.5
- submitted
- 2026-11-10
- notes
- four multiply-accumulate steps per PE per cycle from zero-padded captured operands (a_sh, b_sh), latency ceil(N/4) = 1 at N=4; variation of the shift-register reference
- scoring point
- AccW = 32, N = 4, W = 8
- design
solution.svsha256b6357f45fe9705bc9c9fabfb23bc720837ad203ff17ccb91cdf0aca5e4e8378a- proof
Sol.leansha256e6396e30e6ddc18f7312b5271fc9bb425e783335b5a90b83262d95872fd1c195- canonical text
- sha256
c42606bf5727e11c4627ca5b479819501bf8b2735be95e487e2588b9e15376dc - record hash
1ffe71156e9098006a54a5bd9ab0eb00cd103380b962c7b31482b93853668a1d- downloads
1ffe71156e90.sv1ffe71156e90.lean1ffe71156e90.json- previous frontier
- none: no submission of this task is dated before this one; 3 other submissions are dated the same day (2026-11-10), which does not order them
The proof
- code lines
- 855
- build time
- 10.72 s
- axioms
Classical.choice,Quot.sound,propext- tactics seen
decide,induction,omega,simp- imports
Submission.Design,LeanModels.Sv.Gen2,LeanModels.Sv.Hier2Gen,LeanModels.Sv.SeqProfile,LeanModels.Sv.SeqFamily,LeanModels.Sv.Known2,LeanModels.Sv.Lib.BitVecArith,LeanModels.Sv.CombViewArith,LeanModels.Sv.Mono2,SvGolf.SeqV2
Checker and flow
Checked under the pinned flow of this site's boards.
- Yosys
Yosys 0.53 (git sha1 53c22ab7c0ced80861c7536c5dae682c30fb5834, clang++ 17.0.0 -fPIC -O3)- Verilator
Verilator 5.042 2025-11-02 rev UNKNOWN.REV- Lean toolchain
leanprover/lean4:v4.33.0-rc1- svGolf revision
892ba94b00e36c0f83e15b4acf3641cee47d43c8- lean-surfaces revision
892ba94b00e36c0f83e15b4acf3641cee47d43c8- canonicalization script
bbd11227d45dc1365f27597fd45c565fe721ba2648450588fc4fd983d00b367b- scoring script
c3874dbfc85921a87df6eacdfc6b2c9a0e2b34edd5738951e0d8bdb51f4558b2- GE table
0a1aa1147b6916efa4a41e03aa3c8c70e68c1e3fd7a0e9c9a3cbc177edefd1f6- extractor
4930696021c588c75dc890034c534c95560ad9a28a4955b87d1f8d68b5edbc0c- canonical RTLIL
4b67fca3c7fa62216d1b9c035ce3658b457665e8a11399fb90fb2cb019386e1c
the checker's files (sha256)
| file | sha256 |
|---|---|
check.py | b0c939438a765c2ffeb679721c83ced6213c0a96bfef84759948d3857db9f431 |
check/ | 936f97636d6256374eaa4435d03321eeff29e22f808a72c7781375ab648600e8 |
check/ | fb51d72510b515460866c2798aad3c6f55295536e627b92854c3580af215bb91 |
check/ | 73856a45905724544247ad29042bdc2f1778271ec07b15cdb3a5f214685165c1 |
check/ | 076db01a9791bcc25ee84e9b0033604a4b675f00b212eab0c864078e6a4748b7 |
check/ | dd89805f9e8335ab15d6bb753eea76865bce47e84ccffa7e7280c91da8131d23 |
lakefile.toml | f35a88404367d22906effc14d644aac9a9cad47dfd3b704ea8fec88acfaf9c4d |
lean-toolchain | 62c2d9c0fc1ec4c67e151c11eff41ca004ef38e179cf9476c230406e6defedef |
score/ | f18c14c23a14d55db63e202f42cc424dc84c1dd1c337f132ad60859dc9d5d33a |
score/ | bbd11227d45dc1365f27597fd45c565fe721ba2648450588fc4fd983d00b367b |
score/ | 95332451ae3754226fb87b46027385b46a91121e7eb9ae25b302bce756d90475 |
score/ | 6a29196a3f4b58241f4c9af650c9141e4632b737214d563a11ba142a725c4bea |
score/ | 0a1aa1147b6916efa4a41e03aa3c8c70e68c1e3fd7a0e9c9a3cbc177edefd1f6 |
score/ | 912031912c3e3f6790725161753e95415df34846bd8c27dde3eb707747ddbcbe |
score/ | 93547b343dbac135737e98516b1004a780e1cf2f579e1bd7fed82c908c6d4125 |
score/ | 4b8688ba3f4d67681658e5944afe878592adf588cd416a8b47597ebe06deb045 |
score/ | d66a6086a37a97f5985266057051979fb6a8a3c557d3ef9c42acc1b5e41502d8 |
score/ | 71d2e3b82f8813ec64a58f0a46909ca23dfab377db44eda6f294b713f45ae781 |
score/ | 11952f61058051c9e8de91a801b672d07e66a29308e978156c04ffd9f0dbef45 |
score/ | ea58219d8ef14c3bad265218ee266ccdb709ac9d4dc5258edc34d51ee83a0445 |
score/ | c3874dbfc85921a87df6eacdfc6b2c9a0e2b34edd5738951e0d8bdb51f4558b2 |
Post-synthesis check
At each point below, the check ran the scored netlist and ls-sv's reading of the design 8 times, once per seed, each run from a power-up state and through a seeded stimulus of 24 transactions. Each point ran against its own latency, the solution's lat at that point (the table's latency column): every transaction had to finish within it. The check found no mismatch. The stimulus is a sample of the inputs: a guard against the two tools disagreeing, not a proof.
- mode
- full
- points
- AccW = 8, N = 2, W = 4, AccW = 32, N = 4, W = 8 (the scoring point)
- seeds
- 8 seeds per point: seed 0 from the all-zeros power-up, every other one from a random power-up drawn from its seed (below)
- stimulus
- 24 transactions per seed
- clock cycles
- 1 317 in all
- mismatches
- 0
- salted
- no (the default empty salt: anyone can run this exact stimulus again)
- scored netlist
- sha256
6b3b742b53c0cd46ae887237efc4a8466fdac91b903abfdb0b39ae6876726ef2
| point | latency (cycles) | seeds | clock cycles | transactions | mismatches | netlist sha256 |
|---|---|---|---|---|---|---|
| AccW = 8, N = 2, W = 4 | 1 | 8 | 636 | 192 | 0 | 20b6953d6b71 |
| AccW = 32, N = 4, W = 8 | 1 | 8 | 681 | 192 | 0 | 6b3b742b53c0 |
every seed's run (16)
A seed's draw is sha256(salt, task, point, seed) (its first 12 digits below; check/postsynth.py seed_draw). Everything random in the seed's run comes from it: the stimulus, ls-sv's power-up (zeros for seed 0, else seed:<k>) and the netlist's Verilator randSeed, which the all-zeros power-up of seed 0 does not use.
| point | seed | ls-sv power-up | netlist randSeed | clock cycles | transactions | draw |
|---|---|---|---|---|---|---|
| AccW = 8, N = 2, W = 4 | 0 | zeros | 303973935 | 85 | 24 | 4ed5c6f8253a |
| AccW = 8, N = 2, W = 4 | 1 | seed:6761245804292904090 | 1195438325 | 85 | 24 | 82e6921a1035 |
| AccW = 8, N = 2, W = 4 | 2 | seed:845291531553475423 | 391528801 | 88 | 24 | 2b060ddd6dc0 |
| AccW = 8, N = 2, W = 4 | 3 | seed:5147191549726746004 | 1397868753 | 73 | 24 | 37b381d742d3 |
| AccW = 8, N = 2, W = 4 | 4 | seed:3814886047666414678 | 1168828466 | 72 | 24 | 93e4b866922d |
| AccW = 8, N = 2, W = 4 | 5 | seed:3507955749274142316 | 192587499 | 78 | 24 | 8c2d8181b840 |
| AccW = 8, N = 2, W = 4 | 6 | seed:9665391063568079335 | 688604025 | 77 | 24 | 7c2b8adaae80 |
| AccW = 8, N = 2, W = 4 | 7 | seed:11035912927779587285 | 825733514 | 78 | 24 | 1db8b9c4c23c |
| AccW = 32, N = 4, W = 8 | 0 | zeros | 1946689348 | 79 | 24 | e3ef9e3638b5 |
| AccW = 32, N = 4, W = 8 | 1 | seed:9734188592010554240 | 1063280839 | 96 | 24 | 2132be692d56 |
| AccW = 32, N = 4, W = 8 | 2 | seed:6594409222167094000 | 88936619 | 88 | 24 | 0371e783230d |
| AccW = 32, N = 4, W = 8 | 3 | seed:15091031972466303227 | 590535838 | 79 | 24 | ed721e3e3e18 |
| AccW = 32, N = 4, W = 8 | 4 | seed:17087008212923660376 | 707745727 | 90 | 24 | fcdbab7c9305 |
| AccW = 32, N = 4, W = 8 | 5 | seed:13568177433463839254 | 834142182 | 80 | 24 | ccccf631a1d2 |
| AccW = 32, N = 4, W = 8 | 6 | seed:2839497944125262158 | 1252134952 | 90 | 24 | f1dc560f7767 |
| AccW = 32, N = 4, W = 8 | 7 | seed:8076188500603944899 | 547904091 | 79 | 24 | 831e3fb7b1cd |
Area against time
- submission
- proved reference
- the Pareto front of the entries
- this design
- par's score: area × time = 3 729 636 µm²·ns
the designs as numbers, by score
| design | kind | area (µm²) | time (ps) | score (µm²·ns) |
|---|---|---|---|---|
| fanout-ferret · this design | submission | 241 920.77 | 9 410 | 2 276 474 |
| vacuity-check | submission | 85 260.52 | 27 880 | 2 377 063 |
| fanout-ferret | submission | 137 103.99 | 18 318 | 2 511 471 |
| vacuity-check | submission | 79 700.19 | 42 756 | 3 407 661 |
matmul_g1 | proved reference · par | 87 173.61 | 42 784 | 3 729 636 |
- submission
- proved reference
- the Pareto front of the entries
- this design
- par's score: area × time = 8217 µm²·ns
the designs as numbers, by score
| design | kind | area (µm²) | time (ps) | score (µm²·ns) |
|---|---|---|---|---|
| fanout-ferret · this design | submission | 3 661.548 | 1 621 | 5935 |
| vacuity-check | submission | 1 315.583 | 4 905 | 6453 |
| fanout-ferret | submission | 2 085.873 | 3 126 | 6520 |
| vacuity-check | submission | 1 216.745 | 5 992 | 7291 |
matmul_g1 | proved reference · par | 1 334.726 | 6 156 | 8217 |
Reproduce and verify
From svgolf/, with the pinned tools (reproduce a score lists them):
# the ranked numbers, at the scoring point (from svgolf/, with the pinned Yosys and liberty)
# a clocked task: the clocked flow, --seq (the time is the register-to-register period)
python3.12 score/ppa.py 1ffe71156e9098006a54a5bd9ab0eb00cd103380b962c7b31482b93853668a1d.sv --task tasks/matmul --seq --path \
--json out.json --netlist scored.v --repaired-netlist repaired.v
Expect, in out.json:
- score.rtlil_sha256
4b67fca3c7fa62216d1b9c035ce3658b457665e8a11399fb90fb2cb019386e1c- ppa.netlist_sha256
6b3b742b53c0cd46ae887237efc4a8466fdac91b903abfdb0b39ae6876726ef2- ppa.repair.netlist_sha256
39483feb34278fe8050da9f2eb7bad97aea85adae0ca47093d1b6edf31d8ac95- ppa.area_e4um2_sky130
2419207712- ppa.delay_ps_sky130
9410
The schematic and the critical-path data are site/schematics/matmul/b6357f45fe9705bc.json and its .svg, written by site/gen_schematics.py (the display instance is ppa.py ... --param AccW=4 --param N=2 --param W=2 --repaired-netlist, drawn by netlistsvg 1.0.2; repaired netlist sha256 1af1be441bd645b1…).
From svgolf/, with the pinned tools (reproduce a score lists them):
# the ASAP7 view's numbers, at the scoring point (from svgolf/, with the pinned Yosys; once:
# python3.12 score/ppa.py --pdk asap7 --fetch-liberty, the five pinned ASAP7 files into score/lib/asap7/)
python3.12 score/ppa.py 1ffe71156e9098006a54a5bd9ab0eb00cd103380b962c7b31482b93853668a1d.sv --task tasks/matmul --seq --pdk asap7 --no-mapped \
--json out.json
Expect, in out.json:
- ppa.netlist_sha256
6b3b742b53c0cd46ae887237efc4a8466fdac91b903abfdb0b39ae6876726ef2- ppa.repair.netlist_sha256
e7495556c291dd78cc3d61e359cb405014b8b58e788d4aa5568ad26b3971eed2- ppa.area_e5um2_asap7
366154830- ppa.delay_ps_asap7
1621
leaderboard/asap7/matmul.json keeps these numbers; python3.12 leaderboard/leaderboard.py asap7 --rescore --task matmul weighs every design of the task again and compares, bit for bit.
Verify the record
Download the three files, then from svgolf/:
# re-run the whole check on this record's files (the cost of the original check)
H=1ffe71156e9098006a54a5bd9ab0eb00cd103380b962c7b31482b93853668a1d
mkdir -p rec/matmul && cp "$H.json" "$H.sv" "$H.lean" rec/matmul/
python3.12 leaderboard/leaderboard.py verify --data-dir rec --task matmul --full
verify --offline checks only the hashes and the arithmetic; plain verify also rescores the design (no Lean; seconds for most designs, a minute or more for the largest), on sky130 and on ASAP7.