时序逻辑电路——有限状态机FSM
参考
[1]状态机,从细节出发(一段式、两段式、三段式,moore型、mealy型)
状态机的优势
有限状态机(Finite State Machine, FSM)在硬件设计中十分常见,其稳定性高、速度快、面积小也更容易维护。
状态机的类型
状态机分成Moore状态机和Mealy状态机。
(1)Moore型状态机:输出信号只取决于当前状态。
(2)Mealy型状态机:输出信号不仅取决于当前状态,还取决于输入信号的值。
它们的区别就在于输出信号是否与输入信号有关,造成的结果是:
实现相同功能时,Moore型状态机需要比Mealy型状态机多一个状态,且Moore型状态机的输出比Mealy型延后一个时钟周期。
对于Moore状态机来说,优势在于
- 时序稳定性:输出仅由当前状态决定,在时钟边沿同步更新,避免输入信号毛刺导致的输出抖动。
- 设计简单:输出逻辑与输入解耦,代码结构清晰(如三段式状态机中输出独立于转移逻辑)。
- 同步性:适合对输出稳定性要求高的场景(如控制信号生成、显示驱动)。
劣势在于 - 延迟较高:输出必须等待时钟边沿更新,响应速度可能不足(如高速通信协议)。
- 状态数可能更多:若相同状态需根据输入产生不同输出,需拆分成多个状态,增加复杂度
对于Mealy状态机来说,优势在于 - 响应速度快:输出可随输入立即变化,无需等待时钟边沿,适合实时性要求高的场景。
- 状态数更少:因输出与输入相关,可能通过合并相似状态减少状态数量(例如用输入条件区分行为)。
- 资源占用低:在某些设计中,由于状态合并,寄存器资源消耗可能更少。
劣势在于 - 时序风险:输入信号的毛刺或异步变化可能导致输出不稳定(需额外同步逻辑)。
- 设计复杂度高:输出逻辑需同时处理状态和输入,代码调试难度增加。
- 时序收敛挑战:组合逻辑路径(输入→输出)可能成为关键路径,影响最大时钟频率。
状态机的描述方式
以下面状态转移图为例,对一段式、二段式以及三段式状态机进行描述。状态转移线上的0/0等表示的意思是过程中data/flag的值,其中data为输入,flag为输出,可以看出,该状态机伪Mealy状态机,因为输出和输出的data有关。
如果通过Moore状态机实现,会多一个状态,如下图所示。
1. 一段式
一段式状态机只有一个always块,既实现状态跳转又实现逻辑输出。这种写法看起来很简洁,但是不利于维护,如果状态复杂一些就很容易出错,不推荐这种方法。
module fsm1(
input wire clk ,
input wire rst ,
input wire data ,
output reg flag
);
//*************code***********//
parameter S0 = 'd0;
parameter S1 = 'd1;
parameter S2 = 'd2;
parameter S3 = 'd3;
reg [1:0] state;
//第一段,状态跳转+时序逻辑输出
always @(posedge clk or negedge rst)begin
if(!rst)begin
state <= S0;
flag <= 1'b0;
end
else begin
if(state==S0)begin
state <= (!data)?S0:S1;
flag <= 1'b0;
end
else if(state==S1)begin
state <= (!data)?S1:S2;
flag <= 1'b0;
end
else if(state==S2)begin
state <= (!data)?S2:S3;
flag <= 1'b0;
end
else if(state==S3)begin
state <= (!data)?S3:S0;
flag <= data? 1'b1:1'b0;
end
else begin
state <= S0;
flag <= 1'b0;
end
end
end
//*************code***********//
endmodule
2. 二段式
二段式状态机有两个always block,把时序逻辑和组合逻辑分隔开来。时序逻辑里进行当前状态和下一状态的切换,组合逻辑实现各个输入、输出以及状态判断。在两段式描述中,当前状态的输出用组合逻辑实现,可能存在竞争和冒险,产生毛刺,因此为了保险起见,二段式状态机实现会对输出多打一拍。二段式的输出是组合逻辑输出,因此在数字电路设计中,二段式状态机通常用于实现Moore型状态机。
module fsm2(
input wire clk ,
input wire rst ,
input wire data ,
output reg flag
);
//*************code***********//
parameter S0 = 'd0;
parameter S1 = 'd1;
parameter S2 = 'd2;
parameter S3 = 'd3;
parameter S4 = 'd4;
reg [2:0] curr_state;
reg [2:0] next_state;
//第一段,时序逻辑
always @(posedge clk or negedge rst)begin
if(!rst)
curr_state <= S0;
else
curr_state <= next_state;
end
//第二段,组合逻辑,状态跳转+输出
always @(*)begin
case(curr_state)
S0: begin
next_state = (!data)?S0:S1;
flag = 1'b0;
end
S1:begin
next_state = (!data)?S1:S2;
flag = 1'b0;
end
S2:begin
next_state = (!data)?S2:S3;
flag = 1'b0;
end
S3:begin
next_state = (!data)?S3:S4;
flag = 1'b0;
end
S4:begin
next_state = (!data)?S0:S1;
flag = 1'b1;
end
default:begin
next_state = S0;
flag = 1'b0;
end
endcase
end
//*************code***********//
endmodule
3. 三段式
有三个always block,一个时序逻辑采用同步时序的方式描述状态转移,一个采用组合逻辑的方式判断状态转移条件、描述状态转移规律,第三个模块使用同步时序的方式描述每个状态的输出。代码容易维护,时序逻辑的输出解决了两段式组合逻辑的毛刺问题,但是从资源消耗的角度上看,三段式的资源消耗多一些。
module fsm1(
input wire clk ,
input wire rst ,
input wire data ,
output reg flag
);
//*************code***********//
parameter S0 = 'd0;
parameter S1 = 'd1;
parameter S2 = 'd2;
parameter S3 = 'd3;
reg [1:0] curr_state;
reg [1:0] next_state;
//第一段
always @(posedge clk or negedge rst)begin
if(!rst)
curr_state <= S0;
else
curr_state <= next_state;
end
//第二段
always @(*)begin
case(curr_state)
S0 :next_state = (!data)?S0:S1;
S1 :next_state = (!data)?S1:S2;
S2 :next_state = (!data)?S2:S3;
S3 :next_state = (!data)?S3:S0;
default:next_state = S0;
endcase
end
//第三段,组合逻辑输出
always @(posedge clk or negedge rst)begin
if(!rst)
flag <= 1'b0;
else if(curr_state==S3 && data)
flag <= 1'b1;
else
flag <= 1'b0;
end
//*************code***********//
endmodule
总结
对于Moore状态机和Mealy状态机,需要根据实际应用场景自行选择。而状态机的描述方法,更倾向于使用三段式状态机,逻辑清晰且便于维护。