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:
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 |
---|---|---|
|
|
Returns |
|
|
Returns |
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 innextState
the next cycle.exit()
schedules the state machine to be in the boot state the next cycle (or, inStateFsm
, 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.
State encoding
By default the FSM state vector will be encoded using the native encoding of the language/tools the RTL is generated for (Verilog or VHDL).
This default can be overridden by using the setEncoding(...)
method which either takes a SpinalEnumEncoding
or
varargs of type (State, BigInt)
for a custom encoding.
val fsm = new StateMachine {
setEncoding(binaryOneHot)
...
}
val fsm = new StateMachine {
val stateA = new State with EntryPoint
val stateB = new State
...
setEncoding((stateA -> 0x23), (stateB -> 0x22))
}
Warning
When using the graySequential
enum encoding, no check is done to verify that the FSM transitions only produce
single-bit changes in the state vector. The encoding is done according to the order of state definitions and the
designer must ensure that only valid transitions are done if needed.
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
}
|
|
state.onExit {
yourStatements
}
|
|
state.whenIsActive {
yourStatements
}
|
|
state.whenIsNext {
yourStatements
}
|
|
state.
is implicit in a new State
block:
val stateB : State = new State {
onEntry(counter := 0)
whenIsActive {
counter := counter + 1
when(counter === 4) {
goto(stateC)
}
}
onExit(io.result := True)
}
StateDelay
StateDelay
allows you to create a state which waits for a fixed number of cycles before executing statements 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
allows you to describe a state containing a nested state machine. When the nested state machine is done (exited), statements 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 you to handle multiple nested state machines. When all nested state machine are done, statements 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 useful, 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 sequence : 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))
}
Notes about using state value
In cases that users want to retrieve the state value for purpose, where state value could be accessed by stateReg. However, the stateReg is not initialized during elaboration of state machine, so any access of stateReg directly could cause error. Use the postBuild method as below can solve this problem.
// After or inside the fsm's definition.
fsm.postBuild{
io.status := fsm.stateReg.asBits //io.status is the signal user want to assigned to.
}