一段式、二段式、三段式状态机是按照书写FSM时使用的always块数目进行划分的,一般而言对于简单的状态机,可以使用一段式,其代码量以及使用资源都最少,但如果状态机较复杂,一段式状态机会对代码维护产生很大的不便,因此多使用便于维护的三段式状态机。下面对几种状态机进行介绍。
一段式FSM在一个时序always块中完成所有的状态转移以及输出工作,使用非阻塞赋值,如以下代码
//一段式状态机
module FSM1(
input clk,
input rst_n,
input [3:0] i,
output reg [3:0] o
);
parameter S1 = 4'b0001;
parameter S2 = 4'b0010;
parameter S3 = 4'b0100;
parameter S4 = 4'b1000;
reg [3:0] state;
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
state <= S1;
o <= 0;
end
else begin
case(state)
S1: begin
state <= S2;
o <= i + 1;
end
S2: begin
state <= S3;
o <= i + 2;
end
S3: begin
state <= S4;
o <= i + 3;
end
S4: begin
state <= S1;
o <= i + 4;
end
default: begin
state <= S1;
o <= 0;
end
endcase
end
end
endmodule
其仿真结果如下
二段式状态机使用两个always块,实现组合逻辑与时序逻辑的分开,其中第一个always块为时序逻辑,控制状态的更新,第二个always块为组合逻辑,产生下一状态next_state,同时产生状态输出。其输出是基于当前状态state的组合逻辑,代码如下
//二段式状态机
module FSM2(
input clk,
input rst_n,
input [3:0] i,
output reg [3:0] o
);
parameter S1 = 4'b0001;
parameter S2 = 4'b0010;
parameter S3 = 4'b0100;
parameter S4 = 4'b1000;
reg [3:0] state;
reg [3:0] next_state;
always @(posedge clk or negedge rst_n)begin //时序逻辑
if(!rst_n)begin
state <= S1;
end
else begin
state <= next_state;
end
end
always @(*) begin //组合逻辑,产生next_state和output
if(!rst_n) begin
next_state <= S1;
o <= 0;
end
else begin
case(state)
S1: begin
next_state <= S2;
o <= i + 1;
end
S2: begin
next_state <= S3;
o <= i + 2;
end
S3: begin
next_state <= S4;
o <= i + 3;
end
S4: begin
next_state <= S1;
o <= i + 4;
end
default: begin
next_state <= S1;
o <= 0;
end
endcase
end
end
endmodule
仿真结果如下
可以看到,其输出与一段式FSM相同,而在状态上,其next_state与一段式FSM的state变化相同。
我们可以看到,二段式FSM的第二个always块实际上是可以再进行划分的,可以分为生成next_state、产生输出两部分,因此我们直接拆分二段式FSM,获得逻辑输出基于当前状态state的三段式状态机:
//三段式状态机
module FSM3(
input clk,
input rst_n,
input [3:0] i,
output reg [3:0] o
);
parameter S1 = 4'b0001;
parameter S2 = 4'b0010;
parameter S3 = 4'b0100;
parameter S4 = 4'b1000;
reg [3:0] state;
reg [3:0] next_state;
always @(posedge clk or negedge rst_n)begin //时序逻辑
if(!rst_n)begin
state <= S1;
end
else begin
state <= next_state;
end
end
always @(*) begin //组合逻辑,产生next_state
if(!rst_n) begin
next_state <= S1;
end
else begin
case(state)
S1: begin
next_state <= S2;
end
S2: begin
next_state <= S3;
end
S3: begin
next_state <= S4;
end
S4: begin
next_state <= S1;
end
default: begin
next_state <= S1;
end
endcase
end
end
always @(*) begin //组合逻辑,基于state产生逻辑输出
if(!rst_n) begin
o <= 0;
end
else begin
case(state)
S1: begin
o <= i + 1;
end
S2: begin
o <= i + 2;
end
S3: begin
o <= i + 3;
end
S4: begin
o <= i + 4;
end
default: begin
o <= 0;
end
endcase
end
end
endmodule
由于此三段式FSM仅仅是在写法上将二段式FSM的第二个always块进行了拆分,因此其状态变化以及输出与二段式FSM完全相同,仿真结果如下:
由于第三段使用的是组合逻辑,因此比较容易出现毛刺,那么很通常的一个想法是将第三个always块变为时序逻辑,通过插入寄存器,实现对毛刺的消除。然而直接将 always(*) 改为 always(posedge clk or negedge rst_n) 就可以了吗?请看下面的代码
//三段式状态机2
module FSM3_2(
input clk,
input rst_n,
input [3:0] i,
output reg [3:0] o
);
parameter S1 = 4'b0001;
parameter S2 = 4'b0010;
parameter S3 = 4'b0100;
parameter S4 = 4'b1000;
reg [3:0] state;
reg [3:0] next_state;
always @(posedge clk or negedge rst_n)begin //时序逻辑
if(!rst_n)begin
state <= S1;
end
else begin
state <= next_state;
end
end
always @(*) begin //组合逻辑,产生next_state
if(!rst_n) begin
next_state <= S1;
end
else begin
case(state)
S1: begin
next_state <= S2;
end
S2: begin
next_state <= S3;
end
S3: begin
next_state <= S4;
end
S4: begin
next_state <= S1;
end
default: begin
next_state <= S1;
end
endcase
end
end
always @(posedge clk or negedge rst_n) begin //时序逻辑
if(!rst_n) begin
o <= 0;
end
else begin
case(state) //基于当前状态state产生同步时序输出,其输出将出现一拍延迟
S1: begin
o <= i + 1;
end
S2: begin
o <= i + 2;
end
S3: begin
o <= i + 3;
end
S4: begin
o <= i + 4;
end
default: begin
o <= 0;
end
endcase
end
end
endmodule
可以看到,其输出将相较于第三段使用组合逻辑的FSM延迟一拍,有些同学在书写三段式FSM时没有注意到这一点,直接将一段式FSM重构为这种三段式FSM,却以为他们的逻辑是一样的,从而导致在逻辑上出现问题。
在二段式FSM的讨论中我们提到过,其next_state的变化才是和一段式FSM相同的,因此在将第三段转换为同步时序逻辑时,不应基于当前状态state,而是应当基于下一状态next_state,代码如下
//三段式状态机3
module FSM3_3(
input clk,
input rst_n,
input [3:0] i,
output reg [3:0] o
);
parameter S1 = 4'b0001;
parameter S2 = 4'b0010;
parameter S3 = 4'b0100;
parameter S4 = 4'b1000;
reg [3:0] state;
reg [3:0] next_state;
always @(posedge clk or negedge rst_n)begin //时序逻辑
if(!rst_n)begin
state <= S1;
end
else begin
state <= next_state;
end
end
always @(*) begin //组合逻辑
if(!rst_n) begin
next_state <= S1;
end
else begin
case(state)
S1: begin
next_state <= S2;
end
S2: begin
next_state <= S3;
end
S3: begin
next_state <= S4;
end
S4: begin
next_state <= S1;
end
default: begin
next_state <= S1;
end
endcase
end
end
always @(posedge clk or negedge rst_n) begin //同步时序逻辑
if(!rst_n) begin
o <= 0;
end
else begin
case(next_state) //使用next_state,此时其输出与一段式FSM相同
S1: begin
o <= i + 1;
end
S2: begin
o <= i + 2;
end
S3: begin
o <= i + 3;
end
S4: begin
o <= i + 4;
end
default: begin
o <= 0;
end
endcase
end
end
endmodule
可以看到,此时三段式FSM的输出与一段式FSM相同。
这里我们发现一个有意思的地方,使用next_state产生输出可以输出提前一拍,正是这提前的一拍将同步时序滞后的一拍给抵消了。那如果我们在第三段使用组合逻辑,但也基于next_state进行输出,就可以将输出提前一拍!
有小伙伴可能会疑惑关于状态的定义部分,为什么要定义为独热的形式?
parameter S1 = 4'b0001;
parameter S2 = 4'b0010;
parameter S3 = 4'b0100;
parameter S4 = 4'b1000;
因为这样,由任一状态向其他状态转换时,其出现的不稳定状态都是无效的,这样可以提高系统的稳定性。比如state由S1向S2转换时,可能出现0011或者0000这两种情况,而这两种都是无效状态,很容易辨识剔除。而如果我们使用下面的定义方法
parameter S1 = 4'b0000;
parameter S2 = 4'b0001;
parameter S3 = 4'b0010;
parameter S4 = 4'b0011;
当从状态S2向S3转换时,可能出现的0011或者0000,分别对应状态S4、S1,此时将可能导致逻辑错误,尤其是输出是关于state的组合逻辑的时候。