一、UART通讯协议原理与时序
串口(UART)协议的发送和接收时序、物理层接口下面两篇博客讲的很详细。
y串口(UART)的FPGA实现(含源码工程)_孤独的单刀的博客-CSDN博客_fpga uarthttps://blog.csdn.net/wuzhikaidetb/article/details/114596930UART的fpga实现_alone_l的博客-CSDN博客_uart fpgahttps://blog.csdn.net/alone_l/article/details/124946773 这里我主要参考的是博主孤独的单刀的代码,虽然博主写的系列文章都很不错而且基础理论的讲解也很详细到位,但是博主并没有使用状态机的方式实现,并且使用case来产生uart_txd输出和串转并得到输入的语句会综合出一个很大的多路选择器,而同样的功能可是使用移位寄存器来实现,这就会造成资源不必要的浪费。RTL代码与其综合出的电路的对应如下所示:
1、UART发送模块中
2、UART接收模块中
在本篇博客中笔者使用了状态机+移位寄存器的方式来实现,很大程度上减少了资源的损耗,移位寄存器部分代码和综合结果如下。
1、UART发送模块
2、UART接收模块
可以看到,使用移位寄存器的方式来实现UART驱动更加节省资源。下面笔者将分别介绍UART接收端和发送端的RTL实现。
二、UART接口驱动的RTL实现与仿真结果
1、时钟分频产生波特率时钟
RTL代码:
为了满足波特率的要求,需要将总线电平维持(时钟频率/波特率)个周期才能被接收端正确接收。为什么是(时钟频率/波特率)个系统时钟周期呢?得从波特率定义来看,波特率是指每秒钟发送/接收的bit数,单位为bit数/秒=bps;时钟频率是每秒的时钟周期数,单位为周期数/秒=Hz,因此(时钟频率/波特率)的单位为周期数/bit数也就是一个发送/接收1bit数据所需要的周期数!
因此我们可以通过产生一个波特率时钟来控制状态机的状态转移,为了实现参数化的设计,使用parameter可以在例化的时候调整调整波特率和时钟频率:
并定义传输一位所需要的周期数
为了使占空比接近1/2,这里定义了计数分频跳变沿时计数器的计数数为
剩下的就是简单的计数分频逻辑了,CLK_div模块整体代码如下:
module CLK_div #(
parameter BAUD = 115200,
parameter CLK_frq = 100000000
)(
input sys_clk,//系统时钟
input rst_n,//系统复位
output reg baud_clk//波特率时钟
);
parameter DIV_MAX = CLK_frq/BAUD;//传输一位所需周期数
parameter EDGE_NUM =DIV_MAX/2;//跳变周期
reg [8:0]clk_cnt;//分频计数器
//计数器控制逻辑
always @(posedge sys_clk or negedge rst_n) begin
if(!rst_n)begin
clk_cnt <= 10'd0;
baud_clk <= 1'b0;
end
else if(clk_cnt == EDGE_NUM)begin
clk_cnt <= 10'd0;
baud_clk <= ~baud_clk;
end
else begin
clk_cnt <= clk_cnt + 10'd1;
baud_clk <= baud_clk;
end
end
endmodule
2、UART发送端
(1)RTL代码
为了实现可变参数设计,这里使用parameter可以在例化的时候调整波特率和时钟频率以及校验模式:
其中校验模式参数定义为:
输入输出端口如下所示:
在UART_TX中例化上一节介绍过的时钟分频模块用于控制状态机的状态转移和输出等逻辑。
由于输入UART发送端的使能信号uart_en为异步输入信号,因此需要两级寄存将异步信号同步化,降低亚稳态的影响:
本设计使用的校验为奇偶校验,将奇偶校验位产生逻辑分离出来使用寄存器寄存,这样就不用在校验周期的时候等待校验位计算完成再传输而是直接使用:
本设计采用三段式状态机,其状态编码如下所示,由于状态少,使用独热码编码节省组合逻辑资源:
状态机的第一段为状态转移同步逻辑:
此外需要一个发送bit数计数器来计数已经发送了多少个bit用来控制状态机在数据发送完成之后的转移。 当状态为发送数据状态时每发送一个bie,发送bit数计数器将进行递增直到位满溢出。
状态机的第二段为产生下一个状态组合逻辑,最开始状态机上电复位到IDLE空闲状态,被两级寄存后的使能信号uart_en_d1触发之后进入起始位发送状态START,发送完毕起始位之后进入数据发送状态DATA,当发送bit数计数器为7即3’b111的时候判断校验模式VERIFY_MODE是否为无校验,是则进入终止位发送STOP,否则进入校验位发送状态VERI,若未发送完数据则停留在DATA继续发送数据。
这里使用按位与运算&data_cnt来替代data_cnt==3’d111是因为相同逻辑功能的情况下,前者综合出三输入与门,后者综合出减法器加一个组合逻辑。相比之下前者更节省逻辑资源。
状态机处于校验状态VERI后进入终止位发送状态STOP。处于终止位发送状态时判断寄存后的UART发送使能信号uart_en_d1,如果使能则进入START继续发送,如果不使能则回到空闲IDLE。
状态机的第三段为产生输出的同步逻辑,先使用数据寄存器data_reg在START状态寄存输入的并行数据data,在DATA数据发送状态每发送一位data_reg右移一位(UART先发送低位后发送高位,因此是右移;如果是IIC或SPI则应该左移,因为先发送高位后发送低位)。在校验周期VERI的时候uart_txd发送的是已经运算好的校验位uart_verify_bit。
完整代码如下:
module UART_TX_VERIFY #(
parameter BAUD = 115200,
parameter CLK_frq = 100000000,
parameter VERIFY_MODE = 0
)(
input sys_clk,//系统时钟
input rst_n,//系统复位
input uart_en,//UART发送使能端 异步信号
input [7:0]data,//即将输出的BYTE
output reg uart_txd//UART发送端
);
//校验参数定义
parameter VERIFY_NONE = 2'b00;//无校验
parameter VERIFY_ODD = 2'b01;//奇校验
parameter VERIFY_EVEN = 2'b11;//偶校验
wire baud_clk;
//产生波特率时钟
CLK_div #(
.BAUD(BAUD),
.CLK_frq(CLK_frq)
)CLK_div_dut(
.sys_clk(sys_clk),
.rst_n(rst_n),
.baud_clk (baud_clk)
);
//uart_en异步信号同步化
reg uart_en_d0,uart_en_d1;
always @(posedge baud_clk or negedge rst_n) begin
if(!rst_n)begin
uart_en_d0 <= 1'b0;
uart_en_d1 <= 1'b0;
end
else begin
uart_en_d0 <= uart_en;
uart_en_d1 <= uart_en_d0;
end
end
//产生校验位
reg uart_verify_bit;
always @(*) begin
case(VERIFY_MODE)//0表示传输成功
VERIFY_ODD: uart_verify_bit = ~(^data);
VERIFY_EVEN:uart_verify_bit = ^data;
default:uart_verify_bit = 1'b1;
endcase
end
//FSM状态编码
localparam IDLE = 5'b00001;
localparam START= 5'b00010;
localparam DATA = 5'b00100;
localparam VERI = 5'b01000;
localparam STOP = 5'b10000;
//FSM状态寄存器
reg [4:0] cstate,nstate;
//发送数据寄存器
reg [7:0] data_reg;
//发送bit计数器
reg [2:0] data_cnt;
always @(posedge baud_clk or negedge rst_n) begin
if(!rst_n)begin
data_cnt <= 3'd0;
end
else if(cstate == DATA)begin
data_cnt <= data_cnt + 3'd1;
end
else begin
data_cnt <= 3'd0;
end
end
//FSM状态转移同步逻辑
always @(posedge baud_clk or negedge rst_n) begin
if(!rst_n)begin
cstate <= IDLE;
end
else begin
cstate <= nstate;
end
end
//FSM产生下一个状态组合逻辑
always @(*) begin
case (cstate)
IDLE,STOP:begin
nstate = uart_en_d1?START:IDLE;
end
START:begin
nstate = DATA;
end
DATA:begin//记满0~7则发送BYTE完毕
nstate = (&data_cnt)?((VERIFY_MODE==VERIFY_NONE)?STOP:VERI):DATA;
end
VERI:begin
nstate = STOP;
end
STOP:begin
nstate = uart_en_d1?START:IDLE;
end
default:nstate = IDLE;
endcase
end
//FSM产生输出逻辑
always @(posedge baud_clk or negedge rst_n) begin
if(!rst_n)begin
data_reg <= 8'h00;
uart_txd <= 1'b1;
end
else begin
case (nstate)
START:begin
data_reg <= data;
uart_txd <= 1'b0;
end
DATA:begin
data_reg <= data_reg>>1;
uart_txd <= data_reg[0];
end
VERI:begin
data_reg <= 8'h00;
uart_txd <= uart_verify_bit;
end
STOP:begin
data_reg <= 8'h00;
uart_txd <= 1'b1;
end
default:begin
data_reg <= 8'h00;
uart_txd <= 1'b1;
end
endcase
end
end
endmodule
(2)仿真结果
testbench代码如下:
module UART_TX_tb;
// Parameters
localparam BAUD = 115200;
localparam CLK_frq = 100000000;
//校验参数定义
parameter VERIFY_NONE = 2'b00;//无校验
parameter VERIFY_ODD = 2'b01;//奇校验
parameter VERIFY_EVEN = 2'b11;//偶校验
// Ports
reg sys_clk;
reg uart_en;
reg rst_n;
reg [7:0] data;
wire uart_txd;
UART_TX_VERIFY#(
.BAUD(BAUD),
.CLK_frq(CLK_frq),
.VERIFY_MODE(VERIFY_EVEN)
)UART_TX_dut(
.sys_clk(sys_clk),
.uart_en(uart_en),
.rst_n(rst_n),
.data(data),
.uart_txd(uart_txd)
);
initial begin
begin
sys_clk = 0;
uart_en = 0;
rst_n = 0;
data = 8'b01011010;
#1300;
uart_en = 1;
rst_n = 1;
// #100;
// uart_en = 0;
// #100000 ;
// uart_en = 1;
// data = 8'b11011101;
#500000 $finish;
end
end
always
#5 sys_clk = ! sys_clk ;
endmodule
测试在偶校验情况下的UART传送情况,仿真波形图如下,功能正确:
3、UART接收端
(1)RTL代码
与UART_TX同理,使用参数化设计:
输入输出端口如下所示:
例化分频器产生波特率时钟驱动状态机:
由于UART是串行全双工异步通讯协议,因此发送端UART_TX和接收端UART_RX所使用的系统时钟可以不同(笔者为了方便还是使用了相同频率的时钟,但是实际上是可以不同的),因此接收端输入串行信号是异步信号,需要进行两级寄存同步化,减少亚稳态的影响(注意是减少而不是消除)。
校验模式和状态机参数编码如下:
同样需要一个接收bit数计数器来判断接收的bit数来控制状态转移:
状态机第一段为状态转移同步逻辑:
状态机第二段为产生下一个状态的组合逻辑,因为数据稳定性的要求,uart_rxd_d1需要在发送周期的中间时刻被采样,处于IDLE的状态机在采样到低电平起始位的时候就进入数据接收状态DATA。同理,在数据接收状态时判断接收bit数计数器是否为3‘b111,如果接收完毕判断校验模式选择进入VERI或者STOP。
状态机第三段是产生输出的逻辑,输出有两个,分别是接收的并行数据uart_rx_data和接收完成标志位。
串转并逻辑如下,注意是在baud_clk下降沿进行采样,此时数据早已经建立好所以稳定。在DATA数据接收状态的时候通过右移运算来接收数据。
产生接收到的数据产生校验位逻辑分离出来,降低时延:
注意这里是接收端接收的数据uart_rx_data进行逻辑运算与两级寄存后的uart_rxd_d1进行逻辑运算来判断是由传输错误。如果传输成功,则uart_rx_done在VERI状态就拉高,反之只有在STOP状态才拉高。
完整代码如下所示:
module UART_RX #( parameter BAUD = 115200,//bps parameter CLK_frq = 100000000,//Hz parameter VERIFY_MODE = 0//默认不校验 )( input sys_clk,//系统时钟 input rst_n,//系统复位 input uart_rxd,//UART传输端 output reg uart_rx_done,//接收完成标志 output reg [7:0]uart_rx_data//接收到的数据 ); //校验参数定义 parameter VERIFY_NONE = 2'b00;//无校验 parameter VERIFY_ODD = 2'b01;//奇校验 parameter VERIFY_EVEN = 2'b11;//偶校验 //FSM状态编码 localparam IDLE = 4'b0001; localparam DATA = 4'b0010; localparam VERI = 4'b0100; localparam STOP = 4'b1000; //FSM状态寄存器 reg [3:0] cstate,nstate;//状态寄存器 reg uart_rxd_d0,uart_rxd_d1;//打两拍同步化 reg [2:0] data_cnt;//接收数据位数寄存器 wire baud_clk;//波特率时钟 wire odd_verify_flag;//奇校验成功标志 wire even_verify_flag;//偶校验成功标志 //产生波特率时钟 CLK_div #( .BAUD(BAUD), .CLK_frq(CLK_frq) )CLK_div( .sys_clk(sys_clk), .rst_n(rst_n), .baud_clk (baud_clk) ); //uart_rxd异步信号同步化 减少亚稳态影响 always @(posedge sys_clk or negedge rst_n) begin if(!rst_n)begin uart_rxd_d0 <= 1'b0; uart_rxd_d1 <= 1'b0; end else begin uart_rxd_d0 <= uart_rxd; uart_rxd_d1 <= uart_rxd_d0; end end //接收bit数计数器 always @(posedge baud_clk or negedge rst_n) begin if(!rst_n)begin data_cnt <= 3'd0; end else if(cstate == DATA)begin data_cnt <= data_cnt + 3'd1; end else begin data_cnt <= 3'd0; end end //状态转移同步逻辑 always @(posedge baud_clk or negedge rst_n) begin if(!rst_n)begin cstate <= IDLE; end else begin cstate <= nstate; end end //产生下一个状态组合逻辑 always @(*) begin case (cstate) IDLE: nstate = uart_rxd_d1?IDLE:DATA; DATA: nstate = (&data_cnt)?((VERIFY_MODE==VERIFY_NONE)?STOP:VERI):DATA; VERI: nstate = STOP; STOP: nstate = IDLE; default:nstate = IDLE; endcase end //产生输出逻辑 下降沿采样数据稳定 always @(negedge baud_clk or negedge rst_n) begin if(!rst_n)begin uart_rx_data <= 8'd0; end else begin case (cstate) DATA:uart_rx_data <= {uart_rxd_d1,uart_rx_data[7:1]}; VERI,STOP:uart_rx_data <= uart_rx_data; default:uart_rx_data <= 8'd0; endcase end end //产生校验位 assign odd_verify_flag = (VERIFY_MODE == VERIFY_ODD) && (uart_rxd_d1 == ~(^uart_rx_data))?1'b1:1'b0; assign even_verify_flag = (VERIFY_MODE == VERIFY_EVEN) && (uart_rxd_d1 == ^uart_rx_data)?1'b1:1'b0; always @(posedge baud_clk or negedge rst_n) begin if(!rst_n)begin uart_rx_done <= 1'b0; end else begin case (nstate) VERI:uart_rx_done <= (odd_verify_flag||even_verify_flag)?1'b1:1'b0; STOP:uart_rx_done <= 1'b1; default:uart_rx_done <= 1'b0; endcase end end endmodule
(2)仿真结果
testbench代码如下,使用UART_TX发送数据,UART_RX接收数据:
module testbench(); // Parameters localparam BAUD = 115200; localparam CLK_frq = 100000000; //校验参数定义 parameter VERIFY_NONE = 2'b00;//无校验 parameter VERIFY_ODD = 2'b01;//奇校验 parameter VERIFY_EVEN = 2'b11;//偶校验 //声明信号线 reg sys_clk; reg uart_en; reg rst_n; reg [7:0]data; wire uart_bus; wire uart_rx_done; wire [7:0] uart_rx_data; // wire tx_state; //产生激励 initial begin sys_clk = 0; rst_n = 0; uart_en = 0; data = 8'h72; #100; rst_n = 1; uart_en = 1; @(posedge uart_rx_done); data = 8'h89; @(posedge uart_rx_done); data = 8'b10010011; #100000; $finish; end always #5 sys_clk = ~sys_clk; //模块实例化 UART_TX_VERIFY#( .BAUD(BAUD), .CLK_frq(CLK_frq), .VERIFY_MODE(VERIFY_EVEN) )UART_TX_dut( .sys_clk(sys_clk), .uart_en(uart_en), .rst_n(rst_n), .data(data), .uart_txd(uart_bus) ); UART_RX #( .BAUD(BAUD), .CLK_frq(CLK_frq), .VERIFY_MODE(VERIFY_EVEN) ) UART_RX_dut ( .sys_clk(sys_clk), .rst_n(rst_n), .uart_rxd(uart_bus), .uart_rx_data(uart_rx_data), .uart_rx_done(uart_rx_done) ); endmodule
仿真波形如下所示:
没有回复内容