一、有限状态机FSM(Finite State Machine)
- 组成元素:
输入、状态、状态转移条件、输出。
- 可以分为两类:
Mealy状态机:时序逻辑的输出不仅取决于当前状态,还与输入有关;
Moore状态机:时序逻辑的输出只与当前状态有关。
- 描述方式:
① 状态转移图:设计分析时使用,工具自动翻译的代码效率不高,适合规模小的设计;对于大规模设计,HDL更好;
② 状态转移表;
③ HDL描述。
- 设计步骤:
① 逻辑抽象,得到状态转移图:确定输入、输出、状态变量、画状态转移图;
② 状态简化,得到最简的状态转移图:合并等价状态(它们的输入相同,转换到的次态和输出也相同的两个或两个以上的状态);
③ 状态编码:binary、gray、one-hot编码方式;
④ 用HDL描述。
二、Coding Style
- 一段式:
只有一个always block,把所有的逻辑(输入、输出、状态)都在一个always block的时序逻辑中实现。这种写法看起来很简洁,但是不利于维护,如果状态复杂一些就很容易出错,不推荐这种方法。
在简单的状态机可以使用。
- 二段式:
有两个always block,把时序逻辑和组合逻辑分隔开来。时序逻辑里进行当前状态和下一状态的切换,组合逻辑实现各个输入、输出以及状态判断。这种写法不仅便于阅读、理解、维护,而且利于综合器优化代码,利于用户添加合适的时序约束条件,利于布局布线器实现设计。在两段式描述中,当前状态的输出用组合逻辑实现,可能存在竞争和冒险,产生毛刺。
要求对状态机的输出用寄存器打一拍,但很多情况不允许插入寄存器节拍,此时使用三段式描述。其优势在于能够根据状态转移规律,在上一状态根据输入条件判断出当前状态的输出,从而不需要额外插入时钟节拍。
- 三段式:
有三个always block,一个时序逻辑采用同步时序的方式描述状态转移,一个采用组合逻辑的方式判断状态转移条件、描述状态转移规律,第三个模块使用同步时序的方式描述每个状态的输出。代码容易维护,时序逻辑的输出解决了两段式组合逻辑的毛刺问题,但是从资源消耗的角度上看,三段式的资源消耗多一些。
模板如下:
always @ ( posedge clk or negedge rst_n ) begin if ( !rst_n ) CS <= IDLE; else CS <= NS; end always @* begin NS = 'b0; //初始化寄存器,避免生成latch case (CS) //注意为CS IDLE: begin end; S1: begin end; default: NS = 'b0; //与硬件电路一致 endcase end always @ (posedge clk or negedge rst_n) begin if(!rst_n) begin end else begin case (CS/NS) //这里有2种写法,推荐NS写法(moore型写法) ... default: ; endcase end end
下面,举例说明:
状态转换图如下所示:
- 一段式:
//时序逻辑电路always @(posedge clk or negedge rst_n)beginif(!rst_n)begincstate <= IDLE;cmd <= 3'b000;endelsecase(cstate)IDLE:if(wr_req)begincstate <= WR_S1;cmd <= 3'b001;endelse if(rd_req)begincstate <= RD_S1;cmd <= 3'b011;endelse begincstate <= IDLE;cmd <= 3'b000;endWR_S1: begincstate <= WR_S2;cmd <= 3'b010;endWR_S2: begincstate <= IDLE;cmd <= 3'b000;endRD_S1:if(wr_req)begincstate <= WR_S2;cmd <= 3'b010;endelse begincstate <= RD_S2;cmd <= 3'b100;endRD_S2:if(wr_req) begincstate <= WR_S1;cmd <= 3'b001;endelse begincstate <= IDLE;cmd <= 3'b000;enddefault:cstate <= IDLE;endcaseend
testbench如下:
`timescale 1 ns/ 100 psmodule fsm1_vlg_tst();reg clk;reg rd_req;reg rst_n;reg wr_req; wire [2:0] cmd;wire [2:0] cstate;fsm1 i1 ( .clk(clk),.cmd(cmd),.cstate(cstate),.rd_req(rd_req),.rst_n(rst_n),.wr_req(wr_req));always #10 clk = ~clk;initial beginclk = 0;rst_n = 1;wr_req = 0;rd_req = 0;#2 rst_n = 0;#10 rst_n = 1;repeat(100)begin#20 wr_req = {$random}%2;rd_req = {$random}%2;end#100 $stop;end endmodule
功能仿真波形图:
三段式:
always block①:时序逻辑
//1st always block, sequential logic, store current state
always @(posedge clk or negedge rst_n)if(!rst_n)cstate <= IDLE;elsecstate <= nstate;
always block②:组合逻辑
//2nd always block, combinational logic, decide next state
always @(cstate or wr_req or rd_req)beginnstate = 3'b0;case(cstate)IDLE:if(wr_req)nstate = WR_S1;else if(rd_req)nstate = RD_S1;elsenstate = IDLE;WR_S1:nstate = WR_S2;WR_S2:nstate = IDLE;RD_S1:if(wr_req)nstate = WR_S2;elsenstate = RD_S2;RD_S2:if(wr_req)nstate = WR_S1;elsenstate = IDLE;default:nstate = 3'b0;endcaseend
- 注意:
always @(敏感电平信号)需要列举完全,可以用“@*”或者“@(*)”代替;
初始值设置避免组合逻辑条件不全生成latch,但不一定符合设计。
case(表达式)中的表达式为“cstate”,即现态;
阻塞赋值“=”;
default项必须设置,与实际电路一致。
always block③:时序逻辑
//3rd always block, FSM sequential output
- Mealy型写法:
always @(posedge clk or negedge rst_n)if(!rst_n)cmd <= 3'b000;elsecase(cstate)IDLE:if(wr_req)cmd <= 3'b001;else if(rd_req)cmd <= 3'b011;elsecmd <= 3'b000;WR_S1:cmd <= 3'b010;WR_S2:cmd <= 3'b000;RD_S1:if(wr_req)cmd <= 3'b010;elsecmd <= 3'b100;RD_S2:if(wr_req)cmd <= 3'b001;elsecmd <= 3'b000;default:;endcase
- 注意:
case(表达式)中的表达式为“cstate”,即现态;
非阻塞赋值“<=”;
default项必须设置。
- Moore型写法:
always @(posedge clk or negedge rst_n)if(!rst_n)cmd <= 3'b000;elsecase(nstate)IDLE: cmd <= 3'b000;WR_S1: cmd <= 3'b001;WR_S2: cmd <= 3'b010;RD_S1: cmd <= 3'b011;RD_S2: cmd <= 3'b100;default:;endcaseendmodule
- 注意:
case(表达式)中的表达式为“nstate”,即次态,这里使用nextstate和state的区别在于,当状态跳转时,基于nextstate的输 出是立刻变化的,而基于state输出会延迟一个周期,其他情况都一样,应该根据自己的时序要求,选择用nextstate还是state。
非阻塞赋值“<=”;
default项必须设置。
参考状态机详解,以及三段式状态机的思维陷阱。