State a timer condition in a contract¶
You want a safety claim about a timer: the drive must not run once a hand button has been held for 30 seconds. Written the obvious way, the compiler refuses:
FUNCTION_BLOCK TwoHandRejected
VAR_INPUT T1 : BOOL; T2 : BOOL; END_VAR
VAR_OUTPUT PA : BOOL; END_VAR
CONTRACT
INVARIANT (NOT PA) OR NOT (HELD(T1, T#30s) OR HELD(T2, T#30s));
END_CONTRACT
IMPLEMENTATION
PA := T1 AND T2;
END_IMPLEMENTATION
END_FUNCTION_BLOCK
verification failed:
[sema-error] line 5: HELD/ELAPSED may not appear inside an INVARIANT: the
timers' own laws are not yet part of the step relation (ADR TMR-1). Mirror
the condition into a register and state the claim over that (ADR TX-1.2), or
use them in rule guards / definitions.
This page shows the way that works, and — more usefully — the two ways that look like they should and do not.
Why the refusal¶
An INVARIANT is a predicate over a single state, discharged by one-step
induction: it holds initially, and if it holds in a state it holds in the
next. PRE(x) widens that by exactly one register. A running timer is not a
state predicate — it is a statement about how long something has been true,
which one-step induction cannot see.
That is a deliberate boundary, not a gap. Admitting timers would require k-induction, an unrolling bound, and counterexamples far longer than the two states you get today.
The pattern that works¶
Mirror the timer condition into a register, and state the claim over that register:
FUNCTION_BLOCK TwoHand
VAR_INPUT T1 : BOOL; T2 : BOOL; END_VAR
VAR_OUTPUT PA : BOOL; KL : BOOL; END_VAR
CONTRACT
INVARIANT (NOT PA) OR (NOT KL);
END_CONTRACT
IMPLEMENTATION
KL:
ON WHEN HELD(T1, T#30s) OR HELD(T2, T#30s);
OFF OTHERWISE;
PA:
ON WHEN T1 AND T2 AND NOT (HELD(T1, T#30s) OR HELD(T2, T#30s));
OFF OTHERWISE;
END_IMPLEMENTATION
END_FUNCTION_BLOCK
Compilation succeeded: TwoHand
1 output property verified
ON WHEN X; OFF OTHERWISE; sets the register to the current value of
X, so KL in the successor state is exactly the timer condition at the
decision instant — which is what the safety claim is about. No sharpness is
lost.
Two variants that look right and are not¶
Both of these were written first, and both are rejected. They are worth knowing, because the reason is the same in each case and it is easy to miss.
The drive as a plain definition¶
PA := T1 AND T2 AND NOT KL;
Rejected:
verification failed:
[invariant-output] invariant over a derived/output value is not implied
by the register invariants: (NOT PA OR NOT KL)
An invariant over a derived value has to follow from what the registers
guarantee. PA here is recomputed from inputs every scan, so the claim about
it is not anchored in any register invariant the prover can induct over.
The drive reading the mirror register¶
PA:
ON WHEN T1 AND T2 AND NOT KL;
OFF OTHERWISE;
Rejected, and this counterexample is the instructive one:
verification failed:
[invariant-step] invariant can be violated after one cycle:
(NOT PA OR NOT KL)
counterexample: KL=0, PA=0, T1=1, T2=1, now=30000, started_0=0
Both registers update from the same previous state. In the scan where the
timer matures, PA still sees KL = 0 and switches on, while KL switches
on as well — and in the successor state both are set. The register you
mirrored into cannot guard the very output that is updated beside it.
So the guard names the condition, not the mirror:
PA:
ON WHEN T1 AND T2 AND NOT (HELD(T1, T#30s) OR HELD(T2, T#30s));
OFF OTHERWISE;
KL then exists for the contract to speak about, and both registers see the
same instant.
What stays out of reach¶
Named so the absence is a known boundary rather than a suspected gap:
- Properties over a running timer inside
INVARIANT— rejected outright, as above. - Relating an input event to a state change more than one step back.
PREreaches back exactly one step; a property needing two steps of history cannot be written. - Liveness ("eventually"), which needs a fairness notion the model does not have.
The first is a compiler error. The other two are silently inexpressible — you simply cannot write them down, which is why they are listed here.
Related¶
docs/adr-temporal-expressiveness.md— the limit and why it is where it isdocs/adr-timer-wellformedness.md— what makes a timer usable in a proof