目录
1、理论介绍
2、架构设计
3、代码设计
一、发送模块代码
二、接收代码设计
三、顶层模块设计
四、测试代码
4、仿真实验
uart:通用异步收发传输器(Universal Asynchronous Receiver/Transmitter),是一种串行的收发方式,由于没有时钟,因此需要双方约定好传输的速率,以及起始和停止,为了保证数据的可靠传输,还需要使用校验位。uart协议如图
图1 uart协议
在协议未开始传输时,物理层通路上电平为高,起始位将电平拉低,意为着传输开始,这边使用大端传输方式 ,即先传输高字节数据,8位数据位传输完成后,使用奇偶校验位进行校验(是一种验错方式,但是不能纠错),最后传输1位/1.5位/2位停止位。
以前总是想着通信协议应该是有一个统一结构标准的,这里我先斗胆这样定义,如果有问题随时改正,这里先定义物理层和链路层:
物理层:主要任务为确定与传输媒体的接口有关的一些特性
机械特性:指明接口所用接线器的形状和尺寸、引脚数目和排列、固定和锁定装置等。
电气特性:指明在接口电缆的各条线上出现的电压范围、阻抗匹配、传输速率等。
功能特性:指明物理接口上各条信号线的功能分配和确切定义。
过程特性:指明对于不同功能的各种可能事件的出现顺序(理解为建立物理连接、维持和交换信息)。
数据链路层:将来源于物理层的数据进行可靠的传输。如何可靠的传输,就是协议里面具体规定的内容,以帧为单位进行传输。
这里理解的uart协议,是一个没有定义物理层,只定义了协议层的协议,因此我们可以使单端线,RS232,RS485或者其他接口传输,不同的接口也就造成了传输距离和速率的区别,可以根据自己的需要自由的选择接口。
在编写代码之前是需要先构建模块框图的,先分析一下uart需要哪些模块,这是一个异步全双工的协议,因此需要一个发送模块和一个接收模块,分别在各自模块内部实现波特率的生成,以及数据收发,模块以及其信号如下:
图2 发送模块
图3 接收模块
在完成顶层模块框图划分后,需要对各个模块分析,发送模块内部主要电路原理图如图4所示,接收模块内部主要电路框图如图5所示。根据设计的电路框图开始编写程序。
图4 发送模块内部主要电路图
图5 接收模块内部主要电路图
/***************************************
#
# Filename:tx.v
#
# Developer:annotater
# Description:---
# CreatTime:2021-08-09 23:00:12
#
***************************************/
module tx(
input clk_200m,
input sys_rst,
input[7:0] tx_data,
input oe,
output tx,
output reg tx_done
);
localparam BAUD = 115200;
localparam DIV_NUM = 200000000/BAUD;
localparam IDLE = 3'b001;
localparam PRE = 3'b010;
localparam SEND = 3'b100;
reg[2:0] cstate,nstate;//定义当前状态和次态
reg[11:0] cout;//波特率分频计数
reg[3:0] shift_num;//移位次数
reg[10:0] tx_data_pre;//需要发送的一帧数据
wire baud_pdg;//波特率上升沿
wire baud_ndg;//波特率下降沿
//buad generater
always@(posedge clk_200m or posedge sys_rst)begin
if(sys_rst)begin
cout <= 12'b0;
end
else if(cout == DIV_NUM-1'b1)begin
cout <= 12'b0;
end
else if(cstate == SEND)begin
cout <= cout + 1'b1;
end
end
assign baud_pdg = (cout == DIV_NUM >> 1'b1 )?1'b1:1'b0;
assign baud_ndg = (cout == DIV_NUM - 1'b1 )?1'b1:1'b0;
//shift
always@(posedge clk_200m or posedge sys_rst)begin
if(sys_rst)begin
tx_data_pre[10:0] <= 11'b1111_1111_11;
shift_num <= 4'd0;
end
else if(cstate == PRE)begin
tx_data_pre[10:0] <= {1'b0,tx_data,(^tx_data),1'b1};
end
else if(shift_num == 4'd10 && baud_ndg)begin
tx_data_pre[10:0] <= 11'b1111_1111_11;
shift_num <= 4'd0;
end
else if(baud_ndg)begin
tx_data_pre[10:0] <= {tx_data_pre[9:0],1'b1};
shift_num <= shift_num + 1'b1;
end
end
//state machine
always@(posedge clk_200m or posedge sys_rst)begin
if(sys_rst)begin
cstate <= IDLE;
end
else begin
cstate <= nstate;
end
end
always@(*)begin
case(cstate)//synthesis full_case
IDLE:nstate = (oe)?PRE:IDLE;
PRE :nstate = SEND;
SEND:nstate = (shift_num == 4'd10 && baud_ndg)?IDLE:SEND;
endcase
end
always@(posedge clk_200m or posedge sys_rst)begin
if(sys_rst)begin
tx_done <= 1'b0;
end
else begin
case(cstate)
IDLE:tx_done <= 1'b0;
PRE :;
SEND:begin
if(shift_num == 4'd10 && baud_ndg)
tx_done <= 1'b1;
else
tx_done <= 1'b0;
end
endcase
end
end
assign tx = (cstate == SEND)?tx_data_pre[10]:1'b1;
endmodule
/***************************************
#
# Filename:rx.v
#
# Developer:annotater
# Description:---
# CreatTime:2021-08-10 17:44:59
#
***************************************/
module rx(
input clk_200m,
input sys_rst,
input rx,
output reg[7:0] rx_data,
output reg rx_done
);
localparam BAUD = 115200;
localparam DIV_NUM = 200000000/BAUD;
localparam IDLE = 2'b01;
localparam RECE = 2'b10;
reg[1:0] cstate,nstate;//定义当前状态和次态
reg[11:0] cout;//波特率分频计数
reg[3:0] shift_num;//移位次数
reg[10:0] rx_data_rec;//接收一帧数据
reg[1:0] rx_d;//同步器,用来消除亚稳态和检测下降沿
wire baud_pdg;//波特率上升沿
wire baud_ndg;//波特率下降沿
wire rx_ndg;//起始位下降沿
wire rx_error;//起始位接收错误
//buad generater
always@(posedge clk_200m or posedge sys_rst)begin
if(sys_rst)begin
cout <= 12'b0;
end
else if(cout == DIV_NUM - 1'b1 )begin
cout <= 12'b0;
end
else if(cstate == RECE)begin
cout <= cout + 1'b1;
end
end
assign baud_pdg = (cout == DIV_NUM >> 1'b1 )?1'b1:1'b0;
assign baud_ndg = (cout == DIV_NUM - 1'b1 )?1'b1:1'b0;
//同步器
always@(posedge clk_200m or posedge sys_rst)begin
if(sys_rst)begin
rx_d <= 2'b11;
end
else begin
rx_d <= {rx_d[0],rx};
end
end
assign rx_ndg = rx_d[1] & ~(rx_d[0]);
assign rx_error = (baud_pdg&&(cstate == RECE)&&rx&&(shift_num == 4'b0000));//判断起始位是否检测错误
//shift
always@(posedge clk_200m or posedge sys_rst)begin
if(sys_rst)begin
rx_data_rec[10:0] <= 11'b0;
end
else if(baud_pdg)begin
rx_data_rec[10:0] <= {rx_data_rec[9:0],rx};
end
end
always@(posedge clk_200m or posedge sys_rst)begin
if(sys_rst)
shift_num <= 0;
else if(shift_num == 10 && baud_ndg)
shift_num <= 0;
else if(baud_ndg)
shift_num <= shift_num + 1;
end
always@(posedge clk_200m or posedge sys_rst)begin
if(sys_rst)
rx_data <= 8'd0;
else if(rx_done && ~(^rx_data_rec[9:1]))
rx_data <= rx_data_rec[9:2];
end
//state machine
always@(posedge clk_200m or posedge sys_rst)begin
if(sys_rst)begin
cstate <= IDLE;
end
else begin
cstate <= nstate;
end
end
always@(*)begin
case(cstate)//synthesis full_case
IDLE:nstate = (rx_ndg)?RECE:IDLE;
RECE:nstate = (shift_num == 4'd10 && baud_ndg || rx_error)?IDLE:RECE;
endcase
end
always@(posedge clk_200m or posedge sys_rst)begin
if(sys_rst)begin
rx_done <= 1'b0;
end
else begin
case(cstate)
IDLE:rx_done <= 1'b0;
RECE:begin
if(shift_num == 4'd10 && baud_ndg)
rx_done <= 1'b1;
else
rx_done <= 1'b0;
end
endcase
end
end
endmodule
顶层模块中需注意避免胶连逻辑(glue logic)。
/***************************************
#
# Filename:uart_top.v
#
# Developer:annotater
# Description:---
# CreatTime:2021-08-10 18:15:58
#
***************************************/
module uart_top(
input clk_200m,
input sys_rst,
input rx,
input [7:0] tx_data,
input oe,
output [7:0] rx_data,
output tx,
output rx_done,
output tx_done
);
rx U_RX(
.clk_200m ( clk_200m ), //i
.sys_rst ( sys_rst ), //i
.rx ( rx ), //i
.rx_data ( rx_data ), //o
.rx_done ( rx_done ) //o
);
tx U_TX(
.clk_200m ( clk_200m ), //i
.sys_rst ( sys_rst ), //i
.tx_data ( tx_data ), //i
.oe ( oe ), //i
.tx ( tx ), //o
.tx_done ( tx_done ) //o
);
endmodule
`timescale 1ns / 1ps
//
// Company:
// Engineer:
//
// Create Date: 2021/08/10 18:21:32
// Design Name:
// Module Name: tb_uart_top
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module tb_uart_top();
reg clk_200m ;
reg sys_rst ;
reg rx ;
reg[7:0] tx_data ;
reg oe ;
wire[7:0] rx_data ;
wire tx ;
wire rx_done ;
wire tx_done ;
initial begin
clk_200m = 0;
sys_rst = 1;
rx = 0;
tx_data= 0;
oe = 0;
#100 sys_rst = 0;
forever #2.5 clk_200m = ~clk_200m;
end
initial begin
#200 tx_data = 8'b1010_1010;
#10 oe = 1;
#10 oe = 0;
#200000 tx_data = 8'b0101_0101;
#10 oe = 1;
#10 oe = 0;
end
uart_top U_UART_TOP(
.clk_200m ( clk_200m ), //i
.sys_rst ( sys_rst ), //i
.rx ( 0 ), //i
.tx_data ( tx_data ), //i
.oe ( oe ), //i
.rx_data ( ), //o
.tx ( tx ), //o
.rx_done ( ), //o
.tx_done ( tx_done ) //o
);
uart_top U_UART_TOP2(
.clk_200m ( clk_200m ), //i
.sys_rst ( sys_rst ), //i
.rx ( tx ), //i
.tx_data ( 0 ), //i
.oe ( 0 ), //i
.rx_data ( rx_data ), //o
.tx ( ), //o
.rx_done ( rx_done ), //o
.tx_done ( ) //o
);
endmodule
设计代码首先要进行功能仿真验证其功能,功能验证后进行综合,综合是一个将RTL代码转换为门级网表的过程,不同的元件库会综合出不同的门级网表,因此有时候会出现一种库综合通过,另一种库综合不通过的现象,这里直接观察综合后仿真。
仿真文件里面写的仿真过程为在两个时刻分别由一个串口发送AA和55数据,并由另一个串口接收,可以看到,仿真结果没有问题,其中一个bit占用时间8.7us左右。
图6 综合后仿真