LOADING

加载过慢请开启缓存 浏览器默认开启

Verilog-UART设计v3

2024/4/8 Verilog

Abstract: Verilog UART 设计第三版

代码编写思路同第二版

第三版进行了逻辑优化,使用更加合理的满名规范,以及解决了 数据有效信号 和 接收数据 时序不完全同步的问题

上板测试时发现 PC 发送的串口数据和接收的串口数据数目不相等,后进行了简单验证:

每当接收到一次数据,计数器加一并把计数器的值通过串口送出。在 PC 连续(如1ms一次)发送串口数据时,发现 PC 接收到的串口数据(计数器的值)发生间断,假设 FPGA 内部的计数器不出错,结论是 PC 的接收速度跟不上 FPGA 的发送速度

模块代码

Rx 部分

// UART 接收模块 可选波特率 可选数据位 仅支持一位停止位

module UART_Rx_module #(
    parameter CLK_FREQ_MHZ = 27,
    parameter BAUD_RATE = 9600,
    parameter DATA_WIDTH = 8  // 数据位宽
) (
    input in_sys_clk,
    input in_rst_n,
    input in_rx_ready,  // 准备好接受串口数据
    input in_rx_pin,

    output [DATA_WIDTH-1:0] out_rx_data,  // 接收到的串口数据 rx_data_ready和rx_data_valid均为高时送出
    output out_rx_data_valid  // 接收到的串口数据有效
);

    localparam BAUD_CNT_THRESHOLD = CLK_FREQ_MHZ * 1000000 / BAUD_RATE - 1;  // 波特率计数最大值
    localparam BAUD_CLK_CNT_THRESHOLD = (DATA_WIDTH + 2);  // 波特率时钟计数最大值 数据位+起始位+结束位

    wire w_rx_negedge;
    wire w_rx_done;  // 接收接收标识

    reg r_rx_pin_t0, r_rx_pin_t1;
    reg r_rx_state;  // 接收状态
    reg [$clog2(BAUD_CNT_THRESHOLD)-1:0] r_baud_rate_cnt;  // 波特率计数器
    reg r_baud_rate_sample_clk;  // 采样时钟 为高时采样
    reg [$clog2(BAUD_CLK_CNT_THRESHOLD)-1:0] r_baud_rate_clk_cnt;  // 波特率时钟计数
    reg [2:0] r_rx_sample_bit;  // 一位采样数据 六次采样
    reg [BAUD_CLK_CNT_THRESHOLD-1:0] r_rx_data_frame_byte;  // 完整数据帧
    reg [DATA_WIDTH-1:0] r_rx_data;
    reg r_rx_data_valid;

    assign w_rx_negedge = r_rx_pin_t1 && (~r_rx_pin_t0);
    assign w_rx_done = (r_baud_rate_clk_cnt==BAUD_CLK_CNT_THRESHOLD);
    assign out_rx_data = (out_rx_data_valid && in_rx_ready)? r_rx_data_frame_byte[BAUD_CLK_CNT_THRESHOLD-2:1] : 'd0;
    assign out_rx_data_valid = w_rx_done;

//****输入数据同步****//
    always @(posedge in_sys_clk or negedge in_rst_n) begin
        if (~in_rst_n) begin
            r_rx_pin_t0 <= 1'b0; 
            r_rx_pin_t1 <= 1'b0;
        end
        else begin
            r_rx_pin_t0 <= in_rx_pin;
            r_rx_pin_t1 <= r_rx_pin_t0;
        end
    end
//********//

//****接收状态逻辑****//
    always @(posedge in_sys_clk or negedge in_rst_n) begin
        if (~in_rst_n) begin
            r_rx_state <= 1'b0;
        end
        else begin
            if (w_rx_negedge && ~r_rx_state) begin  // 下降沿到来
                r_rx_state <= 1'b1;
            end
            else if (w_rx_done) begin  // 接收完成
                r_rx_state <= 1'b0;
            end
            else if (r_baud_rate_clk_cnt=='d1 && r_rx_data_frame_byte[0]==1'b1) begin  // 起始位采样后高电平居多
                r_rx_state <= 1'b0;
            end
            else begin
                r_rx_state <= r_rx_state;  // 在一次接收中rx_state保持高电平
            end
        end
    end
//********//

//****波特率计数器****//
    always @(posedge in_sys_clk or negedge in_rst_n) begin
        if (~in_rst_n) begin
            r_baud_rate_cnt <= 'd0;
        end
        else begin
            if (r_rx_state) begin  // 接收状态
                if (r_baud_rate_cnt==BAUD_CNT_THRESHOLD) begin
                    r_baud_rate_cnt <= 'd0;
                end
                else begin
                    r_baud_rate_cnt <= r_baud_rate_cnt + 1'd1;
                end
            end
            else begin
                r_baud_rate_cnt <= 'd0;
            end
        end
    end
//********//

//****波特率采样时钟生成****//
    always @(posedge in_sys_clk or negedge in_rst_n) begin
        if (~in_rst_n) begin
            r_baud_rate_sample_clk <= 1'b0;
        end
        else begin
            if (r_baud_rate_cnt==BAUD_CNT_THRESHOLD/2 - 3) begin  // 持续六个时钟
                r_baud_rate_sample_clk <= 1'b1;
            end
            else if (r_baud_rate_cnt==BAUD_CNT_THRESHOLD/2 + 3) begin
                r_baud_rate_sample_clk <= 1'b0;
            end
            else begin
                r_baud_rate_sample_clk <= r_baud_rate_sample_clk;
            end
        end
    end
//********//

//****波特率时钟计数****//
    always @(posedge in_sys_clk or negedge in_rst_n) begin
        if (~in_rst_n) begin
            r_baud_rate_clk_cnt <= 'd0;
        end
        else begin
            if (r_rx_state && ~w_rx_done) begin
                if (r_baud_rate_cnt==BAUD_CNT_THRESHOLD/2 + 6) begin  // 采样结束三个时钟后计数加一
                    r_baud_rate_clk_cnt <= r_baud_rate_clk_cnt + 1'd1;
                end
                else begin
                    r_baud_rate_clk_cnt <= r_baud_rate_clk_cnt;
                end
            end
            else begin
                r_baud_rate_clk_cnt <= 'd0;
            end
        end
    end
//********//

//****数据采样****//
    always @(posedge in_sys_clk or negedge in_rst_n) begin
        if (~in_rst_n) begin
            r_rx_sample_bit <= 3'd0;
        end
        else begin
            if (r_baud_rate_sample_clk) begin  // 采样时钟为高
                r_rx_sample_bit <= r_rx_sample_bit+r_rx_pin_t1;
            end
            else begin
                if (r_baud_rate_cnt==BAUD_CNT_THRESHOLD/2 + 5) begin  // 采样结束两个时钟后清零
                    r_rx_sample_bit <= 3'b0;
                end
                else begin
                    r_rx_sample_bit <= r_rx_sample_bit;
                end
            end
        end
    end
//********//

//****串行数据存入****//
    always @(posedge in_sys_clk or negedge in_rst_n) begin
        if (~in_rst_n) begin
            r_rx_data_frame_byte <= 'd0;
        end
        else begin
            if (r_rx_state) begin
                if (r_baud_rate_cnt==BAUD_CNT_THRESHOLD/2 + 4) begin  // 采样结束一个时钟后移位寄存采样数据
                    r_rx_data_frame_byte <= {r_rx_sample_bit[2], r_rx_data_frame_byte[BAUD_CLK_CNT_THRESHOLD-1:1]};
                end
                else begin
                    r_rx_data_frame_byte <= r_rx_data_frame_byte;
                end
            end
            else begin
                r_rx_data_frame_byte <= 'd0;
            end
        end
    end
//********//

endmodule

Tx 部分

// UART 发送模块 可选波特率 可选数据位 仅支持一位停止位

module UART_Tx_module #(
    parameter CLK_FREQ_MHZ = 27,
    parameter BAUD_RATE = 9600,
    parameter DATA_WIDTH = 8  // 数据位宽
) (
    input in_sys_clk,
    input in_rst_n,
    input [DATA_WIDTH-1:0] in_tx_data,  // 准备发送的数据
    input in_tx_data_valid,  // 发送的数据有效

    output out_tx_ready,  // 发送模块已准备好发送数据
    output out_tx_pin  // 发送的串口数据 tx_data_ready和tx_data_valid都为高时数据被发送
);
    localparam BAUD_CLK_CNT_THRESHOLD = (DATA_WIDTH + 2) + 1;  // 波特率时钟计数最大值 数据位+起始位(两位)+停止位(一位)
    localparam BAUD_CNT_THRESHOLD = CLK_FREQ_MHZ * 1000000 / BAUD_RATE - 1;  // 波特率计数最大值

    wire w_tx_done;  // 发送完成

    reg [BAUD_CLK_CNT_THRESHOLD-1:0] r_tx_data_frame;  // 准备发送的完整数据帧
    reg r_tx_state;  // 发送状态
    reg [$clog2(BAUD_CNT_THRESHOLD)-1:0] r_baud_rate_cnt;  // 波特率计数器
    reg [$clog2(BAUD_CLK_CNT_THRESHOLD)-1:0] r_baud_rate_clk_cnt;  // 波特率时钟计数
    reg r_tx_pin;

    assign w_tx_done = (r_baud_rate_clk_cnt==BAUD_CLK_CNT_THRESHOLD);
    assign out_tx_ready = ~r_tx_state;
    assign out_tx_pin = r_tx_pin;

//****发送数据帧生成****//
    always @(posedge in_sys_clk or negedge in_rst_n) begin
        if (~in_rst_n) begin
            r_tx_data_frame <= 'd0;
        end
        else begin
            if (in_tx_data_valid && out_tx_ready) begin
                r_tx_data_frame <= {1'b1, 1'b1, in_tx_data, 1'b0};
            end
            else begin
                if (r_baud_rate_cnt=='d2) begin  // 低位发送后移位
                    r_tx_data_frame <= {1'b0, r_tx_data_frame[BAUD_CLK_CNT_THRESHOLD-1:1]};  // 数据帧移位
                end
                else begin
                    r_tx_data_frame <= r_tx_data_frame;
                end
            end
        end
    end
//********//

//****发送状态逻辑****//
    always @(posedge in_sys_clk or negedge in_rst_n) begin
        if (~in_rst_n) begin
            r_tx_state <= 1'b0;
        end
        else begin
            if (in_tx_data_valid && out_tx_ready) begin
                r_tx_state <= 1'b1;
            end
            else if (w_tx_done) begin
                r_tx_state <= 1'b0;
            end
            else begin
                r_tx_state <= r_tx_state;
            end
        end
    end
//********//

//****波特率计数器****//
    always @(posedge in_sys_clk or negedge in_rst_n) begin
        if (~in_rst_n) begin
            r_baud_rate_cnt <= 'd0;
        end
        else begin
            if (r_tx_state) begin  // 接收状态
                if (r_baud_rate_cnt==BAUD_CNT_THRESHOLD) begin
                    r_baud_rate_cnt <= 'd0;
                end
                else begin
                    r_baud_rate_cnt <= r_baud_rate_cnt + 1'd1;
                end
            end
            else begin
                r_baud_rate_cnt <= 'd0;
            end
        end
    end
//********//

//****波特率时钟计数****//
    always @(posedge in_sys_clk or negedge in_rst_n) begin
        if (~in_rst_n) begin
            r_baud_rate_clk_cnt <= 'd0;
        end
        else begin
            if (r_tx_state && ~w_tx_done) begin
                if (r_baud_rate_cnt=='d1) begin
                    r_baud_rate_clk_cnt <= r_baud_rate_clk_cnt + 1'd1;
                end
                else begin
                    r_baud_rate_clk_cnt <= r_baud_rate_clk_cnt;
                end
            end
            else begin
                r_baud_rate_clk_cnt <= 'd0;
            end
        end
    end
//********//

//****发送数据部分****//
    always @(posedge in_sys_clk or negedge in_rst_n) begin
        if (~in_rst_n) begin
            r_tx_pin <= 1'b1;
        end
        else begin
            if (r_baud_rate_cnt=='d1) begin  // 数据帧移位前送出
                r_tx_pin <= r_tx_data_frame[0];  // 数据低位送出
            end
            else if (~r_tx_state) begin
                r_tx_pin <= 1'b1;
            end
            else begin
                r_tx_pin <= r_tx_pin;
            end
        end
    end
//********//

endmodule

Module-sim 测试代码

// UART 模型测试

`timescale 1ns/1ns

module UART_module_tb;

    wire [7:0] rx_data_tb;
    wire uart_tb;
    wire rx_data_valid_tb;
    wire tx_data_ready_tb;

    reg sys_clk_tb;
    reg rst_n_tb;
    reg tx_data_valid_tb;
    reg [7:0] tx_data_tb;

    wire true;

    assign true = ((tx_data_tb == rx_data_tb) && rx_data_valid_tb);

    initial begin
        sys_clk_tb = 1'b0;
        rst_n_tb = 1'b0;
        tx_data_valid_tb = 1'b0;
        tx_data_tb = 8'h0;
        # 5;
        rst_n_tb = 1'b1;
        tx_data_valid_tb = 1'b1;
        tx_data_tb = 8'h5d;
    end

    always #1 begin
        sys_clk_tb = ~sys_clk_tb;
    end

    always begin
        @(posedge tx_data_ready_tb) tx_data_valid_tb = 1'b1; tx_data_tb = ($random) % 256;
        @(negedge tx_data_ready_tb) tx_data_valid_tb = 1'b0; tx_data_tb = tx_data_tb;
    end

    UART_Rx_module #(
        .CLK_FREQ_MHZ(500),
        .BAUD_RATE(1152000)
    ) u_uart_rx (
        .in_sys_clk(sys_clk_tb),
        .in_rst_n(rst_n_tb),
        .in_rx_ready(1'b1),  // 准备好接受串口数据
        .in_rx_pin(uart_tb),

        .out_rx_data(rx_data_tb),  // 接收到的串口数据 rx_data_ready和rx_data_valid均为高时送出
        .out_rx_data_valid(rx_data_valid_tb)  // 接收到的串口数据有效
    );

    UART_Tx_module #(
        .CLK_FREQ_MHZ(500),
        .BAUD_RATE(1152000)
    ) u_uart_tx (
        .in_sys_clk(sys_clk_tb),
        .in_rst_n(rst_n_tb),
        .in_tx_data(tx_data_tb),  // 准备发送的数据
        .in_tx_data_valid(tx_data_valid_tb),  // 发送的数据有效

        .out_tx_ready(tx_data_ready_tb),  // 发送模块已准备好发送数据
        .out_tx_pin(uart_tb)  // 发送的串口数据 tx_data_ready和tx_data_valid都为高时数据被发送
    );

endmodule

Gowin FPGA 烧录测试代码

// 收到数据后发送回去

module top_module #(
    parameter CLK_FREQ_MHZ = 27,
    parameter BAUD_RATE = 115200
) (
    input in_sys_clk,
    input in_rst_n,
    input in_rx_pin,  // UART 接收

    output out_led_pin_rx,  //LED Rx
    output out_led_pin_tx,  //LED Tx
    output out_tx_pin  // UART 发送
);
    wire w_rx_data_valid;  // 接收数据有效
    wire [7:0] w_rx_data;  // 接收到的数据

    reg r_rx_data_valid;
    reg [7:0] r_rx_data;

    assign out_led_pin_rx = in_rx_pin;
    assign out_led_pin_tx = out_tx_pin;

    always @(posedge in_sys_clk or negedge in_rst_n) begin
        if (~in_rst_n) begin
            r_rx_data_valid <= 1'b0;
            r_rx_data <= 8'd0;
        end
        else begin
            r_rx_data_valid <= w_rx_data_valid;

            if (w_rx_data_valid) begin
                r_rx_data <= w_rx_data;
            end
            else begin
                r_rx_data <= r_rx_data;
            end
        end
    end

    UART_Rx_module #(
        .CLK_FREQ_MHZ(CLK_FREQ_MHZ),
        .BAUD_RATE(BAUD_RATE)
    ) u_uart_rx (
        .in_sys_clk(in_sys_clk),
        .in_rst_n(in_rst_n),
        .in_rx_ready(1'b1),  // 准备好接受串口数据
        .in_rx_pin(in_rx_pin),

        .out_rx_data(w_rx_data),  // 接收到的串口数据 rx_data_ready和rx_data_valid均为高时送出
        .out_rx_data_valid(w_rx_data_valid)  // 接收到的串口数据有效
    );

    UART_Tx_module #(
        .CLK_FREQ_MHZ(CLK_FREQ_MHZ),
        .BAUD_RATE(BAUD_RATE)
    ) u_uart_tx (
        .in_sys_clk(in_sys_clk),
        .in_rst_n(in_rst_n),
        .in_tx_data(r_rx_data),  // 准备发送的数据
        .in_tx_data_valid(r_rx_data_valid),  // 发送的数据有效

        .out_tx_ready(),  // 发送模块已准备好发送数据
        .out_tx_pin(out_tx_pin)  // 发送的串口数据 tx_data_ready和tx_data_valid都为高时数据被发送
    );
    
endmodule