数字电子技术中,时序逻辑电路的描述方法和一段式状态机是非常类似的,并没有引入次态这个变量,而是在clk上升沿到来之时检测现态和输入,判断后,更新现态和输出,也有一些数字电路的输出和时序无关,其是输入和现态的组合逻辑。一段式状态机是当clk上升沿上升沿到来之时检测现态和输入,更新现态和输出,这种状态机输出和现态均只是在clk上升沿到来之后才会更新,而在两个上升沿之间是保持不变的。
一段式状态机的源文件代码和测试文件代码如下:
//一段式状态机
module Fsm(
input clk,
input rst_n,
input x,
output reg [2:0] state
);
//parameter define
parameter S0=3'd0;
parameter S1=3'd1;
parameter S2=3'd2;
parameter S3=3'd3;
parameter S4=3'd4;
//*****************************************************
//** main code
//*****************************************************
//一段式状态机
always @ (posedge clk or negedge rst_n)
begin
if(!rst_n)
state<=S0;
else
begin
case(state)
S0:
begin
if(x==1)
state<=S0;
else
state<=S1;
end
S1:
begin
if(x==1)
state<=S2;
else
state<=S1;
end
S2:
begin
if(x==1)
state<=S3;
else
state<=S1;
end
S3:
begin
if(x==1)
state<=S0;
else
state<=S4;
end
S4:
begin
if(x==1)
state<=S2;
else
state<=S1;
end
default: state<=S0; //在state处于未知状态下启动状态机
endcase
end
end
endmodule
`timescale 1 ns/ 1 ns
module Fsm_vlg_tst(); //Modelsim是对仿真文件进行仿真
wire [2:0] state;
reg clk,rst_n,x;
Fsm u0_Fsm(
.clk(clk),
.rst_n(rst_n),
.x(x),
.state(state)
);
initial // fork join是并行运行的 begin end是顺序执行的
fork
clk=0;
#10 clk=1;
#20 clk=0;
#30 clk=1;
#40 clk=0;
#50 clk=1;
#60 clk=0;
#70 clk=1;
#80 clk=0;
#90 clk=1;
#100 clk=0;
#110 clk=1;
#120 clk=0;
#130 clk=1;
#140 clk=0;
#150 clk=1;
#160 clk=0;
rst_n=0;
#15 rst_n=1;
x=0;
#20 x=1;
#40 x=0;
#60 x=1;
#80 x=0;
#100 x=1;
#120 x=0;
#140 x=1;
#160 x=0;
join
endmodule
二段式状态机的特色就是将时序逻辑和组合逻辑分成了两个always块,一个always块用于更新现态,这是时序逻辑,使用非阻塞性赋值,其仅在clk上升沿到来时进行;另一个用于更新次态,这是组合逻辑,使用阻塞性赋值,当输入和现态变化时,次态立刻更新。二段式的状态转移和一段式完全一样,只不过二段式是在当现态和输入变化时就更新次态,所以二段式有可能在一个时钟周期内多次更新次态,但是现态仅在clk上升沿到来时进行更新。但是二段式的输出和一段式不同,二段式的输出和次态一样,是实时更新的,而不是仅在clk上升沿到来时进行更新。
二段式状态机的源文件代码如下:
// 二段式状态机
module Fsm(
input clk,
input rst_n,
input x,
output reg [2:0] state,
output reg [2:0] next_state
);
//parameter define
parameter S0=3'd0;
parameter S1=3'd1;
parameter S2=3'd2;
parameter S3=3'd3;
parameter S4=3'd4;
//*****************************************************
//** main code
//*****************************************************
//CLK上升沿到来时更新现态(时序逻辑)
always @ (posedge clk or negedge rst_n)
begin
if(!rst_n)
state<=S0;
else
state<=next_state;
end
//当现态和输入发生变化时更新次态(组合逻辑)
always @ (state or x)
begin
if(!rst_n)
next_state=S0;
else
begin
case(state)
S0:
begin
if(x==1)
next_state=S0;
else
next_state=S1;
end
S1:
begin
if(x==1)
next_state=S2;
else
next_state=S1;
end
S2:
begin
if(x==1)
next_state=S3;
else
next_state=S1;
end
S3:
begin
if(x==1)
next_state=S0;
else
next_state=S4;
end
S4:
begin
if(x==1)
next_state=S2;
else
next_state=S1;
end
default: next_state=S0; //在state处于未知状态下启动状态机
endcase
end
end
endmodule
二段式状态机的仿真时序图如下:
可以发现,二段式状态机中state(现态)的变化和一段式是完全一致的,next_state(次态)是输入和现态的组合逻辑函数,并不是时序逻辑,其在一个时钟周期可能变化多次。
三段式状态机和两段式状态机的区别仅仅是将输出量单独拿出来,目前有多种写法,一种是将输出量写成时序逻辑,当clk上升沿到来时,检测现态和输入,更新输出量,这样和一段式状态机其实完全一样;还有一种是将输出量写成组合逻辑,当现态或输入变化时更新输出量,这样和二段式状态机完全一致。此处不再赘述。
之前教科书一致推荐使用两段式或者三段式,理由大多是便于优化代码,便于布局布线,但是随着技术发展,这个问题日渐消失,而且一段式状态机更加直观,适合初学者。具体比较可以参考一下博客三段式状态机的思维陷阱。