FPGA学习笔记(七)——FSM(Finite State Machine,有限状态机)设计

  FPGA设计中,最重要的设计思想就是状态机的设计思想!状态机的本质就是对具有逻辑顺序时序规律的事件的一种描述方法,它有三个要素:状态、输入、输出:状态也叫做状态变量(比如可以用电机的不同转速作为状态),输出指在某一个状态的特定输出,输入指状态机中进入每个状态的条件。根据状态机的输出是否和输入有关,可分为摩尔(Moore)型状态机和米勒型(Mealy)状态机:摩尔型状态机的输出只取决于当前状态,而米勒型状态机的输出不仅取决于当前状态,还与当前输入有关。通常,我们描述状态机有三种方法:状态转移图、状态转移表、HDL描述,状态转移图直观,设计用,而HDL语言方便描述,实现时用。  那么,如何用HDL描述一个好的状态机呢?主要有以下四点:安全、稳定性高速度快面积小设计清晰在描述过程中,我们会引用两个新的verilog语法:localparam描述参数(等价于parameter)以及用task/endtask将输出功能块封装,增强代码可读性;描述状态机的关键是要描述清楚状态机的三大要素:如何进行状态转移?每个状态的输出?状态输出是否和输入条件相关?通常有三种写法,下面通过一个实例说明;实例.检测“Hello”序列状态机  1、功能:在输入一串字符中检测“Hello”序列,检测到后将led状态进行翻转;
  2、根据设计的FSM状态转移图(visio绘制):  3、一段式描述法:在一个always块里既描述状态转移,又描述状态的输入和输出;    verilog代码如下:
//检测“Hello”后led状态翻转

module check_hello(
    input  clk,         //50M时钟信号
    input  rst,         //低电平复位
    input  [7:0]asci,   //字符输入
    output reg  led     //控制led
);
    //状态寄存器
    reg  [4:0]NS;     //nextstate
    
    //状态独热编码
    localparam 
        CHECK_H   = 5'b0_0001,
        CHECK_e     = 5'b0_0010,
        CHECK_la  = 5'b0_0100,
        CHECK_lb  = 5'b0_1000,
        CHECK_o   = 5'b1_0000;
        
    //一段式状态机
    always@(posedge clk,negedge rst)
        if(!rst)begin
            NS  <= CHECK_H;
            led <= 1'b1;    //led熄灭
        end
        else begin    
            case(NS)
                CHECK_H:
                    begin
                        if(asci == "H")
                            NS <= CHECK_e;
                        else
                            NS <= CHECK_H;
                    end
                CHECK_e:
                    begin
                        if(asci == "e")
                            NS <= CHECK_la;
                        else
                            NS <= CHECK_H;
                    end
                CHECK_la:
                    begin
                        if(asci == "l")
                            NS <= CHECK_lb;
                        else
                            NS <= CHECK_H;
                    end
                CHECK_lb:
                    begin
                        if(asci == "l")
                            NS <= CHECK_o;
                        else
                            NS <= CHECK_H;
                    end
                CHECK_o:
                    begin
                        if(asci == "o")
                            led <= ~led;
                        else
                            led <= led;
                        NS <= CHECK_H;
                    end
                default     //排除任意情况,增强FSM安全性
                    begin
                        NS  <= CHECK_H;
                        led <= 1'b1;
                    end
            endcase
    end 
endmodule 
  testbench测试文件如下:
 `timescale 1ns/1ps
 `define    clk_period 20
 
 module check_hello_tb();
 
        reg  clk;         //50M时钟信号
        reg  rst;         //低电平复位
        reg  [7:0]asci;   //字符输入
        wire  led;        //控制led
        
        //例化测试模块
        check_hello  check_hello_test(
            .clk(clk),         //50M时钟信号
            .rst(rst),         //低电平复位
            .asci(asci),       //字符输入
            .led(led)          //控制led
        );
        
        //产生50M时钟信号
        initial clk = 1;
        always #(`clk_period / 2)clk <= ~clk;
            
        //开始测试
        initial begin
            rst  = 0;          //系统复位
            asci = 3'bx;
        #(`clk_period * 2);
            rst = 1;
        #(`clk_period);
            asci = "H";
        #(`clk_period);
            asci = "e";
        #(`clk_period);
            asci = "l";
        #(`clk_period);
            asci = "l";
        #(`clk_period);
            asci = "o";
        #(`clk_period);
            asci = "2";
        #(`clk_period);
            asci = "e";
        #(`clk_period);
            asci = "h";
        #(`clk_period);
            asci = "l";    
        #(`clk_period);
            $stop;
        end 
endmodule
  测试结果如下,可以看到,刚开始输入数据是任意数据,FSM为CHECK_H状态,led输出高电平,保持熄灭;当检测到Hello序列时,led输出低电平,状态翻转:  状态转移图如下,可以看到按照预定设计执行:  综合出来的电路图如下,可以看到FSM实现的重点在于状态寄存器,耗费资源很少,由综合报告也可看出:    在一段式描述方法中可以看到,虽然一个alaways块就可以解决问题,但描述不清晰,不利于维护修改,并且不利用附加约束,不利于综合其和布局布线器对设计的优化;  4、两段式描述法:一个always块描述状态转移,另一个always块描述状态判断转移条件  verilog代码如下:
//检测“Hello”后led状态翻转

module check_hello(
    input  clk,         //50M时钟信号
    input  rst,         //低电平复位
    input  [7:0]asci,//字符输入
    output reg led     //控制led
);
    //状态寄存器
    reg  [4:0]NS;     //nextstate
    reg  [4:0]CS;    //currentstate
    
    //状态独热编码
    localparam 
        CHECK_H   = 5'b0_0001,
        CHECK_e     = 5'b0_0010,
        CHECK_la  = 5'b0_0100,
        CHECK_lb  = 5'b0_1000,
        CHECK_o   = 5'b1_0000;
        
    //两段式状态机
    //第一个always块描述状态转移
    always@(posedge clk,negedge rst)
        if(!rst)
            CS  <= CHECK_H;
        else
            CS <= NS;        //状态转移到下一状态
            
    //第二个always块描述状态输出以及判断状态转移        
    always@(*)
        case(CS)
            CHECK_H:
                begin
                    led <= 1'b1;
                    if(asci == "H")
                        NS <= CHECK_e;
                    else
                        NS <= CHECK_H;
                end
            CHECK_e:
                begin
                    led <= 1'b1;
                    if(asci == "e")
                        NS <= CHECK_la;
                    else
                        NS <= CHECK_H;
                end
            CHECK_la:
                begin
                    led <= 1'b1;
                    if(asci == "l")
                        NS <= CHECK_lb;
                    else
                        NS <= CHECK_H;
                end
            CHECK_lb:
                begin
                    led <= 1'b1;
                    if(asci == "l")
                        NS <= CHECK_o;
                    else
                        NS <= CHECK_H;
                end
            CHECK_o:
                begin
                    if(asci == "o")
                        led <= 1'b0;
                    else
                        led <= 1'b1;
                    NS <= CHECK_H;
                end
            default 
                begin
                    NS  <= CHECK_H;
                    led <= 1'b1;
                end
        endcase
endmodule
            
        
    
           
  

相关内容推荐