一、要点
1.结构:TTL电平的串口由两条信号线连接,一条为接收端RX,另一条为发送端TX。UART是一种无时钟线的串行接口,接线简单。
2.时序:即信息流动的时间顺序。下图是串口的时序图。
串口数据按帧发送,帧内的数据位、检验位、停止位是可配置的,一般情况下,数据位为8位,无检验位,停止位为1位,一帧数据则包含1+8+0+1=10位(bit)。
空闲状态下,总线为高电平,起始位固定为低电平,当在空闲状态下发送低电平时代表一帧数据开始发送,停止位固定为高电平,代表一帧数据结束。
3.波特率:数据传输的速率,即每秒中能传输的bit的个数。例如波特率115200即每秒钟最多能传输115200个bit的数据(包含起始位、停止位、校验位),则每bit数据需要维持的时间为1s/115200≈8.68055us。
二、实现
1.架构:发送模块主要包含3个部分,1、用计数器实现波特率定时,2、模块状态的转移,3、发送数据的填充
2.Verilog语言描述
新建一个uart_tx.v文件
module uart_tx
(
input clk,
input nrst,
input tx_en,
input [7:0]tx_data,
output tx_pin,
output tx_done
);
以上描述模块的端口,分别为时钟、复位、使能、发送数据、发送引脚、发送完成标志。
parameter INPUT_CLK = 125000000 , BUDO = 115200;
localparam B_CNT_MAX = (INPUT_CLK/BUDO)-1;//波特率计数器最大值
reg [3:0]CS; //状态
reg [10:0]b_cnt; //用来产生波特率时钟
reg [10:0]tx_buf;//包含起始位与停止位的数据缓冲区
reg r_tx_pin;
reg r_txdone;
以上定义模块的参数及本地参数、使用到的寄存器。模块默认输入时钟为125MHz,波特率为115200。
//CS依据波特率计数器自增,实现状态转移
task CS_increase;
if(CS == 'd10) begin //一帧完成,复位
CS <= 'd0;
b_cnt <= 'd0;
end
else if (b_cnt == B_CNT_MAX) begin//计数满1bti的发送时间,转移到下一个bit,计数器清零
CS <= CS + 1'b1;
b_cnt <= 'd0;
end
else //计数中
b_cnt <= b_cnt + 1'b1;
endtask
以上定义一个任务,在其他代码中被调用,实现状态寄存器CS根据波特率计数器自增,实现状态转移。此段结合了波特率定时和状态转移两部分。
always @(posedge clk) begin
if (!nrst) begin//复位
CS <= 'd0;
b_cnt <= 'd0;
tx_buf <= 'd0;
r_tx_pin <= 1'b1;
r_txdone <= 1'b1;
end
else if (tx_en) begin
//状态转移
CS_increase;
//各状态输出
if (CS == 'd0) begin
r_txdone <= 1'b0;//开始发送,发送完成标志置0
tx_buf <= {2'b11,tx_data,1'b0};
end
else if (CS == 'd10) begin
r_txdone <= 1'b1;//发送完成标志置1
tx_buf <= tx_buf;
end
else begin //正在发送数据位、停止位
r_txdone <= 1'b0;
tx_buf <= tx_buf;
end
r_tx_pin <= tx_buf[CS];//输出引脚数据更新
end
else
r_tx_pin <= 1'b1;
end
以上部分是发送模块的主要结构,在时钟上升沿到来后检测有无复位(nrst=0时进行复位),无复位任务且发送模块被使能时则完成两个任务:1、调用状态转移任务CS_increase实现状态转移,2、根据状态寄存器CS对发送寄存器r_tx_pin、发送完成寄存器r_txdone进行赋值。
状态寄存器CS的每个值对应TX线上的每个发送状态,CS=0时表示开始发送起始位,此时将需要发送的数据填充进tx_buf,1'b0是起始位,tx_data是数据,2'b11是一位停止位加一个结束状态时发送的数据(或者说是停止位加空闲状态)。
CS=10表示停止位发送完后的下一个状态,此时tx线进入空闲状态,与停止位一样发送1。此时将发送完成标志r_txdone置1。(在后续的顶层文件中,当检测到发送完成标志置1时会失能tx_en,使得状态寄存器CS无法进行转移,实现的效果是r_tx_pin <= 1'b1,即tx线固定输出1)。
//驱动输出声明
assign tx_pin = r_tx_pin;
assign tx_done = r_txdone;
endmodule // uart_tx
最后将输出寄存器驱动到输出的线网,至此发送模块定义完成。
三、编写测试文件,VCS仿真测试
1.编写测试文件:包括产生时钟、管理发送使能信号
新建一个uart_tb.v文件
`timescale 1ns/1ps
module uart_tb();
reg nrst;
reg clk;
reg en_tx;
reg [7:0] tx_data;
wire tx_pin,tx_done;
reg [7:0]tx_cnt;
//实例化
uart_tx uart_tx(
.clk(clk),
.nrst(nrst),
.tx_en(en_tx),
.tx_data(tx_data),
.tx_pin(tx_pin),
.txdone(tx_done)
);
定义一个uart_tb模块用于测试,定义相关信号,实例化uart_tx模块
//产生125Mhz的时钟
initial begin
clk = 0;
forever begin
#4;
clk = ~clk;
end
end
initial begin
en_tx = 0;
tx_data = 8'b0;
tx_cnt = 'd0;
nrst = 1;
#100;
nrst = 0;//复位信号
#103;
nrst = 1;
tx_data = 'd64;
end
产生125MHz的时钟,初始化各信号的值
//产生循环发送的信号
always @(negedge clk) begin
if (nrst) begin
if (tx_done) begin
if (en_tx)//刚刚发送完成
en_tx <= 1'b0;
else begin//发送完成一段时间
if (tx_cnt == 'd250) begin
tx_cnt <= 'd0;
en_tx <= 1'b1;//重启发送
end
else
tx_cnt <= tx_cnt + 1'b1;
end
end
else begin
en_tx <= en_tx;
tx_cnt <= tx_cnt;
end
end
end
endmodule // usart_tb
在tx_done置高的时候说明发送完成,将en_tx失能;然后使用tx_cnt进行计数,一段时间后重新使能en_tx,开始新一帧的发送
2.VCS仿真
将uart_tx.v和uart_tb.v文件放在同一文件夹中,在该目录路径中打开终端,使用vcs -full64 *.v -debug_all命令进行编译
再使用./simv -gui命令打开gui图形界面,将相关信号添加到示波器中进行仿真
仿真波形如图,在红线处tx_done信号上升、en_tx信号下降,经过计数器一段时间的计数后en_tx再次置高