Skip to content

State machine: traffic light controller

This example models the traffic light controller of a two-road intersection as a pair of interacting Moore-type synchronous state machines.

A Moore machine's output is determined solely by its current state. Each module logs its light color on every state transition, and the only information exchanged between the two machines is a zero-width synchronization signal: who has the right of way.

What this example demonstrates:

  • Moore FSM modeling with enum state variables in decl
  • Inter-module synchronization via zero-width signal tokens
  • State-based timing with wait(N, 0)
  • Coordinated behavior across two concurrently running modules

Problem description

A main road and a side road share an intersection. The controller enforces the following cycle, with the main road getting priority (longer green phase):

Phase Duration
Main GREEN 15 cycles
Main YELLOW 3 cycles
Side GREEN 8 cycles
Side YELLOW 3 cycles

The side road stays RED while the main road is active. The two FSMs coordinate through handshake signals: Main tells Side when it has cleared the intersection, and Side acknowledges when it has done the same.


State diagrams

stateDiagram-v2
    direction LR
    [*] --> GREEN
    GREEN --> YELLOW : after 15 cycles
    YELLOW --> RED   : after 3 cycles
    RED --> GREEN    : side road done signal received

Main road FSM (starts GREEN)

stateDiagram-v2
    direction LR
    [*] --> RED
    RED --> GREEN    : main road release signal received
    GREEN --> YELLOW : after 8 cycles
    YELLOW --> RED   : after 3 cycles, send done signal to main

Side road FSM (starts RED)


Structure

The TrafficController module instantiates both FSMs and wires them together with two zero-width nets — one for each direction of the handshake.

module Top
    submodule ctrl : TrafficController
end module

module TrafficController
    submodule main : MainRoad
    submodule side : SideRoad

    // Signal nets: zero-width, capacity 1
    net n_main_release : capacity 1   // Main -> Side: "you may go"
    net n_side_release : capacity 1   // Side -> Main: "I am done"

    main.to_side  => n_main_release
    side.from_main <= n_main_release
    side.to_main  => n_side_release
    main.from_side <= n_side_release
end module
flowchart LR
    main["MainRoad\n(starts GREEN)"]
    side["SideRoad\n(starts RED)"]
    main -->|"n_main_release"| side
    side -->|"n_side_release"| main

Main road FSM

The module cycles through GREEN, YELLOW, and RED states using if blocks on a State enum. The wait(N, 0) inside each state produces the timed dwell. On entering RED, the module pushes a zero-width signal token to Side (blocking retry until the push succeeds), then suspends until it receives Side's acknowledgement.

module MainRoad
    outport to_side
    inport  from_side

    decl $
    // Moore FSM state — output is a function of this variable only
    enum State { GREEN, YELLOW, RED };
    State  state;
    token<> sig;   // zero-width signal token
    bool    ok;
    int     seq;   // counts completed GREEN-YELLOW-RED sequences
    $
    init $state = GREEN; seq = 0;$

    behavior
        do
            // ---- GREEN ----
            if (state == GREEN) then
                $log << endl << "[MAIN] GREEN  (main=GREEN  side=RED)";$;
                wait(15, 0);
                $state = YELLOW;$;
            end if;

            // ---- YELLOW ----
            if (state == YELLOW) then
                $log << endl << "[MAIN] YELLOW (main=YELLOW side=RED)";$;
                wait(3, 0);
                $state = RED;$;
            end if;

            // ---- RED ----
            if (state == RED) then
                $log << endl << "[MAIN] RED -- releasing side road";$;

                // Signal Side to proceed (phase 1 push)
                $ok = false;$;
                do
                    wait until (this_phase == 1);
                    $ok = to_side.push(sig);$;
                    if (not ok) then wait end if;
                while (not ok) end do;

                // Wait for Side to complete its sequence
                wait until (from_side.peek());
                $from_side.pull(sig);$;

                $seq++; state = GREEN;$;
                if (seq >= 3) then
                    stop simulation;
                end if;
            end if;
        while (1) end do;
    end behavior
end module

Moore property

The log statement at the entry of each state represents the Moore output: the light color depends only on the value of state, with no reference to any input signal.


Side road FSM

Side starts in RED and blocks on wait until (from_main.peek()). Once Main releases it, Side completes its GREEN-YELLOW-RED cycle and replies with its own signal token.

module SideRoad
    inport  from_main
    outport to_main

    decl $
    enum State { RED, GREEN, YELLOW };
    State  state;
    token<> sig;
    bool    ok;
    $
    init $state = RED;$

    behavior
        do
            // ---- RED: wait for main to release ----
            if (state == RED) then
                $log << endl << "[SIDE] RED -- waiting for main";$;
                wait until (from_main.peek());
                $from_main.pull(sig); state = GREEN;$;
            end if;

            // ---- GREEN ----
            if (state == GREEN) then
                $log << endl << "[SIDE] GREEN  (main=RED  side=GREEN)";$;
                wait(8, 0);
                $state = YELLOW;$;
            end if;

            // ---- YELLOW ----
            if (state == YELLOW) then
                $log << endl << "[SIDE] YELLOW (main=RED  side=YELLOW)";$;
                wait(3, 0);
                $state = RED;$;

                // Signal Main that we are done
                $ok = false;$;
                do
                    wait until (this_phase == 1);
                    $ok = to_main.push(sig);$;
                    if (not ok) then wait end if;
                while (not ok) end do;
            end if;
        while (1) end do;
    end behavior
end module

Expected output

(0,0)  TOP.ctrl.main : [MAIN] GREEN  (main=GREEN  side=RED)
(15,0) TOP.ctrl.main : [MAIN] YELLOW (main=YELLOW side=RED)
(18,0) TOP.ctrl.main : [MAIN] RED -- releasing side road
(19,0) TOP.ctrl.side : [SIDE] RED -- waiting for main
(19,0) TOP.ctrl.side : [SIDE] GREEN  (main=RED  side=GREEN)
(27,0) TOP.ctrl.side : [SIDE] YELLOW (main=RED  side=YELLOW)
(30,0) TOP.ctrl.side : [SIDE] RED -- releasing main
(31,0) TOP.ctrl.main : [MAIN] GREEN  (main=GREEN  side=RED)
...
Simulation stopped at time (93,...)

The simulation runs for three complete main-road sequences (controlled by seq >= 3 in MainRoad). One complete cycle — Main green through Side red — spans 15 + 3 + 8 + 3 = 29 cycles plus one cycle of handshake overhead.

Extending the model

To add a third road or a pedestrian crossing phase, introduce additional FSM modules and extend the handshake chain. Each FSM remains purely Moore: only its own state determines its output, and all coordination is via explicit signal tokens.