目录
一、uart概述
UART的全称是通用异步收发器(Universal Asynchronous Receiver/Transmitter)
UART是一个通用的接口协议,被广泛的应用在各类MCU和SOC产品上。“不需要额外的时钟线进行数据的同步传输”,只要信号拉低,就可开始传送数据。UART 只需要两条信号线:RXD 和 TXD(一个数据的发送方和一个数据的接收方 ),其中 RXD 是 UART 的接收端,TXD 是 UART 的发送端,接收与发送是全双工形式。
它可以和各种标准串行接口,如 RS 232 和 RS 485 等进行全双工异步通信, 具有传输距离远、成本低、可靠性高等优点。一般 UART 由专用芯片如: 8250, 16450 来实现,但专用芯片引脚都较多,内含许多辅助功能,在实际使用时往往只需要用到 UART 的基本功能,使用专用芯片会造成资源浪费和成本提高。
二、uart数据格式
UART的一帧由四部分组成起始位(1bit),数据位(6\7\8bit),校验位(1bit),停止位(1\1.5\2bit),如下图所示,
uart 是将传输数据的每个字符一位接一位地传输。 其中每一位(Bit)的意义如下:
空闲位:高电平,表明当前无传输事务。
起始位:一位低电平信号,标志着数据传输的开始。
数据位:紧接着起始位之后。数据位的个数可以是 6、7、8 等,构 成一个字符。通常采用 ASCII 码。从最低位开始传送,依靠时钟定位。
奇偶校验位:数据位加上这一位后,使得“1”的位数应为偶数(偶校验)或 奇数(奇校验),以此来校验数据传送的正确性。
停止位:一个字符数据的结束标志。可以是 1 位、1.5 位、2 位的高电平。
由于数据是在传输线上定时的,并且每一个设备有其自己的时钟,很可能在通 信中两台设备间出现了小小的不同步。因此停止位不仅仅是表示传输的结束, 并且提供计算机校正时钟同步的机会。停止位的位数越多,不同时钟同步的容 忍程度越大,但是数据传输率同时也越慢。
2.1 uart起始位的意义
因为UART没有控制线,要让接收方知道什么时候开始接收数据,需要一些手段,UART数据的传输中,只有一根线,所以在发送数据之前,先发一位逻辑“0”作为数据发送的起始标志,接收方在空闲时,当检测到有一个低电平,则开始接逐位接收数据。
2.2 停止位的意义
因为通信两个机器之间不可避免的存在时钟偏移,为了消除数据交互之间时钟偏移对数据传输产生的影响,所以在每一次数据发送结束后,要发送一位逻辑“1”作为数据发送的结束标志,接收方在空闲时,当检测到有一个高电平,则结束接收一次数据。
2.3 UART基本的数据形式
-
默认无传输数据时,为高电平
-
当信号拉低,传输线上的电平拉低,意味着开始进行数据传输
-
紧接着起始位的是数据位,它可以是5、6、7或8位。
-
UART的“校验位”紧挨着“数据位”,采用奇偶校验方式,根据设置,校验位可以存在也可以不存在。
-
UART将停止位作为停止标志,是在数据位(没有校验位)和校验位(有校验位)之后发送1~2位的逻辑“1”高电平。当发送完停止位之后,UART总线进入空闲。
2.4 UART的数据位可变
因为UART是一种低速总线,每多发一位都占用不少的时间(由传输波特率决定),所以可以根据传输数据的特点,采用不同位宽以节约数据传输的时间。
三、uart的波特率
波特率等于每秒钟传输的数据位数。
常用的波特率有:9600、19200、38400、57600、115200,本工程所用到的是115200。
计算传输1bit需要的时间如下表:假如我们的全局时钟频率为100MHz,波特率设置为9600,那么意味着每秒该UART传输协议可以传输9600bits的数据。传输1比特需时间约为:10^9(ns)/9600=104166(ns)。
四、Verilog 模块划分
uart 的设计分为两个部分,一部分为发送模块,另一部分为接收模块。程 序设计主要通过状态机实现,系统框架图如下:
-
全局参数
parameter CHECK_BIT = "None" , //校验位 “None”无校验 “Odd”奇校验,“Even”偶校验
parameter BPS = 115200 , //系统波特率
parameter CLK = 50_000_000 , //系统时钟频率
parameter DATA_BIT = 8 , //数据位(6、7、8)
parameter STOP_BIT = 1 //停止位
五、发送模块(TX)
5.1 发送模块状态跳变
IDLE:空闲状态,无数据传输,输出高电平,当tx_data_vld信号到来时跳转到START;
START:起始位,无数据传输,输出低电平为 ,无条件跳转至DATA;
DATA:数据位,进行数据传输,先发送低比特,根据数据输出高低电平,假如有校验位,跳到CHECK,假如数据传输不设校验位,跳转到STOP;
CHECK:奇偶校验位,
STOP:停止位,高电平,共 1 位。 发送模块在上一状态向下一状态切换时,改变电平
5.2 Verilog 设计文件
/**************************************功能介绍***********************************
Date : 2023年9月18日21:37:53
Author : linxiaoxiao.
Version :
Description: uart串口发送模块
*********************************************************************************/
//例化模版
// tx_uart #(
// .CHECK_BIT ( "None" ), //校验位 “None”无校验 “Odd”奇校验,“Even”偶校验
// .BPS ( 115200 ), //系统波特率
// .CLK ( 50_000_000 ), //系统时钟频率
// .DATA_BIT ( 8 ), //数据位(6、7、8)
// .STOP_BIT ( 1 ) //停止位
// )tx_uart_inst(
// /* input */.clk (),
// /* input */.rst_n (),
// /* input */.tx_data_vld (),
// /* input [DATA_BIT - 1:0] */.tx_data (),
// /* output */.tx_ready (),
// /* output reg */.tx ()
// );
//---------<模块及端口声名>------------------------------------------------------
module tx_uart#(
parameter CHECK_BIT = "None" , //校验位 “None”无校验 “Odd”奇校验,“Even”偶校验
parameter BPS = 115200 , //系统波特率
parameter CLK = 50_000_000 , //系统时钟频率
parameter DATA_BIT = 8 , //数据位(6、7、8)
parameter STOP_BIT = 1 //停止位
)(
input clk ,
input rst_n ,
input tx_data_vld ,
input [DATA_BIT - 1:0] tx_data ,
output tx_ready ,
output reg tx
);
//---------<参数定义>---------------------------------------------------------
//波特计数最大值
localparam BPS_MAX = CLK/BPS;
//状态机参数定义
localparam IDLE = 0 ,//
START = 1 ,//
DATA = 2 ,//
CHECK = 3 ,
STOP = 4 ;//
//---------<内部信号定义>-----------------------------------------------------
reg [2:0] state ;//现态
//状态转移条件
wire idle2start ;
wire start2data ;
wire data2check ;
wire data2stop ;
wire check2stop ;
wire stop2idle ;
reg [25:0] bps_cnt ;
wire add_bps_cnt ,
end_bps_cnt ;
reg [3:0] bit_cnt ,
BIT_MAX ;
wire add_bit_cnt ,
end_bit_cnt ;
reg [DATA_BIT - 1:0] tx_data_r ;
wire check_vld ;//校验位使能
/****************************************************************
数据校验
****************************************************************/
//奇校验缩位同或
/* assign check_vld = ~^tx_data_r; */
//偶校验缩位异或
/* assign check_vld = ^tx_data_r; */
assign check_vld = (CHECK_BIT == "Odd")?~^tx_data_r:^tx_data_r;
/****************************************************************
输入数据寄存
****************************************************************/
always @(posedge clk or negedge rst_n)
if(!rst_n)
tx_data_r <= 'd0;
else if(tx_data_vld)
tx_data_r <= tx_data;
/****************************************************************
fsm
****************************************************************/
// 时序逻辑描述状态转移
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
state <= IDLE;
end
else case(state)
IDLE : if(idle2start) state <= START;
START : if(start2data) state <= DATA ;
DATA : if(data2check) state <= CHECK;
else if(data2stop ) state <= STOP ;
CHECK : if(check2stop) state <= STOP ;
STOP : if(stop2idle ) state <= IDLE ;
default : state <= IDLE ;
endcase
end
assign idle2start = state == IDLE && tx_data_vld;
assign start2data = state == START && end_bit_cnt;
assign data2check = state == DATA && end_bit_cnt && CHECK_BIT != "None";
assign data2stop = state == DATA && end_bit_cnt && CHECK_BIT == "None";
assign check2stop = state == CHECK && end_bit_cnt;
assign stop2idle = state == STOP && end_bit_cnt;
/****************************************************************
bps_cnt
****************************************************************/
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
bps_cnt <= 'd0;
end
else if(add_bps_cnt)begin
if(end_bps_cnt)begin
bps_cnt <= 'd0;
end
else begin
bps_cnt <= bps_cnt + 1'b1;
end
end
end
assign add_bps_cnt = state != IDLE;
assign end_bps_cnt = add_bps_cnt && bps_cnt == BPS_MAX - 1;
/****************************************************************
bit_cnt
****************************************************************/
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
bit_cnt <= 'd0;
end
else if(add_bit_cnt)begin
if(end_bit_cnt)begin
bit_cnt <= 'd0;
end
else begin
bit_cnt <= bit_cnt + 1'b1;
end
end
end
assign add_bit_cnt = end_bps_cnt;
assign end_bit_cnt = add_bit_cnt && bit_cnt == BIT_MAX - 1;
//计数器复用
always @(*)begin
case (state)
START : BIT_MAX = 1;
DATA : BIT_MAX = DATA_BIT;
CHECK : BIT_MAX = 1;
STOP : BIT_MAX = STOP_BIT;
default: BIT_MAX = 1;
endcase
end
/****************************************************************
tx时序
****************************************************************/
always @(*)begin
case (state)
IDLE : tx = 1;
START : tx = 0;
DATA : if(tx_data_r[bit_cnt]) tx= 1;
else tx = 0;
CHECK : tx = check_vld;
STOP : tx = 1;
default : tx = 1;
endcase
end
assign tx_ready = state == IDLE;
endmodule
5.3 仿真文件
`timescale 1ns/1ns
module tx_tb();
//激励信号定义
reg tb_clk ;
reg tb_rst_n ;
reg [7:0] tx_data ;
reg tx_data_vld ;
//输出信号定义
wire tx ;
//时钟周期参数定义
parameter CLOCK_CYCLE = 20;
//模块例化
tx_uart #(
.CHECK_BIT ( "None" ), //校验位 “None”无校验 “Odd”奇校验,“Even”偶校验
.BPS ( 115200 ), //系统波特率
.CLK ( 50_000_000 ), //系统时钟频率
.DATA_BIT ( 8 ), //数据位(6、7、8)
.STOP_BIT ( 1 ) //停止位
)tx_uart_inst(
/* input */.clk (tb_clk ),
/* input */.rst_n (tb_rst_n ),
/* input */.tx_data_vld (tx_data_vld ),
/* input [DATA_BIT - 1:0] */.tx_data (tx_data ),
/* output */.tx_ready (tx_ready ),
/* output reg */.tx (tx )
);
//产生时钟
initial tb_clk = 1'b0;
always #(CLOCK_CYCLE/2) tb_clk = ~tb_clk;
//复位
initial begin
tb_rst_n = 1'b1;
#(CLOCK_CYCLE*2);
tb_rst_n = 1'b0;
#(CLOCK_CYCLE*20);
tb_rst_n = 1'b1;
end
integer i;
//描述输入
initial begin
#(CLOCK_CYCLE*200);
#1;
tx_data = 0;
tx_data_vld = 0;
#(CLOCK_CYCLE*4);
for ( i= 0;i<10 ;i=i+1 ) begin
#(CLOCK_CYCLE*20);
tx_data_vld = 1;
tx_data = {$random}%256;
#20;
tx_data_vld = 0;
wait(tx_uart_inst.stop2idle == 1);
end
#100800;
$stop;
end
endmodule
5.4 测试结果
模拟多次数据传输:
放大:
5.5 上板测试(tx_text)
这里引入一个按键消抖模块作为接收使能,检验代码如下:
/**************************************功能介绍***********************************
Date :
Author : linxiaoxiao.
Version :
Description: 测试tx模块
*********************************************************************************/
//---------<模块及端口声名>------------------------------------------------------
module tx_text(
input clk ,
input rst_n ,
input key_in ,
output tx
);
//---------<内部信号定义>-----------------------------------------------------
wire key_down;
//模块例化
tx_uart #(
.CHECK_BIT ( "None" ), //校验位 “None”无校验 “Odd”奇校验,“Even”偶校验
.BPS ( 115200 ), //系统波特率
.CLK ( 50_000_000 ), //系统时钟频率
.DATA_BIT ( 8 ), //数据位(6、7、8)
.STOP_BIT ( 1 ) //停止位
)tx_uart_inst(
/* input */.clk ( clk ),
/* input */.rst_n ( rst_n ),
/* input */.tx_data_vld ( key_down ),
/* input [DATA_BIT - 1:0] */.tx_data ( 8'hac ),
/* output */.tx_ready ( tx_ready ),
/* output reg */.tx ( tx )
);
fsm_key_filter #( .WIDTH(1)) fsm_key_filter_inst (
/* input */.clk ( clk ),
/* input */.rst_n ( rst_n ),
/* input [WIDTH-1:0] */.key_in ( key_in ),
/* output reg [WIDTH-1:0]*/.key_down ( key_down )
);
endmodule
配置引脚
#引脚参数导入脚本
#时钟、复位
set_location_assignment PIN_E1 -to clk
set_location_assignment PIN_E15 -to rst_n
#UART
#set_location_assignment PIN_M2 -to uart_rx
set_location_assignment PIN_G1 -to tx
串口窗口 可设置奇偶校验:
六、接收模块(rx)
6.1 接收模块状态跳转
IDLE:空闲状态,无数据传输,输出高电平,当tx_data_vld信号到来时跳转到START;
START:起始位,无数据传输,输出低电平为 ,延时结束后至DATA;
DATA:数据位,进行数据传输,先发送低比特,根据数据输出高低电平,假如有校验位,跳到CHECK,假如数据传输不设校验位,跳转到STOP;
CHECK:奇偶校验位
STOP:停止位,高电平,共 1 位。 发送模块在上一状态向下一状态切换时,改变电平
6.2 接收模块 Verilog 设计文件
/**************************************功能介绍***********************************
Date : 2023年9月18日22:37:53
Author : linxiaoxiao.
Version :
Description: uart串口接收模块
*********************************************************************************/
//例化模版
// rx_uart #(
// .CHECK_BIT ( "None" ), //校验位 “None”无校验 “Odd”奇校验,“Even”偶校验
// .BPS ( 115200 ), //系统波特率
// .CLK ( 50_000_000 ), //系统时钟频率
// .DATA_BIT ( 8 ), //数据位(6、7、8)
// .STOP_BIT ( 1 ) //停止位
// )rx_uart_inst(
// /* input */ .clk (),
// /* input */ .rst_n (),
// /* input reg */ .rx (),
// /* output */ .rx_data_vld (),
// /* output [DATA_BIT - 1:0] */ .rx_data (),
// /* output */ .rx_ready ()
// // );
//---------<模块及端口声名>------------------------------------------------------
module rx_uart#(
parameter CHECK_BIT = "None" , //校验位 “None”无校验 “Odd”奇校验,“Even”偶校验
parameter BPS = 115200 , //系统波特率
parameter CLK = 50_000_000 , //系统时钟频率
parameter DATA_BIT = 8 , //数据位(6、7、8)
parameter STOP_BIT = 1 //停止位
)(
input clk ,
input rst_n ,
input rx ,
output rx_data_vld ,
output [DATA_BIT - 1:0] rx_data ,
output rx_ready
);
//---------<参数定义>---------------------------------------------------------
//波特计数最大值
localparam BPS_MAX = CLK/BPS;
//状态机参数定义
localparam IDLE = 0 ,//空闲
START = 1 ,//起始位
DATA = 2 ,//数据位
CHECK = 3 ,//奇偶校验位
STOP = 4 ;//停止位
//---------<内部信号定义>-----------------------------------------------------
reg [2:0] state ;//现态
//状态转移条件
wire idle2start ;
wire start2data ;
wire data2check ;
wire data2stop ;
wire check2stop ;
wire stop2idle ;
reg [25:0] bps_cnt ;
wire add_bps_cnt ,
end_bps_cnt ;
reg [3:0] bit_cnt ,
BIT_MAX ;
wire add_bit_cnt ,
end_bit_cnt ;
reg [DATA_BIT - 1:0] temp_data ;//输入数据零时缓存
reg check_reg ;//校验寄存
wire check_vld ;//校验位使能
reg rx_r0 ,//输入数据同步寄存
rx_r1 ;
wire flag_n ;//下降沿监测 确定rx采样时序
/****************************************************************
输入数据寄存(同步打拍)
****************************************************************/
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
rx_r0 <= 0;
rx_r1 <= 0;
end
else begin
rx_r0 <= rx;
rx_r1 <= rx_r0;
end
end
//下降沿标志
assign flag_n = !rx_r0 && rx_r1;
/****************************************************************
数据接收逻辑
****************************************************************/
always @(posedge clk or negedge rst_n)
if(!rst_n)
temp_data <= 'd0;
else if(state == DATA && bps_cnt == BPS_MAX >> 1)
temp_data[bit_cnt] <= rx_r0;
/****************************************************************
接收数据校验
****************************************************************/
always @(posedge clk or negedge rst_n)
if(!rst_n)
check_reg <= 'd0;
else if(state == CHECK && bps_cnt == BPS_MAX >> 1)
check_reg <= rx_r0;
//校验位选择
assign check_vld = (CHECK_BIT == "Odd")?~^temp_data : ^temp_data;
/****************************************************************
fsm
****************************************************************/
// 时序逻辑描述状态转移
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
state <= IDLE;
end
else case(state)
IDLE : if(idle2start) state <= START;
START : if(start2data) state <= DATA ;
DATA : if(data2check) state <= CHECK;
else if(data2stop ) state <= STOP ;
CHECK : if(check2stop) state <= STOP ;
STOP : if(stop2idle ) state <= IDLE ;
default : state <= IDLE ;
endcase
end
assign idle2start = state == IDLE && flag_n ;
assign start2data = state == START && end_bit_cnt;
assign data2check = state == DATA && end_bit_cnt && CHECK_BIT != "None";
assign data2stop = state == DATA && end_bit_cnt && CHECK_BIT == "None";
assign check2stop = state == CHECK && end_bit_cnt;
assign stop2idle = state == STOP && end_bit_cnt;
/****************************************************************
bps_cnt
****************************************************************/
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
bps_cnt <= 'd0;
end
else if(add_bps_cnt)begin
if(end_bps_cnt)begin
bps_cnt <= 'd0;
end
else begin
bps_cnt <= bps_cnt + 1'b1;
end
end
end
assign add_bps_cnt = state != IDLE;
assign end_bps_cnt = add_bps_cnt && bps_cnt == BPS_MAX - 1;
/****************************************************************
bit_cnt
****************************************************************/
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
bit_cnt <= 'd0;
end
else if(add_bit_cnt)begin
if(end_bit_cnt)begin
bit_cnt <= 'd0;
end
else begin
bit_cnt <= bit_cnt + 1'b1;
end
end
end
assign add_bit_cnt = end_bps_cnt;
assign end_bit_cnt = add_bit_cnt && bit_cnt == BIT_MAX - 1;
//计数器复用
always @(*)begin
case (state)
START : BIT_MAX = 1;
DATA : BIT_MAX = DATA_BIT;
CHECK : BIT_MAX = 1;
STOP : BIT_MAX = STOP_BIT;
default: BIT_MAX = 1;
endcase
end
/****************************************************************
输出逻辑
****************************************************************/
assign rx_ready = state == IDLE;
assign rx_data_vld = (CHECK_BIT == "None")? stop2idle
: (stop2idle && (check_vld == check_reg))? 1
: 0;
assign rx_data =(state == STOP && bit_cnt == STOP_BIT - 1) ? temp_data : 'b0 ;
endmodule
6.3 仿真文件
两种模拟输入方式,都在下面体现了:
`timescale 1ns/1ns
module rx_tb ();
//激励信号定义
reg clk ;
reg rst_n ;
reg /* tx_data_vld , */rx_din;
/* reg [7:0] tx_data ; */
/* wire tx ; */
//输出信号定义
wire /* tx_ready , */rx_data_vld ;
//时钟周期参数定义
parameter CLOCK_CYCLE = 20;
//模块例化
// tx_uart #(
// .CHECK_BIT ( "None" ), //校验位 “None”无校验 “Odd”奇校验,“Even”偶校验
// .BPS ( 115200 ), //系统波特率
// .CLK ( 50_000_000 ), //系统时钟频率
// .DATA_BIT ( 8 ), //数据位(6、7、8)
// .STOP_BIT ( 1 ) //停止位
// )tx_uart_inst(
// /* input */.clk (clk ),
// /* input */.rst_n (rst_n ),
// /* input */.tx_data_vld (tx_data_vld ),
// /* input [DATA_BIT - 1:0] */.tx_data (tx_data ),
// /* output */.tx_ready (tx_ready ),
// /* output reg */.tx (tx )
// );
rx_uart #(
.CHECK_BIT ( "None" ), //校验位 “None”无校验 “Odd”奇校验,“Even”偶校验
.BPS ( 115200 ), //系统波特率
.CLK ( 50_000_000 ), //系统时钟频率
.DATA_BIT ( 8 ), //数据位(6、7、8)
.STOP_BIT ( 1 ) //停止位
)rx_uart_inst(
/* input */ .clk ( clk ),
/* input */ .rst_n ( rst_n ),
/* input reg */ .rx ( rx_din ),
/* output */ .rx_data_vld ( rx_data_vld ),
/* output [DATA_BIT - 1:0] */ .rx_data ( rx_data ),
/* output */ .rx_ready ( rx_ready )
);
//产生时钟
initial clk = 1'b0;
always #(CLOCK_CYCLE/2) clk = ~clk;
//产生激励
initial begin
rst_n = 1'b1;
#(CLOCK_CYCLE*2);
rst_n = 1'b0;
#(CLOCK_CYCLE*20);
rst_n = 1'b1;
end
initial begin
repeat(5)begin
rx_din = 1'b1;
#(20*100)
rx_din = 1'b0;
#(434*20)
repeat(8)begin
rx_din = {$random};
#(434*20);
end
rx_din = 1'b1;
wait(rx_data_vld == 1);
end
#10000;
$stop;
// tx_data = 0;
// tx_data_vld = 0;
// #(CLOCK_CYCLE*6);
// tx_data_vld = 1;
// repeat(8) begin
// tx_data = {$random};#20;
// tx_data_vld = 0;
// #200;
// tx_data_vld = 1;
// wait(tx_ready == 1);
// #20;
// end
// #20000000;
// $stop;
end
endmodule
6.5 上板测验(加入tx模块的串口回环)
代码:
/**************************************功能介绍***********************************
Date :
Author : linxiaoxiao.
Version :
Description: 串口回环(未加fifo)
*********************************************************************************/
//---------<模块及端口声名>------------------------------------------------------
module top(
input clk ,
input rst_n ,
input rx ,
output tx
);
//---------<内部信号定义>-----------------------------------------------------
wire [7:0] rx_data ;
wire tx_ready ;
wire rx_data_vld;
//模块例化
tx_uart #(
.CHECK_BIT ( "None" ), //校验位 “None”无校验 “Odd”奇校验,“Even”偶校验
.BPS ( 115200 ), //系统波特率
.CLK ( 50_000_000 ), //系统时钟频率
.DATA_BIT ( 8 ), //数据位(6、7、8)
.STOP_BIT ( 1 ) //停止位
)tx_uart_inst(
/* input */.clk ( clk ),
/* input */.rst_n ( rst_n ),
/* input */.tx_data_vld ( rx_data_vld && tx_ready ),
/* input [DATA_BIT - 1:0] */.tx_data ( rx_data ),
/* output */.tx_ready ( tx_ready ),
/* output reg */.tx ( tx )
);
rx_uart #(
.CHECK_BIT ( "None" ), //校验位 “None”无校验 “Odd”奇校验,“Even”偶校验
.BPS ( 115200 ), //系统波特率
.CLK ( 50_000_000 ), //系统时钟频率
.DATA_BIT ( 8 ), //数据位(6、7、8)
.STOP_BIT ( 1 ) //停止位
)rx_uart_inst(
/* input */ .clk ( clk ),
/* input */ .rst_n ( rst_n ),
/* input reg */ .rx ( rx ),
/* output */ .rx_data_vld ( rx_data_vld ),
/* output [DATA_BIT - 1:0] */ .rx_data ( rx_data ),
/* output */ .rx_ready ( rx_ready )
);
endmodule
七、uart_ctrl模块(fifo)
7.1 引入fifo作为数据缓冲器的意义
在串口通信中,采用缓冲器FIFO的主要意义是解决数据传输中的**时序差异**问题,并提供数据的缓冲和流控能力。具体而言,它有以下几个作用:
1. 时序匹配:串口通信中发送方和接收方的时钟频率可能存在差异,使用FIFO可以将数据以一定的速率存储在缓冲区中,从而实现发送方和接收方之间的时序匹配,避免数据的丢失和错误。
2. 数据缓冲:在数据传输中,当发送方产生数据的速率快于接收方处理数据的速率时,FIFO可以暂时存储多出来的数据,保证数据不会丢失。
3. 流控控制:当接收方处理能力不足以处理发送方的数据时,FIFO可以用于流控,即发送方通过监测FIFO的状态来控制数据的发送速率,从而避免数据的丢失。
4. 缓存传输:一些应用场景中,需要在某个特定时刻对数据进行处理,而不是即时处理。FIFO可以将接收到的数据缓存在缓冲区中,待到特定时刻再进行处理。
总之,**FPGA串口数据缓冲器FIFO在数据传输中起到了时序匹配、数据缓冲、流控控制和缓存传输的重要作用,提高了数据传输的可靠性和灵活性。**
7.2 fifo IP的配置
7.3 uart_ctrl 设计文件
/**************************************功能介绍***********************************
Date : 2023年9月21日21:48:59
Author : linxiaoxiao.
Version :
Description: uart_ctrl控制模块——fifo
*********************************************************************************/
//---------<模块及端口声名>------------------------------------------------------
module uart_ctrl#(
.DATA_BIT ( 8 ) //数据位(6、7、8)更改数据位宽请注意同步修改FIFO IP配置
(
input clk ,
input rst_n ,
input [7:0] rx_data ,
input rx_data_vld ,
input tx_ready ,
output [7:0] tx_data ,
output tx_data_vld
);
//---------<内部信号定义>-----------------------------------------------------
wire fifo_empty ;
wire fifo_full ;
fifo fifo_inst (
.aclr ( ~rst_n ),
.clock ( clk ),
.data ( rx_data ),
.rdreq ( ~fifo_empty && tx_ready ),
.wrreq ( rx_data_vld && ~fifo_full ),
.empty ( fifo_empty ),
.full ( fifo_full ),
.q ( tx_data ),
.usedw ( )
);
assign tx_data_vld = tx_ready && ~fifo_empty ;
endmodule
八、顶层整合
8.1 top文件
/**************************************功能介绍***********************************
Date :
Author : linxiaoxiao.
Version :
Description:
*********************************************************************************/
//---------<模块及端口声名>------------------------------------------------------
module top#(.DATA_BIT ( 8 ) //数据位(6、7、8)根据需要修改括号中的数据即可
(
input clk ,
input rst_n ,
input rx ,
output tx
);
//---------<内部信号定义>-----------------------------------------------------
wire [DATA_BIT - 1 :0] tx_data , rx_data ;
wire tx_ready , rx_ready ;
wire tx_data_vld,rx_data_vld;
//模块例化
tx_uart #(
.CHECK_BIT ( "None" ), //校验位 “None”无校验 “Odd”奇校验,“Even”偶校验
.BPS ( 115200 ), //系统波特率
.CLK ( 50_000_000 ), //系统时钟频率
.DATA_BIT ( 8 ), //数据位(6、7、8)
.STOP_BIT ( 1 ) //停止位
)tx_uart_inst(
/* input */.clk ( clk ),
/* input */.rst_n ( rst_n ),
/* input */.tx_data_vld ( tx_data_vld ),
/* input [DATA_BIT - 1:0] */.tx_data ( tx_data ),
/* output */.tx_ready ( tx_ready ),
/* output reg */.tx ( tx )
);
uart_ctrl uart_ctrl_inst(
/* input */.clk ( clk ),
/* input */.rst_n ( rst_n ),
/* input [7:0] */.rx_data ( rx_data ),
/* input */.rx_data_vld ( rx_data_vld ),
/* input */.tx_ready ( tx_ready ),
/* output [7:0] */.tx_data ( tx_data ),
/* output */.tx_data_vld ( tx_data_vld )
);
rx_uart #(
.CHECK_BIT ( "None" ), //校验位 “None”无校验 “Odd”奇校验,“Even”偶校验
.BPS ( 115200 ), //系统波特率
.CLK ( 50_000_000 ), //系统时钟频率
.DATA_BIT ( 8 ), //数据位(6、7、8)
.STOP_BIT ( 1 ) //停止位
)rx_uart_inst(
/* input */ .clk ( clk ),
/* input */ .rst_n ( rst_n ),
/* input reg */ .rx ( rx ),
/* output */ .rx_data_vld ( rx_data_vld ),
/* output [DATA_BIT - 1:0] */ .rx_data ( rx_data ),
/* output */ .rx_ready ( rx_ready )
);
endmodule
8.2 整体仿真
8.2.1 仿真文件
`timescale 1ns/1ns
module tb_top ();
//激励信号定义
reg clk ;
reg rst_n ;
reg rx_din ;
//时钟周期参数定义
parameter CLOCK_CYCLE = 20;
top top_inst(
/* input */.clk ( clk ),
/* input */.rst_n ( rst_n ),
/* input */.rx ( rx_din ),
/* output */.tx ( tx )
);
//产生时钟
initial clk = 1'b0;
always #(CLOCK_CYCLE/2) clk = ~clk;
//产生激励
initial begin
rst_n = 1'b1;
#(CLOCK_CYCLE*2);
rst_n = 1'b0;
#(CLOCK_CYCLE*20);
rst_n = 1'b1;
end
initial begin
repeat(5)begin
rx_din = 1'b1;
#(20*100)
rx_din = 1'b0;
#(434*20)
repeat(8)begin
rx_din = {$random};
#(434*20);
end
rx_din = 1'b1;
wait(top_inst.rx_uart_inst.rx_data_vld == 1);
end
#10000;
$stop;
end
endmodule