## 状态机(State machine) ### 一、简介(Introduction) 在SpinalHDL中用户可以像在VHDL/Verilog中一样定义状态机, 通过使用计数和Swith/Case语句。但是在SpinalHDL中也可以使用专用的语法。 下方的状态机可以被如下代码实现: ![fsm_simple](../../img/fsm_simple.svg) 格式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`块中: ![fsm_stateb](../../img/fsm_stateb.svg) ```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)) } ```