You're reading the documentation for a development version.
For the latest stable release version, please have a look at master.

State machine

Introduction

In SpinalHDL you can define your state machine like in VHDL/Verilog, by using enumerations and switch/case statements. But in SpinalHDL you can also use a dedicated syntax.

The state machine below is implemented in the following examples:

../../_images/fsm_simple.svg

Style A:

import spinal.lib.fsm._

class TopLevel extends Component {
  val io = new Bundle {
    val result = out Bool()
  }

  val fsm = new StateMachine {
    val counter = Reg(UInt(8 bits)) init (0)
    io.result := False

    val stateA : State = new State with EntryPoint {
      whenIsActive(goto(stateB))
    }
    val stateB : State = new State {
      onEntry(counter := 0)
      whenIsActive {
        counter := counter + 1
        when(counter === 4) {
          goto(stateC)
        }
      }
      onExit(io.result := True)
    }
    val stateC : State = new State {
      whenIsActive(goto(stateA))
    }
  }
}

Style B:

import spinal.lib.fsm._

class TopLevel extends Component {
  val io = new Bundle {
    val result = out Bool()
  }

  val fsm = new StateMachine{
    val stateA = new State with EntryPoint
    val stateB = new State
    val stateC = new State

    val counter = Reg(UInt(8 bits)) init (0)
    io.result := False

    stateA
      .whenIsActive(goto(stateB))

    stateB
      .onEntry(counter := 0)
      .whenIsActive {
        counter := counter + 1
        when(counter === 4) {
          goto(stateC)
        }
      }
      .onExit(io.result := True)

    stateC
      .whenIsActive(goto(stateA))
  }
}

StateMachine

StateMachine is the base class. It manages the logic of the FSM.

val myFsm = new StateMachine {
  // Definition of states
}

StateMachine also provides some accessors:

Name

Return

Description

isActive(state)

Bool

Returns True when the state machine is in the given state

isEntering(state)

Bool

Returns True when the state machine is entering the given state

Entry point

A state can be defined as the entry point of the state machine by extending the EntryPoint trait:

val stateA = new State with EntryPoint

Or by using setEntry(state):

val stateA = new State
setEntry(stateA)

Transitions

  • Transitions are represented by goto(nextState), which schedules the state machine to be in nextState the next cycle.

  • exit() schedules the state machine to be in the boot state the next cycle (or, in StateFsm, to exit the current nested state machine).

These two functions can be used inside state definitions (see below) or using always { yourStatements }, which always applies yourStatements, with a priority over states.

States

Multiple kinds of states can be used:

  • State (the base one)

  • StateDelay

  • StateFsm

  • StateParallelFsm

Each of them provides the following functions to define the logic associated to them:

Name

Description

state.onEntry {
``  yourStatements``
}

yourStatements is applied when the state machine is not in state and will be in state the next cycle

state.onExit {
``  yourStatements``
}

yourStatements is applied when the state machine is in state and will be in another state the next cycle

state.whenIsActive {
``  yourStatements``
}

yourStatements is applied when the state machine is in state

state.whenIsNext {
``  yourStatements``
}

yourStatements is executed when the state machine will be in state the next cycle (even if it is already in it)

state. is implicit in a new State block:

../../_images/fsm_stateb.svg
val stateB : State = new State {
  onEntry(counter := 0)
  whenIsActive {
    counter := counter + 1
    when(counter === 4) {
      goto(stateC)
    }
  }
  onExit(io.result := True)
}

StateDelay

StateDelay allows to create a state which waits for a fixed number of cycles before executing statments in whenCompleted {...}. The preferred way to use it is:

val stateG : State = new StateDelay(cyclesCount=40) {
  whenCompleted {
    goto(stateH)
  }
}

It can also be written in one line:

val stateG : State = new StateDelay(40) { whenCompleted(goto(stateH)) }

StateFsm

StateFsm allow to describe a state containing a nested state machine. When the nested state machine is done (exited), statments in whenCompleted { ... } are executed.

There is an example of StateFsm definition :

// internalFsm is a function defined below
val stateC = new StateFsm(fsm=internalFsm()) {
  whenCompleted {
    goto(stateD)
  }
}

def internalFsm() = new StateMachine {
  val counter = Reg(UInt(8 bits)) init (0)

  val stateA : State = new State with EntryPoint {
    whenIsActive {
      goto(stateB)
    }
  }

  val stateB : State = new State {
    onEntry (counter := 0)
    whenIsActive {
      when(counter === 4) {
        exit()
      }
      counter := counter + 1
    }
  }
}

In the example above, exit() makes the state machine jump to the boot state (a internal hidden state). This notifies StateFsm about the completion of the inner state machine.

StateParallelFsm

StateParallelFsm allows to handle multiple nested state machines. When all nested state machine are done, statments in whenCompleted { ... } are executed.

Example:

val stateD = new StateParallelFsm (internalFsmA(), internalFsmB()) {
  whenCompleted{
    goto(stateE)
  }
}

Notes about the entry state

The way the entry state has been defined above makes it so that between the reset and the first clock sampling, the state machine is in a boot state. It is only after the first clock sampling that the defined entry state becomes active. This allows to properly enter the entry state (applying statements in onEntry), and allows nested state machines.

While it is usefull, it is also possible to bypass that feature and directly having a state machine booting into a user state.

To do so, use makeInstantEntry() instead of defining a new State. This function returns the boot state, active directly after reset.

Note

The onEntry of that state will only be called when it transitions from another state to this state and not during boot.

Note

During simulation, the boot state is always named BOOT.

Example:

// State sequance: IDLE, STATE_A, STATE_B, ...
val fsm = new StateMachine {
  // IDLE is named BOOT in simulation
  val IDLE = makeInstantEntry()
  val STATE_A, STATE_B, STATE_C = new State

  IDLE.whenIsActive(goto(STATE_A))
  STATE_A.whenIsActive(goto(STATE_B))
  STATE_B.whenIsActive(goto(STATE_C))
  STATE_C.whenIsActive(goto(STATE_B))
}
//  State sequance : BOOT, IDLE, STATE_A, STATE_B, ...
val fsm = new StateMachine {
  val IDLE, STATE_A, STATE_B, STATE_C = new State
  setEntry(IDLE)

  IDLE.whenIsActive(goto(STATE_A))
  STATE_A.whenIsActive(goto(STATE_B))
  STATE_B.whenIsActive(goto(STATE_C))
  STATE_C.whenIsActive(goto(STATE_B))
}