LOADING

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

Verilog-UART设计v2

2023/11/6 Verilog

Abstract: Verilog UART 设计第二版

代码编写思路

先说明几个重要的部分:

  • 波特率计数器:表示一个数据位的持续时间,每个时钟上升沿计数一次,最大值为 $BAUD_CNT_THRESHOLD=CLK_FREQ_MHZ * 1000000 / BAUD_RATE - 1$,即时钟除以波特率再减一
  • 波特率采样时钟:表示对每个数据位的采样时间,高电平持续时间为 $BAUD_CNT_THRESHOLD/2-3$ 到 $BAUD_CNT_THRESHOLD/2+3$,即持续六个时钟,采样六次
  • 波特率时钟计数器:表示获取的数据位数(包括起始位和停止位),最大值为 $BAUD_CLK_CNT_THRESHOLD = (DATA_WIDTH + 2)$,即有效数据位加二

下面简述代码思路。

接收模块

当检测到起始条件(下降沿)后,波特率计数器开始计数,当计数值达到 $BAUD_CNT_THRESHOLD/2-3$ 和 $BAUD_CNT_THRESHOLD/2+3$ 之间时,对输入进行六次采样,采样结束后,即波特率计数器数值达到 $BAUD_CNT_THRESHOLD/2+4$时,若高电平数目大于三,则保存采样结果为一,否则为零。采样的方法很简单,定义一个三位宽的寄存器,每采样一次寄存器加上本次的采样结果,最后取寄存器的最高位为结果。之后,在波特率计数器数值达到 $BAUD_CNT_THRESHOLD/2+5$ 时采样寄存器清零,在波特率计数器数值达到 $BAUD_CNT_THRESHOLD/2+6$ 时采样波特率时钟计数器加一。当波特率时钟计数器达到最大值时视为接收结束。可以看出,对于每个数据帧,接收模块实际上只检测了大约半个停止位,这样可以保证不漏采下一个数据的起始位。

发送模块

发送模块比接收模块要简单一些。读入要发送的数据后生成一个完整的数据帧,包括起始位和停止位,为了保证连续发送数据时起始位不混乱,实际上完整的数据帧为 {1,1,[有效数据],0}。当波特率计数器达到一时,将数据帧的最低位送出,最高位补零,从结果上看就是右移操作。与接收模块类似,当波特率时钟计数器达到最大值时视为发送接收,不过这里的最大值为 $BAUD_CLK_CNT_THRESHOLD = (DATA_WIDTH + 2) + 1$,和数据帧的位宽对应。

模块代码

Rx 部分

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

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

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

    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_sample_clk;  // 采样时钟 为高时采样
    reg [$clog2(BAUD_CLK_CNT_THRESHOLD)-1:0] baud_rate_clk_cnt;  // 波特率时钟计数
    reg [2:0] rx_sample_bit;  // 一位采样数据 六次采样
    reg [BAUD_CLK_CNT_THRESHOLD-1:0] rx_data_frame_byte;  // 完整数据帧
    reg [DATA_WIDTH-1:0] rx_data_r;
    reg rx_data_valid_r;

    wire rx_negedge;

/****输入数据同步****/
    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=='d1 && rx_data_frame_byte[0]==1'b1) 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_sample_clk <= 1'b0;
        end
        else begin
            if (baud_rate_cnt==BAUD_CNT_THRESHOLD/2 - 3) begin  // 持续六个时钟
                baud_rate_sample_clk <= 1'b1;
            end
            else if (baud_rate_cnt==BAUD_CNT_THRESHOLD/2 + 3) begin
                baud_rate_sample_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_state) begin
                if (baud_rate_cnt==BAUD_CNT_THRESHOLD/2 + 6) 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
            rx_sample_bit <= 3'd0;
        end
        else begin
            if (baud_rate_sample_clk) begin  // 采样时钟为高
                rx_sample_bit <= rx_sample_bit+rx_data_r1;
            end
            else begin
                if (baud_rate_cnt==BAUD_CNT_THRESHOLD/2 + 5) begin  // 采样结束两个时钟后清零
                    rx_sample_bit <= 3'b0;
                end
                else begin
                    rx_sample_bit <= rx_sample_bit;
                end
            end
        end
    end

    always @(posedge clk or negedge rst_n) begin
        if (~rst_n) begin
            rx_data_frame_byte <= 'd0;
        end
        else begin
            if (rx_state) begin
                if (baud_rate_cnt==BAUD_CNT_THRESHOLD/2 + 4) begin  // 采样结束一个时钟后移位寄存采样数据
                    rx_data_frame_byte <= {rx_sample_bit[2], rx_data_frame_byte[BAUD_CLK_CNT_THRESHOLD-1:1]};
                end
                else begin
                    rx_data_frame_byte <= rx_data_frame_byte;
                end
            end
            else begin
                rx_data_frame_byte <= 'd0;
            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
                    rx_data_r <= rx_data_frame_byte[BAUD_CLK_CNT_THRESHOLD-2:1];
                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,
    parameter DATA_WIDTH = 8
) (
    input clk,
    input rst_n,
    input [DATA_WIDTH-1: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 = (DATA_WIDTH + 2) + 1;  // 波特率时钟计数最大值 数据位+起始位(两位)+停止位(一位)
    localparam BAUD_CNT_THRESHOLD = CLK_FREQ_MHZ * 1000000 / BAUD_RATE - 1;  // 波特率计数最大值

    reg [BAUD_CLK_CNT_THRESHOLD-1:0] tx_data_frame;  // 准备发送的完整数据帧
    reg tx_state;  // 发送状态
    reg tx_done;  // 发送完成
    reg [$clog2(BAUD_CNT_THRESHOLD)-1:0] baud_rate_cnt;  // 波特率计数器
    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_frame <= 'd0;
        end
        else begin
            if (tx_data_valid && tx_data_ready) begin
                tx_data_frame <= {1'b1, 1'b1, tx_data, 1'b0};
            end
            else begin
                if (baud_rate_cnt=='d2) begin  // 低位发送后移位
                    tx_data_frame <= {1'b0, tx_data_frame[BAUD_CLK_CNT_THRESHOLD-1:1]};  // 数据帧移位
                end
                else begin
                    tx_data_frame <= tx_data_frame;
                end
            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_cnt <= 'd0;
        end
        else begin
            if (~tx_done) begin
                if (baud_rate_cnt=='d1) 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'b1;
        end
        else begin
            if (baud_rate_cnt=='d1) begin  // 数据帧移位前送出
                tx_pin_r <= tx_data_frame[0];  // 数据低位送出
            end
            else if (~tx_state) begin
                tx_pin_r <= 1'b1;
            end
            else begin
                tx_pin_r <= tx_pin_r;
            end
        end
    end

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

endmodule

Module-sim 测试代码

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 发送
);
    wire rx_data_valid;  // 接收数据有效
    wire tx_data_ready;  // 准备好发送数据
    wire [7:0] rx_data;  // 接收到的数据

    reg [7:0] rx_data_r;  // 接收的数据缓存
    reg tx_data_valid;  // 发送的数据有效

    always @(posedge sys_clk or negedge rst_n) begin
        if (~rst_n) begin
            rx_data_r <= 8'd0;
        end
        else begin
            if (rx_data_valid) begin
                rx_data_r <= rx_data;
            end
            else begin
                rx_data_r <= rx_data_r;
            end
        end
    end

    always @(posedge sys_clk or negedge rst_n) begin
        if (~rst_n) begin
            tx_data_valid <= 1'b0;
        end
        else begin
            if (rx_data_valid && ~tx_data_ready) begin  // 接收到数据但是发送忙
                tx_data_valid <= 1'b1;
            end
            else if (~rx_data_valid && tx_data_ready) begin  // 可以发送
                tx_data_valid <= 1'b0;
            end
            else if (rx_data_valid && tx_data_ready) begin  // 可以发送
                tx_data_valid <= 1'b1;
            end
            else if (~rx_data_valid && ~tx_data_ready) begin  // 可以发送
                tx_data_valid <= 1'b0;
            end
            else begin
                tx_data_valid <= tx_data_valid;
            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(rx_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

Gowin FPGA 烧录测试代码

// 收到数据后发送回去

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

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

    reg [7:0] rx_data_r;  // 接收的数据缓存
    reg tx_data_valid;  // 发送的数据有效

    always @(posedge sys_clk or negedge rst_n) begin
        if (~rst_n) begin
            rx_data_r <= 8'd0;
        end
        else begin
            if (rx_data_valid) begin
                rx_data_r <= rx_data;
            end
            else begin
                rx_data_r <= rx_data_r;
            end
        end
    end

    always @(posedge sys_clk or negedge rst_n) begin
        if (~rst_n) begin
            tx_data_valid <= 1'b0;
        end
        else begin
            if (rx_data_valid && ~tx_data_ready) begin  // 接收到数据但是发送忙
                tx_data_valid <= 1'b1;
            end
            else if (~rx_data_valid && tx_data_ready) begin  // 可以发送
                tx_data_valid <= 1'b0;
            end
            else if (rx_data_valid && tx_data_ready) begin  // 可以发送
                tx_data_valid <= 1'b1;
            end
            else if (~rx_data_valid && ~tx_data_ready) begin  // 可以发送
                tx_data_valid <= 1'b0;
            end
            else begin
                tx_data_valid <= tx_data_valid;
            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(rx_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