FPGA 33专题 状态机的的设计及工作流程、设计标准

本文详细介绍了FPGA状态机的基础概念,包括状态机的工作原理、状态转移图与条件转移表,以及Moore和Mealy状态机的区别。涵盖了设计步骤、状态机分类和编写方式,如一段式、二段式和三段式。最后强调了状态机设计的安全性、效率和可读性标准。
摘要由CSDN通过智能技术生成

FPGA 33专题 状态机的的设计及工作流程、设计标准

在这里插入图片描述

基本概念:

状态机(FSM : Finite State Machine) : 现在我们工程中使用到的只有 有限状态机,即不论状态有多少个,总是有限的。理论上说是有无限的状态机,但是,在实际的工程中,这个是不存在的。

状态机定义 : 指的是,一系列数量有限的状态组成的循环机制。它是由寄存器和组合逻辑构成的硬件时序电路。状态机通过控制各个状态的跳转来控制流程,使得整个代码看上去更加清晰易懂,在控制复杂流程的时候,状态机优势明显,因此基本上都会用到状态机。

首先,介绍两个比较简单的状态机图:

第一个 :“Hello” 数据流判断的状态机

状态转移图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nezcYawd-1627121003158)(img/blog_img/fpga/image-20210724144938097.png)]

状态条件转移表:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yqFaqo70-1627121003160)(img/blog_img/fpga/image-20210724145240714.png)]

分析: 在第一个图里面,我们可以很清晰的看到状态到其它状态之间的转换。总的来说,我们在图上可以直观的看到,设计的状态机中,无论在那一个状态,都是可以实现自启动的情况。也就是我们说的闭环,这样就不会出现死机或者不工作的情况。

​ 在第二个图中,有一个状态转移条件判断表,每个状态跳转到下一个状态,只有满足跳转条件,才会进入下一个状态。图二中有3列,Source State , Destination State , Condition(跳转条件)。我们可以看到这个的判断条件看着特别复杂(注:这个是经过综合以后给出的最优布局布线的判断条件,实际我们在设计的时候是根据自己的条件来进行判断的)。

我们再看另外一个按键滤波的状态转移图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ehIAUeKD-1627121003162)(img/blog_img/fpga/image-20210724150742375.png)]

状态条件转移图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-clbCAi9n-1627121003163)(img/blog_img/fpga/image-20210724150812684.png)]

分析 :我们在按键滤波状态转移图中,也可以看到,整个状态机是内部闭环的,即不论在什么条件下,都可以实现循环或者自启动。也就是我们说的不会出现死循环或者死机状态。

在按键滤波状态条件转移中,我们可以看到,这个条件转移图的条件明显比第一个简单,这个也是综合(优化)后的逻辑判断条件后的结果。

总结:在每一个状态转移图的条件转移图的时候,我们使用的是if----else if —else 来作为状态转移的判断。另外,在每个状态到下一个状态的时候,有多少个线就有多少个 if…elseif…else 的条件(刚开始写Verilog的时候建议是把所有的 else 都给补齐,不然很容易把自己给绕晕)。

另外: 在每一个状态里面我们可能要执行不同的功能(操作),在这上面两个图里面是没有的,这个是根据实际我们在执行的时候来进行的。所以在这里,我们开始引入一段、二段、三段式状态机这三种在fpga里面用的状态机编写过程。

状态机分类和编写方式:

Moore状态机和Mealy状态机: 两种状态机的区别

Moore状态机:输出只和状态有关

原作者代码参考:https://blog.csdn.net/Reborn_Lee/article/details/85763185

eg:序列检测,如 ’1101 ‘状态检测,就是一个典型的Moore 状态机(或者我们上面的那个字符串检测,也是同样的Moore 状态机)

代码中可以看到,使用了 3个always块,在第3个always块中,可以看到,我们输出的结果只和状态机有关,和输入之间没有关系(或者说,不会收到输入信号的影响)。

`timescale 1ns / 1ps
 
 
module seq_det_moore(
    input clk,
    input reset,
    input din,
    output reg dout
    );
    //状态声明
    localparam [2:0]
    s0 = 3'b000,
    s1 = 3'b001,
    s2 = 3'b010,
    s3 = 3'b011,
    s4 = 3'b100;
    
    reg [2:0] current_state,next_state;
    
    always @(posedge clk, posedge reset)
    begin
        if(reset)
            current_state <= s0;
        else
            current_state <= next_state;
    end
    
    always @ *
    begin
        case(current_state)
        s0:
            if(din == 1'b1) next_state = s1;
            else next_state = s0;
        s1:
            if(din == 1'b1) next_state = s2;
            else next_state = s0;
        s2:
            if(din == 1'b0) next_state = s3;
            else next_state = s2;
        s3:
            if(din == 1'b1) next_state = s4;
            else next_state = s0;
        s4:
            if(din == 1'b1) next_state = s1;
            else next_state = s0;
        default: next_state = s0;
        
        endcase
    
    end
    
    always @*
    begin
        if(current_state == s4) dout = 1;
        else dout = 0;
    end
    
    
endmodule
    
Mealy状态机 : 不仅仅和状态有关,也和输入有关

eg: 同样是序列状态的检测,我们使用Mealy 状态机也可以实现.

原作者代码参考: https://blog.csdn.net/Reborn_Lee/article/details/85798105

代码中可以看到,使用了 3个always块,在第3个always块中,可以看到,我们输出的结果不仅和状态机有关,还和输入之间有关系(或者说,输入信号也会决定输出信号的结果)。

`timescale 1ns / 1ps

module seq_det_mealy(
    input clk,
    input reset,
    input din,
    output reg dout
    );
	
	localparam [1:0]
	s0 = 2'b00,
	s1 = 2'b01,
	s2 = 2'b10,
	s3 = 2'b11;
	
	reg [1:0] current_state,next_state;
	
	always @(posedge clk, posedge reset)
	begin
		if(reset)
			current_state <= s0;
		else
			current_state <= next_state;
	
	end
	
	always @ *
	begin
		case(current_state)
		s0:
			if(din == 1'b1) next_state = s1;
			else next_state = s0;
		s1: 
			if(din == 1'b1) next_state = s2;
			else next_state = s1;
		s2:
			if(din == 1'b0) next_state = s3;
			else next_state = s2;
		s3: next_state = s0;
		default: next_state = s0;
		endcase
	
	end
	
	always @ *
	begin
		if(reset) dout = 1'b0;
		else if( (current_state == s3)&&(din == 1'b1) ) dout = 1'b1;
		else dout = 1'b0;
	
	end
	
	
endmodule
Moore 和 Mealy 的对比分析:

①功能上,两种状态机都可以实现相同的功能。所以,两种状态机是可以进行相互转换的。
​ ② 实现相同的情况下,Mealy 形的状态机所需要的状态数比Moore少,但是需增加输入信号在输出组合逻辑电路描述模块中对于输出信号的直接影响,Moore状态机的输出就仅仅由状态决定,输出组合逻辑电路的结构更加简单Moore需要待状态稳定以后才能输出。

​ ③ 输出状态上,Melay形的比Moore 的输出快一个时钟周期。(我们可以看到,一个(Melay)是在状态 S3 一个(Moore)是在 S4 才输出结果)

​ ④ Moore状态机同步输出,Mealy状态机异步输出;解释(原因),Mealy机器中,输入更改(输入信号并不是同步的)可能会在逻辑完成后立即导致输出更改, 当两台机器互连时出现大问题 ,如果不小心,可能会发生异步反馈。**解决方案:个人理解(不知道对不对) **,如果非要使用Mealy状态机,可以在输入端口接几级寄存器,来消除亚稳态,在判断的时候根据寄存器的输入信号,也可以基本上实现是同步输出.

Verilog 编写状态机的三种方式:

​ 一般来讲,状态机分为三种方式,分别是 一段、二段三段式状态机:

​ 原作者代码参考:https://zhuanlan.zhihu.com/p/362740453

eg: 功能描述: 这里写了一个自动售水机,设置了0.5 和 1 元的两种硬币,每瓶水的售出价格是 2元,往售货机投币,投币到2元或者超过2元以后,输出 矿泉水出售信号(water ),并且根据投入的价格,退还多余的钱(coin_back).

一段式状态机:

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

// 自动售水机 verilog的一段式状态机
module  auto_sell(
    input   clk,
    input       rst_n,
    input       coin_one,
    input       coin_half,
     
    output  reg     water,
    output  reg     coin_back
);
 
    parameter   ZERO        = 3'b000;
    parameter   HALF        = 3'b001;
    parameter   ONE         = 3'b010;
    parameter   ONE_HALF    = 3'b011;
    parameter   TWO         = 3'b100;
     
    //一段式状态机
    reg [2:0]       status;
     
    always@(posedge clk,negedge rst_n)begin
        if(!rst_n)
            begin
                status   <= ZERO;
                water    <= 0;
                coin_back <= 0;
            end
        else
            case(status)
                ZERO :
                        begin  
                            water    <= 0;
                            coin_back <= 0;
                            if(coin_half)
                                status <= HALF;
                            else if(coin_one)
                                status <= ONE;
                            else
                                status <= status;
                        end
                HALF :
                        begin  
                            water    <= 0;
                            coin_back <= 0; 
                            if(coin_half)
                                status <= ONE;
                            else if(coin_one)
                                status <= ONE_HALF;
                            else
                                status <= status;   
                        end
                ONE :  
                        begin  
                            water    <= 0;
                            coin_back <= 0;
                            if(coin_half)
                                status <= ONE_HALF;
                            else if(coin_one)
                                status <= TWO;
                            else
                                status <= status;               
                        end
                ONE_HALF :
                            begin  
                                if(coin_half)
                                    begin
                                        status <= TWO;
                                        water    <= 1'b0;
                                        coin_back <= 1'b0;
                                    end
                                else if(coin_one)
                                    begin
                                        status <= ZERO;
                                        water    <= 1'b1;
                                        coin_back <= 1'b0;
                                    end
                                else
                                    begin
                                        status <= status;   
                                        water    <= 1'b0;
                                        coin_back <= 1'b0;                                      
                                    end
                            end
                TWO :  
                        begin  
                            if(coin_half)
                                    begin
                                        status <= ZERO;
                                        water    <= 1'b1;
                                        coin_back <= 1'b0;
                                    end
                            else if(coin_one)
                                    begin
                                        status <= ZERO;
                                        water    <= 1'b1;
                                        coin_back <= 1'b1;
                                    end
                            else
                                    begin
                                        status <= status;   
                                        water    <= 1'b0;
                                        coin_back <= 1'b0;                                      
                                    end    
                        end
                default:
                            begin
                                status <= ZERO; 
                                water    <= 1'b0;
                                coin_back <= 1'b0;                                      
                            end
            endcase        
    end
 
endmodule

可以看到:使用一段式状态机,可以实现改功能, 但是阅读性很不友好(实际上,看到第一眼有点看不下去,太长了),而且里面描述了转移图,输入和输出.

二段式状态机 :

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

// 售货机 二段式状态机 方式1
module  auto_sell(
    input   clk,
    input       rst_n,
    input       coin_one,
    input       coin_half,
     
    output  reg     water,
    output  reg     coin_back
);
 
    parameter   ZERO        = 3'b000;
    parameter   HALF        = 3'b001;
    parameter   ONE         = 3'b010;
    parameter   ONE_HALF = 3'b011;
    parameter   TWO         = 3'b100;
 
    //--------------------二段式 1  ok--------------------------
    //二段式状态机
    reg [2:0]   c_status;
    reg [2:0]   n_status;
     
    //状态转移
    always@(posedge clk,negedge rst_n)begin
        if(!rst_n)
            c_status <= ZERO;
        else
            c_status <= n_status;
    end
     
    //描述状态转移规律以及输出
    always@(posedge clk,negedge rst_n)begin
        if(!rst_n)
            begin
                n_status <= ZERO;
                water <= 1'b0;
                coin_back <= 1'b0;  
            end
        else
            case(c_status)
                ZERO :
                        begin  
                            water <= 1'b0;
                            coin_back <= 1'b0;                          
                            if(coin_half)
                                n_status <= HALF;
                            else if(coin_one)
                                n_status <= ONE;
                            else
                                n_status <= ZERO;
                        end
                HALF :
                        begin  
                            water <= 1'b0;
                            coin_back <= 1'b0;                          
                            if(coin_half)
                                n_status <= ONE;
                            else if(coin_one)
                                n_status <= ONE_HALF;
                            else
                                n_status <= HALF;   
                        end
                ONE :  
                        begin  
                            water <= 1'b0;
                            coin_back <= 1'b0;                          
                            if(coin_half)
                                n_status <= ONE_HALF;
                            else if(coin_one)
                                n_status <= TWO;
                            else
                                n_status <= ONE;            
                        end
                ONE_HALF :
                            begin  
                                water <= 1'b0;
                                coin_back <= 1'b0;                              
                                if(coin_half)
                                        n_status <= TWO;
                                else if(coin_one)
                                    begin
                                        n_status <= ZERO;
                                        water <= 1'b1;
                                        coin_back <= 1'b0;  
                                    end
                                else
                                        n_status <= ONE_HALF;   
                            end
                TWO :  
                        begin  
                            water <= 1'b0;
                            coin_back <= 1'b0;                          
                            if(coin_half)
                                begin
                                    n_status <= ZERO;
                                    water <= 1'b1;
                                    coin_back <= 1'b0;                                      
                                end
                            else if(coin_one)
                                begin
                                    n_status <= ZERO;
                                    water <= 1'b1;
                                    coin_back <= 1'b1;                                          
                                end
                            else
                                n_status <= TWO;                                        
                        end
                default:
                            n_status <= ZERO;                                   
            endcase
    end
     
 
endmodule

​ 上面这种二段式有点类似与,一端=段式状态机,只是把状态机状态给寄存了一下,保证信号输出的稳定性,阅读方面和一段式比较类似。

第二种方式:

// 售货机 二段式状态机 方式2
module  auto_sell(
    input   clk,
    input       rst_n,
    input       coin_one,
    input       coin_half,
     
    output  reg     water,
    output  reg     coin_back
);
 
    parameter   ZERO        = 3'b000;
    parameter   HALF        = 3'b001;
    parameter   ONE         = 3'b010;
    parameter   ONE_HALF = 3'b011;
    parameter   TWO         = 3'b100;
 
//---------------------二段式  2 ok--------------------------------------
 
    //二段式状态机
    reg [2:0]   status;
     
    //状态转移
    always@(posedge clk,negedge rst_n)begin
        if(!rst_n)
            status <= ZERO;
        else
            begin
                case(status)
                    ZERO :
                            begin                          
                                if(coin_half)
                                    status <= HALF;
                                else if(coin_one)
                                    status <= ONE;
                                else
                                    status <= ZERO;
                            end
                    HALF :
                            begin                          
                                if(coin_half)
                                    status <= ONE;
                                else if(coin_one)
                                    status <= ONE_HALF;
                                else
                                    status <= HALF; 
                            end
                    ONE :  
                            begin                      
                                if(coin_half)
                                    status <= ONE_HALF;
                                else if(coin_one)
                                    status <= TWO;
                                else
                                    status <= ONE;              
                            end
                    ONE_HALF :
                                begin                              
                                    if(coin_half)
                                            status <= TWO;
                                    else if(coin_one)
                                        begin
                                            status <= ZERO;
                                        end
                                    else
                                            status <= ONE_HALF; 
                                end
                    TWO :  
                            begin                          
                                if(coin_half)
                                    begin
                                        status <= ZERO;                                 
                                    end
                                else if(coin_one)
                                    begin
                                        status <= ZERO;                                     
                                    end
                                else
                                    status <= TWO;                                          
                            end
                    default:
                                status <= ZERO;                                 
                endcase        
            end
    end
 
 
    //输出 时序逻辑
    always@(posedge clk,negedge rst_n)begin
        if(!rst_n)
            begin
                water <= 1'b1;
                coin_back <= 1'b0;  
            end
        else
            case(status)
                ONE_HALF:
                            begin
                                if(coin_one)
                                    begin
                                        water <= 1'b1;
                                        coin_back <= 1'b0;
                                    end
                                else
                                    begin
                                        water <= 1'b0;
                                        coin_back <= 1'b0;                              
                                    end
                            end
                TWO:
                            begin
                                if(coin_half)
                                    begin
                                        water <= 1'b1;
                                        coin_back <= 1'b0;
                                    end
                                else if(coin_one)
                                    begin
                                        water <= 1'b1;
                                        coin_back <= 1'b1;                              
                                    end
                                else
                                    begin
                                        water <= 1'b0;
                                        coin_back <= 1'b0;                                  
                                    end
                            end
                default:
                            begin
                                water <= 1'b0;
                                coin_back <= 1'b0;                                  
                            end        
            endcase
    end
 
endmodule

这种二段式就区分就比较明显了,可以看到,第一个always快里面的都是状态机转移信号,在这个语句中,只判断状态的跳转过程,而不看状态的一些操作。在第二个always 块里面,只判断在改过程需要做什么操作(执行什么动作)。相当二者将其分开,便于阅读和编写。

三段式状态机 :

​ 在两个always模块描述方法基础上,使用三个always模块,一个always模块采用同步时序描述状态转移,一个always采用组合逻辑判断状态转移条件,描述状态转移规律,另一个always模块描述状态输出(可以用组合电路输出,也可以时序电路输出)

// 自动售货机   三段式状态机
module  auto_sell(
    input   clk,
    input       rst_n,
    input       coin_one,
    input       coin_half,
     
    output  reg     water,
    output  reg     coin_back
);
 
    parameter   ZERO        = 3'b000;
    parameter   HALF        = 3'b001;
    parameter   ONE         = 3'b010;
    parameter   ONE_HALF = 3'b011;
    parameter   TWO         = 3'b100;
 
    //三段式状态机
    reg [2:0]   c_status;
    reg [2:0]   n_status;
     
    //状态转移
    always@(posedge clk,negedge rst_n)begin
        if(!rst_n)
            c_status <= ZERO;
        else
            c_status <= n_status;
    end
     
    //状态转移规律及状态输出,组合逻辑输出只与输入有关
    //如果有n_status = n_status,电路会出错;
    always@(*)begin
        case(c_status)
            ZERO :
                    begin  
                        if(coin_half)
                            n_status = HALF;
                        else if(coin_one)
                            n_status = ONE;
                        else
                            n_status = ZERO;
                    end
            HALF :
                    begin  
                        if(coin_half)
                            n_status = ONE;
                        else if(coin_one)
                            n_status = ONE_HALF;
                        else
                            n_status = HALF;   
                    end
            ONE :  
                    begin  
                        if(coin_half)
                            n_status = ONE_HALF;
                        else if(coin_one)
                            n_status = TWO;
                        else
                            n_status = ONE;            
                    end
            ONE_HALF :
                        begin  
                            if(coin_half)
                                n_status = TWO;
                            else if(coin_one)
                                n_status = ZERO;
                            else
                                n_status = ONE_HALF;                                       
                        end
            TWO :  
                    begin  
                        if(coin_half)
                            n_status = ZERO;
                        else if(coin_one)
                            n_status = ZERO;
                        else
                            n_status = TWO;        
                    end
            default:
                        n_status = ZERO;                                   
        endcase        
    end
 
    always@(posedge clk,negedge rst_n)begin
        if(!rst_n)
            begin
                water = 1'b1;
                coin_back = 1'b0;  
            end
        else
            case(c_status)
                ONE_HALF:
                            begin
                                if(coin_one)
                                    begin
                                        water = 1'b1;
                                        coin_back = 1'b0;
                                    end
                                else
                                    begin
                                        water = 1'b0;
                                        coin_back = 1'b0;                              
                                    end
                            end
                TWO:
                            begin
                                if(coin_half)
                                    begin
                                        water = 1'b1;
                                        coin_back = 1'b0;
                                    end
                                else if(coin_one)
                                    begin
                                        water = 1'b1;
                                        coin_back = 1'b1;                              
                                    end
                                else
                                    begin
                                        water = 1'b0;
                                        coin_back = 1'b0;                                  
                                    end
                            end
                default:
                            begin
                                water = 1'b0;
                                coin_back = 1'b0;                                  
                            end        
            endcase
    end
     
endmodule

​ 三段式状态机和二段式状态机是非常类似的,只不过是多加了一个 always 块来实现 FSM状态的一个寄存,使FSM做到了同步寄存器输出,消除了组合逻辑输出的不稳定与毛刺的隐患,而且更利于时序路径分组,一般来说在FPGA等可编程逻辑器件上的综合与布局布线效果更佳。

状态机设计标准:

第一,状态机要安全,是指FSM不会进入死循环,特别是不会进入非预知的状态,而且由于某些扰动(干扰)进入非设计状态,也能很快的恢复到正常的状态循环中来。(三段式状态机可以尽可能的避免状态输出的不稳定和毛刺的问题)

第二,状态机的设计要满足设计的面积和速度的要求。

第三,状态机的设计要清晰易懂、易维护。

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值