LOADING

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

Verilog-UART设计

2023/10/23 Verilog

Abstract: Verilog UART  设计

代码编写参考

参考:FPGA学习-UART接收单字节

模块代码

Rx 部分

// UART 接收模块 可选波特率 仅支持八位数据+一位停止位
module UART_Rx #(
    parameter CLK_FREQ_MHZ = 27,
    parameter BAUD_RATE = 9600
)(
    input clk,
    input rst_n,
    input rx_data_ready,  // 准备好接受串口数据
    input rx_pin,

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

    localparam BAUD_CNT_THRESHOLD = CLK_FREQ_MHZ * 1000000 / BAUD_RATE / 16 - 1;  // 波特率计数最大值
    localparam BAUD_CLK_CNT_THRESHOLD = (8 + 2) * 16 - 1;  // 波特率时钟计数最大值

    reg rx_data_r0, rx_data_r1;
    reg rx_data_t0, rx_data_t1;
    reg rx_state;
    reg rx_done;
    reg [$clog2(BAUD_CNT_THRESHOLD)-1:0] baud_rate_cnt;  // 波特率计数器
    reg baud_rate_clk;
    reg [$clog2(BAUD_CLK_CNT_THRESHOLD)-1:0] baud_rate_clk_cnt;
    reg [2:0] start_bit, stop_bit;
    reg [2:0] data_byte [7:0];  // 有效数据
    reg [7:0] rx_data_r;
    reg rx_data_valid_r;

    wire rx_negedge;

    integer i;

/****输入数据同步****/
    always @(posedge clk or negedge rst_n) begin
        if (~rst_n) begin
            rx_data_r0 <= 1'b0; 
            rx_data_r1 <= 1'b0;
        end
        else begin
            rx_data_r0 <= rx_pin;
            rx_data_r1 <= rx_data_r0;
        end
    end
/******************/

/****同步后的数据暂存 边沿检测****/
    always @(posedge clk or negedge rst_n) begin
        if (~rst_n) begin
            rx_data_t0 <= 1'b0; 
            rx_data_t1 <= 1'b0;
        end
        else begin
            rx_data_t0 <= rx_data_r1;
            rx_data_t1 <= rx_data_t0;
        end
    end

    assign rx_negedge = rx_data_t1 && (~rx_data_t0);
/******************/

/****接收状态逻辑****/
    always @(posedge clk or negedge rst_n) begin
        if (~rst_n) begin
            rx_state <= 1'b0;
        end
        else begin
            if (rx_negedge) begin  // 下降沿到来
                rx_state <= 1'b1;
            end
            else if (rx_done) begin  // 接收完成
                rx_state <= 1'b0;
            end
            else if (baud_rate_clk_cnt=='d12 && start_bit[2]) begin  // 起始位采样后高电平居多
                rx_state <= 1'b0;
            end
            else begin
                rx_state <= rx_state;  // 在一次接收中rx_state保持高电平
            end
        end
    end
/******************/

/****接收结束逻辑****/
    always @(posedge clk or negedge rst_n) begin
        if (~rst_n) begin
            rx_done <= 1'b0;
        end
        else begin
            if (baud_rate_clk_cnt==BAUD_CLK_CNT_THRESHOLD) begin
                rx_done <= 1'b1;
            end
            else begin
                rx_done <= 1'b0;
            end
        end
    end
/******************/

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

/****波特率时钟生成****/
    always @(posedge clk or negedge rst_n) begin
        if (~rst_n) begin
            baud_rate_clk <= 1'b0;
        end
        else begin
            if (baud_rate_cnt=='d1) begin
                baud_rate_clk <= 1'b1;
            end
            else begin
                baud_rate_clk <= 1'b0;
            end
        end
    end
/******************/

/****波特率时钟计数****/
    always @(posedge clk or negedge rst_n) begin
        if (~rst_n) begin
            baud_rate_clk_cnt <= 'd0;
        end
        else begin
            if (~rx_done) begin
                if (baud_rate_clk) begin
                    baud_rate_clk_cnt <= baud_rate_clk_cnt + 1'd1;
                end
                else begin
                    baud_rate_clk_cnt <= baud_rate_clk_cnt;
                end
            end
            else begin
                baud_rate_clk_cnt <= 'd0;
            end
        end
    end
/******************/

/****数据采集部分****/
    always @(posedge clk or negedge rst_n) begin
        if (~rst_n) begin
            start_bit <= 3'd0;
            stop_bit <= 3'd0;

            for (i = 0; i<8; i=i+1) begin
                data_byte[i] <= 3'd0;
            end
        end
        else begin
            if (baud_rate_clk) begin
                case (baud_rate_clk_cnt)
                    0 : begin  // 重置
                        start_bit <= 3'd0;
                        stop_bit <= 3'd0;

                        for (i = 0; i<8; i=i+1) begin
                            data_byte[i] <= 3'd0;
                        end
                    end
                    6,7,8,9,10,11 : begin  // 起始位采集
                        start_bit <= start_bit+rx_data_r1;
                    end
                    22,23,24,25,26,27 : begin  // 第一位数据
                        data_byte[0] <= data_byte[0]+rx_data_r1;
                    end
                    38,39,40,41,42,43 : begin  // 第二位数据
                        data_byte[1] <= data_byte[1]+rx_data_r1;
                    end
                    54,55,56,57,58,59 : begin  // 第三位数据
                        data_byte[2] <= data_byte[2]+rx_data_r1;
                    end
                    70,71,72,73,74,75 : begin  // 第四位数据
                        data_byte[3] <= data_byte[3]+rx_data_r1;
                    end
                    86,87,88,89,90,91 : begin  // 第五位数据
                        data_byte[4] <= data_byte[4]+rx_data_r1;
                    end
                    102,103,104,105,106,107 : begin  // 第六位数据
                        data_byte[5] <= data_byte[5]+rx_data_r1;
                    end
                    118,119,120,121,122,123 : begin  // 第七位数据
                        data_byte[6] <= data_byte[6]+rx_data_r1;
                    end
                    134,135,136,137,138,139 : begin  // 第八位数据
                        data_byte[7] <= data_byte[7]+rx_data_r1;
                    end
                    150,151,152,153,154,155 : begin  // 停止位
                        stop_bit <= stop_bit+rx_data_r1;
                    end
                    default : begin
                        start_bit <= start_bit;
                        stop_bit <= stop_bit;

                        for (i = 0; i<8; i=i+1) begin
                            data_byte[i] <= data_byte[i];
                        end
                    end
                endcase  
            end
            else begin
                start_bit <= start_bit;
                stop_bit <= stop_bit;

                for (i = 0; i<8; i=i+1) begin
                    data_byte[i] <= data_byte[i];
                end
            end
        end
    end
/******************/

/****送出接收到的数据****/
    always @(posedge clk or negedge rst_n) begin
        if (~rst_n) begin
            rx_data_r <= 'd0;
            rx_data_valid_r <= 1'b0;
        end
        else begin
            if (rx_done) begin
                if (rx_data_ready) begin
                    for (i = 0; i<8; i=i+1) begin
                        rx_data_r[i] <= data_byte[i][2];
                    end
                end
                else begin
                    rx_data_r <= rx_data_r;
                end
                rx_data_valid_r <= 1'b1;
            end
            else begin
                rx_data_r <= 'd0;
                rx_data_valid_r <= 1'b0;
            end
        end
    end

    assign rx_data = rx_data_r;
    assign rx_data_valid = rx_data_valid_r;
/******************/

endmodule

Tx 部分

// UART 发送模块 可选波特率 仅支持八位数据+一位停止位
module UART_Tx #(
    parameter CLK_FREQ_MHZ = 27,
    parameter BAUD_RATE = 9600
)(
    input clk,
    input rst_n,
    input [7:0] tx_data,  // 准备发送的数据
    input tx_data_valid,  // 发送的数据有效

    output tx_data_ready,  // 发送模块已准备好发送数据
    output tx_pin  // 发送的串口数据 tx_data_ready和tx_data_valid都为高时数据被发送
);

    localparam BAUD_CLK_CNT_THRESHOLD = (8 + 2) + 1;  // 波特率时钟计数最大值
    localparam BAUD_CNT_THRESHOLD = CLK_FREQ_MHZ * 1000000 / BAUD_RATE - 1;  // 波特率计数最大值

    reg [7:0] tx_data_r;
    reg tx_state;
    reg tx_done;
    reg [$clog2(BAUD_CNT_THRESHOLD)-1:0] baud_rate_cnt;  // 波特率计数器
    reg baud_rate_clk;
    reg [$clog2(BAUD_CLK_CNT_THRESHOLD)-1:0] baud_rate_clk_cnt;
    reg tx_pin_r;

/****发送数据暂存****/
    always @(posedge clk or negedge rst_n) begin
        if (~rst_n) begin
            tx_data_r <= 'd0;
        end
        else begin
            if (tx_data_valid && tx_data_ready) begin
                tx_data_r <= tx_data;
            end
            else begin
                tx_data_r <= tx_data_r;
            end
        end
    end
/******************/

/****发送状态逻辑****/
    always @(posedge clk or negedge rst_n) begin
        if (~rst_n) begin
            tx_state <= 1'b0;
        end
        else begin
            if (tx_data_valid && tx_data_ready) begin
                tx_state <= 1'b1;
            end
            else if (tx_done) begin
                tx_state <= 1'b0;
            end
            else begin
                tx_state <= tx_state;
            end
        end
    end
/******************/

/****发送结束逻辑****/
    always @(posedge clk or negedge rst_n) begin
        if (~rst_n) begin
            tx_done <= 1'b0;
        end
        else begin
            if (baud_rate_clk_cnt==BAUD_CLK_CNT_THRESHOLD) begin
                tx_done <= 1'b1;
            end
            else begin
                tx_done <= 1'b0;
            end
        end
    end
/******************/

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

/****波特率时钟生成****/
    always @(posedge clk or negedge rst_n) begin
        if (~rst_n) begin
            baud_rate_clk <= 1'b0;
        end
        else begin
            if (baud_rate_cnt=='d1) begin
                baud_rate_clk <= 1'b1;
            end
            else begin
                baud_rate_clk <= 1'b0;
            end
        end
    end
/******************/

/****波特率时钟计数****/
    always @(posedge clk or negedge rst_n) begin
        if (~rst_n) begin
            baud_rate_clk_cnt <= 'd0;
        end
        else begin
            if (~tx_done) begin
                if (baud_rate_clk) begin
                    baud_rate_clk_cnt <= baud_rate_clk_cnt + 1'd1;
                end
                else begin
                    baud_rate_clk_cnt <= baud_rate_clk_cnt;
                end
            end
            else begin
                baud_rate_clk_cnt <= 'd0;
            end
        end
    end
/******************/

/****发送数据部分****/
    always @(posedge clk or negedge rst_n) begin
        if (~rst_n) begin
            tx_pin_r <= 1'b0;
        end
        else begin
            case(baud_rate_clk_cnt)
                0 : begin tx_pin_r <= 1'b1; end
                1 : begin tx_pin_r <= 1'b0; end  // 起始位
                2 : begin tx_pin_r <= tx_data_r[0]; end  // 第一位数据
                3 : begin tx_pin_r <= tx_data_r[1]; end  // 第二位数据
                4 : begin tx_pin_r <= tx_data_r[2]; end  // 第三位数据
                5 : begin tx_pin_r <= tx_data_r[3]; end  // 第四位数据
                6 : begin tx_pin_r <= tx_data_r[4]; end  // 第五位数据
                7 : begin tx_pin_r <= tx_data_r[5]; end  // 第六位数据
                8 : begin tx_pin_r <= tx_data_r[6]; end  // 第七位数据
                9 : begin tx_pin_r <= tx_data_r[7]; end  // 第八位数据
                10 : begin tx_pin_r <= 1'b1; end  // 停止位
                default : begin tx_pin_r <= 1'b1; end
            endcase
            end
        end

        assign tx_data_ready = ~tx_state;
        assign tx_pin = tx_pin_r;
/******************/

endmodule

Module-sim 测试代码

`timescale 1ns/1ns

module top_module_tb;

    reg clk;
    reg rst_n;

    reg rx_data_ready;
    wire [7:0] rx_data;
    wire rx_data_valid;

    reg [7:0] tx_data;
    reg tx_data_valid;
    wire tx_data_ready;

    wire uart_pin;

    always begin
        clk = 1'b0; # 18;  // 27M Hz
        clk = 1'b1; # 19;
    end

    initial begin
        rst_n = 1'b0;
        # 20 rst_n = 1'b1;
    end

    // initial begin
    //     # 200000;
    //     $stop;
    // end

    integer i;

    initial begin
        rx_data_ready = 1'b1;
        tx_data = 8'h11;
        tx_data_valid = 1'b1;
    end

    UART_Rx #(
        .CLK_FREQ_MHZ(27),
        .BAUD_RATE(9600)
    ) u_uart_rx (
        .clk(clk),
        .rst_n(rst_n),
        .rx_data_ready(rx_data_ready),  // 准备好接受串口数据
        .rx_pin(uart_pin),

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

    UART_Tx #(
        .CLK_FREQ_MHZ(27),
        .BAUD_RATE(9600)
    ) u_uart_tx (
        .clk(clk),
        .rst_n(rst_n),
        .tx_data(tx_data),  // 准备发送的数据
        .tx_data_valid(tx_data_valid),  // 发送的数据有效

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

endmodule

Gowin FPGA 烧录测试代码

// 收到数据后发送回去

module UART_test #(
    parameter CLK_FREQ_MHZ = 27,
    parameter BAUD_RATE = 9600
) (
    input sys_clk,
    input rst_n,
    input rx_pin,  // UART 接收

    output tx_pin  // UART 发送
);
    reg [7:0] rx_data_r;  // 待发送数据
    reg [7:0] tx_data_r;  // 接收到的数据
    reg tx_data_valid;  // 待发送数据有效

    wire rx_data_valid;  // 接收数据有效
    wire tx_data_ready;  // 准备好发送数据
    wire [7:0] rx_data;  // 接收到的数据

    always @(posedge sys_clk or negedge rst_n) begin
        if (~rst_n) begin
            rx_data_r <= 8'd0;
            tx_data_r <= 8'd0;
            tx_data_valid <= 1'b0;
        end
        else begin
            if (rx_data_valid) begin  // 接收到数据
                rx_data_r <= rx_data;  // 接收到的数据缓存
                if (tx_data_ready) begin  // 准备好发送数据
                    tx_data_valid <= 1'b1;
                    tx_data_r <= rx_data_r;
                end
                else begin
                    tx_data_r <= tx_data_r;
                    tx_data_valid <= 1'b0;
                end
            end
            else begin
                tx_data_valid <= 1'b0;
                rx_data_r <= rx_data_r;
            end
        end
    end

    UART_Rx #(
        .CLK_FREQ_MHZ(CLK_FREQ_MHZ),
        .BAUD_RATE(BAUD_RATE)
    ) u_uart_rx (
        .clk(sys_clk),
        .rst_n(rst_n),
        .rx_data_ready(1'b1),  // 准备好接受串口数据
        .rx_pin(rx_pin),

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

    UART_Tx #(
        .CLK_FREQ_MHZ(CLK_FREQ_MHZ),
        .BAUD_RATE(BAUD_RATE)
    ) u_uart_tx (
        .clk(sys_clk),
        .rst_n(rst_n),
        .tx_data(tx_data_r),  // 准备发送的数据
        .tx_data_valid(tx_data_valid),  // 发送的数据有效

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