LeanModels.Sv.Comb2Proc
ls-sv semantics
- Lines
- 3035
- file
LeanModels/Sv/Comb2Proc.lean- sha256
80b1c132bb3da913795c9a9b08f02fb72473a536759491bc13e2ac0198b5c8a9- statements that rely on it
_example,add,aes_sbox,arbiter,clmul,fdivp,matmul,mul,sorter- download
LeanModels.Sv.Comb2Proc.lean
LeanModels/Sv/Comb2Proc.leanLeanModels.Sv.Comb2Proc.lean
import LeanModels.Sv.Comb2
import LeanModels.Sv.Mono2
import LeanModels.Sv.Known2
/-!
# `always_comb` in the combinational settle theorem (`LeanModels.Sv` — Design2)
The combinational settle theorem (`Comb2Base`/`Comb2Sound`/`Comb2Term`/
`Comb2`) is stated for designs whose processes are all continuous assigns.
This file extends it to designs that also contain `always_comb` processes
(`Process2.alwaysComb body`) whose bodies are blocking assignments under
`begin … end`, `if`/`else` and `case` (`case … inside` included).
## The idea: lower the body to one continuous assign
A body in the profile is run SYMBOLICALLY (`Stmt2.symExec`) over a
symbolic state (`SymState`): the list of windows the body has written so
far, each with an expression over the PRE-state saying what the window now
holds. Reads of a written window are replaced by its expression
(`Expr2.symSubst`), an `if`/`case` joins its branches window by window with
a known 1-bit guard (`SymState.join`; `Expr2.ifGuard` is `(|c) === 1'b1`,
which is exactly the interpreter's `if` truthiness, `Expr2.anyGuard` the
`case` arm hit), and a window a branch does not write keeps its pre-state
value (`Win.held`). The result is ONE equation per written window. The
process is then replaced by the continuous assign
`assign {w₀, …, wₖ} = {E₀, …, Eₖ}` (`SymState.toAssign`; every leaf a
constant slice `n[lo +: w]`), and `Design2.lowerComb d` is `d` with every
`always_comb` lowered this way (assigns kept, anything else refused). A body
whose equations read a window the body itself writes is refused too
(`SymState.selfReadFree`, below).
**The lowering is exact on shaped states** (`runCombProcess2_alwaysComb_ok`,
`runCombProcess2_alwaysComb_fwd`): a successful run of the body, at any
fuel, is exactly the lowered assign's `combWrite`, and at every fuel from
the body's static bound `Stmt2.fuelB` on the body run succeeds. So
`Runs2 d σ stim tr ↔ Runs2 d' σ stim tr` (`Runs2.lowerComb_iff`) whenever
`d.lowerComb = some d'` and `d'` is in the comb profile — the two designs
have the same completed runs, under every schedule, with the same traces —
and EVERYTHING proved about comb designs transfers: `⊨₂`
(`Models2.of_lowerComb`, `Models2.of_combProc`, …), termination
(`Terminates2.of_lowerComb`), the fixpoint characterization.
## The profile, in terms of the lowered design
* **(a) Static write set, disjoint from every other process.** The windows
of the symbolic state are the write set; `CombDesign d'` asks them to be
in range, pairwise disjoint within the process (`selfDisjoint`) and
disjoint from every other process's windows (`disjoint`), exactly as for a
concatenated assign.
* **(b) Latch freedom, and no self-reads.** A window that some path does not
write holds its pre-state value, so its equation READS ITSELF (`Win.held`);
a read of a window the body writes only later reads its pre-state value
too. The lowering refuses every body with such a read
(`SymState.selfReadFree`: no equation reads any window of the body's write
set — latches, `y = t; t = a;`, and `if (c) t = a; y = t; t = b;` alike).
A `Win.held` value is always such a read, so an accepted body writes each
of its windows on every path. The reason is simulator agreement, not only the fixpoint argument: an
event-driven simulator runs an `always_comb` block once per wake-up and is
never woken by the block's own writes (IEEE 1800-2023 §9.2.2.2.1), while
the settle semantics re-runs every comb process until nothing changes.
Icarus 12 lags one input change behind the settle answer on the last two
bodies (Yosys 0.53 `proc` synthesizes the settle answer); since WP4
(2026-10-28) `name.sem` refuses those two at instantiation
(`CombGuard2.lean`) and admits the latch. The check makes
one run of a lowered body its own fixpoint
(`runCombProcess2_alwaysComb_idem`): the single run a simulator makes per
wake-up already gives the block's settled value, and across blocks a
simulator (Xcelium 24.03, Verilator 5.042) re-runs a block when another
block's write changes what it reads, as the settle loop does — Icarus
12.0 does not on a pure copy of a variable the block writes (it
propagates the copy inside the block's run, an LRM-permitted
interleaving) nor on a bit split (it drops the whole variable from the
sensitivity list); both declared, `CombGuard2Sound.lean` module
docstring.
The check is the self-edge half of
`Acyclic d'`; for a one-process design it is all of it
(`Acyclic.of_lowerComb_single`).
* **(c) Termination of the body.** `Stmt2` has no loops; `Stmt2.fuelB` is a
static fuel bound (`runCombProcess2_alwaysComb_fwd`).
* **(d) Dependency edges.** The read set of the lowered right-hand side —
every read reached before the window it reads is written, every guard —
is what `Acyclic d'` ranks: the Comb2Term rank
argument, applied to `d'`, is the read/write-set rank argument for
`always_comb`.
## The equations
`CombEqs d' S` is the generalized `CombEqs`: one equation per written window
(`CombEqs.symWindow`, `CombEqs.symWindow_ident`: the window holds the value
of its symbolic expression in `S`). `ProcFixpoint d S` — every process of `d`
maps `S` to itself (running the body on `S` yields `S`) — is equivalent to it
on shaped states (`procFixpoint_iff_combEqs`), and every snapshot of a run
satisfies both (`Runs2.combProc_snapshot`).
## API index
* Symbolic execution: `SymState`, `SymState.write`, `SymState.join`
(`Expr2.joinIte`, with the sound syntactic equality `Expr2.same` /
`Expr2.eq_of_same`), `SymState.readWhole`, `SymState.readWin`,
`Expr2.symSubst`, `Lhs2.symWin`, `Stmt2.symExec` (with
`Stmt2.symExecList`, `CaseItem2.symExecList`), `Expr2.ifGuard`,
`Expr2.hitGuard`, `Expr2.anyGuard`, `Expr2.splice`, `Win.held`, the static
fuel bound `Stmt2.fuelB`; names only grow (`Stmt2.symExec_names`).
* Lowering: `SymState.target`, `SymState.rhs`, `SymState.toAssign`,
`SymState.selfReadFree` (`SymState.selfReadFree_iff`,
`SymState.rhs_reads`), `Process2.lowerComb`, `Design2.lowerComb`,
`Design2.lowerComb_spec`, `Design2.lowerComb_proc` / `_proc'`,
`Process2.lowerComb_alwaysComb`, `SymState.target_wins`.
* Semantics of symbolic execution: `SymRel` (a concrete state agrees with
the symbolic state over a pre-state), `SymOk` (pre-state width facts),
`Expr2.symSubst_sound`, `SymState.write_sound`,
`SymState.join_sound_left` / `_right`, `evalPure2_ifGuard`,
`evalPure2_anyGuard`, `symExec_sound` (fuel induction over
`execStmt2`/`execCase2`/`execStmts2`), `symExec_total`, `SymRel.unique`,
`SymRel.of_combWrite`.
* Process level: `runCombProcess2_alwaysComb_ok` (inversion, any fuel),
`runCombProcess2_alwaysComb_fwd` (forward, from `Stmt2.fuelB`),
`runCombProcess2_alwaysComb_idem` (one run is the body's own fixpoint).
* Run level: `CombSim` (`CombSim.runs2`), `CombSim.of_lowerComb` /
`_of_lowerComb'`, **`Runs2.lowerComb_iff`**, `Models2.of_lowerComb`,
`Terminates2.of_lowerComb`, **`Terminates2.of_lowerComb_acyclic`**,
**`Models2.of_combProc`**, `Models2.of_combProc_inputs`,
`Models2.of_combProc_pointwise`, `ProcFixpoint`,
**`procFixpoint_iff_combEqs`**, `Runs2.combProc_snapshot`,
**`CombEqs.symWindow`**, `CombEqs.symWindow_ident`.
* Single-process designs: `CombDesign.of_single`, `Acyclic.of_single` —
the profile and the certificate as checks on closed data (`decide`) —
and `Acyclic.of_lowerComb_single` / `Terminates2.of_lowerComb_single`:
for a design whose only process is one `always_comb`, the certificate
needs no proof at all.
* Worked examples: `Comb2ProcExample.lean` (a full adder with a temporary
and an `if`/`else`, a `case` multiplexer, a "default then override one
bit" decoder, an `N`-bit adder/subtractor with a zero flag proved for
EVERY `N` — all proved correct and terminating — and three bodies the
lowering refuses: a latch and two self-reads).
## Covered `always_comb` shapes
Covered: `begin … end` blocks; blocking assignments to a whole signal, a
constant slice or a constant-literal element select; `if` with or without
`else`; `case` and `case … inside` (the structure-preserving evaluator uses
first-match selection, while the public `run2` validator returns a named loud
`CaseCheck:*` result when a `unique`/`unique0`/`priority` claim is violated;
valid claims retain the same value as plain case). Yosys 0.53 `proc` treats a
`unique`/`priority` case with no matching arm as don't-care, and reads x bits
in a plain `case`'s item constants as wildcards, so the netlist can differ
from the settle answer; the declared entries are in
`docs/sv-oracle-disagreements.md`) with or
without `default`, several patterns per
arm (a window that an `if` without `else`, or a `case` without `default`,
writes on some arms only must already have been written before it, or the
body is a latch and the self-read check refuses it); reads of whole signals, constant slices and arbitrary expressions,
dynamic selects included; a write to a sub-window of an already written
window (the "default then override" idiom `y = '0; if (c) y[2] = 1'b1;` —
`Expr2.splice`). Reads of a window written earlier on the path are replaced
by its expression, so a body may use temporaries freely (the temporary is
itself a written window with its own equation).
## Gaps (refused or rejected, never silently wrong)
Refused by `symExec` (so `lowerComb` returns `none`): nonblocking
assignments; dynamic-index targets (`y[i] = …` with `i` not a literal);
procedural concatenation targets (`{c, s} = a + b;` — write it as a
continuous assign, which the comb profile admits); a whole-signal read of a
signal of which only part was written; a window read that straddles a
written window; a write that partially overlaps a written window without
lying inside it (`y[0] = a; y = b;`); branches that write overlapping but
different windows of one signal; any `.unsupported` node or undeclared
identifier. There are no loops in `Stmt2`; the extractor refuses a
procedural `for` in symbolic mode (`ForLoopStatement`). Refused before any
of this: a block-local declaration inside the `always_comb`
(`begin logic t; … end`) reaches the envelope as a `LocalDecl` statement,
which the sv-0.2 ingestion does not parse, so `load_design_sv2` and the
differential runner refuse the whole envelope (declare the temporary at
module level instead); a `unique if`/`unique0 if`/`priority if` is refused
by the extractor (`ConditionalStatement:Unique`, `:Unique0`, `:Priority` —
a loud node, so `symExec` refuses it too). Refused by the
self-read check (`SymState.selfReadFree`): latches (a window some path does
not write keeps its pre-state value), a body that reads one of its own
windows before writing it (`y = t; t = a;`), and a body that reads a value a
join kept for a window it later overwrites (`if (c) t = a; y = t; t = b;`).
An event-driven simulator does NOT re-run such a block on its own writes:
Icarus 12 leaves `y` one input change behind on the last two, where the
settle semantics (and Yosys 0.53 `proc`) give `y = a` and `y = c ? a : b`.
This refusal covers this API only. Since 2026-10-28 (WP4) the always_comb
self-read guard (`CombGuard2.lean`) refuses the self-reading bodies at
instantiation too, so no `name.sem` and no differential-runner design runs
them (`rbw_guard`, `held_guard` in `Comb2ProcExample`; the fixtures'
`agree.lean` pin `load_design_sv2`'s refusal). `run2` on a HAND-BUILT
`Design2` still settles them (`rbwDesign`/`heldDesign`'s `#guard` pins) —
the retired declared divergence `sv-div-3`. The guard and
`SymState.selfReadFree` are different tests, neither stronger: the guard is
path-sensitive and admits latches (one run of a latch is its own fixpoint,
`runCombProcess2_alwaysComb_idem_of_admits`, which generalizes
`runCombProcess2_alwaysComb_idem` to every admitted body), while
`selfReadFree` sees through values that never reach an equation and lowers
`y = t; y = a; t = b;` and `if (t) y = a; else y = a; t = b;`, which the
guard refuses (`dead_lowers`/`dead_refused`, `same_lowers`/`same_refused`).
On such a body the API meets the guard's loud process through `name.sem`
(`lowerComb` is `none` there), never a body the guard let through; every
body of `Comb2ProcExample` the lowering accepts is admitted by the guard
(`fa_admits`, `mux_admits`, `dec_admits`, `as_admits` at every `N`). Only comb-only designs lower (`alwaysFF`
and friends refuse; the settle theorem is comb-only). The termination
certificate `Acyclic d'` ranks PROCESSES, not windows, and a whole body
lowers to ONE assign: a body that writes `t` and reads `w`, next to
`assign w = t + 1`, is a process-level cycle although no window depends on
itself (Icarus and the settle semantics both settle it). Split such a body
into separate `always_comb` blocks or assigns; a window-level rank is future
work. Expression size: substitution copies a written
window's expression into every later read, and a join wraps every window a
branch CHANGED in a `?:` (windows both branches leave alone are kept,
`Expr2.joinIte`), so `k` sequential `if`s that write the same window nest
`k` deep. Parametric (`∀ N`) bodies: `symExec` is computed by `rfl` on
closed designs; at a symbolic width its window comparisons do not reduce by
`rfl`, and `simp` over the definitions evaluates it instead
(`Comb2ProcExample.as_symExec`, an `N`-bit adder/subtractor) — no generic
tactic packages that recipe yet.
## Design laws
* **Reuse, not re-proof.** The settle soundness and termination theorems of
`Comb2Sound`/`Comb2Term` are NOT re-proved for `always_comb`: the lowering
simulates the design step for step (`CombSim`: every pass, settle, cycle
and run of `d` is one of `d'` at some fuel, and back, on states with the
comb shape of `d'`), so the existing theorems apply to `d'` verbatim and
`Runs2.lowerComb_iff` carries them back. `Comb2*.lean` are unchanged.
* **Fuel never reaches a statement.** The symbolic-execution lemmas are fuel
inductions over the interpreter's mutual block (`symExec_sound`,
`symExec_total`); every consumer-facing theorem quantifies over `Runs2` /
`⊨₂` / `Terminates2` only.
* **4-state exact.** Nothing assumes known values: `x`/`z` conditions take
the `else` branch exactly as the interpreter's `if` does (the guard
`(|c) === 1'b1` is never `x`), a `case` subject with `x`/`z` bits
matches a plain pattern only when the two agree bit for bit under `===`
(so never a fully known pattern), and the equivalence holds on every
shaped state.
* **The symbolic execution is computed, not trusted.** It is an ordinary
structural function, evaluated by `rfl` on a concrete body; its soundness
(`symExec_sound`) is proved once, against the interpreter.
## Deviations from the original statement
* The profile, `CombEqs` and the rank certificate are not generalized IN
PLACE (`CombDesign`, `CombEqs`, `Acyclic` keep their assign-only
statements): they are stated of the lowered design `d'`. The generalized
fixpoint condition is `ProcFixpoint d S` (every process of `d`, the
`always_comb` bodies included, maps `S` to itself), proved equivalent to
`CombEqs d' S` on shaped states; the read/write-set rank argument for
`always_comb` is `Acyclic d'`, whose read set for a lowered body is the
read set of its symbolic equations.
* Latch freedom is not a separate condition: the self-read check
(`SymState.selfReadFree`) refuses latches together with every other body
whose equations read the body's own write set, which is also the
condition under which the settle semantics and an event-driven simulator
agree on the body (`runCombProcess2_alwaysComb_idem`).
* The termination bound for a body is `Stmt2.fuelB`; the run-level
termination theorem is stated, like `Terminates2.of_acyclic`, without a
bound.
-/
namespace LeanModels.Sv
/-! ## Symbolic states -/
/-- A symbolic state: the windows an `always_comb` body has written so far,
each with the expression — over the PRE-state of the body — whose value it
now holds. -/
abbrev SymState := List (Win × Expr2)
/-- The declared signal names of a design (in declaration order). -/
def Design2.symNames (d : Design2) : List String := d.decls.toList.map Decl.name
/-- Well-formedness of an expression a body evaluates: no `.unsupported`
node and every identifier declared (`evalPure2_ok_of_wf` then makes it
evaluate on every state that binds the declared names). -/
def Expr2.wfB (d : Design2) (e : Expr2) : Bool :=
e.unsupportedFree && e.idents.all fun x => decide (x ∈ d.symNames)
/-- `Expr2.wfB` of every element of a list. -/
def Expr2.listWfB (d : Design2) (es : List Expr2) : Bool :=
Expr2.listUnsupportedFree es && (Expr2.listIdents es).all fun x => decide (x ∈ d.symNames)
/-- The pre-state value of a window, `n[lo +: w]` (what a window the current
path did not write still holds). -/
def Win.held (W : Win) : Expr2 := .slice (.ident W.1) W.2.1 W.2.2
/-- `W` contains `V`: same signal and `V`'s bit range inside `W`'s. -/
def Win.contains (W V : Win) : Bool :=
W.1 == V.1 && decide (W.2.1 ≤ V.2.1) && decide (V.2.1 + V.2.2 ≤ W.2.1 + W.2.2)
/-- Pairwise disjointness of a window list, as a checker. -/
def Win.allDisjoint : List Win → Bool
| [] => true
| W :: ws => ws.all (fun V => !Win.overlaps W V) && Win.allDisjoint ws
/-- The `if (c)` guard as a KNOWN bit: `(|c) === 1'b1` is `1` exactly when
`c` has a `1` bit (`LVec.condTrue`, the interpreter's `if` truthiness) and
`0` otherwise — never `x`, so a `?:` on it picks one arm. -/
def Expr2.ifGuard (c : Expr2) : Expr2 :=
.binary .caseEq (.reduce .or c) (.lit (LVec.ofLogic .l1))
/-- One `case` pattern's hit as a known bit: `subj === pat` (plain `case`),
`(subj ==? pat) === 1'b1` (`case … inside`) — `caseHit`. -/
def Expr2.hitGuard (mode : CaseMode) (subj pat : Expr2) : Expr2 :=
match mode with
| .normal => .binary .caseEq subj pat
| .inside => .binary .caseEq (.binary .wildEq subj pat) (.lit (LVec.ofLogic .l1))
| .casez => .binary .casez subj pat
| .casex => .binary .casex subj pat
/-- A `case` arm's hit as a known bit: the `|` of its patterns' hits (`0`
for no pattern). -/
def Expr2.anyGuard (mode : CaseMode) (subj : Expr2) : List Expr2 → Expr2
| [] => .lit (LVec.ofLogic .l0)
| p :: ps => .binary .or (Expr2.hitGuard mode subj p) (Expr2.anyGuard mode subj ps)
/-- Replace bits `[r, r + w)` of a `w0`-bit value `E0` by `E` (a `w`-bit
value): `{E0[w0-1 : r+w], E, E0[r-1 : 0]}`. -/
def Expr2.splice (E0 : Expr2) (w0 r w : Nat) (E : Expr2) : Expr2 :=
.concat #[.slice E0 (r + w) (w0 - (r + w)), E, .slice E0 0 r]
mutual
/-- Sound syntactic equality of expressions (`Expr2` derives no
`DecidableEq`): `true` only on identical trees. -/
def Expr2.same : Expr2 → Expr2 → Bool
| .lit v, e' => match e' with | .lit v' => decide (v = v') | _ => false
| .ident n, e' => match e' with | .ident n' => decide (n = n') | _ => false
| .unsupported k x, e' => match e' with
| .unsupported k' x' => decide (k = k') && decide (x = x') | _ => false
| .unary o a, e' => match e' with | .unary o' a' => decide (o = o') && a.same a' | _ => false
| .resizeSigned w a, e' => match e' with
| .resizeSigned w' a' => decide (w = w') && a.same a' | _ => false
| .squash2 a, e' => match e' with | .squash2 a' => a.same a' | _ => false
| .repl k a, e' => match e' with | .repl k' a' => decide (k = k') && a.same a' | _ => false
| .reduce o a, e' => match e' with | .reduce o' a' => decide (o = o') && a.same a' | _ => false
| .resize w a, e' => match e' with | .resize w' a' => decide (w = w') && a.same a' | _ => false
| .binary o l r, e' => match e' with
| .binary o' l' r' => decide (o = o') && l.same l' && r.same r' | _ => false
| .scmp o l r, e' => match e' with
| .scmp o' l' r' => decide (o = o') && l.same l' && r.same r' | _ => false
| .logical b l r, e' => match e' with
| .logical b' l' r' => decide (b = b') && l.same l' && r.same r' | _ => false
| .index a i w, e' => match e' with
| .index a' i' w' => decide (w = w') && a.same a' && i.same i' | _ => false
| .ternary c t f, e' => match e' with
| .ternary c' t' f' => c.same c' && t.same t' && f.same f' | _ => false
| .slice a lo w, e' => match e' with
| .slice a' lo' w' => decide (lo = lo') && decide (w = w') && a.same a' | _ => false
| .concat ps, e' => match e' with
| .concat ps' => Expr2.listSame ps.toList ps'.toList | _ => false
termination_by structural e => e
def Expr2.listSame : List Expr2 → List Expr2 → Bool
| [], l' => match l' with | [] => true | _ => false
| e :: es, l' => match l' with | e' :: es' => e.same e' && Expr2.listSame es es' | _ => false
termination_by structural es => es
end
/-- The joined value of a window: the first branch's if the two branches
agree syntactically, else `G ? a : b`. -/
def Expr2.joinIte (G A B : Expr2) : Expr2 := if A.same B then A else .ternary G A B
namespace SymState
/-- The signals a symbolic state writes. -/
def names (S : SymState) : List String := S.map fun e => e.1.1
/-- The expression of the entry with window exactly `W`. -/
def findWin (S : SymState) (W : Win) : Option Expr2 := (S.find? fun e => e.1 == W).map Prod.snd
/-- The first entry whose window contains `V`. -/
def findCover (S : SymState) (V : Win) : Option (Win × Expr2) :=
S.find? fun e => Win.contains e.1 V
/-- Some entry's window overlaps `V`. -/
def overlapsAny (S : SymState) (V : Win) : Bool := S.any fun e => Win.overlaps e.1 V
/-- Some entry writes signal `n`. -/
def writesName (S : SymState) (n : String) : Bool := S.any fun e => e.1.1 == n
/-- The value window `W` holds: its entry, or its pre-state value. -/
def lookupOr (S : SymState) (W : Win) : Expr2 := (S.findWin W).getD W.held
/-- A whole-signal read `n`: the pre-state `n` if the body has not written
`n`, the entry of the FULL window `(n, 0, declared width)` if it has; any
other partial write refuses. -/
def readWhole (d : Design2) (S : SymState) (n : String) : Option Expr2 :=
if S.writesName n then (d.declWidth n).bind fun dw => S.findWin (n, 0, dw)
else some (.ident n)
/-- A window read `n[lo +: w]`: a slice of the entry whose window contains
it, the pre-state read if no entry overlaps it; a straddling read
refuses. -/
def readWin (S : SymState) (n : String) (lo w : Nat) : Option Expr2 :=
match S.findCover (n, lo, w) with
| some (W, E) => some (.slice E (lo - W.2.1) w)
| none => if S.overlapsAny (n, lo, w) then none else some (.slice (.ident n) lo w)
/-- Blocking write of window `W` with value `E`: replace the entry with
window exactly `W`; else splice `E` into the entry whose window contains
`W` (`Expr2.splice`); else append `W` if it overlaps no entry; else
refuse. -/
def write (S : SymState) (W : Win) (E : Expr2) : Option SymState :=
if S.any (fun e => e.1 == W) then some (S.map fun e => if e.1 == W then (W, E) else e)
else
match S.findCover W with
| some (W0, E0) =>
some (S.map fun e =>
if e.1 == W0 then (W0, Expr2.splice E0 W0.2.2 (W.2.1 - W0.2.1) W.2.2 E) else e)
| none => if S.overlapsAny W then none else some (S ++ [(W, E)])
/-- The windows of a join: the first branch's, then the second branch's new
ones. -/
def joinWins (Sa Sb : SymState) : List Win :=
Sa.map Prod.fst ++ (Sb.map Prod.fst).filter fun W => !(Sa.any fun e => e.1 == W)
/-- Join two branches under a known 1-bit guard `G`: every window either
branch wrote gets `G ? (branch a's value) : (branch b's value)`, a branch
that did not write it contributing the pre-state value (`lookupOr`); a
window both branches leave syntactically equal keeps that value
(`Expr2.joinIte`). The windows must be pairwise disjoint (a signal written
in different windows by the two branches refuses). -/
def join (G : Expr2) (Sa Sb : SymState) : Option SymState :=
if Win.allDisjoint (joinWins Sa Sb) then
some ((joinWins Sa Sb).map fun W => (W, Expr2.joinIte G (Sa.lookupOr W) (Sb.lookupOr W)))
else none
end SymState
/-- Substitution under a slice: a slice of an identifier is a window read
(`SymState.readWin`); any other slice substitutes its base. -/
def Expr2.sliceSubst (d : Design2) (S : SymState) : Expr2 → Nat → Nat → Option Expr2 →
Option Expr2
| .ident n, lo, w, _ => if n ∈ d.symNames then S.readWin n lo w else none
| _, lo, w, r => r.map fun a => .slice a lo w
mutual
/-- **Symbolic substitution**: rewrite an expression read at the CURRENT
point of a body into an expression over the body's pre-state — written
windows are replaced by their entries (`SymState.readWhole`,
`SymState.readWin`); `none` on an `.unsupported` node, an undeclared
identifier, or a read the symbolic state cannot express. -/
def Expr2.symSubst (d : Design2) (S : SymState) : Expr2 → Option Expr2
| .lit v => some (.lit v)
| .ident n => if n ∈ d.symNames then S.readWhole d n else none
| .unsupported _ _ => none
| .unary op a => (a.symSubst d S).map (.unary op)
| .resizeSigned w a => (a.symSubst d S).map (.resizeSigned w)
| .squash2 a => (a.symSubst d S).map .squash2
| .repl k a => (a.symSubst d S).map (.repl k)
| .reduce op a => (a.symSubst d S).map (.reduce op)
| .resize w a => (a.symSubst d S).map (.resize w)
| .binary op l r => (l.symSubst d S).bind fun l' => (r.symSubst d S).map (.binary op l')
| .scmp op l r => (l.symSubst d S).bind fun l' => (r.symSubst d S).map (.scmp op l')
| .logical b l r => (l.symSubst d S).bind fun l' => (r.symSubst d S).map (.logical b l')
| .index a i w => (a.symSubst d S).bind fun a' => (i.symSubst d S).map fun i' => .index a' i' w
| .ternary c t f =>
(c.symSubst d S).bind fun c' => (t.symSubst d S).bind fun t' =>
(f.symSubst d S).map (.ternary c' t')
| .slice a lo w => Expr2.sliceSubst d S a lo w (a.symSubst d S)
| .concat parts => (Expr2.listSymSubst d S parts.toList).map fun ps => .concat ps.toArray
termination_by structural e => e
/-- `Expr2.symSubst` of a list, elementwise. -/
def Expr2.listSymSubst (d : Design2) (S : SymState) : List Expr2 → Option (List Expr2)
| [] => some []
| e :: es => (e.symSubst d S).bind fun e' => (Expr2.listSymSubst d S es).map (e' :: ·)
termination_by structural es => es
end
/-- The static write window of a blocking-assignment target: a leaf
(whole signal, constant slice, constant-literal element select) with a
declared signal, inside its declared width. Concatenations and dynamic
selects refuse. -/
def Lhs2.symWin (d : Design2) (t : Lhs2) : Option Win :=
match t with
| .concat _ => none
| _ =>
(t.window? d).bind fun W =>
(d.declWidth W.1).bind fun dw => if W.2.1 + W.2.2 ≤ dw then some W else none
mutual
/-- **Symbolic execution of an `always_comb` body** from a symbolic state
(`none`: outside the profile — see the module docstring). -/
def Stmt2.symExec (d : Design2) (S : SymState) : Stmt2 → Option SymState
| .blockingAssign t e =>
if e.wfB d then
(e.symSubst d S).bind fun e' => (t.symWin d).bind fun W => S.write W (.resize W.2.2 e')
else none
| .ifStmt c t e =>
if c.wfB d then
(c.symSubst d S).bind fun c' => (t.symExec d S).bind fun St =>
(Stmt2.symExecOpt d S e).bind fun Sf => SymState.join c'.ifGuard St Sf
else none
| .block body => Stmt2.symExecList d S body.toList
| .caseStmt subj items dflt mode _ =>
if subj.wfB d then
(subj.symSubst d S).bind fun subj' => (Stmt2.symExecOpt d S dflt).bind fun Sd =>
CaseItem2.symExecList d S subj' mode Sd items.toList
else none
| .nbaAssign _ _ => none
| .unsupported _ _ => none
termination_by structural s => s
/-- `Stmt2.symExec` of an optional statement (`none`: no-op). -/
def Stmt2.symExecOpt (d : Design2) (S : SymState) : Option Stmt2 → Option SymState
| none => some S
| some s => s.symExec d S
termination_by structural o => o
/-- `Stmt2.symExec` of a statement list, in sequence. -/
def Stmt2.symExecList (d : Design2) (S : SymState) : List Stmt2 → Option SymState
| [] => some S
| s :: ss => (s.symExec d S).bind fun S' => Stmt2.symExecList d S' ss
termination_by structural ss => ss
/-- One `case` arm: its patterns (substituted) and its body's symbolic
state. -/
def CaseItem2.symArm (d : Design2) (S : SymState) : CaseItem2 → Option (List Expr2 × SymState)
| .mk pats body =>
if Expr2.listWfB d pats.toList then
(Expr2.listSymSubst d S pats.toList).bind fun pats' =>
(body.symExec d S).map fun Sb => (pats', Sb)
else none
termination_by structural it => it
/-- The arms of a `case`, first match first, ending in the default's
symbolic state `Sd`: arm `k` joins its body with the rest under its hit
guard. -/
def CaseItem2.symExecList (d : Design2) (S : SymState) (subj : Expr2) (mode : CaseMode)
(Sd : SymState) : List CaseItem2 → Option SymState
| [] => some Sd
| it :: rest =>
(it.symArm d S).bind fun arm =>
(CaseItem2.symExecList d S subj mode Sd rest).bind fun Sr =>
SymState.join (Expr2.anyGuard mode subj arm.1) arm.2 Sr
termination_by structural its => its
end
/-! ## Static fuel bound of a body -/
mutual
/-- Fuel at which `execStmt2` runs a statement of the profile to completion
on every state (`symExec_total`). -/
def Stmt2.fuelB : Stmt2 → Nat
| .blockingAssign _ e => e.size + 2
| .ifStmt c t e => max (c.size + 1) (max t.fuelB (Stmt2.optFuelB e)) + 1
| .block body => Stmt2.listFuelB body.toList + 1
| .caseStmt subj items dflt _ _ =>
max (subj.size + 1) (CaseItem2.listFuelB (Stmt2.optFuelB dflt) items.toList) + 1
| .nbaAssign _ _ => 0
| .unsupported _ _ => 0
termination_by structural s => s
/-- `Stmt2.fuelB` of an optional statement. -/
def Stmt2.optFuelB : Option Stmt2 → Nat
| none => 0
| some s => s.fuelB
termination_by structural o => o
/-- Fuel for `execStmts2` over a list (one unit per cell, the empty list
included). -/
def Stmt2.listFuelB : List Stmt2 → Nat
| [] => 1
| s :: ss => max s.fuelB (Stmt2.listFuelB ss) + 1
termination_by structural ss => ss
/-- Fuel for one `case` arm (its patterns and body). -/
def CaseItem2.fuelB : CaseItem2 → Nat
| .mk pats body => max (Expr2.listSize pats.toList + 1) body.fuelB
termination_by structural it => it
/-- Fuel for `execCase2` over an arm list ending in a default of fuel
`fd`. -/
def CaseItem2.listFuelB (fd : Nat) : List CaseItem2 → Nat
| [] => fd + 1
| it :: rest => max it.fuelB (CaseItem2.listFuelB fd rest) + 1
termination_by structural its => its
end
/-! ## The lowering -/
/-- A window as a constant-slice assignment target `n[lo +: w]`. -/
def Win.lhs (W : Win) : Lhs2 := .slice W.1 W.2.1 W.2.2
namespace SymState
/-- The lowered target `{w₀, …, wₖ}` (entry order, MSB-first). -/
def target (S : SymState) : Lhs2 := .concat (S.map fun e => e.1.lhs)
/-- The lowered right-hand side `{E₀, …, Eₖ}`. -/
def rhs (S : SymState) : Expr2 := .concat (S.map Prod.snd).toArray
/-- The continuous assign an `always_comb` body lowers to. -/
def toAssign (S : SymState) : Process2 := .assign S.target S.rhs
/-- **No self-read**: no equation reads a window the body itself writes
(`Expr2.reads` of every entry, against every entry's window). Such a read is
the PRE-state value of a window the body overwrites: a read made before the
write (`y = t; t = a;`), or a value a join keeps (`Win.held` — a latch
`if (en) q = d;`, or `if (c) t = a; y = t; t = b;`). An event-driven
simulator runs an `always_comb` block once per wake-up and the block's own
writes never wake it again (IEEE 1800-2023 §9.2.2.2.1 leaves variables the
block writes out of its implicit sensitivity list; Icarus 12 measured
lagging on both bodies above), while the settle semantics re-runs every
comb process until nothing changes. The two agree on a block whose single
run is already a fixpoint of the block, and this check is what makes it one
(`runCombProcess2_alwaysComb_idem`). -/
def selfReadFree (S : SymState) : Bool :=
(Expr2.listReads (S.map Prod.snd)).all fun r => (S.map Prod.fst).all fun W => !r.overlaps W
end SymState
/-- Lower one process: an assign is kept, an `always_comb` body in the
profile — symbolically executable (`Stmt2.symExec`) and free of self-reads
(`SymState.selfReadFree`) — becomes its `SymState.toAssign`, anything else
refuses. -/
def Process2.lowerComb (d : Design2) : Process2 → Option Process2
| .assign t e => some (.assign t e)
| .alwaysComb body =>
(body.symExec d []).bind fun S => if S.selfReadFree then some S.toAssign else none
| _ => none
/-- `Process2.lowerComb` over a process list (all or nothing). -/
def Design2.lowerProcs (d : Design2) : List Process2 → Option (List Process2)
| [] => some []
| p :: ps => (p.lowerComb d).bind fun q => (Design2.lowerProcs d ps).map (q :: ·)
/-- **Lower a design**: every `always_comb` body replaced by its continuous
assign; same name and declarations. `none` if some process is not an assign
or an `always_comb` in the profile. -/
def Design2.lowerComb (d : Design2) : Option Design2 :=
(d.lowerProcs d.processes.toList).map fun ps => { d with processes := ps.toArray }
/-! ## Static facts -/
theorem Win.allDisjoint_iff : ∀ ws : List Win,
Win.allDisjoint ws = true ↔ ws.Pairwise fun a b => Win.overlaps a b = false
| [] => by simp [Win.allDisjoint]
| W :: ws => by
simp only [Win.allDisjoint, Bool.and_eq_true, List.all_eq_true, Bool.not_eq_true',
List.pairwise_cons, Win.allDisjoint_iff ws]
/-- Two distinct members of a pairwise list are related (in one order). -/
theorem List.pairwise_of_mem_ne {α : Type} {R : α → α → Prop} (hsym : ∀ a b, R a b → R b a) :
∀ {l : List α}, l.Pairwise R → ∀ {a b : α}, a ∈ l → b ∈ l → a ≠ b → R a b
| [], _, _, _, ha, _, _ => absurd ha List.not_mem_nil
| x :: xs, h, a, b, ha, hb, hab => by
rw [List.pairwise_cons] at h
rcases List.mem_cons.mp ha with h1 | h1 <;> rcases List.mem_cons.mp hb with h2 | h2
· exact absurd (h1.trans h2.symm) hab
· subst h1; exact h.1 b h2
· subst h2; exact hsym _ _ (h.1 a h1)
· exact List.pairwise_of_mem_ne hsym h.2 h1 h2 hab
theorem Win.contains_iff {W V : Win} : Win.contains W V = true ↔
W.1 = V.1 ∧ W.2.1 ≤ V.2.1 ∧ V.2.1 + V.2.2 ≤ W.2.1 + W.2.2 := by
simp [Win.contains, and_assoc]
theorem Win.overlaps_symm_false {a b : Win} (h : Win.overlaps a b = false) :
Win.overlaps b a = false := by rw [Win.overlaps_comm]; exact h
mutual
/-- `Expr2.same` is sound. -/
theorem Expr2.eq_of_same : ∀ (a b : Expr2), a.same b = true → a = b
| .lit v, b, h => by cases b <;> simp_all [Expr2.same]
| .ident n, b, h => by cases b <;> simp_all [Expr2.same]
| .unsupported k x, b, h => by cases b <;> simp_all [Expr2.same]
| .unary o a, b, h => by
cases b <;> simp only [Expr2.same, Bool.and_eq_true, decide_eq_true_eq, reduceCtorEq] at h
obtain ⟨h1, h2⟩ := h
rw [h1, Expr2.eq_of_same _ _ h2]
| .resizeSigned w a, b, h => by
cases b <;> simp only [Expr2.same, Bool.and_eq_true, decide_eq_true_eq, reduceCtorEq] at h
obtain ⟨h1, h2⟩ := h
rw [h1, Expr2.eq_of_same _ _ h2]
| .squash2 a, b, h => by
cases b <;> simp only [Expr2.same, Bool.and_eq_true, decide_eq_true_eq, reduceCtorEq] at h
rw [Expr2.eq_of_same _ _ h]
| .repl k a, b, h => by
cases b <;> simp only [Expr2.same, Bool.and_eq_true, decide_eq_true_eq, reduceCtorEq] at h
obtain ⟨h1, h2⟩ := h
rw [h1, Expr2.eq_of_same _ _ h2]
| .reduce o a, b, h => by
cases b <;> simp only [Expr2.same, Bool.and_eq_true, decide_eq_true_eq, reduceCtorEq] at h
obtain ⟨h1, h2⟩ := h
rw [h1, Expr2.eq_of_same _ _ h2]
| .resize w a, b, h => by
cases b <;> simp only [Expr2.same, Bool.and_eq_true, decide_eq_true_eq, reduceCtorEq] at h
obtain ⟨h1, h2⟩ := h
rw [h1, Expr2.eq_of_same _ _ h2]
| .binary o l r, b, h => by
cases b <;> simp only [Expr2.same, Bool.and_eq_true, decide_eq_true_eq, reduceCtorEq] at h
obtain ⟨⟨h1, h2⟩, h3⟩ := h
rw [h1, Expr2.eq_of_same _ _ h2, Expr2.eq_of_same _ _ h3]
| .scmp o l r, b, h => by
cases b <;> simp only [Expr2.same, Bool.and_eq_true, decide_eq_true_eq, reduceCtorEq] at h
obtain ⟨⟨h1, h2⟩, h3⟩ := h
rw [h1, Expr2.eq_of_same _ _ h2, Expr2.eq_of_same _ _ h3]
| .logical c l r, b, h => by
cases b <;> simp only [Expr2.same, Bool.and_eq_true, decide_eq_true_eq, reduceCtorEq] at h
obtain ⟨⟨h1, h2⟩, h3⟩ := h
rw [h1, Expr2.eq_of_same _ _ h2, Expr2.eq_of_same _ _ h3]
| .index a i w, b, h => by
cases b <;> simp only [Expr2.same, Bool.and_eq_true, decide_eq_true_eq, reduceCtorEq] at h
obtain ⟨⟨h1, h2⟩, h3⟩ := h
rw [h1, Expr2.eq_of_same _ _ h2, Expr2.eq_of_same _ _ h3]
| .ternary c t f, b, h => by
cases b <;> simp only [Expr2.same, Bool.and_eq_true, reduceCtorEq] at h
obtain ⟨⟨h1, h2⟩, h3⟩ := h
rw [Expr2.eq_of_same _ _ h1, Expr2.eq_of_same _ _ h2, Expr2.eq_of_same _ _ h3]
| .slice a lo w, b, h => by
cases b <;> simp only [Expr2.same, Bool.and_eq_true, decide_eq_true_eq, reduceCtorEq] at h
obtain ⟨⟨h1, h2⟩, h3⟩ := h
rw [h1, h2, Expr2.eq_of_same _ _ h3]
| .concat ps, b, h => by
cases b <;> simp only [Expr2.same, reduceCtorEq] at h
rename_i ps'
have := Expr2.listEq_of_same ps.toList ps'.toList h
cases ps; cases ps'; simp_all
termination_by structural a => a
/-- `Expr2.listSame` is sound. -/
theorem Expr2.listEq_of_same : ∀ (as bs : List Expr2), Expr2.listSame as bs = true → as = bs
| [], bs, h => by cases bs <;> simp_all [Expr2.listSame]
| a :: as, bs, h => by
cases bs with
| nil => simp [Expr2.listSame] at h
| cons b bs =>
simp only [Expr2.listSame, Bool.and_eq_true] at h
rw [Expr2.eq_of_same _ _ h.1, Expr2.listEq_of_same _ _ h.2]
termination_by structural as => as
end
namespace SymState
theorem names_append (S T : SymState) : (S ++ T).names = S.names ++ T.names := by
simp [names]
theorem mem_names {S : SymState} {n : String} : n ∈ S.names ↔ ∃ e ∈ S, e.1.1 = n := by
simp [names]
theorem findWin_eq_some {S : SymState} {W : Win} {E : Expr2} (h : S.findWin W = some E) :
(W, E) ∈ S := by
simp only [findWin, Option.map_eq_some_iff] at h
obtain ⟨⟨W', E'⟩, hf, rfl⟩ := h
have hW : W' = W := by simpa using List.find?_some hf
subst hW
exact List.mem_of_find?_eq_some hf
theorem findWin_eq_none {S : SymState} {W : Win} (h : S.findWin W = none) :
W ∉ S.map Prod.fst := by
simp only [findWin, Option.map_eq_none_iff, List.find?_eq_none] at h
intro hm
obtain ⟨⟨W', E'⟩, hm', rfl⟩ := List.mem_map.mp hm
simpa using h _ hm'
theorem findWin_of_mem {S : SymState} {W : Win} (h : W ∈ S.map Prod.fst) :
∃ E, S.findWin W = some E := by
cases hf : S.findWin W with
| none => exact absurd h (findWin_eq_none hf)
| some E => exact ⟨E, rfl⟩
theorem findCover_eq_some {S : SymState} {V W : Win} {E : Expr2}
(h : S.findCover V = some (W, E)) : (W, E) ∈ S ∧ Win.contains W V = true := by
simp only [findCover] at h
exact ⟨List.mem_of_find?_eq_some h, by simpa using List.find?_some h⟩
theorem findCover_eq_none {S : SymState} {V : Win} (h : S.findCover V = none) :
∀ e ∈ S, Win.contains e.1 V = false := by
simp only [findCover, List.find?_eq_none] at h
intro e he
simpa using h e he
theorem overlapsAny_eq_false {S : SymState} {V : Win} (h : S.overlapsAny V = false) :
∀ W ∈ S.map Prod.fst, Win.overlaps W V = false := by
simp only [overlapsAny, List.any_eq_false] at h
intro W hW
obtain ⟨⟨W', E'⟩, hm, rfl⟩ := List.mem_map.mp hW
simpa using h _ hm
theorem writesName_eq_false {S : SymState} {n : String} (h : S.writesName n = false) :
∀ W ∈ S.map Prod.fst, W.1 ≠ n := by
simp only [writesName, List.any_eq_false] at h
intro W hW
obtain ⟨⟨W', E'⟩, hm, rfl⟩ := List.mem_map.mp hW
simpa using h _ hm
theorem writesName_eq_true {S : SymState} {n : String} (h : S.writesName n = true) :
n ∈ S.names := by
simp only [writesName, List.any_eq_true, beq_iff_eq] at h
obtain ⟨e, he, rfl⟩ := h
exact mem_names.mpr ⟨e, he, rfl⟩
/-- The written signal is a name of the written symbolic state, and every
old name stays. -/
theorem write_names {S S' : SymState} {W : Win} {E : Expr2} (h : S.write W E = some S') :
W.1 ∈ S'.names ∧ ∀ n ∈ S.names, n ∈ S'.names := by
unfold write at h
split at h
· rename_i hany
cases h
refine ⟨?_, ?_⟩
· simp only [List.any_eq_true, beq_iff_eq] at hany
obtain ⟨e, he, hEq⟩ := hany
refine mem_names.mpr ⟨(W, E), List.mem_map.mpr ⟨e, he, by simp [hEq]⟩, rfl⟩
· intro n hn
obtain ⟨e, he, rfl⟩ := mem_names.mp hn
refine mem_names.mpr ⟨if e.1 == W then (W, E) else e, List.mem_map.mpr ⟨e, he, rfl⟩, ?_⟩
by_cases hEq : e.1 = W
· simp [hEq]
· simp [hEq]
· split at h
· rename_i W0 E0 hc
cases h
obtain ⟨hm, hcont⟩ := findCover_eq_some hc
have hn : W0.1 = W.1 := (Win.contains_iff.mp hcont).1
refine ⟨?_, ?_⟩
· refine mem_names.mpr ⟨(W0, Expr2.splice E0 W0.2.2 (W.2.1 - W0.2.1) W.2.2 E),
List.mem_map.mpr ⟨(W0, E0), hm, by simp⟩, hn⟩
· intro n hn'
obtain ⟨e, he, rfl⟩ := mem_names.mp hn'
refine mem_names.mpr ⟨_, List.mem_map.mpr ⟨e, he, rfl⟩, ?_⟩
by_cases hEq : e.1 = W0
· simp [hEq]
· simp [hEq]
· split at h
· cases h
· cases h
exact ⟨by simp [names], fun n hn => by simp [names_append, hn]⟩
theorem mem_joinWins_left {Sa Sb : SymState} {W : Win} (h : W ∈ Sa.map Prod.fst) :
W ∈ joinWins Sa Sb := List.mem_append_left _ h
theorem mem_joinWins_right {Sa Sb : SymState} {W : Win} (h : W ∈ Sb.map Prod.fst) :
W ∈ joinWins Sa Sb := by
by_cases ha : W ∈ Sa.map Prod.fst
· exact List.mem_append_left _ ha
· refine List.mem_append_right _ (List.mem_filter.mpr ⟨h, ?_⟩)
simp only [Bool.not_eq_true', List.any_eq_false, beq_iff_eq]
intro e he hEq
exact ha (List.mem_map.mpr ⟨e, he, hEq⟩)
theorem mem_joinWins {Sa Sb : SymState} {W : Win} (h : W ∈ joinWins Sa Sb) :
W ∈ Sa.map Prod.fst ∨ W ∈ Sb.map Prod.fst := by
rcases List.mem_append.mp h with h | h
· exact Or.inl h
· exact Or.inr (List.mem_filter.mp h).1
/-- The windows of a join are the windows of its entries. -/
theorem join_wins {G : Expr2} {Sa Sb S' : SymState} (h : join G Sa Sb = some S') :
S'.map Prod.fst = joinWins Sa Sb := by
unfold join at h
split at h
· cases h; simp [List.map_map, Function.comp_def]
· cases h
/-- Both branches' names are names of the join. -/
theorem join_names {G : Expr2} {Sa Sb S' : SymState} (h : join G Sa Sb = some S') :
(∀ n ∈ Sa.names, n ∈ S'.names) ∧ (∀ n ∈ Sb.names, n ∈ S'.names) := by
have hw := join_wins h
have key : ∀ W ∈ joinWins Sa Sb, W.1 ∈ S'.names := by
intro W hW
rw [← hw] at hW
obtain ⟨e, he, rfl⟩ := List.mem_map.mp hW
exact mem_names.mpr ⟨e, he, rfl⟩
refine ⟨fun n hn => ?_, fun n hn => ?_⟩
· obtain ⟨e, he, rfl⟩ := mem_names.mp hn
exact key _ (mem_joinWins_left (List.mem_map_of_mem he))
· obtain ⟨e, he, rfl⟩ := mem_names.mp hn
exact key _ (mem_joinWins_right (List.mem_map_of_mem he))
end SymState
/-! ### Names only grow along symbolic execution -/
mutual
theorem Stmt2.symExec_names : ∀ (s : Stmt2) {d : Design2} {S S' : SymState},
s.symExec d S = some S' → ∀ n ∈ S.names, n ∈ S'.names
| .blockingAssign t e, d, S, S', h => by
simp only [Stmt2.symExec] at h
split at h
· simp only [Option.bind_eq_some_iff] at h
obtain ⟨e', -, W, -, hw⟩ := h
exact (SymState.write_names hw).2
· cases h
| .ifStmt c t e, d, S, S', h => by
simp only [Stmt2.symExec] at h
split at h
· simp only [Option.bind_eq_some_iff] at h
obtain ⟨c', -, St, ht, Sf, -, hj⟩ := h
exact fun n hn => (SymState.join_names hj).1 n (Stmt2.symExec_names t ht n hn)
· cases h
| .block body, d, S, S', h => by
simp only [Stmt2.symExec] at h
exact Stmt2.symExecList_names body.toList h
| .caseStmt subj items dflt mode _, d, S, S', h => by
simp only [Stmt2.symExec] at h
split at h
· simp only [Option.bind_eq_some_iff] at h
obtain ⟨subj', -, Sd, hd, hl⟩ := h
exact fun n hn => CaseItem2.symExecList_names items.toList hl n
(Stmt2.symExecOpt_names dflt hd n hn)
· cases h
| .nbaAssign _ _, d, S, S', h => by simp [Stmt2.symExec] at h
| .unsupported _ _, d, S, S', h => by simp [Stmt2.symExec] at h
termination_by structural s => s
theorem Stmt2.symExecOpt_names : ∀ (o : Option Stmt2) {d : Design2} {S S' : SymState},
Stmt2.symExecOpt d S o = some S' → ∀ n ∈ S.names, n ∈ S'.names
| none, d, S, S', h => by
simp only [Stmt2.symExecOpt, Option.some.injEq] at h
subst h; exact fun _ h => h
| some s, d, S, S', h => by
simp only [Stmt2.symExecOpt] at h
exact Stmt2.symExec_names s h
termination_by structural o => o
theorem Stmt2.symExecList_names : ∀ (ss : List Stmt2) {d : Design2} {S S' : SymState},
Stmt2.symExecList d S ss = some S' → ∀ n ∈ S.names, n ∈ S'.names
| [], d, S, S', h => by
simp only [Stmt2.symExecList, Option.some.injEq] at h
subst h; exact fun _ h => h
| s :: ss, d, S, S', h => by
simp only [Stmt2.symExecList, Option.bind_eq_some_iff] at h
obtain ⟨S1, h1, h2⟩ := h
exact fun n hn => Stmt2.symExecList_names ss h2 n (Stmt2.symExec_names s h1 n hn)
termination_by structural ss => ss
theorem CaseItem2.symExecList_names : ∀ (its : List CaseItem2) {d : Design2} {S : SymState}
{subj : Expr2} {mode : CaseMode} {Sd S' : SymState},
CaseItem2.symExecList d S subj mode Sd its = some S' →
(∀ n ∈ Sd.names, n ∈ S'.names)
| [], d, S, subj, mode, Sd, S', h => by
simp only [CaseItem2.symExecList, Option.some.injEq] at h
subst h; exact fun _ h => h
| it :: rest, d, S, subj, mode, Sd, S', h => by
simp only [CaseItem2.symExecList, Option.bind_eq_some_iff] at h
obtain ⟨arm, -, Sr, hr, hj⟩ := h
exact fun n hn => (SymState.join_names hj).2 n (CaseItem2.symExecList_names rest hr n hn)
termination_by structural its => its
end
/-- An arm's body state keeps the names of the starting state. -/
theorem CaseItem2.symArm_names : ∀ (it : CaseItem2) {d : Design2} {S : SymState}
{pats : List Expr2} {Sb : SymState},
it.symArm d S = some (pats, Sb) → ∀ n ∈ S.names, n ∈ Sb.names
| .mk pats body, d, S, pats', Sb, h => by
simp only [CaseItem2.symArm] at h
split at h
· simp only [Option.bind_eq_some_iff, Option.map_eq_some_iff, Prod.mk.injEq] at h
obtain ⟨p, -, Sb', hb, -, rfl⟩ := h
exact Stmt2.symExec_names body hb
· cases h
/-- The arms' names grow too: every name of the starting state reaches the
`case`'s result (through the default, which every arm list ends in). -/
theorem CaseItem2.symExecList_names_arm {d : Design2} {S : SymState} {subj : Expr2}
{mode : CaseMode} {Sd S' : SymState} {its : List CaseItem2}
(h : CaseItem2.symExecList d S subj mode Sd its = some S') {it : CaseItem2}
(hit : it ∈ its) {pats : List Expr2} {Sb : SymState} (ha : it.symArm d S = some (pats, Sb)) :
∀ n ∈ Sb.names, n ∈ S'.names := by
induction its generalizing S' with
| nil => exact absurd hit List.not_mem_nil
| cons it' rest ih =>
simp only [CaseItem2.symExecList, Option.bind_eq_some_iff] at h
obtain ⟨arm, harm, Sr, hr, hj⟩ := h
rcases List.mem_cons.mp hit with rfl | hit
· rw [ha, Option.some.injEq] at harm
subst harm
exact (SymState.join_names hj).1
· exact fun n hn => (SymState.join_names hj).2 n (ih hr hit n hn)
/-! ## `LVec` facts used by the lowering -/
namespace LVec
/-- A vector resized to its own width is itself. -/
theorem resizeU_of_width {v : LVec} {w : Nat} (h : v.width = w) : v.resizeU w = v := by
apply LVec.ext_getD (by simp [h])
intro i hi
simp only [LVec.width_resizeU] at hi
simp only [LVec.resizeU, Array.getElem?_ofFn, hi, dite_true, Option.getD_some]
have : i < v.bits.size := by rw [← h] at hi; exact hi
simp [this]
/-- Bit `i` of a concatenation `{p, q}` (`q` in the low bits). -/
theorem getD_concat (p q : LVec) (i : Nat) :
(p.concat q).bits[i]?.getD .lx =
if i < q.width then q.bits[i]?.getD .lx else p.bits[i - q.width]?.getD .lx := by
simp only [LVec.concat, LVec.width]
rw [Array.getElem?_append]
by_cases hi : i < q.bits.size <;> simp [hi]
@[simp] theorem width_concat (p q : LVec) : (p.concat q).width = p.width + q.width := by
simp [LVec.concat, LVec.width, Nat.add_comm]
/-- The high part of `{p, q}` is `p`. -/
theorem extract_concat_hi (p q : LVec) : (p.concat q).extract q.width p.width = p := by
apply LVec.ext_getD (by simp)
intro i hi
simp only [LVec.width_extract] at hi
rw [LVec.getD_extract, if_pos hi, getD_concat, if_neg (by omega), Nat.add_sub_cancel_left]
/-- A window inside the low part of `{p, q}` is a window of `q`. -/
theorem extract_concat_lo (p q : LVec) {off w : Nat} (h : off + w ≤ q.width) :
(p.concat q).extract off w = q.extract off w := by
apply LVec.ext_getD (by simp)
intro i hi
simp only [LVec.width_extract] at hi
rw [LVec.getD_extract, LVec.getD_extract, if_pos hi, if_pos hi, getD_concat, if_pos (by omega)]
/-- **A write inside a window, seen through the window**: writing `p` at
`lo` and reading the window `[lo0, lo0 + w0)` around it is writing `p` at the
relative offset into the window. -/
theorem extract_insertAt_inner {old p : LVec} {lo lo0 w0 : Nat} (h1 : lo0 ≤ lo)
(h3 : lo + p.width ≤ old.width) :
(old.insertAt lo p).extract lo0 w0 = (old.extract lo0 w0).insertAt (lo - lo0) p := by
apply LVec.ext_getD (by simp)
intro i hi
simp only [LVec.width_extract] at hi
rw [LVec.getD_extract, if_pos hi, LVec.getD_insertAt, LVec.getD_insertAt, LVec.width_extract,
if_pos hi, LVec.getD_extract, if_pos hi]
by_cases hin : lo - lo0 ≤ i ∧ i < lo - lo0 + p.width
· rw [if_pos hin, if_pos ⟨by omega, by omega⟩, if_pos (by omega)]
congr 2
omega
· rw [if_neg hin, if_neg (by omega)]
/-- **A write as a concatenation** `{v[w0-1 : r+k], p, v[r-1 : 0]}` (`k` the
part's width). -/
theorem insertAt_eq_concat {v p : LVec} {r : Nat} (h : r + p.width ≤ v.width) :
v.insertAt r p =
(v.extract (r + p.width) (v.width - (r + p.width))).concat (p.concat (v.extract 0 r)) := by
apply LVec.ext_getD (by simp; omega)
intro i hi
simp only [LVec.width_insertAt] at hi
rw [LVec.getD_insertAt, if_pos hi, getD_concat, getD_concat]
simp only [LVec.width_concat, LVec.width_extract]
by_cases hlo : i < r
· rw [if_neg (by omega), if_pos (by omega), if_pos hlo, LVec.getD_extract, if_pos hlo,
Nat.zero_add]
· by_cases hmid : i < r + p.width
· rw [if_pos ⟨by omega, hmid⟩, if_pos (by omega), if_neg hlo]
· rw [if_neg (by omega), if_neg (by omega), LVec.getD_extract, if_pos (by omega)]
congr 2
omega
end LVec
/-! ## `ReadWin` helpers -/
/-- A window agreement whose right-hand state binds the signal hands over a
binding of the left-hand state with the same width and window. -/
theorem ReadWin.agree_win_some {st st' : SvState} {n : String} {lo w : Nat}
(h : (ReadWin.win n lo w).agree st st') {u' : LVec}
(hu' : SvState.lookup st' n = some u') :
∃ u, SvState.lookup st n = some u ∧ u.width = u'.width ∧ u.extract lo w = u'.extract lo w := by
simp only [ReadWin.agree, hu', Option.map_some] at h
cases hu : SvState.lookup st n with
| none => rw [hu] at h; cases h
| some u =>
rw [hu] at h
simp only [Option.map_some, Option.some.injEq, Prod.mk.injEq] at h
exact ⟨u, rfl, h.1, h.2⟩
/-- A 1-bit read of a bit inside a window that overlaps nothing overlaps
nothing. -/
theorem ReadWin.bit_overlaps_false {n : String} {lo w k : Nat} {V : Win} (hk : k < w)
(h : Win.overlaps (n, lo, w) V = false) :
(ReadWin.win n (lo + k) 1).overlaps V = false :=
Bool.eq_false_iff.mpr fun hov => by
have := Comb2Term.overlaps_win_of_bit hk hov
simp only [ReadWin.overlaps] at this
rw [h] at this
cases this
/-! ## The semantics of a symbolic state -/
/-- **`S` describes `st` over the pre-state `st₀`**: same keys and widths;
the entry windows are pairwise disjoint; every entry window holds the value
of its expression in `st₀` (whenever that evaluates); every bit outside the
entry windows is `st₀`'s. -/
structure SymRel (st₀ : SvState) (S : SymState) (st : SvState) : Prop where
/-- Same key list as the pre-state. -/
keys : st.map Prod.fst = st₀.map Prod.fst
/-- Same widths as the pre-state. -/
width : ∀ n, (SvState.lookup st n).map LVec.width = (SvState.lookup st₀ n).map LVec.width
/-- The entry windows are pairwise disjoint. -/
disj : (S.map Prod.fst).Pairwise fun a b => Win.overlaps a b = false
/-- Every entry window holds its expression's pre-state value. -/
entry : ∀ W E, (W, E) ∈ S → ∀ v, evalPure2 st₀ E = .ok v →
∃ u, SvState.lookup st W.1 = some u ∧ u.extract W.2.1 W.2.2 = v
/-- Every bit outside the entry windows is the pre-state's. -/
frame : ∀ n b, (∀ W ∈ S.map Prod.fst, (ReadWin.win n b 1).overlaps W = false) →
(ReadWin.win n b 1).agree st st₀
/-- The empty symbolic state describes the pre-state. -/
theorem SymRel.init (st₀ : SvState) : SymRel st₀ [] st₀ :=
⟨rfl, fun _ => rfl, List.Pairwise.nil, fun _ _ h => absurd h List.not_mem_nil,
fun _ _ _ => ReadWin.agree_refl _ _⟩
/-- Width facts about the pre-state for the signals a symbolic state writes:
each is bound at its declared width. -/
def SymOk (d : Design2) (st₀ : SvState) (S : SymState) : Prop :=
∀ n ∈ S.names, ∀ dw, d.declWidth n = some dw → (SvState.lookup st₀ n).map LVec.width = some dw
theorem SymOk.mono {d : Design2} {st₀ : SvState} {S S' : SymState} (h : SymOk d st₀ S')
(hm : ∀ n ∈ S.names, n ∈ S'.names) : SymOk d st₀ S :=
fun n hn => h n (hm n hn)
namespace SymRel
variable {st₀ st : SvState} {S : SymState}
/-- A window disjoint from every entry window is the pre-state's. -/
theorem win_of_disjoint (hR : SymRel st₀ S st) {n : String} {lo w : Nat}
(h : ∀ W ∈ S.map Prod.fst, Win.overlaps (n, lo, w) W = false) :
(ReadWin.win n lo w).agree st st₀ :=
ReadWin.agree_win_of_bits (hR.width n) fun _ hk =>
hR.frame n _ fun W hW => ReadWin.bit_overlaps_false hk (h W hW)
/-- A signal no entry writes is the pre-state's. -/
theorem lookup_of_not_written (hR : SymRel st₀ S st) {n : String}
(h : ∀ W ∈ S.map Prod.fst, W.1 ≠ n) : SvState.lookup st n = SvState.lookup st₀ n :=
ReadWin.lookup_eq_of_agree_bits fun b => hR.frame n b fun W hW => by
obtain ⟨m, lo, w⟩ := W
have hne : n ≠ m := fun hnm => h _ hW hnm.symm
simp [ReadWin.overlaps, Win.overlaps, hne]
/-- A bound signal stays bound. -/
theorem lookup_some (hR : SymRel st₀ S st) {n : String} {u₀ : LVec}
(h : SvState.lookup st₀ n = some u₀) : ∃ u, SvState.lookup st n = some u ∧ u.width = u₀.width := by
have := hR.width n
rw [h] at this
cases hu : SvState.lookup st n with
| none => rw [hu] at this; cases this
| some u => rw [hu] at this; exact ⟨u, rfl, by simpa using this⟩
end SymRel
/-! ### Substitution is sound -/
/-- `x` decides `.ok a` only if `y` decides `.ok a` too. -/
def ResOkImp {α : Type} (x y : Res α) : Prop := ∀ a, x = .ok a → y = .ok a
theorem ResOkImp.refl {α : Type} (x : Res α) : ResOkImp x x := fun _ h => h
theorem ResOkImp.bind {α β : Type} {x y : Res α} {f g : α → Res β} (h : ResOkImp x y)
(hf : ∀ a, ResOkImp (f a) (g a)) : ResOkImp (x >>= f) (y >>= g) := by
intro b hb
cases hx : x with
| ok a =>
rw [hx] at hb
rw [h a hx]
exact hf a b hb
| timeout => rw [hx] at hb; cases hb
| unsupported msg => rw [hx] at hb; cases hb
/-- Substitution under a slice of a non-identifier is the slice of the
substituted base. -/
theorem Expr2.sliceSubst_of_not_ident {d : Design2} {S : SymState} {a : Expr2}
(h : ∀ x, a ≠ .ident x) (lo w : Nat) (r : Option Expr2) :
Expr2.sliceSubst d S a lo w r = r.map fun a' => .slice a' lo w := by
cases a <;> first | rfl | exact absurd rfl (h _)
private theorem symSubst_aux {d : Design2} {S : SymState} {st₀ st : SvState}
(hR : SymRel st₀ S st) (hok : SymOk d st₀ S) : ∀ n : Nat,
(∀ e : Expr2, e.size < n → ∀ e', e.symSubst d S = some e' →
ResOkImp (evalPure2 st₀ e') (evalPure2 st e)) ∧
(∀ es : List Expr2, Expr2.listSize es < n → ∀ es', Expr2.listSymSubst d S es = some es' →
ResOkImp (evalPures2 st₀ es') (evalPures2 st es))
| 0 => ⟨fun _ h => absurd h (Nat.not_lt_zero _), fun _ h => absurd h (Nat.not_lt_zero _)⟩
| n + 1 => by
obtain ⟨ihE, ihL⟩ := symSubst_aux hR hok n
refine ⟨fun e hs e' h => ?_, fun es hs es' h => ?_⟩
· cases e with
| lit v =>
simp only [Expr2.symSubst, Option.some.injEq] at h
subst h
exact ResOkImp.refl _
| ident x =>
simp only [Expr2.symSubst] at h
split at h
· simp only [SymState.readWhole] at h
split at h
· next hwn =>
simp only [Option.bind_eq_some_iff] at h
obtain ⟨dw, hdw, hf⟩ := h
have hm := SymState.findWin_eq_some hf
intro v hv
obtain ⟨u, hu, hext⟩ := hR.entry _ _ hm v hv
have hw := (hR.width x).trans (hok x (SymState.writesName_eq_true hwn) dw hdw)
rw [hu] at hw
simp only [Option.map_some, Option.some.injEq] at hw
simp only at hext
rw [← hw, LVec.extract_full] at hext
simp [evalPure2, readSignal, hu, hext]
· next hwn =>
cases h
intro v hv
have hl := hR.lookup_of_not_written
(SymState.writesName_eq_false (Bool.eq_false_iff.mpr hwn))
simpa [evalPure2, readSignal, hl] using hv
· cases h
| unsupported => simp [Expr2.symSubst] at h
| unary op a =>
simp only [Expr2.symSubst, Option.map_eq_some_iff] at h
obtain ⟨a', ha, rfl⟩ := h
simp only [Expr2.size] at hs
simp only [evalPure2]
exact ResOkImp.bind (ihE a (by omega) a' ha) fun _ => ResOkImp.refl _
| resizeSigned w a =>
simp only [Expr2.symSubst, Option.map_eq_some_iff] at h
obtain ⟨a', ha, rfl⟩ := h
simp only [Expr2.size] at hs
simp only [evalPure2]
exact ResOkImp.bind (ihE a (by omega) a' ha) fun _ => ResOkImp.refl _
| squash2 a =>
simp only [Expr2.symSubst, Option.map_eq_some_iff] at h
obtain ⟨a', ha, rfl⟩ := h
simp only [Expr2.size] at hs
simp only [evalPure2]
exact ResOkImp.bind (ihE a (by omega) a' ha) fun _ => ResOkImp.refl _
| repl k a =>
simp only [Expr2.symSubst, Option.map_eq_some_iff] at h
obtain ⟨a', ha, rfl⟩ := h
simp only [Expr2.size] at hs
simp only [evalPure2]
exact ResOkImp.bind (ihE a (by omega) a' ha) fun _ => ResOkImp.refl _
| reduce op a =>
simp only [Expr2.symSubst, Option.map_eq_some_iff] at h
obtain ⟨a', ha, rfl⟩ := h
simp only [Expr2.size] at hs
simp only [evalPure2]
exact ResOkImp.bind (ihE a (by omega) a' ha) fun _ => ResOkImp.refl _
| resize w a =>
simp only [Expr2.symSubst, Option.map_eq_some_iff] at h
obtain ⟨a', ha, rfl⟩ := h
simp only [Expr2.size] at hs
simp only [evalPure2]
exact ResOkImp.bind (ihE a (by omega) a' ha) fun _ => ResOkImp.refl _
| binary op l r =>
simp only [Expr2.symSubst, Option.bind_eq_some_iff, Option.map_eq_some_iff] at h
obtain ⟨l', hl, r', hr, rfl⟩ := h
simp only [Expr2.size] at hs
simp only [evalPure2]
exact ResOkImp.bind (ihE l (by omega) l' hl) fun _ =>
ResOkImp.bind (ihE r (by omega) r' hr) fun _ => ResOkImp.refl _
| scmp op l r =>
simp only [Expr2.symSubst, Option.bind_eq_some_iff, Option.map_eq_some_iff] at h
obtain ⟨l', hl, r', hr, rfl⟩ := h
simp only [Expr2.size] at hs
rw [evalPure2]
exact ResOkImp.bind (ihE l (by omega) l' hl) fun _ =>
ResOkImp.bind (ihE r (by omega) r' hr) fun _ => ResOkImp.refl _
| logical b l r =>
simp only [Expr2.symSubst, Option.bind_eq_some_iff, Option.map_eq_some_iff] at h
obtain ⟨l', hl, r', hr, rfl⟩ := h
simp only [Expr2.size] at hs
simp only [evalPure2]
exact ResOkImp.bind (ihE l (by omega) l' hl) fun _ =>
ResOkImp.bind (ihE r (by omega) r' hr) fun _ => ResOkImp.refl _
| index a i w =>
simp only [Expr2.symSubst, Option.bind_eq_some_iff, Option.map_eq_some_iff] at h
obtain ⟨a', ha, i', hi, rfl⟩ := h
simp only [Expr2.size] at hs
simp only [evalPure2]
exact ResOkImp.bind (ihE a (by omega) a' ha) fun _ =>
ResOkImp.bind (ihE i (by omega) i' hi) fun _ => ResOkImp.refl _
| ternary c t f =>
simp only [Expr2.symSubst, Option.bind_eq_some_iff, Option.map_eq_some_iff] at h
obtain ⟨c', hc, t', ht, f', hf, rfl⟩ := h
simp only [Expr2.size] at hs
simp only [evalPure2]
exact ResOkImp.bind (ihE c (by omega) c' hc) fun _ =>
ResOkImp.bind (ihE t (by omega) t' ht) fun _ =>
ResOkImp.bind (ihE f (by omega) f' hf) fun _ => ResOkImp.refl _
| concat parts =>
simp only [Expr2.symSubst, Option.map_eq_some_iff] at h
obtain ⟨ps, hps, rfl⟩ := h
simp only [Expr2.size] at hs
simp only [evalPure2]
exact ResOkImp.bind (ihL parts.toList (by omega) ps hps) fun _ => ResOkImp.refl _
| slice a lo w =>
simp only [Expr2.symSubst] at h
by_cases ha : ∃ x, a = .ident x
· obtain ⟨x, rfl⟩ := ha
simp only [Expr2.sliceSubst] at h
split at h
· simp only [SymState.readWin] at h
split at h
· next W E hc =>
cases h
obtain ⟨hm, hcont⟩ := SymState.findCover_eq_some hc
obtain ⟨hn, hlo, hhi⟩ := Win.contains_iff.mp hcont
intro v hv
simp only [evalPure2, Pure2.res_bind_eq_ok, Pure2.res_pure_eq,
Res.ok.injEq] at hv
obtain ⟨vE, hvE, rfl⟩ := hv
obtain ⟨u, hu, hext⟩ := hR.entry _ _ hm vE hvE
simp only at hn hlo hhi
rw [hn] at hu
have hx := LVec.extract_extract u (lo := W.2.1) (w := W.2.2) (lo' := lo - W.2.1)
(w' := w) (by omega)
rw [Nat.add_sub_cancel' hlo, hext] at hx
simp [evalPure2, readSignal, hu, hx]
· next hc =>
split at h
· cases h
· next hov =>
cases h
intro v hv
have hag := hR.win_of_disjoint (n := x) (lo := lo) (w := w) fun W hW =>
Win.overlaps_symm_false
(SymState.overlapsAny_eq_false (Bool.eq_false_iff.mpr hov) W hW)
rw [evalPure2_congr_reads (st := st) (st' := st₀) (e := .slice (.ident x) lo w)
(by intro r hr; simp [Expr2.reads, Expr2.sliceReads] at hr; subst hr; exact hag)]
exact hv
· cases h
· rw [Expr2.sliceSubst_of_not_ident (fun x hx => ha ⟨x, hx⟩)] at h
simp only [Option.map_eq_some_iff] at h
obtain ⟨a', ha', rfl⟩ := h
simp only [Expr2.size] at hs
simp only [evalPure2]
exact ResOkImp.bind (ihE a (by omega) a' ha') fun _ => ResOkImp.refl _
· cases es with
| nil =>
simp only [Expr2.listSymSubst, Option.some.injEq] at h
subst h
exact ResOkImp.refl _
| cons e rest =>
simp only [Expr2.listSymSubst, Option.bind_eq_some_iff, Option.map_eq_some_iff] at h
obtain ⟨e', he, rest', hrest, rfl⟩ := h
simp only [Expr2.listSize] at hs
simp only [evalPures2]
exact ResOkImp.bind (ihE e (by omega) e' he) fun _ =>
ResOkImp.bind (ihL rest (by omega) rest' hrest) fun _ => ResOkImp.refl _
/-- **Substitution is sound**: if the substituted expression evaluates in
the pre-state, the original evaluates to the same value in any state the
symbolic state describes. -/
theorem Expr2.symSubst_sound {d : Design2} {S : SymState} {st₀ st : SvState}
(hR : SymRel st₀ S st) (hok : SymOk d st₀ S) {e e' : Expr2} (h : e.symSubst d S = some e')
{v : LVec} (hv : evalPure2 st₀ e' = .ok v) : evalPure2 st e = .ok v :=
(symSubst_aux hR hok (e.size + 1)).1 e (Nat.lt_succ_self _) e' h v hv
/-- List twin of `Expr2.symSubst_sound`. -/
theorem Expr2.listSymSubst_sound {d : Design2} {S : SymState} {st₀ st : SvState}
(hR : SymRel st₀ S st) (hok : SymOk d st₀ S) {es es' : List Expr2}
(h : Expr2.listSymSubst d S es = some es') {vs : List LVec}
(hv : evalPures2 st₀ es' = .ok vs) : evalPures2 st es = .ok vs :=
(symSubst_aux hR hok (Expr2.listSize es + 1)).2 es (Nat.lt_succ_self _) es' h vs hv
/-! ### Guards -/
private theorem foldl_or_eq_l1 : ∀ (l : List Logic) (a : Logic),
l.foldl Logic.or a = .l1 ↔ a = .l1 ∨ ∃ x ∈ l, x = .l1
| [], a => by simp
| x :: xs, a => by
rw [List.foldl_cons, foldl_or_eq_l1 xs]
have h : Logic.or a x = .l1 ↔ a = .l1 ∨ x = .l1 := by cases a <;> cases x <;> decide
rw [h]
simp only [List.mem_cons, exists_eq_or_imp]
constructor
· rintro ((h | h) | h) <;> simp_all
· rintro (h | h | h) <;> simp_all
/-- `|v` is `1` exactly when `v` has a `1` bit — the `if` truthiness. -/
theorem LVec.redOr_eq_l1_iff (v : LVec) : v.redOr = .l1 ↔ v.condTrue = true := by
rw [LVec.redOr, LVec.condTrue, ← Array.foldl_toList, foldl_or_eq_l1, Array.any_eq_true']
simp
/-- `?:` on a known `1` picks the first arm. -/
theorem LVec.ternary_ofLogic_l1 (a b : LVec) : LVec.ternary (LVec.ofLogic .l1) a b = a := by
simp only [LVec.ternary]; rw [if_pos (by simp [LVec.condTrue, LVec.ofLogic])]
/-- `?:` on a known `0` picks the second arm. -/
theorem LVec.ternary_ofLogic_l0 (a b : LVec) : LVec.ternary (LVec.ofLogic .l0) a b = b := by
simp only [LVec.ternary]
rw [if_neg (by simp [LVec.condTrue, LVec.ofLogic]),
if_pos (by simp [LVec.isKnownZero, LVec.ofLogic])]
theorem LVec.ofLogic_beq_l1 (a : Logic) : (LVec.ofLogic a == LVec.ofLogic .l1) = (a == .l1) := by
cases a <;> decide
theorem LVec.or_ofLogic (a b : Logic) :
(LVec.ofLogic a).or (LVec.ofLogic b) = LVec.ofLogic (Logic.or a b) := by
simp [LVec.or, LVec.zipWith, LVec.ofLogic, LVec.width]
/-- A boolean as a known bit. -/
abbrev Logic.ofB (b : Bool) : Logic := if b then .l1 else .l0
@[simp] theorem Logic.ofB_true : Logic.ofB true = .l1 := rfl
@[simp] theorem Logic.ofB_false : Logic.ofB false = .l0 := rfl
/-- **The `if` guard decides like `if`**: whenever it evaluates, its
condition evaluates, and the guard is the known bit of the condition's
truthiness. -/
theorem evalPure2_ifGuard {st : SvState} {c : Expr2} {g : LVec}
(h : evalPure2 st c.ifGuard = .ok g) :
∃ cv, evalPure2 st c = .ok cv ∧ g = LVec.ofLogic (Logic.ofB cv.condTrue) := by
simp only [Expr2.ifGuard, evalPure2, Pure2.res_bind_eq_ok, Pure2.res_pure_eq, Res.ok.injEq] at h
obtain ⟨a, ⟨cv, hcv, rfl⟩, b, rfl, rfl⟩ := h
refine ⟨cv, hcv, ?_⟩
simp only [evalBinOp, evalRedOp, LVec.eqCase, LVec.ofLogic_beq_l1, Logic.ofB]
by_cases hc : cv.condTrue = true
· rw [if_pos hc, if_pos (by simpa using (LVec.redOr_eq_l1_iff cv).mpr hc)]
· rw [if_neg hc, if_neg (by simpa using fun h => hc ((LVec.redOr_eq_l1_iff cv).mp h))]
/-- **A pattern guard decides like `caseHit`.** -/
theorem evalPure2_hitGuard {st : SvState} {mode : CaseMode} {subj pat : Expr2} {g : LVec}
(h : evalPure2 st (Expr2.hitGuard mode subj pat) = .ok g) :
∃ sv pv, evalPure2 st subj = .ok sv ∧ evalPure2 st pat = .ok pv ∧
g = LVec.ofLogic (Logic.ofB (caseHit mode sv pv)) := by
cases mode with
| normal =>
simp only [Expr2.hitGuard, Bool.false_eq_true, ↓reduceIte, evalPure2,
Pure2.res_bind_eq_ok, Pure2.res_pure_eq, Res.ok.injEq] at h
obtain ⟨sv, hsv, pv, hpv, rfl⟩ := h
refine ⟨sv, pv, hsv, hpv, ?_⟩
simp only [evalBinOp, LVec.eqCase, caseHit, Bool.false_eq_true, ↓reduceIte, Logic.ofB]
rfl
| inside =>
simp only [Expr2.hitGuard, ↓reduceIte, evalPure2,
Pure2.res_bind_eq_ok, Pure2.res_pure_eq, Res.ok.injEq] at h
obtain ⟨a, ⟨sv, hsv, pv, hpv, rfl⟩, b, rfl, rfl⟩ := h
refine ⟨sv, pv, hsv, hpv, ?_⟩
simp only [evalBinOp, LVec.eqCase, LVec.ofLogic_beq_l1, caseHit, ↓reduceIte,
LVec.matchesWild, Logic.ofB]
rfl
| casez =>
simp only [Expr2.hitGuard, evalPure2, Pure2.res_bind_eq_ok,
Pure2.res_pure_eq, Res.ok.injEq] at h
obtain ⟨sv, hsv, pv, hpv, rfl⟩ := h
refine ⟨sv, pv, hsv, hpv, ?_⟩
simp only [evalBinOp, caseHit, Logic.ofB]
rfl
| casex =>
simp only [Expr2.hitGuard, evalPure2, Pure2.res_bind_eq_ok,
Pure2.res_pure_eq, Res.ok.injEq] at h
obtain ⟨sv, hsv, pv, hpv, rfl⟩ := h
refine ⟨sv, pv, hsv, hpv, ?_⟩
simp only [evalBinOp, caseHit, Logic.ofB]
rfl
/-- **An arm guard decides like the arm's hit test** (`pvs.any (caseHit …)`),
whenever the subject's value is `sv`. -/
theorem evalPure2_anyGuard {st : SvState} {mode : CaseMode} {subj : Expr2} {sv : LVec}
(hs : ∀ v, evalPure2 st subj = .ok v → v = sv) :
∀ {pats : List Expr2} {g : LVec}, evalPure2 st (Expr2.anyGuard mode subj pats) = .ok g →
∃ pvs, evalPures2 st pats = .ok pvs ∧
g = LVec.ofLogic (Logic.ofB (pvs.any fun pv => caseHit mode sv pv))
| [], g, h => by
simp only [Expr2.anyGuard, evalPure2, Res.ok.injEq] at h
exact ⟨[], rfl, by simp [h, Logic.ofB]⟩
| p :: ps, g, h => by
simp only [Expr2.anyGuard, evalPure2_binary, Pure2.res_bind_eq_ok, Pure2.res_pure_eq,
Res.ok.injEq] at h
obtain ⟨g1, h1, g2, h2, rfl⟩ := h
obtain ⟨sv', pv, hsv', hpv, rfl⟩ := evalPure2_hitGuard h1
obtain ⟨pvs, hpvs, rfl⟩ := evalPure2_anyGuard hs h2
have hsv := hs sv' hsv'
subst hsv
refine ⟨pv :: pvs, by simp [evalPures2, hpv, hpvs], ?_⟩
simp only [evalBinOp, LVec.or_ofLogic, List.any_cons, Logic.ofB]
by_cases h1b : caseHit mode sv' pv = true <;>
by_cases h2b : (pvs.any fun pv => caseHit mode sv' pv) = true <;>
simp only [h1b, h2b, Bool.or_true, Bool.or_false, Bool.false_eq_true, ↓reduceIte] <;>
simp_all [Logic.or]
/-! ### Writes and joins are sound -/
/-- A held window evaluates to the pre-state window. -/
theorem evalPure2_held {st : SvState} {W : Win} {v : LVec} (h : evalPure2 st W.held = .ok v) :
∃ u, SvState.lookup st W.1 = some u ∧ v = u.extract W.2.1 W.2.2 := by
cases hl : SvState.lookup st W.1 with
| none => simp [Win.held, evalPure2, readSignal, hl] at h
| some u =>
simp [Win.held, evalPure2, readSignal, hl] at h
exact ⟨u, rfl, h.symm⟩
/-- A splice evaluates to the concatenation of its three parts. -/
theorem evalPure2_splice {st : SvState} {E0 E : Expr2} {w0 r w : Nat} {v : LVec}
(h : evalPure2 st (Expr2.splice E0 w0 r w E) = .ok v) :
∃ v0 p, evalPure2 st E0 = .ok v0 ∧ evalPure2 st E = .ok p ∧
v = (v0.extract (r + w) (w0 - (r + w))).concat (p.concat (v0.extract 0 r)) := by
cases h0 : evalPure2 st E0 with
| ok v0 =>
cases h1 : evalPure2 st E with
| ok p =>
simp [Expr2.splice, evalPure2, evalPures2, h0, h1] at h
exact ⟨v0, p, rfl, rfl, h.symm⟩
| timeout => simp [Expr2.splice, evalPure2, evalPures2, h0, h1] at h
| unsupported m => simp [Expr2.splice, evalPure2, evalPures2, h0, h1] at h
| timeout => simp [Expr2.splice, evalPure2, evalPures2, h0] at h
| unsupported m => simp [Expr2.splice, evalPure2, evalPures2, h0] at h
namespace SymState
theorem map_fst_replace (S : SymState) (W : Win) (f : Expr2) :
(S.map fun e => if e.1 == W then (W, f) else e).map Prod.fst = S.map Prod.fst := by
induction S with
| nil => rfl
| cons e rest ih =>
simp only [List.map_cons, ih, List.cons.injEq, and_true]
by_cases h : e.1 = W <;> simp [h]
theorem mem_replace {S : SymState} {W : Win} {f : Expr2} {V : Win} {F : Expr2}
(h : (V, F) ∈ S.map fun e => if e.1 == W then (W, f) else e) :
(V = W ∧ F = f) ∨ ((V, F) ∈ S ∧ V ≠ W) := by
obtain ⟨e, he, heq⟩ := List.mem_map.mp h
by_cases hW : e.1 = W
· simp only [hW, beq_self_eq_true, ↓reduceIte, Prod.mk.injEq] at heq
exact Or.inl ⟨heq.1.symm, heq.2.symm⟩
· have : (e.1 == W) = false := by simpa using hW
rw [this] at heq
simp only [Bool.false_eq_true, ↓reduceIte] at heq
subst heq
exact Or.inr ⟨he, hW⟩
/-- Two distinct windows of a symbolic state described by `SymRel` are
disjoint. -/
theorem disj_of_ne {st₀ st : SvState} {S : SymState} (hR : SymRel st₀ S st) {V W : Win}
(hV : V ∈ S.map Prod.fst) (hW : W ∈ S.map Prod.fst) (hne : V ≠ W) :
Win.overlaps V W = false :=
List.pairwise_of_mem_ne (fun _ _ h => Win.overlaps_symm_false h) hR.disj hV hW hne
/-- A 1-bit read outside a containing window is outside every contained one. -/
theorem bit_not_overlaps_of_contains {W0 W : Win} (hc : Win.contains W0 W = true) {m : String}
{b : Nat} (h : (ReadWin.win m b 1).overlaps W0 = false) :
(ReadWin.win m b 1).overlaps W = false := by
obtain ⟨n0, lo0, w0⟩ := W0
obtain ⟨n, lo, w⟩ := W
obtain ⟨hn, hlo, hhi⟩ := Win.contains_iff.mp hc
simp only at hn hlo hhi
subst hn
rw [← Bool.not_eq_true, ReadWin.overlaps_win_one] at h ⊢
rintro ⟨hm, h1, h2⟩
exact h ⟨hm, by omega, by omega⟩
/-- A window disjoint from a containing window is disjoint from every
contained one. -/
theorem overlaps_false_of_contains {W0 W V : Win} (hc : Win.contains W0 W = true)
(h : Win.overlaps V W0 = false) : Win.overlaps V W = false := by
obtain ⟨n0, lo0, w0⟩ := W0
obtain ⟨n, lo, w⟩ := W
obtain ⟨m, lo', w'⟩ := V
obtain ⟨hn, hlo, hhi⟩ := Win.contains_iff.mp hc
simp only at hn hlo hhi
subst hn
rw [Win.overlaps_eq_false] at h ⊢
rcases h with h | h | h
· exact Or.inl h
· exact Or.inr (Or.inl (by omega))
· exact Or.inr (Or.inr (by omega))
/-- **A blocking write is sound**: writing `p` (the value the symbolic
expression `E` denotes) into window `W` (bound and in range) of a state the
symbolic state describes gives a state the written symbolic state
describes. -/
theorem write_sound {S S' : SymState} {W : Win} {E : Expr2} (hw : S.write W E = some S')
{st₀ st : SvState} (hR : SymRel st₀ S st) {old p : LVec}
(hold : SvState.lookup st W.1 = some old) (hin : W.2.1 + W.2.2 ≤ old.width)
(hp : p.width = W.2.2) (hE : ∀ v, evalPure2 st₀ E = .ok v → v = p) :
SymRel st₀ S' (SvState.set st W.1 (old.insertAt W.2.1 p)) := by
obtain ⟨n, lo, w⟩ := W
simp only at hold hin hp ⊢
have hkeys : (SvState.set st n (old.insertAt lo p)).map Prod.fst = st₀.map Prod.fst := by
rw [SvState.keys_set _ (SvState.lookup_isSome_iff.mp (by simp [hold])), hR.keys]
have hwidth : ∀ m, (SvState.lookup (SvState.set st n (old.insertAt lo p)) m).map LVec.width =
(SvState.lookup st₀ m).map LVec.width := by
intro m
rw [← hR.width m, SvState.lookup_set]
by_cases h : n = m
· subst h; simp [hold]
· simp [h]
have hframe : ∀ r : ReadWin, r.overlaps (n, lo, w) = false →
r.agree st (SvState.set st n (old.insertAt lo p)) :=
fun r hr => ReadWin.agree_set hold hp hr
have hkeep : ∀ V F, (V, F) ∈ S → Win.overlaps V (n, lo, w) = false →
∀ v, evalPure2 st₀ F = .ok v →
∃ u, SvState.lookup (SvState.set st n (old.insertAt lo p)) V.1 = some u ∧
u.extract V.2.1 V.2.2 = v := by
intro V F hm hov v hv
obtain ⟨u, hu, hext⟩ := hR.entry V F hm v hv
obtain ⟨u', hu', -, hext'⟩ := ReadWin.agree_win_some
(ReadWin.agree_symm (hframe (.win V.1 V.2.1 V.2.2) hov)) hu
exact ⟨u', hu', hext'.trans hext⟩
have hnew : SvState.lookup (SvState.set st n (old.insertAt lo p)) n =
some (old.insertAt lo p) := SvState.lookup_set_self _ _ _
have hbit : ∀ m b, (ReadWin.win m b 1).overlaps (n, lo, w) = false →
(ReadWin.win m b 1).agree st st₀ →
(ReadWin.win m b 1).agree (SvState.set st n (old.insertAt lo p)) st₀ :=
fun m b hov hag => ReadWin.agree_trans (ReadWin.agree_symm (hframe _ hov)) hag
unfold write at hw
split at hw
· -- the exact window is already an entry: replace it
next hany =>
cases hw
have hWm : (n, lo, w) ∈ S.map Prod.fst := by
simp only [List.any_eq_true, beq_iff_eq] at hany
obtain ⟨e, he, hEq⟩ := hany
exact List.mem_map.mpr ⟨e, he, hEq⟩
refine ⟨hkeys, hwidth, by rw [map_fst_replace]; exact hR.disj, ?_, ?_⟩
· intro V F hm v hv
rcases mem_replace hm with ⟨rfl, rfl⟩ | ⟨hm, hne⟩
· rw [hE v hv]
exact ⟨_, hnew, LVec.extract_insertAt_self hp hin⟩
· exact hkeep V F hm (disj_of_ne hR (List.mem_map_of_mem hm) hWm hne) v hv
· intro m b hb
rw [map_fst_replace] at hb
exact hbit m b (hb _ hWm) (hR.frame m b hb)
· split at hw
· -- splice into the entry whose window contains the written one
next W0 E0 hc =>
cases hw
obtain ⟨hm0, hcont⟩ := findCover_eq_some hc
have hW0m : W0 ∈ S.map Prod.fst := List.mem_map_of_mem hm0
obtain ⟨hn0, hlo0, hhi0⟩ := Win.contains_iff.mp hcont
simp only at hn0 hlo0 hhi0
refine ⟨hkeys, hwidth, by rw [map_fst_replace]; exact hR.disj, ?_, ?_⟩
· intro V F hm v hv
rcases mem_replace hm with ⟨rfl, rfl⟩ | ⟨hm, hne⟩
· -- the spliced entry
obtain ⟨v0, pE, h0, hb, rfl⟩ := evalPure2_splice hv
have hbp := hE pE hb
subst hbp
obtain ⟨u, hu, hext⟩ := hR.entry _ _ hm0 v0 h0
rw [hn0, hold, Option.some.injEq] at hu
subst hu
have hv0w : v0.width = V.2.2 := by rw [← hext]; simp
refine ⟨_, by rw [hn0]; exact hnew, ?_⟩
rw [LVec.extract_insertAt_inner hlo0 (by omega), hext,
LVec.insertAt_eq_concat (by omega), hv0w, hp]
· exact hkeep V F hm (overlaps_false_of_contains hcont
(disj_of_ne hR (List.mem_map_of_mem hm) hW0m hne)) v hv
· intro m b hb
rw [map_fst_replace] at hb
exact hbit m b (bit_not_overlaps_of_contains hcont (hb _ hW0m)) (hR.frame m b hb)
· split at hw
· cases hw
· -- a fresh window: append it
next hc hov =>
cases hw
have hdis := overlapsAny_eq_false (Bool.eq_false_iff.mpr hov)
refine ⟨hkeys, hwidth, ?_, ?_, ?_⟩
· rw [List.map_append, List.pairwise_append]
refine ⟨hR.disj, by simp, ?_⟩
intro V hV W hW
simp only [List.map_cons, List.map_nil, List.mem_singleton] at hW
subst hW
exact hdis V hV
· intro V F hm v hv
rcases List.mem_append.mp hm with hm | hm
· exact hkeep V F hm (hdis V (List.mem_map_of_mem hm)) v hv
· simp only [List.mem_singleton, Prod.mk.injEq] at hm
obtain ⟨rfl, rfl⟩ := hm
rw [hE v hv]
exact ⟨_, hnew, LVec.extract_insertAt_self hp hin⟩
· intro m b hb
have hb' : ∀ W ∈ S.map Prod.fst, (ReadWin.win m b 1).overlaps W = false :=
fun W hW => hb W (by rw [List.map_append]; exact List.mem_append_left _ hW)
exact hbit m b (hb _ (by simp)) (hR.frame m b hb')
/-- **A join is sound, first branch**: when the guard denotes `1` and the
first branch's symbolic state describes the state, the join describes it. -/
theorem join_sound_left {G : Expr2} {Sa Sb S' : SymState} (hj : join G Sa Sb = some S')
{st₀ st : SvState} (hG : ∀ g, evalPure2 st₀ G = .ok g → g = LVec.ofLogic .l1)
(hR : SymRel st₀ Sa st) : SymRel st₀ S' st := by
have hws := join_wins hj
unfold join at hj
split at hj
· next hd =>
cases hj
have hpw := (Win.allDisjoint_iff _).mp hd
refine ⟨hR.keys, hR.width, by rw [hws]; exact hpw, ?_, ?_⟩
· intro V F hm v hv
obtain ⟨V', hV', hEq⟩ := List.mem_map.mp hm
simp only [Prod.mk.injEq] at hEq
obtain ⟨rfl, rfl⟩ := hEq
have ha : evalPure2 st₀ (Sa.lookupOr V') = .ok v := by
unfold Expr2.joinIte at hv
split at hv
· exact hv
· simp only [evalPure2, Pure2.res_bind_eq_ok, Pure2.res_pure_eq, Res.ok.injEq] at hv
obtain ⟨g, hg, a, ha, b, -, rfl⟩ := hv
rw [hG g hg, LVec.ternary_ofLogic_l1]
exact ha
clear hv
simp only [lookupOr] at ha
cases hf : Sa.findWin V' with
| some E =>
rw [hf, Option.getD_some] at ha
exact hR.entry _ _ (findWin_eq_some hf) v ha
| none =>
rw [hf, Option.getD_none] at ha
have hnot := findWin_eq_none hf
have hdisj : ∀ U ∈ Sa.map Prod.fst, Win.overlaps V' U = false := by
intro U hU
refine List.pairwise_of_mem_ne (fun _ _ h => Win.overlaps_symm_false h) hpw hV'
(mem_joinWins_left hU) ?_
rintro rfl
exact hnot hU
have hag := hR.win_of_disjoint (n := V'.1) (lo := V'.2.1) (w := V'.2.2) hdisj
obtain ⟨u₀, hu₀, rfl⟩ := evalPure2_held ha
obtain ⟨u, hu, -, hext⟩ := ReadWin.agree_win_some hag hu₀
exact ⟨u, hu, hext⟩
· intro m b hb
exact hR.frame m b fun U hU => hb U (by rw [hws]; exact mem_joinWins_left hU)
· cases hj
/-- **A join is sound, second branch**: when the guard denotes `0` and the
second branch's symbolic state describes the state, the join describes it. -/
theorem join_sound_right {G : Expr2} {Sa Sb S' : SymState} (hj : join G Sa Sb = some S')
{st₀ st : SvState} (hG : ∀ g, evalPure2 st₀ G = .ok g → g = LVec.ofLogic .l0)
(hR : SymRel st₀ Sb st) : SymRel st₀ S' st := by
have hws := join_wins hj
unfold join at hj
split at hj
· next hd =>
cases hj
have hpw := (Win.allDisjoint_iff _).mp hd
refine ⟨hR.keys, hR.width, by rw [hws]; exact hpw, ?_, ?_⟩
· intro V F hm v hv
obtain ⟨V', hV', hEq⟩ := List.mem_map.mp hm
simp only [Prod.mk.injEq] at hEq
obtain ⟨rfl, rfl⟩ := hEq
have hb : evalPure2 st₀ (Sb.lookupOr V') = .ok v := by
unfold Expr2.joinIte at hv
split at hv
· next hsame => rw [← Expr2.eq_of_same _ _ hsame]; exact hv
· simp only [evalPure2, Pure2.res_bind_eq_ok, Pure2.res_pure_eq, Res.ok.injEq] at hv
obtain ⟨g, hg, a, -, b, hb, rfl⟩ := hv
rw [hG g hg, LVec.ternary_ofLogic_l0]
exact hb
clear hv
simp only [lookupOr] at hb
cases hf : Sb.findWin V' with
| some E =>
rw [hf, Option.getD_some] at hb
exact hR.entry _ _ (findWin_eq_some hf) v hb
| none =>
rw [hf, Option.getD_none] at hb
have hnot := findWin_eq_none hf
have hdisj : ∀ U ∈ Sb.map Prod.fst, Win.overlaps V' U = false := by
intro U hU
refine List.pairwise_of_mem_ne (fun _ _ h => Win.overlaps_symm_false h) hpw hV'
(mem_joinWins_right hU) ?_
rintro rfl
exact hnot hU
have hag := hR.win_of_disjoint (n := V'.1) (lo := V'.2.1) (w := V'.2.2) hdisj
obtain ⟨u₀, hu₀, rfl⟩ := evalPure2_held hb
obtain ⟨u, hu, -, hext⟩ := ReadWin.agree_win_some hag hu₀
exact ⟨u, hu, hext⟩
· intro m b hb
exact hR.frame m b fun U hU => hb U (by rw [hws]; exact mem_joinWins_right hU)
· cases hj
end SymState
/-! ## Symbolic execution is sound -/
/-- The static window of a blocking target, unpacked. -/
theorem Lhs2.symWin_spec {d : Design2} {t : Lhs2} {W : Win} (h : t.symWin d = some W) :
(∀ ps, t ≠ .concat ps) ∧ t.window? d = some W ∧
∃ dw, d.declWidth W.1 = some dw ∧ W.2.1 + W.2.2 ≤ dw := by
unfold Lhs2.symWin at h
split at h
· cases h
· next hnc =>
simp only [Option.bind_eq_some_iff] at h
obtain ⟨W', hW', dw, hdw, h⟩ := h
split at h
· next hle =>
cases h
exact ⟨fun ps hps => hnc ps hps, hW', dw, hdw, hle⟩
· cases h
theorem Expr2.wfB_spec {d : Design2} {e : Expr2} (h : e.wfB d = true) :
e.unsupportedFree = true ∧ ∀ x ∈ e.idents, x ∈ d.symNames := by
simpa [Expr2.wfB, List.all_eq_true] using h
theorem Expr2.listWfB_spec {d : Design2} {es : List Expr2} (h : Expr2.listWfB d es = true) :
Expr2.listUnsupportedFree es = true ∧ ∀ x ∈ Expr2.listIdents es, x ∈ d.symNames := by
simpa [Expr2.listWfB, List.all_eq_true] using h
/-- In a described state, a signal written by the symbolic state is bound at
its declared width. -/
theorem SymOk.lookup {d : Design2} {st₀ st : SvState} {S T : SymState} (hok : SymOk d st₀ S)
(hR : SymRel st₀ T st) {n : String} (hn : n ∈ S.names) {dw : Nat}
(hdw : d.declWidth n = some dw) : ∃ old, SvState.lookup st n = some old ∧ old.width = dw := by
have h := (hR.width n).trans (hok n hn dw hdw)
cases hl : SvState.lookup st n with
| none => rw [hl] at h; cases h
| some old => rw [hl] at h; exact ⟨old, rfl, by simpa using h⟩
/-- In a described state, every name bound in the pre-state is bound. -/
theorem SymRel.bound {st₀ st : SvState} {S : SymState} (hR : SymRel st₀ S st) {names : List String}
(hb : ∀ x ∈ names, ∃ v, SvState.lookup st₀ x = some v) :
∀ x ∈ names, ∃ v, SvState.lookup st x = some v := by
intro x hx
obtain ⟨u₀, hu₀⟩ := hb x hx
obtain ⟨u, hu, -⟩ := hR.lookup_some hu₀
exact ⟨u, hu⟩
/-- **One blocking assignment, run**: a successful run is the symbolic
write. -/
theorem execStmt2_blocking_sound {d : Design2} {st₀ st : SvState} {S S' : SymState}
{t : Lhs2} {e : Expr2} (hsym : (Stmt2.blockingAssign t e).symExec d S = some S')
(hok : SymOk d st₀ S') (hR : SymRel st₀ S st) {f : Nat} {nba nba' : NbaQueue2}
{st' : SvState} (hrun : execStmt2 (f + 1) st nba (.blockingAssign t e) = .ok (st', nba')) :
nba' = nba ∧ SymRel st₀ S' st' := by
simp only [Stmt2.symExec] at hsym
split at hsym
· simp only [Option.bind_eq_some_iff] at hsym
obtain ⟨e', he', W, hW, hw⟩ := hsym
obtain ⟨hnc, hwin, dw, hdw, hin⟩ := Lhs2.symWin_spec hW
have hokS : SymOk d st₀ S := hok.mono (SymState.write_names hw).2
obtain ⟨old, hold, hwold⟩ := hok.lookup hR (SymState.write_names hw).1 hdw
obtain ⟨n, lo, w⟩ := W
simp only at hold hin hdw hwold hwin
have hres : resolveLhs2 (f + 1) st t = .ok (n, some (lo, w)) :=
resolveLhs2_of_window? (by omega) hwin hold (by omega) (by rw [hdw, hwold])
simp only [execStmt2, Res.bind_eq_ok] at hrun
obtain ⟨v, hv, r, hr, hrun⟩ := hrun
have hr' := resolveLhs2_mono hr (by simp) (f + 1) (by omega)
rw [hres, Res.ok.injEq] at hr'
subst hr'
simp only [hold, Res.ok.injEq, Prod.mk.injEq, Pure2.res_pure_eq] at hrun
obtain ⟨rfl, rfl⟩ := hrun
refine ⟨rfl, SymState.write_sound hw hR hold (by show lo + w ≤ old.width; omega)
(LVec.width_resizeU _ _) ?_⟩
intro x hx
simp only [evalPure2, Pure2.res_bind_eq_ok, Pure2.res_pure_eq, Res.ok.injEq] at hx
obtain ⟨a, ha, rfl⟩ := hx
have := Expr2.symSubst_sound hR hokS he' ha
rw [evalPure2_of_evalExpr2_ok hv, Res.ok.injEq] at this
rw [this]
· cases hsym
/-- **Symbolic execution is sound** (one fuel induction over the statement
interpreter's mutual block): a successful run of a statement (statement
list, `case` arm list) from a state the input symbolic state describes
leaves the NBA queue alone and ends in a state the output symbolic state
describes. -/
theorem symExec_sound {d : Design2} {st₀ : SvState} : ∀ fuel : Nat,
(∀ (s : Stmt2) (S S' : SymState) (st : SvState) (nba : NbaQueue2) (st' : SvState)
(nba' : NbaQueue2),
s.symExec d S = some S' → SymOk d st₀ S' → SymRel st₀ S st →
execStmt2 fuel st nba s = .ok (st', nba') → nba' = nba ∧ SymRel st₀ S' st') ∧
(∀ (ss : List Stmt2) (S S' : SymState) (st : SvState) (nba : NbaQueue2) (st' : SvState)
(nba' : NbaQueue2),
Stmt2.symExecList d S ss = some S' → SymOk d st₀ S' → SymRel st₀ S st →
execStmts2 fuel st nba ss = .ok (st', nba') → nba' = nba ∧ SymRel st₀ S' st') ∧
(∀ (items : List CaseItem2) (S Sd S' : SymState) (subj : Expr2) (sv : LVec) (mode : CaseMode)
(dflt : Option Stmt2) (st : SvState) (nba : NbaQueue2) (st' : SvState)
(nba' : NbaQueue2),
Stmt2.symExecOpt d S dflt = some Sd →
CaseItem2.symExecList d S subj mode Sd items = some S' →
(∀ v, evalPure2 st₀ subj = .ok v → v = sv) →
SymOk d st₀ S' → SymRel st₀ S st →
execCase2 fuel st nba sv items dflt mode = .ok (st', nba') →
nba' = nba ∧ SymRel st₀ S' st')
| 0 => by
refine ⟨fun _ _ _ _ _ _ _ _ _ _ h => by simp [execStmt2] at h,
fun _ _ _ _ _ _ _ _ _ _ h => by simp [execStmts2] at h, ?_⟩
intro items _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ h
cases items with
| nil => simp [execCase2] at h
| cons it rest => cases it; simp [execCase2] at h
| f + 1 => by
obtain ⟨ihS, ihL, ihC⟩ := symExec_sound (d := d) (st₀ := st₀) f
refine ⟨?_, ?_, ?_⟩
· intro s S S' st nba st' nba' hsym hok hR hrun
cases s with
| blockingAssign t e => exact execStmt2_blocking_sound hsym hok hR hrun
| nbaAssign t e => simp [Stmt2.symExec] at hsym
| unsupported k x => simp [Stmt2.symExec] at hsym
| block body =>
simp only [Stmt2.symExec] at hsym
simp only [execStmt2] at hrun
exact ihL _ S S' st nba st' nba' hsym hok hR hrun
| ifStmt c t e =>
simp only [Stmt2.symExec] at hsym
split at hsym
· simp only [Option.bind_eq_some_iff] at hsym
obtain ⟨c', hc', St, ht, Sf, hf, hj⟩ := hsym
have hokT : SymOk d st₀ St := hok.mono (SymState.join_names hj).1
have hokF : SymOk d st₀ Sf := hok.mono (SymState.join_names hj).2
have hokS : SymOk d st₀ S := hokT.mono (Stmt2.symExec_names t ht)
simp only [execStmt2, Res.bind_eq_ok] at hrun
obtain ⟨cv, hcv, hrun⟩ := hrun
have hG : ∀ g, evalPure2 st₀ c'.ifGuard = .ok g →
g = LVec.ofLogic (Logic.ofB cv.condTrue) := by
intro g hg
obtain ⟨cv', hcv', rfl⟩ := evalPure2_ifGuard hg
have := Expr2.symSubst_sound hR hokS hc' hcv'
rw [evalPure2_of_evalExpr2_ok hcv, Res.ok.injEq] at this
rw [this]
by_cases hct : cv.condTrue = true
· rw [if_pos hct] at hrun
obtain ⟨hn, hRt⟩ := ihS t S St st nba st' nba' ht hokT hR hrun
refine ⟨hn, SymState.join_sound_left hj (fun g hg => ?_) hRt⟩
rw [hG g hg, hct, Logic.ofB_true]
· rw [if_neg hct] at hrun
have hG0 : ∀ g, evalPure2 st₀ c'.ifGuard = .ok g → g = LVec.ofLogic .l0 := by
intro g hg
rw [hG g hg, Bool.eq_false_iff.mpr hct, Logic.ofB_false]
cases e with
| none =>
simp only [Stmt2.symExecOpt, Option.some.injEq] at hf
subst hf
simp only [Res.ok.injEq, Prod.mk.injEq] at hrun
obtain ⟨rfl, rfl⟩ := hrun
exact ⟨rfl, SymState.join_sound_right hj hG0 hR⟩
| some s =>
simp only [Stmt2.symExecOpt] at hf
obtain ⟨hn, hRf⟩ := ihS s S Sf st nba st' nba' hf hokF hR hrun
exact ⟨hn, SymState.join_sound_right hj hG0 hRf⟩
· cases hsym
| caseStmt subj items dflt mode chk =>
simp only [Stmt2.symExec] at hsym
split at hsym
· simp only [Option.bind_eq_some_iff] at hsym
obtain ⟨subj', hs', Sd, hd, hl⟩ := hsym
have hokS : SymOk d st₀ S :=
(hok.mono (CaseItem2.symExecList_names _ hl)).mono (Stmt2.symExecOpt_names dflt hd)
simp only [execStmt2, Res.bind_eq_ok] at hrun
obtain ⟨sv, hsv, hrun⟩ := hrun
refine ihC items.toList S Sd S' subj' sv mode dflt st nba st' nba' hd hl ?_ hok hR
hrun
intro v hv
have := Expr2.symSubst_sound hR hokS hs' hv
rw [evalPure2_of_evalExpr2_ok hsv, Res.ok.injEq] at this
exact this.symm
· cases hsym
· intro ss S S' st nba st' nba' hsym hok hR hrun
cases ss with
| nil =>
simp only [Stmt2.symExecList, Option.some.injEq] at hsym
subst hsym
simp only [execStmts2, Res.ok.injEq, Prod.mk.injEq] at hrun
obtain ⟨rfl, rfl⟩ := hrun
exact ⟨rfl, hR⟩
| cons s rest =>
simp only [Stmt2.symExecList, Option.bind_eq_some_iff] at hsym
obtain ⟨S1, h1, h2⟩ := hsym
simp only [execStmts2, Res.bind_eq_ok] at hrun
obtain ⟨⟨st1, nba1⟩, hr1, hr2⟩ := hrun
obtain ⟨hn1, hR1⟩ := ihS s S S1 st nba st1 nba1 h1
(hok.mono (Stmt2.symExecList_names rest h2)) hR hr1
subst hn1
exact ihL rest S1 S' st1 _ st' nba' h2 hok hR1 hr2
· intro items S Sd S' subj sv mode dflt st nba st' nba' hd hl hsubj hok hR hrun
cases items with
| nil =>
simp only [CaseItem2.symExecList, Option.some.injEq] at hl
subst hl
cases dflt with
| none =>
simp only [Stmt2.symExecOpt, Option.some.injEq] at hd
subst hd
simp only [execCase2, Res.ok.injEq, Prod.mk.injEq] at hrun
obtain ⟨rfl, rfl⟩ := hrun
exact ⟨rfl, hR⟩
| some s =>
simp only [Stmt2.symExecOpt] at hd
simp only [execCase2] at hrun
exact ihS s S Sd st nba st' nba' hd hok hR hrun
| cons it rest =>
obtain ⟨pats, body⟩ := it
simp only [CaseItem2.symExecList, Option.bind_eq_some_iff] at hl
obtain ⟨⟨pats', Sb⟩, harm, Sr, hr, hj⟩ := hl
have hokB : SymOk d st₀ Sb := hok.mono (SymState.join_names hj).1
have hokR : SymOk d st₀ Sr := hok.mono (SymState.join_names hj).2
have hokS : SymOk d st₀ S := hokB.mono (CaseItem2.symArm_names _ harm)
have harm' := harm
simp only [CaseItem2.symArm] at harm'
split at harm'
· simp only [Option.bind_eq_some_iff, Option.map_eq_some_iff, Prod.mk.injEq] at harm'
obtain ⟨ps, hps, Sb', hb, rfl, rfl⟩ := harm'
simp only [execCase2, Res.bind_eq_ok] at hrun
obtain ⟨pvs, hpvs, hrun⟩ := hrun
have hG : ∀ g, evalPure2 st₀ (Expr2.anyGuard mode subj ps) = .ok g →
g = LVec.ofLogic (Logic.ofB (pvs.any fun pv => caseHit mode sv pv)) := by
intro g hg
obtain ⟨pvs', hpvs', rfl⟩ := evalPure2_anyGuard hsubj hg
have := Expr2.listSymSubst_sound hR hokS hps hpvs'
rw [evalPures2_of_evalExprs2_ok hpvs, Res.ok.injEq] at this
rw [this]
by_cases hhit : (pvs.any fun pv => caseHit mode sv pv) = true
· rw [if_pos hhit] at hrun
obtain ⟨hn, hRb⟩ := ihS body S Sb' st nba st' nba' hb hokB hR hrun
refine ⟨hn, SymState.join_sound_left hj (fun g hg => ?_) hRb⟩
rw [hG g hg, hhit, Logic.ofB_true]
· rw [if_neg hhit] at hrun
obtain ⟨hn, hRr⟩ := ihC rest S Sd Sr subj sv mode dflt st nba st' nba' hd hr
hsubj hokR hR hrun
refine ⟨hn, SymState.join_sound_right hj (fun g hg => ?_) hRr⟩
rw [hG g hg, Bool.eq_false_iff.mpr hhit, Logic.ofB_false]
· cases harm'
/-! ## Symbolic execution is total above the static fuel bound -/
/-- **Symbolic execution is total**: from a state the input symbolic state
describes (pre-state binding every declared name), at every fuel from the
static bound on, the statement (list, arm list) runs to completion. -/
theorem symExec_total {d : Design2} {st₀ : SvState}
(hb : ∀ x ∈ d.symNames, ∃ v, SvState.lookup st₀ x = some v) : ∀ fuel : Nat,
(∀ (s : Stmt2) (S S' : SymState) (st : SvState) (nba : NbaQueue2),
s.symExec d S = some S' → SymOk d st₀ S' → SymRel st₀ S st → s.fuelB ≤ fuel →
∃ r, execStmt2 fuel st nba s = .ok r) ∧
(∀ (ss : List Stmt2) (S S' : SymState) (st : SvState) (nba : NbaQueue2),
Stmt2.symExecList d S ss = some S' → SymOk d st₀ S' → SymRel st₀ S st →
Stmt2.listFuelB ss ≤ fuel → ∃ r, execStmts2 fuel st nba ss = .ok r) ∧
(∀ (items : List CaseItem2) (S Sd S' : SymState) (subj : Expr2) (sv : LVec) (mode : CaseMode)
(dflt : Option Stmt2) (st : SvState) (nba : NbaQueue2),
Stmt2.symExecOpt d S dflt = some Sd →
CaseItem2.symExecList d S subj mode Sd items = some S' →
SymOk d st₀ S' → SymRel st₀ S st →
CaseItem2.listFuelB (Stmt2.optFuelB dflt) items ≤ fuel →
∃ r, execCase2 fuel st nba sv items dflt mode = .ok r)
| 0 => by
refine ⟨fun s S S' _ _ hsym _ _ hf => ?_, fun ss _ _ _ _ _ _ _ hf => ?_,
fun items _ _ _ _ _ _ _ _ _ _ _ _ _ hf => ?_⟩
· cases s with
| nbaAssign => simp [Stmt2.symExec] at hsym
| unsupported => simp [Stmt2.symExec] at hsym
| _ => simp [Stmt2.fuelB] at hf
· cases ss <;> simp [Stmt2.listFuelB] at hf
· cases items <;> simp [CaseItem2.listFuelB] at hf
| f + 1 => by
obtain ⟨ihS, ihL, ihC⟩ := symExec_total hb f
refine ⟨?_, ?_, ?_⟩
· intro s S S' st nba hsym hok hR hf
have hbst := hR.bound hb
cases s with
| nbaAssign => simp [Stmt2.symExec] at hsym
| unsupported => simp [Stmt2.symExec] at hsym
| blockingAssign t e =>
simp only [Stmt2.fuelB] at hf
simp only [Stmt2.symExec] at hsym
split at hsym
· next hwf =>
simp only [Option.bind_eq_some_iff] at hsym
obtain ⟨e', -, W, hW, hw⟩ := hsym
obtain ⟨hnc, hwin, dw, hdw, hin⟩ := Lhs2.symWin_spec hW
obtain ⟨old, hold, hwold⟩ := hok.lookup hR (SymState.write_names hw).1 hdw
obtain ⟨n, lo, w⟩ := W
simp only at hold hin hdw hwold hwin
obtain ⟨hu, hid⟩ := Expr2.wfB_spec hwf
obtain ⟨v, hv⟩ := evalPure2_ok_of_wf hu fun x hx => hbst x (hid x hx)
have hev : evalExpr2 f st e = .ok v := (evalExpr2_eq_evalPure2 (by omega)).trans hv
have hres : resolveLhs2 f st t = .ok (n, some (lo, w)) :=
resolveLhs2_of_window? (by omega) hwin hold (by omega) (by rw [hdw, hwold])
simp only [execStmt2, hev, Res.ok_bind, hres, hold]
exact ⟨_, rfl⟩
· cases hsym
| block body =>
simp only [Stmt2.fuelB] at hf
simp only [Stmt2.symExec] at hsym
simp only [execStmt2]
exact ihL _ S S' st nba hsym hok hR (by omega)
| ifStmt c t e =>
simp only [Stmt2.fuelB] at hf
simp only [Stmt2.symExec] at hsym
split at hsym
· next hwf =>
simp only [Option.bind_eq_some_iff] at hsym
obtain ⟨c', -, St, ht, Sf, hfe, hj⟩ := hsym
obtain ⟨hu, hid⟩ := Expr2.wfB_spec hwf
obtain ⟨cv, hcv⟩ := evalPure2_ok_of_wf hu fun x hx => hbst x (hid x hx)
have hev : evalExpr2 f st c = .ok cv := (evalExpr2_eq_evalPure2 (by omega)).trans hcv
simp only [execStmt2, hev, Res.ok_bind]
split
· exact ihS t S St st nba ht (hok.mono (SymState.join_names hj).1) hR (by omega)
· cases e with
| none => exact ⟨_, rfl⟩
| some s =>
simp only [Stmt2.symExecOpt] at hfe
simp only [Stmt2.optFuelB] at hf
exact ihS s S Sf st nba hfe (hok.mono (SymState.join_names hj).2) hR (by omega)
· cases hsym
| caseStmt subj items dflt mode chk =>
simp only [Stmt2.fuelB] at hf
simp only [Stmt2.symExec] at hsym
split at hsym
· next hwf =>
simp only [Option.bind_eq_some_iff] at hsym
obtain ⟨subj', -, Sd, hd, hl⟩ := hsym
obtain ⟨hu, hid⟩ := Expr2.wfB_spec hwf
obtain ⟨sv, hsv⟩ := evalPure2_ok_of_wf hu fun x hx => hbst x (hid x hx)
have hev : evalExpr2 f st subj = .ok sv :=
(evalExpr2_eq_evalPure2 (by omega)).trans hsv
simp only [execStmt2, hev, Res.ok_bind]
exact ihC items.toList S Sd S' subj' sv mode dflt st nba hd hl hok hR (by omega)
· cases hsym
· intro ss S S' st nba hsym hok hR hf
cases ss with
| nil => exact ⟨(st, nba), by simp [execStmts2]⟩
| cons s rest =>
simp only [Stmt2.listFuelB] at hf
simp only [Stmt2.symExecList, Option.bind_eq_some_iff] at hsym
obtain ⟨S1, h1, h2⟩ := hsym
have hok1 : SymOk d st₀ S1 := hok.mono (Stmt2.symExecList_names rest h2)
obtain ⟨⟨st1, nba1⟩, hr1⟩ := ihS s S S1 st nba h1 hok1 hR (by omega)
obtain ⟨-, hR1⟩ := (symExec_sound f).1 s S S1 st nba st1 nba1 h1 hok1 hR hr1
obtain ⟨r, hr2⟩ := ihL rest S1 S' st1 nba1 h2 hok hR1 (by omega)
exact ⟨r, by simp only [execStmts2, hr1, Res.ok_bind]; exact hr2⟩
· intro items S Sd S' subj sv mode dflt st nba hd hl hok hR hf
have hbst := hR.bound hb
cases items with
| nil =>
simp only [CaseItem2.listFuelB] at hf
simp only [CaseItem2.symExecList, Option.some.injEq] at hl
subst hl
cases dflt with
| none => exact ⟨(st, nba), by simp [execCase2]⟩
| some s =>
simp only [Stmt2.symExecOpt] at hd
simp only [Stmt2.optFuelB] at hf
simp only [execCase2]
exact ihS s S Sd st nba hd hok hR (by omega)
| cons it rest =>
obtain ⟨pats, body⟩ := it
simp only [CaseItem2.listFuelB, CaseItem2.fuelB] at hf
simp only [CaseItem2.symExecList, Option.bind_eq_some_iff] at hl
obtain ⟨⟨pats', Sb⟩, harm, Sr, hr, hj⟩ := hl
simp only [CaseItem2.symArm] at harm
split at harm
· next hwf =>
simp only [Option.bind_eq_some_iff, Option.map_eq_some_iff, Prod.mk.injEq] at harm
obtain ⟨ps, -, Sb', hbd, rfl, rfl⟩ := harm
obtain ⟨hu, hid⟩ := Expr2.listWfB_spec hwf
obtain ⟨pvs, hpvs⟩ := evalPures2_ok_of_wf hu fun x hx => hbst x (hid x hx)
have hev : evalExprs2 f st pats.toList = .ok pvs :=
(evalExprs2_eq_evalPures2 (by omega)).trans hpvs
simp only [execCase2, hev, Res.ok_bind]
split
· exact ihS body S Sb' st nba hbd (hok.mono (SymState.join_names hj).1) hR (by omega)
· exact ihC rest S Sd Sr subj sv mode dflt st nba hd hr
(hok.mono (SymState.join_names hj).2) hR (by omega)
· cases harm
/-! ## The lowered process is the body -/
namespace SymState
/-- The lowered target's leaves are constant slices, so its windows are the
entries' windows, in order, in every design. -/
theorem target_listWindows (d : Design2) : ∀ S : SymState,
Lhs2.listWindows? d (S.map fun e => e.1.lhs) = some (S.map Prod.fst)
| [] => by simp
| e :: S => by
rw [List.map_cons, Lhs2.listWindows?_cons, target_listWindows d S]
simp [Win.lhs]
/-- The lowered target's window list. -/
theorem target_windows (d : Design2) (S : SymState) :
S.target.windows? d = some (S.map Prod.fst) := by
simp [target, target_listWindows]
/-- The lowered target's windows are the entries' windows. -/
theorem target_wins (d : Design2) (S : SymState) : S.target.wins d = S.map Prod.fst :=
Lhs2.wins_of_windows? (target_windows d S)
/-- The lowered right-hand side reads what its entries read. -/
theorem rhs_reads (S : SymState) : S.rhs.reads = Expr2.listReads (S.map Prod.snd) := by
simp [rhs, Expr2.reads]
/-- `selfReadFree` as a proposition: no read of the lowered right-hand side
overlaps a window of the lowered target. -/
theorem selfReadFree_iff {S : SymState} :
S.selfReadFree = true ↔ ∀ r ∈ S.rhs.reads, ∀ W ∈ S.map Prod.fst, r.overlaps W = false := by
rw [rhs_reads]
unfold selfReadFree
simp only [List.all_eq_true, Bool.not_eq_true']
/-- The concatenated right-hand side places each entry's value at its
window's offset (`Win.placed`), when every entry value has its window's
width. -/
theorem rhs_placed {st : SvState} : ∀ {S : SymState} {vs : List LVec},
evalPures2 st (S.map Prod.snd) = .ok vs →
(∀ W E, (W, E) ∈ S → ∀ v, evalPure2 st E = .ok v → v.width = W.2.2) →
(LVec.concatMany vs.toArray).width = Win.sumWidth (S.map Prod.fst) ∧
∀ W E, (W, E) ∈ S → ∀ v, evalPure2 st E = .ok v →
∃ off, (W, off) ∈ Win.placed (S.map Prod.fst) ∧
(LVec.concatMany vs.toArray).extract off W.2.2 = v
| [], vs, h, _ => by
simp only [List.map_nil, evalPures2, Res.ok.injEq] at h
subst h
exact ⟨rfl, fun _ _ hm => absurd hm List.not_mem_nil⟩
| (W, E) :: S, vs, h, hw => by
simp only [List.map_cons, evalPures2, Pure2.res_bind_eq_ok, Pure2.res_pure_eq,
Res.ok.injEq] at h
obtain ⟨v, hv, vs', hvs', rfl⟩ := h
obtain ⟨ihw, ihp⟩ := rhs_placed hvs' fun W' E' hm => hw W' E' (List.mem_cons_of_mem _ hm)
have hvw := hw W E (List.mem_cons_self ..) v hv
rw [LVec.concatMany_cons]
refine ⟨by simp only [LVec.width_concat, ihw, hvw, List.map_cons, Win.sumWidth_cons], ?_⟩
intro W' E' hm v' hv'
rcases List.mem_cons.mp hm with h | h
· simp only [Prod.mk.injEq] at h
obtain ⟨rfl, rfl⟩ := h
rw [hv, Res.ok.injEq] at hv'
subst hv'
refine ⟨Win.sumWidth (S.map Prod.fst), by simp, ?_⟩
rw [← ihw, ← hvw, LVec.extract_concat_hi]
· obtain ⟨off, hoff, hext⟩ := ihp W' E' h v' hv'
refine ⟨off, by simp [hoff], ?_⟩
rw [LVec.extract_concat_lo _ _ (by rw [ihw]; exact Win.le_sumWidth_of_mem_placed hoff),
hext]
end SymState
/-- A multi-window write keeps every width. -/
theorem SvState.writeWins_width {val : LVec} : ∀ {st : SvState} {pl : List (Win × Nat)}
(m : String), (SvState.lookup (SvState.writeWins st val pl) m).map LVec.width =
(SvState.lookup st m).map LVec.width
| _, [], _ => rfl
| st, ((n, lo, w), off) :: rest, m => by
cases hl : SvState.lookup st n with
| none => rw [SvState.writeWins_cons_none hl]; exact SvState.writeWins_width m
| some old =>
rw [SvState.writeWins_cons_some hl, SvState.writeWins_width m, SvState.lookup_set]
by_cases h : n = m
· subst h; simp [hl]
· simp [h]
/-- **Two states one symbolic state describes are equal** (the entries
evaluating in the pre-state, its keys duplicate-free): every bit is either
in an entry window, holding the entry's value, or the pre-state's. -/
theorem SymRel.unique {st₀ s₁ s₂ : SvState} {S : SymState} (h₁ : SymRel st₀ S s₁)
(h₂ : SymRel st₀ S s₂) (hev : ∀ W E, (W, E) ∈ S → ∃ v, evalPure2 st₀ E = .ok v)
(hnd : (st₀.map Prod.fst).Nodup) : s₁ = s₂ := by
refine SvState.ext (h₁.keys.trans h₂.keys.symm) (by rw [h₁.keys]; exact hnd) fun n =>
ReadWin.lookup_eq_of_agree_bits fun b => ?_
by_cases hc : ∃ W ∈ S.map Prod.fst, (ReadWin.win n b 1).overlaps W = true
· obtain ⟨W, hW, hov⟩ := hc
obtain ⟨⟨W', E⟩, hm, rfl⟩ := List.mem_map.mp hW
obtain ⟨v, hv⟩ := hev _ _ hm
obtain ⟨u₁, hu₁, he₁⟩ := h₁.entry _ _ hm v hv
obtain ⟨u₂, hu₂, he₂⟩ := h₂.entry _ _ hm v hv
have hw : u₁.width = u₂.width := by
have := (h₁.width W'.1).trans (h₂.width W'.1).symm
rw [hu₁, hu₂] at this
simpa using this
obtain ⟨m, lo, w⟩ := W'
simp only at hu₁ hu₂ he₁ he₂
rw [ReadWin.overlaps_win_one] at hov
obtain ⟨rfl, hb₁, hb₂⟩ := hov
exact Comb2Term.agree_bit_of_extract hu₁ hu₂ hw (he₁.trans he₂.symm) hb₁ hb₂
· have hc' : ∀ W ∈ S.map Prod.fst, (ReadWin.win n b 1).overlaps W = false :=
fun W hW => Bool.eq_false_iff.mpr fun h => hc ⟨W, hW, h⟩
exact ReadWin.agree_trans (h₁.frame n b hc') (ReadWin.agree_symm (h₂.frame n b hc'))
/-- An evaluated list evaluates elementwise. -/
theorem evalPures2_ok_mem {st : SvState} : ∀ {es : List Expr2} {vs : List LVec},
evalPures2 st es = .ok vs → ∀ e ∈ es, ∃ v, evalPure2 st e = .ok v
| [], _, _, e, he => absurd he List.not_mem_nil
| e :: es, vs, h, e', he' => by
simp only [evalPures2, Pure2.res_bind_eq_ok, Pure2.res_pure_eq] at h
obtain ⟨v, hv, vs', hvs', -⟩ := h
rcases List.mem_cons.mp he' with rfl | he'
· exact ⟨v, hv⟩
· exact evalPures2_ok_mem hvs' e' he'
/-- `declWidth` reads only the declarations. -/
theorem Design2.declWidth_congr {d d' : Design2} (h : d'.decls = d.decls) :
d'.declWidth = d.declWidth := by
funext n; simp [Design2.declWidth, h]
/-- `symNames` reads only the declarations. -/
theorem Design2.symNames_congr {d d' : Design2} (h : d'.decls = d.decls) :
d'.symNames = d.symNames := by
simp [Design2.symNames, h]
/-- The right-hand side of the lowered assign, evaluated. -/
theorem SymState.evalPure2_rhs {st : SvState} {S : SymState} {v : LVec}
(h : evalPure2 st S.rhs = .ok v) :
∃ vs, evalPures2 st (S.map Prod.snd) = .ok vs ∧ v = LVec.concatMany vs.toArray := by
simp only [SymState.rhs, evalPure2, Pure2.res_bind_eq_ok,
Pure2.res_pure_eq, Res.ok.injEq] at h
obtain ⟨vs, hvs, rfl⟩ := h
exact ⟨vs, hvs, rfl⟩
/-- **The lowered assign's write is described by the symbolic state**
(given a run that the symbolic state describes, which supplies the entries'
widths). -/
theorem SymRel.of_combWrite {d' : Design2} (hd' : CombDesign d') {st₀ : SvState}
(hs : StateShape d' st₀) {i : Nat} {S : SymState}
(hp : d'.processes[i]? = some S.toAssign) {st : SvState} (hR : SymRel st₀ S st) {v : LVec}
(hv : evalPure2 st₀ S.rhs = .ok v) : SymRel st₀ S (combWrite d' st₀ S.target v) := by
obtain ⟨vs, hvs, rfl⟩ := SymState.evalPure2_rhs hv
have hwidth : ∀ W E, (W, E) ∈ S → ∀ x, evalPure2 st₀ E = .ok x → x.width = W.2.2 := by
intro W E hm x hx
obtain ⟨u, -, hext⟩ := hR.entry W E hm x hx
rw [← hext, LVec.width_extract]
obtain ⟨hvw, hplace⟩ := SymState.rhs_placed hvs hwidth
have hres : (LVec.concatMany vs.toArray).resizeU (Win.sumWidth (S.target.wins d')) =
LVec.concatMany vs.toArray := by
rw [SymState.target_wins]; exact LVec.resizeU_of_width hvw
have hsC := StateShape.combWrite hs S.target (LVec.concatMany vs.toArray)
refine ⟨hsC.keys.trans hs.keys.symm, fun m => SvState.writeWins_width m, hR.disj, ?_, ?_⟩
· intro W E hm x hx
obtain ⟨off, hoff, hext⟩ := hplace W E hm x hx
obtain ⟨n, lo, w⟩ := W
obtain ⟨u, hu, hue⟩ := hd'.combWrite_readBack hs hp (LVec.concatMany vs.toArray)
(n := n) (lo := lo) (w := w) (off := off) (by rw [SymState.target_wins]; exact hoff)
rw [hres] at hue
exact ⟨u, hu, hue.trans hext⟩
· intro n b hb
exact ReadWin.agree_symm (ReadWin.agree_combWrite fun wn hwn => by
rw [SymState.target_wins] at hwn
exact hb wn hwn)
/-- The pre-state width facts of an `always_comb` body whose lowered assign
is process `i` of a comb design, on a shaped state: every signal the body
writes is driven there, hence bound at its declared width. -/
theorem SymOk.of_shape {d d' : Design2} (hdecls : d'.decls = d.decls) {i : Nat} {S : SymState}
(hp : d'.processes[i]? = some S.toAssign) {st : SvState} (hs : StateShape d' st) :
SymOk d st S := by
intro n hn dw hdw
obtain ⟨⟨⟨m, lo, w⟩, E⟩, he, rfl⟩ := SymState.mem_names.mp hn
have hW : d'.Writes i (m, lo, w) :=
⟨S.target, S.rhs, hp, by rw [SymState.target_wins]; exact List.mem_map_of_mem he⟩
obtain ⟨v, hv, hvw⟩ := hs.driven m ⟨i, lo, w, hW⟩
rw [Design2.declWidth_congr hdecls, hdw, Option.some.injEq] at hvw
simp [hv, hvw]
/-- **An `always_comb` run is its lowered assign's write** (inversion, any
fuel): on a state with the comb shape of a design containing the lowered
assign, a successful run of the body is `combWrite` of the lowered
right-hand side's value. -/
theorem runCombProcess2_alwaysComb_ok {d d' : Design2} (hd' : CombDesign d')
(hdecls : d'.decls = d.decls) {i : Nat} {body : Stmt2} {S : SymState}
(hsym : body.symExec d [] = some S) (hp : d'.processes[i]? = some S.toAssign) {st : SvState}
(hs : StateShape d' st) {f : Nat} {r : SvState}
(h : runCombProcess2 f st (.alwaysComb body) = .ok r) :
∃ v, evalPure2 st S.rhs = .ok v ∧ r = combWrite d' st S.target v := by
simp only [runCombProcess2, Res.bind_eq_ok] at h
obtain ⟨⟨st', nba⟩, hrun, hr⟩ := h
obtain ⟨rfl, hR⟩ := (symExec_sound f).1 body [] S st [] st' nba hsym
(SymOk.of_shape hdecls hp hs) (SymRel.init st) hrun
simp only [Res.ok.injEq] at hr
subst hr
obtain ⟨v, hv⟩ := hd'.evalPure2_ok hs hp
refine ⟨v, hv, SymRel.unique hR (SymRel.of_combWrite hd' hs hp hR hv) ?_ ?_⟩
· intro W E hm
obtain ⟨vs, hvs, -⟩ := SymState.evalPure2_rhs hv
exact evalPures2_ok_mem hvs E (List.mem_map_of_mem (f := Prod.snd) hm)
· rw [hs.keys]; exact hd'.nodup
/-- **An `always_comb` run completes and is its lowered assign's write**
(forward): at every fuel from the body's static bound `Stmt2.fuelB` on. -/
theorem runCombProcess2_alwaysComb_fwd {d d' : Design2} (hd' : CombDesign d')
(hdecls : d'.decls = d.decls) {i : Nat} {body : Stmt2} {S : SymState}
(hsym : body.symExec d [] = some S) (hp : d'.processes[i]? = some S.toAssign) {st : SvState}
(hs : StateShape d' st) {f : Nat} (hf : body.fuelB ≤ f) :
∃ v, evalPure2 st S.rhs = .ok v ∧
runCombProcess2 f st (.alwaysComb body) = .ok (combWrite d' st S.target v) := by
have hb : ∀ x ∈ d.symNames, ∃ v, SvState.lookup st x = some v := by
intro x hx
rw [← Design2.symNames_congr hdecls] at hx
exact hs.lookup_of_mem hx
have hok := SymOk.of_shape hdecls hp hs
obtain ⟨⟨st', nba⟩, hrun⟩ := (symExec_total hb f).1 body [] S st [] hsym hok (SymRel.init st) hf
obtain ⟨rfl, -⟩ := (symExec_sound f).1 body [] S st [] st' nba hsym hok (SymRel.init st) hrun
have h : runCombProcess2 f st (.alwaysComb body) = .ok st' := by
simp [runCombProcess2, hrun]
obtain ⟨v, hv, rfl⟩ := runCombProcess2_alwaysComb_ok hd' hdecls hsym hp hs h
exact ⟨v, hv, h⟩
/-! ## The lowering, structurally -/
theorem Design2.lowerProcs_spec {d : Design2} : ∀ {ps qs : List Process2},
d.lowerProcs ps = some qs → qs.length = ps.length ∧
∀ i : Nat, qs[i]? = ps[i]?.bind (Process2.lowerComb d)
| [], qs, h => by
simp only [Design2.lowerProcs, Option.some.injEq] at h
subst h
exact ⟨rfl, fun _ => rfl⟩
| p :: ps, qs, h => by
simp only [Design2.lowerProcs, Option.bind_eq_some_iff, Option.map_eq_some_iff] at h
obtain ⟨q, hq, qs', hqs', rfl⟩ := h
obtain ⟨hlen, hget⟩ := Design2.lowerProcs_spec hqs'
refine ⟨by simp [hlen], fun i => ?_⟩
cases i with
| zero => simp [hq]
| succ i => simp [hget i]
/-- **What the lowering does**: same name and declarations, same number of
processes, and process `i` of the lowered design is process `i` of `d`
lowered. -/
theorem Design2.lowerComb_spec {d d' : Design2} (h : d.lowerComb = some d') :
d'.name = d.name ∧ d'.decls = d.decls ∧ d'.processes.size = d.processes.size ∧
∀ i : Nat, d'.processes[i]? = d.processes[i]?.bind (Process2.lowerComb d) := by
simp only [Design2.lowerComb, Option.map_eq_some_iff] at h
obtain ⟨qs, hqs, rfl⟩ := h
obtain ⟨hlen, hget⟩ := Design2.lowerProcs_spec hqs
refine ⟨rfl, rfl, by simp [hlen], fun i => ?_⟩
simp only [List.getElem?_toArray, hget, Array.getElem?_toList]
/-- Every process of `d` has its lowered counterpart at the same index. -/
theorem Design2.lowerComb_proc {d d' : Design2} (h : d.lowerComb = some d') {i : Nat}
{p : Process2} (hp : d.processes[i]? = some p) :
∃ q, p.lowerComb d = some q ∧ d'.processes[i]? = some q := by
obtain ⟨-, -, hsize, hget⟩ := Design2.lowerComb_spec h
have hi : i < d'.processes.size := by
rw [hsize]; exact (Array.getElem?_eq_some_iff.mp hp).1
obtain ⟨q, hq⟩ : ∃ q, d'.processes[i]? = some q := ⟨_, Array.getElem?_eq_getElem hi⟩
rw [hget i, hp, Option.bind_some] at hq
exact ⟨q, hq, by rw [hget i, hp, Option.bind_some, hq]⟩
/-- Every process of the lowered design comes from a process of `d`. -/
theorem Design2.lowerComb_proc' {d d' : Design2} (h : d.lowerComb = some d') {i : Nat}
{q : Process2} (hq : d'.processes[i]? = some q) :
∃ p, d.processes[i]? = some p ∧ p.lowerComb d = some q := by
obtain ⟨-, -, -, hget⟩ := Design2.lowerComb_spec h
rw [hget i] at hq
cases hp : d.processes[i]? with
| none => rw [hp] at hq; cases hq
| some p => rw [hp, Option.bind_some] at hq; exact ⟨p, rfl, hq⟩
/-- An `always_comb` the lowering accepts: its body executes symbolically to
a self-read-free state, whose assign is the lowered process. -/
theorem Process2.lowerComb_alwaysComb {d : Design2} {body : Stmt2} {q : Process2}
(h : (Process2.alwaysComb body).lowerComb d = some q) :
∃ S, body.symExec d [] = some S ∧ S.selfReadFree = true ∧ q = S.toAssign := by
simp only [Process2.lowerComb, Option.bind_eq_some_iff] at h
obtain ⟨S, hS, h⟩ := h
split at h
· exact ⟨S, hS, by assumption, (Option.some.inj h).symm⟩
· cases h
/-- The two process shapes the lowering accepts. -/
theorem Process2.lowerComb_eq_some {d : Design2} {p q : Process2} (h : p.lowerComb d = some q) :
(∃ t e, p = .assign t e ∧ q = .assign t e) ∨
(∃ body S, p = .alwaysComb body ∧ body.symExec d [] = some S ∧ q = S.toAssign) := by
cases p with
| assign t e =>
simp only [Process2.lowerComb, Option.some.injEq] at h
exact Or.inl ⟨t, e, rfl, h.symm⟩
| alwaysComb body =>
obtain ⟨S, hS, -, rfl⟩ := Process2.lowerComb_alwaysComb h
exact Or.inr ⟨body, S, rfl, hS, rfl⟩
| _ => simp [Process2.lowerComb] at h
/-- A design whose lowering succeeds has only comb-phase processes, none of
them an edge or reset process. -/
theorem Design2.lowerComb_phases {d d' : Design2} (h : d.lowerComb = some d') :
d.edgeIndices = [] ∧ (∀ a b, d.resetIndices a b = []) ∧
d.combIndices = List.range d.processes.size := by
have hk : ∀ i, i < d.processes.size → ∃ p, d.processes[i]? = some p ∧
((∃ t e, p = .assign t e) ∨ ∃ body, p = .alwaysComb body) := by
intro i hi
obtain ⟨q, hq, -⟩ := Design2.lowerComb_proc h (Array.getElem?_eq_getElem hi)
rcases Process2.lowerComb_eq_some hq with ⟨t, e, hpe, -⟩ | ⟨body, S, hpe, -, -⟩
· exact ⟨_, Array.getElem?_eq_getElem hi, Or.inl ⟨t, e, hpe⟩⟩
· exact ⟨_, Array.getElem?_eq_getElem hi, Or.inr ⟨body, hpe⟩⟩
refine ⟨?_, ?_, ?_⟩
· unfold Design2.edgeIndices
rw [List.filter_eq_nil_iff]
intro i hi
obtain ⟨p, hp, hk⟩ := hk i (List.mem_range.mp hi)
rcases hk with ⟨t, e, rfl⟩ | ⟨body, rfl⟩ <;> simp [hp, Process2.isEdgePhase]
· intro a b
unfold Design2.resetIndices
rw [List.filter_eq_nil_iff]
intro i hi
obtain ⟨p, hp, hk⟩ := hk i (List.mem_range.mp hi)
rcases hk with ⟨t, e, rfl⟩ | ⟨body, rfl⟩ <;> simp [hp]
· unfold Design2.combIndices
rw [List.filter_eq_self]
intro i hi
obtain ⟨p, hp, hk⟩ := hk i (List.mem_range.mp hi)
rcases hk with ⟨t, e, rfl⟩ | ⟨body, rfl⟩ <;> simp [hp, Process2.isCombPhase]
/-! ## Run-level simulation -/
/-- A cycle of a design with no edge and no reset process is the input
settle followed by the post-edge settle (`cycleStep2_of_comb` without the
comb profile). -/
theorem cycleStep2_of_noEdge {d : Design2} (he : d.edgeIndices = [])
(hr : ∀ a b, d.resetIndices a b = []) (σ : ScheduleOracle) (fuel : Nat)
(inputs st : SvState) (k : Nat) :
cycleStep2 d σ fuel inputs st k =
(combSettle2 d σ fuel (applyInputs2 d inputs st) k >>=
fun p => combSettle2 d σ fuel p.1 (p.2 + 1)) := by
unfold cycleStep2
simp only [hr, he, ScheduleOracle.choose_nil]
cases combSettle2 d σ fuel (applyInputs2 d inputs st) k with
| ok p => obtain ⟨st1, k1⟩ := p; rfl
| timeout => rfl
| unsupported msg => rfl
/-- `applyInputs2` reads only the declarations. -/
theorem applyInputs2_congr {d d' : Design2} (h : d'.decls = d.decls) (inputs st : SvState) :
applyInputs2 d' inputs st = applyInputs2 d inputs st := by
simp [applyInputs2, Design2.inputNames, h]
/-- `initState2` reads only the declarations. -/
theorem initState2_congr {d d' : Design2} (h : d'.decls = d.decls) :
initState2 d' = initState2 d := by
simp [initState2, h]
/-- **`d₂` simulates the comb runs of `d₁` on the states `I`**: same
declarations and comb ready list, no edge or reset process in either, and
every process of `d₁` is matched (same index) by a process of `d₂` that, on
every `I`-state, reaches the same result at some fuel whenever it succeeds;
`I` holds initially, survives `applyInputs2`, and every successful process
run. -/
structure CombSim (d₁ d₂ : Design2) (I : SvState → Prop) : Prop where
decls : d₂.decls = d₁.decls
comb : d₂.combIndices = d₁.combIndices
edge₁ : d₁.edgeIndices = []
edge₂ : d₂.edgeIndices = []
reset₁ : ∀ a b, d₁.resetIndices a b = []
reset₂ : ∀ a b, d₂.resetIndices a b = []
none : ∀ i : Nat, d₁.processes[i]? = none → d₂.processes[i]? = none
proc : ∀ (i : Nat) (p₁ : Process2), d₁.processes[i]? = some p₁ →
∃ p₂, d₂.processes[i]? = some p₂ ∧ ∀ st, I st → ∀ f r, runCombProcess2 f st p₁ = .ok r →
I r ∧ ∃ f', runCombProcess2 f' st p₂ = .ok r
init : I (initState2 d₁)
inputs : ∀ inputs st, I st → I (applyInputs2 d₁ inputs st)
namespace CombSim
variable {d₁ d₂ : Design2} {I : SvState → Prop}
/-- A pass simulates. -/
theorem combPass2 (h : CombSim d₁ d₂ I) : ∀ (is : List Nat) (st : SvState) (f : Nat)
(r : SvState), I st → LeanModels.Sv.combPass2 d₁ f st is = .ok r →
I r ∧ ∃ f', LeanModels.Sv.combPass2 d₂ f' st is = .ok r
| [], st, f, r, hI, hp => by
simp only [LeanModels.Sv.combPass2, Res.ok.injEq] at hp
subst hp
exact ⟨hI, 0, rfl⟩
| i :: is, st, f, r, hI, hp => by
cases h1 : d₁.processes[i]? with
| none =>
rw [combPass2_cons_none h1] at hp
obtain ⟨hIr, f', hf'⟩ := h.combPass2 is st f r hI hp
exact ⟨hIr, f', by rw [combPass2_cons_none (h.none i h1)]; exact hf'⟩
| some p₁ =>
obtain ⟨p₂, h2, hsim⟩ := h.proc i p₁ h1
rw [combPass2_cons_some h1, Res.bind_eq_ok] at hp
obtain ⟨m, hm, hrest⟩ := hp
obtain ⟨hIm, f1, hf1⟩ := hsim st hI f m hm
obtain ⟨hIr, f2, hf2⟩ := h.combPass2 is m f r hIm hrest
refine ⟨hIr, max f1 f2, ?_⟩
rw [combPass2_cons_some h2, runCombProcess2_mono hf1 (by simp) _ (Nat.le_max_left _ _),
Res.ok_bind]
exact combPass2_mono hf2 (by simp) _ (Nat.le_max_right _ _)
/-- A settle simulates. -/
theorem combSettle2 (h : CombSim d₁ d₂ I) (σ : ScheduleOracle) : ∀ (f : Nat) (st : SvState)
(k : Nat) (S : SvState) (k' : Nat), I st →
LeanModels.Sv.combSettle2 d₁ σ f st k = .ok (S, k') →
I S ∧ ∃ f', LeanModels.Sv.combSettle2 d₂ σ f' st k = .ok (S, k')
| 0, _, _, _, _, _, hs => by simp [LeanModels.Sv.combSettle2] at hs
| f + 1, st, k, S, k', hI, hs => by
simp only [LeanModels.Sv.combSettle2, Res.bind_eq_ok] at hs
obtain ⟨st', hpass, hrest⟩ := hs
rw [← h.comb] at hpass
obtain ⟨hI', fp, hfp⟩ := h.combPass2 _ st f st' hI hpass
by_cases heq : (st' == st) = true
· rw [if_pos heq] at hrest
simp only [Res.ok.injEq, Prod.mk.injEq] at hrest
obtain ⟨rfl, rfl⟩ := hrest
refine ⟨hI', fp + 1, ?_⟩
simp only [LeanModels.Sv.combSettle2, hfp, Res.ok_bind, if_pos heq]
· rw [if_neg heq] at hrest
obtain ⟨hIS, fr, hfr⟩ := h.combSettle2 σ f st' (k + 1) S k' hI' hrest
refine ⟨hIS, max fp fr + 1, ?_⟩
simp only [LeanModels.Sv.combSettle2]
rw [combPass2_mono hfp (by simp) _ (Nat.le_max_left _ _), Res.ok_bind, if_neg heq]
exact combSettle2_mono hfr (by simp) _ (Nat.le_max_right _ _)
/-- A cycle simulates. -/
theorem cycleStep2 (h : CombSim d₁ d₂ I) (σ : ScheduleOracle) {f : Nat} {inputs st : SvState}
{k : Nat} {S : SvState} {k' : Nat} (hI : I st)
(hc : LeanModels.Sv.cycleStep2 d₁ σ f inputs st k = .ok (S, k')) :
I S ∧ ∃ f', LeanModels.Sv.cycleStep2 d₂ σ f' inputs st k = .ok (S, k') := by
rw [cycleStep2_of_noEdge h.edge₁ h.reset₁, Res.bind_eq_ok] at hc
obtain ⟨⟨st1, k1⟩, h1, h2⟩ := hc
obtain ⟨hI1, f1, hf1⟩ := h.combSettle2 σ f _ k st1 k1 (h.inputs inputs st hI) h1
obtain ⟨hIS, f2, hf2⟩ := h.combSettle2 σ f st1 (k1 + 1) S k' hI1 h2
refine ⟨hIS, max f1 f2, ?_⟩
rw [cycleStep2_of_noEdge h.edge₂ h.reset₂, applyInputs2_congr h.decls,
combSettle2_mono hf1 (by simp) _ (Nat.le_max_left _ _), Res.ok_bind]
exact combSettle2_mono hf2 (by simp) _ (Nat.le_max_right _ _)
/-- A run from an `I`-state simulates. -/
theorem runFrom2 (h : CombSim d₁ d₂ I) (σ : ScheduleOracle) : ∀ (stim : List SvState) (f : Nat)
(st : SvState) (k : Nat) (tr : List SvState), I st →
LeanModels.Sv.runFrom2 d₁ σ f st k stim = .ok tr →
∃ f', LeanModels.Sv.runFrom2 d₂ σ f' st k stim = .ok tr
| [], f, st, k, tr, _, hr => ⟨0, by simpa [LeanModels.Sv.runFrom2] using hr⟩
| inputs :: rest, f, st, k, tr, hI, hr => by
simp only [LeanModels.Sv.runFrom2, Res.bind_eq_ok] at hr
obtain ⟨⟨st', k'⟩, hc, tr', hrest, htr⟩ := hr
obtain ⟨hI', f1, hf1⟩ := h.cycleStep2 σ hI hc
obtain ⟨f2, hf2⟩ := h.runFrom2 σ rest f st' k' tr' hI' hrest
refine ⟨max f1 f2, ?_⟩
simp only [LeanModels.Sv.runFrom2]
rw [cycleStep2_mono hf1 (by simp) _ (Nat.le_max_left _ _), Res.ok_bind,
runFrom2_mono hf2 (by simp) _ (Nat.le_max_right _ _), Res.ok_bind]
exact htr
/-- **A completed run simulates.** -/
theorem runs2 (h : CombSim d₁ d₂ I) {σ : ScheduleOracle} {stim tr : List SvState}
(hnq : d₂.processes.toList.any Process2.hasQualifiedCase = false)
(hr : Runs2 d₁ σ stim tr) : Runs2 d₂ σ stim tr := by
obtain ⟨f, hf⟩ := hr
obtain ⟨f', hf'⟩ := h.runFrom2 σ stim f _ 0 tr h.init (run2_from_ok hf)
refine ⟨f', ?_⟩
have hf'' : LeanModels.Sv.runFrom2 d₂ σ f' (initState2 d₂) 0 stim = .ok tr := by
rw [initState2_congr h.decls]
exact hf'
simp [run2, hnq, hf'']
end CombSim
/-! ## The lowered design simulates the design, and back -/
section Lowered
variable {d d' : Design2}
/-- The design and its lowering have the same comb ready list. -/
theorem Design2.lowerComb_combIndices (hl : d.lowerComb = some d') (hd' : CombDesign d') :
d'.combIndices = d.combIndices := by
rw [hd'.combIndices_eq, (Design2.lowerComb_phases hl).2.2, (Design2.lowerComb_spec hl).2.2.1]
/-- **The lowered design simulates the design** (on shaped states). -/
theorem CombSim.of_lowerComb (hl : d.lowerComb = some d') (hd' : CombDesign d') :
CombSim d d' (StateShape d') where
decls := (Design2.lowerComb_spec hl).2.1
comb := Design2.lowerComb_combIndices hl hd'
edge₁ := (Design2.lowerComb_phases hl).1
edge₂ := hd'.edgeIndices_eq
reset₁ := (Design2.lowerComb_phases hl).2.1
reset₂ := hd'.resetIndices_eq
none := fun i h => by rw [(Design2.lowerComb_spec hl).2.2.2 i, h]; rfl
proc := by
intro i p hp
obtain ⟨q, hq, hq'⟩ := Design2.lowerComb_proc hl hp
refine ⟨q, hq', fun st hs f r hr => ?_⟩
rcases Process2.lowerComb_eq_some hq with ⟨t, e, rfl, rfl⟩ | ⟨body, S, rfl, hsym, rfl⟩
· obtain ⟨v, -, rfl⟩ := runCombProcess2_assign_write_ok hd' hs hq' hr
exact ⟨hs.combWrite t v, f, by rw [hr]⟩
· obtain ⟨v, hv, rfl⟩ := runCombProcess2_alwaysComb_ok hd' (Design2.lowerComb_spec hl).2.1
hsym hq' hs hr
exact ⟨hs.combWrite _ v, S.rhs.size + 1,
runCombProcess2_assign_write hd' hs hq' (Nat.lt_succ_self _) hv⟩
init := by rw [← initState2_congr (Design2.lowerComb_spec hl).2.1]; exact StateShape.of_initState2 hd'
inputs := fun inputs st hs => by
rw [← applyInputs2_congr (Design2.lowerComb_spec hl).2.1]
exact StateShape.of_applyInputs2 hd' hs inputs
/-- **The design simulates its lowering** (on shaped states). -/
theorem CombSim.of_lowerComb' (hl : d.lowerComb = some d') (hd' : CombDesign d') :
CombSim d' d (StateShape d') where
decls := (Design2.lowerComb_spec hl).2.1.symm
comb := (Design2.lowerComb_combIndices hl hd').symm
edge₁ := hd'.edgeIndices_eq
edge₂ := (Design2.lowerComb_phases hl).1
reset₁ := hd'.resetIndices_eq
reset₂ := (Design2.lowerComb_phases hl).2.1
none := fun i h => by
cases hp : d.processes[i]? with
| none => rfl
| some p =>
obtain ⟨q, -, hq'⟩ := Design2.lowerComb_proc hl hp
rw [h] at hq'; cases hq'
proc := by
intro i q hq'
obtain ⟨p, hp, hq⟩ := Design2.lowerComb_proc' hl hq'
refine ⟨p, hp, fun st hs f r hr => ?_⟩
rcases Process2.lowerComb_eq_some hq with ⟨t, e, rfl, rfl⟩ | ⟨body, S, rfl, hsym, rfl⟩
· obtain ⟨v, -, rfl⟩ := runCombProcess2_assign_write_ok hd' hs hq' hr
exact ⟨hs.combWrite t v, f, by rw [hr]⟩
· obtain ⟨v, hv, rfl⟩ := runCombProcess2_assign_write_ok hd' hs hq' hr
obtain ⟨v', hv', hrun⟩ := runCombProcess2_alwaysComb_fwd hd'
(Design2.lowerComb_spec hl).2.1 hsym hq' hs (Nat.le_refl body.fuelB)
rw [hv, Res.ok.injEq] at hv'
subst hv'
exact ⟨hs.combWrite _ v, body.fuelB, hrun⟩
init := StateShape.of_initState2 hd'
inputs := fun inputs st hs => StateShape.of_applyInputs2 hd' hs inputs
/-- A comb-profile design has no qualified case process. -/
theorem CombDesign.noQualified {d : Design2} (hd : CombDesign d) :
d.processes.toList.any Process2.hasQualifiedCase = false := 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]
/-- **The design and its lowering have the same completed runs** — under
every schedule, for every stimulus, with the same trace. The source design's
qualified-case claims must be discharged separately; the comb profile on the
lowered side supplies the corresponding no-qualified fact automatically. -/
theorem Runs2.lowerComb_iff (hl : d.lowerComb = some d') (hd' : CombDesign d')
(hnq : d.processes.toList.any Process2.hasQualifiedCase = false)
{σ : ScheduleOracle} {stim tr : List SvState} :
Runs2 d σ stim tr ↔ Runs2 d' σ stim tr :=
⟨(CombSim.of_lowerComb hl hd').runs2 hd'.noQualified,
(CombSim.of_lowerComb' hl hd').runs2 hnq⟩
/-- **`⊨₂` transfers from the lowered design.** -/
theorem Models2.of_lowerComb (hl : d.lowerComb = some d') (hd' : CombDesign d')
{P : List SvState → List SvState → Prop} (h : d' ⊨₂ spec2 P)
(hnq : d.processes.toList.any Process2.hasQualifiedCase = false) : d ⊨₂ spec2 P :=
fun σ stim tr hr => h σ stim tr ((Runs2.lowerComb_iff hl hd' hnq).mp hr)
/-- **Termination transfers from the lowered design.** -/
theorem Terminates2.of_lowerComb (hl : d.lowerComb = some d') (hd' : CombDesign d')
(h : Terminates2 d')
(hnq : d.processes.toList.any Process2.hasQualifiedCase = false) : Terminates2 d := fun σ stim => by
obtain ⟨tr, hr⟩ := h σ stim
exact ⟨tr, (Runs2.lowerComb_iff hl hd' hnq).mpr hr⟩
/-- **The termination theorem for `always_comb`**: a design whose lowering
is in the comb profile with an acyclicity certificate terminates — the
certificate ranks every `always_comb` process by its read set (the reads of
its symbolic right-hand side) against every write window. -/
theorem Terminates2.of_lowerComb_acyclic (hl : d.lowerComb = some d') (hd' : CombDesign d')
(ha : Acyclic d')
(hnq : d.processes.toList.any Process2.hasQualifiedCase = false) : Terminates2 d :=
Terminates2.of_lowerComb hl hd' (Terminates2.of_acyclic hd' ha) hnq
/-- **`⊨₂` from the lowered equations** (the basic form, `Models2.of_comb`
for `always_comb` designs). -/
theorem Models2.of_combProc (hl : d.lowerComb = some d') (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)
(hnq : d.processes.toList.any Process2.hasQualifiedCase = false) :
d ⊨₂ spec2 P :=
Models2.of_lowerComb hl hd' (Models2.of_comb hd' hP) hnq
/-- `Models2.of_comb_inputs` for `always_comb` designs: the hypothesis also
receives, per cycle, that every input port bound by the cycle's stimulus
entry holds that value. -/
theorem Models2.of_combProc_inputs (hl : d.lowerComb = some d') (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)
(hnq : d.processes.toList.any Process2.hasQualifiedCase = false) :
d ⊨₂ spec2 P :=
Models2.of_lowerComb hl hd' (Models2.of_comb_inputs hd' hP) hnq
/-- **Pointwise `⊨₂`** for `always_comb` designs. -/
theorem Models2.of_combProc_pointwise (hl : d.lowerComb = some d') (hd' : CombDesign d')
{Q : SvState → Prop} (hQ : ∀ st, CombEqs d' st → StateShape d' st → Q st)
(hnq : d.processes.toList.any Process2.hasQualifiedCase = false) :
d ⊨₂ spec2 fun _ tr => ∀ (c : Nat) (st : SvState), tr[c]? = some st → Q st :=
Models2.of_lowerComb hl hd' (Models2.of_comb_pointwise hd' hQ) hnq
/-! ## The fixpoint characterization -/
/-- **Every process maps `S` to itself** (running each assign, and each
`always_comb` body, on `S` yields `S`, at every fuel from some bound on) —
the settled-state condition for designs with `always_comb`. -/
def ProcFixpoint (d : Design2) (S : SvState) : Prop :=
∀ (i : Nat) (p : Process2), d.processes[i]? = some p →
∃ F, ∀ f, F ≤ f → runCombProcess2 f S p = .ok S
/-- **The generalized equations are the fixpoint condition**: on a shaped
state, every process of `d` maps `S` to itself iff `S` solves the lowered
design's equations (one per written window of every assign and every
`always_comb` body). -/
theorem procFixpoint_iff_combEqs (hl : d.lowerComb = some d') (hd' : CombDesign d')
{S : SvState} (hs : StateShape d' S) : ProcFixpoint d S ↔ CombEqs d' S := by
constructor
· intro hfix i t e n lo w off hp hm
obtain ⟨p, hpd, -⟩ := Design2.lowerComb_proc' hl hp
obtain ⟨F, hF⟩ := hfix i p hpd
obtain ⟨q, hq, hsim⟩ := (CombSim.of_lowerComb hl hd').proc i p hpd
rw [hp, Option.some.injEq] at hq
subst hq
obtain ⟨-, f', hf'⟩ := hsim S hs F S (hF F (Nat.le_refl _))
obtain ⟨v, hv, hS⟩ := runCombProcess2_assign_write_ok hd' hs hp hf'
obtain ⟨u, hu, hue⟩ := hd'.combWrite_readBack hs hp v hm
rw [← hS] at hu
exact ⟨u, v, hu, hv, hue⟩
· intro heqs i p hp
obtain ⟨q, -, hq'⟩ := Design2.lowerComb_proc hl hp
obtain ⟨t, e, ws, rfl, -⟩ := hd'.assign i q hq'
obtain ⟨v, hv⟩ := hd'.evalPure2_ok hs hq'
have hid : combWrite d' S t v = S := combWrite_eq_self fun n lo w off hm => by
obtain ⟨old, v', hold, hv', hext⟩ := heqs i t e n lo w off hq' hm
rw [hv, Res.ok.injEq] at hv'
subst hv'
exact ⟨old, hold, hext⟩
have hrun : runCombProcess2 (e.size + 1) S (.assign t e) = .ok S := by
rw [runCombProcess2_assign_write hd' hs hq' (Nat.lt_succ_self _) hv, hid]
obtain ⟨p', hp', hsim⟩ := (CombSim.of_lowerComb' hl hd').proc i _ hq'
rw [hp, Option.some.injEq] at hp'
subst hp'
obtain ⟨-, f', hf'⟩ := hsim S hs _ S hrun
exact ⟨f', fun f hf => runCombProcess2_mono hf' (by simp) f hf⟩
/-- **Snapshots of a run of an `always_comb` design**: every snapshot is a
fixpoint of every process, solves the lowered equations and has the comb
shape. -/
theorem Runs2.combProc_snapshot (hl : d.lowerComb = some d') (hd' : CombDesign d')
{σ : ScheduleOracle} {stim tr : List SvState} (hr : Runs2 d σ stim tr) {c : Nat}
{st : SvState} (hc : tr[c]? = some st)
(hnq : d.processes.toList.any Process2.hasQualifiedCase = false) :
ProcFixpoint d st ∧ CombEqs d' st ∧ StateShape d' st := by
obtain ⟨heqs, hs, -⟩ := ((Runs2.lowerComb_iff hl hd' hnq).mp hr).comb_snapshot hd' hc
exact ⟨(procFixpoint_iff_combEqs hl hd' hs).mpr heqs, heqs, hs⟩
/-- **One equation per written window of an `always_comb` body**: in a
shaped solution of the lowered equations, every window the body's symbolic
execution wrote holds the value, in that same state, of its symbolic
expression. -/
theorem CombEqs.symWindow (hl : d.lowerComb = some d') (hd' : CombDesign d') {S : SvState}
(heqs : CombEqs d' S) (hs : StateShape d' S) {i : Nat} {body : Stmt2}
(hp : d.processes[i]? = some (.alwaysComb body)) {sy : SymState}
(hsym : body.symExec d [] = some sy) {W : Win} {E : Expr2} (hm : (W, E) ∈ sy) {v : LVec}
(hv : evalPure2 S E = .ok v) :
∃ u, SvState.lookup S W.1 = some u ∧ u.extract W.2.1 W.2.2 = v := by
obtain ⟨F, hF⟩ := (procFixpoint_iff_combEqs hl hd' hs).mpr heqs i _ hp
have hrun := hF F (Nat.le_refl _)
obtain ⟨q, hq, hq'⟩ := Design2.lowerComb_proc hl hp
obtain ⟨sy', hsy', -, rfl⟩ := Process2.lowerComb_alwaysComb hq
rw [hsym, Option.some.injEq] at hsy'
subst hsy'
simp only [runCombProcess2, Res.bind_eq_ok] at hrun
obtain ⟨⟨st', nba⟩, hexec, hr⟩ := hrun
obtain ⟨rfl, hR⟩ := (symExec_sound F).1 body [] sy S [] st' nba hsym
(SymOk.of_shape (Design2.lowerComb_spec hl).2.1 hq' hs) (SymRel.init S) hexec
simp only [Res.ok.injEq] at hr
subst hr
exact hR.entry W E hm v hv
/-- **A whole-signal window, read back**: if the body's symbolic execution
wrote all of `n` (window `(n, 0, w)`, `w` its declared width), then in a
shaped solution `n` holds its symbolic expression's value. -/
theorem CombEqs.symWindow_ident (hl : d.lowerComb = some d') (hd' : CombDesign d')
{S : SvState} (heqs : CombEqs d' S) (hs : StateShape d' S) {i : Nat} {body : Stmt2}
(hp : d.processes[i]? = some (.alwaysComb body)) {sy : SymState}
(hsym : body.symExec d [] = some sy) {n : String} {w : Nat} {E : Expr2}
(hm : ((n, 0, w), E) ∈ sy) (hw : d.declWidth n = some w) {v : LVec}
(hv : evalPure2 S E = .ok v) : SvState.lookup S n = some v := by
obtain ⟨u, hu, hext⟩ := CombEqs.symWindow hl hd' heqs hs hp hsym hm hv
obtain ⟨q, hq, hq'⟩ := Design2.lowerComb_proc hl hp
obtain ⟨sy', hsy', -, rfl⟩ := Process2.lowerComb_alwaysComb hq
rw [hsym, Option.some.injEq] at hsy'
subst hsy'
have hok := SymOk.of_shape (Design2.lowerComb_spec hl).2.1 hq' hs
have hwu := hok n (SymState.mem_names.mpr ⟨_, hm, rfl⟩) w hw
rw [hu] at hwu
simp only [Option.map_some, Option.some.injEq] at hwu
simp only at hext
rw [← hwu, LVec.extract_full] at hext
rw [hu, hext]
/-- **One run of a lowered body is already its own fixpoint**: on a shaped
state, re-running an `always_comb` body of the profile on the state its
run produced changes nothing. This is why the settle semantics (which
re-runs a comb process until nothing changes) and an event-driven simulator
(which runs an `always_comb` block once per wake-up and is never woken by
the block's own writes, IEEE 1800-2023 §9.2.2.2.1) give a body of the
profile the same result. The lowering's self-read check
(`SymState.selfReadFree`) is the hypothesis that makes it true: the lowered
right-hand side reads nothing the run writes. -/
theorem runCombProcess2_alwaysComb_idem (hl : d.lowerComb = some d') (hd' : CombDesign d')
{i : Nat} {body : Stmt2} (hp : d.processes[i]? = some (.alwaysComb body)) {st : SvState}
(hs : StateShape d' st) {f : Nat} (hf : body.fuelB ≤ f) {r : SvState}
(h : runCombProcess2 f st (.alwaysComb body) = .ok r) :
runCombProcess2 f r (.alwaysComb body) = .ok r := by
obtain ⟨q, hq, hq'⟩ := Design2.lowerComb_proc hl hp
obtain ⟨S, hsym, hfree, rfl⟩ := Process2.lowerComb_alwaysComb hq
have hdecls := (Design2.lowerComb_spec hl).2.1
obtain ⟨v, hv, rfl⟩ := runCombProcess2_alwaysComb_ok hd' hdecls hsym hq' hs h
obtain ⟨v', hv', hrun⟩ :=
runCombProcess2_alwaysComb_fwd hd' hdecls hsym hq' (hs.combWrite S.target v) hf
have hsame : evalPure2 (combWrite d' st S.target v) S.rhs = evalPure2 st S.rhs := by
refine (evalPure2_congr_reads fun rd hrd => ReadWin.agree_combWrite fun wn hwn => ?_).symm
rw [SymState.target_wins] at hwn
exact SymState.selfReadFree_iff.mp hfree rd hrd wn hwn
rw [hsame, hv, Res.ok.injEq] at hv'
subst hv'
rw [hrun]
congr 1
exact combWrite_eq_self fun n lo w off hm => hd'.combWrite_readBack hs hq' v hm
end Lowered
/-! ## Single-process designs: the profile as decidable checks
A design whose only process is one `always_comb` lowers to a design whose
only process is one assign. For such a design the comb profile and the
acyclicity certificate reduce to finitely many checks on closed data, each
discharged by `decide` (or `rfl`) on a concrete design. -/
/-- **The comb profile of a one-assign design**, from static checks. -/
theorem CombDesign.of_single {d : Design2} {t : Lhs2} {e : Expr2}
(hp : d.processes = #[.assign t e]) (hnodup : (d.decls.toList.map Decl.name).Nodup)
{ws : List Win} (hws : t.windows? d = some ws)
(hin : ∀ W ∈ ws, ((d.declWidth W.1).map fun dw => decide (W.2.1 + W.2.2 ≤ dw)) = some true)
(hni : ∀ W ∈ ws, W.1 ∉ d.inputNames.toList)
(hdisj : Win.allDisjoint ws = true) (hfree : e.unsupportedFree = true)
(hdecl : ∀ x ∈ e.idents, x ∈ d.decls.toList.map Decl.name)
(hinit : ∀ dc ∈ d.decls.toList, dc.name ∈ ws.map Prod.fst →
dc.init.all (fun v => v.width == dc.width) = true)
(hfour : ∀ dc ∈ d.decls.toList, dc.isInput = true → dc.twoState = false) :
CombDesign d := by
have hproc : ∀ {i : Nat} {p : Process2}, d.processes[i]? = some p → i = 0 ∧ p = .assign t e := by
intro i p h
rw [hp] at h
match i, h with
| 0, h => simpa [eq_comm] using h
have hwins : t.wins d = ws := Lhs2.wins_of_windows? hws
have hW : ∀ {i : Nat} {wn : Win}, d.Writes i wn → i = 0 ∧ wn ∈ ws := by
rintro i wn ⟨t', e', h, hm⟩
obtain ⟨rfl, hpe⟩ := hproc h
simp only [Process2.assign.injEq] at hpe
obtain ⟨rfl, rfl⟩ := hpe
exact ⟨rfl, hwins ▸ hm⟩
refine ⟨hnodup, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_, hfour⟩
· intro i p h
obtain ⟨-, rfl⟩ := hproc h
exact ⟨t, e, ws, rfl, hws⟩
· intro i n lo w h
have := hin _ (hW h).2
cases hdw : d.declWidth n with
| none => simp [hdw] at this
| some dw => simp only [hdw, Option.map_some, Option.some.injEq,
decide_eq_true_eq] at this; exact ⟨dw, rfl, this⟩
· rintro n ⟨i, lo, w, h⟩
exact hni _ (hW h).2
· intro i j wi wj hij hi hj
exact absurd ((hW hi).1.trans (hW hj).1.symm) hij
· intro i t' e' h
obtain ⟨-, hpe⟩ := hproc h
simp only [Process2.assign.injEq] at hpe
obtain ⟨rfl, rfl⟩ := hpe
rw [hwins]
exact (Win.allDisjoint_iff ws).mp hdisj
· intro i t' e' h
obtain ⟨-, hpe⟩ := hproc h
simp only [Process2.assign.injEq] at hpe
obtain ⟨rfl, rfl⟩ := hpe
exact hfree
· intro i t' e' h
obtain ⟨-, hpe⟩ := hproc h
simp only [Process2.assign.injEq] at hpe
obtain ⟨rfl, rfl⟩ := hpe
exact hdecl
· rintro n ⟨i, lo, w, h⟩ dc hdc rfl v hv
have := hinit dc hdc (List.mem_map.mpr ⟨_, (hW h).2, rfl⟩)
simpa [hv] using this
/-- **Acyclicity of a one-assign design**: its right-hand side reads none of
its own windows (any rank works). -/
theorem Acyclic.of_single {d : Design2} {t : Lhs2} {e : Expr2}
(hp : d.processes = #[.assign t e])
(h : ∀ r ∈ e.reads, ∀ W ∈ t.wins d, r.overlaps W = false) : Acyclic d := by
refine ⟨fun _ => 0, ?_⟩
intro i j ti ei tj ej wj hi hj hw ⟨r, hr, hov⟩
exfalso
rw [hp] at hi hj
match i, j, hi, hj with
| 0, 0, hi, hj =>
simp only [List.getElem?_toArray, List.getElem?_cons_zero, Option.some.injEq,
Process2.assign.injEq] at hi hj
obtain ⟨rfl, rfl⟩ := hi
obtain ⟨rfl, rfl⟩ := hj
rw [h r hr wj hw] at hov
cases hov
/-- **The lowering of a one-`always_comb` design is acyclic, for free**: the
self-read check the lowering already ran (`SymState.selfReadFree`) is the
whole acyclicity obligation of a single process. -/
theorem Acyclic.of_lowerComb_single {d d' : Design2} (hl : d.lowerComb = some d')
{body : Stmt2} (hp : d.processes = #[.alwaysComb body]) : Acyclic d' := by
obtain ⟨-, -, hsize, hget⟩ := Design2.lowerComb_spec hl
have hq : d'.processes[0]? = (Process2.alwaysComb body).lowerComb d := by
rw [hget 0, hp]; rfl
have h1 : d'.processes.size = 1 := by rw [hsize, hp]; rfl
cases hq' : (Process2.alwaysComb body).lowerComb d with
| none =>
rw [hq', Array.getElem?_eq_getElem (by omega : 0 < d'.processes.size)] at hq
cases hq
| some q =>
obtain ⟨S, -, hfree, rfl⟩ := Process2.lowerComb_alwaysComb hq'
rw [hq'] at hq
refine ⟨fun _ => 0, ?_⟩
intro i j ti ei tj ej wj hi hj hw ⟨rd, hrd, hov⟩
exfalso
have hi0 : i = 0 := by have := (Array.getElem?_eq_some_iff.mp hi).1; omega
have hj0 : j = 0 := by have := (Array.getElem?_eq_some_iff.mp hj).1; omega
subst hi0 hj0
rw [hq] at hi hj
simp only [SymState.toAssign, Option.some.injEq, Process2.assign.injEq] at hi hj
obtain ⟨rfl, rfl⟩ := hi
obtain ⟨rfl, rfl⟩ := hj
rw [SymState.target_wins] at hw
rw [SymState.selfReadFree_iff.mp hfree rd hrd wj hw] at hov
cases hov
/-- **Termination of a one-`always_comb` design** from the lowering and the
comb profile alone (`Acyclic.of_lowerComb_single` supplies the
certificate). -/
theorem Terminates2.of_lowerComb_single {d d' : Design2} (hl : d.lowerComb = some d')
(hd' : CombDesign d') {body : Stmt2} (hp : d.processes = #[.alwaysComb body])
(hnq : d.processes.toList.any Process2.hasQualifiedCase = false) :
Terminates2 d :=
Terminates2.of_lowerComb_acyclic hl hd' (Acyclic.of_lowerComb_single hl hp) hnq
end LeanModels.Sv