赞
踩
串口(UART)协议的发送和接收时序、物理层接口下面两篇博客讲的很详细。
y串口(UART)的FPGA实现(含源码工程)_孤独的单刀的博客-CSDN博客_fpga uarthttps://blog.csdn.net/wuzhikaidetb/article/details/114596930UART的fpga实现_alone_l的博客-CSDN博客_uart fpga
https://blog.csdn.net/alone_l/article/details/124946773 这里我主要参考的是博主孤独的单刀的代码,虽然博主写的系列文章都很不错而且基础理论的讲解也很详细到位,但是博主并没有使用状态机的方式实现,并且使用case来产生uart_txd输出和串转并得到输入的语句会综合出一个很大的多路选择器,而同样的功能可是使用移位寄存器来实现,这就会造成资源不必要的浪费。RTL代码与其综合出的电路的对应如下所示:
1、UART发送模块中
2、UART接收模块中
在本篇博客中笔者使用了状态机+移位寄存器的方式来实现,很大程度上减少了资源的损耗,移位寄存器部分代码和综合结果如下。
1、UART发送模块
2、UART接收模块
可以看到,使用移位寄存器的方式来实现UART驱动更加节省资源。下面笔者将分别介绍UART接收端和发送端的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

为了实现可变参数设计,这里使用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
-

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传送情况,仿真波形图如下,功能正确:
与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

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
-

仿真波形如下所示:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。