三段式状态机
有限状态机(FiniteStateMachine, FSM),是由寄存器组合组合逻辑构成的硬件时序电路。FSM的状态只可能在同一时钟跳变沿的情况下才能从一个状态转向另一个状态。
根据状态机的输出是否与输入有关,可分为Moore型状态机和Mealy型状态机。Moore型状态机输出仅仅与现态有关,而Mealy型状态机不仅与现态有关,也与输入有关,所以会受到输入的干扰,可能会产生毛刺(Glith)的现象,所以我们通常使用的是Moore型状态机。
状态机的编码,二进制编码(Binary),格雷码编码(Gray-code),独热码(One-hot)。不同的编码方式是防止在状态转移中发生突变,使得状态转移更为稳定,系统更加可靠,对于用FPGA实现的FSM建议采用独热码。因为采用独热码可省下许多组合电路的使用,提高电路的速度和可靠性,且总的单元数并无显著增加。
写法
三段式状态机的基本格式是:
- 第一个always语句实现同步状态跳转;
- 第二个always语句采用组合逻辑判断状态转移条件;
- 第三个always语句描述状态输出(可以用组合电路输出,也可以时序电路输出)。
下面以一个7分频为例(对于分频等较简单的功能,可以不使用状态机,这里只是演示状态机编写的方法)
1、通过parameter来定义各个不同状态的参数,这里是使用独热码的方式来定义状态机,每个状态只有一位为1,当然也可以直接定义成十进制的0, 1, 2……7。因为我们定义成独热码的方式,每一个状态的位宽为7位,接下来还需要定义两个7位的寄存器,一个用来表示当前状态,另一个用来表示下一个状态,如下所示:
//独热码定义方式
parameter S0 = 7'b0000001;
parameter S1 = 7'b0000010;
parameter S2 = 7'b0000100;
parameter S3 = 7'b0001000;
parameter S4 = 7'b0010000;
parameter S5 = 7'b0100000;
parameter S6 = 7'b1000000;
//reg define
reg [6:0] curr_st ; //当前状态
reg [6:0] next_st ; //下一状态
2、使用三个always语句来开始编写状态机的代码,第一个always采用同步时序描述状态转移, 第二个always采用组合逻辑判断状态转移条件, 第三个always是描述状态输出。
//第一段采用同步时序描述状态转移
常用写法:
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n)
curr_st <= S0;
else
curr_st <= next_st;
end
另有写法:
parameter S0 = 1;
parameter S1 = 2;
……
parameter S6 = 7;
always @(posedge sys_clk or negedge sys_rst_n)
if (!sys_rst_n)
curr_st <= (1‘b1 << S0);
else
curr_st <= next_st;
//第二段采用组合逻辑判断状态转移条件
常用写法:
always @(*) begin
case (curr_st)
S0: next_st = S1;
S1: next_st = S2;
S2: next_st = S3;
S3: next_st = S4;
S4: next_st = S5;
S5: next_st = S6;
S6: next_st = S0;
default: next_st = S0;
endcase
end
另有写法:
always @(*) begin
next_st = curr_st;
case (1'b1)
curr_st[S0]:
next_st = 1'b1 << S1;
curr_st[S1]:
next_st = 1'b1 << S2;
curr_st[S2]:
next_st = 1'b1 << S3;
curr_st[S3]:
next_st = 1'b1 << S4;
curr_st[S4]:
next_st = 1'b1 << S5;
curr_st[S5]:
next_st = 1'b1 << S6;
curr_st[S6]:
next_st = 1'b1 << S0;
default:
next_st = 1'b1 << S0;
endcase
end
//第三段描述状态输出(这里采用时序电路输出)
对应常用写法:
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n)
clk_divide_7 <= 1'b0;
else if ((curr_st == S0) | (curr_st == S1) | (curr_st == S2) | (curr_st == S3))
clk_divide_7 <= 1'b0;
else if ((curr_st == S4) | (curr_st == S5) | (curr_st == S6))
clk_divide_7 <= 1'b1;
end
另有写法:
always @(posedge sys_clk or negedge sys_rst_n)
if (!sys_rst_n)
clk_divide_7 <= 1'b0;
else if (curr_st[S0] | curr_st[S1]) | curr_st[S2] | curr_st[S3])
clk_divide_7 <= 1'b0;
else if (curr_st[S4] | curr_st[S5]) | curr_st[S6])
clk_divide_7 <= 1'b1;
注:
- 第二个always采用组合逻辑判断状态转移条件,这里每一个状态只保持一个时钟周期,也就是直接跳转到下一个状态,在实际应用中,一般根据输入的条件来判断是否跳转到其它状态或者停留在当前转态。
- 状态机的第三段可以使用组合逻辑电路输出,也可以使用时序逻辑电路输出,一般推荐使用时序电路输出,因为状态机的设计和其它设计一样,最好使用同步时序方式设计,以提高设计的稳定性,消除毛刺。
- case(1‘b1)这种写法实则同 if else,状态过多时可能会对时序造成影响。