The previous tick
With pre(x), the value x had one tick ago, a chart can react to changes instead of levels: detect edges, fire one-tick pulses and count.
A doorbell should ring once per press, however long the finger stays on the button. A light switch
should toggle once per press, not once per tick. Reacting to a change needs two values: the one
now and the one a tick ago. So far a chart could only see the first. This chapter adds the second,
with pre, and then shows count delays, which are built on it.
Looking one tick back
Section titled “Looking one tick back”Diagram
Code
scchart Change { input int temp output int previous, delta
initial state Watch { immediate during do previous = pre(temp); delta = temp - pre(temp) }}| Tick | input int temp | output int previous | output int delta | Active state of the chart |
|---|
Guided tour, 5 ticks:
- temp = 20Tick 1: there is no previous tick, so
pre(temp)is the default for anint, 0. That makesdelta20, as if the temperature had jumped from 0. - temp = 21
pre(temp)is 20, the valuetemphad when tick 1 ended.deltais 1. tempstays 21 (inputs keep their value), sopre(temp)is 21 anddeltais 0.- temp = 19A drop of 2:
deltais −2. - temp = 22
pre(temp)is 19:deltais 3.
previous is always last tick’s temp. Look at tick 1, where there is no last tick.pre(temp) is the value temp had at the end of the previous tick. The chart computes it in an
immediate during action, so it runs in every tick including the first (see
Entry, during and exit actions):
| Tick | temp |
previous |
delta |
|---|---|---|---|
| 1 | 20 | 0 | 20 |
| 2 | 21 | 20 | 1 |
| 3 | 21 (held) | 21 | 0 |
| 4 | 19 | 21 | −2 |
| 5 | 22 | 19 | 3 |
pre works on inputs, outputs and local variables alike, and it fits into any expression:
temp - pre(temp) is “how much did temp change since the last tick”. Writes in the current tick
don’t affect pre(x): it reads a copy that was taken when the previous tick ended, so a chart may
both write x and read pre(x) in the same tick.
Tick 1 has no past
Section titled “Tick 1 has no past”In tick 1, delta is 20, as if the temperature had jumped from 0. There is no previous tick, so
what does pre give there? temp is an input without an initial value. Predict what happens for a
variable that does have one, then run it and open the explanation:
Diagram
Code
scchart Tank { output int level = 50 output int before
initial state Fill { immediate during do before = pre(level) during do level += 10 }}level starts at 50 and a delayed during action adds 10 every tick. What is before, which is pre(level), after ticks 1 and 2?
Explanation
before is 0 after tick 1 and 50 after tick 2. level is 50 throughout tick 1, but pre(level) in
tick 1 is 0, not 50. From tick 2 on it is what you expect: 50 in tick 2 while level has already moved
on to 60, 60 in tick 3, and so on.
In tick 1, KIELER fills the gap with the default of the variable’s type: 0 for an int, false for a
bool, even for a variable with an initial value.
The same happens whenever a scope starts afresh: a variable declared inside a superstate gets a new
past each time the superstate is entered, so in that entry tick pre of it is the default again.
Inputs, outputs and the chart’s other top-level variables belong to the chart itself, which is
entered only once, in tick 1.
Edges and pulses
Section titled “Edges and pulses”A rising edge is the tick in which a bool goes from false to true, a falling edge the tick in
which it goes from true to false. With pre both are one expression: pressed below is
button && !pre(button), “true now and false a tick ago”, and released is !button && pre(button).
Diagram
Code
scchart Edges { input bool button output bool pressed, released
initial state Watch { immediate during do pressed = button && !pre(button); released = !button && pre(button) }}| Tick | input bool button | output bool pressed | output bool released | Active state of the chart |
|---|
Guided tour, 8 ticks:
- button = falseNot pressed.
pre(button)is false in tick 1 (the default). - button = truePressed now, not pressed before: a rising edge, so
pressedis true for this tick. - Still held:
buttonandpre(button)are both true, so no edge. - Still held: no edge.
- button = falseReleased now, pressed before: a falling edge,
releasedis true. - Not pressed, and not pressed before: nothing.
- button = trueA new press:
pressedagain. - button = falseA one-tick press gives a rising edge and, one tick later, a falling edge.
pressed and released are each true for exactly one tick per change, however long the button is held.| Tick | button |
pressed |
released |
|---|---|---|---|
| 1 | false | false | false |
| 2 | true | true | false |
| 3 | true (held) | false | false |
| 4 | true (held) | false | false |
| 5 | false | false | true |
| 6 | false (held) | false | false |
| 7 | true | true | false |
| 8 | false | false | true |
pressed is a one-tick pulse: true in one tick, false in the next, whatever the input does. Ticks 7
and 8 show the shortest press there is, one tick long: a rising edge, and a falling edge right after.
Because pre(button) is false in tick 1, a button that is already held in tick 1 counts as a rising
edge in tick 1.
Edges in triggers
Section titled “Edges in triggers”Edges matter most in triggers. Here is the toggle from Thinking in ticks next to a version that triggers on the rising edge, both with the button held for three ticks:
Diagrams
Toggle
EdgeToggle
Code
Toggle
scchart Toggle { input bool button output bool light = false
initial state Off if button do light = true go to On
state On if button do light = false go to Off}EdgeToggle
scchart EdgeToggle { input bool button output bool light = false
initial state Off if button && !pre(button) do light = true go to On
state On if button && !pre(button) do light = false go to Off}| Tick | input bool button | Toggle: output bool light | Toggle: Active state of the chart | EdgeToggle: output bool light | EdgeToggle: Active state of the chart |
|---|
Guided tour, 7 ticks, the same inputs for both charts:
- button = falseTick 1 enters
Offin both charts. - button = trueThe button goes down. For
Togglebuttonis true; forEdgeToggleit is a rising edge,button && !pre(button). Both go fromOfftoOn: the light is on. - The button is still held.
Toggleseesbuttontrue inOnand goes straight back toOff.EdgeToggleseespre(button)true: no edge, so it staysOn. - Still held:
Toggleflips back toOn, and will flicker for as long as the button is held.EdgeTogglestill sees no edge. - button = falseReleased. Neither chart reacts to a falling button.
Toggle’s light is wherever the flicker left it, here on. - button = trueA new press, a new rising edge: both charts go from
OntoOff. - button = falseReleased.
EdgeToggletoggled exactly once per press.
Toggle: if button. EdgeToggle: if button && !pre(button). The button is held from tick 2 to tick 4: watch light in both charts.| Tick | button |
light in Toggle |
light in EdgeToggle |
|---|---|---|---|
| 1 | false | false | false |
| 2 | true | true | true |
| 3 | true (held) | false | true |
| 4 | true (held) | true | true |
| 5 | false | true | true |
| 6 | true | false | false |
| 7 | false | false | false |
Toggle flickers for as long as the button is held: in every tick button is true, so whichever state
the chart is in, its transition fires. On a real controller a tick may last a millisecond and a press a
few hundred, so the light would end up on or off at random. EdgeToggle toggles exactly once per press.
When one press should do one thing, trigger on the edge. Edges and pulses collects
more patterns of this kind. Edges help in testing too: in SCCharts Lab a breakpoint can be a condition
that uses pre, so button != pre(button) pauses the simulation on every edge of button
(Test your chart has more on breakpoints).
pre with signals
Section titled “pre with signals”For a signal, pre(S) asks whether S was present in the previous tick. For a valued signal V,
pre(V) is its presence in the previous tick and pre(val(V)) its value at the end of the previous
tick (which, like any signal value, stays put while the signal is absent). A doorbell that chimes once
per press and counts its rings:
Diagram
Code
scchart Doorbell { input signal press output signal chime output signal int rings
initial state Watch { immediate during if press && !pre(press) do chime; rings(pre(val(rings)) + 1) }}| Tick | input signal press | output signal chime | output signal int rings | Active state of the chart |
|---|
Guided tour, 6 ticks:
- press = falseNo press.
ringshas the value 0 (the default). - press = true
pressis present andpre(press)is absent: a new press. The chart emitschimeand emitsringswithpre(val(rings)) + 1= 1. pressis still present (held), but it was present last tick too: no chime.ringsis absent this tick; its value stays 1.- press = falseReleased.
- press = trueA second press:
chime, andringscarries 2. - press = falseReleased. The value of
ringsstays 2 until the next emission.
chime is emitted on the first tick of each press. rings carries the number of rings so far.| Tick | press |
chime |
rings |
val(rings) |
|---|---|---|---|---|
| 1 | absent | absent | absent | 0 |
| 2 | present | present | present | 1 |
| 3 | present (held) | absent | absent | 1 |
| 4 | absent | absent | absent | 1 |
| 5 | present | present | present | 2 |
| 6 | absent | absent | absent | 2 |
The emission rings(pre(val(rings)) + 1) builds on last tick’s value. rings(val(rings) + 1) would
read the value it is about to set, and KIELER rejects it, as you saw in
Signals. Reading the previous value is the standard way
to build on a signal’s value.
Further back
Section titled “Further back”pre can be nested: pre(pre(x)) is the value two ticks ago. A long press is a button held now, one
tick ago and two ticks ago:
Diagram
Code
scchart LongPress { input bool button output bool longPress
initial state Watch { immediate during do longPress = button && pre(button) && pre(pre(button)) }}| Tick | input bool button | output bool longPress | Active state of the chart |
|---|
Guided tour, 8 ticks:
- button = falseNot pressed.
- button = truePressed in this tick only so far.
- Pressed now and one tick ago.
- Pressed now, one tick ago and two ticks ago (
pre(pre(button))): a long press. - Still a long press.
- button = falseReleased.
- button = truePressed again.
- Only two ticks so far:
pre(pre(button))is false, so this is not a long press yet.
longPress needs three ticks in a row. Ticks 7 and 8 are a press of two ticks: not long enough.Each pre adds one remembered value to the compiled program. To look ten ticks back, count instead:
with a counter variable, or with a count delay.
Count delays
Section titled “Count delays”A count delay puts a number in front of a trigger: if 3 knock go to Open fires in the third tick
in which knock holds while the state is active. A door that opens after three knocks:
Diagram
Code
scchart Knock { input bool knock, away output bool open = false
initial state Shut if 3 knock do open = true go to Open if away go to Away
state Away if !away go to Shut
state Open}| Tick | input bool knock | input bool away | output bool open | Active state of the chart |
|---|
Guided tour, 11 ticks:
- knock = false, away = falseTick 1 enters
Shut. The count delay starts at zero. - knock = trueFirst knock: one tick in which
knockholds. - knock = falseA pause does not reset the count: the ticks need not be consecutive.
- knock = trueSecond knock.
- knock = false, away = trueThe visitor walks away before the third knock:
Shutis left. - away = falseBack again:
Shutis entered anew, and every entry starts the count from zero. - knock = trueFirst knock of the new visit.
- knock = falsePause.
- knock = trueSecond knock.
- Third tick with
knockin this visit: the transition fires and the door opens. - Open.
| Tick | knock |
away |
State after | open |
|---|---|---|---|---|
| 1 | false | false | Shut | false |
| 2 | true | false (held) | Shut | false |
| 3 | false | false (held) | Shut | false |
| 4 | true | false (held) | Shut | false |
| 5 | false | true | Away | false |
| 6 | false (held) | false | Shut | false |
| 7 | true | false (held) | Shut | false |
| 8 | false | false (held) | Shut | false |
| 9 | true | false (held) | Shut | false |
| 10 | true (held) | false (held) | Open | true |
Read off the rules:
- The ticks need not be consecutive. Ticks 2 and 4 both count, with a pause between them.
- Every entry restarts the count. In tick 5 the visitor leaves with two knocks counted. When
Shutis entered again in tick 6, the count starts from zero, and it takes three more ticks withknock(7, 9 and 10) to open the door. A self-loop is an entry too, so it restarts the count as well. - The entry tick doesn’t count for a delayed count delay, just as a delayed transition is not
checked in its source’s entry tick. With
immediate if 3 knock, the entry tick counts.
if 1 knock means the same as if knock. if 0 knock is rejected with
A count delay of 0 is not allowed on a trigger.. And because true holds in every tick,
if 5 true go to Next waits for five ticks after the state was entered: a simple timeout, measured in
ticks. Ticks are not seconds, though: how long a tick lasts depends on how often the program is run.
Time shows how to work with real time.
Count delays are for transitions. On an entry or exit action a count is a syntax error, and on a
during action it is ignored: during if 3 knock do … compiles, but runs exactly as
during if knock do …, and KIELER warns (the spelling is KIELER’s):
Count Delays on During Actions are not supported yet. The valued will be ignored by the transformation.To count in an action, use a counter variable, as KnockByHand does below.
Count delays count ticks, not presses. Predict what a held knock does, then run it and open the explanation:
Diagram
Code
scchart Knock { input bool knock, away output bool open = false
initial state Shut if 3 knock do open = true go to Open if away go to Away
state Away if !away go to Shut
state Open}knock is true from tick 1 and stays true: one long knock. Is the door open after tick 3? After tick 4?
Inputs per tick. A value in grey was not set in that tick: it holds from an earlier one.
| Tick | knock | away |
|---|---|---|
| 1 | true | false |
| 2 | true | false |
| 3 | true | false |
| 4 | true | false |
Explanation
The door is still shut after tick 3 and open after tick 4. Tick 1 is Shut’s entry tick and doesn’t
count; ticks 2, 3 and 4 do, so one long knock opens the door in tick 4.
If you mean three separate knocks, count rising edges: that is the second exercise below.
What a count delay really is
Section titled “What a count delay really is”KIELER has no counting transitions at run time. Its compiler rewrites if 3 knock into a counter and
pre, and you can write the same thing by hand:
Diagrams
Knock
KnockByHand
Code
Knock
scchart Knock { input bool knock, away output bool open = false
initial state Shut if 3 knock do open = true go to Open if away go to Away
state Away if !away go to Shut
state Open}KnockByHand
scchart KnockByHand { input bool knock, away output bool open = false output int count
initial state Shut { entry do count = 0 during if knock do count++ } if pre(count) == 2 && knock do open = true go to Open if away go to Away
state Away if !away go to Shut
state Open}| Tick | input bool knock | input bool away | Knock: output bool open | Knock: Active state of the chart | KnockByHand: output bool open | KnockByHand: output int count | KnockByHand: Active state of the chart |
|---|
Guided tour, 11 ticks, the same inputs for both charts:
- knock = false, away = falseTick 1 enters
Shutin both charts.KnockByHand’s entry action setscount = 0. - knock = trueFirst knock.
KnockByHand’s during action counts it:countis 1. Its trigger readspre(count), 0, so it does not fire;Knock’s count delay has one tick too. - knock = falseA pause:
countstays 1. The ticks need not be consecutive. - knock = trueSecond knock:
countis 2, andpre(count)was 1. - knock = false, away = trueThe visitor walks away before the third knock: both charts leave
ShutforAway. - away = falseBack again:
Shutis entered anew.KnockByHand’s entry action resetscountto 0, andKnock’s count delay starts from zero as well. - knock = trueFirst knock of the new visit:
countis 1. - knock = falsePause:
countstays 1. - knock = trueSecond knock:
countis 2. - Third tick with
knockin this visit.KnockByHandseespre(count) == 2andknock, andKnock’s count delay reaches 3: both doors open in this same tick. - Both open.
Knock: the count delay. KnockByHand: the same logic with a counter, count, that you can watch. With the same knocks both doors open in tick 10.output int count // an output, so you can watch itinitial state Shut { entry do count = 0 // every entry restarts during if knock do count++ // delayed: skips the entry tick}if pre(count) == 2 && knock do open = true go to OpenThe trigger asks “two knocks counted before this tick, and a knock now”. Each rule above is visible
in the expansion: the entry action restarts the count, and the plain during skips the entry tick.
For immediate if 3 knock, the compiler writes immediate during instead.
That expansion has one consequence that nobody would design on purpose:
Diagram
Code
scchart ImmediateCount { input bool knock, away output bool open = false
initial state Shut immediate if 3 knock do open = true go to Open if away go to Away
state Away if !away go to Shut
state Open}| Tick | input bool knock | input bool away | output bool open | Active state of the chart |
|---|
Guided tour, 5 ticks:
- knock = true, away = falseAn
immediatecount delay counts the entry tick too: 1. knockis still true: two counted, one short of opening the door.- knock = false, away = trueThe visitor walks away with two knocks counted.
- knock = true, away = falseBack, and knocking:
Shutis entered anew, which should restart the count. Yet the door opens at once, after one knock in this visit. The trigger readspreof the counter, which still holds the 2 from the last visit. - The door stays open.
Note: KIELER's immediate count can complete as soon as Shut is entered again, if Shut was left one count short, however long ago
- Note, line 6, column 5KIELER implements
immediate if 3 ...with a counter that restarts on entry, but compares the counter's previous value, which is still the old visit's in the tick after a re-entry. A delayed count (without immediate) does not have this problem.
immediate if 3 knock. Two knocks, a walk away, and on return a single knock opens the door.In tick 4 the visitor comes back and knocks once, and the door opens. Entering Shut did reset the
counter to 0 and the knock counted 1, but the trigger reads pre(count), which is still 2: the value
when the previous tick ended. The reset only shows in pre from the next tick on. A delayed count delay
can’t do this, because its transition is not checked in the entry tick.
Count delays inside a reset loop
Section titled “Count delays inside a reset loop”ABRO’s reset from Aborts, the self-loop if R abort to ABO
around the whole behaviour, is the usual way to start a chart over. KIELER cannot compile a count
delay anywhere inside a state with such a loop:
Diagram
Code
scchart KnockRestart { input bool knock, restart output signal unlock
initial state Door { initial state Shut if 3 knock do unlock go to Open
state Open } if restart abort to Door}KIELER rejects this chart
- Line 7, column 12KIELER cannot compile the count delay
if 3 knockonShut's transition toOpen:Door, around it, has the self-loopif restart abort to DoorKIELER says:The SCG is NOT asc-schedulable!
KIELER keeps the count in a variable of the state aroundShut, and in a tick in whichif restart abort to DoorleavesDoorand enters it again, restarting the count and counting wait for each other. It rejects every count delay inside a state with a self-loop. Count by hand, with a variable declared outsideDoor:entry do count = 0andduring if knock do count++onShut, andif count == 3on its transition.About this message
if 3 knock inside Door, and if restart abort to Door around it. KIELER rejects the chart.The messages are the scheduling errors that Why the compiler says no explains,
ending in The SCG is NOT asc-schedulable!, although the chart has no concurrent regions. The count
delay on its own compiles, and so does the reset loop; together they leave no valid order in the code
KIELER generates for the count delay. It makes no difference whether the reset is abort to or
go to, or how deep inside the state the count delay sits.
The way out is to count by hand, with a counter declared at the top of the chart, outside the state with the reset loop:
Diagram
Code
scchart KnockRestartByHand { input bool knock, restart output signal unlock output int count
initial state Door { initial state Shut { entry do count = 0 during if knock do count++ } if count == 3 do unlock go to Open
state Open } if restart abort to Door}| Tick | input bool knock | input bool restart | output signal unlock | output int count | Active state of the chart | Active state of the region of Door |
|---|
Guided tour, 12 ticks:
- knock = false, restart = falseTick 1 enters
DoorandShut.Shut’s entry action setscountto 0. - knock = trueFirst knock: the during action counts it,
countis 1. - knock = falseA pause:
countstays 1. - knock = trueSecond knock:
countis 2. - knock = false, restart = true
restartleavesDoorand enters it again in the same tick. EnteringShutsetscountback to 0. - knock = true, restart = falseThe first knock after the restart:
countis 1. - knock = falseA pause.
- knock = trueSecond knock:
countis 2. - Third knock: the during action makes
count3, the transition out ofShutreads it after that update, andunlockis present in this tick. - knock = falseIn
Open.unlockis a signal, so it was present for that one tick only. - restart = true
restartworks fromOpentoo: back inShut, withcountat 0. - restart = falseWaiting for knocks again.
Warning: KIELER warns about a potential instantaneous loop here, but compiles and runs the chart correctly
- Warning, line 7, column 19KIELER says:
Instantaneous loop detected!
KIELER's loop check works on the compiled chart, in which entry and during actions and strong aborts are extra regions and transitions, and warns whenever control could go round a circle there within one tick, without checking whether the triggers prevent it: here the circle runs throughDoorandShut. The chart works as written.About this message
count. restart in tick 5 starts the count again, and the third knock after it, in tick 9, unlocks the door.The transition tests count == 3 itself: it reads count after the during action has updated it,
so the door unlocks in the tick of the third knock, as with if 3 knock. KIELER compiles this
version with an instantaneous-loop warning, as it does for many charts with a reset loop around
actions; the chart behaves as described (more about this warning).
Count delay or counter? weighs the two in general.
Try it
Section titled “Try it”Count presses, not ticks
Presses should count how often the button is pressed, but holding it down counts every tick. Change
the during action so that each press counts once, however long it lasts.
Show the starter chart
scchart Presses { input bool button output int presses = 0
initial state Count { immediate during if button do presses++ }}Show a solution
scchart Presses { input bool button output int presses = 0
initial state Count { immediate during if button && !pre(button) do presses++ }}Three separate knocks
In Knock, one long knock opens the door. Change the transition so that it opens only after three
separate knocks: three rising edges of knock. Keep the count delay; only its trigger needs to change.
Show the starter chart
scchart Knock { input bool knock, away output bool open = false
initial state Shut if 3 knock do open = true go to Open if away go to Away
state Away if !away go to Shut
state Open}Show a hint
if 3 knock counts the ticks in which knock holds. Which trigger holds only in the first tick of each knock?
Show a solution
scchart Knock { input bool knock, away output bool open = false
initial state Shut if 3 knock && !pre(knock) do open = true go to Open if away go to Away
state Away if !away go to Shut
state Open}Check your understanding
Section titled “Check your understanding”Show the answer
Answer A: 0, the default for an int
In tick 1 there is no previous tick, and pre gives the default of the type, not the initial value.
Tank above shows it: before is 0 in tick 1 and 50 in tick 2.
Show the answer
Answer B: !b && pre(b)
!b && pre(b) means “false now, true a tick ago”: a falling edge. b && !pre(b) is the rising edge,
and !pre(b) is true in every tick after a tick in which b was false.
Show the answer
Answer B: 3
Every entry of Shut restarts the count, so the two earlier knocks are forgotten: it takes three more,
as ticks 7, 9 and 10 of the simulation show. (With an immediate count delay it can be different, as
the quirk above shows.)
Key ideas
pre(x)is the valuexhad at the end of the previous tick. Writes in the current tick don’t change it.- In tick 1, and when a superstate is entered again for its own locals,
pre(x)is the default of the type (0,false), not the initial value. b && !pre(b)is a rising edge and!b && pre(b)a falling edge: each is true for exactly one tick. Trigger on the edge when one press should do one thing.pre(S)asks whether a signal was present in the previous tick;pre(val(V))is a valued signal’s previous value.if n Tfires in the n-th tick in whichTholds since the state was entered. Onlyimmediatecounts the entry tick, and every entry restarts the count. Inside a state with a reset self-loop KIELER rejects count delays: count by hand there.
Next, Why the compiler says no, the last chapter of Learn: the checks behind KIELER’s scheduling errors, and how to change a chart it refuses.