FPGA实现uart协议

简介

使用verilog实现uart协议,能够和pc进行通信,实现串口回环功能,各参数设置如下:

  • 波特率:115200
  • 数据位:8
  • 停止位:任意
  • 校验位:无

系统时钟为50M,115200波特率下,每一个bit占50M/115200 = 434时钟周期。
停止位任意即不考虑停止位。代码追求简洁,能用就行。
注意:uart协议是LSB优先的

接收模块

接收模块从pc端接收到异步的串行信号,解析为valid信号+一字节并行数据,传递到其他模块,回环的话,直接传递到发送模块。
流程:

  1. 首先对输入的异步信号rx打两拍防止亚稳态
  2. 检测rx下降沿,拉高计数器使能
  3. 波特计数器循环计数0到433,比特计数器根据波特计数器溢出,从0计数到8,其中0是起始位,1-8是数据位
  4. 在每一位中间进行采样,即波特计数器=433/2时,数据比较稳定,所以在此刻采样进移位寄存器
  5. 波特计数器和比特计数器都结束时,拉低计数器使能,拉高valid一时钟周期,输出移位寄存器中的数据。

代码:

module uart_rx(
    input               clk,            // 50M
    input               rst_n,
    
    // uart side
    input               rx,

    // host side
    output reg          valid,
    output reg  [7:0]   data
);
//---------------------------------信号声明-----------------------------------
    localparam BAUD_RATE    = 115200;
    localparam BAUD_CNT_MAX = (50_000_000 / BAUD_RATE) - 1;
    // rx打两拍,第三拍检测下降沿
    reg [2:0]   rx_shift;
    // 计数器使能
    reg         cnt_ena;
    // baud计数器
    reg [12:0]  baud_cnt;
    // bit计数器
    reg [3:0]   bit_cnt;
//---------------------------------------------------------------------------
    // rx打两拍,第三拍检测下降沿
    always @(posedge clk, negedge rst_n) begin
        if(!rst_n)
            rx_shift <= 3'b111;
        else
            rx_shift <= {rx, rx_shift[2:1]};
    end

    // 计数器使能
    always @(posedge clk, negedge rst_n) begin
        if(!rst_n)
            cnt_ena <= 0;
        else if(~rx_shift[1] & rx_shift[0])// rx下降沿
            cnt_ena <= 1'b1;
        else if(baud_cnt == BAUD_CNT_MAX && bit_cnt == 4'd8)
            cnt_ena <= 1'b0;
    end

    // baud cnt
    always @(posedge clk, negedge rst_n) begin
        if(!rst_n)
            baud_cnt <= 0;
        else if(cnt_ena) begin
            if(baud_cnt == BAUD_CNT_MAX)
                baud_cnt <= 0;
            else
                baud_cnt <= baud_cnt + 13'd1;
        end  
    end

    // bit cnt
    always @(posedge clk, negedge rst_n) begin
        if(!rst_n)
            bit_cnt <= 0;
        else if(cnt_ena && baud_cnt == BAUD_CNT_MAX) begin
            if(bit_cnt == 4'd8)
                bit_cnt <= 0;
            else
                bit_cnt <= bit_cnt + 4'd1;
        end
    end

    // output valid
    always @(posedge clk, negedge rst_n) begin
        if(!rst_n)
            valid <= 0;
        else if(baud_cnt == BAUD_CNT_MAX && bit_cnt == 4'd8)
            valid <= 1'b1;
        else
            valid <= 1'b0;
    end

    // output data
    always @(posedge clk, negedge rst_n) begin
        if(!rst_n)
            data <= 0;
        else if(cnt_ena && baud_cnt == (BAUD_CNT_MAX / 2))    // 在每个bit中间采样进入移位寄存器
            data <= {rx_shift[0], data[7:1]};
    end
endmodule

对该模块进行简单的波形仿真,对其发送三次数据,分别是8’h01, 8’hab, 8’hff, tb如下:

`timescale 1ps/1ps
module uart_rx_tb;
    reg clk = 1'b1;
    always #10 clk = ~clk;
    reg rst_n = 1'b0;

    reg rx = 1'b1;

    wire       valid;
    wire [7:0] data;

    integer i = 0;

    initial begin
        #30 rst_n = 1'b1;
        #10;
        #10000;
        receieve_data(8'h01);
        receieve_data(8'hab);
        receieve_data(8'hff);
        #100 $stop(2);// $finish;
    end

    task receieve_data;
        input [7:0] sim_data;
        begin
            // 起始位
            rx <= 1'b0;
            #(20 * 434);
            // 8bit数据
            for(i = 0; i < 8; i = i + 1) begin
                rx <= sim_data[i];
                #(20 * 434);
            end
            // 停止位
            rx <= 1'b1;
            #(20 * 434);
            #1000;
        end
    endtask

    uart_rx inst_uart_rx (
        .clk   (clk), 
        .rst_n (rst_n), 
        .rx    (rx), 
        .valid (valid), 
        .data  (data)
    );

endmodule

波形图如下,可以观察到,三次valid拉高时的data正好对应了8’h01, 8’hab, 8’hff
FPGA实现uart协议_第1张图片

发送模块

发送模块接收内部的valid+data,转化为uart串行信号通过io输出。
流程:

  1. 检测到valid信号,则对data进行寄存,并拉高计数器使能
  2. 波特计数器循环计数0到433,比特计数器根据波特计数器溢出,从0计数到8,其中0是起始位,1-8是数据位
  3. 根据比特计数器值输出对应的数据位

代码:

module uart_tx(
    input           clk,            // 50M
    input           rst_n,
    
    // host side
    input           valid,
    input   [7:0]   data,
    
    // uart side
    output          tx
);
//------------------------------------信号声明---------------------------
    localparam BAUD_RATE    = 115200;
    localparam BAUD_CNT_MAX = (50_000_000 / BAUD_RATE) - 1;
    // 数据寄存 + 起始位
    reg [8:0]   data_start_reg;
    // 计数器使能
    reg         cnt_ena;
    // baud计数器
    reg [12:0]  baud_cnt;
    // bit计数器
    reg [3:0]   bit_cnt;
//----------------------------------------------------------------------
    // 数据寄存 + 起始位
    always @(posedge clk, negedge rst_n) begin
        if(!rst_n)
            data_start_reg <= 0;
        else if(valid)
            data_start_reg <= {data, 1'b0};
    end

    // cnt_ena
    always @(posedge clk, negedge rst_n) begin
        if(!rst_n)
            cnt_ena <= 0;
        else if(valid)
            cnt_ena <= 1'b1;
        else if(baud_cnt == BAUD_CNT_MAX && bit_cnt == 4'd8)
            cnt_ena <= 0;
    end

    // baud_cnt
    always @(posedge clk, negedge rst_n) begin
        if(!rst_n)
            baud_cnt <= 0; 
        else if(cnt_ena) begin
            if(baud_cnt == BAUD_CNT_MAX)
                baud_cnt <= 0;
            else
                baud_cnt <= baud_cnt + 13'd1;
        end
    end

    // bit_cnt
    always @(posedge clk, negedge rst_n) begin
        if(!rst_n)
            bit_cnt <= 0;
        else if(cnt_ena && baud_cnt == BAUD_CNT_MAX) begin
            if(bit_cnt == 4'd8)
                bit_cnt <= 0;
            else
                bit_cnt <= bit_cnt + 4'd1;
        end
    end

    // tx
    assign tx = cnt_ena ? data_start_reg[bit_cnt] : 1'b1;
endmodule

对该模块进行简单的波形仿真,让其发送三次数据,分别是8’h01, 8’hab, 8’hff, tb如下:

`timescale 1ps/1ps
module uart_tx_tb;
    reg clk = 1'b1;
    always #10 clk = ~clk;
    reg rst_n = 1'b0;

    reg       valid = 1'b0;
    reg [7:0] data  = 8'd0;

    wire tx;

    initial begin
        #30 rst_n = 1'b1;
        #10;
        #10000;
        uart_send(8'h01);
        uart_send(8'hab);
        uart_send(8'hff);
        #100 $stop(2);// $finish;
    end

    task uart_send;
        input [7:0] send_data;
        begin
            valid <= 1'b1;
            data  <= send_data;
            #20;
            valid <= 1'b0;
            data  <= 8'd0;
            #(20 * 434 * 10);   // 10baud
            #1000;
        end
    endtask

    uart_tx inst_uart_tx (
        .clk   (clk),
        .rst_n (rst_n),
        .valid (valid),
        .data  (data),
        .tx    (tx)
    );

endmodule

波形如下,可以看到,valid信号后,tx端会将数据转化为uart串行数据发送出去,由于data只持续1时钟周期,所以图片看不清楚,三次valid对应的data分别是8’h01, 8’hab, 8’hff
在这里插入图片描述

效果演示

顶层模块就不细说了,将接收模块的valid+data接到发送模块的valid+data即可
串口软件使用正点原子的XCOM,发送字符串hello world!,接收如下:
FPGA实现uart协议_第2张图片

你可能感兴趣的:(FPGA,Modelsim,fpga开发)