## 状态机(State machine)
### 一、简介(Introduction)
在SpinalHDL中用户可以像在VHDL/Verilog中一样定义状态机, 通过使用计数和Swith/Case语句。但是在SpinalHDL中也可以使用专用的语法。
下方的状态机可以被如下代码实现:

格式A:
```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))
}
}
}
```
格式B:
```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))
}
}
```
Verilog:
```Verilog
module MyTopLevel (
input io_cond0,
input io_cond1,
output io_flag,
output [7:0] io_state,
output reg io_result,
input clk,
input reset
);
localparam fsm_enumDef_BOOT = 2'd0;
localparam fsm_enumDef_stateA = 2'd1;
localparam fsm_enumDef_stateB = 2'd2;
localparam fsm_enumDef_stateC = 2'd3;
wire fsm_wantExit;
reg fsm_wantStart;
wire fsm_wantKill;
reg [7:0] fsm_counter;
reg [7:0] counter;
reg [1:0] fsm_stateReg;
reg [1:0] fsm_stateNext;
wire _zz_when_StateMachine_l233;
wire _zz_when_StateMachine_l233_1;
wire when_MyTopLevel_l54;
wire when_StateMachine_l233;
wire when_StateMachine_l249;
`ifndef SYNTHESIS
reg [47:0] fsm_stateReg_string;
reg [47:0] fsm_stateNext_string;
`endif
`ifndef SYNTHESIS
always @(*) begin
case(fsm_stateReg)
fsm_enumDef_BOOT : fsm_stateReg_string = "BOOT ";
fsm_enumDef_stateA : fsm_stateReg_string = "stateA";
fsm_enumDef_stateB : fsm_stateReg_string = "stateB";
fsm_enumDef_stateC : fsm_stateReg_string = "stateC";
default : fsm_stateReg_string = "??????";
endcase
end
always @(*) begin
case(fsm_stateNext)
fsm_enumDef_BOOT : fsm_stateNext_string = "BOOT ";
fsm_enumDef_stateA : fsm_stateNext_string = "stateA";
fsm_enumDef_stateB : fsm_stateNext_string = "stateB";
fsm_enumDef_stateC : fsm_stateNext_string = "stateC";
default : fsm_stateNext_string = "??????";
endcase
end
`endif
assign fsm_wantExit = 1'b0;
always @(*) begin
fsm_wantStart = 1'b0;
case(fsm_stateReg)
fsm_enumDef_stateA : begin
end
fsm_enumDef_stateB : begin
end
fsm_enumDef_stateC : begin
end
default : begin
fsm_wantStart = 1'b1;
end
endcase
end
assign fsm_wantKill = 1'b0;
always @(*) begin
io_result = 1'b0;
if(when_StateMachine_l233) begin
io_result = 1'b1;
end
end
assign _zz_when_StateMachine_l233 = (fsm_stateReg == fsm_enumDef_stateB);
assign _zz_when_StateMachine_l233_1 = (fsm_stateNext == fsm_enumDef_stateB);
always @(*) begin
fsm_stateNext = fsm_stateReg;
case(fsm_stateReg)
fsm_enumDef_stateA : begin
fsm_stateNext = fsm_enumDef_stateB;
end
fsm_enumDef_stateB : begin
if(when_MyTopLevel_l54) begin
fsm_stateNext = fsm_enumDef_stateC;
end
end
fsm_enumDef_stateC : begin
fsm_stateNext = fsm_enumDef_stateA;
end
default : begin
end
endcase
if(fsm_wantStart) begin
fsm_stateNext = fsm_enumDef_stateA;
end
if(fsm_wantKill) begin
fsm_stateNext = fsm_enumDef_BOOT;
end
end
assign when_MyTopLevel_l54 = (fsm_counter == 8'h04);
assign when_StateMachine_l233 = (_zz_when_StateMachine_l233 && (! _zz_when_StateMachine_l233_1));
assign when_StateMachine_l249 = ((! _zz_when_StateMachine_l233) && _zz_when_StateMachine_l233_1);
always @(posedge clk or posedge reset) begin
if(reset) begin
fsm_counter <= 8'h0;
fsm_stateReg <= fsm_enumDef_BOOT;
end else begin
fsm_stateReg <= fsm_stateNext;
case(fsm_stateReg)
fsm_enumDef_stateA : begin
end
fsm_enumDef_stateB : begin
fsm_counter <= (fsm_counter + 8'h01);
end
fsm_enumDef_stateC : begin
end
default : begin
end
endcase
if(when_StateMachine_l249) begin
fsm_counter <= 8'h0; //这会产生有优先级的选择器,该情况优先级更高
end
end
end
endmodule
```
### 二、状态机(StateMachine)
`StateMachine`是基本类(class), 它管理FSM的逻辑。
```Scala
val myFsm = new StateMachine {
// 定义状态
}
```
`StateMachine`还提供了一些访问器(accessors):
| 命名 | 返回类型 | 描述 |
| :---------------: | :------: | :----------------------------------: |
| isActive(state) | Bool | 当状态机处于所给定的状态时返回true |
| isEntering(state) | Bool | 当状态机要进入所给定的状态时返回true |
1. 入口点(Entry point)
通过扩展EntryPoint特征, 可以将状态定义为状态机的入口点:
```Scala
val stateA = new State with EntryPoint
```
或者通过使用`val stateA = new State with EntryPoint`:
```Scala
val stateA = new State
setEntry(stateA)
```
2. 转换(Transitions)
+ 转换由`goto(nextState)`表示, 它使得状态机在下一个周期转换到`nextState`。
+ `exit()`将状态机在下一个周期转换为boot状态(boot state)(或者, 在`StateFsm`中, 退出当前嵌套状态机)。
### 三、状态(States)
多种状态可以被使用:
+ `State` (最基本的)
+ `StateDelay`
+ `StateFsm`
+ `StateParallelFsm`
它们每个都提供以下函数来定义与它们相关的逻辑:
| 命名 | 描述 |
| :-----------------------------------------------: | :----------------------------------------------------------------------------: |
| state.onEntry {
`` yourStatements``
} | `yourStatements`在状态机未处于`state`时应用, 并将在下一个周期处于`state` |
| state.onExit {
`` yourStatements``
} | `yourStatements`在状态机处于`state`时应用, 并将在下一个周期处于其他状态 |
| state.whenIsActive {
`` yourStatements``
} | `yourStatements`在状态机处于`state`时应用 |
| state.whenIsNext {
`` yourStatements``
} | `yourStatements`在状态机将在下一个周期处于`state`时执行(即使已经是`state`状态) |
``state.`隐含在一个`new State`块中:

```Scala
val stateB : State = new State {
onEntry(counter := 0)
whenIsActive {
counter := counter + 1
when(counter === 4) {
goto(stateC)
}
}
onExit(io.result := True)
}
```
1. 状态延迟(StateDelay)
`StateDelay`允许创建一个状态, 该状态在`whenCompleted{…}`语句执行之前等待固定数量的周期。首选的使用方法是:
```Scala
val stateG : State = new StateDelay(cyclesCount=40) {
whenCompleted {
goto(stateH)
}
}
```
同样可以用一行代码来写:
```Scala
val stateG : State = new StateDelay(40) { whenCompleted(goto(stateH)) }
```
2. 状态中的有限状态机(StateFsm)
`StateFsm`允许描述包含嵌套状态机的状态。当嵌套状态机完成(退出)时, `whenCompleted{…}`执行。
例如:
```Scala
// 内部的FSM按如下定义
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
}
}
}
```
在上面的示例中, `exit()`使状态机跳转到boot状态(内部隐藏状态)。这将通知`StateFsm`其内部状态机已完成。
3. 状态中的并行有限状态机(StateParallelFsm)
`StateParallelFsm`允许处理多个嵌套状态机。当所有嵌套状态机完成时, `whenCompleted{…}`执行。
例如:
```Scala
val stateD = new StateParallelFsm (internalFsmA(), internalFsmB()) {
whenCompleted{
goto(stateE)
}
}
```
4. 关于入口状态的注释(Notes about the entry state)
上面定义的进入状态的方式使得在重置复位和第一个时钟采样之间, 状态机处于启动状态。只有在第一次时钟采样之后, 定义的进入状态才有效。这允许正确地进入entry状态(应`onEntry`中的语句), 并允许嵌套状态机。
虽然它很有用, 但也可以绕过该特性, 直接让状态机引导(boot)到用户状态。
为此, 使用`makeInstantEntry()`而不是定义一个`new State`。这个函数返回boot状态, 在复位后直接激活。
> **注意:该状态的`onEntry`只会在它从另一个状态转换到这个状态时调用, 而不会在启动期间调用。**
> **注意:在仿真过程中, boot状态总是命名为`Boot`.**
例如:
```Scala
// 状态序列: IDLE, STATE_A, STATE_B, ...
val fsm = new StateMachine {
// IDLE在仿真中被命名为BOOT
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))
}
// 状态序列 : 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))
}
```