Skip to content

Arrays and for regions

Arrays of inputs and outputs, and for regions, which make one concurrent copy of a region for every index.

A car with four seats has four seatbelt sensors, and the logic for each seat is the same: wait until that belt is buckled. You could write four identical regions, one per seat, and four more the day the car gets a fifth seat. A for region writes the region once and lets KIELER make the copies, one per index, each with its own constant i.

For regions are almost always used with arrays, so this chapter starts with a short reminder of how arrays work.

Variables, types and expressions introduced arrays. The parts that matter here:

const int SEATS = 4
input bool buckled[SEATS]
output bool lamp[SEATS] = {true, true, true, true}
  • T name[n] declares an array of n elements, numbered from 0 to n − 1. The size must be an integer literal or a const int with a literal value; anything else is rejected with

    Array cardinalities must be an int literal or a reference to a constant int object.
  • Elements start at their type’s default (false, 0) unless a vector such as {true, true, true, true} gives initial values. A shorter vector sets only the first elements.

  • a[2] reads or writes one element. In the simulator, an array input has one value per element.

  • A vector assignment a = {x, y, z} assigns the elements one after another, in index order. Copying a whole array with b = a does not compile (the C compiler reports assignment to expression with array type).

Between concurrent regions, elements with different constant indices count as different variables: two regions writing lamp[0] and lamp[1] do not conflict. An element whose index is a variable, lamp[k], counts as all of them at once.

Here are the seatbelts. Checking holds one region, Seat, written once with for i : 0 to 3:

SeatbeltsSimulation

Diagram

Diagram of SeatbeltsStates: Checking (initial, superstate), Open (initial), Fastened (final), Ready (superstate). Transitions: Open to Fastened: buckled[i]; Checking to Ready.Seatbeltsinput boolbuckled[4]output boolchime = trueCheckingSeat|i [ 0, 3 ]OpenFastenedbuckled[i]Readyentry/ chime = false

Code

scchart Seatbelts {
input bool buckled[4]
output bool chime = true
initial state Checking {
region Seat for i : 0 to 3 {
initial state Open
if buckled[i] go to Fastened
final state Fastened
}
}
join to Ready
state Ready {
entry do chime = false
}
}
Tick 0 of 6
Inputs for tick 1
Timing diagram of Seatbelts
Tickinput bool buckledoutput bool chimeActive state of the chartActive state of region Seat (in Checking), in the copy with i = 0Active state of region Seat (in Checking), in the copy with i = 1Active state of region Seat (in Checking), in the copy with i = 2Active state of region Seat (in Checking), in the copy with i = 3
Click a tick to show it, or use the Left and Right arrow keys to go back and step.
Guided tour, 6 ticks Press Next to run tick 1, or Take over to set the inputs yourself.

Guided tour, 6 ticks:

  1. buckled = true,false,false,falseTick 1 enters Checking and four copies of the region Seat, with i = 0, 1, 2 and 3. Seat 0 is already buckled, but the transitions are delayed, so nothing moves yet.
  2. The copy with i = 0 sees buckled[0] and goes to Fastened. The other three keep waiting.
  3. buckled = true,true,false,trueSeats 1 and 3 are buckled: their copies finish too, in the same tick. Only seat 2 is still open.
  4. buckled = false,false,false,falseEveryone unbuckles, but the finished copies stay in Fastened: this simple chart only waits for each belt once.
  5. buckled = false,false,true,falseSeat 2 is buckled. Now every copy is in its final state, so Checking terminates and join to Ready fires in the same tick: the chime stops.
  6. In Ready.
Each copy of Seat waits for its own belt. In tick 5 the last belt finishes the last copy, and the join fires in the same tick.

KIELER replaces the for region by four ordinary regions, one for each value of i from 0 to 3. In each copy, i is a constant: the first copy tests buckled[0], the second buckled[1], and so on. The copies run concurrently, like regions you had written out by hand, and join to Ready waits until every copy has reached its final state. The diagram draws the region once, labelled with the counter and its range: Seat | i [0, 3].

In the simulation, the diagram marks a state as active when any copy is in it, and a small badge says how many copies are in it: in tick 3, Open carries ×1 and Fastened ×3. The timing diagram has a lane for each copy, Seat i=0 to Seat i=3, so you can follow every copy on its own: Seat i=2 is the one that stays in Open until tick 5.

This chart only waits for each belt once: unbuckling in tick 4 changes nothing. A real seatbelt warning would also have to notice a belt being opened again.

A for region is written region Name for i : a to b { ... }, or without a name, region for i : a to b { ... }. KIELER makes one copy of the region for every value of i from a to b, both ends included:

  • In each copy, i is a constant with that copy’s value. It is replaced by the number itself, so it is not a variable you can watch in the simulator, and assigning to it is an error.
  • If a is greater than b, the copies count down: for i : 3 to 1 gives i = 3, 2, 1. for i : 5 to 5 gives one copy.
  • All copies are concurrent regions of the same state, and each has the region’s name.
  • A join to on a state with for regions waits until every copy, and every other region, is in a final state.

The second form runs over the indices of an array: region for i : buckled is the same as region for i : 0 to 3 when buckled has four elements. Its size may be a literal or a const, so changing SEATS changes the number of copies:

SeatLampsSimulation

Diagram

Diagram of SeatLampsStates: Open (initial), Fastened (final). Transitions: Open to Fastened: buckled[i] / lamp[i] = false; fastened++.SeatLampsconst intSEATS = 4input boolbuckled[SEATS]output boollamp[SEATS] = {true, true, true, true}output intfastened = 0Seat|i [ 0, 3 ]OpenFastenedbuckled[i]/ lamp[i] = false;fastened++

Code

scchart SeatLamps {
const int SEATS = 4
input bool buckled[SEATS]
output bool lamp[SEATS] = {true, true, true, true}
output int fastened = 0
region Seat for i : buckled {
initial state Open
if buckled[i] do lamp[i] = false; fastened++ go to Fastened
final state Fastened
}
}
Tick 0 of 5
Inputs for tick 1
Timing diagram of SeatLamps
Tickinput bool buckledoutput bool lampoutput int fastenedActive state of region Seat, in the copy with i = 0Active state of region Seat, in the copy with i = 1Active state of region Seat, in the copy with i = 2Active state of region Seat, in the copy with i = 3
Click a tick to show it, or use the Left and Right arrow keys to go back and step.
Guided tour, 5 ticks Press Next to run tick 1, or Take over to set the inputs yourself.

Guided tour, 5 ticks:

  1. buckled = false,false,false,falsefor i : buckled makes one copy per element of buckled: SEATS is 4, so i = 0 to 3. All four lamps are on.
  2. buckled = true,false,true,falseSeats 0 and 2: their copies switch off their own lamp and both add 1 to fastened. Concurrent ++ on one variable is fine; the copies write different elements of lamp, so they do not conflict.
  3. buckled = false,false,false,falseNothing new.
  4. buckled = true,true,true,trueSeats 1 and 3 finish (fastened = 4). Every region of the chart is now final, so the chart itself terminates.
  5. A terminated chart does nothing more.
Seats 0 and 2 in tick 2: each copy switches off its own lamp[i], and both add to fastened in the same tick.

Each copy writes its own element, lamp[0] to lamp[3], so the copies never conflict over lamp, and fastened++ from several copies in one tick is fine: concurrent increments add up (see Why the compiler says no). Without a join to around it, the copies are regions of the chart itself, and when the last one finishes in tick 4, the whole chart terminates.

Concurrent regions have no order: data decides what runs first. Copies of a for region are no different. As long as their writes do not depend on order (++, +=, writes to their own element), you never see an order. If they do, KIELER may still accept the chart, and the order it picks is not the one you would guess:

CopyOrderSimulation

Diagram

Diagram of CopyOrderStates: W (initial), D (final), W (initial), D (final), W (initial), D (final). Transitions: W to D: / log = log * 10 + 9; W to D: / log = log * 10 + i; W to D: / log = log * 10 + 7.CopyOrderoutput intlog = 0FirstWD/ log = log * 10 + 9R|i [ 3, 1 ]WD/ log = log * 10 + iLastWD/ log = log * 10 + 7

Code

scchart CopyOrder {
output int log = 0
region First {
initial state W
immediate do log = log * 10 + 9 go to D
final state D
}
region R for i : 3 to 1 {
initial state W
immediate do log = log * 10 + i go to D
final state D
}
region Last {
initial state W
immediate do log = log * 10 + 7 go to D
final state D
}
}
Tick 0 of 1
Timing diagram of CopyOrder
Tickoutput int logActive state of region FirstActive state of region R, in the copy with i = 3Active state of region R, in the copy with i = 2Active state of region R, in the copy with i = 1Active state of region Last
Click a tick to show it, or use the Left and Right arrow keys to go back and step.
Guided tour, 1 tick Press Next to run tick 1, or Take over to set the inputs yourself.

Guided tour, 1 tick:

  1. Every region runs its immediate transition in tick 1, and each one multiplies log by 10 and adds a digit. KIELER runs these concurrent updates in region order: First (9), Last (7), then the copies of R for i = 3, 2, 1. So log = 97321: the copies come after all the other regions.
3 warnings: This assignment reads log, so the compiler treats it as an update of log
  • Warning, line 6, column 20KIELER says: This assignment reads log, so the compiler treats it as an update of log (a relative write, as in the deprecated old SC syntax). Write it as log *= 10; log += 9 to say so. In one region it means the same. It only matters when another region also writes log in the same tick: this assignment then runs after that write instead of conflicting with it.Write it as log *= 10; log += 9: in one region it means the same, and it says that it is an update.About this message
  • Warning, line 11, column 20KIELER says: This assignment reads log, so the compiler treats it as an update of log (a relative write, as in the deprecated old SC syntax). Write it as log *= 10; log += i to say so. In one region it means the same. It only matters when another region also writes log in the same tick: this assignment then runs after that write instead of conflicting with it.Write it as log *= 10; log += i: in one region it means the same, and it says that it is an update.About this message
  • Warning, line 16, column 20KIELER says: This assignment reads log, so the compiler treats it as an update of log (a relative write, as in the deprecated old SC syntax). Write it as log *= 10; log += 7 to say so. In one region it means the same. It only matters when another region also writes log in the same tick: this assignment then runs after that write instead of conflicting with it.Write it as log *= 10; log += 7: in one region it means the same, and it says that it is an update.About this message
In tick 1 three regions append a digit to log: First (9), the copies of R for i = 3, 2, 1, written in the middle, and Last (7). The result is 97321: the copies run after all the other regions.

Each region runs log = log * 10 + digit, which KIELER treats as an update of log. Official KIELER warns

This assignment looks like old relative write SC syntax and is considered deprecated.

and SCCharts Lab says what it means:

This assignment reads log, so the compiler treats it as an update of log (a relative write, as in the deprecated old SC syntax).

KIELER runs the updates of a tick in region order, with the copies after all the other regions of the state. The timing diagram cannot show that order: it lists the lanes as the regions are written, First, the copies, then Last. Don’t write concurrent updates whose result depends on the order.

KIELER expands a for region by copying it, and it is strict about what it can copy. Keep for regions flat and simple.

Two more things to avoid: assigning to the counter (i = 5) is rejected with Cannot replace left hand side of assignment by a value., and dataflow for i : ... is accepted by the editor but cannot be compiled.

Be careful where a for region’s variables are declared. A declaration at the top of the for region belongs to the enclosing state, like any region’s declarations, so all copies share one k. If each copy writes its own value into it in the same tick, KIELER rejects the chart as a write–write conflict:

ForSharedLocalChart

Diagram

Diagram of ForSharedLocalStates: W (initial), D (final). Transitions: W to D: / k = i; sum += k.ForSharedLocaloutput intsum = 0|i [ 1, 3 ]intk = 0WD/ k = i;sum += k

Code

scchart ForSharedLocal {
output int sum = 0
region for i : 1 to 3 {
int k = 0
initial state W
immediate do k = i; sum += k go to D
final state D
}
}

KIELER rejects this chart

  • Line 6, column 20The copies of the for-region assign different values to k, which they shareKIELER says: The SCG is NOT asc-schedulable!KIELER makes one copy of the for-region for each value of i, but a variable declared in the region is declared once, outside the copies: they all write the same k in the same tick. Declare k in a state inside the region (initial state W { int k = 0 ... }), or use an update such as k += 1.About this message
int k is declared once, for all three copies, and each copy assigns a different value to it in tick 1.

A variable each copy should have for itself goes inside a state of the for region. Then every copy gets its own, and its initial value may use the counter:

ForLocalPerCopySimulation

Diagram

Diagram of ForLocalPerCopyStates: W (initial, superstate), X (initial), Y (final), D (final). Transitions: X to Y: / sum += k; W to D.ForLocalPerCopyoutput intsum = 0|i [ 1, 3 ]Wintk = i * 2XY/ sum += kD

Code

scchart ForLocalPerCopy {
output int sum = 0
region for i : 1 to 3 {
initial state W {
int k = i * 2
initial state X
immediate do sum += k go to Y
final state Y
}
join to D
final state D
}
}
Tick 0 of 1
Timing diagram of ForLocalPerCopy
Tickoutput int sumActive state of the chart, in the copy with i = 1Active state of the region of W, in the copy with i = 1Active state of the chart, in the copy with i = 2Active state of the region of W, in the copy with i = 2Active state of the chart, in the copy with i = 3Active state of the region of W, in the copy with i = 3
Click a tick to show it, or use the Left and Right arrow keys to go back and step.
Guided tour, 1 tick Press Next to run tick 1, or Take over to set the inputs yourself.

Guided tour, 1 tick:

  1. Declared inside a state of the for region, k exists once per copy: 2 + 4 + 6 = 12. Every region of the chart finishes in tick 1, so the chart terminates.
Each copy’s W declares its own k = i * 2: the copies add 2, 4 and 6, and sum is 12 after tick 1.

At the top of the for region, an initial value that uses the counter (int k = i) crashes the compiler the same way as nested for regions (SCCharts Lab’s message; no answer at all in the KIELER VS Code extension).

Turn the keys

LaunchKeys launches when all three keys have been turned, in any order. It has one hand-written region per key. Replace the three regions with a single for region, so that giving key a fourth element is the only change needed for a fourth key. The behaviour must stay exactly the same, so the playground’s comparison with the model solution matches even before you start: use it to check that your for region still behaves like the three regions.

Show the starter chart
scchart LaunchKeys {
input bool key[3]
output bool launch = false
initial state Arming {
region Key0 {
initial state Off
if key[0] go to Turned
final state Turned
}
region Key1 {
initial state Off
if key[1] go to Turned
final state Turned
}
region Key2 {
initial state Off
if key[2] go to Turned
final state Turned
}
}
join to Launch
state Launch {
entry do launch = true
}
}
Show a solution
scchart LaunchKeys {
input bool key[3]
output bool launch = false
initial state Arming {
region Key for i : key {
initial state Off
if key[i] go to Turned
final state Turned
}
}
join to Launch
state Launch {
entry do launch = true
}
}
How many copies does region R for i : 2 to 5 make, and which values does i take?
Show the answer

Answer B: Four: 2, 3, 4 and 5

Both ends are included, so i takes the values 2, 3, 4 and 5, one copy each. In each copy i is that constant.

Which of these does KIELER compile?
Show the answer

Answer D: region for i : A where A is declared input int A[N] with const int N = 3

The range form needs integer literals, so 0 to N is rejected, but the array form accepts an array whose size is a literal const. Nesting crashes the compiler, and the counter is a constant that cannot be assigned.

Key ideas

  • region R for i : a to b makes one concurrent copy of R for each i from a to b, both included; in each copy, i is a constant.
  • region R for i : A runs over the indices of array A, from 0 to its size minus 1.
  • Copies that write their own array element, or add to a shared counter, never conflict.
  • Copies are concurrent, like hand-written regions: don’t write concurrent updates whose result depends on their order.
  • Bounds must be integer literals (use the array form to size by a const), for regions cannot be nested, and a variable each copy needs for itself is declared inside a state of the copy.

Next, Dataflow regions: equations that are worked out again in every tick.

Report a mistake on this page