LeanModels.Sv.Comb2
ls-sv semantics
- Lines
- 355
- file
LeanModels/Sv/Comb2.lean- sha256
553d6eeecf42a665c5d0e26555c6abeca3903cabd27604b12e1fe32801896a53- statements that rely on it
_example,add,aes_sbox,arbiter,clmul,fdivp,matmul,mul,sorter- download
LeanModels.Sv.Comb2.lean
LeanModels/Sv/Comb2.leanLeanModels.Sv.Comb2.lean
import LeanModels.Sv.Comb2Sound
import LeanModels.Sv.Comb2Term
/-!
# Combinational designs at run level (`LeanModels.Sv` — Design2)
The run-level layer of the combinational settle theorem (the
`Comb2Base`/`Comb2Sound`/`Comb2Term`/`Comb2` layer): what a challenge
statement about a comb design consumes. `Comb2Sound.lean` proved that a
successful settle (and a successful comb-only cycle) lands on a solution of
the design's equations;
`Comb2Term.lean` proved that, under an acyclicity certificate, every settle
returns. This file lifts both through `Runs2` / `⊨₂` / `Terminates2`
(`Spec2.lean`), so a correctness theorem about a comb design is stated and
proved against the fixpoint equations `CombEqs` alone — no fuel, no schedule,
no interpreter step in sight.
## API index
* Snapshots of a completed run (`Runs2`):
`Runs2.comb_shape` (every snapshot has the comb shape),
`Runs2.comb_snapshot` — **the snapshot theorem**: every snapshot satisfies
`CombEqs` and `StateShape`, and its input ports hold whatever the stimulus
entry of that cycle supplied;
`Runs2.comb_snapshot_step` — the full characterization (every undriven
signal of snapshot `c` is that of the previous snapshot with cycle `c`'s
inputs applied) and its corollary `Runs2.comb_held` (an undriven signal the
stimulus does not supply keeps its previous value).
* `⊨₂` introduction: `Models2.of_comb` (the basic form: the property
follows from the per-snapshot equations and shape),
`Models2.of_comb_inputs` (the hypothesis also receives the per-cycle input
agreement), `Models2.of_comb_pointwise` (a per-state property that follows
from `CombEqs`/`StateShape` holds of every snapshot).
* Non-vacuity: `cycleStep2_comb_ok` (a comb-only cycle succeeds from every
shaped state at every fuel from `Design2.settleFuel` on, and keeps the
shape) and `Terminates2.of_acyclic` — **the termination theorem**
`CombDesign d → Acyclic d → Terminates2 d` (dotted like `Spec2`'s
`Terminates2.of_invariant`; `terminates2_of_acyclic` is the same theorem
under a root name).
* Reading the equations of one process: `LVec.extract_full` (the window
`[0, v.width)` of `v` is `v`), `CombEqs.lookup_ident` (a
whole-signal assign: the signal IS its resized right-hand side),
`CombEqs.extract_slice` (a slice assign: the window holds it),
`CombEqs.extract_index` (a constant-index assign), all three through
`CombEqs.extract_window` (any leaf target); for a CONCATENATED target
`CombEqs.concat_part` (each placed window holds its slice of the
right-hand side resized to the target's total width) and
`CombEqs.concat_part_ident` (a whole-signal part IS that slice) —
`Comb2ConcatExample.lean` is the worked example.
## Design laws
* **Fuel never reaches a statement.** Every theorem here quantifies over
`Runs2` / `⊨₂` / `Terminates2`; the one fuel-carrying lemma,
`cycleStep2_comb_ok`, is the step of the `Terminates2.of_invariant`
construction, at the explicit bound `Design2.settleFuel`.
* **One invariant: `StateShape`.** `initState2 d` has it
(`StateShape.of_initState2`, through the `CombDesign.initWidth` field),
`applyInputs2` keeps it (inputs are never driven), and every successful
settle keeps it (`combSettle2_eqs`). The comb-only cycle is input settle
then post-edge settle (`cycleStep2_of_comb`: no reset processes, the edge
pass runs over `σ.choose k [] = []`, the NBA queue is empty), so the
snapshot is the second settle's result and the shape flows through every
cycle. Snapshot facts are proved by induction on the cycle index
(`Runs2.forall_of_step`), not by unfolding the run.
* **Value-agnostic.** Like the other `Comb2*` files, this one is 4-state: no
`BitVec`, no `Known2`. The known-value reading of the equations is the
consumer's business (`Comb2Example.lean` shows the pattern on a full adder).
## Deviations from the original statement
* None in the statements of `Models2.of_comb` and `Terminates2.of_acyclic`.
`Runs2.comb_snapshot` states its third conjunct (originally the
placeholder "inputs of `st` = inputs applied from `stim` at cycle `c`, held
otherwise") as: for the stimulus entry `inputs` of cycle `c`, every input
port the entry binds holds that value. The "held otherwise" half is the
separate `Runs2.comb_held`, and the exact undriven-signal equation is
`Runs2.comb_snapshot_step`.
-/
namespace LeanModels.Sv
variable {d : Design2}
/-! ## Snapshots of a completed comb run -/
/-- Every snapshot of a completed run of a comb design has the comb shape. -/
theorem Runs2.comb_shape {σ : ScheduleOracle} {stim tr : List SvState} (hd : CombDesign d)
(hr : Runs2 d σ stim tr) :
∀ (c : Nat) (st : SvState), tr[c]? = some st → StateShape d st :=
hr.forall_of_step (I := StateShape d) (StateShape.of_initState2 hd)
(fun _ _ _ _ _ _ hprev h => (cycleStep2_eqs hd hprev h).2.1)
/-- The state a cycle starts from (`initState2 d` at cycle 0, the previous
snapshot afterwards) has the comb shape. -/
private theorem Runs2.comb_prev_shape {σ : ScheduleOracle} {stim tr : List SvState}
(hd : CombDesign d) (hr : Runs2 d σ stim tr) {c : Nat} {prev : SvState}
(hp : (initState2 d :: tr)[c]? = some prev) : StateShape d prev := by
cases c with
| zero =>
simp only [List.getElem?_cons_zero, Option.some.injEq] at hp
subst hp
exact StateShape.of_initState2 hd
| succ c =>
simp only [List.getElem?_cons_succ] at hp
exact hr.comb_shape hd c prev hp
/-- **Snapshot characterization.** Snapshot `c` of a completed run of a comb
design satisfies the equations and the shape, and every undriven signal
(inputs, constants, undriven nets) holds its value in the previous snapshot
(`initState2 d` at cycle 0) after cycle `c`'s stimulus entry was applied. -/
theorem Runs2.comb_snapshot_step {σ : ScheduleOracle} {stim tr : List SvState}
(hd : CombDesign d) (hr : Runs2 d σ stim tr) {c : Nat} {st : SvState}
(hc : tr[c]? = some st) :
∃ inputs prev, stim[c]? = some inputs ∧ (initState2 d :: tr)[c]? = some prev ∧
StateShape d prev ∧ CombEqs d st ∧ StateShape d st ∧
∀ n, NotDriven d n →
SvState.lookup st n = SvState.lookup (applyInputs2 d inputs prev) n := by
obtain ⟨_, inputs, prev, _, _, hi, hp, hs⟩ := hr.snapshot_step hc
have hprev := Runs2.comb_prev_shape hd hr hp
obtain ⟨heqs, hsh, hnd⟩ := cycleStep2_eqs hd hprev hs
exact ⟨inputs, prev, hi, hp, hprev, heqs, hsh, hnd⟩
/-- **Every snapshot of a comb run solves the equations** (the snapshot
theorem). Snapshot `c` of a completed run (any schedule, any stimulus)
satisfies `CombEqs` and `StateShape`, and every input port that cycle `c`'s
stimulus entry binds holds exactly that value (the profile's
`CombDesign.inFourState`: a 2-state input would squash x/z). The "held otherwise" half —
an undriven signal the entry does not supply keeps its previous value — is
`Runs2.comb_held`; the exact undriven-signal equation is
`Runs2.comb_snapshot_step`. -/
theorem Runs2.comb_snapshot {σ : ScheduleOracle} {stim tr : List SvState} (hd : CombDesign d)
(hr : Runs2 d σ stim tr) {c : Nat} {st : SvState} (hc : tr[c]? = some st) :
CombEqs d st ∧ StateShape d st ∧
∀ inputs, stim[c]? = some inputs → ∀ n ∈ d.inputNames.toList, ∀ v,
SvState.lookup inputs n = some v → SvState.lookup st n = some v := by
obtain ⟨inputs, prev, hi, -, -, heqs, hsh, hnd⟩ := hr.comb_snapshot_step hd hc
refine ⟨heqs, hsh, fun inputs' hi' n hn v hv => ?_⟩
rw [hi, Option.some.injEq] at hi'
subst hi'
rw [hnd n (hd.notDriven_of_input hn), lookup_applyInputs2 d inputs prev n hd.inFourState,
if_pos hn, hv]
rfl
/-- **Held otherwise.** An undriven signal that cycle `c`'s stimulus entry
does not supply (not an input port, or not bound by the entry) keeps its
value from the previous snapshot (`initState2 d` at cycle 0). -/
theorem Runs2.comb_held {σ : ScheduleOracle} {stim tr : List SvState} (hd : CombDesign d)
(hr : Runs2 d σ stim tr) {c : Nat} {st inputs prev : SvState} (hc : tr[c]? = some st)
(hi : stim[c]? = some inputs) (hp : (initState2 d :: tr)[c]? = some prev) {n : String}
(hn : NotDriven d n) (hns : n ∉ d.inputNames.toList ∨ SvState.lookup inputs n = none) :
SvState.lookup st n = SvState.lookup prev n := by
obtain ⟨inputs', prev', hi', hp', -, -, -, hnd⟩ := hr.comb_snapshot_step hd hc
rw [hi, Option.some.injEq] at hi'
rw [hp, Option.some.injEq] at hp'
subst hi' hp'
rw [hnd n hn, lookup_applyInputs2 d inputs prev n hd.inFourState]
by_cases hin : n ∈ d.inputNames.toList
· rw [if_pos hin, hns.resolve_left (fun h => h hin)]
rfl
· rw [if_neg hin]
/-! ## `⊨₂` introduction for comb designs -/
/-- **`⊨₂` from the equations** (the basic form). A stimulus/trace
property that follows from "every snapshot satisfies `CombEqs` and
`StateShape`" holds of every completed run of a comb design, under every
schedule. -/
theorem Models2.of_comb (hd : CombDesign d) {P : List SvState → List SvState → Prop}
(hP : ∀ stim tr,
(∀ (c : Nat) (st : SvState), tr[c]? = some st → CombEqs d st ∧ StateShape d st) →
P stim tr) :
d ⊨₂ spec2 P :=
fun _ stim tr hr => hP stim tr fun _ _ hc =>
let h := hr.comb_snapshot hd hc
⟨h.1, h.2.1⟩
/-- **`⊨₂` from the equations and the applied inputs.** `Models2.of_comb`
whose hypothesis also receives, per cycle, that every input port bound by the
cycle's stimulus entry holds that value in the snapshot. -/
theorem Models2.of_comb_inputs (hd : CombDesign d) {P : List SvState → List SvState → Prop}
(hP : ∀ stim tr,
(∀ (c : Nat) (st : SvState), tr[c]? = some st → CombEqs d st ∧ StateShape d st ∧
∀ inputs, stim[c]? = some inputs → ∀ n ∈ d.inputNames.toList, ∀ v,
SvState.lookup inputs n = some v → SvState.lookup st n = some v) →
P stim tr) :
d ⊨₂ spec2 P :=
fun _ stim tr hr => hP stim tr fun _ _ hc => hr.comb_snapshot hd hc
/-- **Pointwise `⊨₂`.** A property of single states that follows from
`CombEqs` and `StateShape` holds of every snapshot of every completed run. -/
theorem Models2.of_comb_pointwise (hd : CombDesign d) {Q : SvState → Prop}
(hQ : ∀ st, CombEqs d st → StateShape d st → Q st) :
d ⊨₂ spec2 fun _ tr => ∀ (c : Nat) (st : SvState), tr[c]? = some st → Q st :=
Models2.of_comb hd fun _ _ h c st hc => hQ st (h c st hc).1 (h c st hc).2
/-! ## Termination -/
/-- **A comb-only cycle succeeds and keeps the shape**: on a comb design
with an acyclicity certificate, from every shaped state, every `cycleStep2`
(any schedule, inputs, and schedule counter) at fuel `≥ Design2.settleFuel d`
returns `.ok` into a shaped state. -/
theorem cycleStep2_comb_ok (hd : CombDesign d) (ha : Acyclic d) {fuel : Nat}
(hf : d.settleFuel ≤ fuel) (σ : ScheduleOracle) (inputs st : SvState) (k : Nat)
(hs : StateShape d st) :
∃ S k', cycleStep2 d σ fuel inputs st k = .ok (S, k') ∧ StateShape d S := by
have hs0 := StateShape.of_applyInputs2 hd hs inputs
obtain ⟨S1, k1, h1⟩ := combSettle2_terminates_of_le hd ha hf σ _ k hs0
have hs1 := (combSettle2_eqs hd hs0 h1).2.1
obtain ⟨S2, k2, h2⟩ := combSettle2_terminates_of_le hd ha hf σ S1 (k1 + 1) hs1
refine ⟨S2, k2, ?_, (combSettle2_eqs hd hs1 h2).2.1⟩
rw [cycleStep2_of_comb hd, h1, Res.ok_bind]
exact h2
/-- **Acyclic comb designs terminate** (the termination theorem): every
schedule and every stimulus has a completed run. Together with
`Models2.of_comb` this makes a `⊨₂` theorem about a comb design non-vacuous.
Named like `Spec2`'s `Terminates2.of_invariant`, the general way to build
`Terminates2`; `terminates2_of_acyclic` is the same theorem under a root
name. -/
theorem Terminates2.of_acyclic (hd : CombDesign d) (ha : Acyclic d) : Terminates2 d :=
Terminates2.of_invariant (d := d) (StateShape d) (StateShape.of_initState2 hd) (fun σ =>
⟨d.settleFuel, fun inputs st k hs =>
cycleStep2_comb_ok hd ha (Nat.le_refl _) σ inputs st k hs⟩) (by
simp only [List.any_eq_false]
intro p hp
obtain ⟨i, hi⟩ := List.mem_iff_getElem?.mp hp
have hget : d.processes[i]? = some p := by
rw [Array.getElem?_toList] at hi
exact hi
obtain ⟨t, e, ws, hpe, hws⟩ := hd.assign i p hget
cases hpe
simp [Process2.hasQualifiedCase])
/-- Root-name alias of `Terminates2.of_acyclic` (acyclic comb designs
terminate). -/
theorem terminates2_of_acyclic (hd : CombDesign d) (ha : Acyclic d) : Terminates2 d :=
Terminates2.of_acyclic hd ha
/-! ## Reading one equation -/
/-- The full window — starting at bit 0, as wide as the vector — is the
vector itself. -/
theorem LVec.extract_full (v : LVec) : v.extract 0 v.width = v := by
apply LVec.ext_getD (by simp)
intro i hi
simp only [LVec.width_extract] at hi
rw [LVec.getD_extract, if_pos hi, Nat.zero_add]
/-- **A leaf assign, read back** (the common core of the three leaf forms
below). In a solution of the equations, the window `(n, lo, w)` of a leaf
target holds the right-hand side's value resized to `w`. -/
theorem CombEqs.extract_window {st : SvState} (heqs : CombEqs d st) {i : Nat} {t : Lhs2}
{e : Expr2} (hp : d.processes[i]? = some (.assign t e)) {n : String} {lo w : Nat}
(ht : t.window? d = some (n, lo, w)) {v : LVec} (hv : evalPure2 st e = .ok v) :
∃ old, SvState.lookup st n = some old ∧ old.extract lo w = v.resizeU w := by
have hwins := Lhs2.wins_of_window? ht
obtain ⟨old, v', hold, hv', hext⟩ := heqs i t e n lo w 0 hp (by simp [hwins])
rw [hv, Res.ok.injEq] at hv'
subst hv'
refine ⟨old, hold, ?_⟩
rw [hext, hwins]
simp only [Win.sumWidth_cons, Win.sumWidth_nil, Nat.add_zero]
exact LVec.extract_zero_of_width (LVec.width_resizeU _ _)
/-- **A whole-signal assign, read back.** In a shaped solution of the
equations, the target of `assign n = e` holds `e`'s value resized to `n`'s
declared width. -/
theorem CombEqs.lookup_ident (hd : CombDesign d) {st : SvState} (heqs : CombEqs d st)
(hs : StateShape d st) {i : Nat} {n : String} {e : Expr2}
(hp : d.processes[i]? = some (.assign (.ident n) e)) {v : LVec} (hv : evalPure2 st e = .ok v)
{w : Nat} (hw : d.declWidth n = some w) : SvState.lookup st n = some (v.resizeU w) := by
have hwin : (Lhs2.ident n).window? d = some (n, 0, w) := by simp [Lhs2.window?, hw]
obtain ⟨old, hold, hext⟩ := heqs.extract_window hp hwin hv
obtain ⟨old', hold', -, hdw⟩ := hd.lookup_writes hs (Design2.writes_of_window? hp hwin)
rw [hold] at hold'
cases hold'
rw [hw, Option.some.injEq] at hdw
subst hdw
rw [hold, ← hext, LVec.extract_full]
/-- **A slice assign, read back.** In a solution of the equations, the
window `[lo, lo + w)` of the target of `assign n[lo +: w] = e` holds `e`'s
value resized to `w`. -/
theorem CombEqs.extract_slice {st : SvState} (heqs : CombEqs d st) {i : Nat} {n : String}
{lo w : Nat} {e : Expr2} (hp : d.processes[i]? = some (.assign (.slice n lo w) e)) {v : LVec}
(hv : evalPure2 st e = .ok v) :
∃ old, SvState.lookup st n = some old ∧ old.extract lo w = v.resizeU w :=
heqs.extract_window hp rfl hv
/-- **A constant-index assign, read back.** In a solution of the equations,
element `k` (window `[k·elemW, (k+1)·elemW)`) of the target of
`assign n[c] = e` (literal `c` with value `k`) holds `e`'s value resized to
`elemW`. -/
theorem CombEqs.extract_index {st : SvState} (heqs : CombEqs d st) {i : Nat} {n : String}
{c : LVec} {k elemW : Nat} {e : Expr2}
(hp : d.processes[i]? = some (.assign (.index n (.lit c) elemW) e)) (hk : c.toNat? = some k)
{v : LVec} (hv : evalPure2 st e = .ok v) :
∃ old, SvState.lookup st n = some old ∧
old.extract (k * elemW) elemW = v.resizeU elemW :=
heqs.extract_window hp (by simp [Lhs2.window?, hk]) hv
/-- **A concatenation part, read back.** In a solution of the equations,
every placed window `(n, lo, w)` (offset `off`, `Win.placed`) of an assign's
target — `assign {p₀, …, pₖ} = e`, nested concatenations flattened — holds
bits `[off, off + w)` of `e`'s value resized to the target's total width
`Win.sumWidth (t.wins d)` (the first part gets the high slice). -/
theorem CombEqs.concat_part {st : SvState} (heqs : CombEqs d st) {i : Nat} {t : Lhs2}
{e : Expr2} (hp : d.processes[i]? = some (.assign t e)) {v : LVec}
(hv : evalPure2 st e = .ok v) {n : String} {lo w off : Nat}
(hm : ((n, lo, w), off) ∈ Win.placed (t.wins d)) :
∃ old, SvState.lookup st n = some old ∧
old.extract lo w = (v.resizeU (Win.sumWidth (t.wins d))).extract off w := by
obtain ⟨old, v', hold, hv', hext⟩ := heqs i t e n lo w off hp hm
rw [hv, Res.ok.injEq] at hv'
subst hv'
exact ⟨old, hold, hext⟩
/-- **A whole-signal concatenation part, read back.** In a shaped solution of
the equations, a part `n` of a concatenated target (window `(n, 0, w)` with
`w` the declared width, offset `off`) holds bits `[off, off + w)` of the
right-hand side's value resized to the target's total width — e.g. for
`assign {co, s} = e` with `s` `N` bits: `s = (v.resizeU (1 + N)).extract 0 N`
and `co = (v.resizeU (1 + N)).extract N 1`. -/
theorem CombEqs.concat_part_ident (hd : CombDesign d) {st : SvState} (heqs : CombEqs d st)
(hs : StateShape d st) {i : Nat} {t : Lhs2} {e : Expr2}
(hp : d.processes[i]? = some (.assign t e)) {v : LVec} (hv : evalPure2 st e = .ok v)
{n : String} {w off : Nat} (hm : ((n, 0, w), off) ∈ Win.placed (t.wins d))
(hw : d.declWidth n = some w) :
SvState.lookup st n = some ((v.resizeU (Win.sumWidth (t.wins d))).extract off w) := by
obtain ⟨old, hold, hext⟩ := heqs.concat_part hp hv hm
obtain ⟨old', hold', -, hdw⟩ := hd.lookup_writes hs ⟨t, e, hp, Win.mem_of_mem_placed hm⟩
rw [hold] at hold'
cases hold'
rw [hw, Option.some.injEq] at hdw
subst hdw
rw [hold, ← hext, LVec.extract_full]
/-! ## Smoke test: the full adder of `Comb2Base`
`Comb2Smoke.faDesign` terminates and every snapshot of every run solves its
equations; `Comb2Example.lean` turns the equations into the full-adder
truth (with the `Known2` calculus). -/
namespace Comb2Smoke
example : Terminates2 faDesign := terminates2_of_acyclic fa_comb fa_acyclic
example : faDesign ⊨₂ spec2 fun _ tr => ∀ (c : Nat) (st : SvState), tr[c]? = some st →
CombEqs faDesign st ∧ StateShape faDesign st :=
Models2.of_comb_pointwise fa_comb fun _ h1 h2 => ⟨h1, h2⟩
end Comb2Smoke
end LeanModels.Sv