FPGA实现UART通讯(FSM+移位寄存器实现 含校验位)-FPGA CPLD资料源码分享社区-FPGA CPLD-ChipDebug

FPGA实现UART通讯(FSM+移位寄存器实现 含校验位)

 

 一、UART通讯协议原理与时序

串口(UART)协议的发送和接收时序、物理层接口下面两篇博客讲的很详细。

y​​​​​​​​​​​​​​串口(UART)的FPGA实现(含源码工程)_孤独的单刀的博客-CSDN博客_fpga uart图片[1]-FPGA实现UART通讯(FSM+移位寄存器实现 含校验位)-FPGA CPLD资料源码分享社区-FPGA CPLD-ChipDebughttps://blog.csdn.net/wuzhikaidetb/article/details/114596930UART的fpga实现_alone_l的博客-CSDN博客_uart fpga图片[1]-FPGA实现UART通讯(FSM+移位寄存器实现 含校验位)-FPGA CPLD资料源码分享社区-FPGA CPLD-ChipDebughttps://blog.csdn.net/alone_l/article/details/124946773        这里我主要参考的是博主孤独的单刀的代码,虽然博主写的系列文章都很不错而且基础理论的讲解也很详细到位,但是博主并没有使用状态机的方式实现,并且使用case来产生uart_txd输出和串转并得到输入的语句会综合出一个很大的多路选择器,而同样的功能可是使用移位寄存器来实现,这就会造成资源不必要的浪费。RTL代码与其综合出的电路的对应如下所示:

1、UART发送模块中

图片[3]-FPGA实现UART通讯(FSM+移位寄存器实现 含校验位)-FPGA CPLD资料源码分享社区-FPGA CPLD-ChipDebug

图片[4]-FPGA实现UART通讯(FSM+移位寄存器实现 含校验位)-FPGA CPLD资料源码分享社区-FPGA CPLD-ChipDebug
2、UART接收模块中

图片[5]-FPGA实现UART通讯(FSM+移位寄存器实现 含校验位)-FPGA CPLD资料源码分享社区-FPGA CPLD-ChipDebug图片[6]-FPGA实现UART通讯(FSM+移位寄存器实现 含校验位)-FPGA CPLD资料源码分享社区-FPGA CPLD-ChipDebug

在本篇博客中笔者使用了状态机+移位寄存器的方式来实现,很大程度上减少了资源的损耗,移位寄存器部分代码和综合结果如下。

1、UART发送模块

图片[7]-FPGA实现UART通讯(FSM+移位寄存器实现 含校验位)-FPGA CPLD资料源码分享社区-FPGA CPLD-ChipDebug

图片[8]-FPGA实现UART通讯(FSM+移位寄存器实现 含校验位)-FPGA CPLD资料源码分享社区-FPGA CPLD-ChipDebug

2、UART接收模块

图片[9]-FPGA实现UART通讯(FSM+移位寄存器实现 含校验位)-FPGA CPLD资料源码分享社区-FPGA CPLD-ChipDebug

图片[10]-FPGA实现UART通讯(FSM+移位寄存器实现 含校验位)-FPGA CPLD资料源码分享社区-FPGA CPLD-ChipDebug

可以看到,使用移位寄存器的方式来实现UART驱动更加节省资源。下面笔者将分别介绍UART接收端和发送端的RTL实现。

二、UART接口驱动的RTL实现与仿真结果

1、时钟分频产生波特率时钟

RTL代码:

为了满足波特率的要求,需要将总线电平维持(时钟频率/波特率)个周期才能被接收端正确接收。为什么是(时钟频率/波特率)个系统时钟周期呢?得从波特率定义来看,波特率是指每秒钟发送/接收的bit数,单位为bit数/秒=bps;时钟频率是每秒的时钟周期数,单位为周期数/秒=Hz,因此(时钟频率/波特率)的单位为周期数/bit数也就是一个发送/接收1bit数据所需要的周期数!

因此我们可以通过产生一个波特率时钟来控制状态机的状态转移,为了实现参数化的设计,使用parameter可以在例化的时候调整调整波特率和时钟频率:

图片[11]-FPGA实现UART通讯(FSM+移位寄存器实现 含校验位)-FPGA CPLD资料源码分享社区-FPGA CPLD-ChipDebug

并定义传输一位所需要的周期数图片[12]-FPGA实现UART通讯(FSM+移位寄存器实现 含校验位)-FPGA CPLD资料源码分享社区-FPGA CPLD-ChipDebug

为了使占空比接近1/2,这里定义了计数分频跳变沿时计数器的计数数为图片[13]-FPGA实现UART通讯(FSM+移位寄存器实现 含校验位)-FPGA CPLD资料源码分享社区-FPGA CPLD-ChipDebug

剩下的就是简单的计数分频逻辑了,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可以在例化的时候调整波特率和时钟频率以及校验模式:

图片[14]-FPGA实现UART通讯(FSM+移位寄存器实现 含校验位)-FPGA CPLD资料源码分享社区-FPGA CPLD-ChipDebug

其中校验模式参数定义为:

图片[15]-FPGA实现UART通讯(FSM+移位寄存器实现 含校验位)-FPGA CPLD资料源码分享社区-FPGA CPLD-ChipDebug

输入输出端口如下所示:

图片[16]-FPGA实现UART通讯(FSM+移位寄存器实现 含校验位)-FPGA CPLD资料源码分享社区-FPGA CPLD-ChipDebug

在UART_TX中例化上一节介绍过的时钟分频模块用于控制状态机的状态转移和输出等逻辑。

图片[17]-FPGA实现UART通讯(FSM+移位寄存器实现 含校验位)-FPGA CPLD资料源码分享社区-FPGA CPLD-ChipDebug

由于输入UART发送端的使能信号uart_en为异步输入信号,因此需要两级寄存将异步信号同步化,降低亚稳态的影响:

图片[18]-FPGA实现UART通讯(FSM+移位寄存器实现 含校验位)-FPGA CPLD资料源码分享社区-FPGA CPLD-ChipDebug

本设计使用的校验为奇偶校验,将奇偶校验位产生逻辑分离出来使用寄存器寄存,这样就不用在校验周期的时候等待校验位计算完成再传输而是直接使用:

图片[19]-FPGA实现UART通讯(FSM+移位寄存器实现 含校验位)-FPGA CPLD资料源码分享社区-FPGA CPLD-ChipDebug

本设计采用三段式状态机,其状态编码如下所示,由于状态少,使用独热码编码节省组合逻辑资源:

图片[20]-FPGA实现UART通讯(FSM+移位寄存器实现 含校验位)-FPGA CPLD资料源码分享社区-FPGA CPLD-ChipDebug

状态机的第一段为状态转移同步逻辑:

图片[21]-FPGA实现UART通讯(FSM+移位寄存器实现 含校验位)-FPGA CPLD资料源码分享社区-FPGA CPLD-ChipDebug

此外需要一个发送bit数计数器来计数已经发送了多少个bit用来控制状态机在数据发送完成之后的转移。 当状态为发送数据状态时每发送一个bie,发送bit数计数器将进行递增直到位满溢出。

图片[22]-FPGA实现UART通讯(FSM+移位寄存器实现 含校验位)-FPGA CPLD资料源码分享社区-FPGA CPLD-ChipDebug

状态机的第二段为产生下一个状态组合逻辑,最开始状态机上电复位到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。

图片[23]-FPGA实现UART通讯(FSM+移位寄存器实现 含校验位)-FPGA CPLD资料源码分享社区-FPGA CPLD-ChipDebug

状态机的第三段为产生输出的同步逻辑,先使用数据寄存器data_reg在START状态寄存输入的并行数据data,在DATA数据发送状态每发送一位data_reg右移一位(UART先发送低位后发送高位,因此是右移;如果是IIC或SPI则应该左移,因为先发送高位后发送低位)。在校验周期VERI的时候uart_txd发送的是已经运算好的校验位uart_verify_bit。

图片[24]-FPGA实现UART通讯(FSM+移位寄存器实现 含校验位)-FPGA CPLD资料源码分享社区-FPGA CPLD-ChipDebug

完整代码如下:

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

图片[25]-FPGA实现UART通讯(FSM+移位寄存器实现 含校验位)-FPGA CPLD资料源码分享社区-FPGA CPLD-ChipDebug

3、UART接收端

(1)RTL代码

与UART_TX同理,使用参数化设计:

图片[26]-FPGA实现UART通讯(FSM+移位寄存器实现 含校验位)-FPGA CPLD资料源码分享社区-FPGA CPLD-ChipDebug

输入输出端口如下所示:

图片[27]-FPGA实现UART通讯(FSM+移位寄存器实现 含校验位)-FPGA CPLD资料源码分享社区-FPGA CPLD-ChipDebug

例化分频器产生波特率时钟驱动状态机:

图片[28]-FPGA实现UART通讯(FSM+移位寄存器实现 含校验位)-FPGA CPLD资料源码分享社区-FPGA CPLD-ChipDebug

由于UART是串行全双工异步通讯协议,因此发送端UART_TX和接收端UART_RX所使用的系统时钟可以不同(笔者为了方便还是使用了相同频率的时钟,但是实际上是可以不同的),因此接收端输入串行信号是异步信号,需要进行两级寄存同步化,减少亚稳态的影响(注意是减少而不是消除)。

图片[29]-FPGA实现UART通讯(FSM+移位寄存器实现 含校验位)-FPGA CPLD资料源码分享社区-FPGA CPLD-ChipDebug

校验模式和状态机参数编码如下:

图片[30]-FPGA实现UART通讯(FSM+移位寄存器实现 含校验位)-FPGA CPLD资料源码分享社区-FPGA CPLD-ChipDebug图片[31]-FPGA实现UART通讯(FSM+移位寄存器实现 含校验位)-FPGA CPLD资料源码分享社区-FPGA CPLD-ChipDebug

同样需要一个接收bit数计数器来判断接收的bit数来控制状态转移:

图片[32]-FPGA实现UART通讯(FSM+移位寄存器实现 含校验位)-FPGA CPLD资料源码分享社区-FPGA CPLD-ChipDebug

状态机第一段为状态转移同步逻辑:

图片[33]-FPGA实现UART通讯(FSM+移位寄存器实现 含校验位)-FPGA CPLD资料源码分享社区-FPGA CPLD-ChipDebug

状态机第二段为产生下一个状态的组合逻辑,因为数据稳定性的要求,uart_rxd_d1需要在发送周期的中间时刻被采样,处于IDLE的状态机在采样到低电平起始位的时候就进入数据接收状态DATA。同理,在数据接收状态时判断接收bit数计数器是否为3‘b111,如果接收完毕判断校验模式选择进入VERI或者STOP。

图片[34]-FPGA实现UART通讯(FSM+移位寄存器实现 含校验位)-FPGA CPLD资料源码分享社区-FPGA CPLD-ChipDebug

状态机第三段是产生输出的逻辑,输出有两个,分别是接收的并行数据uart_rx_data和接收完成标志位。

串转并逻辑如下,注意是在baud_clk下降沿进行采样,此时数据早已经建立好所以稳定。在DATA数据接收状态的时候通过右移运算来接收数据。

图片[35]-FPGA实现UART通讯(FSM+移位寄存器实现 含校验位)-FPGA CPLD资料源码分享社区-FPGA CPLD-ChipDebug

产生接收到的数据产生校验位逻辑分离出来,降低时延:

图片[36]-FPGA实现UART通讯(FSM+移位寄存器实现 含校验位)-FPGA CPLD资料源码分享社区-FPGA CPLD-ChipDebug

注意这里是接收端接收的数据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  

仿真波形如下所示:

图片[37]-FPGA实现UART通讯(FSM+移位寄存器实现 含校验位)-FPGA CPLD资料源码分享社区-FPGA CPLD-ChipDebug

请登录后发表评论

    没有回复内容