一、简介
在上一篇文章当中我们实现了UART接收模块的相关设计和功能实现,在今天的文章中我们继续实现剩下的发送模块的相关设计和完成完整的串口数据回环的实验。
在文章的最后我会给出完整的工程,给小伙伴们参考。
二、接收模块的基本设计
在接收模块设计的时候我们就采用了状态机的实现方式,所以在本次的发送模块的设计中我们也采用状态机进行实现。本将状态机划分为四个,第一个就是空闲状态,表示发送模块没有接收到来自接收模块的信号和数据,第二个是开始状态,和接受模块一样也是表示设备检测到起始位,第三个发送数据过程状态,用于表示设备发送数据的过程,最后一个就是停止位,表示设备发送数据完成。
三、接收模块的波形图绘制
根据上面的状态机,我们可以据此展开波形图的绘制,分别就是对于来自接收模块转换的数据以及发送信号标志位,状态机转移、相关计数器、busy等信号的表示。
当发送模块接收到来自接收模块的标志信号时,状态机就从IDLE进入到START,然后利用波特率计数器计数1bit的起始位,来到DATA,利用波特率计数器和bit计数器用于接收数据,接收完数据之后进入STOP,最后利用波特率计数器计数1bit的停止位,状态又回到初始的IDLE状态。
四、发送模块代码编写
1、设计代码的编写
在上一篇文章中的基础之上新建一个uart_tx.v文件,如下:在代码中可能会看到中间部分会和屏蔽了一样,这里应该是verilog使用其他语言格式造成的,这里不影响,直接使用代码就行,哪些代码时没有屏蔽的。
//发送模块
module uart_tx (
input clk ,
input rst_n ,
input [7:0] tx_din ,//进入发送模块准备发送的数据
input tx_din_vld ,//要发送数据的有效信号
output reg tx_dout ,//串行发送出去的数据
output busy //发送一字节完成信号
);
//参数定义
localparam IDLE = 4'b0001,//空闲状态
START = 4'b0010,//发送起始位
DATA = 4'b0100,//发送数据
STOP = 4'b1000;//发送停止位
parameter CLOCK_FRQ = 50_000_000,//50M时钟
BAUD_9600 = 9600 ,//波特率设置
BAUD_14400 = 14400 ,
BAUD_19200 = 19200 ,
BAUD_38400 = 38400 ,
BAUD_115200 = 115200 ;
//中间信号定义
reg [12:0] cnt_bps ;//波特率计数器,最大值是50M/9600 13位宽
wire add_cnt_bps ;
wire end_cnt_bps ;
reg [3:0] cnt_bit ;//bit计数器,只记数据位 8位
wire add_cnt_bit ;
wire end_cnt_bit ;
reg [7:0] tx_din_r ;//寄存一次数据,否则第一个数据会被丢弃
reg [3:0] state_c ;//现态
reg [3:0] state_n ;//次态
wire idle2start ;//状态机转移条件
wire start2data ;
wire data2stop ;
wire stop2idle ;
//状态机
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
state_c <= IDLE;
else
state_c <= state_n;
end
always @(*)begin
case (state_c)
IDLE : begin
if(idle2start)begin
state_n = START;
end
else begin
state_n = state_c;
end
end
START : begin
if(start2data)begin
state_n = DATA;
end
else begin
state_n = state_c;
end
end
DATA : begin
if(data2stop)begin
state_n = STOP;
end
else begin
state_n = state_c;
end
end
STOP : begin
if(stop2idle)begin
state_n = IDLE;
end
else begin
state_n = state_c;
end
end
default: state_n = IDLE;
endcase
end
assign idle2start = state_c == IDLE && (tx_din_vld );//数据来临有效即可以发
assign start2data = state_c == START && (end_cnt_bps);//发完1bit
assign data2stop = state_c == DATA && (end_cnt_bit );//发完8bit
assign stop2idle = state_c == STOP && (end_cnt_bps);//发完1bit
//tx_din_r
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
tx_din_r <= 'd0;
end
else if(tx_din_vld)begin
tx_din_r <= tx_din;
end
end
//cnt_bps
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_bps <= 0;
end
else if(add_cnt_bps)begin
if(end_cnt_bps)begin
cnt_bps <= 0;
end
else begin
cnt_bps <= cnt_bps + 1;
end
end
else begin
cnt_bps <= cnt_bps;
end
end
assign add_cnt_bps = (state_c != IDLE);
assign end_cnt_bps = add_cnt_bps && (cnt_bps == (CLOCK_FRQ/BAUD_115200 - 1));
//cnt_bit
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_bit <= 0;
end
else if(add_cnt_bit)begin
if(end_cnt_bit)begin
cnt_bit <= 0;
end
else begin
cnt_bit <= cnt_bit + 1;
end
end
else begin
cnt_bit <= cnt_bit;
end
end
assign add_cnt_bit = (state_c == DATA && end_cnt_bps);//每发完1bit才会加1
assign end_cnt_bit = add_cnt_bit && (cnt_bit == 8 - 1);
//tx_dout
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
tx_dout <= 1'b1;
end
else begin
case (state_c)
IDLE : tx_dout <= 1'b1;
START: tx_dout <= 1'b0;
DATA : tx_dout <= tx_din_r[cnt_bit];//LSB发送数据
STOP : tx_dout <= 1'b1;
default: tx_dout <= 1'b1;
endcase
end
end
//busy
assign busy = state_c !=IDLE;
endmodule
2、测试文件的编写
新建一个uart_tx_tb.v文件,如下:先对编写的发送模块进行一个设计再进行整个回环实验进行一个实现。这里直接对输入数据进行一个8位赋值,观察发送模块发送出的数据。
`timescale 1ns/1ns
module uart_tx_tb();
//激励信号定义
reg clk ;
reg rst_n ;
reg tx_din_vld ;
wire tx_dout ;
wire tx_done ;
//时钟周期参数定义
parameter CYCLE = 20;
uart_tx uart_tx_inst(
/*input */ .clk (clk ) ,
/*input */ .rst_n (rst_n ) ,
/*input [7:0] */ .tx_din (8'b10100101 ) ,//进入发送模块准备发送的数据
/*input */ .tx_din_vld(tx_din_vld && ~busy) ,//要发送数据的有效信号
/*output reg */ .tx_dout (tx_dout ) ,//串行发送出去的数据
/*output reg */ .busy (busy ) //发送一字节完成信号
);
//产生时钟
initial clk = 1'b0;
always #(CYCLE/2) clk = ~clk;
//产生激励
initial begin
rst_n = 1'b0;
#(CYCLE*10+3);
rst_n = 1'b1;
repeat (10)begin
//起始位
tx_din_vld = 1;
#20;
#(434*CYCLE);
#(434*CYCLE*8);
//停止位
#(434*CYCLE);
#(CYCLE*100);
$stop;
end
end
endmodule
3、波形仿真
通过波形图我们可以看到发送模块的发送端发出的数据和我们设定的值一样,发送模块设计没有问题。
五、UART回环实验的实现
1、编写顶层模块
编写一个顶层模块,将接收模块和发送模块两者结合起来,从而实现数据回环,新建一个top.v文件,如下:
module top (
input clk ,
input rst_n ,
input rx ,
output tx
);
wire [7:0] rx_data;
wire rx_dout_vld;
wire busy;
//不加控制模块
uart_rx uart_rx_inst(
/*input */ .clk (clk ),
/*input */ .rst_n (rst_n ),
/*input */ .din_rx (rx ),
/*output [7:0]*/ .dout_data (rx_data ),
/*output */ .dout_flag (rx_dout_vld )
);
uart_tx uart_tx_inst(
/*input */ .clk (clk ) ,
/*input */ .rst_n (rst_n ) ,
/*input [7:0]*/ .tx_din (rx_data ) ,//进入发送模块准备发送的数据
/*input */ .tx_din_vld (rx_dout_vld && ~busy ) ,//要发送数据的有效信号
/*output reg */ .tx_dout (tx ) ,//串行发送出去的数据
/*output reg */ .busy (busy ) //发送一字节完成信号
);
endmodule
2、顶层测试文件的编写
`timescale 1ns/1ns
module top_tb();
//激励信号定义
reg tb_clk ;
reg tb_rst_n ;
reg rx ;
//输出信号定义
wire tx ;
//时钟周期参数定义
parameter CLOCK_CYCLE = 20;
//模块例化
top tb_top(
/*input */.clk (tb_clk ),
/*input */.rst_n (tb_rst_n ),
/*input */.rx (rx ),
/*output */.tx (tx )
);
//产生时钟
initial tb_clk = 1'b0;
always #(CLOCK_CYCLE/2) tb_clk = ~tb_clk;
//产生激励
integer i;
initial begin
tb_rst_n = 1'b1;
rx = 1;//空闲为高电平
#(CLOCK_CYCLE*2);
tb_rst_n = 1'b0;
#(CLOCK_CYCLE*20);
tb_rst_n = 1'b1;
#1002;
//模拟UART接收模块的串行输入
repeat(20)begin
//起始位
rx = 0;
#(434*CLOCK_CYCLE);
//数据位:
for (i=0;i<8;i=i+1) begin
rx = $random;
#(434*CLOCK_CYCLE);
end
//停止位
rx = 1;
#(434*CLOCK_CYCLE);
//两次发送的间隔时间
#(CLOCK_CYCLE*1000);
end
// #(CLOCK_CYCLE*434*11);
// $stop;
end
endmodule
3、波形仿真
从最后的仿真图中我们可以看到接收端和发送端最后的数据都是一样,说明我们的串口回环实验设计成功。
4、下板验证
经过我们最后的下板验证之后,发现数据能够正确的进行回环,如图所示:
到这里UART回环就设计完成了。
六、完整工程
https://pan.baidu.com/s/12IrXc6A00a_zLP3SHXlriA?pwd=7f9i
提取码:7f9i