小梅哥FPGA学习笔记

一、38译码器
功能: 译码器其任一时刻的稳态输出,仅仅与该时刻的输入变量的取值有关,它是一种多输入多输出的组合逻辑电路,负责将二进制代码翻译为特定的对象(如逻辑电平等)。38译码器,即将3种输入翻译成8种输出状态。

module led_test(a,b,key_in,led_out);input a; //输入端口ainput b; //输入端口binput key_in; //按键输入output led_out; //led控制端口assign led_out = (key_in == 0)? a : b;endmodule

testbench文件

`timescale 1ns/1psmodule led_test_tb;//激励信号定义,对应连接到待测试模块的输入端口reg signal_a;reg signal_b;reg signal_c;//待测信号定义,对应连接到待测试模块的输出端口wire led;//例化待测试模块led_test led_test0(.a(signal_a),.b(signal_b),.key_in(signal_c),.led_out(led));//产生激励initial beginsignal_a=0;signal_b=0;signal_c=0;#100;//延时100nssignal_a=0;signal_b=0;signal_c=1;#100;//延时100nssignal_a=0;signal_b=1;signal_c=0;#100;//延时100nssignal_a=0;signal_b=1;signal_c=1;#100;//延时100nssignal_a=1;signal_b=0;signal_c=0;#100;//延时100nssignal_a=1;signal_b=0;signal_c=1;#100;//延时100nssignal_a=1;signal_b=1;signal_c=0;#100;//延时100nssignal_a=1;signal_b=1;signal_c=1;#200;$stop;//停止仿真endendmodule

Rtl Simulation

二、计数器
功能: 设计一个计数器,使其使能载板LED每500ms,状态翻转一次。核心板晶振为50MHz,也就是说时钟周期为20ns,这样可以计算出:次数 = 500_000_000ns / 20ns =25_000_000次,也就是需要一个至少25位的计数器——且每当计数次数达到目标值时清零重新计算。

module counter(Clk50M,Res_n,led);input Clk50M;  //系统时钟,50Minput Res_n;  //全局复位,低电平复位output  reg led;  //led输出reg [24:0]cnt;  //定义十进制计数器寄存器//计数器计数进程always @(posedge Clk50M or negedge Res_n)  //时钟上升沿和复位信号下降沿if(Res_n == 1'b0)cnt <= 25'd0;else if(cnt == 25'd24_999_999)cnt <= 25'd0;elsecnt <= cnt + 1'b1;//led输出控制always @(posedge Clk50M or negedge Res_n)if(Res_n ==1'b0)led <=1'b1;  //高电平led灭else if(cnt == 25'd24_999_999)led <= ~led;else led <=led;
endmodule

或者合并起来写也是可以的:

module counter(Clk50M,Res_n,led);input Clk50M;  //系统时钟,50Minput Res_n;  //全局复位,低电平复位output  reg led;  //led输出reg [24:0]cnt;  //定义十进制计数器寄存器always @(posedge Clk50M or negedge Res_n)  //时钟上升沿和复位信号下降沿if(Res_n == 1'b0)begincnt <= 25'd0;led <=1'b1;  //高电平led灭endelse if(cnt == 25'd24_999_999)begincnt <= 25'd0;led <= ~led;endelsebegincnt <= cnt + 1'b1;led <=led;endendmodule

testbench文件

`timescale 1ns/1ns
`define clock_period 20module counter_tb;reg clk;reg res_n;wire led;counter counter0(.Clk50M(clk),.Res_n(res_n),.led(led));initial clk =1;always #(`clock_period/2) clk = ~clk;  //延时20nsinitial beginres_n = 1'b0;#(`clock_period *200);res_n = 1'b1;#2000000000;$stop;endendmodule

Rtl Simulation
(略)

三、IP核计数器
1.创建工程后点击:Tools——Mega Wizard Plug-In Manager,以此启动 Mega Wizard 插件管理器。
2.选择创建一个新的定制IP。3.如图选择counter里的LPM_COUNTER,并且将生成文件保留在ip文件夹中,名字定义为counter。

4.计数器位数可以自选,这里选择4位,计数方式为递增计数。


5.
Plain binary:满数输出
Module,with a count modules of:设置计数最大值
Clock Enable:时钟使能信号
Count Enable:计数使能信号
Carry-in:进位链输入
Carry-out:进位链输出


6.
可选同步(异步)复位、加载和设置输入


7.确认把生成的IP核添加到工程中。

testbench文件

四、BCD计数器
功能: BCD码用4位二进制来表示十进制数中的0~9。主要应用之一是数管码。

五、阻塞赋值与非阻塞赋值
阻塞赋值:后面的语句必须等到当前的赋值语句执行完毕才能执行。操作符:“=”
非阻塞赋值:当前的赋值语句不会阻断其后的语句。操作符:“<=”
1.时序电路建模时,用非阻塞赋值。
2.锁存器电路建模时,用非阻塞赋值。
3.用always块建立组合逻辑模型时,用阻塞赋值。
4.在同一个always块中建立时序和组合逻辑电路时,用非阻塞赋值。
5.在同一个always块中不要既用非阻塞赋值又用阻塞赋值。
6.不要在一个以上的always块中为同一个变量赋值。
7.用$strobe系统任务来显示用非阻塞赋值的变量值。
8.在赋值时不要使用 #0 延迟。

六、状态机
功能: 状态机全称有限状态机,是表示有限个状态以及在这些状态之间的转移和动作等行为的数学模型。
状态机的种类: 摩尔型状态机(不直接依赖当前状态)和米利型状态机(输出还与当前状态有关)。
状态机的描述方式: 一段式,两段式和三段式。
一段式: 整个状态机写在一个always模块里面,在该模块中既描述状态转移,又描述状态的输入和输出。
两段式: 用两个always模块来描述状态机,其中一个always模块采用同步时序描述状态转移;另一个模块采用组合逻辑判断转移状态条件,描述状态转移规律以及输出。
三段式: 在两个always模块描述方法的基础上,使用第三个always模块,一个always模块采用同步时序描述状态移动,一个always采用组合逻辑判断状态转移条件,描述状态转移规律,另外一个always模块描述状态输出(可以用组合电路输出,也可以用时序电路输出)。

module Hello(Clk,Res_n,data,led);input Clk;  //50Minput Res_n;  //复位信号input [7:0]data;  //8位位宽的输入数据output led;//用独热码的编码方式定义状态
//文件内部使用,无法在外部被更改localparamCHECK_H = 5'b0_0001,CHECK_e = 5'b0_0010,CHECK_la = 5'b0_0100,CHECK_lb = 5'b0_1000,CHECK_o = 5'b1_0000;reg [4:0] state;  //状态所需寄存器,独热码为5位位宽always @(posedge Clk or negedge Res_n)if(!Res_n)beginled <= 1'b1;state <= CHECK_H;endelse begin case(state)CHECK_H:if(data == "H")state <= CHECK_e;elsestate <= CHECK_H;CHECK_e:if(data == "e")state <= CHECK_la;elsestate <= CHECK_H;CHECK_La:if(data == "l")state <= CHECK_lb;elsestate <= CHECK_H;CHECK_lb:if(data == "l")state <= CHECK_o;elsestate <= CHECK_H;CHECK_o:beginstate <= CHECK_H;if(data == "o")led <= ~led;elseled <= led;enddefault:state <=CHECK_H;endcaseendendmodule

七、独立按键消抖实验
实现功能: 每次按下按键0,4个LED显示状态以二进制加法格式加1;每次按下按键1,4个LED显示状态以二进制加法格式减1。但是由于实际波形中会出现抖动状况,所以要通过硬件电路或者逻辑设计的方法来消除。
消除抖动的方法:
1.利用RS触发器进行硬件消抖。
2.通过利用电容器和电阻对噪声波形积分,吸收因触点跳动产生的噪声。
3.通过555定时器组成的单稳态触发器可以消除开关的抖动。
异步信号与同步信号:
同步信号:变化永远发生在系统时钟的上升沿时刻。
异步信号:与系统时钟无关。系统时钟采集信号时可能会采集到亚稳态的信号亚稳态——D触发器——震荡——稳定到0/1(不确定)——再加一级寄存器后,在下一个时钟信号输出稳定(但仍不确定时0/1)
使用两级寄存器对异步信号进行处理,虽然无法确定0/1状态,但是大概率能达到稳定态。

按键消抖代码(元件)

module key_filter(Clk,Res_n,key_in,key_flag,key_state);input Clk;input Res_n;input key_in;  //按键信号output reg key_flag;  //使能信号表示输出结果(按键一次稳定后产生一个脉冲信号)output reg key_state;  //表示按键的稳定状态,没有按下时为稳定值(抖动也认为是高电平)
//这里的key_in相对于FPGA内部信号来说是一个异步信号
//所以这里需要对其使用两级同步的触发器进行异步信号的同步化(两级寄存器)
//边缘检测+异步信号的处理//对外部输入的异步信号进行同步处理(消除亚稳态问题)reg key_in_a,key_in_b;always@(posedge Clk or negedge Res_n)if(!Res_n)begin  //复位初始化key_in_a = 1'b0;key_in_b = 1'b0;endelse beginkey_in_a <= key_in;  key_in_b <= key_in_a;endreg key_tmpa,key_tmpb;  //寄存两次状态wire pedge,nedge;  //输出高电平和低电平//使用D触发器存储两个相邻时钟上升沿时外部输入信号的电平状态(已经同步到系统时钟域中)always@(posedge Clk or negedge Res_n)if(!Res_n)begin  //复位初始化key_tmpa <= 1'b0;key_tmpb <= 1'b0;endelse beginkey_tmpa <= key_in_b;key_tmpb <= key_tmpa;end//判断产生了高电平还是产生了低电平的模块assign nedge = (!key_tmpa) & key_tmpb;  //下降沿assign pedge = key_tmpa & (!key_tmpb);  //上升沿
//20ms的计数器(抖动一般持续20ms以下)reg[19:0]cnt;reg cnt_full;reg en_cnt;  //使能计数寄存器//计数使能模块always @(posedge Clk or negedge Res_n)if(!Res_n)cnt <= 20'd0;else if(en_cnt)cnt <= cnt + 1'b1;elsecnt <= 20'd0;//计数计满模块always @(posedge Clk or negedge Res_n)if(!Res_n)cnt_full <= 1'b0;else if(cnt == 999_999)  //系统时钟20ns,故20ms为1000_000次数cnt_full <= 1'b1;elsecnt_full <= 1'b0;
//独热码编码状态机localparamIDEL  = 4'b0001,FILTER0  = 4'b0010,DOWN  = 4'b0100,FILTER1  = 4'b1000;
//一段式状态机reg [3:0] state;  //状态机状态寄存器always@(posedge Clk or negedge Res_n)if(!Res_n)beginstate <= IDEL;en_cnt <= 1'b0;key_flag <= 1'b0;key_state <= 1'b1;endelse begincase(state)IDEL:beginkey_flag <= 1'b0;  //FILTER1返回,复位key_flagif(nedge)beginstate <= FILTER0;en_cnt <= 1'b1;endelsestate <= IDEL;endFILTER0:beginif(cnt_full)begin  //计数满20mskey_flag <= 1'b1;key_state <=1'b0;en_cnt <= 1'b0;state <= DOWN;endelse if(pedge)begin  //在判断下降沿的时候遇到上升沿,说明这是一个抖动en_cnt <= 1'b0;state <=IDEL;  //返回IDEL状态等待下一个下降沿endelsestate <= FILTER0;endDOWN:beginkey_flag <= 1'b0;  //脉冲信号复位if(pedge)beginstate <= FILTER1;en_cnt <= 1'b1;endelsestate <= DOWN;endFILTER1:beginif(cnt_full)begin  //计数满20mskey_flag <= 1'b1;key_state <=1'b1;en_cnt <= 1'b0;state <= IDEL;endelse if(nedge)begin  //在判断上升沿的时候遇到下降沿,说明这是一个抖动en_cnt <= 1'b0;state <=DOWN;  //返回IDEL状态等待下一个下降沿endelsestate <= FILTER1;enddefault:beginstate <= IDEL;en_cnt <= 1'b0;key_flag <= 1'b0;key_state <= 1'b1;endendcaseend

task语法
task<任务名>;
<端口及数据类型声明语句>
<语句1>
<语句2>
……
<语句n>
endtask

任务调用的语法
<任务名>(端口1,端口2,…端口n);

testbench文件

`timescale 1ns/1ns
`define clock_period 20module key_filter_tb;reg key_in;reg Clk;reg Res_n;wire key_flag;wire key_state;reg [15:0]myrand;  //随机数寄存器task press_key;beginrepeat(50)begin  //模拟实现50次的0~65535ns的按下抖动myrand = {$random}%65536;#myrand key_in = ~key_in;endkey_in = 0;  //按下#50_000_000;  //延时50msrepeat(50)begin  myrand = {$random}%65536;#myrand key_in = ~key_in;endkey_in = 1;  //松开#50_000_000;  //延时50msendendtaskkey_filter key_filter0(.Clk(Clk),.Res_n(Res_n),.key_in(key_in),.key_flag(key_flag),.key_state(key_state));initial Clk=1;always #(`clock_period/2) Clk = ~Clk;initial begin Res_n = 1'b0;key_in = 1'b0;#(`clock_period*10) Res_n = 1'b1;#30000;press_key;#10000;press_key;#10000;press_key;#10000;$stop;endendmodule

通过顶层文件连接,实现功能

control模块

module led_ctrl(Clk,Res_n,key_flag0,key_state0,key_flag1,key_state1,led);input Clk;input Res_n;input key_flag0,key_state0;input key_flag1,key_state1;output [3:0]led;reg [3:0]led_r;  //由于当led为高电平时实际是灯灭,所以通过引入led_r来进行逻辑调整always@(posedge Clk or negedge Res_n)if(!Res_n)led_r <= 4'b0000;else if(key_flag0 && !key_state0)led_r <= led_r + 1'b1;else if(key_flag1 && !key_state1)led_r <= led_r - 1'b1;elseled_r <= led_r;assign led = ~led_r;endmodule

顶层文件

module key_led_top(Clk,Res_n,key_in0,key_in1,led);input Clk;input Res_n;input key_in0;input key_in1;output [3:0]led;wire key_flag0;wire key_state0;wire key_flag1;wire key_state1;key_filter key_filter0(.Clk(Clk),.Res_n(Res_n),.key_in(key_in0),.key_flag(key_flag0),.key_state(key_state0));
* key_filter key_filter1(.Clk(Clk),.Res_n(Res_n),.key_in(key_in1),.key_flag(key_flag1),.key_state(key_state1));led_ctrl led_ctrl0(.Clk(Clk),.Res_n(Res_n),.key_flag0(key_flag0),.key_state0(key_state0),.key_flag1(key_flag1),.key_state1(key_state1),.led(led));endmodule

testbench文件

八、8位7段数码管驱动
实现功能: 在Quartus II 中,使用 In system sources and probes editor 工具,输入需要显示在数码管上的数据,则数码管显示对应数值。(数码管采用动态显示扫描)
模块:
1.4输入查找表,8位输出。
2.分频模块,从系统时钟分频得到1KHz的扫描时钟。
3.8选1多路器,选择端为当前扫描的数码管位置。
4.8位循环移位寄存器。

数码管驱动模块逻辑电路图
driver:分频产生1KHz的扫描时钟
Shift8:8位循环位移寄存器
MUX8:数据输入选择
MUX2:使能选择
LUT:数据译码器

module HEX8(Clk,Res_n,En,disp_data,seg,sel);input Clk;  //50Minput Res_n;input En;input [31:0]disp_data;  //待显示内容reg [3:0]data_tmp;  //待显示数据output reg[6:0]seg;  //控制显示图案(段选)output [7:0]sel;  //控制显示数码管(位选)reg [7:0]sel_r;//(二)分频计数器,计数1ms——500000ns/20ns(时钟周期) = 25000次reg [14:0] divider_cnt; //计数器寄存器reg clk_1K;always @(posedge Clk or negedge Res_n)if(!Res_n)divider_cnt <= 15'd0;else if(!En)divider_cnt <= 15'd0;else if(divider_cnt == 24999)divider_cnt <= 15'd0;elsedivider_cnt <= divider_cnt + 1'b1;//clk_1K扫描时钟生成模块 always @(posedge Clk or negedge Res_n)if(!Res_n)clk_1K <= 1'b0;else if(divider_cnt == 24_999)clk_1K <= ~clk_1K;elseclk_1K <= clk_1K;//8位循环移位寄存器always @(posedge clk_1K or negedge Res_n)if(!Res_n)sel_r <= 8'b0000_0001;else if(sel_r == 8'b1000_0000)sel_r <= 8'b0000_0001;elsesel_r <= sel_r << 1;  //左移一位//八选一多路器always@(*)  //敏感变量由综合器根据always里面的输入变量自动添加case(sel_r)8'b0000_0001:data_tmp = disp_data[3:0];8'b0000_0010:data_tmp = disp_data[7:4];8'b0000_0100:data_tmp = disp_data[11:8];8'b0000_1000:data_tmp = disp_data[15:12];8'b0001_0000:data_tmp = disp_data[19:16];8'b0010_0000:data_tmp = disp_data[23:20];8'b0100_0000:data_tmp = disp_data[27:24];8'b1000_0000:data_tmp = disp_data[31:28];default:data_tmp = 4'b0000;endcase//LUTalways@(*)case(data_tmp)  //按照段码表编码4'h0:seg = 7'b1000_000;4'h1:seg = 7'b1111_001;4'h2:seg = 7'b0100_100;4'h3:seg = 7'b0110_000;4'h4:seg = 7'b0011_001;4'h5:seg = 7'b0010_010;4'h6:seg = 7'b0000_010;4'h7:seg = 7'b1111_000;4'h8:seg = 7'b0000_000;4'h9:seg = 7'b0010_000;4'ha:seg = 7'b0001_000;4'hb:seg = 7'b0000_011;4'hc:seg = 7'b1000_110;4'hd:seg = 7'b0100_001;4'he:seg = 7'b0000_110;4'hf:seg = 7'b0001_110;endcase//二选一多路器assign sel = (En) ? sel_r:8'b0000_0000;endmodule

testbench文件

`timescale 1ns/1ns
`define clock_period 20module HEX_tb;reg Clk;  //50Mreg Res_n;reg En;  //数码管显示使能,1使能,0关闭reg [31:0]disp_data;wire [7:0]sel;  //数码管位选wire [6:0]seg;  //数码管段选HEX8 HEX8(.Clk(Clk),.Res_n(Res_n),.En(En),.disp_data(disp_data),.seg(seg),.sel(sel));initial Clk = 1;always #(`clock_period/2) Clk = ~Clk;initial beginRes_n = 1'b0;En = 1;disp_data = 32'h12345678;#(`clock_period*20);Res_n = 1'b1;#(`clock_period*20);#(`clock_period*20);#20_000_000;  //20msdisp_data = 32'h87654321;#20_000_000;disp_data = 32'h89abcdef;#20_000_000;$stop;end
endmodule

板级调试

顶层代码

module HEX_top(Clk,Res_n,seg,sel);  //顶层模块 = 调试模块 + 驱动模块input Clk;  //50Minput Res_n;output [6:0]seg;  //控制显示图案(段选)output [7:0]sel;  //控制显示数码管(位选)wire [31:0]disp_data;  //待显示内容hex_data hex_data(    //相当于使用现成模块.probe(),  //选空表示不连接.source(disp_data));HEX8 HEX8(.Clk(Clk),.Res_n(Res_n),.En(1'b1),.disp_data(disp_data),.seg(seg),.sel(sel));endmodule

In system sources and probes editor(ISSP)调试工具
Tools——Mega Wizard Plug-In Manager

分配引脚后全编译无误后下载工程到开发板中。
在Quartus II 中点击Tools——In-system Source and Probes Editor 启动ISSP,可以手动输入data。

九、UART串口发送模块设计
实现功能: 实现FPGA通过UART协议发送数据。
实验现象: 在 Quartus II 中,使用 In system sources and probes editor 工具,输入需要通过串口发送出去的数据,然后按下学习板上的按键0,则FPGA自动将所需要发送的数据发送出去。
串口发送模块包括两个主要组件
1.发送波特率生成模块。
2.数据发送模块。
串口发送模块整体结构体

串口发送模块结构图

模块代码

module uart_byte_tx(Clk,Res_n,data_byte,send_en,baud_set,Rs232_Tx,Tx_Done,uart_state
);input Clk;input Res_n;input [7:0]data_byte;input send_en;input [2:0]baud_set;output reg Rs232_Tx;output reg Tx_Done;output reg uart_state;//查找表——得到不同计数周期的计数器reg [15:0]bps_DR;  //分频计数最大值always @(posedge Clk or negedge Res_n)if(!Res_n)bps_DR <= 16'd5207;  //默认9600波特率else begincase(baud_set)0:bps_DR <= 16'd5207;1:bps_DR <= 16'd2603;2:bps_DR <= 16'd1301;3:bps_DR <= 16'd867;4:bps_DR <= 16'd433;default:bps_DR <= 16'd5207;endcaseend//实现优先级的信号控制 使能模块always @(posedge Clk or negedge Res_n)if(!Res_n)uart_state <= 1'b0;else if(send_en)uart_state <= 1'b1;else if(bps_cnt == 4'd11)uart_state <= 1'b0;elseuart_state <= uart_state;//外部输入数据的内部寄存(保证数据稳定)reg [7:0]r_data_byte;always @(posedge Clk or negedge Res_n)if(!Res_n)r_data_byte <= 8'd0;else if(send_en)  //检测到输入信号r_data_byte <= data_byte;  //储存输入值elser_data_byte <= r_data_byte;//波特率时钟
//分频时钟计数reg [15:0]div_cnt;  //分频计数器always @(posedge Clk or negedge Res_n)if(!Res_n)div_cnt <= 16'd0;else if(uart_state)beginif(div_cnt == bps_DR)div_cnt <= 16'd0;elsediv_cnt <= div_cnt + 1'b1;endelsediv_cnt <=16'd0;//bps_clk产生模块  reg bps_clk;  //波特率时钟always @(posedge Clk or negedge Res_n)if(!Res_n)bps_clk <= 1'b0;else if(div_cnt == 16'd1)  //当分频计数开始了,则clky而开始脉冲输出bps_clk <= 1'b1;elsebps_clk <= 1'b0;//bps counterreg [3:0]bps_cnt;  //波特率计数时钟always @(posedge Clk or negedge Res_n)if(!Res_n)bps_cnt <= 4'd0;else if(bps_cnt == 4'd11)  //计数满bps_cnt <=4'd0;else if(bps_clk)bps_cnt <= bps_cnt +1'b1;elsebps_cnt <= bps_cnt;//Tx_Donesh输出,是一个闭环 发送完成信号(个人感觉写这个没必要)always @(posedge Clk or negedge Res_n)if(!Res_n)Tx_Done <= 1'b0;else if(bps_cnt == 4'd11)Tx_Done <= 1'b1;elseTx_Done <= 1'b0;//十选一多路器always @(posedge Clk or negedge Res_n)  //尽量避免组合逻辑(存在毛刺)if(!Res_n)Rs232_Tx <= 1'b1;  //高电平为不输出状态else begincase(bps_cnt)0:Rs232_Tx <= 1'b1;1:Rs232_Tx <= 1'b0;  //START_BIT2:Rs232_Tx <= r_data_byte[0];3:Rs232_Tx <= r_data_byte[1];4:Rs232_Tx <= r_data_byte[2];5:Rs232_Tx <= r_data_byte[3];6:Rs232_Tx <= r_data_byte[4];7:Rs232_Tx <= r_data_byte[5];8:Rs232_Tx <= r_data_byte[6];9:Rs232_Tx <= r_data_byte[7];10:Rs232_Tx <= 1'b1;  //STOP_BITdefault:Rs232_Tx <= 1'b1;endcaseendendmodule

testbench文件

`timescale 1ns/1ns
`define clock_period 20reg Clk;reg Res_n;reg [7:0]data_byte;reg send_en;reg [2:0]baud_set;wire Rs232_Tx;wire Tx_Done;wire uart_state;uart_byte_tx uart_byte_tx(.Clk(Clk),.Res_n(Res_n),.data_byte(data_byte),.send_en(send_en),.baud_set(baud_set),.Rs232_Tx(Rs232_Tx),.Tx_Done(Tx_Done),.uart_state(uart_state));initial Clk =1;always #(`clock_period/2)Clk = ~Clk;initial beginRes_n = 1'b0;data_byte = 8'd0;send_en = 1'd0;baud_set = 3'd4;  //默认为115200(比较快)#(`clock_period*20 + 1)  //加一是为了与系统时钟错开,更好观察时序情况Res_n = 1'b1;#(`clock_period*50);data_byte = 8'haa;send_en = 1'd1;#`clock_period;send_en = 1'd0;@(posedge Tx_Done)  //等待完成信号的上升沿#(`clock_period*5000)//重新发送data_byte = 8'h55;send_en = 1'd1;#`clock_period;send_en = 1'd0;@(posedge Tx_Done)#(`clock_period*5000)$stop;endendmodule

板级代码
1.issp模块
2.key_filter模块

module uart_tx_top(Clk,Res_n,Rs232_Tx,key_in,led);input Clk;input Res_n;input key_in;output Rs232_Tx;output led;wire send_en;wire [7:0]data_byte;wire key_flag;wire key_state;assign send_en = key_flag & (!key_state);uart_byte_tx uart_byte_tx(.Clk(Clk),.Res_n(Res_n),.data_byte(data_byte),.send_en(send_en),.baud_set(3'd4),.Rs232_Tx(Rs232_Tx),.Tx_Done(),.uart_state(led)  //通过led知道目前串口状态);key_filter key_filter0(.Clk(Clk),.Res_n(Res_n),.key_in(key_in),.key_flag(key_flag),.key_state(key_state));issp issp (.probe(),.source(data_byte));endmodule

十、UART串口接收模块设计
实现功能: 实现FPGA接受其他设备通过UART协议发送过来的数据。
实验现象: 在Quartus II 中,使用 In system sources and probes editor 工具,查看UART接受模块接收到的数据,串口接收到的数据由PC机发出。

对于其中的每一位进行采样,一般情况下每一位数据的中间点是最稳定的,因此一般应用中,采集中间时刻时的数据即可。但是在工业应用中,往往有非常强的电磁干扰,只采样一次就作为该数据的电平判定,是不保险的,有可能恰好采集到被干扰的信号而导致结果出错,因此需要使用多次采样求概率的方式进行。

串口接收模块
1.起始位检测进程。
2.波特率产生模块。
3.数据接受进程。

波特率分频计数值 = 波特率周期 / 系统时钟周期 /16

module uart_byte_rx(Clk,Res_n,baud_set,Rs232_Rx,data_byte,Rx_Done
);input Clk;input Res_n;input [2:0]baud_set;input Rs232_Rx;output reg[7:0] data_byte;output reg Rx_Done;reg uart_state;reg [2:0]START_BIT,STOP_BIT;wire nedege;//起始位检测reg s0_Rs232_Rx,s1_Rs232_Rx;  //同步寄存器(消除亚稳态)reg tmp0_Rs232_Rx,tmp1_Rs232_Rx;  //暂存寄存器(数据寄存器)//同步寄存器(消除亚稳态——同按键消抖)always@(posedge Clk or negedge Res_n)if(!Res_n)begins0_Rs232_Rx = 1'b0;s1_Rs232_Rx = 1'b0;endelse begins0_Rs232_Rx <= Rs232_Rx;s1_Rs232_Rx <= s0_Rs232_Rx;end//数据寄存器always@(posedge Clk or negedge Res_n)if(!Res_n)begintmp0_Rs232_Rx = 1'b0;tmp1_Rs232_Rx = 1'b0;endelse begintmp0_Rs232_Rx <= s1_Rs232_Rx;tmp1_Rs232_Rx <= tmp0_Rs232_Rx;end//下降沿定义,起始位检测assign nedege = !tmp0_Rs232_Rx & tmp1_Rs232_Rx;//查找表——得到不同计数周期的计数器
//这里是16倍的波特率时钟(因为采集信息16次)reg [15:0]bps_DR;  //分频计数最大值always @(posedge Clk or negedge Res_n)if(!Res_n)bps_DR <= 16'd324;  //默认9600波特率else begincase(baud_set)0:bps_DR <= 16'd324;1:bps_DR <= 16'd162;2:bps_DR <= 16'd80;3:bps_DR <= 16'd53;4:bps_DR <= 16'd26;default:bps_DR <= 16'd324;endcaseend //波特率时钟
//分频时钟计数reg [15:0]div_cnt;  //分频计数器always @(posedge Clk or negedge Res_n)if(!Res_n)div_cnt <= 16'd0;else if(uart_state)beginif(div_cnt == bps_DR)div_cnt <= 16'd0;elsediv_cnt <= div_cnt + 1'b1;endelsediv_cnt <=16'd0;//bps_clk产生模块  reg bps_clk;  //波特率时钟always @(posedge Clk or negedge Res_n)if(!Res_n)bps_clk <= 1'b0;else if(div_cnt == 16'd1)  //当分频计数开始了,则clk而开始脉冲输出bps_clk <= 1'b1;elsebps_clk <= 1'b0;//bps counterreg [7:0]bps_cnt;  //波特率计数时钟always @(posedge Clk or negedge Res_n)if(!Res_n)bps_cnt <= 8'd0;else if(Rx_Done | (bps_cnt == 8'd12 && (START_BIT > 2)))  //计数满或出现开始接受错误bps_cnt <=8'd0;else if(bps_clk)bps_cnt <= bps_cnt +1'b1;elsebps_cnt <= bps_cnt;//Rx_Done输出,是一个闭环 发送完成信号always @(posedge Clk or negedge Res_n)if(!Res_n)Rx_Done <= 1'b0;else if(bps_cnt == 8'd159)  //总共采集信息160次(10×16)Rx_Done <= 1'b1;elseRx_Done <= 1'b0;//数据处理reg [2:0]r_data_byte [7:0];  //前位宽,后个数always @(posedge Clk or negedge Res_n)if(!Res_n)beginSTART_BIT <= 3'd0;r_data_byte[0] <= 3'd0;r_data_byte[1] <= 3'd0;r_data_byte[2] <= 3'd0;r_data_byte[3] <= 3'd0;r_data_byte[4] <= 3'd0;r_data_byte[5] <= 3'd0;r_data_byte[6] <= 3'd0;r_data_byte[7] <= 3'd0;STOP_BIT <= 3'd0;endelse if(bps_clk)begincase(bps_cnt)//清零操作0:beginSTART_BIT <= 3'd0;r_data_byte[0] <= 3'd0;r_data_byte[1] <= 3'd0;r_data_byte[2] <= 3'd0;r_data_byte[3] <= 3'd0;r_data_byte[4] <= 3'd0;r_data_byte[5] <= 3'd0;r_data_byte[6] <= 3'd0;r_data_byte[7] <= 3'd0;STOP_BIT <= 3'd0;end5,6,7,8,9,10:START_BIT <= START_BIT +s1_Rs232_Rx;21,22,23,24,25,26:r_data_byte[0] <= r_data_byte[0] + s1_Rs232_Rx;37,38,39,40,41,42:r_data_byte[1] <= r_data_byte[1] + s1_Rs232_Rx;53,54,55,56,57,58:r_data_byte[2] <= r_data_byte[2] + s1_Rs232_Rx;69,70,71,72,73,74:r_data_byte[3] <= r_data_byte[3] + s1_Rs232_Rx;85,86,87,88,89,90:r_data_byte[4] <= r_data_byte[4] + s1_Rs232_Rx;101,102,103,104,105,106:r_data_byte[5] <= r_data_byte[5] + s1_Rs232_Rx;117,118,119,120,121,122:r_data_byte[6] <= r_data_byte[6] + s1_Rs232_Rx;133,134,135,136,137,138:r_data_byte[7] <= r_data_byte[7] + s1_Rs232_Rx;149,150,151,152,153,154:STOP_BIT <= STOP_BIT + s1_Rs232_Rx;default;  //由于进行了清零操作,所以STOP_BIT 和 default 不用管了endcaseend//控制逻辑(产生uart_state) always @(posedge Clk or negedge Res_n)if(!Res_n)uart_state = 1'b0;else if(nedege)  //检测到下降沿就开始接受uart_state = 1'b1;else if(Rx_Done || (bps_cnt == 8'd12 && (START_BIT > 2)))  //1.接收完成 2.接收错误,起始位不对(可能是干扰信号)uart_state = 1'b0;elseuart_state <= uart_state;//输出数据  always @(posedge Clk or negedge Res_n)if(!Res_n)data_byte <= 8'd0;else if(bps_cnt == 8'd159)  begin//data_byte[0] = (r_data_byte[2])?(1'b1):1;b0;  //二进制下100=4,故最高位等于1时,寄存1,否则为0//简化逻辑后:data_byte[0] <= r_data_byte[0][2];data_byte[1] <= r_data_byte[1][2];data_byte[2] <= r_data_byte[2][2];data_byte[3] <= r_data_byte[3][2];data_byte[4] <= r_data_byte[4][2];data_byte[5] <= r_data_byte[5][2];data_byte[6] <= r_data_byte[6][2];data_byte[7] <= r_data_byte[7][2];endendmodule

testbench文件
注意事项:在testbench中例化了tx模块,所以需要在在simulation将tx.v文件手动添加进来。

`timescale 1ns/1ns
`define clock_period 20module uart_byte_rx_tb;reg Clk;reg Res_n;reg [2:0]baud_set;reg Rs232_Rx;reg send_en;reg [7:0]data_byte_t;wire [7:0]data_byte_r;wire Rx_Done;wire Tx_Done;wire Rs232_Tx;wire uart_state;uart_byte_rx uart_byte_rx(.Clk(Clk),.Res_n(Res_n),.baud_set(baud_set),.Rs232_Rx(Rs232_Tx),  //将发送的Tx信号连接到Rx信号上去.data_byte(data_byte_r),.Rx_Done(Rx_Done));uart_byte_tx uart_byte_tx(.Clk(Clk),.Res_n(Res_n),.data_byte(data_byte_t),.send_en(send_en),.baud_set(baud_set),.Rs232_Tx(Rs232_Tx),.Tx_Done(Tx_Done),.uart_state(uart_state)  );initial Clk = 1;always #(`clock_period/2) Clk = ~Clk;initial begin  //通过发送数据检验接受模块是否正确Res_n = 1'b0;data_byte_t = 8'd0;send_en = 1'd0;baud_set = 3'd4;  //默认为115200(比较快)#(`clock_period*20 + 1)  //加一是为了与系统时钟错开,更好观察时序情况Res_n = 1'b1;#(`clock_period*50);data_byte_t = 8'haa;send_en = 1'd1;#`clock_period;send_en = 1'd0;@(posedge Tx_Done)  //等待完成信号的上升沿#(`clock_period*5000)//重新发送data_byte_t = 8'h55;send_en = 1'd1;#`clock_period;send_en = 1'd0;@(posedge Tx_Done)#(`clock_period*5000)$stop;endendmodule

RTL simulation

板级调试

module uart_rx_top(Clk,Res_n,Rs232_Rx);input Clk;input Res_n;input Rs232_Rx;reg [7:0]data_rx_r;wire [7:0]data_rx;wire Rx_Done;uart_byte_rx uart_byte_rx(.Clk(Clk),.Res_n(Res_n),.baud_set(3'd0),.Rs232_Rx(Rs232_Rx), .data_byte(data_rx),.Rx_Done(Rx_Done));issp issp(.probe(data_rx_r),  //保证只有正确输入后.source());always @(posedge Clk or negedge Res_n)if(!Res_n)data_rx_r <= 8'd0;else if(Rx_Done)data_rx_r <= data_rx;elsedata_rx_r <=data_rx_r;endmodule

十一、嵌入式RAM使用之双端口RAM
实验目的: 学会调用Quartus II 软件中提供爽口RAM核并进行仿真。
实验步骤:
1.新建一个新的IP核。选择 Memory Compiler 下的 RAM:2-PORT。

With one read port and one write port,As a number of words。对于单端口RAM,读写操作共用端口A的地址,数据通过端口A写入和读出;但本次使用的双端口RAM,则是一个读端口一个写端口。

时钟选择单时钟,用一个时钟和时钟使能信号控制存储块所有的寄存器。(在别的应用场景可以设置双时钟使用独立的输入时钟和输出时钟/双时钟使用单独的读时钟和写时钟)。不创建读使能信号。

对输出端口进行寄存。不创建时钟使能信号,不创建异步复位信号(这里复位并不复位RAM中的数据,而只是复位寄存器上的值)

这里不对RAM进行初始化。

2.testbench文件

`timescale 1ns/1ns
`define clock_period 20module dpram_tb;reg clock;reg [7:0]data;reg [7:0]rdaddress;  //读地址端口reg [7:0]wraddress;  //写地址端口reg wren;  //使能信号wire [7:0]q;integer i;  //定义整数idpram dpram(.clock(clock),.data(data),.rdaddress(rdaddress),.wraddress(wraddress),.wren(wren),.q(q));initial clock =1;always #(`clock_period/2) clock = ~clock;initial begindata = 0;rdaddress = 0;wraddress = 0;wren = 0;#(`clock_period*20 + 1);for (i=0;i<=15;i=i+1)beginwren = 1;data = 255-i;wraddress = i;#`clock_period;endwren = 0;#(`clock_period*20);for(i=0;i<=15;i=i+1)beginrdaddress = i;#`clock_period;end#(`clock_period*20);$stop;endendmodule

读数据时,可以看出第一个时钟上升沿采到地址0;第二个时钟上升沿开始
赋值,但是由于逻辑延迟,因此在第三个时钟上升沿的数据才是稳定的。

十二、搭建串口收发与存储双口RAM简易应用系统
实验目的: 以模块化设计为基础利用已编写的串口收发模块、按键模块以及RAM的IP模块来设计一个简易应用系统。通过串口发送数据到FPGA中,FPGA接收到数据后将数据存储在双口RAM的一段连续空间中,当需要时,按下按键0,则FPGA将RAM中存储的数据通过串口发送出去。

功能模块:
1.串口接收模块
2.按键消抖模块
3.RAM模块
4.串口发送模块
5.控制模块

控制模块代码

module Ctrl(Clk,Res_n,Key_flag,Key_state,Rx_Done,Tx_Done,wren,Send_en,rdaddress,wraddress);input Clk;input Res_n;input Key_flag;input Key_state;input Rx_Done;input Tx_Done;output reg [7:0]rdaddress;output reg [7:0]wraddress;output wren;output reg Send_en;//实现FPGA接收到数据后将数据存储在双口RAM的一段连续空间中
//需要设计一个写地址自加的控制部分,且其控制信号为串口接收模块输出的Rx_Done信号assign wren = Rx_Done;always@(posedge Clk or negedge Res_n)if(!Res_n)wraddress <= 8'd0;else if(Rx_Done)wraddress <= wraddress + 1'b1;  //每来一个Rx_Done也就是每成功接收一字节数,地址数进行加一elsewraddress <= wraddress;//实现当按下按键0,FPGA将RAM中存储的数据通过串口发送出去
//这样也就是实现一旦按键按下即启动连续读操作,再次按下即可暂停发送reg do_send;always@(posedge Clk or negedge Res_n)if(!Res_n)do_send <= 1'b0;else if(Key_flag && !Key_state)do_send <= ~do_send;always@(posedge Clk or negedge Res_n)if(!Res_n)rdaddress <= 8'd0;else if(do_send && Tx_Done)rdaddress <= rdaddress + 8'd1;elserdaddress <= rdaddress;//双端口RAM发现其输出延迟两个系统时钟周期。
//为了保证数据变化稳定之后才进行输出,将驱动Send_en的信号接两级寄存器进行延迟两拍。
//当按键按下后启动一次发送,然后判断上一字节是否发送结束,是则进行下一字节发送,否则不进行下一次发送reg r0_send_done,r1_send_done;always@(posedge Clk or negedge Res_n)if(!Res_n)beginr0_send_done <= 1'b0;r1_send_done <= 1'b0;endelse beginr0_send_done <= (do_send && Tx_Done);r1_send_done <= r0_send_done;endalways@(posedge Clk or negedge Res_n)if(!Res_n)Send_en <= 1'b0;else if(Key_flag && !Key_state)Send_en <= 1'b1;else if(r1_send_done)Send_en <= 1'b1;  //延迟了两拍elseSend_en <= 1'b0;endmodule

顶层模块代码
将前面模块例化进来

//该程序中波特率设置为9600bps
module UART_DPRAM(Clk,Key_in,Rs232_Rx,Res_n,Rs232_Tx);input Clk;input Key_in;input Res_n;input Rs232_Rx;output Rs232_Tx;wire Key_flag;wire Key_state;wire Rx_Done;wire Tx_Done;wire [7:0]rdaddress;wire [7:0]wraddress;wire wren;wire Send_en;wire [7:0]rx_data;wire [7:0]tx_data;wire uart_state;key_filter key_filter(.Clk(Clk),.Res_n(Res_n),.key_in(Key_in),.key_flag(Key_flag),.key_state(Key_state));uart_byte_rx uart_byte_rx(.Clk(Clk),.Res_n(Res_n),.baud_set(3'd0),.Rs232_Rx(Rs232_Rx),.data_byte(rx_data),.Rx_Done(Rx_Done));Ctrl Ctrl(.Clk(Clk),.Res_n(Res_n),.Key_flag(Key_flag),.Key_state(Key_state),.Rx_Done(Rx_Done),.Tx_Done(Tx_Done),.wren(wren),.Send_en(Send_en),.rdaddress(rdaddress),.wraddress(wraddress));uart_byte_tx uart_byte_tx(.Clk(Clk),.Res_n(Res_n),.data_byte(tx_data),.send_en(Send_en),.baud_set(3'd0),.Rs232_Tx(Rs232_Tx),.Tx_Done(Tx_Done),.uart_state(uart_state));dpram dpram(.clock(Clk),.data(rx_data),.rdaddress(rdaddress),.wraddress(wraddress),.wren(wren),.q(tx_data));endmodule

系统顶层 RTL viewer

testbench文件
simulation中要加入key_model的仿真文件

`timescale 1ns/1ns
`define clock_period 20module UART_DPRAM_tb;reg Clk;reg Res_n;reg press; reg [7:0]data_byte_t;reg send_en;reg [2:0]baud_set;wire Rs232_Rx;wire Rs232_Tx;wire Tx_Done;wire uart_state;wire Key_in;  //并非通过tb产生,是通过仿真模型出来的,定义为wire型UART_DPRAM UART_DPRAM(.Clk(Clk),.Key_in(Key_in),.Rs232_Rx(Rs232_Rx),  .Res_n(Res_n),.Rs232_Tx(Rs232_Tx));//这里是例化一个发送模块来对总模块功能进行测试uart_byte_tx uart_byte_tx(.Clk(Clk),.Res_n(Res_n),.data_byte(data_byte_t),.send_en(send_en),.baud_set(baud_set),.Rs232_Tx(Rs232_Rx),  //使用的发送模块连接到总模块的接收端口上.Tx_Done(Tx_Done),.uart_state(uart_state));key_model key_model(.press(press),.key(Key_in));initial Clk =1;always #(`clock_period/2)Clk = ~Clk;initial beginRes_n = 1'b0;press = 0;data_byte_t = 8'd0;send_en = 1'd0;#(`clock_period*20 + 1)  //加一是为了与系统时钟错开,更好观察时序情况Res_n = 1'b1;#(`clock_period*50);data_byte_t = 8'haa;send_en = 1'd1;#`clock_period;send_en = 1'd0;@(posedge Tx_Done)  //等待完成信号的上升沿#(`clock_period*5000)//再次发送data_byte_t = 8'h55;send_en = 1'd1;#`clock_period;send_en = 1'd0;@(posedge Tx_Done)press = 1;  //然后开始连续不断地发送#(`clock_period*3);press = 0;#50000000;$stop;endendmodule

十三、嵌入式RAM使用之ROM
实验目标:
1.学会调用 Quartus II 软件中提供的ROM核并进行仿真。
2.学会使用Signal Tap II 软件以及In System Memory Content Editor。

实验步骤:
1.File—New—Memory Files—Memory Initialization File(数据个数:256,数据位度:8)。
2.在rom.mif文件中输入三角波形。
3.Tools—Mega Wizard Plug-In Manager—Memory Complier—ROM:1-PORT。
4.通过Browse选择三角波形.mif作为初始化文件。

5.将该IP的配置加入工程中并设置为顶层文件。

小梅哥FPGA学习笔记相关推荐

  1. 小梅哥FPGA学习笔记——开发流程及仿真示例

    开发流程及仿真示例 FPGA整体设计开发流程 1. 设计定义 2. 设计输入(Quartus II) 3. 分析和综合(Quartus II) 4. 功能仿真(modelsim-altera/mode ...

  2. 小梅哥FPGA学习笔记——状态机设计学习

    状态机学习 状态机编写方式 设计目的 设计思路 设计代码编写 测试代码编写 仿真波形结果 状态机编写方式 状态机设计编写有三种方式,分别是一段式.两段式.三段式三种方式. 设计目的 通过一段式状态机编 ...

  3. 小梅哥FPGA学习笔记——串口发送模块

    串口发送模块 串口发送模块结构框图 顶层模块 串口发送模块结构框图 发送模块具体实现结构框图如图所示,按照图片的内容一步步实现发送模块的设计. DR_LUT查找表的作用是选择不同波特率时,得到对应波特 ...

  4. 【小梅哥SOPC学习笔记】系统时钟的使用

    给NIOS II CPU添加一颗澎湃的心--系统时钟的使用 本实验介绍如何在Qsys中添加一个定时器作为NIOS II的心跳定时器,并在NIOS II中软件编程使用该定时器. 将上一个实验watchd ...

  5. 【小梅哥SOPC学习笔记】Altera SOPC嵌入式系统设计教程

    Altera SOPC嵌入式系统设计教程 第1章 概述 SOPC(System On Programmable Chip,可编程的片上系统)是Altera公司提出来的一种灵活.高效的SOC解决方案.它 ...

  6. 小梅哥FPGA视频教程学习总结(持续学习中……)

    首先附上小梅哥FPGA视频教程链接:https://www.bilibili.com/video/BV1va411c7Dz?p=2&spm_id_from=pageDriver 小梅哥yyds ...

  7. 学习小梅哥FPGA培训视频第一天

    ** 学习小梅哥FPGA培训视频第一天 ** 学习实现计数器 利用quartus II 编写代码并仿真 仿真为前仿真和后仿真 这是前仿真 这是后仿真 在后仿真中,会有毛刺的出现,例如在 1(0001) ...

  8. FPGA学习笔记之Altera FPGA使用JIC文件配置固化教程

    FPGA学习笔记之Altera FPGA使用JIC文件配置固化教程 很多做过单片机的朋友都知 道,我们在对MCU烧写完程序固件后,那么该程序固件就存储在了该MCU内部.即使MCU断电了再重新上电,程序 ...

  9. FPGA学习笔记(七): DSB调制解调的仿真

    笔记七是DSB调制解调的仿真实现. DSB调制解调的实现原理:首先使用DDS产生低频正弦波信号作为调制信号,再用DDS产生高频信号作为载波信号,然后使用乘法器将两者相乘产生DSB信号,DSB信号与载波 ...

最新文章

  1. 用最骚的话让你彻底弄懂贝叶斯!
  2. 争议中挺进全新里程——中国“超级对撞机”《概念设计报告》发布侧记
  3. 恼人的函数指针(二)
  4. honey select 模型导出_道路建模-基本模型
  5. spring中基于XML的AOP配置步骤
  6. go数组详解:数组的定义、遍历、使用细节、二维数组的定义及其遍历
  7. 年报统计系统—基本信息模块的目标文档
  8. 算术运算符_加号的多种用法
  9. OI回忆录——一个过气OIer的制杖历程
  10. 理论基础 —— 二叉树 —— 三叉链表
  11. python网络-多线程(22)
  12. UWP 流畅设计中的光照效果(容易的 RevealBorderBrush 和不那么容易的 RevealBackgroundBrush)...
  13. Chinese_PRC
  14. json 和 数组的区别
  15. HTML表格(table)属性--左右(colspan)、上下(rowspan)合并、合并边框、位置 部分内容
  16. Julia :PyPlot的plot_date
  17. gimp中文版教程_GIMP中详细教程.pdf
  18. 【FatFs】FAT32文件系统协议总结(理论+实践)
  19. windows server 2003 asp环境搭建
  20. Word表格中文字如何居中?

热门文章

  1. VisionPro和Halcon 的详细对比
  2. 关闭计算机小键盘快捷键,笔记本小键盘怎么关 关闭小键盘的方法【详解】
  3. Windows10 中使用nvidia-smi
  4. 卫康直销如何安全的参与中国MMM互助金融社区系统?
  5. python爬虫登录微博_python爬虫-模拟微博登录
  6. 2019年“华为杯”研究生数学建模竞赛--E题(全球变暖)思路感想
  7. (理财一)如何为家庭配置保险
  8. java三个技术平台_java三大技术平台是什么
  9. java咖啡_JAVA—咖啡馆
  10. 成功者刻骨铭心的一句话