本文主要是介绍小梅哥Xilinx FPGA学习笔记16——FSM(状态机)的学习,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
目录
一、 状态机导读
1.1 理论学习
1.2 状态机的表示
1.3 状态机编码
1.4 状态机描述方式
二 、实战演练一(来自野火)
2.1 实验目标
2.2 模块框图
2.3 状态转移图绘制
2.4 设计文件
2.5 仿真测试文件
2.6 仿真结果
三、 实战演练二(来自野火)
3.1 实验目标
3.2 模块框图
3.3 状态转移图绘制
3.4 设计文件
3.5 仿真测试文件
3.6 仿真结果
四、 实战演练三(来自小梅哥)
4.1 实验目标
4.2 模块框图
4.3 端口功能描述
4.4 设计文件
4.5 仿真测试文件
4.6 仿真结果
五、 实战演练四(来自小梅哥)
5.1 实验目标
5.2 设计文件
5.2.1 顶层文件
5.2.2 串口接收文件
5.2.3 字符检测模块
5.3 引脚约束文件
5.4 板上验证
一、 状态机导读
1.1 理论学习
1.2 状态机的表示
1.3 状态机编码
1.4 状态机描述方式
二 、实战演练一(来自野火)
2.1 实验目标
2.2 模块框图


2.3 状态转移图绘制
2.4 设计文件
module simple_fsm(input sys_clk,input sys_rst_n,input pi_money,output reg po_cola);parameter IDLE = 0;//记住独热码,二进制码,格雷码的区别。parameter ONE = 1;parameter TWO = 2;reg [1:0]state;always@(posedge sys_clk or negedge sys_rst_n)if(!sys_rst_n)beginstate <= IDLE; po_cola <= 0;endelse begin case(state) IDLE: beginif(pi_money)beginstate <= ONE;po_cola <= 0; //可以使用两个时序逻辑分别描述状态转换和输出。endelsestate <= IDLE; endONE:if(pi_money)beginstate <= TWO;po_cola <= 0; endelsestate <= ONE; TWO:if(pi_money)beginstate <= IDLE;po_cola <= 1; endelsestate <= TWO; default:begin state <= IDLE;po_cola <= 0;endendcaseend
endmodule
2.5 仿真测试文件
`timescale 1ns / 1ps
module simple_fsm_test();reg sys_clk ;
reg sys_rst_n;
reg pi_money;
wire po_cola;simple_fsm simple_fsm_inst(.sys_clk(sys_clk),.sys_rst_n(sys_rst_n),.pi_money(pi_money),.po_cola(po_cola));initial sys_clk = 1;always #10 sys_clk = ~sys_clk;initial beginsys_rst_n = 0;pi_money = 0;#201;sys_rst_n = 1;#20000;pi_money = 1;#20;pi_money = 0;#20000;pi_money = 1;#20;pi_money = 0;#20000;pi_money = 1;#20;pi_money = 0;#20000;$stop;end
endmodule
2.6 仿真结果
三、 实战演练二(来自野火)
3.1 实验目标
3.2 模块框图
端口列表与功能总结如下面表格所示。
3.3 状态转移图绘制
3.4 设计文件
module hard_fsm(input sys_clk,input sys_rst_n,input pi_money_half,input pi_money_one,output reg po_cola,output reg po_money);parameter IDLE = 0,//使用二进制码HALF = 1,ONE = 2,ONE_HALF = 3,TWO = 4,TWO_HALF = 5;reg [2:0]state;always@(posedge sys_clk or negedge sys_rst_n)if(!sys_rst_n)beginstate <= IDLE; endelse begin case(state)IDLE:if(pi_money_half)beginstate <= HALF;endelse if(pi_money_one)beginstate <= ONE;endelse beginstate <= IDLE;endHALF:if(pi_money_half)beginstate <= ONE;endelse if(pi_money_one)beginstate <= ONE_HALF;endelse beginstate <= HALF;endONE:if(pi_money_half)beginstate <= ONE_HALF;endelse if(pi_money_one)beginstate <= TWO;endelse beginstate <= ONE;endONE_HALF:if(pi_money_half)beginstate <= TWO;endelse if(pi_money_one)beginstate <= IDLE;endelse beginstate <= ONE_HALF;end TWO:if(pi_money_half || pi_money_one)beginstate <= IDLE;endelse beginstate <= TWO;end default:state <= IDLE;endcaseendalways@(posedge sys_clk or negedge sys_rst_n)//使用两段式状态机写法if(!sys_rst_n)po_cola <= 0; else if(((state == TWO) && (pi_money_half == 1))||((state ==ONE_HALF) &&(pi_money_one == 1))||((state ==TWO) &&(pi_money_one == 1)))po_cola <= 1;elsepo_cola <= 0;always@(posedge sys_clk or negedge sys_rst_n)if(!sys_rst_n)po_money <= 0; else if((state == TWO) && (pi_money_one == 1))po_money <= 1; elsepo_money <= 0;
endmodule
3.5 仿真测试文件
`timescale 1ns/1ns
module hard_fsm_tb();//********************************************************************//
//****************** Parameter and Internal Signal *******************//
//********************************************************************////reg definereg sys_clk;reg sys_rst_n;reg pi_money_one;reg pi_money_half;reg random_data_gen;//wire definewire po_cola;wire po_money;//********************************************************************////***************************** Main Code ****************************////********************************************************************////初始化系统时钟、全局复位initial beginsys_clk = 1'b1;sys_rst_n <= 1'b0;#20sys_rst_n <= 1'b1;end//sys_clk:模拟系统时钟,每 10ns 电平翻转一次,周期为 20ns,频率为 50MHzalways #10 sys_clk = ~sys_clk;//random_data_gen:产生非负随机数 0、1always@(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1'b0)random_data_gen <= 1'b0;elserandom_data_gen <= {$random} % 2;//pi_money_one:模拟投入 1 元的情况always@(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1'b0)pi_money_one <= 1'b0;elsepi_money_one <= random_data_gen;//pi_money_half:模拟投入 0.5 元的情况always@(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1'b0)pi_money_half <= 1'b0;else
//取反是因为一次只能投一个币,即 pi_money_one 和 pi_money_half 不能同时为 1pi_money_half <= ~random_data_gen;//********************************************************************//
//*************************** Instantiation **************************//
//********************************************************************////------------------------complex_fsm_inst------------------------hard_fsm hard_fsm_inst(.sys_clk (sys_clk ), //input sys_clk.sys_rst_n (sys_rst_n ), //input sys_rst_n.pi_money_one (pi_money_one ), //input pi_money_one.pi_money_half (pi_money_half ), //input pi_money_half.po_cola (po_cola ), //output po_money.po_money (po_money ) //output po_cola
); endmodule
3.6 仿真结果
四、 实战演练三(来自小梅哥)
4.1 实验目标
4.2 模块框图
4.3 端口功能描述
4.4 设计文件
module hello(input sys_clk,input sys_rst_n,input [7:0]data_in,//输入数据input data_in_valid,//有效数据输入output reg check_ok//检测出来一个hello就出现一个高脉冲
);//定义5个状态localparam CHECK_h = 0,CHECK_e = 1,CHECK_l1 = 2,CHECK_l2 = 3, CHECK_o = 4;reg [2:0]state;//我使用的是新二段式状态机,小梅哥用的是一段式状态机。always@(posedge sys_clk or posedge sys_rst_n) if(!sys_rst_n) state <= CHECK_h; else begincase(state)CHECK_h:beginif(data_in_valid && data_in == "h")state <= CHECK_e;else state <= CHECK_h; end CHECK_e:beginif(data_in_valid && data_in == "e")//千万注意if还有else if的顺序问题,不然可能会造成检测不成功的现象出现。比如出现hhello就无法检测出来。state <= CHECK_l1;else if(data_in_valid && data_in == "h")state <= CHECK_e; else if(data_in_valid)state <= CHECK_h; elsestate <= CHECK_e; endCHECK_l1:if(data_in_valid && data_in == "l")state <= CHECK_l2;else if(data_in_valid && data_in == "h")state <= CHECK_e; else if(data_in_valid)state <= CHECK_h; elsestate <= CHECK_l1; CHECK_l2:if(data_in_valid && data_in == "l")state <= CHECK_o;else if(data_in_valid && data_in == "h")state <= CHECK_e; else if(data_in_valid)state <= CHECK_h; elsestate <= CHECK_l2; CHECK_o:if(data_in_valid && data_in == "o")state <= CHECK_h;//检测完毕回到起始状态else if(data_in_valid && data_in == "h")state <= CHECK_e; else if(data_in_valid)state <= CHECK_h; elsestate <= CHECK_o; endcaseend //输出单独控制逻辑
always@(posedge sys_clk or posedge sys_rst_n) if(!sys_rst_n) check_ok <= 0; else if((state == CHECK_o) && data_in_valid && (data_in == "o"))check_ok <= 1; else if(state == CHECK_h)check_ok <= 0; elsecheck_ok <= check_ok;
endmodule
4.5 仿真测试文件
`timescale 1ns / 1ps
`define CLK_PERIOD 20
module hello_tb();reg sys_clk;
reg sys_rst_n;
reg data_valid;
reg [7:0]data_in;
wire check_ok;hello hello(.sys_clk(sys_clk),.sys_rst_n(sys_rst_n),.data_in(data_in),.data_in_valid(data_valid),.check_ok(check_ok));initial sys_clk = 1;
always#(`CLK_PERIOD/2) sys_clk = ~sys_clk;initial beginsys_rst_n = 0;data_valid = 0;data_in = 0;#(`CLK_PERIOD*20);sys_rst_n = 1;#(`CLK_PERIOD*20 + 1);repeat(2)begingen_char("I");#(`CLK_PERIOD);gen_char("A");#(`CLK_PERIOD);gen_char("h");#(`CLK_PERIOD);gen_char("e");#(`CLK_PERIOD);gen_char("l");#(`CLK_PERIOD);gen_char("l");#(`CLK_PERIOD); gen_char("h");#(`CLK_PERIOD);gen_char("h");#(`CLK_PERIOD);gen_char("e");#(`CLK_PERIOD);gen_char("l");#(`CLK_PERIOD);gen_char("l");#(`CLK_PERIOD);gen_char("o");#(`CLK_PERIOD);gen_char("e");#(`CLK_PERIOD);gen_char("h");#(`CLK_PERIOD);gen_char("h");#(`CLK_PERIOD);gen_char("o");#(`CLK_PERIOD); end#200;$stop;endtask gen_char;input [7:0]char; begindata_in = char;data_valid = 1'b1;#(`CLK_PERIOD);data_valid = 1'b0;endendtask
endmodule
4.6 仿真结果
五、 实战演练四(来自小梅哥)
5.1 实验目标
5.2 设计文件
5.2.1 顶层文件
module fsm_hello_test(input sys_clk,input sys_rst_n,input uart_rxd,output reg Led
);wire [7:0]data_in;wire data_in_valid;wire check_ok;zdyz_rs232_rx #(.CLK_FREQ (5000_0000),//波特率设置.UART_BPS (115200))zdyz_rs232_rx(.sys_clk(sys_clk) , //系统时钟.sys_rst_n(sys_rst_n) , //系统复位,低有效.uart_rxd(uart_rxd) , //UART 接收端口.uart_rx_done(data_in_valid), //UART 接收完成信号,接收完成后就代表数据有效.uart_rx_data(data_in) //UART 接收到的数据送给字符检测模块作为输入
);hello hello(.sys_clk(sys_clk),.sys_rst_n(sys_rst_n),.data_in(data_in),.data_in_valid(data_in_valid),.check_ok(check_ok)
);//每检测一次,LED就要翻转一次
always@(posedge sys_clk or posedge sys_rst_n)if(!sys_rst_n)Led <= 0;else if(check_ok)Led <= ~Led;else Led <= Led;endmodule
5.2.2 串口接收文件
(个人觉得正点原子的串口接收模块比小梅哥的简单易懂,实用)
module zdyz_rs232_rx(input sys_clk , //系统时钟input sys_rst_n , //系统复位,低有效input uart_rxd , //UART 接收端口output reg uart_rx_done, //UART 接收完成信号output reg [7:0] uart_rx_data //UART 接收到的数据
);parameter CLK_FREQ = 5000_0000; //系统时钟频率
parameter UART_BPS = 115200 ; //串口波特率
localparam BAUD_CNT_MAX = CLK_FREQ/UART_BPS; //为得到指定波特率,对系统时钟计数 BPS_CNT 次reg uart_rxd_d0;
reg uart_rxd_d1;
reg rx_flag ; //接收过程标志信号
reg [3:0] rx_cnt ; //接收数据位计数器
reg [15:0] baud_cnt ; //波特率计数器(位宽为16,防止溢出)
reg [7:0 ] rx_data_t ; //接收数据寄存器wire start_flag;//开始接收的标志,下降沿到来。//打两拍:波特率时钟和系统时钟不同步,为异步信号,所以要进行打拍处理,防止产生亚稳态
always @(posedge sys_clk or negedge sys_rst_n) beginif(!sys_rst_n) beginuart_rxd_d0 <= 1'b0;uart_rxd_d1 <= 1'b0;endelse beginuart_rxd_d0 <= uart_rxd;uart_rxd_d1 <= uart_rxd_d0;end
end assign start_flag = (uart_rxd_d0 == 0)&&(uart_rxd_d1 == 1);//下降沿到来的表示方法
// rx_flag接收信号的拉高与拉低
always @(posedge sys_clk or negedge sys_rst_n) beginif(!sys_rst_n)rx_flag <= 1'b0;else if(start_flag) //检测到起始位rx_flag <= 1'b1; //接收过程中,标志信号 rx_flag 拉高//在停止位一半的时候,即接收过程结束,标志信号 rx_flag 拉低else if((rx_cnt == 4'd9) && (baud_cnt == BAUD_CNT_MAX/2 - 1))//rx_flag 要提前拉低,防止其影响下一帧数据的接收rx_flag <= 1'b0;elserx_flag <= rx_flag;end
//波特率的计数器计数逻辑
always @(posedge sys_clk or negedge sys_rst_n)begin if(!sys_rst_n) baud_cnt <= 0; else if(rx_flag)beginif(baud_cnt == BAUD_CNT_MAX - 1)baud_cnt <= 0;elsebaud_cnt <= baud_cnt + 1; endelsebaud_cnt <= 0;
end
//位计数实现逻辑
always @(posedge sys_clk or negedge sys_rst_n)begin if(!sys_rst_n)rx_cnt <= 0; else if(rx_flag)beginif(baud_cnt == BAUD_CNT_MAX - 1)rx_cnt <= rx_cnt + 1; elserx_cnt <= rx_cnt;endelserx_cnt <= 0;//其他情况下都为0,所以不用担心计数超过9,且其计数也不会超过9,当rx_flag为0时就不计数了
end
//根据 rx_cnt 来寄存 rxd 端口的数据
always @(posedge sys_clk or negedge sys_rst_n) beginif(!sys_rst_n)rx_data_t <= 0; else if(rx_flag)begin //系统处于接收过程时if(baud_cnt == BAUD_CNT_MAX/2 - 1)begin//判断 baud_cnt 是否计数到数据位的中间 case(rx_cnt)1:rx_data_t[0] <= uart_rxd_d1; //寄存数据的最低位2:rx_data_t[1] <= uart_rxd_d1;3:rx_data_t[2] <= uart_rxd_d1;4:rx_data_t[3] <= uart_rxd_d1;5:rx_data_t[4] <= uart_rxd_d1;6:rx_data_t[5] <= uart_rxd_d1;7:rx_data_t[6] <= uart_rxd_d1;8:rx_data_t[7] <= uart_rxd_d1;//寄存数据的高低位default:rx_data_t <= rx_data_t; endcaseendelse rx_data_t <= rx_data_t;endelserx_data_t <= 0;
end
//给接收完成信号和接收到的数据赋值
always @(posedge sys_clk or negedge sys_rst_n)begin if(!sys_rst_n)begin uart_rx_done <= 0; uart_rx_data <= 0;end//当接收数据计数器计数到停止位,且 baud_cnt 计数到停止位的中间时else if((rx_cnt == 4'd9) && (baud_cnt == BAUD_CNT_MAX/2 - 1))beginuart_rx_done <= 1; //拉高接收完成信号uart_rx_data <= rx_data_t;//并对 UART 接收到的数据进行赋值end else beginuart_rx_done <= 0; uart_rx_data <= uart_rx_data; end
end
endmodule
5.2.3 字符检测模块
module hello(input sys_clk,input sys_rst_n,input [7:0]data_in,//输入数据input data_in_valid,//有效数据输入output reg check_ok//检测出来一个hello就出现一个高脉冲
);//定义5个状态localparam CHECK_h = 0,CHECK_e = 1,CHECK_l1 = 2,CHECK_l2 = 3, CHECK_o = 4;reg [2:0]state;//我使用的是新二段式状态机,小梅哥用的是一段式状态机。always@(posedge sys_clk or posedge sys_rst_n) if(!sys_rst_n) state <= CHECK_h; else begincase(state)CHECK_h:beginif(data_in_valid && data_in == "h")state <= CHECK_e;else state <= CHECK_h; end CHECK_e:beginif(data_in_valid && data_in == "e")//千万注意if还有else if的顺序问题,不然可能会造成检测不成功的现象出现。比如出现hhello就无法检测出来。state <= CHECK_l1;else if(data_in_valid && data_in == "h")state <= CHECK_e; else if(data_in_valid)state <= CHECK_h; elsestate <= CHECK_e; endCHECK_l1:if(data_in_valid && data_in == "l")state <= CHECK_l2;else if(data_in_valid && data_in == "h")state <= CHECK_e; else if(data_in_valid)state <= CHECK_h; elsestate <= CHECK_l1; CHECK_l2:if(data_in_valid && data_in == "l")state <= CHECK_o;else if(data_in_valid && data_in == "h")state <= CHECK_e; else if(data_in_valid)state <= CHECK_h; elsestate <= CHECK_l2; CHECK_o:if(data_in_valid && data_in == "o")state <= CHECK_h;//检测完毕回到起始状态else if(data_in_valid && data_in == "h")state <= CHECK_e; else if(data_in_valid)state <= CHECK_h; elsestate <= CHECK_o; endcaseend //输出单独控制逻辑
always@(posedge sys_clk or posedge sys_rst_n) if(!sys_rst_n) check_ok <= 0; else if((state == CHECK_o) && data_in_valid && (data_in == "o"))check_ok <= 1; else if(state == CHECK_h)check_ok <= 0; elsecheck_ok <= check_ok;
endmodule
5.3 引脚约束文件
set_property PACKAGE_PIN U18 [get_ports sys_clk]
set_property IOSTANDARD LVCMOS33 [get_ports sys_clk]
set_property IOSTANDARD LVCMOS33 [get_ports Led]
set_property IOSTANDARD LVCMOS33 [get_ports sys_rst_n]
set_property IOSTANDARD LVCMOS33 [get_ports uart_rxd]
set_property PACKAGE_PIN F20 [get_ports sys_rst_n]
set_property PACKAGE_PIN K16 [get_ports uart_rxd]
set_property PACKAGE_PIN G17 [get_ports Led]
5.4 板上验证
本文所有程序均经过板上验证过,均正常,放心使用参考。
这篇关于小梅哥Xilinx FPGA学习笔记16——FSM(状态机)的学习的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!