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