利用FPGA构建一个状态机控制小灯亮灭,以便理解状态机的实际十几下应用。
1.例程介绍:
假设有输入数据流,这个数据流会任意输入A~Z或a~z字母,其中某一段时刻会接连输入“Hellow”。现在设计一个状态机,当检测到Hellow时LED灯亮灭翻转。
2.设计分析:
整个检测过程可以拆分成以下各个状态:
1.等待H到来,如果检测到H那么进入2开始检测e,否则一直等待H;
2.检测是否为e,如果是e->进入3开始检测l,如果不是e->返回1继续等待H;
3.检测是否为l,如果是l->进入4开始检测l,如果不是l->返回1继续等待H;
4.检测是否为l,如果是l->进入5开始检测o,如果不是l->返回1继续等待H;
5.检测是否为o,如果是o->进入6开始检测w,如果不是o->返回1继续等待H;
6.检测是否为w,如果是w->翻转LED并且返回1等待下一个H;如果不是w->返回1继续等待H;
3.rtl程序:
module Hellow(clk50M,rst_n,data,led);
input clk50M;
input rst_n;
input [7:0] data; //输入的数据流是8位2进制ASCII码
output reg led;
reg [5:0] state; //各个状态使用编码密度低的独热码。这里6个状态,所以6位就够用
localparam //定义状态对应的独热码,以便case语句中调用(localparam用法与parameter一样)
CHECK_H=6'b000001,
CHECK_e=6'b000010,
CHECK_la=6'b000100,
CHECK_lb=6'b001000,
CHECK_o=6'b010000,
CHECK_w=6'b100000;
always @ (posedge clk50M or negedge rst_n) begin
if(!rst_n) begin //复位的时候LED灭,状态为1“等待H到来”
led<=1'd0;
state<=CHECK_H;
end
else begin
case(state) //case语句检测属于什么状态
CHECK_H: //例如:当是CHECK_H状态时
if(data=="H") //每次进入一个数据都判断data是不是H
state<=CHECK_e; //如果是->进入下一状态
else
state<=CHECK_H; //如果不是->返回CHECK_H状态
CHECK_e:
if(data=="e")
state<=CHECK_la;
else
state<=CHECK_H;
CHECK_la:
if(data=="l")
state<=CHECK_lb;
else
state<=CHECK_H;
CHECK_lb:
if(data=="l")
state<=CHECK_o;
else
state<=CHECK_H;
CHECK_o:
if(data=="o")
state<=CHECK_w;
else
state<=CHECK_H;
CHECK_w:
if(data=="w") begin
led<=~led;
state<=CHECK_H;
end
else
state<=CHECK_H;
default:
state<=CHECK_H;
endcase
end
end
endmodule
注意:
最后加上default,避免输入无对应的输出产生latch锁存器;6位独热码只用了6个数值,当state为其他数值时执行default后面的语句。
4.testbench测试文件:
`timescale 1ns/1ps
`define clock_period 20
module Hellow_tb;
reg clk;
reg rst_n;
reg [7:0] ASCII;
wire led;
Hellow Hellow1(
.clk50M(clk),
.rst_n(rst_n),
.data(ASCII),
.led(led)
);
initial clk=1;
always #(`clock_period/2) clk=~clk;
initial begin
rst_n=0;
ASCII=0;
#(`clock_period*200);
rst_n=1;
forever begin
ASCII="a"; //abc
#(`clock_period);
ASCII="b";
#(`clock_period);
ASCII="c";
#(`clock_period);
ASCII="H"; //Hellow
#(`clock_period);
ASCII="e";
#(`clock_period);
ASCII="l";
#(`clock_period);
ASCII="l";
#(`clock_period);
ASCII="o";
#(`clock_period);
ASCII="w";
#(`clock_period);
end
end
endmodule
注意:
1.这里设置仿真数据流时设置了一个干扰数据(abc),目的就是为了检验是不是只有Hellow才能实现状态机功能;
2.初始化激励数据流时使用的是forever语句,程序会一直跑下去;我们需要在配置modelsim中手动设定仿真时间;
3.Quartus中“H”完全等价于大写H对应的16进制数据0x48。
5.波形图:
上图可以看到LED的翻转。
上图通过前仿真可以看到,每次翻转与时钟上升沿是对齐的。
上图后仿真可以观察到是真实情况。每次检测到w后,下一拍LED才翻转 。