Back to components

State Machines

Finite state machines with transition conditions, timed states, and output logic. Complex control flow made declarative.

FSM Transitions Timed States Output Logic

FSM Basics

A Finite State Machine (FSM) is a sequential circuit that moves between a fixed set of states based on input conditions. At any given time, the machine is in exactly one state. On each clock edge, it evaluates transition conditions and either moves to a new state or remains in the current one. Each state can drive specific output values, making FSMs ideal for control logic: protocol handlers, bus arbiters, instruction sequencers, and peripheral controllers.

An FSM consists of three elements: states (the set of possible configurations), transitions (rules that determine which state comes next), and outputs (signals driven based on the current state and optionally the inputs). RHDL makes all three explicit and declarative, so the hardware intent is clear from the code.

ElementDescription
StatesNamed configurations the machine can be in. One state is designated as the initial/reset state.
TransitionsConditional edges between states, evaluated on each clock cycle. Guarded by when/unless conditions.
OutputsSignals driven by the current state. Can be Moore-style (state only) or Mealy-style (state + inputs).
Timed statesStates that automatically transition after a specified number of clock cycles, using a built-in counter.
ResetReturns the machine to its initial state on synchronous reset assertion.

Defining State Machines

The state_machine DSL in RHDL lets you define an FSM declaratively. You list the states, their outputs, and their transitions. The framework handles state encoding, the state register, and the next-state logic automatically.

basic_fsm.rb
class BasicFSM < RHDL::Sim::Component
  input  :clk, :rst
  input  :start, :done
  output :busy, :ready

  state_machine clock: :clk,
                reset: :rst,
                initial: :idle do

    state :idle do
      ready <= 1
      busy  <= 0
      transition to: :running, when: start
    end

    state :running do
      ready <= 0
      busy  <= 1
      transition to: :complete, when: done
    end

    state :complete do
      ready <= 0
      busy  <= 0
      transition to: :idle  # unconditional
    end
  end
end

Transition Conditions

Transitions can be guarded with when and unless conditions. Multiple transitions from the same state are evaluated in priority order (first match wins). You can also specify multi-state transitions where the same condition leads to different target states based on additional logic.

Priority-based transitions with when/unless guards. The first matching condition determines the next state. If no condition matches, the machine stays in its current state.

guarded_transitions.rb
state :fetch do
  # Output: request memory read
  mem_rd <= 1
  addr   <= pc

  # Priority transitions (first match wins)
  transition to: :halt,
    when: halt_req
  transition to: :interrupt,
    when: irq & irq_en
  transition to: :decode,
    when: mem_ready
  # Stay in fetch if memory not ready
end

state :execute do
  # Conditional next state based on opcode
  transition to: :mem_access,
    when: is_load | is_store
  transition to: :branch_resolve,
    when: is_branch
  transition to: :writeback,
    unless: is_load | is_store | is_branch
end

Timed states use a built-in counter to automatically transition after a specified number of clock cycles. This is useful for wait states, timing protocols, and debouncing.

timed_states.rb
state :warmup, ticks: 100 do
  # Drive outputs for 100 clock cycles
  power_good <= 0
  led        <= blink_pattern

  # Auto-transitions after 100 ticks
  transition to: :ready
end

state :debounce, ticks: 50_000 do
  # Wait 1ms at 50MHz for button debounce
  btn_valid <= 0

  # Early exit if button released
  transition to: :idle,
    unless: btn_raw
  # After timeout, confirm press
  transition to: :pressed
end

state :pressed do
  btn_valid <= 1
  transition to: :idle,
    unless: btn_raw
end

Practical Example: Traffic Light Controller

A traffic light controller is a classic FSM application. It cycles through green, yellow, and red phases for two directions, using timed states to hold each phase for the correct duration. A pedestrian crossing button can interrupt the normal cycle. This example targets a 50 MHz clock, using tick counts to derive real-world timing.

traffic_light.rb
class TrafficLight < RHDL::Sim::Component
  # Timing constants (50 MHz clock)
  GREEN_TICKS  = 750_000_000  # 15 seconds
  YELLOW_TICKS = 150_000_000  # 3 seconds
  RED_TICKS    = 100_000_000  # 2 second all-red overlap
  PED_TICKS    = 500_000_000  # 10 seconds pedestrian

  input  :clk, :rst
  input  :ped_request
  # North-South lights
  output :ns_red, :ns_yellow, :ns_green
  # East-West lights
  output :ew_red, :ew_yellow, :ew_green
  # Pedestrian signal
  output :ped_walk, :ped_stop

  state_machine clock: :clk,
                reset: :rst,
                initial: :ns_green do

    # === North-South green phase ===
    state :ns_green, ticks: GREEN_TICKS do
      ns_green <= 1; ns_yellow <= 0; ns_red <= 0
      ew_green <= 0; ew_yellow <= 0; ew_red <= 1
      ped_walk <= 0; ped_stop  <= 1

      transition to: :ns_yellow
    end

    state :ns_yellow, ticks: YELLOW_TICKS do
      ns_green <= 0; ns_yellow <= 1; ns_red <= 0
      ew_green <= 0; ew_yellow <= 0; ew_red <= 1
      ped_walk <= 0; ped_stop  <= 1

      transition to: :all_red_1
    end

    # === Safety overlap: all red ===
    state :all_red_1, ticks: RED_TICKS do
      ns_green <= 0; ns_yellow <= 0; ns_red <= 1
      ew_green <= 0; ew_yellow <= 0; ew_red <= 1
      ped_walk <= 0; ped_stop  <= 1

      # Check for pedestrian request
      transition to: :ped_phase, when: ped_request
      transition to: :ew_green
    end

    # === East-West green phase ===
    state :ew_green, ticks: GREEN_TICKS do
      ns_green <= 0; ns_yellow <= 0; ns_red <= 1
      ew_green <= 1; ew_yellow <= 0; ew_red <= 0
      ped_walk <= 0; ped_stop  <= 1

      transition to: :ew_yellow
    end

    state :ew_yellow, ticks: YELLOW_TICKS do
      ns_green <= 0; ns_yellow <= 0; ns_red <= 1
      ew_green <= 0; ew_yellow <= 1; ew_red <= 0
      ped_walk <= 0; ped_stop  <= 1

      transition to: :all_red_2
    end

    state :all_red_2, ticks: RED_TICKS do
      ns_green <= 0; ns_yellow <= 0; ns_red <= 1
      ew_green <= 0; ew_yellow <= 0; ew_red <= 1
      ped_walk <= 0; ped_stop  <= 1

      transition to: :ped_phase, when: ped_request
      transition to: :ns_green
    end

    # === Pedestrian crossing phase ===
    state :ped_phase, ticks: PED_TICKS do
      ns_green <= 0; ns_yellow <= 0; ns_red <= 1
      ew_green <= 0; ew_yellow <= 0; ew_red <= 1
      ped_walk <= 1; ped_stop  <= 0

      transition to: :ns_green
    end
  end
end