.. role:: raw-html-m2r(raw)
:format: html
.. _state_machine:
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:
.. image:: /asset/picture/fsm_simple.svg
:align: center
:width: 300
Style A:
.. code-block:: scala
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:
.. code-block:: scala
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.
.. code-block:: scala
val myFsm = new StateMachine {
// Definition of states
}
``StateMachine`` also provides some accessors:
.. list-table::
:header-rows: 1
:widths: 1 1 5
* - 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:
.. code-block:: scala
val stateA = new State with EntryPoint
Or by using ``setEntry(state)``:
.. code-block:: scala
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:
.. list-table::
:header-rows: 1
:widths: 1 10
* - 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:
.. image:: /asset/picture/fsm_stateb.svg
:align: center
:width: 300
.. code-block:: scala
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:
.. code-block:: scala
val stateG : State = new StateDelay(cyclesCount=40) {
whenCompleted {
goto(stateH)
}
}
It can also be written in one line:
.. code-block:: scala
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 :
.. code-block:: scala
// 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:
.. code-block:: scala
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:
.. code-block:: scala
// 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))
}
.. code-block:: scala
// 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))
}