本篇博客的实验内容是实现uart串口通信发送数据和接收数据,并加入按键模块调整波特率,数码管模块显示波特率,实现不同波特率下的PC端与FPGA板子的数据回环,并加入FIFO IP核对数据进行缓存,当数据大于等于8个的时候,一次性发送8个数据出去。
实验环境:quartus 18.1 modelsim Cyclone IV开发板
UART 即通用异步收发传输器(Universal Asynchronous Receiver/Transmitter),是一种串行、异步、全双工的通信协议。特点是通信线路简单,适用于远距离通信,但传输速度慢。它在发送数据时将并行数据转换成串行数据来传输,在接收数据时将接收到的串行数据转换成并行数据。
它的一帧数据由4部分组成:
起始位(1bit)
数据位(6\7\8 bit)
奇偶校验位(1bit)
停止位(1bit\1.5bit\2bit)
注意:它在空闲状态下为高电平,起始位为低电平,停止位为高电平。它的数据位甚至可以为5位或者4位,但是不能为9位及以上。
为什么起始位是低电平?
因为它的空闲位为高电平,必须有一个由高到低的下降沿变化才能知道后面要传输数据了,不然如果起始位为高电平那它怎么知道你何时传数据呢?
它的数据位传输顺序可以采用MSB也可以采用LSB,由通信双方决定。
MSB:数据高位先发送
LSB:数据低位先发送
在本次实验的工程里面采用的是LSB的格式传输。
奇校验:当实际数据中“1”的个数为奇数的时候,这个校验位就是“0”,否则这个校验位就是“1”;
偶校验:当实际数据中“1”的个数为偶数的时候,这个校验位就是“0”,否则这个校验位就是“1”。
注意:奇偶校验只能检验奇数个数据的错误,对于偶数个数据的错误无法检测。
本次实验没有用到奇偶校验,因此在这里单独进行说明:
在接收模块中接收串行数据的校验方法:
奇校验实现方式:校验位默认为高电平,每检测到1,则状态翻转
偶校验实现方式:校验位默认为低电平,每检测到1,则状态翻转
核心代码:
在接收模块接收并行数据进行校验:
偶校验:将输入数据按位异或
奇校验:将输入数据按位异或再取反(与偶校验相反)
核心代码:
UART的数据传输速率用波特率(baud)表示,常见的波特率有9600、19200、38400、57600、115200,最常用的是9600和115200。
通信双方必须保持一致的波特率,不然数据就会出错,这个在最后我们的上板验证会体现。
以9600为例,它代表我们传输1bit数据所需要的时间是1/9600=104160ns,我们的时钟周期为20ns,所以传输1bit数据需要104160/20 = 5208个时钟周期。
注意:波特率和比特率的区别
比特率:是指每秒传送的比特(bit)数。单位为 bps(Bit Per Second),比特率越高,每秒传送数据就越多。
波特率:在电子通信领域,波特(Baud)即调制速率,指的是有效数据信号调制载波的速率,即单位时间内载波调制状态变化的次数。它是对符号传输速率的一种度量,1波特即指每秒传输1个符号,而通过不同的调制方式,可以在一个码元符号上负载多个bit位信息。
在信息传输通道中,携带数据信息的信号单元叫码元,每秒钟通过信道传输的码元数称为码元传输速率,简称波特率。波特率是传输通道频宽的指标。
比特率=波特率x单个调制状态对应的二进制位数。
该系统分为了6个模块,分别是按键消抖模块(fsm_key)、波特率设置模块(baud)、数码管显示模块(sel_seg)、接收模块(uart_rx)、FIFO数据缓存模块(fifo)以及发送模块(uart_tx)。
首先是通过按键调整波特率:输入的按键信号进入按键消抖模块,然后将消抖后的按键信号传给波特率设置模块,通过按键信号可以设置不同的波特率,有常用的波特率选择:9600、19200、38400、57600、115200。默认情况下是9600。然后把设置好的波特率传给数码管显示模块进行波特率的显示。同时将设置好的波特率传给接收和发送模块,两个模块的波特率应该保持一致。同时接收模块接收了外界传入的串行数据后,将串行数据转成并行数据传给FIFO模块进行数据缓存,当FIFO里面缓存的数据大于等于8个数据时,FIFO模块将开启数据发送使能信号,然后把读取到的一个数据传给发送模块,然后发送模块再把并行数据转成串行数据一个一个的发送出去。每发送完一个数据后都拉高一个数据发送完成的使能信号传给FIFO模块,FIFO在根据这个使能信号再读一个数据传给发送模块进行数据发送,重复该步骤直到发送出8个数据为止。
//三段式状态机实现按键消抖
module fsm_key #(parameter CNT_20MS = 26'd1_000_000,
KEY_NUM = 3'd1)(//20MS计数器和按键的数量
input clk ,//时钟信号
input rst_n ,//复位信号
input [KEY_NUM-1:0] key_in ,//按键输入信号
output reg [KEY_NUM-1:0] key_out //按键输出信号
);
parameter IDLE = 4'b0001, //空闲状态
DOWN = 4'b0010, //按键按下抖动状态
HOLD = 4'b0100, //消抖后保持低电平有效状态
UP = 4'b1000; //释放抖动状态
reg [3:0] state_c;//现态
reg [3:0] state_n;//次态
reg [25:0] cnt_20ms;//20ms计数寄存器
wire add_cnt_20ms;//开始计数
wire end_cnt_20ms;//结束计数
reg [KEY_NUM-1:0] key_r0;//同步
reg [KEY_NUM-1:0] key_r1;//打拍
wire [KEY_NUM-1:0] nedge;//下降沿
wire [KEY_NUM-1:0] podge;//上升沿
//第一段状态机:时序逻辑,描述状态空间的切换
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
state_c <= IDLE;
end
else begin
state_c <= state_n;
end
end
//第二段状态机:组合逻辑,描述状态转移条件
always @(*) begin
case (state_c)
IDLE:begin
if(nedge)begin //检测到下降沿进入抖动状态
state_n = DOWN;
end
else begin
state_n = state_c;
end
end
DOWN:begin
if(podge)begin //检测到上升沿证明是一个抖动回到空闲状态
state_n = IDLE;
end
else if(end_cnt_20ms && podge == 1'b0)begin//计时结束并且没有出现上升沿就证明是一个有效按下,进入下一个状态
state_n = HOLD;
end
else begin
state_n = state_c;
end
end
HOLD:begin
if(podge)begin//出现上升沿,进入释放抖动状态
state_n = UP;
end
else begin
state_n = state_c;
end
end
UP :begin
if(end_cnt_20ms)begin//延时结束进入空闲状态
state_n = IDLE;
end
else begin
state_n = state_c;
end
end
default: state_n = state_c;
endcase
end
//第三段状态机,组合时序都可以,描述输出
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
key_out <= {KEY_NUM{1'b0}};
end
else if(state_c == DOWN && end_cnt_20ms)begin
key_out <= ~key_r1;
end
else begin
key_out <= {KEY_NUM{1'b0}};
end
end
//打拍寄存
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
key_r0 <= {KEY_NUM{1'b1}}; //{3{1'b1}} -> 3'b111 {}拼接符号
key_r1 <= {KEY_NUM{1'b1}};
end
else begin
key_r0 <= key_in;
key_r1 <= key_r0;
end
end
// always @(posedge Clk or negedge Rst_n)begin
// if(!Rst_n)begin
// key_r <= 10'b11;
// end
// else begin
// key_r <= {key_r[0],&key_in}; //打拍寄存
// end
// end
assign nedge = ~key_r0 & key_r1;//要用按位与,因为不止一个按键信号
assign podge = ~key_r1 & key_r0;
//20MS计数器
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
cnt_20ms <= 26'd0;
end
else if(add_cnt_20ms)begin
if(end_cnt_20ms)begin
cnt_20ms <= CNT_20MS;
end
else begin
cnt_20ms <= cnt_20ms + 1'd1;
end
end
else begin
cnt_20ms <= 26'd0;
end
end
assign add_cnt_20ms = state_c == DOWN || state_c == UP;
assign end_cnt_20ms = add_cnt_20ms && (cnt_20ms == CNT_20MS - 1'd1);
endmodule
module baud(
input clk ,
input rst_n ,
input key ,//按键输入信号
output reg [16:0] baud //波特率
);
reg [2:0] cnt;//设置波特率的计数器:0为9600,1为19200,2为38400,3为57600,4为115200
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
cnt <= 3'd0;
end
else if(key)begin
if(cnt == 3'd4)begin
cnt <= 3'd0;
end
else begin
cnt <= cnt + 1'd1;
end
end
else begin
cnt <= cnt;
end
end
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
baud <= 17'd9600;
end
else begin
case (cnt)
0: baud <= 17'd9600 ;
1: baud <= 17'd19200 ;
2: baud <= 17'd38400 ;
3: baud <= 17'd57600 ;
4: baud <= 17'd115200 ;
default: baud <= 17'd9600 ;
endcase
end
end
endmodule
module sel_seg(
input clk ,//系统时钟
input rst_n ,//复位信号
input [16:0] baud ,//波特率
output reg [5:0] sel ,//位选信号
output reg [7:0] seg //段选信号
);
parameter ZERO = 8'b1100_0000 ,
ONE = 8'b1111_1001 ,
TWO = 8'b1010_0100 ,
THREE = 8'b1011_0000 ,
FOUR = 8'b1001_1001 ,
FIVE = 8'b1001_0010 ,
SIX = 8'b1000_0010 ,
SEVEN = 8'b1111_1000 ,
EIGHT = 8'b1000_0000 ,
NINE = 8'b1001_0000 ,
A = 8'b1000_1000 ,
B = 8'b1000_0011 ,
C = 8'b1100_0110 ,
D = 8'b1010_0001 ,
E = 8'b1000_0110 ,
F = 8'b1000_1110 ;
parameter CNT_20US = 10'd999 ;//20us需要1000个时钟周期
reg [9:0] cnt_20us ;//20us计数寄存器
wire add_cnt_20us ;//开始计数标志符
wire end_cnt_20us ;//结束计数标志符
reg [4:0] number ;//数码管要显示的数字
//20us计数器
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
cnt_20us <= 10'd0;
end
else if(add_cnt_20us)begin
if(end_cnt_20us)begin
cnt_20us <= 10'd0;
end
else begin
cnt_20us <= cnt_20us + 1'd1;
end
end
else begin
cnt_20us <= cnt_20us;
end
end
assign add_cnt_20us = 1'b1;
assign end_cnt_20us = add_cnt_20us && cnt_20us == CNT_20US;
//位选信号控制,每20us刷新一次
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
sel <= 6'b111_110;
end
else if(end_cnt_20us)begin
sel <= {sel[4:0],sel[5]};
end
else begin
sel <= sel;
end
end
//每个数码管需要显示的数字
always @(*) begin
case (sel)
6'b111_110: number = baud / 100000 ;//最左侧数码管
6'b111_101: number = baud % 100000 / 10000 ;
6'b111_011: number = baud % 10000 / 1000 ;
6'b110_111: number = baud % 1000 / 100 ;
6'b101_111: number = baud % 100 / 10 ;
6'b011_111: number = baud % 10 ;//最右侧数码管
default: number = 4'd0;
endcase
end
//段选信号控制显示
always @(*) begin
case (number)
4'd0: seg <= ZERO ;
4'd1: seg <= ONE ;
4'd2: seg <= TWO ;
4'd3: seg <= THREE ;
4'd4: seg <= FOUR ;
4'd5: seg <= FIVE ;
4'd6: seg <= SIX ;
4'd7: seg <= SEVEN ;
4'd8: seg <= EIGHT ;
4'd9: seg <= NINE ;
default:seg <= ZERO ;
endcase
end
endmodule
这里的FIFO是调用的IP核,使用的前显模式
module fifo(
input clk ,
input rst_n ,
input [7:0] rx_dout ,//接收并行数据
input dout_sign ,//接收完成
input dout_sign_tx,
output tx_req ,//发送请求拉高时开始发送
output [7:0] tx_din ,//输入并行数据
output empty ,//空标志
output full ,//满标志
output [6:0] usedw //已经用了多少空间
);
reg tx_rd ;//读数据信号寄存
reg flag ;//状态信号,为0时缓存数据,为1时发送数据,>=8时为1;
reg [3:0] cnt_tx ;//一次性发送8个数据出去
wire add_cnt_tx ;//发送开始信号
wire end_cnt_tx ;//发送结束信号
reg rdreq ;//读取数据请求
fifo_128x8 fifo_128x8_inst (
.clock ( clk ),
.data ( rx_dout ),
.rdreq ( rdreq ),
.wrreq ( dout_sign ),
.empty ( empty ),
.full ( full ),
.q ( tx_din ),
.usedw ( usedw )
);
//读取数据信号使能
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
rdreq <= 1'b0;
end
else if((tx_rd || dout_sign_tx) && !end_cnt_tx)begin
rdreq <= 1'b1;
end
else begin
rdreq <= 1'b0;
end
end
//当可度量达到标志时第一次读取数据使能信号
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
tx_rd <= 1'b0;
end
else if(!flag && usedw >= 7'd8)begin
tx_rd <= 1'b1;
end
else begin
tx_rd <= 1'b0;
end
end
//状态信号,0为空闲状态,1为读取数据状态
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
flag <= 1'b0;
end
else if(end_cnt_tx)begin
flag <= 1'b0;
end
else if(!flag && usedw >= 7'd8)begin//空闲状态时检测剩余可读量达到标志
flag <= 1'b1;
end
else begin
flag <= flag;
end
end
//8个数据计数器
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
cnt_tx <= 3'd0;
end
else if(add_cnt_tx)begin
if(end_cnt_tx )begin
cnt_tx <= 3'd0;
end
else begin
cnt_tx <= cnt_tx + 1'd1;
end
end
else begin
cnt_tx <= cnt_tx;
end
end
assign add_cnt_tx = flag && dout_sign_tx;
assign end_cnt_tx = add_cnt_tx && cnt_tx == 3'd7;
assign tx_req = rdreq;
endmodule
module uart_rx(
input clk ,
input rst_n ,
input rx_din ,//输入串行数据
input [16:0] baud ,//波特率
output [7:0] rx_dout ,//接收并行数据
output reg dout_sign//接收完成
);
parameter CNT_1S = 26'd50_000_000;
// parameter BAUD = 14'd9600;
reg [12:0] cnt_baud ;//波特率计数器
wire add_baud ;//波特率开始计数
wire end_baud ;//波特率结束计数
reg [3:0] cnt_bit ;//比特计数器
wire add_bit ;//比特开始计数
wire end_bit ;//比特结束计数
reg [9:0] rx_data ;//数据寄存器
reg rx_din_r0 ;//同步
reg rx_din_r1 ;//打拍
wire nedge ;//起始位的下降沿
reg rx_flag ;//开始接收数据标志
//波特率计数器
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
cnt_baud <= 13'd0;
end
else if(rx_flag == 1'b0)begin
cnt_baud <= 13'd0;
end
else if(add_baud)begin
if(end_baud)begin
cnt_baud <= 13'd0;
end
else begin
cnt_baud <= cnt_baud + 1'd1;
end
end
else begin
cnt_baud <= cnt_baud;
end
end
assign add_baud = rx_flag;
assign end_baud = add_baud && cnt_baud == (CNT_1S/baud) - 1'd1;//根据波特率求得传输1bit所需要的时钟周期
//比特计数器
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
cnt_bit <= 4'd0;
end
else if(rx_flag == 1'b0)begin
cnt_bit <= 13'd0;
end
else if(add_bit)begin
if(end_bit)begin
cnt_bit <= 4'd0;
end
else begin
cnt_bit <= cnt_bit + 1'd1;
end
end
else begin
cnt_bit <= cnt_bit;
end
end
assign add_bit = end_baud;
assign end_bit = add_bit && cnt_bit == 4'd9;//一个数据有1bit起始位,8bit数据位,1bit结束位
//同步打拍检测下降沿
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
rx_din_r0 <= 1'b1;
rx_din_r1 <= 1'b1;
end
else begin
rx_din_r0 <= rx_din;
rx_din_r1 <= rx_din_r0;
end
end
assign nedge = ~rx_din_r0 && rx_din_r1;//同步,打拍,检测到下降沿开始接收数据
//同步完成检测下降沿将开始的接收的标志置为1
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
rx_flag <= 1'b0;
end
else if(nedge)begin
rx_flag <= 1'b1;
end
else if(rx_data[0] == 1'b1 || end_bit)begin//开始位为1,意外关闭
rx_flag <= 1'b0;
end
else begin
rx_flag <= rx_flag;
end
end
//缓存接收到的数据
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
rx_data <= 10'b0;
end
else if(rx_flag && cnt_baud == (CNT_1S/baud-1)/2)begin
rx_data[cnt_bit] <= rx_din_r1;
end
else begin
rx_data <= rx_data;
end
end
//结束位为1时才接收数据,否则让数据为0视为无效数据
assign rx_dout[7:0] = rx_data[8:1] ;
//接收到一个完整的有效数据后接收完成信号拉高
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
dout_sign <= 1'b0;
end
else if(end_bit && rx_data[9] == 1'b1)begin
dout_sign <= 1'b1;
end
else begin
dout_sign <= 1'b0;
end
end
endmodule
module uart_tx (
input clk ,
input rst_n ,
input tx_req ,//发送请求拉高时开始发送
input [7:0] tx_din ,//输入并行数据
input [16:0] baud ,//波特率
output reg tx_dout,//发送串行数据,低位在前,高位在后
output dout_sign_tx //发送结束标志
);
parameter CNT_1S = 26'd50_000_000;
// parameter BAUD = 14'd9600;
reg [12:0] cnt_baud ;//波特率计数器
wire add_baud ;//波特率开始计数
wire end_baud ;//波特率结束计数
reg [9:0] tx_data ;//10bit数据
reg tx_flag ;//发送标志
reg [3:0] cnt_bit ;//比特计数器
wire add_bit ;//比特开始计数
wire end_bit ;//比特结束计数
//波特率计数器
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
cnt_baud <= 13'd0;
end
else if(add_baud)begin
if(end_baud)begin
cnt_baud <= 13'd0;
end
else begin
cnt_baud <= cnt_baud + 1'd1;
end
end
else begin
cnt_baud <= cnt_baud;
end
end
assign add_baud = tx_flag;
assign end_baud = add_baud && cnt_baud == (CNT_1S/baud - 1'd1);//根据波特率求得传输1bit所需要的时钟周期
//比特计数器
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
cnt_bit <= 4'd0;
end
else if(add_bit)begin
if(end_bit)begin
cnt_bit <= 4'd0;
end
else begin
cnt_bit <= cnt_bit + 1'd1;
end
end
else begin
cnt_bit <= cnt_bit;
end
end
assign add_bit = end_baud;
assign end_bit = add_bit && cnt_bit == 4'd9;//一个数据有1bit起始位,8bit数据位,1bit结束位
//发送请求拉高时寄存数据
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
tx_data <= 10'b0;
end
else if(tx_req)begin
tx_data <= {1'b1,tx_din,1'b0};
end
else begin
tx_data <= tx_data;
end
end
//发送请求拉高时保持发送信号拉高直到发送完毕
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
tx_flag <= 1'b0;
end
else if (tx_req)begin
tx_flag <= 1'b1;
end
else if(end_bit)begin
tx_flag <= 1'b0;
end
else begin
tx_flag <= tx_flag;
end
end
//串行数据输出
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
tx_dout <= 1'b1;
end
else if(tx_flag && cnt_baud)begin
tx_dout <= tx_data[cnt_bit];
end
else begin
tx_dout <= tx_dout;
end
end
assign dout_sign_tx = end_bit;//直到没有再发送就结束了
endmodule
module top_uart(
input clk ,
input rst_n ,
input key ,//按键输入信号
input rx ,//输入串行数据
output tx ,//输出串行数据
output [5:0] sel ,//位选信号
output [7:0] seg //段选信号
);
wire [7:0] rx_dout;//串转并数据
wire rx_dout_sign;
wire tx_dout_sign;
wire tx_req;
wire [7:0] tx_din;
wire key_r;
wire [16:0] baud_r;
fsm_key fsm_key_inst(
.clk (clk),//时钟信号
.rst_n (rst_n),//复位信号
.key_in (key),//按键输入信号
.key_out (key_r) //按键输出信号
);
baud baud_inst(
.clk (clk),
.rst_n (rst_n),
.key (key_r),//按键输入信号
.baud (baud_r) //波特率
);
sel_seg sel_seg_inst(
.clk (clk),//系统时钟
.rst_n (rst_n),//复位信号
.baud (baud_r),//波特率
.sel (sel),//位选信号
.seg (seg) //段选信号
);
uart_rx uart_rx_inst(
.clk (clk ),
.rst_n (rst_n ),
.rx_din (rx ),
.baud (baud_r ),
.rx_dout (rx_dout ),
.dout_sign (rx_dout_sign)
);
fifo fifo_inst(
.clk (clk),
.rst_n (rst_n),
.rx_dout (rx_dout),//接收并行数据
.dout_sign (rx_dout_sign),//接收完成
.dout_sign_tx (tx_dout_sign),
.tx_req (tx_req),//发送请求拉高时开始发送
.tx_din (tx_din) //输入并行数据
// .empty (),//空标志
// .full (),//满标志
// .usedw () //已经用了多少空间
);
uart_tx uart_tx_inst(
.clk (clk ),
.rst_n (rst_n ),
.tx_req (tx_req ),
.tx_din (tx_din ),
.baud (baud_r ),
.tx_dout (tx ),
.dout_sign_tx (tx_dout_sign)
);
endmodule
从时序图看,这里想要发送的并行数据是8’b11010011。首先是发送请求信号拉高,此时发送标志信号拉高直到数据发送完成。采用一个10bit的寄存器对并行数据进行寄存,同时由于是低位在前,高位在后,开始位为0,停止位为1,因此寄存的数据为10’b1110100110。同时在发送标志信号拉高时,cnt_baud计数器开始计数,该计数器是计的发送1bit数据所需要的系统时钟周期。计满时cnt_bit计数器加1,然后发送的串行数据就是根据cnt_bit计数器对寄存的数据进行一个一个的发送。在出现发送停止位后就表明这个数据发送完成了,因此发送结束标志信号dout_sign就拉高。
从这张时序图看,收到了一串的串行数据,首先需要对这一串数据进行同步打拍,通过同步打拍判断出下降沿,出现下降沿时开始接收数据的标志信号rx_flag拉高直到接收数据完成。然后依旧是两个计数器与发送模块的计数器功能一样,cnt_baud和cnt_bit。然后采用一个10bit的数据寄存器rx_data。为了接收到的数据稳定,因此采用当baud计数器过了一半时根据传入的串行数据打拍后的波形数据写入寄存器,同时判断了开始位为1和停止位为0的话都属于无效数据,不会接收无效数据。当数据有效时,最终接收到的并行数据rx_dout就为rx_data寄存器的[8:1]位。同时接收完毕后将接收完成的标志信号拉高。
当FIFO中缓存的数据大于等于8个数据时,开启读取数据的状态,开启读取第一个数据的使能信号,将读取到的数据发给数据发送模块,当数据发送模块将一个数据发送完成后,将数据发送完成的标志信号dout_sign_tx传给FIFO控制模块,cnt_tx计数器加1,同时重新读取一个新的数据传给数据发送模块进行发送,直到发送完8个数据才重新进入空闲状态。并且由于FIFO是使用的前显模式,因此当读取数据有效时读取的数据是前面显示的那个数据。
`timescale 1ns/1ns
module top_uart_tb();
reg clk ;
reg rst_n ;
reg rx ;
reg key_r ;
wire tx ;
defparam uart_rx_inst.CNT_1S = 26'd50;
defparam uart_tx_inst.CNT_1S = 26'd50;
parameter CYCLE = 20;
wire [7:0] rx_dout;//串转并数据
wire rx_dout_sign;
wire tx_dout_sign;
wire tx_req;
wire [7:0] tx_din;
wire [6:0] usedw ;
wire [16:0] baud_r;
reg [7:0] test ;
baud baud_inst(
.clk (clk),
.rst_n (rst_n),
.key (key_r),//按键输入信号
.baud (baud_r) //波特率
);
uart_rx uart_rx_inst(
.clk (clk ),
.rst_n (rst_n ),
.rx_din (rx ),
.baud (baud_r ),
.rx_dout (rx_dout ),
.dout_sign (rx_dout_sign)
);
fifo fifo_inst(
.clk (clk),
.rst_n (rst_n),
.rx_dout (rx_dout),//接收并行数据
.dout_sign (rx_dout_sign),//接收完成
.dout_sign_tx (tx_dout_sign),
.tx_req (tx_req),//发送请求拉高时开始发送
.tx_din (tx_din), //输入并行数据
// .empty (),//空标志
// .full (),//满标志
.usedw (usedw) //已经用了多少空间
);
uart_tx uart_tx_inst(
.clk (clk ),
.rst_n (rst_n ),
.tx_req (tx_req ),
.tx_din (tx_din ),
.baud (baud_r ),
.tx_dout (tx ),
.dout_sign_tx (tx_dout_sign)
);
always #(CYCLE/2) clk = ~clk;
initial begin
clk = 1'b0;
rst_n = 1'b0;
test = 8'b0;
#(CYCLE/2 + 3);
rst_n = 1'b1;
repeat(8)begin
test = test + 1'b1;
rx = 1'b0;
#(5*CYCLE);
rx = test[0];
#(5*CYCLE);
rx = test[1];
#(5*CYCLE);
rx = test[2];
#(5*CYCLE);
rx = test[3];
#(5*CYCLE);
rx = test[4];
#(5*CYCLE);
rx = test[5];
#(5*CYCLE);
rx = test[6];
#(5*CYCLE);
rx = test[7];
#(5*CYCLE);
rx = 1'b1;
#(30*CYCLE);
end
#(2000*CYCLE);
$stop;
end
endmodule
从这张仿真波形图可以看到,发送的标志信号拉高后,两个计数器正常工作,然后10bit的数据寄存器rx_data。在cnt_baud计数器过了一半时才会根据传入的串行数据打拍后的波形数据写入寄存器,同时判断了开始位为1和停止位为0的话都属于无效数据,不会接收无效数据。当数据有效时,最终接收到的并行数据rx_dout就为rx_data寄存器的[8:1]位。同时接收完毕后将接收完成的标志信号拉高。
从这张仿真波形图可以看到,当发送请求信号tx_req拉高后,发送标志信号tx_flag拉高直到发送完毕。同时10bit的数据寄存器将tx_din的并行数据存入并加上开始位0,停止位1。然后两个计数器开始正常计数。然后发送的串行数据tx_dout就是根据cnt_bit计数器对寄存的数据进行一个一个的发送。在出现发送停止位后就表明这个数据发送完成了,因此发送结束标志信号dout_sign就拉高。
从这张仿真波形图可以看出,当FIFO中缓存的数据大于等于8个数据时,开启读取数据的状态,开启读取第一个数据的使能信号,将读取到的数据发给数据发送模块,当数据发送模块将一个数据发送完成后,将数据发送完成的标志信号dout_sign_tx传给FIFO控制模块,cnt_tx计数器加1,同时重新读取一个新的数据传给数据发送模块进行发送,直到发送完8个数据才重新进入空闲状态。并且由于FIFO是使用的前显模式,因此当读取数据有效时读取的数据是前面显示的那个数据。
从图片中可以看出,当发送数据小于8个时,它将数据接收并存入了FIFO缓冲器。当再次输入几个数据时,它会一次性输出8个数据,并且先输出之前存入的数据。当我们切换了波特率,PC端设置的波特率与FPGA板子上设置的波特率不同时,我们发送的数据就会解析混乱,变成一些乱码存入缓存器然后达到8个数据时输出。当我们重新调整波特率,使得双方波特率一致后,又能够收发同步了。
因为该工程是分模块化的设计,因此如果只需要串口的发送和接收功能就只需要将这两个模块直接拿去用就可以了,大大方便了之后其他工程的设计。另外这只是我的一点学习笔记,如果有错误还请提出。