Finite state machines with transition conditions, timed states, and output logic. Complex control flow made declarative.
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.
| Element | Description |
|---|---|
| States | Named configurations the machine can be in. One state is designated as the initial/reset state. |
| Transitions | Conditional edges between states, evaluated on each clock cycle. Guarded by when/unless conditions. |
| Outputs | Signals driven by the current state. Can be Moore-style (state only) or Mealy-style (state + inputs). |
| Timed states | States that automatically transition after a specified number of clock cycles, using a built-in counter. |
| Reset | Returns the machine to its initial state on synchronous reset assertion. |
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.
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
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.
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.
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
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.
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