0%

中科院实习—— 知识补充

FPGA开发。包括偶数分频电路练习、阻塞与非阻塞、状态机。

偶数分频电路

module Clk_div(
    Clk,
    Reset_n,

    clk_div

    );
    input Clk;
    input Reset_n;
    output reg clk_div;
    
    parameter num_div = 4;
    
    reg [3:0]cnt;
    
    always@(posedge Clk or negedge Reset_n)
    if(!Reset_n)
        cnt <= 0;
    else if (cnt < (num_div/2 - 1))
        cnt <= cnt + 1'b1;
     else
        cnt <= 0;
        
    always@(posedge Clk or negedge Reset_n)
    if(!Reset_n)
        clk_div <= 0;
    else if (cnt < (num_div/2 - 1))
        clk_div <=  clk_div;
    else
        clk_div = !clk_div;
    
endmodule

==============================================================================

`timescale 1ns / 1ns


module Clk_div_tb();

    reg Clk;
    reg Reset_n;
    wire clk_div;
    
    Clk_div  
    #(
        .num_div(4)
        )
    Clk_div(
            .Clk(Clk),
            .Reset_n(Reset_n),
            .clk_div(clk_div)
            );
            
        initial Clk = 1;
        always #10 Clk = !Clk;
        
        initial begin
           Reset_n = 0;
           #201;
           Reset_n = 1;
           #2000000;
           $stop;
        end

    
    endmodule

重讲阻塞与非阻塞 转自link

非阻塞与阻塞

(1).非阻塞(Non_Blocking)赋值方式( 如 b <= a; )

  • 块结束后才完成赋值操作。
  • b的值并不是立刻就改变的。
  • 这是一种比较常用的赋值方法。(特别在编写可综合模块时)

(2).阻塞(Blocking)赋值方式( 如 b = a; )

  • 赋值语句执行完后,块才结束。
  • b的值在赋值语句执行完后立刻就改变的。
  • 可能会产生意想不到的结果。

非阻塞赋值方式和阻塞赋值方式的区别常给设计人员带来问题。问题主要是给”always”块内的reg型信号的赋值方式不易把握。

到目前为止,前面所举的例子中的”always”模块内的reg型信号都是采用下面的这种赋值方式:
b <= a;
这种方式的赋值并不是马上执行的,也就是说”always”块内的下一条语句执行后,b并不等于a,而是保持原来的值。”always”块结束后,才进行赋值。而另一种赋值方式阻塞赋值方式,如下所示:

 b = a;

这种赋值方式是马上执行的。也就是说执行下一条语句时,b已等于a。尽管这种方式看起来很直观,但是可能引起麻烦。下面举例说明:
[例1]

always @( posedge clk )
begin
    b<=a;
    c<=b;
end    

[例1] 中的”always”块中用了非阻塞赋值方式,定义了两个reg型信号b和c,clk信号的上升沿到来时,b就等于a,c就等于b,这里应该用到了两个触发器。请注意:赋值是在”always”块结束后执行的,c应为原来b的值。这个”always”块实际描述的电路功能如下图所示:
image.png

[例2]:

always @(posedge clk)
begin
    b=a;
    c=b;
end

[例2]中的 “always”块用了阻塞赋值方式。clk信号的上升沿到来时,将发生如下的变化:b马上取a的值,c马上取b的值(即等于a),生成的电路图如下所示只用了一个触发器来寄存器a的值,又输出给b和c。这大概不是设计者的初衷,如果采用[例1]所示的非阻塞赋值方式就可以避免这种错误。
image.png

深入理解阻塞和非阻塞赋值的不同

在描述组合逻辑的always 块中用阻塞赋值,则综合成组合逻辑的电路结构。
**在描述时序逻辑的always 块中用非阻塞赋值,则综合成时序逻辑的电路结构。 **
为什么一定要这样做呢?这是因为要使综合前仿真和综合后仿真一致的缘故。

首先了解两个定义:

RHS – 方程式右手方向的表达式或变量可分别缩写为: RHS 表达式或 RHS 变量。

LHS – 方程式左手方向的表达式或变量可分别缩写为: LHS 表达式或 LHS 变量。

阻塞赋值的执行可以认为是只有一个步骤的操作:

计算RHS 并更新LHS,此时不能允许有来自任何其他Verilog 语句的干扰。 所谓阻塞的概念是指在同一个always块中,其后面的赋值语句从概念上(即使不设定延迟)是在前一句赋值语句结束后再开始赋值的
[例1]用阻塞赋值的反馈振荡器(不好的例子)

module fbosc1 (y1, y2, clk, rst);
 output y1, y2; 
 input clk, rst;
 reg y1, y2;

 always @(posedge clk or posedge rst)
 begin
     if (rst) y1 = 0; // reset
     else y1 = y2;
 end

 always @(posedge clk or posedge rst)
 begin
     if (rst) y2 = 1; // preset
     else y2 = y1;
 end
endmodule

例1中,如果前一个always块的复位信号先到0 时刻,则y1 和y2 都会取1,而如果后一个always 块的复位信号先到0 时刻,则y1 和y2 都会取0。这清楚地说明这个Verilog 模块是不稳定的会产生冒险和竞争的情况
如果在一个过程块中阻塞赋值的RHS 变量正好是另一个过程块中阻塞赋值的LHS 变量,这两个过程块又用同一个时钟沿触发,如果阻塞赋值的次序安排不好,就会出现竞争。若这两个阻塞赋值操作用同一个时钟沿触发,则执行的次序是无法确定的。

非阻塞赋值的操作可以看作为两个步骤的过程:

  1. 在赋值时刻开始时,计算非阻塞赋值RHS 表达式。

  2. 在赋值时刻结束时,更新非阻塞赋值LHS 表达式。

[例2] 用非阻塞赋值的反馈振荡器(正确示范)

module fbosc2 (y1, y2, clk, rst);
 output y1, y2;
 input clk, rst;
 reg y1, y2;
 
 always @(posedge clk or posedge rst)
 begin
     if (rst) y1 <= 0; // reset
     else y1 <= y2;
 end

 always @(posedge clk or posedge rst)
 begin
     if (rst) y2 <= 1; // preset
     else y2 <= y1;
 end
endmodule

例2中,无论哪一个always 块的复位信号先到, 两个always 块中的非阻塞赋值都在赋值开始时刻计算RHS 表达式,而在结束时刻才更新LHS 表达式。所以这两个always 块在复位信号到来后,在always 块结束时 y1 为0 而y2为1 是确定的。从用户的角度看这两个非阻塞赋值正好是并行执行的

通过几个通过移位寄存器的例子来更好的理解阻塞赋值与非阻塞赋值

下图表示是一个简单的移位寄存器方框图:
image.png
[例3] 不正确地使用的阻塞赋值来描述移位寄存器。(方式 #1)

 module pipeb1 (q3, d, clk);
 output [7:0] q3;
 input [7:0] d;
 input clk;
 reg [7:0] q3, q2, q1;

 always @(posedge clk)
 begin
 q1 = d;
 q2 = q1;
 q3 = q2;
 end

endmodule

在上面的模块中,按顺序进行的阻塞赋值将使得在下一个时钟上升沿时刻,所有的寄存器输出值都等于输入值d。在每个时钟上升沿,输入值d 将无延时地直接输出到q3。

[例4] 用阻塞赋值来描述移位寄存器也是可行的,但这种风格并不好。(方式 #2)

  module pipeb2 (q3, d, clk);
 output [7:0] q3;
 input [7:0] d;
 input clk;
 reg [7:0] q3, q2, q1;

 always @(posedge clk)
 begin
 q3 = q2;
 q2 = q1;
 q1 = d;
 end

endmodule

在上面的模块中,阻塞赋值的次序是经过仔细安排的,以使仿真的结果与移位寄存器相一致。虽然该模块可被综合成移位寄存器,但我们不建议使用这种风格的模块来描述时序逻辑。

[例5] 不好的用阻塞赋值来描述移位时序逻辑的风格(方式 #3)

module pipeb3 (q3, d, clk);
 output [7:0] q3;
 input [7:0] d;
 input clk;
 reg [7:0] q3, q2, q1;

 always @(posedge clk) q1 = d;
 always @(posedge clk) q2 = q1;
 always @(posedge clk) q3 = q2;

endmodule

本例中,阻塞赋值分别被放在不同的always 块里。仿真时,这些块的先后顺序是随机的,因此可能会出现错误的结果。这是Verilog 中的竞争冒险。按不同的顺序执行这些块将导致不同的结果。但是, 这些代码的综合结果却是正确的流水线寄存器。也就是说,前仿真和后仿真结果可能会不一致。

[例6] 不好的用阻塞赋值来描述移位时序逻辑的风格(方式 #4)

module pipeb4 (q3, d, clk);
 output [7:0] q3;
 input [7:0] d;
 input clk;
 reg [7:0] q3, q2, q1;

 always @(posedge clk) q2 = q1;
 always @(posedge clk) q3 = q2;
 always @(posedge clk) q1 = d;

endmodule

[例7] 正确使用非阻塞赋值来描述时序逻辑的设计风格 (方式 #1)

module pipen1 (q3, d, clk);
 output [7:0] q3;
 input [7:0] d;
 input clk;
 reg [7:0] q3, q2, q1;

 always @(posedge clk) 
 begin
 q1 <= d;
 q2 <= q1;
 q3 <= q2;
 end

 endmodule

[例8] 正确使用非阻塞赋值来描述时序逻辑的设计风格 (方式 #2)

 module pipen2 (q3, d, clk);
 output [7:0] q3;
 input [7:0] d;
 input clk;
 reg [7:0] q3, q2, q1;

 always @(posedge clk)
 begin
 q3 <= q2;
 q2 <= q1;
 q1 <= d;
 end
endmodule

[例9] 正确使用非阻塞赋值来描述时序逻辑的设计风格 (方式 #3)

module pipen3 (q3, d, clk);
 output [7:0] q3;
 input [7:0] d;
 input clk;
 reg [7:0] q3, q2, q1;

 always @(posedge clk) q1 <= d;
 always @(posedge clk) q2 <= q1;
 always @(posedge clk) q3 <= q2;
 endmodule

[例10] 正确使用非阻塞赋值来描述时序逻辑的设计风格 (方式 #4)

module pipen4 (q3, d, clk);
 output [7:0] q3;
 input [7:0] d;
 input clk;
 reg [7:0] q3, q2, q1;

 always @(posedge clk) q2 <= q1;
 always @(posedge clk) q3 <= q2;
 always @(posedge clk) q1 <= d;
 endmodule     

以上移位寄存器时序逻辑电路设计的例子表明:

•4种阻塞赋值设计方式中有1种可以保证仿真正确

•4种阻塞赋值设计方式中有3种可以保证综合正确

•4种非阻塞赋值设计方式全部可以保证仿真正确

•4种非阻塞赋值设计方式全部可以保证综合正确

阻塞赋值和非阻塞赋值的原则归纳如下:

原则1:时序电路建模时,用非阻塞赋值。
原则2:锁存器电路建模时,用非阻塞赋值。
原则3:用always 块写组合逻辑时,采用阻塞赋值。
原则4:在同一个always 块中同时建立时序和组合逻辑电路时,用非阻塞赋值。
原则5:在同一个always 块中不要同时使用非阻塞赋值和阻塞赋值。
原则6:不要在多个always 块中为同一个变量赋值。

状态机 转自link

理论

FPGA不同于CPU的一点特点就是CPU是顺序执行的,而FPGA是同步执行(并行)的。那么FPGA如何处理明显具有时间上先后顺序的事件呢?这个时候我们就需要使用到状态机了。

状态机简写为 FSM(Finite State Machine),也称为同步有限状态机,我们一般简称为状态机,之所以说“同步”是因为状态机中所有的状态跳转都是在时钟的作用下进行的,而“有限”则是说状态的个数是有限的。状态机的每一个状态代表一个事件,从执行当前事件到执行另一事件我们称之为状态的跳转或状态的转移,我们需要做的就是执行该事件然后跳转到一下时间,这样我们的系统就“活”了,可以正常的运转起来了。状态机通过控制各个状态的跳转来控制流程,使得整个代码看上去更加清晰易懂,在控制复杂流程的时候,状态机优势明显。

分类

根据状态机的输出是否与输入条件相关,可将状态机分为两大类,即摩尔(Moore)型状态机和米勒(Mealy) 型状态机。

(1)Mealy 状态机:输出不仅取决于当前状态,还取决于输入状态
米勒状态机的模型如下图所示,模型中第一个方框是指产生下一状态的组合逻辑 F,F 是当前状态和输 入信号的函数,状态是否改变、如何改变,取决于组合逻辑 F 的输出;第二框图是指状态寄存器,其由一 组触发器组成,用来记忆状态机当前所处的状态,状态的改变只发生在时钟的跳边沿;第三个框图是指产生输出的组合逻辑 G,状态机的输出是由输出组合逻辑 G 提供的,G 也是当前状态和输入信号的函数。
image.png
(2)Moore 状态机:组合逻辑的输出只取决于当前状态,而与输入状态无关。
摩尔状态机的模型如下图所示,对比米勒状态机的模型可以发现,其区别在于米勒状态机的输出由当 前状态和输入条件决定的,而摩尔状态机的输出只取决于当前状态。
image.png

实例分析

接下来对一个简单的可乐售卖系统使用状态机的思想进行分析。

可乐售卖系统:可乐机每次只能投入 1 枚 1 元硬币,且每瓶可乐卖 3 元钱,即投入 3 个硬币就可以让可乐机出可乐,如果投币不够 3 元想放弃投币需要按复位键,否则之前投入的钱不能退回。

首先分析会有哪些输入、输出信号:

输入信号:
sys_clk_n:既然是同步状态机,那么时钟是肯定少不了的,这里设定时钟是50MHz;
sys_rst_n:一个稳健的系统肯定需要一个复位,这里设定位低电平有效;
money:投币输入,高电平表示投入一元,低电平表示没有投币;
输出信号:
cola:可乐输出,高电平表示掉落一瓶可乐,低电平表示没有可乐掉落;
根据以上输入、输出可以画出状态机模块的示意框图:
image.png
接下来需要想一想这个状态机是怎么运作的,也就是要绘制这个系统的状态转移图。前面提到,状态机的状态转移有和输入挂钩的Mealy 状态机,也有和输入无关的Moore 状态机。所以接下来分别用Moore 状态机、Mealy 状态机的思想来绘制状态转移图:
(1)Moore 状态机(输出和输入无关):
IDLE:首先是系统复位后的默认状态,这个状态下售卖机里没有钱,没有可乐输出;接下来的状态有两种情况:投了1元硬币则跳转状态ONE、没有投硬币则保持IDLE状态;
ONE:这个状态下售卖机里有1元硬币,所以也没有可乐输出;接下来的状态有两种情况:投了1元硬币则跳转状态TWO、没有投硬币则保持ONE状态;
TWO:这个状态下售卖机里有2元硬币,所以也没有可乐输出;接下来的状态有两种情况:投了1元硬币则跳转状态THREE、没有投硬币则保持TWO状态;
THREE:这个状态下售卖机里有3元硬币,但是因为是使用的时序逻辑,所以在这个时钟周期,是不会有可乐输出的,可乐会在状态跳转后(下一个时钟周期输出);接下来的状态有两种情况:投了1元硬币则跳转状态ONE、没有投硬币则跳转状态IDLE状态;而且状态跳转后会输出一瓶可乐(实际上可以理解为THREE状态来自于TWO状态投的一元硬币,也就是这个时钟周期如果输出发生了变化,则输出是和输入有关的了,那就不是Moore 状态机了)。
加黑字体的意思就是,输出必须在下个时钟周期到来时才能发生变化,不能与当前的输入有关。
根据上面列出的这些状态可以绘制出如下的状态转移图(1/0:前面的1代表输入,后面的0代表输出):
image.png
(2) Mealy 状态机(输出和输入相关):
IDLE:首先是系统复位后的默认状态,没有可乐输出(分两种情况,投币和不投币);接下来的状态有两种情况:投了1元硬币则跳转状态ONE且没有可乐输出、没有投硬币则保持IDLE状态且没有可乐输出;
ONE:这个状态下售卖机里有1元硬币;接下来的状态有两种情况:投了1元硬币则跳转状态TWO且没有可乐输出、没有投硬币则保持ONE状态且没有可乐输出;
TWO:这个状态下售卖机里有2元硬币;接下来的状态有两种情况:投了1元硬币则跳转状态IDLE且输出可乐(根据和输入相关要求,此时输入一元,加上原来的2元,一共有三元,满足输出可乐的条件)、没有投硬币则保持TWO状态且没有可乐输出。
根据上面列出的这些状态可以绘制出如下的状态转移图(1/0:前面的1代表输入,后面的0代表输出):
image.png
从上面的分析可以得到以下结论:
Mealy 状态机比Moore状态机的状态个数要少
Mealy 状态机比Moore状态机的输出要早一个时钟周期

写法

根据状态机的实际写法,状态机可以分为一段式、二段式和三段式状态机。

一段式状态机:整个状态机写到一个 always模块里面,在该模块中既描述状态转移,又描述状态的输入和输出。

Moore型(摩尔型)一段式状态机

//==================================================================
//--    1段式状态机(Moore)
//==================================================================
 
//------------<模块及端口声明>----------------------------------------
module FSM_Moore_1(
    input 		sys_clk		,		//输入系统时钟、50M
    input 		sys_rst_n	,		//复位信号、低电平有效
    input 		money		,		//投币输入,高电平有效
    
    output reg	cola				//可乐输出,高电平有效
);
 
//------------<状态机参数定义>------------------------------------------
//这里使用独热码编码节省组合逻辑资源
//此外还可以使用格雷码 、二进制码
localparam	IDLE  = 4'b0001,
            ONE   = 4'b0010,
            TWO   = 4'b0100,
            THREE = 4'b1000;
            
//------------<reg定义>-------------------------------------------------
reg	[3:0]	state;					//定义状态寄存器
 
//-----------------------------------------------------------------------
//--    1段式状态机(Moore)
//-----------------------------------------------------------------------
always@(posedge sys_clk or negedge sys_rst_n)begin
    if(!sys_rst_n)begin
        cola <= 1'b0;				//复位初始状态
        state <= IDLE;
    end
    else
        case(state)					//根据当前状态、输入进行状态转换判断
                                    //根据当前状态进行输出
            IDLE:begin
                cola <= 1'b0;		//初始状态无可乐输出
                if(money)				
                    state <= ONE;	//投币1元则状态跳转到ONE
                else
                    state <= IDLE;	//否则保持原有状态
            end				
            ONE:begin
                cola <= 1'b0;		//该状态只有1元,无可乐输出
                if(money)
                    state <= TWO;	//投币1元则状态跳转到TWO
                else
                    state <= ONE;	//否则保持原有状态
            end
            TWO:begin
                cola <= 1'b0;		//该状态只有2元,无可乐输出
                if(money)
                    state <= THREE;	//投币1元则状态跳转到THREE
                else
                    state <= TWO;	//否则保持原有状态
            end 
            THREE:begin
                cola <= 1'b1;		//该状态有3元,有可乐输出
                                    //但是时序逻辑输出会落后一个时钟周期
                if(money)
                    state <= ONE;	//投币1元则状态跳转到ONE
                else
                    state <= IDLE;	//否则状态跳转到IDLE
            end			
            default:begin			//默认状态同IDLE
                cola <= 1'b0;
                if(money)
                    state <= ONE;
                else
                    state <= IDLE;
            end	
        endcase
end
 
endmodule    

=============================================================================

//-------------------------------------------------------------------
//--    1段式状态机(Moore)
//-------------------------------------------------------------------
`timescale 1ns/1ns
 
//------------<模块及端口声明>----------------------------------------
module tb_FSM_Moore_1();
reg 	sys_clk;
reg 	sys_rst_n;
reg 	money;
wire	cola;
 
//------------<例化被测试模块>----------------------------------------
FSM_Moore_1	FSM_Moore_1_inst(
    .sys_clk	(sys_clk)	,
    .sys_rst_n	(sys_rst_n)	,
    .money		(money)		,
 
    .cola       (cola)
);
//------------<设置初始测试条件>----------------------------------------
initial begin
    sys_clk = 1'b0;					//初始时钟为0
    sys_rst_n <= 1'b0;				//初始复位
    money <= 1'b0;					//投币初始化为0
    #5								//5个时钟周期后
    sys_rst_n <= 1'b1;				//拉高复位,系统进入工作状态
    #25								//25个时钟周期后
    money <= 1'b1;					//拉高投币信号	
    #40								//40个时钟周期后
    money <= 1'b0;	    			//拉低投币信号	
    #20								//25个时钟周期后
    money <= 1'b1;	    			//拉高投币信号	
    #80								//25个时钟周期后
    money <= 1'b0;					//拉低投币信号	
end
//------------<设置时钟>----------------------------------------------
always #10 sys_clk = ~sys_clk;		//系统时钟周期20ns
 
//------------<状态机名称查看器>----------------------------------------
reg [39:0]	state_name;				//每字符8位宽,这里最多5个字符40位宽
 
always @(*) begin
    case(FSM_Moore_1_inst.state)
        4'b0001:	state_name = "IDLE";
        4'b0010: 	state_name = "ONE";
        4'b0100: 	state_name = "TWO";
        4'b1000: 	state_name = "THREE";
        default:	state_name = "IDLE";
    endcase
end
 
endmodule  

使用ModelSim执行仿真,仿真出来的波形如下所示:
image.png
可以看到:
在第2、3、5、6、7、8的6个时钟投入了6个硬币,理论上应该有2个可乐分别输出,实际也有两个可乐输出;
第1次输出可乐滞后THREE状态一个时钟周期,且当前的输入为1;第2次输出可乐滞后THREE状态一个时钟周期,且当前的输入为0;这说明输出会滞后当前状态一个时钟周期,且与输入无关(输入不管是0还是1都有输出);
状态的跳转符合我们绘制的状态转移图。

Mealy型(米勒型)一段式状态机

//==================================================================
//--    1段式状态机(Mealy)
//==================================================================
 
//------------<模块及端口声明>----------------------------------------
module FSM_Mealy_1(
    input 		sys_clk		,			//输入系统时钟、50M
    input 		sys_rst_n	,       	//复位信号、低电平有效
    input 		money		,       	//投币输入,高电平有效
                                        
    output reg	cola                	//可乐输出,高电平有效
);
 
//------------<状态机参数定义>------------------------------------------
//这里使用独热码编码节省组合逻辑资源
//此外还可以使用格雷码 、二进制码
localparam	IDLE  = 3'b001,
            ONE   = 3'b010,
            TWO   = 3'b100;
//------------<reg定义>------------------------------------------------
reg	[2:0]	state;						//定义状态寄存器
//-----------------------------------------------------------------------
//--    1段式状态机(Mealy)
//-----------------------------------------------------------------------
always@(posedge sys_clk or negedge sys_rst_n)begin
    if(!sys_rst_n)begin
        cola <= 1'b0;					//复位初始状态
        state <= IDLE;					//复位初始状态
    end
    else
        case(state)						//根据当前状态、输入进行状态转换判断
                                        //根据当前状态、输入进行输出
            IDLE:begin				
                if(money)begin			//投入1元
                    state <= ONE;		//状态跳转到ONE
                    cola <= 1'b0;		//一共1元 ,没有可乐输出
                end	
                else begin				//没有投入
                    state <= IDLE;		//保持原有状态
                    cola <= 1'b0;		//一共0元 ,没有可乐输出
                end	
            end				
            ONE:begin				
                if(money)begin			//投入1元
                    state <= TWO;		//状态跳转到TWO
                    cola <= 1'b0;       //一共2元 ,没有可乐输出
                end                     
                else begin              //没有投入
                    state <= ONE;       //保持原有状态
                    cola <= 1'b0;       //一共1元 ,没有可乐输出
                end
            end
            TWO:begin					
                if(money)begin			//投入1元
                    state <= IDLE;      //状态跳转到IDLE(一共3元了,需要输出可乐)
                    cola <= 1'b1;       //一共3元 ,输出可乐
                end                     
                else begin              //没有投入
                    state <= TWO;       //保持原有状态
                    cola <= 1'b0;       //一共2元 ,没有可乐输出
                end
            end 		
            default:begin				//默认状态同初始状态
                if(money)begin
                    state <= ONE;
                    cola <= 1'b0;
                end
                else begin
                    state <= IDLE;
                    cola <= 1'b0;
                end	
            end		
        endcase
end
 
endmodule   

===============================================================================================

//-------------------------------------------------------------------
//--    1段式状态机(Mealy)
//-------------------------------------------------------------------
`timescale 1ns/1ns
 
//------------<模块及端口声明>----------------------------------------
module tb_FSM_Mealy_1();
reg 	sys_clk;
reg 	sys_rst_n;
reg 	money;
wire	cola;
 
//------------<例化被测试模块>----------------------------------------
FSM_Mealy_1	FSM_Mealy_1_inst(
    .sys_clk	(sys_clk)	,
    .sys_rst_n	(sys_rst_n)	,
    .money		(money)		,
 
    .cola       (cola)
);
//------------<设置初始测试条件>----------------------------------------
initial begin
    sys_clk = 1'b0;					//初始时钟为0
    sys_rst_n <= 1'b0;				//初始复位
    money <= 1'b0;					//投币初始化为0
    #5								//5个时钟周期后
    sys_rst_n <= 1'b1;				//拉高复位,系统进入工作状态
    #25								//25个时钟周期后
    money <= 1'b1;					//拉高投币信号	
    #40								//40个时钟周期后
    money <= 1'b0;	    			//拉低投币信号	
    #20								//25个时钟周期后
    money <= 1'b1;	    			//拉高投币信号	
    #80								//25个时钟周期后
    money <= 1'b0;					//拉低投币信号	
end
//------------<设置时钟>----------------------------------------------
always #10 sys_clk = ~sys_clk;		//系统时钟周期20ns
 
//------------<状态机名称查看器>----------------------------------------
reg [31:0]	state_name;				//每字符8位宽,这里最多4个字符32位宽
 
always @(*) begin
    case(FSM_Mealy_1_inst.state)
        3'b001:		state_name = "IDLE";
        3'b010: 	state_name = "ONE";
        3'b100: 	state_name = "TWO";
        default:	state_name = "IDLE";
    endcase
end
endmodule  

使用ModelSim执行仿真,仿真出来的波形如下所示:
image.png
从仿真结果可以看到:
在第2、3、5、6、7、8的6个时钟投入了6个硬币,理论上应该有2个可乐分别输出,实际也有两个可乐输出;
第1次输出可乐滞后TWO状态一个时钟周期,且当前的输入为1;第2次输出可乐滞后TWO状态一个时钟周期,且当前的输入也为1;这说明输出会之后当前状态一个时钟周期,且与输入相关(只有输入为 1才有输出,在第4个时钟是,输入为0,所以没有输出);
状态的跳转符合我们绘制的状态转移图。
通过以上,针对一段式状态机可以得出如下小结:
Moore型状态机输出滞后Mealy型状态机一个时钟周期;
一段式状态机将所有状态转移与输出全写在一个always块里,如果状态多的话就会看起来十分臃肿,且不利于维护。

二段式状态机:用两个 always 模块来描述状态机,其中一个 always 模块采用同步时序描述状态转移;另一个模块采用组合逻辑判断状态转移条件,描述状态转移规律以及输出。不同于一段式状态机的是,它需要定义两个状态,现态和次态,然后通过现态和次态的转换来实现时序逻辑。

Moore型(摩尔型)二段式状态机

//==================================================================
//--    2段式状态机(Moore)
//==================================================================
 
//------------<模块及端口声明>----------------------------------------
module FSM_Moore_2(
    input 		sys_clk		,		//输入系统时钟、50M
    input 		sys_rst_n	,   	//复位信号、低电平有效
    input 		money		,   	//投币输入,高电平有效
                                    
    output reg	cola            	//可乐输出,高电平有效
);
 
//------------<状态机参数定义>------------------------------------------
localparam	IDLE  = 4'b0001,
            ONE   = 4'b0010,
            TWO   = 4'b0100,
            THREE = 4'b1000;
//------------<reg定义>-------------------------------------------------
reg	[3:0]	cur_state;				//定义现态寄存器
reg	[3:0]	next_state;				//定义次态寄存器
 
//-----------------------------------------------------------------------
//--状态机第一段:同步时序描述状态转移
//-----------------------------------------------------------------------
always@(posedge sys_clk or negedge sys_rst_n)begin
    if(!sys_rst_n)
        cur_state <= IDLE;			//复位初始状态
    else
        cur_state <= next_state;	//次态转移到现态
end
 
//-----------------------------------------------------------------------
//--状态机第二段:组合逻辑判断状态转移条件,描述状态转移规律以及输出
//-----------------------------------------------------------------------
always@(*)begin						//组合逻辑
    case(cur_state)					//根据当前状态、输入进行状态转换判断
                                    //根据当前状态进行输出
        IDLE:begin
            cola = 1'b0;			//初始状态无可乐输出
            if(money)				//投币1元
                next_state = ONE;	//次态(下个状态)为ONE		
            else 
                next_state = IDLE;	//次态为现态	
        end					
        ONE:begin
            cola = 1'b0;			//无可乐输出
            if(money)				//投币1元
                next_state = TWO;	//次态(下个状态)为TWO
            else 
                next_state = ONE;	//次态为现态			
        end
        TWO:begin					
            cola = 1'b0;	        //无可乐输出
            if(money)               //投币1元
                next_state = THREE; //次态(下个状态)为THREE
            else                    			
                next_state = TWO;	//次态为现态
        end	
        THREE:begin	
            cola = 1'b1;			//输出可乐输出
            if(money)               //投币1元
                next_state = ONE;	//次态(下个状态)为ONE		
            else                    			
                next_state = IDLE;  //次态为IDLE
        end
        default:begin				//默认状态同IDLE
            cola = 1'b0;		
            if(money)
                next_state = ONE;
            else 
                next_state = IDLE;
        end	
    endcase
end
endmodule  

=================================================================================

//------------------------------------------------
//--    2段式状态机(Moore)
//------------------------------------------------
`timescale 1ns/1ns
 
//------------<模块及端口声明>----------------------------------------
module tb_FSM_Moore_2();
 
reg 	sys_clk;
reg 	sys_rst_n;
reg 	money;
 
wire	cola;
 
//------------<例化被测试模块>----------------------------------------
FSM_Moore_2	FSM_Moore_2_inst(
    .sys_clk	(sys_clk)	,
    .sys_rst_n	(sys_rst_n)	,
    .money		(money)		,
 
    .cola       (cola)
);
 
//------------<设置初始测试条件>----------------------------------------
initial begin
    sys_clk = 1'b0;					//初始时钟为0
    sys_rst_n <= 1'b0;				//初始复位
    money <= 1'b0;					//投币初始化为0
    #5								//5个时钟周期后
    sys_rst_n <= 1'b1;				//拉高复位,系统进入工作状态
    #25								//25个时钟周期后
    money <= 1'b1;					//拉高投币信号	
    #40								//40个时钟周期后
    money <= 1'b0;	    			//拉低投币信号	
    #20								//25个时钟周期后
    money <= 1'b1;	    			//拉高投币信号	
    #80								//25个时钟周期后
    money <= 1'b0;					//拉低投币信号	
end
//------------<设置时钟>----------------------------------------------
always #10 sys_clk = ~sys_clk;		//系统时钟周期20ns
 
//------------------------------------------------
//--    状态机名称查看器
//------------------------------------------------
reg [39:0]	state_name_cur;			//每字符8位宽,这里最多5个字符40位宽
reg [39:0]	state_name_next;		//每字符8位宽,这里最多5个字符40位宽
 
always @(*) begin
    case(FSM_Moore_2_inst.cur_state)
         4'b0001:    	state_name_cur = "IDLE";
         4'b0010:   	state_name_cur = "ONE";
         4'b0100:   	state_name_cur = "TWO";
         4'b1000:		state_name_cur = "THREE"; 
         default:		state_name_cur = "IDLE";
    endcase
end
 
always @(*) begin
    case(FSM_Moore_2_inst.next_state)
         4'b0001:    	state_name_next = "IDLE";
         4'b0010:   	state_name_next = "ONE";
         4'b0100:   	state_name_next = "TWO";
         4'b1000:		state_name_next = "THREE"; 
         default:		state_name_next = "IDLE";
    endcase
end
 
endmodule    

image.png
从仿真结果可以看到:

在第2、3、5、6、7、8的6个时钟投入了6个硬币,理论上应该有2个可乐分别输出,实际也有两个可乐输出;
现态落后次态一个时钟周期,这是因为需要用次态去描述现态;
与一段式状态机不同,可乐的输出不会滞后一个时钟周期,这是因为采用了组合逻辑来描述输出;
第1次输出可乐的输入为1,第2次输出可乐的输入为0;这说明输出与输入无关(输入不管是0还是1都有输出);
状态的跳转符合我们绘制的状态转移图。

Mealy型(米勒型)二段式状态机

//------------------------------------------------
//--    2段式状态机(Mealy )
//------------------------------------------------
 
//------------<模块及端口声明>----------------------------------------
module FSM_Mealy_2(
    input 		sys_clk		,			//输入系统时钟、50M
    input 		sys_rst_n	,   		//复位信号、低电平有效
    input 		money		,   		//投币输入,高电平有效
                                        
    output reg	cola            		//可乐输出,高电平有效
);
 
//------------<状态机参数定义>------------------------------------------
localparam	IDLE  = 3'b001,
            ONE   = 3'b010,
            TWO   = 3'b100;
//------------<reg定义>------------------------------------------------
reg	[2:0]	cur_state;					//定义现态
reg	[2:0]	next_state;					//定义次态
//-----------------------------------------------------------------------
//--状态机第一段:同步时序描述状态转移
//-----------------------------------------------------------------------
always@(posedge sys_clk or negedge sys_rst_n)begin
    if(!sys_rst_n)
        cur_state <= IDLE;				//复位初始状态
    else                                
        cur_state <= next_state;        //次态转移到现态
end
//-----------------------------------------------------------------------
//--状态机第二段:组合逻辑判断状态转移条件,描述状态转移规律以及输出
//-----------------------------------------------------------------------
always@(*)begin
    case(cur_state)						//组合逻辑
        IDLE:begin				        //根据当前状态、输入进行状态转换判断
                                        //根据当前状态、输入进行输出
            if(money)begin				//当前输入为1
                next_state = ONE;		//次态为ONE
                cola = 1'b0;			//一共1元 ,没有可乐输出
            end
            else begin					//当前输入为0
                next_state = IDLE;		//次态为IDLE
                cola = 1'b0;			//一共0元 ,没有可乐输出
            end	
        end					
        ONE:begin				
            if(money)begin				//当前输入为1
                next_state = TWO;       //次态为TWO
                cola = 1'b0;            //一共2元 ,没有可乐输出
            end                         
            else begin                  //当前输入为0
                next_state = ONE;       //次态为ONE
                cola = 1'b0;            //一共1元 ,没有可乐输出
            end	
        end
        TWO:begin				
            if(money)begin				//当前输入为1
                next_state = IDLE;		//次态为IDLE
                cola = 1'b1;            //一共3元 ,输出可乐
            end                         
            else begin                  //当前输入为0
                next_state = TWO;       //次态为TWO
                cola = 1'b0;            //一共2元 ,没有可乐输出
            end	
        end		
        default:begin					//默认状态同初始状态
            if(money)begin
                next_state = ONE;
                cola = 1'b0;
            end
            else begin
                next_state = IDLE;
                cola = 1'b0;
            end	
        end
    endcase
end
endmodule   

===========================================================================================

//------------------------------------------------
//--    2段式状态机(Mealy)
//------------------------------------------------
`timescale 1ns/1ns
 
//------------<模块及端口声明>----------------------------------------
module tb_FSM_Mealy_2();
 
reg 	sys_clk;
reg 	sys_rst_n;
reg 	money;
wire	cola;
 
//------------<例化被测试模块>----------------------------------------
FSM_Mealy_2		FSM_Mealy_2_inst(
    .sys_clk	(sys_clk),
    .sys_rst_n	(sys_rst_n),
    .money		(money),
 
    .cola       (cola)
);
 
//------------<设置初始测试条件>----------------------------------------
initial begin
    sys_clk = 1'b0;					//初始时钟为0
    sys_rst_n <= 1'b0;				//初始复位
    money <= 1'b0;					//投币初始化为0
    #5								//5个时钟周期后
    sys_rst_n <= 1'b1;				//拉高复位,系统进入工作状态
    #25								//25个时钟周期后
    money <= 1'b1;					//拉高投币信号	
    #40								//40个时钟周期后
    money <= 1'b0;	    			//拉低投币信号	
    #20								//25个时钟周期后
    money <= 1'b1;	    			//拉高投币信号	
    #80								//25个时钟周期后
    money <= 1'b0;					//拉低投币信号	
end
//------------<设置时钟>----------------------------------------------
always #10 sys_clk = ~sys_clk;		//系统时钟周期20ns
 
//------------------------------------------------
//--    状态机名称查看器
//------------------------------------------------
//1字符8位宽
reg [31:0]	state_name_cur;			//每字符8位宽,这里最多4个字符32位宽
reg [31:0]	state_name_next;		//每字符8位宽,这里最多4个字符32位宽
 
always @(*) begin
    case(FSM_Mealy_2_inst.cur_state)
        3'b001:    	state_name_cur = "IDLE";
        3'b010:   	state_name_cur = "ONE";
        3'b100:   	state_name_cur = "TWO";
        default:	state_name_cur = "IDLE";
    endcase
end
always @(*) begin
    case(FSM_Mealy_2_inst.next_state)
        3'b001:    	state_name_next = "IDLE";
        3'b010:   	state_name_next = "ONE";
        3'b100:   	state_name_next = "TWO";
        default:	state_name_next = "IDLE";
    endcase
end
 
endmodule   

image.png

从仿真结果可以看到:

在第2、3、5、6、7、8的6个时钟投入了6个硬币,理论上应该有2个可乐分别输出,实际也有两个可乐输出;
现态落后次态一个时钟周期,这是因为需要用次态去描述现态;
与一段式状态机不同,可乐的输出不会滞后一个时钟周期,这是因为采用了组合逻辑来描述输出;
第1次输出可乐的输入为1,第2次输出可乐的输入也为1;这说明输出与输入有关;
状态的跳转符合我们绘制的状态转移图。

通过以上,针对二段式状态机可以得出如下小结:
Moore型状态机输出滞后Mealy型状态机一个时钟周期;
二段式状态机的输出使用组合逻辑输出,而使用组合逻辑则无法避免的会引入“毛刺”问题。

三段式状态机:在两个 always 模块描述方法基础上,使用三个always 模块,一个always 模块采用同步时序描述状态转移,一个 always 采用组合逻辑判断状态转移条件,描述状态转移规律,另一个 always 模块描述状态输出。

Moore型(摩尔型)三段式状态机

//==================================================================
//--    3段式状态机(Moore)
//==================================================================
 
//------------<模块及端口声明>----------------------------------------
module FSM_Moore_3(
    input 		sys_clk		,			//输入系统时钟、50M
    input 		sys_rst_n	,   		//复位信号、低电平有效
    input 		money		,   		//投币输入,高电平有效
                                        
    output reg	cola            		//可乐输出,高电平有效
);
 
//------------<状态机参数定义>------------------------------------------
localparam	IDLE  = 4'b0001,
            ONE   = 4'b0010,
            TWO   = 4'b0100,
            THREE = 4'b1000;
            
//------------<reg定义>-------------------------------------------------
reg	[3:0]	cur_state;					//定义现态寄存器
reg	[3:0]	next_state;					//定义次态寄存器
 
//-----------------------------------------------------------------------
//--状态机第一段:同步时序描述状态转移
//-----------------------------------------------------------------------
always@(posedge sys_clk or negedge sys_rst_n)begin
    if(!sys_rst_n)
        cur_state <= IDLE;				//复位初始状态
    else
        cur_state <= next_state;		//次态转移到现态
end
 
//-----------------------------------------------------------------------
//--状态机第二段:组合逻辑判断状态转移条件,描述状态转移规律以及输出
//-----------------------------------------------------------------------
always@(*)begin
    case(cur_state)						//组合逻辑
                                        //根据当前状态、输入进行状态转换判断										
        IDLE:begin				
            if(money)					
                next_state = ONE;		//投币1元,则状态转移到ONE
            else 
                next_state = IDLE;		//没有投币,则状态保持	
        end					
        ONE:begin				
            if(money)
                next_state = TWO;		//投币1元,则状态转移到TWO
            else 
                next_state = ONE;		//没有投币,则状态保持
        end
        TWO:begin				
            if(money)
                next_state = THREE;		//投币1元,则状态转移到THREE
            else                        
                next_state = TWO;       //没有投币,则状态保持
        end	
        THREE:begin				
            if(money)
                next_state = ONE;		//投币1元,则状态转移到ONE
            else                        
                next_state = IDLE;      //没有投币,则状态保持
        end
        default:begin					//默认状态同IDLE
            if(money)
                next_state = ONE;
            else 
                next_state = IDLE;	
        end
    endcase
end
 
//-----------------------------------------------------------------------
//--状态机第三段:时序逻辑描述输出
//-----------------------------------------------------------------------
always@(posedge sys_clk or negedge sys_rst_n)begin
    if(!sys_rst_n)
        cola <= 1'b0;					//复位、初始状态 
    else
        case(cur_state)					//根据当前状态进行输出
            IDLE:	cola <= 1'b0;		//无可乐输出			
            ONE:	cola <= 1'b0;		//无可乐输出
            TWO:	cola <= 1'b0;		//无可乐输出
            THREE:	cola <= 1'b1;		//输出可乐
            default:cola <= 1'b0;		//默认无可乐输出
        endcase
end
 
endmodule               

=========================================================================

//------------------------------------------------
//--    3段式状态机(Moore)
//------------------------------------------------
`timescale 1ns/1ns
 
//------------<模块及端口声明>----------------------------------------
module tb_FSM_Moore_3();
 
reg 	sys_clk;
reg 	sys_rst_n;
reg 	money;
 
wire	cola;
 
//------------<例化被测试模块>----------------------------------------
FSM_Moore_3		FSM_Moore_3_inst(
    .sys_clk	(sys_clk),
    .sys_rst_n	(sys_rst_n),
    .money		(money),
 
    .cola       (cola)
);
 
//------------<设置初始测试条件>----------------------------------------
initial begin
    sys_clk = 1'b0;					//初始时钟为0
    sys_rst_n <= 1'b0;				//初始复位
    money <= 1'b0;					//投币初始化为0
    #5								//5个时钟周期后
    sys_rst_n <= 1'b1;				//拉高复位,系统进入工作状态
    #25								//25个时钟周期后
    money <= 1'b1;					//拉高投币信号	
    #40								//40个时钟周期后
    money <= 1'b0;	    			//拉低投币信号	
    #20								//25个时钟周期后
    money <= 1'b1;	    			//拉高投币信号	
    #80								//25个时钟周期后
    money <= 1'b0;					//拉低投币信号	
end
//------------<设置时钟>----------------------------------------------
always #10 sys_clk = ~sys_clk;		//系统时钟周期20ns
 
//------------------------------------------------
//--    状态机名称查看器
//------------------------------------------------
reg [39:0]	state_name_cur;			//每字符8位宽,这里最多5个字符40位宽
reg [39:0]	state_name_next;		//每字符8位宽,这里最多5个字符40位宽
 
always @(*) begin
    case(FSM_Moore_3_inst.cur_state)
         4'b0001:    	state_name_cur = "IDLE";
         4'b0010:   	state_name_cur = "ONE";
         4'b0100:   	state_name_cur = "TWO";
         4'b1000:		state_name_cur = "THREE"; 
        default:		state_name_cur = "IDLE";
    endcase
end
 
always @(*) begin
    case(FSM_Moore_3_inst.next_state)
         4'b0001:    	state_name_next = "IDLE";
         4'b0010:   	state_name_next = "ONE";
         4'b0100:   	state_name_next = "TWO";
         4'b1000:		state_name_next = "THREE"; 
         default:		state_name_next = "IDLE";
    endcase
end
 
endmodule

使用ModelSim执行仿真,仿真出来的波形如下所示:
image.png

从仿真结果可以看到:

在第2、3、5、6、7、8的6个时钟投入了6个硬币,理论上应该有2个可乐分别输出,实际也有两个可乐输出;
现态落后次态一个时钟周期,这是因为需要用次态去描述现态;
与二段式状态机不同,可乐的输出会滞后一个时钟周期,这是因为采用了时序逻辑来描述输出;
第1次输出可乐的输入为1,第2次输出可乐的输入为0;这说明输出与输入无关;
状态的跳转符合我们绘制的状态转移图;
波形图除了多了一个次态外,其余与一段式 的Moore状态机完全一致;

Mealy型(米勒型)三段式状态机

//==================================================================
//--    3段式状态机(Mealy)
//==================================================================
 
//------------<模块及端口声明>----------------------------------------
module FSM_Mealy_3(
    input 		sys_clk		,			//输入系统时钟、50M
    input 		sys_rst_n	,   		//复位信号、低电平有效
    input 		money		,   		//投币输入,高电平有效
                                        
    output reg	cola            		//可乐输出,高电平有效
);
 
//------------<状态机参数定义>------------------------------------------
localparam	IDLE  = 3'b0001,
            ONE   = 3'b0010,
            TWO   = 3'b0100;
            
//------------<reg定义>-------------------------------------------------
reg	[3:0]	cur_state;					//定义现态寄存器
reg	[3:0]	next_state;					//定义次态寄存器
//-----------------------------------------------------------------------
//--状态机第一段:同步时序描述状态转移
//-----------------------------------------------------------------------
always@(posedge sys_clk or negedge sys_rst_n)begin
    if(!sys_rst_n)
        cur_state <= IDLE;				//复位初始状态
    else
        cur_state <= next_state;		//次态转移到现态
end
//-----------------------------------------------------------------------
//--状态机第二段:组合逻辑判断状态转移条件,描述状态转移规律以及输出
//-----------------------------------------------------------------------
always@(*)begin
    case(cur_state)						//组合逻辑
                                        //根据当前状态、输入进行状态转换判断										
        IDLE:begin				
            if(money)					
                next_state = ONE;		//投币1元,则状态转移到ONE
            else 
                next_state = IDLE;		//没有投币,则状态保持	
        end					
        ONE:begin				
            if(money)
                next_state = TWO;		//投币1元,则状态转移到TWO
            else 
                next_state = ONE;		//没有投币,则状态保持
        end
        TWO:begin				
            if(money)
                next_state = IDLE;		//投币1元,则状态转移到IDLE
            else                        
                next_state = TWO;       //没有投币,则状态保持
        end	
        default:begin					//默认状态同IDLE
            if(money)
                next_state = ONE;
            else 
                next_state = IDLE;	
        end
    endcase
end
//-----------------------------------------------------------------------
//--状态机第三段:时序逻辑描述输出
//-----------------------------------------------------------------------
always@(posedge sys_clk or negedge sys_rst_n)begin
    if(!sys_rst_n)
        cola <= 1'b0;					//复位、初始状态 
    else
        case(cur_state)					//根据当前状态进行输出
            IDLE:	cola <= 1'b0;		//无可乐输出(因为输入不管是0、1都是输出0,所以省略写法)			
            ONE:	cola <= 1'b0;		//无可乐输出(因为输入不管是0、1都是输出0,所以省略写法)	
            TWO:begin					
                if(money)
                    cola <= 1'b1;		//如果输入1,则输出可乐
                else
                    cola <= 1'b0;		//如果输入0,则无可乐输出
            end
            default:cola <= 1'b0;		//默认无可乐输出
        endcase
end
endmodule   

===========================================================================

//------------------------------------------------
//--    3段式状态机(Mealy)
//------------------------------------------------
`timescale 1ns/1ns
 
//------------<模块及端口声明>----------------------------------------
module tb_FSM_Mealy_3();
 
reg 	sys_clk;
reg 	sys_rst_n;
reg 	money;
 
wire	cola;
 
//------------<例化被测试模块>----------------------------------------
FSM_Mealy_3		FSM_Mealy_3_inst(
    .sys_clk	(sys_clk),
    .sys_rst_n	(sys_rst_n),
    .money		(money),
 
    .cola       (cola)
);
 
//------------<设置初始测试条件>----------------------------------------
initial begin
    sys_clk = 1'b0;					//初始时钟为0
    sys_rst_n <= 1'b0;				//初始复位
    money <= 1'b0;					//投币初始化为0
    #5								//5个时钟周期后
    sys_rst_n <= 1'b1;				//拉高复位,系统进入工作状态
    #25								//25个时钟周期后
    money <= 1'b1;					//拉高投币信号	
    #40								//40个时钟周期后
    money <= 1'b0;	    			//拉低投币信号	
    #20								//25个时钟周期后
    money <= 1'b1;	    			//拉高投币信号	
    #80								//25个时钟周期后
    money <= 1'b0;					//拉低投币信号	
end
//------------<设置时钟>----------------------------------------------
always #10 sys_clk = ~sys_clk;		//系统时钟周期20ns
 
//------------------------------------------------
//--    状态机名称查看器
//------------------------------------------------
reg [39:0]	state_name_cur;			//每字符8位宽,这里最多5个字符40位宽
reg [39:0]	state_name_next;		//每字符8位宽,这里最多5个字符40位宽
 
always @(*) begin
    case(FSM_Mealy_3_inst.cur_state)
         4'b0001:    	state_name_cur = "IDLE";
         4'b0010:   	state_name_cur = "ONE";
         4'b0100:   	state_name_cur = "TWO";
         4'b1000:		state_name_cur = "THREE"; 
        default:		state_name_cur = "IDLE";
    endcase
end
 
always @(*) begin
    case(FSM_Mealy_3_inst.next_state)
         4'b0001:    	state_name_next = "IDLE";
         4'b0010:   	state_name_next = "ONE";
         4'b0100:   	state_name_next = "TWO";
         4'b1000:		state_name_next = "THREE"; 
         default:		state_name_next = "IDLE";
    endcase
end
 
endmodule  

使用ModelSim执行仿真,仿真出来的波形如下所示:
image.png

从仿真结果可以看到:

在第2、3、5、6、7、8的6个时钟投入了6个硬币,理论上应该有2个可乐分别输出,实际也有两个可乐输出;
现态落后次态一个时钟周期,这是因为需要用次态去描述现态;
与二段式状态机不同,可乐的输出会滞后一个时钟周期,这是因为采用了时序逻辑来描述输出;
第1次输出可乐的输入为1,第2次输出可乐的输入也为1;这说明输出与输入相关;
状态的跳转符合我们绘制的状态转移图;
波形图除了多了一个次态外,其余与一段式的Mealy状态机完全一致;

通过以上,针对三段式状态机可以得出如下小结:
Moore型状态机输出滞后Mealy型状态机一个时钟周期
三段式状态机的输出使用时序逻辑输出,避免了二段式状态机使用组合逻辑输出从而无法避免的“毛刺”问题

总结与思考

状态机的三种描述方法:

一段式:整个状态机写到一个always模块里面,在该模块中既描述状态转移,又描述状态的输入和输出。

二段式:用两个always模块来描述状态机,其中一个always模块采用同步时序描述状态转移;另一个模块采用组合逻辑判断状态转移条件,描述状态转移规律以及输出。

三段式:在两个always模块描述方法基础上,使用三个always模块,一个always模块采用同步时序描述状态转移,一个always采用组合逻辑判断状态转移条件,描述状态转移规律,另一个always模块描述状态输出。

应该选择哪一种状态机 ?

一段式状态机写法不够模块化 ,且过于臃肿不利于维护,及布局布线;

二段式状态机将同步时序和组合逻辑分别放到不同的always模块中实现,这样做的好处不仅仅是便于阅读、理解、维护,更重要的是利于综合器优化代码,利于用户添加合适的时序约束条件,利于布局布线器实现设计。但是其当前状态的输出用组合逻辑实现,组合逻辑很容易产生毛刺,而且不利于约束,不利于综合器和布局布线器实现高性能的设计;

三段式状态机与二段式状态机相比,关键在于根据状态转移规律,在上一状态根据输入条件判断出当前状态的输出,从而在不插入额外时钟节拍的前提下,实现了寄存器输出,解决了毛刺问题。实际应用中三段式状态机使用最多,因为三段式状态机将组合逻辑和时序分开,有利于综合器分析优 化以及程序的维护;并且三段式状态机将状态转移与状态输出分开,使代码看上去更加清晰易懂,提高了代码的可读性,推荐大家使用三段式状态机。

三段式状态机的基本格式:

第一个 always 语句实现同步状态跳转;

第二个 always 语句采用组合逻辑判断状态转移条件;

第三个 always 语句采用时序逻辑描述状态输出。

状态机的编码方式:

image.png
格雷码:相邻之间只变1bit,编码密度高。
独热码:任何状态只有1bit为1,其余皆为0,编码密度低。
比如说,表示4个状态,那么状态机寄存器采用格雷码编码只需要2bit:00(S0),01(S1),11(S2),10(S3);
采用独热码需要4bit:0001(S0),0010(S1),0100(S2),1000(S3)。所以很明显采用格雷码可以省2bit寄存器。
独热码适合写条件复杂但是状态少的状态机;
格雷码适合写条件不复杂但是状态多的状态机。

独热码:和格雷码相比,虽然独热码多用了触发器,但所用组合电路可以省一些,因而使电路的速度和可靠性有显著提高,而总的单元数并无显著增加。因为独热码只有一位的变化,所以更适用于高速系统。
原因:
考虑最简单的跳变,当A为1时,状态机会从S0跳到S1:。
采用格雷码写:
STATUS[1:0] <= (STATUS==2’h00) & A ? 2’h01 : 2’h00;
采用独热码写:
STATUS[1] <= STATUS[0] & A;

格雷码:使用了更多的组合逻辑资源,但是比独热码能表示更多的状态。
原因:
假如我们要在代码中判断状态机是否处于某状态S1,
对于格雷码的状态机来说,代码是这样的:assign S1 = (STATUS==2’b01);
对于独热码来说,代码是这样的就行:assign S1=STATUS[1];
所以独热码的译码非常简单。

2进制:使用了更多的组合逻辑资源,但是比独热码能表示更多的状态,稳定性不如格雷码。

三段式状态机的第三段采用next_state还是cur_state:

第三段使用next_state和cur_state的区别在于,当状态跳转时,基于next_state的输出是立刻变化的,而基于cur_state输出会延迟一个周期,其他情况都一样,应该根据自己的时序要求选择。

-------------本文结束感谢您的阅读-------------