洛阳新光建站系统,wordpress一个分类名字,网站群系统建设标准,工程建设管理目录
一、 状态机导读
1.1 理论学习
1.2 状态机的表示
1.3 状态机编码
1.4 状态机描述方式
二 、实战演练一#xff08;来自野火#xff09;
2.1 实验目标
2.2 模块框图
2.3 状态转移图绘制
2.4 设计文件
2.5 仿真测试文件
2.6 仿真结果
三、 实战演练二来自野火
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 板上验证 一、 状态机导读 状态机大家一定都听说过为什么需要状态机呢通过前面的学习我们都知道FPGA 是并行执行的如果我们想要处理具有前后顺序的事件该怎么办呢这时就需要引入状态机了。本文将从原理、实例、应用为大家总结了状态机设计和实现的方法。 1.1 理论学习 状态机简写为 FSM Finite State Machine也称为同步有限状态机我们一般简称为状态机之所以说“同步”是因为状态机中所有的状态跳转都是在时钟的作用下进行的而“有限”则是说状态的个数是有限的。状态机根据影响输出的原因分为两大类即 Moore 型状态机和 Mealy 型状态机 Moore 型状态机的输出只和当前状态有关而与输入无关 Mealy 型状态机的输出不仅和当前状态有关还和输入有关。 1.2 状态机的表示 那么状态机该如何表示呢我们会在一些数据手册中经常看到如下图 所示的图这种图就是用于表达状态机的状态转移图。有了状态转移图我们就可以对状态机想要表达的东西一清二楚包括用代码去实现都会变得很容易所以说如何根据实际需求设计抽象出符合要求的状态机是非常关键的。 1.3 状态机编码 状态机可根据控制信号按照预先设定的状态进行状态转移这就出现了如何对状态进行有效编码的问题。编码方式最简单的就是直接使用 二进制编码 进行表示除此之外还有使用 格雷码、独热码 。假设有八个状态从 A 到 H 利 用不同的编码格式分别如下表所示。 从上表中可以发现独热码每一个状态均使用一个寄存器 在与状态比较时仅仅需要比较一位 相比其他译码电路简单格雷码所需寄存器数与二 进制码一样译码复杂但相邻位只跳动一位一般用于异步多时钟域多 bit 位 的转换如异步 FIFO 二进制码最为常见的编码方式所用寄存器少译码 较复杂。 按照 FPGA 厂商给的建议选择哪一种编码格式是要根据状态机的复杂度、 器件类型以及从非法状态中恢复出来的要求来确定。在使用不同的编码格式生 成出来的 RTL 视图中可以看出二进制比独热码使用更少的寄存器。二进制用 7 个寄存器就可以实现 100 个状态的状态机但是独热码就需要 100 个寄存器。 但是另一方面虽然独热码使用更多的寄存器但是其组合逻辑相对简单。一般 在 CPLD 中由于提供较多的组合逻辑资源而推荐使用前者而在 FPGA 中提 供较多的时序逻辑而推荐使用后者。 1.4 状态机描述方式 状态机描述方式可分为一段式、两段式以及三段式。 一段式整个状态机写到一个 always 模块里面。在该模块中既描述状态转 移又描述状态的输入和输出。 两段式用两个 always 模块来描述状态机。其中一个 always 模块采用同步 时序描述状态转移另一个模块采用组合逻辑判断状态转移条件若也使用时序逻辑就称为新二段式描述状态转 移规律及其输出。 三段式在两个 always 模块描述方法基础上使用三个 always 模块。一个 always 模块采用同步时序描述状态转移一个 always 采用组合逻辑判断状态转 移条件描述状态转移规律另一个 always 模块描述状态输出 ( 可以用组合电路 输出也可以时序电路输出 ) 。 二 、实战演练一来自野火
2.1 实验目标 我们都在大街见过自动售卖饮料的可乐机殊不知整个可乐机系统的售卖过程就可以很好的用状态机来实现为了学习我们先来实现一个简单的可乐机系统。可乐机每次只能投入 1 枚 1 元硬币且每瓶可乐卖 3 元钱即投入 3 个硬币就可以让可乐机出可乐如果投币不够 3 元想放弃投币需要按复位键否则之前投入的钱不能退回。 2.2 模块框图 首先必不可少的是时钟和复位信号其次是投币 1 元的输入信号我们取名为 pi_money 可乐机输出我们购买的可乐取名为 po_cola 。根据上面的分析设计出的 框图如下图 所示。 端口列表与功能总结如下面表格 所示。 2.3 状态转移图绘制 斜杠左边表达的是状态的输入斜杠右边表达的是状态的输出结构非常的简单各状态之间的功能、跳转的条件、输入输出都能够在状态转移图中非常清楚的表达出来。 1 、输入 投入 1 元硬币 2 、输出 出可乐、不出可乐 3 、状态 可乐机中有 0 元、可乐机中有 1 元、可乐机中有 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
endmodule2.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
endmodule2.6 仿真结果 三、 实战演练二来自野火
3.1 实验目标 上一部分中的可乐机比较简单只能投 1 元的硬币但是我们生活中还有 0.5 元的硬币所以我们在本章中将可乐机设计的稍微复杂一些做成既可以投 1 元的硬币也可以投0.5 元的硬币然后我们把可乐的定价改为 2.5 元一瓶。我们增加了可乐机的复杂度而引入了新的问题 可乐定价为 2.5 元一瓶可投入 0.5 元、1 元硬币投币不够 2.5 元需要按复位键退回钱款投币超过 2.5 元需找零。 3.2 模块框图 首先时钟和复位信号依然是必不可少的输入信号输入信号还有投币除了可以投 1 元外还可以投 0.5元所以我们将投币 1 元的输入信号取名为 pi_money_one 将投币 0.5 元的输入信号取名 为 pi_money_half可乐机的输出除了可乐还可能会有找零找零的结果只有一种即找回0.5元我们将可乐机输出购买可乐的信号取名为 po_cola找零的信号取名为po_money。根据上面的分析设计出的框图如下图所示。 端口列表与功能总结如下面表格所示。 3.3 状态转移图绘制 1 、输入 投入 0.5 元硬币、投入 1 元硬币 2 、输出 不出可乐 / 不找零、出可乐 / 不找零、出可乐 / 找零 3 、状态 可乐机中有 0 元、可乐机中有 0.5 元、可乐机中有 1 元、可乐机中有 1.5 元、可 乐机中有 2 元、可乐机中有 2.5 元、可乐机中有 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;
endmodule3.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 1b1;sys_rst_n 1b0;#20sys_rst_n 1b1;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 1b0)random_data_gen 1b0;elserandom_data_gen {$random} % 2;//pi_money_one:模拟投入 1 元的情况always(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n 1b0)pi_money_one 1b0;elsepi_money_one random_data_gen;//pi_money_half:模拟投入 0.5 元的情况always(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n 1b0)pi_money_half 1b0;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
); endmodule3.6 仿真结果 四、 实战演练三来自小梅哥
4.1 实验目标 实现字符串检测状态机 为了实现对字符串“ hello ”的检测首先画出其状态转移图如下图所示。 由上图可以看出如果在状态不符合转换条件那么状态机要么回到初态检测 h 出现要么回到状态 e 不满足向后转移条件但是输入字符为‘ h ’ 且每一个状态都有特定的方向其输出仅由当前状态决定因此这是一个摩尔型状态机。 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 1b1;#(CLK_PERIOD);data_valid 1b0;endendtask
endmodule4.6 仿真结果 五、 实战演练四来自小梅哥
5.1 实验目标 通过上面字符串检测状态机的学习结合前面章节学习的串口接收模块可以设计一个串口接收字符串检测系统。整个系统的结构框图如下图所示。系 统功能为 FPGA 通过串口接收模块接收 PC 通过串口发送的字符串数据流然后通过字符串检测模块对字符串进行检测每次完整接收到“ hello ”字符串就让 LED 灯翻转。 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;endmodule5.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 1b0;uart_rxd_d1 1b0;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 1b0;else if(start_flag) //检测到起始位rx_flag 1b1; //接收过程中标志信号 rx_flag 拉高//在停止位一半的时候即接收过程结束标志信号 rx_flag 拉低else if((rx_cnt 4d9) (baud_cnt BAUD_CNT_MAX/2 - 1))//rx_flag 要提前拉低防止其影响下一帧数据的接收rx_flag 1b0;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 4d9) (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
endmodule5.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 板上验证
本文所有程序均经过板上验证过均正常放心使用参考。