刷题推荐
链接: Verilog刷题
1,实验简介
UART关键参数及时序图
UART通信在使用前需要做多项设置,最常见的设置包括数据位数、波特率大小、奇偶校验类型和停止位数。
数据位(Data bits):该参数定义单个UART数据传输在开始到停止发送的数据位数。可选择5、6、7、8;
波特率(Baud):指从一设备发送到另一设备的波特率,即每秒钟可以通信的数据比特个数。典型的波特率有300,1200,2400,9600,19200,115200等。
奇偶校验类型(Parity Type):用来校验数据的正确性。
停止位(Stop bits):在每个字节的数据位发送完成后,发送停止位来标志一次数据传输完成,同时用来帮助接受信号方的硬件重同步。可选择1、1.5或者2位。
在RS-232标准中,最常用的配置是8N1,即8个数据位,无奇偶校验,1个停止位。
按照一个完整的字节包括1位起始位,8位数据位,1位停止位,总共10位数据来算,想要完整的实现这10位数据的发送,就需要11个波特率时钟脉冲,第1个脉冲标记一次传输的开始,第11个脉冲标记一次传输的结束。
2,实验环境
vivado 2019.1
黑金AX7035开发板
3,实验原理
黑金开发板采用USB转串口方案,其电路图如下图所示:
4,程序设计
由上面分析可以得到计数器模块的接口图及接口功能描述
信号名称 | I/O | 功能描述 |
---|---|---|
sys_clk | I | 模块工作时钟50MHZ |
rst_n | I | 模块复位 |
data_byte | I | 待传输8bit数据 |
send_en | I | 发送使能信号 |
baud_set | I | 波特率设置信号 |
uart_tx | O | 串口发送信号输出 |
tx_done | O | 发送结束信号,一个时钟高电平 |
uart_state | O | 发送状态,处于发送状态时为1 |
在波特率时钟生成模块中,计数器需要的计数值与波特率关系如下表所示,其中系统时钟为20ns。
baud_set | 波特率 | 波特率周期 | 波特率分频计数值 | 50MHZ计数值 |
---|---|---|---|---|
0 | 9600 | 104167ns | 104167/20 | 5028-1 |
1 | 19200 | 52083ns | 52083/20 | 2604-1 |
2 | 38400 | 26041ns | 26041/20 | 1302-1 |
3 | 57600 | 17361ns | 17361/20 | 868-1 |
4 | 115200 | 8680ns | 8680/20 | 434-1 |
4.1,vivado工程创建【跳过】
4.2,编写计数器的Verilog代码
//TOP
`timescale 1ns / 1ps
module uart_byte_test(
input sys_clk,
input rst_n,
output uart_tx,
output led
);
assign rst=~rst_n;
wire send_en; //发送使能
wire [7:0]data_byte; //待传输8bit数据
wire test_en; //按键标志信号
reg test_en_dly1;
reg test_en_dly2;
always@(posedge sys_clk)
begin
test_en_dly1 <= test_en;
test_en_dly2 <= test_en_dly1;
end
//VIO输出测试信号控制发送使能
assign send_en = test_en_dly1 & !test_en_dly2;
uart_byte_tx uart_byte_tx(
.sys_clk(sys_clk),
.rst_n(rst_n),
.data_byte(data_byte),
.send_en(send_en),
.baud_set(3'd0),
.uart_tx(uart_tx),
.tx_done( ),
.uart_state(led)
);
endmodule
//Module
module uart_byte_tx(
input sys_clk,
input rst_n,
input [7:0] data_byte,
input send_en,
input [2:0]baud_set,
output reg uart_tx,
output reg tx_done,
output reg uart_state
);
assign rst = ~rst_n;
localparam START_BIT = 1'b0; //不可用于参数传递
localparam STOP_BIT = 1'b1;
reg bps_clk; //波特率时钟
reg [15:0] div_cnt; //分频计数器
reg [15:0] bps_DR; //分频计数最大值
reg [3:0] bps_cnt; //波特率时钟计数器
reg [7:0] data_byte_reg; //data_byte寄存器
/*
产生数据传输状态信号,正常传输的时候uart_state信号为高电平,其他情况均为低电平。
*/
always@(posedge sys_clk or posedge rst)
if(rst)
uart_state <= 1'b0;
else if(send_en)
uart_state <= 1'b1;
else if(bps_cnt)
uart_state <= 1'b0;
else
uart_state <= uart_state;
/*
由于串口是一个异步收发器,为了保证发送的数据在是时钟到来的时候是稳定的,需要
对输入数据进行寄存
*/
always@(posedge sys_clk or posedge rst)
if(rst)
data_byte_reg <= 8'd0;
else if(send_en)
data_byte_reg <= data_byte;
else
data_byte_reg <= data_byte_reg;
/*
为了保证模块的复用性,当需要不同的波特率时,只需设置不同的波特率时钟计数器的
计数值,使用查找表即可实现。
*/
always@(posedge sys_clk or posedge rst)
if(rst)
bps_DR <= 16'd5027;
else begin
case(baud_set)
0:bps_DR <= 16'd5027;
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;
endcase
end
/*
利用计数器来生成波特率时钟
*/
always@(posedge sys_clk or posedge rst)
if(rst)
div_cnt <= 16'd0;
else if(uart_state)begin
if(div_cnt == bps_DR)
div_cnt <= 16'd0;
else
div_cnt <= div_cnt + 1'b1;
end
else
div_cnt <= 16'd0;
// bps_clk gen
always@(posedge sys_clk or posedge rst)
if(rst)
bps_clk <= 1'b0;
else if(div_cnt == 16'd1)
bps_clk <= 1'b1;
else
bps_clk <= 1'b0;
/*
通过对波特率时钟的计数,来确认数据bit位发送的数据量
*/
always@(posedge sys_clk or posedge rst)
if(rst)
bps_cnt <= 4'd0;
else if(bps_cnt == 4'd11)
bps_cnt <= 4'd0;
else if(bps_clk)
bps_cnt <= bps_cnt + 1'b1;
else
bps_cnt <= bps_cnt;
/*
为了模块可以对其他模块进行控制或者调用,这里产生一个bit传输结束后tx_done输出
一个时钟的高电平
*/
always@(posedge sys_clk or posedge rst)
if(rst)
tx_done <= 1'b0;
else if(bps_cnt == 4'd11)
tx_done <= 1'b1;
else
tx_done <= 1'b0;
/*
使用一个十选一多路器,作用是根据bps_cnt的值来确认数据传输的状态,在不同的波特率
时钟计数时,有对应的传输数据位
*/
always@(posedge sys_clk or posedge rst)
if(rst)
uart_tx <= 1'b1;
else begin
case(bps_cnt)
0:uart_tx <= 1'b1;
1:uart_tx <= START_BIT;
2:uart_tx <= data_byte_reg[0];
3:uart_tx <= data_byte_reg[1];
4:uart_tx <= data_byte_reg[2];
5:uart_tx <= data_byte_reg[3];
6:uart_tx <= data_byte_reg[4];
7:uart_tx <= data_byte_reg[5];
8:uart_tx <= data_byte_reg[6];
9:uart_tx <= data_byte_reg[7];
10:uart_tx <= STOP_BIT;
default:uart_tx <= 1'b1;
endcase
end
endmodule
4.3,vivado仿真验证
`timescale 1ns/1ns
`define CLK_PERIOD 20
module vtf_uart_txreg;
reg clk;
reg reset_n;
reg [7:0]data_byte;
reg send_en;
reg [2:0]baud_set;
wire uart_tx;
wire tx_done;
wire uart_state;
uart_byte_tx uart_byte_tx(
.clk(clk),
.reset_n(reset_n),
.data_byte(data_byte),
.send_en(send_en),
.baud_set(baud_set),
.uart_tx(uart_tx),
.tx_done(tx_done),
.uart_state(uart_state)
);
initial clk = 1;
always#(`CLK_PERIOD/2)clk = ~clk;
initial begin
reset_n = 1'b0;
data_byte = 8'd0;
send_en = 1'd0;
baud_set = 3'd4;
#(`CLK_PERIOD*500 + 1 )
reset_n = 1'b1;
#(`CLK_PERIOD*50);
//send first byte
data_byte = 8'haa;
send_en = 1'd1;
#`CLK_PERIOD;
send_en = 1'd0;
@(posedge tx_done)
#(`CLK_PERIOD*5000);
//send second byte
data_byte = 8'h55;
send_en = 1'd1;
#`CLK_PERIOD;
send_en = 1'd0;
@(posedge tx_done)
#(`CLK_PERIOD*5000);
$stop;
end
endmodule
可以看出在复位和使能信号有效之前输出信号uart_tx均为1,在复位结束以及使能后输出信号才开始发送一组串口数据,且当待发送数据位0xaa时,串行输出信号依次为1,0(起始位),01010101b(LSB),1(停止位)。同时在发送串行数据过程中uart_state处于发送状态时为1,每个字节数据发送完成后,tx_done产生一个时钟周期脉冲,与设计的期望一致,仿真通过。
4.4,VIO控制串口发送
为了实现串口发送的目标,使用vivado在线调试工具VIO产生发送数据和控制使能信号,完成FPGA通过串口发送到PC端,通过串口工具显示。
VIO全称Virtual Input/Output,虚拟输入输出信号,主要用于上板调试时,建立一个虚拟的输入输出信号,可以对需要调试的模块的输出信号的数值进行在线查看,方便调试查找问题和验证模块的实际上板的正确性。
VIO在vivado是以一个IP Core的形式存在。使用时首先要配置生成一个VIO IP Core。
1、点击IP Catalog,搜索vio,双击Virtual Input/Output
2、进入到VIO设置界面
(1) Documentation: IP 相关文档入口
(2) IP Location:生成 IP 的存放路径,可以通过点击 … 设置更换存放路径,默认是存放
在工程路径下的 uart_tx.srcs\ sources_1\ ip,这里我们就保持默认。
(3) Switch to Default:点击后所有的设置恢复到默认值
(4) Component Name:设置生成 IP Core 的名称,这里我们保持默认即可。
(5)这里是一个提示,提示通过该界面设置最多可设置 64 个探针,如果想设置更多的探针
需要使用 Tcl 脚本命令去设置,这里就不做详细描述,想要知道具体用法的,可以查询 IP 手
册。
6) VIO 模块输入探针个数,这里调试串口发送模块,需要 VIO 产生输出一个 8bit 数据信
号和一个 1bit 的控制信号,不需要 input probe,这里设置个数为 0;
(7) VIO 模块输出探针个数,根据(6)描述,这里需要设置为 2;
(8)使能 Input Probe 的 Active detectors 功能,每个 VIO Croe 输入都有额外的单元来捕获
输入信号的变化。由于设计时钟(待捕获的信号所处的时钟)可能比分析仪的采样时钟要快,
因此被监测的信号可能在连续采样之间多次变化。 Active detectors 功能就是用来捕获此行为,
让其结果与 vivado 逻辑分析器中的值一起显示。当 Input Probe Count 设置为 0 时,这里是
不需要设置的,当 Input Probe Count 设置不为 0 时,这里按默认勾选即可。
(9)设置 Input Probe 信号的位宽,当 Input Probe Count 设置为 0 时,没有这里的设置,当
Input Probe Count 设置不为 0 时,根据实际需求进行位宽设置即可。由于(6)中 Input Probe
设置为 0,这里没有设置的地方。
(10)设置 Output Probe 信号的位宽和初始值,当 Output Probe Count 设置为 0 时,没有这
里的设置,当 Output Probe Count 设置不为 0 时,根据实际需求进行位宽和初始值设置即可。
这里调试串口发送模块,需要 VIO 产生输出一个 8bit 数据信号和一个 1bit 的控制信号,将
PROBE_OUT0 位宽设置为 1, PROBE_OUT2 位宽设置为 8 即可。
3、对上述参数设置后的结果,点击OK
4、在Source窗口点击IP Sources可以查看生成的VIO Core文件。
5、打开 .veo文件,可以看到该模板代码,具体例化模板代码如下
vio_0 vio_0(
.clk(clk),
.probe_out0(test_en),
.probe_out1(data_byte)
);
该 VIO 模块有 3 个信号,一个时钟信号 clk,二个 Output Probe 信号,时钟信号就是采样时钟, Output Probe 信号就是前面通过 IP 生成界面设置的信号,位宽分别为 1bit 和 8bit。
新建一个用于上板测试的模块文件命名为 uart_tx_test.v,并将其设置为顶层计。将 VIO 例化模板和前面设计的 uart_byte_tx 模块复制粘贴到顶层模块进行修改进行例化。
4.4,添加XDC管脚约束文件【跳过】
4.5,下载和Flash固化【跳过】
1、程序下载完成后的界面:
/2、点击右边vio界面的+
3、选择信号,点击OK
4、设置VIO模块输出信号test_en输出模式,右键test_en,选择Active-High Button。
信号窗口中 test_en 在 Value 一栏就变成了默认值为 0 的类似按钮的形式,当用鼠标点击这个“按钮”时, test_en 的值变为 1,松开鼠标后值变为 0,其功能和按键类似,注意这里设置的是 Active-High Button,表示按下是高电平,松开或默认是低电平(注:如果设置为Active-Low Button 则正好相反,表示按下是低电平,松开或默认是高电平,如果设置为ToggleButton:鼠标点击信号“按钮”,每点击一次,则该输出信号值翻转一次)。通过 test_en 信号
的上升沿作为串口发送的使能信号,这样就可以实现点击一下test_en“按钮”就发送一次数据,发送的数据是设置的 data_byte 的数值。
5、将开发板上USB转串口通过数据线与PC机相接
6、在PC机打开一个串口软件,设置好串口号、波特率9600、数据位8bit、停止位1bit,点击启动串口,然后点击test_en,可以更改其他发送数据,每点击一次test_en,就会使能FPGA将data_byte数据发送出去,在串口上看到接收的相关数据。