上次版本的缺陷就是没有解决奇偶校验,本次设计主要解决该问题,并且加入停止位位数的选择。
接收方和发送方会约定一种校验方式,在一定程度内,来检测接收到的数据是否正确。通常专门设置一个奇偶校验位,用它使这组数据和奇偶校验位中"1"的个数为奇数或偶数。
奇校验:让传输数据(包含校验位)中1的个数为奇数。即:如果数据位中1的个数是偶数,则校验位为“1”,个数为奇数则校验位为”0”。数据位和校验位发送给接受方后,接收方再次对数据位和校验位中1的个数进行统计,如果为奇数则校验通过,表示此次传输过程未发生错误。如果不是奇数,则表示有错误发生,此时接收方可以向发送方发送请求,要求重新发送一遍数据。
偶校验:让传输数据(包含校验位)中1的个数为偶数。即:如果数据位中1的个数是偶数,则校验位为“0”,个数为奇数则校验位为”1”。数据和校验位发送给接受方后,接收方再次对数据位和校验位中1的个数进行统计,如果为偶数则校验通过,表示此次传输过程未发生错误。如果不是偶数,则表示有错误发生,此时接收方可以向发送方发送请求,要求重新发送一遍数据。
优缺点:奇偶校验的检错率只有50%,只有奇数个数据位发生变化能检测到,如果偶数个数据位发生变化则不会被发现。奇偶校验每传输一个字节都需要加一位校验位,会降低传输效率。奇偶校验只能发现错误,但不能纠正错误。
常用格式为1位停止位,但在串口助手里面会见到2位停止位和1.5位停止位的设置方式,所以会增加这个设置。
停止位其实就是将数据线拉高表示停止传输数据,并且为下一次数据传输做准备。1位和2位停止位的区别在于数据线拉高持续的时间长短不同。以上节分析为准,比如波特率9600bit/s,时钟频率为50MHz时,约5208个时钟传输一位数据,那么1位停止位时高电平持续5208个时钟,2位停止位时高电平应持续52082个时钟周期,如果是1.5位,则高电平持续时间为52081.5即可。
理论上需要检测停止位数据位数,但是有效数据在停止位之前就已经接收完了,停止位只是将数据线拉高一段时间,结束本次数据传输,为下一次数据传输做准备。所以在接收数据时,其实可以不考虑停止位,下面会提供两种文件,其中一种是不考虑停止位接收的,另一种就是考虑停止位位数的。
书接上节,在计数器bps_cnt == BPS_CNT/2-1时采集来自数据线上的数据,但是这个条件会被多个电路作为判断条件,造成组合逻辑延迟过长,从而降低系统的最大时钟频率。所以此处使用一个寄存器read_flag来把该条件暂存一下,由于时序逻辑会延迟一个时钟,所以读取数据的条件read_flag可以改为bps_cnt == BPS_CNT/2-2,对应代码如下:
reg read_flag;
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin//
read_flag <= 0;
end
else begin
read_flag <= add_bps_cnt && bps_cnt == BPS_CNT/2-2;
end
end
根据上一节内容,其实uart数据传输的格式已经明确。设计的整体思路不会变,只是计数器cnt的结束条件会受停止位的影响,接收到的数据不会马上输出,会与接收到的校验位进行比对后,如果正确才把接收到的数据输出。这里先给出包含停止位的设计,不包含停止的其实就是将这部分去掉而已。
首先加入一个2位的parameter常量STOP_W用于表示停止位的位数,其值为2’b01表示1位停止位,2’b10表示2位停止位,2’b11表示1.5位停止位,2’b00无效。再加入一个2位的parameter常量CHECK_W用于表示校验方式,其值为2’b00或2’b11代表无校验位,2’b01表示奇校验,2’b10表示偶校验。
parameter CHECK_W = 2'b00 ,//校验位,2'b00代表无校验位,2'b01表示奇校验,2'b10表示偶校验,2'b11无效。
parameter STOP_W = 2'b11 //停止位,2'b01表示1位停止位,2'b10表示2位停止位,2'b11表示1.5位停止位;
接下来就是确定计数器bps_cnt和计数器cnt的结束条件了,计数器cnt的结束条件会根据停止位的位数不同分为两种情况,如果停止位为整数,那么计数器cnt计数到DATA_W + (^CHECK_W) + ((STOP_W2’b11) ? 1 : STOP_W)时结束,其中DATA_W表示数据传输位数,(^CHECK_W) 为1表示有校验位,否则表示无校验位,当停止位为整数时,STOP_W的值就是停止位位数。当停止位为1.5位(STOP_W2’b11)时,计数器cnt需要计数到DATA_W + (^CHECK_W) + ((STOP_W==2’b11) ? 1 : STOP_W)并且read_flag为高电平时计数器cnt清零。
通过下面代码实现上述两种情况的生成,并且利用clogb2函数自动计算出计数器cnt位宽。
localparam CNT_NUM = DATA_W + (^CHECK_W) + ((STOP_W==2'b11) ? 1 : STOP_W);//计数器计数值;
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin//
cnt <= {{CNT_NUM_W}{1'b0}};
end
else if(end_cnt)begin
cnt <= {{CNT_NUM_W}{1'b0}};
end
else if(end_bps_cnt)begin
cnt <= cnt + {{{CNT_NUM_W-1}{1'b0}},1'b1};
end
end
//根据停止位的不同生成不同的计数器结束条件;
generate
if(STOP_W == 2'b11)//1.5位停止位,因为CNT_NUM没有包含起始位,所以要等计数器计数到CNT_NUM时清零;
assign end_cnt = cnt== CNT_NUM && read_flag;
else//停止位为1位或者2位;
assign end_cnt = end_bps_cnt && cnt== CNT_NUM;
endgenerate
另外就是计数器bps_cnt的条件应该变为end_bps_cnt || end_cnt,否则停止位不为整数时,该计数器无法清零。
接下来就是处理校验位了,整体思路通过判断^CHECK_W是否有效,利用generate if() 结构综合不同电路,从而节省资源。如果^CHECK_W表示没有校验位,则综合时不生成接收校验位的相关电路,根据校验类型生成相应接收电路,从而节约资源。
如果有校验位,则在计数器cnt计数到DATA_W+1时提取校验位,在计数器cnt计数结束的时候,对接收到的数据和校验位进行比较,当校验为奇校验时,应满足^rx_data == ~check表示接收到的数据是正确的,将数据输出,否则数据传输错误,不更新输出数据。当校验位为偶校验时应满足^rx_data == check表示接收到的数据是正确的,将数据输出,否则数据传输错误,不更新输出数据。没有校验时,直接将接收到的数据输出,另外输出数据的指示信号也要与输出的数据对齐。本部分对应代码如下:
generate
if(CHECK_W == 2'b01)begin//如果是奇校验,则综合这部分电路。
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin//
check <= 0;
end//接收校验位;
else if(cnt==DATA_W + 1 && read_flag)begin
check <= uart_rx_ff2;
end
end
//当接收完一组数据后,将接收到的数据经过一组触发器暂存后输出;
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin//
rx_out <= 0;
end
else if(rx_vld && ^rx_data == ~check)begin//当校验正确时,将接收到的数据输出;
rx_out <= rx_data;
end
end
//在接收完数据后,拉高一个时钟,指示产生rx_out信号有效;
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
rx_out_vld <= 1'b0;
end
else begin//将数据有效指示信号和校验结果相与后输出;
rx_out_vld <= rx_vld && ^rx_data == ~check;
end
end
end
else if(CHECK_W == 2'b10)begin//如果是偶校验,则综合这部分代码;
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin//
check <= 0;
end//接收校验位;
else if(cnt==DATA_W + 1 && read_flag)begin
check <= uart_rx_ff2;
end
end
//当接收完一组数据后,将接收到的数据经过一组触发器暂存后输出;
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin//
rx_out <= 0;
end
else if(rx_vld && ^rx_data == check)begin//当校验正确时,将接收到的数据输出;
rx_out <= rx_data;
end
end
//在接收完数据后,拉高一个时钟,指示产生rx_out信号有效;
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
rx_out_vld <= 1'b0;
end
else begin//将数据有效指示信号和校验结果相与后输出;
rx_out_vld <= rx_vld && ^rx_data == check;
end
end
end
else begin//如果没有校验位,则综合这部分代码。
//当接收完一组数据后,将接收到的数据经过一组触发器暂存后输出;
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin//
rx_out <= 0;
end
else if(rx_vld)begin//如果没有校验位,将接收到的数据直接输出;
rx_out <= rx_data;
end
end
//在接收完数据后,拉高一个时钟,指示产生rx_out信号有效;
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
rx_out_vld <= 1'b0;
end
else begin//将接收到的数据有效指示信号与数据对齐输出;
rx_out_vld <= rx_vld;
end
end
end
endgenerate
不包含停止位的就是将上述停止位相关的电路去掉即可。
包含检测停止位的参考代码如下所示使用该模块时,只需要修改5个parameter变量即可,当rx_out_vld为高电平时,rx_out的数据就是串口接收到的数。
//--###############################################################################################
//--# Designer : 发送一位数据所需系统时钟数计算方式BPS_CNT = 1000_000_000/(Tclk*比特率),
//Tclk是系统时钟周期,单位ns。
//--###############################################################################################
module uart_rx #(
parameter FLCK = 50_000_000 ,//系统时钟频率,默认50MHZ;
parameter BPS = 9600 ,//串口波特率;
parameter DATA_W = 8 ,//接收数据位数以及输出数据位宽;
parameter CHECK_W = 2'b00 ,//校验位,2'b00代表无校验位,2'b01表示奇校验,2'b10表示偶校验,2'b11无效。
parameter STOP_W = 2'b11 //停止位,2'b01表示1位停止位,2'b10表示2位停止位,2'b11表示1.5位停止位;
)(
input clk ,//系统工作时钟50MHZ
input rst_n ,//系统复位信号,低电平有效
input uart_rx ,//UART接口输入信号
output reg [DATA_W-1:0] rx_out ,//数据输出信号
output reg rx_out_vld //数据有效指示信号
);
localparam BPS_CNT = FLCK/BPS;//波特率为9600bit/s,当波特率为115200bit/s时,DATA_115200==434;
localparam BPS_CNT_W = clogb2(BPS_CNT-1);//根据BPS_CNT调用函数自动计算计数器bps_cnt位宽;
localparam CNT_NUM = DATA_W + (^CHECK_W) + ((STOP_W==2'b11) ? 1 : STOP_W);//计数器计数值;
localparam CNT_NUM_W = clogb2(CNT_NUM);//根据计数器cnt的值,利用函数自动计算此计数器的位宽;
reg rx_vld ;//表示接收完一组串口发来的数据了;
reg check ;//接收的校验位数据;
reg uart_rx_ff0 ;
reg uart_rx_ff1 ;
reg uart_rx_ff2 ;
reg flag ;
reg [BPS_CNT_W-1:0] bps_cnt ;
reg [CNT_NUM_W-1:0] cnt ;
reg [DATA_W-1:0] rx_data ;
wire end_cnt ;
wire add_bps_cnt ;
wire end_bps_cnt ;
/******************注释开始******************
自动计算信号位宽;
******************注释结束******************/
function integer clogb2(input integer depth);begin
if(depth==0)
clogb2 = 1;
else if(depth!=0)
for(clogb2=0;depth>0;clogb2=clogb2+1)
depth=depth>>1;
end
endfunction
/******************注释开始******************
接收一位数据所用时间计数器bps_cnt,初始值为0,当接收到数据时进行计数,
当一位数据接收完成时清零;
******************注释结束******************/
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
bps_cnt <= {{BPS_CNT_W}{1'b0}};
end
else if(add_bps_cnt)begin
if(end_bps_cnt || end_cnt)
bps_cnt <= {{BPS_CNT_W}{1'b0}};
else
bps_cnt <= bps_cnt + {{{BPS_CNT_W-1}{1'b0}},1'b1};
end
end
assign add_bps_cnt = flag;
assign end_bps_cnt = add_bps_cnt && bps_cnt==BPS_CNT-1;
//在每位数据传输的中部读取数据,所以设立一个读取数据的标志位增强时序。
//由于该标志信号是时序电路,故提前一个时钟为(BPS_CNT/2-1)-1=BPS_CNT/2-2
reg read_flag;
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin//
read_flag <= 0;
end
else begin
read_flag <= add_bps_cnt && bps_cnt == BPS_CNT/2-2;
end
end
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin//
cnt <= {{CNT_NUM_W}{1'b0}};
end
else if(end_cnt)begin
cnt <= {{CNT_NUM_W}{1'b0}};
end
else if(end_bps_cnt)begin
cnt <= cnt + {{{CNT_NUM_W-1}{1'b0}},1'b1};
end
end
//根据停止位的不同生成不同的计数器结束条件;
generate
if(STOP_W == 2'b11)//1.5位停止位,因为CNT_NUM没有包含起始位,所以要等计数器计数到CNT_NUM时清零;
assign end_cnt = cnt== CNT_NUM && read_flag;
else//停止位为1位或者2位;
assign end_cnt = end_bps_cnt && cnt== CNT_NUM;
endgenerate
/******************注释开始******************
PC端相对应于FPGA为异步接口,为预防亚稳态产生,对接收数据进行打两拍处理,由于需要采集信号下降沿,故打三拍处理;
******************注释结束******************/
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin//三个寄存器组成移位寄存器,初始化为0;
{uart_rx_ff2,uart_rx_ff1,uart_rx_ff0} <= 3'd0;
end
else begin//时钟上升沿时,将uart_rx信号移入移位寄存器,其余位左移一位;
{uart_rx_ff2,uart_rx_ff1,uart_rx_ff0} <= {uart_rx_ff1,uart_rx_ff0,uart_rx};
end
end
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
flag <= 1'b0;
end
else if(uart_rx_ff2==1 && uart_rx_ff1==0)begin//取UART_RX信号下降沿
flag <= 1'b1;
end
else if(end_cnt)begin//一组数据接收完毕;
flag <= 1'b0;
end
end
//在中间时刻对输入数据进行采集,并且将数据存入rx_data;
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
rx_data <= {{DATA_W}{1'b0}};
end
else if(cnt>=1 && cnt<=DATA_W && read_flag)begin
rx_data[cnt-1] <= uart_rx_ff2;
end
end
//在接收完数据后,指示产生rx_data信号有效;
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
rx_vld <= 1'b0;
end
else begin
rx_vld <= (end_cnt);
end
end
generate
if(CHECK_W == 2'b01)begin//如果是奇校验,则综合这部分电路。
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin//
check <= 0;
end//接收校验位;
else if(cnt==DATA_W + 1 && read_flag)begin
check <= uart_rx_ff2;
end
end
//当接收完一组数据后,将接收到的数据经过一组触发器暂存后输出;
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin//
rx_out <= 0;
end
else if(rx_vld && ^rx_data == ~check)begin//当校验正确时,将接收到的数据输出;
rx_out <= rx_data;
end
end
//在接收完数据后,拉高一个时钟,指示产生rx_out信号有效;
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
rx_out_vld <= 1'b0;
end
else begin//将数据有效指示信号和校验结果相与后输出;
rx_out_vld <= rx_vld && ^rx_data == ~check;
end
end
end
else if(CHECK_W == 2'b10)begin//如果是偶校验,则综合这部分代码;
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin//
check <= 0;
end//接收校验位;
else if(cnt==DATA_W + 1 && read_flag)begin
check <= uart_rx_ff2;
end
end
//当接收完一组数据后,将接收到的数据经过一组触发器暂存后输出;
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin//
rx_out <= 0;
end
else if(rx_vld && ^rx_data == check)begin//当校验正确时,将接收到的数据输出;
rx_out <= rx_data;
end
end
//在接收完数据后,拉高一个时钟,指示产生rx_out信号有效;
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
rx_out_vld <= 1'b0;
end
else begin//将数据有效指示信号和校验结果相与后输出;
rx_out_vld <= rx_vld && ^rx_data == check;
end
end
end
else begin//如果没有校验位,则综合这部分代码。
//当接收完一组数据后,将接收到的数据经过一组触发器暂存后输出;
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin//
rx_out <= 0;
end
else if(rx_vld)begin//如果没有校验位,将接收到的数据直接输出;
rx_out <= rx_data;
end
end
//在接收完数据后,拉高一个时钟,指示产生rx_out信号有效;
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
rx_out_vld <= 1'b0;
end
else begin//将接收到的数据有效指示信号与数据对齐输出;
rx_out_vld <= rx_vld;
end
end
end
endgenerate
endmodul
不包含停止位的参考代码如下:
//--###############################################################################################
//--# Designer : 发送一位数据所需系统时钟数计算方式BPS_CNT = 1000_000_000/(Tclk*比特率),
//Tclk是系统时钟周期,单位ns。
//--###############################################################################################
module uart_rx #(
parameter FLCK = 50_000_000 ,//系统时钟频率,默认50MHZ;
parameter BPS = 9600 ,//串口波特率;
parameter DATA_W = 8 ,//接收数据位数以及输出数据位宽;
parameter CHECK_W = 2'b00 //校验位,2'b00代表无校验位,2'b01表示奇校验,2'b10表示偶校验,2'b11无效。
)(
input clk ,//系统工作时钟50MHZ
input rst_n ,//系统复位信号,低电平有效
input uart_rx ,//UART接口输入信号
output reg [DATA_W-1:0] rx_out ,//数据输出信号
output reg rx_out_vld //数据有效指示信号
);
localparam BPS_CNT = FLCK/BPS;//波特率为9600bit/s,当波特率为115200bit/s时,DATA_115200==434;
localparam BPS_CNT_W = clogb2(BPS_CNT-1);//根据BPS_CNT调用函数自动计算计数器bps_cnt位宽;
localparam CNT_NUM = DATA_W + (^CHECK_W);//计数器计数值;
localparam CNT_NUM_W = clogb2(CNT_NUM);//根据计数器cnt的值,利用函数自动计算此计数器的位宽;
reg rx_vld ;//表示接收完一组串口发来的数据了;
reg check ;//接收的校验位数据;
reg uart_rx_ff0 ;
reg uart_rx_ff1 ;
reg uart_rx_ff2 ;
reg flag ;
reg [BPS_CNT_W-1:0] bps_cnt ;
reg [CNT_NUM_W-1:0] cnt ;
reg [DATA_W-1:0] rx_data ;
wire add_cnt ;
wire end_cnt ;
wire add_bps_cnt ;
wire end_bps_cnt ;
/******************注释开始******************
自动计算信号位宽;
******************注释结束******************/
function integer clogb2(input integer depth);begin
if(depth==0)
clogb2 = 1;
else if(depth!=0)
for(clogb2=0;depth>0;clogb2=clogb2+1)
depth=depth>>1;
end
endfunction
/******************注释开始******************
接收一位数据所用时间计数器bps_cnt,初始值为0,当接收到数据时进行计数,
当一位数据接收完成时清零;
******************注释结束******************/
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
bps_cnt <= {{BPS_CNT_W}{1'b0}};
end
else if(add_bps_cnt)begin
if(end_bps_cnt)
bps_cnt <= {{BPS_CNT_W}{1'b0}};
else
bps_cnt <= bps_cnt + {{{BPS_CNT_W-1}{1'b0}},1'b1};
end
end
assign add_bps_cnt = flag;
assign end_bps_cnt = add_bps_cnt && bps_cnt==BPS_CNT-1;
//在每位数据传输的中部读取数据,所以设立一个读取数据的标志位增强时序。
//由于该标志信号是时序电路,故提前一个时钟为(BPS_CNT/2-1)-1=BPS_CNT/2-2
reg read_flag;
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin//
read_flag <= 0;
end
else begin
read_flag <= add_bps_cnt && bps_cnt == BPS_CNT/2-2;
end
end
//接收一组数据所用时间;
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin//
cnt <= 0;
end
else if(add_cnt)begin
if(end_cnt)
cnt <= 0;
else
cnt <= cnt + 1;
end
end
assign add_cnt = end_bps_cnt;
assign end_cnt = add_cnt && cnt == CNT_NUM;
/******************注释开始******************
PC端相对应于FPGA为异步接口,为预防亚稳态产生,对接收数据进行打两拍处理,由于需要采集信号下降沿,故打三拍处理;
******************注释结束******************/
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin//三个寄存器组成移位寄存器,初始化为0;
{uart_rx_ff2,uart_rx_ff1,uart_rx_ff0} <= 3'd0;
end
else begin//时钟上升沿时,将uart_rx信号移入移位寄存器,其余位左移一位;
{uart_rx_ff2,uart_rx_ff1,uart_rx_ff0} <= {uart_rx_ff1,uart_rx_ff0,uart_rx};
end
end
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
flag <= 1'b0;
end
else if(uart_rx_ff2==1 && uart_rx_ff1==0)begin//取UART_RX信号下降沿
flag <= 1'b1;
end
else if(end_cnt)begin//一组数据接收完毕;
flag <= 1'b0;
end
end
//在中间时刻对输入数据进行采集,并且将数据存入rx_data;
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
rx_data <= {{DATA_W}{1'b0}};
end
else if(cnt>=1 && cnt<=DATA_W && read_flag)begin
rx_data[cnt-1] <= uart_rx_ff2;
end
end
//在接收完数据后,指示产生rx_data信号有效;
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
rx_vld <= 1'b0;
end
else begin
rx_vld <= (end_cnt);
end
end
generate
if(CHECK_W == 2'b01)begin//如果是奇校验,则综合这部分电路。
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin//
check <= 0;
end//接收校验位;
else if(cnt==DATA_W + 1 && read_flag)begin
check <= uart_rx_ff2;
end
end
//当接收完一组数据后,将接收到的数据经过一组触发器暂存后输出;
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin//
rx_out <= 0;
end
else if(rx_vld && ^rx_data == ~check)begin//当校验正确时,将接收到的数据输出;
rx_out <= rx_data;
end
end
//在接收完数据后,拉高一个时钟,指示产生rx_out信号有效;
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
rx_out_vld <= 1'b0;
end
else begin//将数据有效指示信号和校验结果相与后输出;
rx_out_vld <= rx_vld && ^rx_data == ~check;
end
end
end
else if(CHECK_W == 2'b10)begin//如果是偶校验,则综合这部分代码;
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin//
check <= 0;
end//接收校验位;
else if(cnt==DATA_W + 1 && read_flag)begin
check <= uart_rx_ff2;
end
end
//当接收完一组数据后,将接收到的数据经过一组触发器暂存后输出;
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin//
rx_out <= 0;
end
else if(rx_vld && ^rx_data == check)begin//当校验正确时,将接收到的数据输出;
rx_out <= rx_data;
end
end
//在接收完数据后,拉高一个时钟,指示产生rx_out信号有效;
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
rx_out_vld <= 1'b0;
end
else begin//将数据有效指示信号和校验结果相与后输出;
rx_out_vld <= rx_vld && ^rx_data == check;
end
end
end
else begin//如果没有校验位,则综合这部分代码。
//当接收完一组数据后,将接收到的数据经过一组触发器暂存后输出;
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin//
rx_out <= 0;
end
else if(rx_vld)begin//如果没有校验位,将接收到的数据直接输出;
rx_out <= rx_data;
end
end
//在接收完数据后,拉高一个时钟,指示产生rx_out信号有效;
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
rx_out_vld <= 1'b0;
end
else begin//将接收到的数据有效指示信号与数据对齐输出;
rx_out_vld <= rx_vld;
end
end
end
endgenerate
endmodule
利用Quartus对改源文件进行综合后,如下图所示,消耗62个逻辑块,40个LUT。
利用Quartus对改源文件进行综合后,如下图所示,消耗63个逻辑块、41个LUT。
对其进行时序约束后,重新综合结果如下,最大时钟频率为173.73MHz,大于50MHz,该系统能够正常工作。
包含停止位接收Testbetch参考:
`timescale 1 ns/1 ns
module uart_rx_test();
parameter CYCLE = 20;//The unit is ns. The default value is 10ns;
parameter RST_TIME = 10;//Reset time: Reset 3 clock widths by default;
parameter STOP_TIME = 1000;//Time for simulation running after reset (unit: clock cycle). Simulation stops after 1000 clocks are run by default;
// uart_rx Parameters
parameter FCLK = 50_000_000;//系统时钟频率;
parameter BPS = 9600 ;//串口波特率;
parameter BPS_CNT = FCLK/BPS ;//波特率对应时钟数,不用手动修改该参数;
parameter DATA_W = 8 ;//接收数据位数以及输出数据位宽;
parameter CHECK_W = 2'b01 ;//校验位,2'b00代表无校验位,2'b01表示奇校验,2'b10表示偶校验,2'b11无效。
parameter STOP_W = 2'b11 ;//停止位,2'b01表示1位停止位,2'b10表示2位停止位,2'b11表示1.5位停止位;
// uart_rx Inputs
reg clk ;
reg rst_n ;
reg uart_tx ;
// uart_rx Outputs
wire [DATA_W-1:0] rx_out ;
wire rx_out_vld ;
//例化串口接收模块;
uart_rx #(
.FLCK ( FCLK ),
.BPS ( BPS ),
.DATA_W ( DATA_W ),
.CHECK_W ( CHECK_W ),
.STOP_W ( STOP_W ))
u_uart_rx (
.clk ( clk ),
.rst_n ( rst_n ),
.uart_rx ( uart_tx ),
.rx_out ( rx_out ),
.rx_out_vld ( rx_out_vld )
);
//The local clock is generated at 100 MB;
initial begin
clk = 0;
forever #(CYCLE/2) clk=~clk;
end
//Generate reset signal;
initial begin
rst_n = 1;
#2; rst_n = 0;
#(RST_TIME*CYCLE);//复位完成;
rst_n = 1;
end
//Input signal din assignment method;
initial begin
#1;uart_tx = 1; //初始化时输入高电平;
#(100*CYCLE); //Start assigning values;
tx(8'ha5); //以串口形式发送8'h5a;
#(500*CYCLE); //发送完成后延迟500个时钟;
tx(8'h57); //之后发送数据8'h57;
#(500*CYCLE); //发送完成后延迟500个时钟;
$stop; //Stop simulation;
end
//模拟串口发送函数,1位起始位,1位停止位,无校验位,8位数据,先发低位;
integer i;//用于控制循环次数;
task tx(
input [DATA_W-1:0] data //串口待发送数据;
);
begin
@(posedge clk);//延迟一个时钟后发送起始位;
#1; uart_tx = 1'b0;
repeat(BPS_CNT) @(posedge clk);//延迟BPS_CNT个时钟;
for(i=0 ; i<8 ; i=i+1)begin
#1; uart_tx = data[i];
repeat(BPS_CNT) @(posedge clk);//延迟BPS_CNT个时钟;
end
if(CHECK_W == 2'b01)begin
#1;uart_tx = ~(^data);//奇校验时,发送数据;
repeat(BPS_CNT) @(posedge clk);//延迟BPS_CNT个时钟;
end
else if(CHECK_W == 2'b10)begin
#1;uart_tx = (^data);//偶校验时,发送数据;
repeat(BPS_CNT) @(posedge clk);//延迟BPS_CNT个时钟;
end
@(posedge clk);//延迟一个时钟后发送停止位;
#1; uart_tx = 1'b1;
if(STOP_W == 2'b01)//1位停止位;
repeat(BPS_CNT) @(posedge clk);//延迟BPS_CNT个时钟;
else if(STOP_W == 2'b10)//2位停止位;
repeat(2*BPS_CNT) @(posedge clk);//延迟2*BPS_CNT个时钟;
else if(STOP_W == 2'b11)//1.5位停止位;
repeat(BPS_CNT*3/2) @(posedge clk);//延迟1.5*BPS_CNT个时钟;
end
endtask
endmodule
如下图所示,以奇校验、1.5位停止位的产生串口测试信号;
TestBetch通过调用rx()任务以上述格式先后发送8’ha5和8’h57给串口接收模块。
运行上述仿真,串口接收模块接收波形如下所示,rx_out准确接收到上述发送信号,如图8所示。
在计数器cnt为数据位8+1=9并且read_falg为高电平时采集uart_rx_ff2数据作为校验位数据。
其余仿真就不介绍了,之前那一节仿真时序已经做了详细讲解,此工程也会直接提供,使用时只需要配置几个parameter参数即可,如果想要修改仿真发送的数据,也只需要修改那几个parameter变量,以及调用rx()任务即可,其余部分不需要做任何修改。
不检测停止位的代码测试参考文件如下:
下面是不检测停止位的仿真,对比前面仿真可知,这种方式会在校验位传输完后就输出接收到的数据,会快那么一点,最后就不上板了(目前身边没有altera下载器)。
`timescale 1 ns/1 ns
module uart_rx_test();
parameter CYCLE = 20;//The unit is ns. The default value is 10ns;
parameter RST_TIME = 10;//Reset time: Reset 3 clock widths by default;
parameter STOP_TIME = 1000;//Time for simulation running after reset (unit: clock cycle). Simulation stops after 1000 clocks are run by default;
// uart_rx Parameters
parameter FCLK = 50_000_000;//系统时钟频率;
parameter BPS = 9600 ;//串口波特率;
parameter BPS_CNT = FCLK/BPS ;//波特率对应时钟数,不用手动修改该参数;
parameter DATA_W = 8 ;//接收数据位数以及输出数据位宽;
parameter CHECK_W = 2'b01 ;//校验位,2'b00代表无校验位,2'b01表示奇校验,2'b10表示偶校验,2'b11无效。
parameter STOP_W = 2'b11 ;//停止位,2'b01表示1位停止位,2'b10表示2位停止位,2'b11表示1.5位停止位;
// uart_rx Inputs
reg clk ;
reg rst_n ;
reg uart_tx ;
// uart_rx Outputs
wire [DATA_W-1:0] rx_out ;
wire rx_out_vld ;
//例化串口接收模块;
uart_rx #(
.FLCK ( FCLK ),
.BPS ( BPS ),
.DATA_W ( DATA_W ),
.CHECK_W ( CHECK_W ),
.STOP_W ( STOP_W ))
u_uart_rx (
.clk ( clk ),
.rst_n ( rst_n ),
.uart_rx ( uart_tx ),
.rx_out ( rx_out ),
.rx_out_vld ( rx_out_vld )
);
//The local clock is generated at 100 MB;
initial begin
clk = 0;
forever #(CYCLE/2) clk=~clk;
end
//Generate reset signal;
initial begin
rst_n = 1;
#2; rst_n = 0;
#(RST_TIME*CYCLE);//复位完成;
rst_n = 1;
end
//Input signal din assignment method;
initial begin
#1;uart_tx = 1; //初始化时输入高电平;
#(100*CYCLE); //Start assigning values;
tx(8'ha5); //以串口形式发送8'h5a;
#(500*CYCLE); //发送完成后延迟500个时钟;
tx(8'h57); //之后发送数据8'h57;
#(500*CYCLE); //发送完成后延迟500个时钟;
$stop; //Stop simulation;
end
//模拟串口发送函数,1位起始位,1位停止位,无校验位,8位数据,先发低位;
integer i;//用于控制循环次数;
task tx(
input [DATA_W-1:0] data //串口待发送数据;
);
begin
@(posedge clk);//延迟一个时钟后发送起始位;
#1; uart_tx = 1'b0;
repeat(BPS_CNT) @(posedge clk);//延迟BPS_CNT个时钟;
for(i=0 ; i<8 ; i=i+1)begin
#1; uart_tx = data[i];
repeat(BPS_CNT) @(posedge clk);//延迟BPS_CNT个时钟;
end
if(CHECK_W == 2'b01)begin
#1;uart_tx = ~(^data);//奇校验时,发送数据;
repeat(BPS_CNT) @(posedge clk);//延迟BPS_CNT个时钟;
end
else if(CHECK_W == 2'b10)begin
#1;uart_tx = (^data);//偶校验时,发送数据;
repeat(BPS_CNT) @(posedge clk);//延迟BPS_CNT个时钟;
end
@(posedge clk);//延迟一个时钟后发送停止位;
#1; uart_tx = 1'b1;
if(STOP_W == 2'b01)//1位停止位;
repeat(BPS_CNT) @(posedge clk);//延迟BPS_CNT个时钟;
else if(STOP_W == 2'b10)//2位停止位;
repeat(2*BPS_CNT) @(posedge clk);//延迟2*BPS_CNT个时钟;
else if(STOP_W == 2'b11)//1.5位停止位;
repeat(BPS_CNT*3/2) @(posedge clk);//延迟1.5*BPS_CNT个时钟;
end
endtask
endmodule
该工程所使用的软件版本为Quartus 18.1,获取工程后,可以直接联合仿真,另外我已经将波形设置保存,进入modelsim之后,在wave页面删除自动加入的信号,最终如下图所示:
之后信号就如同之前那样设置了,就可以清除当前仿真,重新进行自己的仿真了