**初学者学习FPGA的一些小心得和笔记,以免之后忘记。教程是跟着小梅哥AC620教学视频学的
视频地址:【0基础学FPGA 2023全新版本——小梅哥AC620 FPGA开发板教学视频】 https://www.bilibili.com/video/BV1Ga4y1f7hy/?p=28&share_source=copy_web&vd_source=079f387420bab0d1dc43fb9c7ea5a62d
目录
设计一个串口发送模块,发送用户输入的数据给电脑。
要求如下:
verilog代码展示:
module uart_byte_tx(
input Clk,
input Reset_n,
input [7:0] Data,
output reg uart_tx,
output reg Led
);
reg [12:0] band_div_cnt;
reg en_band_cnt;
reg [7:0] r_Data;
reg [25:0] delay_cnt;
reg [3:0] bit_cnt;
parameter MCNT_DLY=50_000_000-1;
parameter MCNT_BIT=10-1;
parameter MCNT_BAND= 5208-1;
//波特率计数器 1/960*1000 000 000 /20 -1
always @ (posedge Clk or negedge Reset_n)
if(!Reset_n)
en_band_cnt <=0;
else if (delay_cnt == MCNT_DLY)
en_band_cnt <= 1;
else if ((bit_cnt == MCNT_BIT)&&(band_div_cnt ==MCNT_BAND))
en_band_cnt <=0;//关闭
always @ (posedge Clk or negedge Reset_n)
if(!Reset_n)
band_div_cnt <= 0;
else if( en_band_cnt ==1) begin
if(band_div_cnt ==MCNT_BAND)
band_div_cnt <=0;
else
band_div_cnt <= band_div_cnt +1'b1;
end
else
band_div_cnt <=0;
//位计数器
always @ (posedge Clk or negedge Reset_n)
if(!Reset_n)
bit_cnt<=0;
else if(band_div_cnt ==MCNT_BAND) begin
if(bit_cnt==MCNT_BIT)
bit_cnt<=0;
else
bit_cnt <= bit_cnt +1'd1;
end
//延时计数器
always @ (posedge Clk or negedge Reset_n)
if(!Reset_n)
delay_cnt <=0;
else if(delay_cnt == MCNT_DLY)
delay_cnt <=0;
else
delay_cnt <=delay_cnt +1'd1;
//位发送逻辑
always @ (posedge Clk or negedge Reset_n)
if(!Reset_n)
r_Data <=0;
else if(delay_cnt == MCNT_DLY)
r_Data <= Data;
always @ (posedge Clk or negedge Reset_n)
if(!Reset_n)
uart_tx <= 1;
else if( en_band_cnt == 0)
uart_tx <=1;
else begin
case (bit_cnt)
0: uart_tx <=0;
1: uart_tx <=r_Data[0];
2: uart_tx <=r_Data[1];
3: uart_tx <=r_Data[2];
4: uart_tx <=r_Data[3];
5: uart_tx <=r_Data[4];
6: uart_tx <=r_Data[5];
7: uart_tx <=r_Data[6];
8: uart_tx <=r_Data[7];
9: uart_tx <= 1;
default : uart_tx <=uart_tx;
endcase
end
//Led翻转逻辑
always @ (posedge Clk or negedge Reset_n)
if(!Reset_n)
Led<=0;
else if((bit_cnt==MCNT_BIT)&&(band_div_cnt ==MCNT_BAND))
Led<=!Led;
endmodule
testbench代码:
`timescale 1ns / 1ps
module uart_byte_tx_tb();
reg Clk;
reg Reset_n;
reg [7:0] Data;
wire uart_tx;
wire Led;
uart_byte_tx uart_byte_tx(
.Clk (Clk ),
.Reset_n (Reset_n ),
.Data (Data ),
.uart_tx (uart_tx ),
.Led (Led )
);
defparam uart_byte_tx.MCNT_DLY = 500_000 - 1;//10ms
initial Clk = 1;
always #10 Clk = !Clk;
initial begin
Reset_n = 0;
#201;
Reset_n = 1;
Data = 8'b0101_0101;
#30_000_000;
Data = 8'b1010_1010;
#30_000_000;
$stop;
end
endmodule
仿真波形情况:
要求解读
1.“波特率为9600”
波特率为单位时间内传输符号的个数(串口通信的速率),即每秒钟要传输的数量【可以用速度 =路程/时间】来简单理解
要求波特率为9600,意思是传输一个符号,所耗费的时间为
1➗9600=1.04166✖(s)
使用的AC620上带有50MHz的时钟信号(1s有50M个Clk方波信号),即每个Clk信号所占时
1➗50_000_000=2✖(s)
两者相结合,则可以用Clk的数量来代替传输一个符号的时间
Clk数量:1.04166✖(s)➗2✖=5208.333...
取整即为5208个Clk信号(波特率在2.5%的误差范围下,可以保证可靠传输)
2.“8位数据位,1位停止位”
START位为低电平,STOP位为高电平
要发送八位数据,那么就需要十(8+1+1)位的bit_cnt,单个时长为1/9600(s)。
3.“每1s发送一次当前8位拨码开关的值”
串口发送时间为1/960s,那么剩余空闲时间就是1-1/960s,可以用en_band_cnt来控制
4.“每次发送完后将LED0状态反转”
串口发送完成之后,可以给LED进行一个if语句的判断,详细内容后面结合代码讲解
代码详解
1.输入输出部分
module uart_byte_tx(
input Clk,
input Reset_n,
input [7:0] Data,
output reg uart_tx,
output reg Led
);
输入端口Clk(时钟),Reset_n,Data(8位数据位);输出端口uart_tx,Led(控制翻转)
因为在板子上,复位信号按下时是低电平,所以后面判断是否按下复位信号时候,都是用的
if(!Reset_n)
2.波特率计数器部分
引入了
always @ (posedge Clk or negedge Reset_n)//Clk上升沿或者复位信号的下降沿
if(!Reset_n)
en_band_cnt <=0;//波特率分频器使能信号为0,关闭
else if (delay_cnt == MCNT_DLY)//MCNT_DLY为50_000_000-1,因为计算机从0开始数,数到第
//50_000_000-1时候,就数了50_000_000个数
en_band_cnt <= 1;//波特率分频器使能信号为1,开启
else if ((bit_cnt == MCNT_BIT)&&(band_div_cnt ==MCNT_BAND))
//当位计数器等于最大位(10-1)时,且波特率分频计数器计数到最大位(5208-1)时候
en_band_cnt <=0;//波特率分频器使能信号为0,关闭
这一段具象理解就是“做了个减法” ,上面的红线就是en_band_cnt的信号情况
先用
else if (delay_cnt == MCNT_DLY)
en_band_cnt <= 1;
· 让1S内的 en_band_cnt均赋值为1
然后再用
else if ((bit_cnt == MCNT_BIT)&&(band_div_cnt ==MCNT_BAND))
en_band_cnt <=0;
这里的((bit_cnt == MCNT_BIT)&&(band_div_cnt ==MCNT_BAND))就是为了确保STOP后的时间点,所以看着比较长,理解了也还好。
因为波特率计数器每计数满一轮(MCNT_BAND 5208-1)后,就相当于时间上走了1/9600s
always @ (posedge Clk or negedge Reset_n)
if(!Reset_n)
band_div_cnt <= 0;//位计数器清0
else if( en_band_cnt ==1) begin//当波特率计数器使能信号为1时候,启动波特率计数器计数
if(band_div_cnt ==MCNT_BAND)//波特率计数器数到5208-1时候
band_div_cnt <=0;//清0
else
band_div_cnt <= band_div_cnt +1'b1;//否则加一
end
else
band_div_cnt <=0;
3.位计数器
当波特率计数器记满5208-1时候,要让位计数器+1
位计数器经历0->1->2->3->4->5->6->7->8->9,共10个数,依次对应如下部分
always @ (posedge Clk or negedge Reset_n)
if(!Reset_n)
bit_cnt<=0;
else if(band_div_cnt ==MCNT_BAND) begin//当波特率计数器记满5208-1时候,进入位计数器中
if(bit_cnt==MCNT_BIT)//当位计数器计数满10-1时候(bit_cnt)
bit_cnt<=0;//位计数器清0
else
bit_cnt <= bit_cnt +1'd1;//否则位计数器+1
end
4.延时计数器
这个计数器是为了确保1s的时长,和波特率计数器上有点关联
always @ (posedge Clk or negedge Reset_n)
if(!Reset_n)
delay_cnt <=0;//复位时候,延时计数器值0
else if(delay_cnt == MCNT_DLY)//如果Clk上升沿来到了50_000_000-1时
delay_cnt <=0;//清0
else
delay_cnt <=delay_cnt +1'd1;//否则+1
5.位发送逻辑
要保证在串口发送过程中(也就是1/960s之中),不会因为Data数据的变化,而让原本打算发送的数据错乱,所以需要一个“寄存器”(r_Data)来暂时储存Data数据
always @ (posedge Clk or negedge Reset_n)
if(!Reset_n)
r_Data <=0;
else if(delay_cnt == MCNT_DLY)
r_Data <= Data;//数据的寄存
在数据寄存好之后,因为在不发送数据的时候,uart_tx应该处于一个高电平状态,且uart_tx的发送时为低电平(0),结束位为高电平(1),想知道为什么是这样的话,可以看一下UART通信协议及其工作原理(图文并茂+超详细)-CSDN博客
这里运用了case语句(记得endcase结尾)
always @ (posedge Clk or negedge Reset_n)
if(!Reset_n)
uart_tx <= 1;//复位状态下, uart_tx应该处于高电平
else if( en_band_cnt == 0)
uart_tx <=1;//非传输状态下,uart_tx应该处于高电平
else begin
case (bit_cnt)
0: uart_tx <=0;//START
1: uart_tx <=r_Data[0];
2: uart_tx <=r_Data[1];
3: uart_tx <=r_Data[2];
4: uart_tx <=r_Data[3];
5: uart_tx <=r_Data[4];
6: uart_tx <=r_Data[5];
7: uart_tx <=r_Data[6];
8: uart_tx <=r_Data[7];
9: uart_tx <= 1;//STOP
default : uart_tx <=uart_tx;//需要使用default语句来确保 未填写时候的情况
endcase
end
6.LED的翻转逻辑
主要还是用了 else if((bit_cnt==MCNT_BIT)&&(band_div_cnt ==MCNT_BAND))来确保翻转时候的时间情况
always @ (posedge Clk or negedge Reset_n)
if(!Reset_n)
Led<=0;//复位状态下,Led处于低电平
else if((bit_cnt==MCNT_BIT)&&(band_div_cnt ==MCNT_BAND))//在波特率计数器中有见到
Led<=!Led;//用!来取反