一段式、二段式、三段式状态机是按照书写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变化相同。
三段式状态机
第三段为基于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完全相同,仿真结果如下:


第三段为基于state的时序逻辑
由于第三段使用的是组合逻辑,因此比较容易出现毛刺,那么很通常的一个想法是将第三个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,却以为他们的逻辑是一样的,从而导致在逻辑上出现问题。
第三段为基于next_state的时序逻辑
在二段式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的组合逻辑的时候。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/55129.html