概述
1、题目
设计一个串口模块,以一个字节为单位发送数据
2、代码实现
`timescale 1ns / 1ns
module uart_tx(
clk,
reset_n,
data,
send_en,
baud_set,
uart_tx,
tx_done
);
input clk;
input reset_n;
input [7:0]data;
input send_en;
input [2:0]baud_set;
output reg uart_tx;//串口发送信号输出
output reg tx_done; //串口模块8bit发送完毕 再发送一个发送完成信号通知对方
// baud_set=0 就让波特率=9600
//baud_set=1 就让波特率=19200
//baud_set=2 就让波特率=38400
//baud_set=3 就让波特率=115200 5 6 7 位 先留着
//波特率设置
reg [17:0]bps_DR;//一个时钟周期在某波特率下计时器应该计的数
//9600对应5208 最少需要13位 这里用了18位是因为要和div_cnt作比较
always@(*)begin
if(!reset_n)
bps_DR <= 1000000000/9600/20;
else begin
case(baud_set)//fpga保留整数 无需考虑除不尽的情况
0:bps_DR = 1000000000/9600/20;
1:bps_DR = 1000000000/19200/20;
2:bps_DR = 1000000000/38400/20;
3:bps_DR = 1000000000/57600/20;
4:bps_DR = 1000000000/115200/20;
default: bps_DR = 1000000000/9600/20; //baud_set其他的情况还是为9600的波特率
endcase
end
end
//每一个时钟周期计数 send_en 为控制信号 为1则开始计数
reg [17:0]div_cnt;//每一个时钟周期计数器
always@(posedge clk or negedge reset_n)begin
if (!reset_n)
div_cnt <= 0;
else if(send_en)begin
if(div_cnt == bps_DR-1)
div_cnt <= 0;
else
div_cnt <= div_cnt + 1'b1;
end
else
div_cnt <= 0;
end
//数据输出模块设计
reg [3:0]bps_cnt;// 时钟信号个数计数器
always@(posedge clk or negedge reset_n)begin
if(!reset_n)
bps_cnt <= 0;
else if (div_cnt == bps_DR-1)begin//当到达一个时钟信号 bps_cnt+1
if(bps_cnt == 11)
bps_cnt <= 0;
else
bps_cnt <= bps_cnt + 1'b1;
end
else
bps_cnt <= 0;
end
//数据传输模块设计
always@(posedge clk or negedge reset_n)
if(!reset_n)begin
uart_tx <= 1'b1;//空闲时刻为高电平
tx_done <= 0;
end
else begin
case(bps_cnt)
0:begin uart_tx <= 0;tx_done <= 1'b0;end//发送开始信号并让tx_done为0
1:uart_tx <= data[0];
2:uart_tx <= data[1];
3:uart_tx <= data[2];
4:uart_tx <= data[3];
5:uart_tx <= data[4];
6:uart_tx <= data[5];
7:uart_tx <= data[6];
8:uart_tx <= data[7];
9:uart_tx <= 1;//stop信号
10: begin uart_tx <= 1; tx_done <= 1'b1;end//发送结束信号并让tx_done为1
default: uart_tx <= 1;
endcase
end
endmodule
testbench如下
`timescale 1ns / 1ns
module uart_tx_tb( );
reg clk;
reg reset_n;
reg [7:0]data;
reg send_en;
reg [2:0]baud_set;
wire uart_tx;
wire tx_done;
//例化
uart_tx uart_tx_inst0(
.clk(clk),
.reset_n(reset_n),
.data(data),
.send_en(send_en),
.baud_set(baud_set),
.uart_tx(uart_tx),
.tx_done(tx_done)
);
//产生周期为20ns的时钟
initial clk=1;
always #10 clk =~clk;
//对输入进行赋值
initial begin
reset_n = 0;
data = 0;//刚开始要发的数据也让它等于0
send_en = 0;
baud_set = 4; //初始化波特率为115200 速度快11个时钟脉冲下来时间短
#201;
reset_n = 1;
#100
send_en = 1;
data = 8'h57; //0101 0111
#20;
@(posedge tx_done);//死循环 一直等待tx_done上升沿的到来 来了就跳出循环 没有来就持续等待
send_en = 0;
#2000;
$stop;
end
endmodule
仿真结果如下:
send_en还是低电平的时候,串口发送的信号已经变成了低电平,不合常理
发送的第二个信号在send_en为1之后要先以1为发送对象,等待div_cnt到一个新的时钟脉冲,bps_cnt变成11,在bps_cnt为11时,发送对象还是1,所以继续以1为发送对象,等到div_cnt到一个新的时钟脉冲,bps_cnt从1变成0,才开始发送低电平起始位。所以一共用了两个周期才走到低电平的位置。
所以提出解决方法:让bps_cnt也受send_en的控制
reg [3:0]bps_cnt;// 时钟信号个数计数器
always@(posedge clk or negedge reset_n)begin
if(!reset_n)
bps_cnt <= 0;
else if (send_en)
begin
if(div_cnt == bps_DR-1)
begin //当到达一个时钟信号 bps_cnt+1
if(bps_cnt == 11)
bps_cnt <= 0;
else
bps_cnt <= bps_cnt + 1'b1;
end
end
else
bps_cnt <= 0;
end
在stop信号传输中,div_cnt计数到了新的时钟脉冲,div_cnt = 0,bps_cnt=10,于是开始传输高电平同时让tx_done变成高电平。刚一变成高电平,测试平台感应到就立马把send_en赋值为0,send_en=0了,div_cnt还是为0(因为这些操作都是一瞬间的事情,姑且认为div_cnt还没来得及开始计数)且bps_cnt由10变为0,所以在下面的仿真图中可以看见,uart_tx和tx_done立马就变为低电平了,随后就以低电平为传输对象一直传,直到20ns后send_en再次被赋值为1,div_cnt立马重新开始计时,到一个新的时钟脉冲时(div_cnt从0开始到这个新的时钟脉冲这段时间依旧是以0为传输对象,所以这段时间就相当于开始信号),bps_cnt由零变为1,随后就开始传输data[0]
还是有问题:比如20ns这段空闲状态tx是以低电平为传输对象的,不符合常理
原因是bps_cnt被置零,直接就对应发送start信号,所以把代码做如下修改:(记得把11个脉冲那里改为12个)
always@(posedge clk or negedge reset_n)
if(!reset_n)begin
uart_tx <= 1'b1;//空闲时刻为高电平
tx_done <= 0;
end
else begin
case(bps_cnt)
1:begin uart_tx <= 0;tx_done <= 1'b0;end//发送开始信号并让tx_done为0
2:uart_tx <= data[0];
3:uart_tx <= data[1];
4:uart_tx <= data[2];
5:uart_tx <= data[3];
6:uart_tx <= data[4];
7:uart_tx <= data[5];
8:uart_tx <= data[6];
9:uart_tx <= data[7];
10:uart_tx <= 1;//stop信号
11: begin uart_tx <= 1; tx_done <= 1'b1;end//发送结束信号并让tx_done为1
default: uart_tx <= 1;//其他就为1 高电平
endcase
end
但是又出现了之前的问题:要多一个周期后才会开始传输start信号
观察代码可知,计满才加一次(才让bps_cnt变为1)才开始发送start信号,浪费了一位的时间
div_cnt计数到bps_dr需要很长的时间,但是计数到1的时间是可以忽略的(这里的意思是说:原来代码里的div_cnt需要计数一个完整的周期才能将bps_cnt加一,而每一段计数过程都会经过1这个数,所以直接让它跑到1的时候就让bps——cnt加1,节约了很多时间。可能你会想,那这样的话每一个位的时间不就改变了吗?其实并没有,因为div_cnt是每次到bps_dr-1才归零的这个条件没有变。只是之前把div_cnt到了bps_dr-1时当作bps_cnt加1的条件,现在把div_cnt到1时当作bps_cnt加1的条件,同样是一段div_cnt的计数周期仅对应一个数值1.代码改变如下:
仿真结果如下
接下来是波特率验证,从上图仿真结果来看,发一位需要8.68us 符合115200的波特率
另外:将程序修改为bps_clk来控制的思路
仿真结果里有一个bps_clk 11个脉冲把tx分为10位
结束!!
后记:没有天生的适合和不适合 事在人为 只要你想
最后
以上就是故意月亮为你收集整理的09B 串口通信发送的verilog设计与调试的全部内容,希望文章能够帮你解决09B 串口通信发送的verilog设计与调试所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复