UART——串口接收模块
RTL代码
`timescale 1ns/1ps
module uart_byte_rx(
clk,
reset_n,
uart_rx,
baud_set,
Data,
Rx_Done
);
input clk;
input reset_n;
input uart_rx;
input [3-1:0] baud_set;
output reg [8-1:0] Data;
output reg Rx_Done;
// reg width name number
reg [3-1:0] r_data [8-1:0];
reg [3-1:0] sta_bit;
reg [3-1:0] sto_bit;
//边沿检测(上升沿和下降沿)
//通过两位宽的寄存器来实现
//电路实现上面就是两级D触发器
reg [1:0] uart_rx_r;
always@(posedge clk) begin
uart_rx_r[0] <= uart_rx;
uart_rx_r[1] <= uart_rx_r[0];
end
//上升沿,两种写法
wire pedge_uart_rx;
// assign pedge_uart_rx = ((uart_rx_r[1]==0) && (uart_rx_r[0]==1));
assign pedge_uart_rx = (uart_rx_r==2'b01);
//下降沿,两种写法
wire nedge_uart_rx;
// assign nedge_uart_rx = ((uart_rx_r[1]==1) && (uart_rx_r[0]==0));
assign nedge_uart_rx = (uart_rx_r==2'b10);
//波特率设置
//波特率单位,每秒传输的bit数量,()bit/s
//baud_DR计算为每一个bit所需要的时钟周期,这里的单位是ns
//这里时钟周期以50MHz为例,也就是一个周期有20ns,因此是/20
//同时在接收时,将1个bit的数据划分成为16位
reg [8-1:0] Bps_DR;
always@(*)
case(baud_set)
0: Bps_DR<=1_000_000_000/9600/20/16-1;
1: Bps_DR<=1_000_000_000/19200/20/16-1;
2: Bps_DR<=1_000_000_000/38400/20/16-1;
3: Bps_DR<=1_000_000_000/57600/20/16-1;
4: Bps_DR<=1_000_000_000/115200/20/16-1;
default: Bps_DR<=1_000_000_000/9600/20/16-1;
endcase
//接收使能
reg Rx_EN;
always@(posedge clk or negedge reset_n)begin
if(reset_n==0)
Rx_EN<=0;
//当接收当下降沿时,开始接收
else if(nedge_uart_rx==1)
Rx_EN<=1;
//当接收成功时,将使能置0
//为避免起始位误判,起始位若为高电平(也就是sta_bit>=4),也将使能置0
else if((Rx_Done==1)||(sta_bit>=4))
Rx_EN<=0;
end
//分频计数
reg [8-1:0] div_cnt;
always@(posedge clk or negedge reset_n)begin
if(reset_n==0)
div_cnt<=0;
//在使能时,才开始计数
else if(Rx_EN)begin
//计数1bit所需周期数
if(div_cnt==Bps_DR)
div_cnt<=0;
else
div_cnt<=div_cnt+1;
end
//没有使能时,一直置0
else
div_cnt<=0;
end
//数据传输标识符,只有在1bit数据计数到中间时
//也就是div_cnt==Bps_DR/2时,标识符置1
//相较于bps_clk,进一步16倍划分了
wire bps_clk_16x;
assign bps_clk_16x = (div_cnt==Bps_DR/2);
//数据传输计数
//bps_cnt标记着数据传输的数量
//每次bps_clk出现时,bps_cnt才会加1或者清零
reg [8-1:0] bps_cnt;
always@(posedge clk or negedge reset_n)begin
if(reset_n==0)
bps_cnt<=0;
else if(Rx_EN)begin
if(bps_clk_16x)begin
//16倍细分后,总共就需要计数16*(10位bit)=160
if(bps_cnt==160)
bps_cnt<=0;
else
bps_cnt<=bps_cnt+1;
end
else
bps_cnt<=bps_cnt;
end
else
bps_cnt<=0;
end
//核心:数据传输
//对于1bit数据,将其细分为16份,检测中间部分
//通过对中间部分的电平计数,判断该位的电平高低
always@(posedge clk or negedge reset_n)begin
if(reset_n==0)begin
sta_bit<=0;
sto_bit<=0;
r_data[0]<=0;
r_data[1]<=0;
r_data[2]<=0;
r_data[3]<=0;
r_data[4]<=0;
r_data[5]<=0;
r_data[6]<=0;
r_data[7]<=0;
end
else if(bps_clk_16x) begin
//对bps_cnt计数,当技术到每16个区间中间部分时,进行数据判断
//每次都自加上uart_rx数据线上传来的内容
case(bps_cnt)
0:begin
sta_bit<=0;
sto_bit<=0;
r_data[0]<=0;
r_data[1]<=0;
r_data[2]<=0;
r_data[3]<=0;
r_data[4]<=0;
r_data[5]<=0;
r_data[6]<=0;
r_data[7]<=0;
end
5,6,7,8,9,10,11: sta_bit<=sta_bit+uart_rx;
21,22,23,24,25,26,27: r_data[0]<=r_data[0]+uart_rx;
37,38,39,40,41,42,43: r_data[1]<=r_data[1]+uart_rx;
54,55,56,57,58,59,60: r_data[2]<=r_data[2]+uart_rx;
69,70,71,72,73,74,75: r_data[3]<=r_data[3]+uart_rx;
85,86,87,88,89,90,91: r_data[4]<=r_data[4]+uart_rx;
101,102,103,104,105,106,107: r_data[5]<=r_data[5]+uart_rx;
117,118,119,120,121,122,123: r_data[6]<=r_data[6]+uart_rx;
133,134,135,136,137,138,139: r_data[7]<=r_data[7]+uart_rx;
149,150,151,152,153,154,155: sto_bit<=sto_bit+uart_rx;
default:;
endcase
end
end
//在统计完每一位的数据后,进行电平判断
//对bps_cnt计数到每一位的中间部分(7位)累加
//最终高电平有0,1,2,3次出现,判断为低电平
//有4,5,6次出现,则判断为高电平
//判决门限??
always@(posedge clk or negedge reset_n)begin
if(reset_n==0)
Data<=0;
else if(bps_clk_16x && (bps_cnt==160))begin
Data[0]<=(r_data[0]>=4)? 1:0;
Data[1]<=(r_data[1]>=4)? 1:0;
Data[2]<=(r_data[2]>=4)? 1:0;
Data[3]<=(r_data[3]>=4)? 1:0;
Data[4]<=(r_data[4]>=4)? 1:0;
Data[5]<=(r_data[5]>=4)? 1:0;
Data[6]<=(r_data[6]>=4)? 1:0;
Data[7]<=(r_data[7]>=4)? 1:0;
end
end
always@(posedge clk or negedge reset_n)begin
if(reset_n==0)
Rx_Done<=0;
//当bps_cnt最终计数到第16个bit的最后一份(1/16)时(160)
//再计满半个bit位所花费的周期(Bps_DR/2),则返回接收成功标识符
else if((div_cnt==Bps_DR/2) && (bps_cnt==160))
Rx_Done<=1;
else
Rx_Done<=0;
end
endmodule
代码解析
- 串口接收模块相较于发送模块有比较大的区别,最主要的一点在于电平判决上
- 由于发送时,数据是已知的,只需要一位一位的发送出去,保证数据发送成功即可
- 但是在接收时,需要将一位数据进行分解,选取中间部分的进行电平判决,从而避免错误接收
- 需要特别注意,边沿检测和数据传输的写法,并行传输是fpga的优势,可以有效提高传输速率,要重点掌握
- 对于bps_clk,还有rx_done,这类单脉冲信号,重要且有效,不仅对于数据传输的停止有用,在设计更加高层模块也有帮助
测试平台
`timescale 1ns/1ps
module uart_byte_rx_tb();
reg clk;
reg reset_n;
reg uart_rx;
wire [3-1:0] baud_set;
wire [8-1:0] Data;
wire Rx_Done;
assign baud_set=4;
//注意要赋予实例名称
uart_byte_rx uart_byte_rx(
clk,
reset_n,
uart_rx,
baud_set,
Data,
Rx_Done
);
initial begin
clk=1;
forever #10 clk=~clk;
end
initial begin
reset_n=0;
uart_rx = 1;
#201;
reset_n=1;
#200;
uart_tx_byte(8'h5a);
//新语法,当Rx_Done为上升沿时,才会继续执行
@(posedge Rx_Done);
#5000;
uart_tx_byte(8'ha5);
@(posedge Rx_Done);
#5000;
uart_tx_byte(8'h86);
@(posedge Rx_Done);
#5000;
$stop;
end
//新语法,task
//相较于rtl代码,在tb文件中语法会更加丰富自由
可以通过直接在initial块中执行uart_tx_byte(),从而调用整个task块内容
task uart_tx_byte;
input [8-1:0] tx_data;
begin
uart_rx = 1;
#20;
uart_rx = 0;
//刚好是1s/115200
//也就是1bit所需要的周期时间
#8680;
uart_rx = tx_data[0];
#8680;
uart_rx = tx_data[1];
#8680;
uart_rx = tx_data[2];
#8680;
uart_rx = tx_data[3];
#8680;
uart_rx = tx_data[4];
#8680;
uart_rx = tx_data[5];
#8680;
uart_rx = tx_data[6];
#8680;
uart_rx = tx_data[7];
#8680;
uart_rx = 1;
#8680;
end
endtask
endmodule
代码解析
- testbench在写的时候需要注意initial块中的激励
- 有些时候仿真波形的错误,可能源于设置的数据不正确,延迟的时间太短,还有关键信号没有置0,1等