[Verilog] UART RTL design 3

This article will conclude the UART RTL design.

UART RTL design

Rx controller RTL design

If you understand the Tx controller, the Rx controller will be easy to understand.

module uart_rx_ctrl (
     input  wire        pclk
    ,input  wire        presetn

    ,input  wire        uclk_en

    ,input  wire        uart_in
    ,input  wire        complete_clr
    ,input  wire        parity_en
    ,input  wire        stop_en

    ,output wire [ 7:0] uart_data
    ,output wire        complete
    ,output wire        uart_rx
);

    //===================================================================
    // Local Parameters
    //===================================================================
    localparam IDLE        = 3'h0,
               START       = 3'h1,
               DATA        = 3'h2,
               PARITY      = 3'h3,
               STOP        = 3'h4,
               TRANSFINISH = 3'h5;

    reg  [ 2:0] r_cur_st;
    reg  [ 2:0] r_nxt_st;
    reg  [ 7:0] r_bitcnt;

    reg         r_complete;
    reg  [ 7:0] r_shift;

    wire        data_end;
    wire        uart_init;

    //rx_init
    reg r_uart_in;
    always @(posedge pclk or negedge presetn) begin
        if (!presetn)          r_uart_in <= 1'h1;
        else                   r_uart_in <= uart_in;
    end

    //cur_st
    always @(posedge pclk or negedge presetn) begin
        if (!presetn)          r_cur_st <= IDLE;
        else if (complete_clr) r_cur_st <= IDLE;
        else                   r_cur_st <= r_nxt_st;
    end

    //FSM
    always @(*) begin
        case (r_cur_st)
            IDLE        : begin
                if (uart_init) begin
                     r_nxt_st <= START;
                end
                else r_nxt_st <= IDLE;
            end
            START       : begin
                if (uclk_en) begin
                     r_nxt_st <= DATA;
                end
                else r_nxt_st <= START;
            end
            DATA        : begin
                if (data_end & parity_en) begin
                     r_nxt_st <= PARITY;
                end
                else if (data_end & !parity_en & stop_en) begin
                     r_nxt_st <= STOP;
                end
                else if (data_end & !parity_en & !stop_en) begin
                     r_nxt_st <= TRANSFINISH;
                end
                else r_nxt_st <= DATA;
            end
            PARITY      : begin
                if (stop_en & uclk_en) begin
                     r_nxt_st <= STOP;
                end
                else if (uclk_en) begin
                     r_nxt_st <= TRANSFINISH;
                end
                else r_nxt_st <= PARITY;
            end
            STOP        : begin
                if (uclk_en) begin
                     r_nxt_st <= TRANSFINISH;
                end
                else r_nxt_st <= STOP;
            end
            TRANSFINISH : begin
                if (complete_clr) begin
                     r_nxt_st <= IDLE;
                end
                else r_nxt_st <= TRANSFINISH;
            end
            default     : begin
                r_nxt_st <= IDLE;
            end
        endcase
    end

    //BITCNT
    wire data_st;
    assign data_st = (r_cur_st == DATA);
    always @(posedge pclk or negedge presetn) begin
        if (!presetn)               r_bitcnt <= 8'h0;
        else if (complete_clr)      r_bitcnt <= 8'h0;
        else if (data_st & uclk_en) r_bitcnt <= (r_bitcnt + 1);
        else                        r_bitcnt <= r_bitcnt;
    end

    //UART rx
    always @(posedge pclk or negedge presetn) begin
        if (!presetn)               r_shift <= 8'h0;
        else if (data_st) begin
            if (uclk_en)            r_shift <= {uart_in,r_shift[7:1]};
            else                    r_shift <= r_shift;
        end
        else                        r_shift <= r_shift;
    end

    //complete intr
    always @(posedge pclk or negedge presetn) begin
        if (!presetn)               r_complete <= 1'b0;
        else if (complete_clr)      r_complete <= 1'b0;
        else if ((r_cur_st == TRANSFINISH) & uclk_en) begin
                                    r_complete <= 1'b1;
        end
        else                        r_complete <= r_complete;
    end

    assign data_end  = (r_bitcnt == 8'h8);
    assign complete  = r_complete;
    assign uart_data = r_shift;
    assign uart_init = ~uart_in & r_uart_in;
    assign uart_rx   = (r_cur_st != IDLE);

endmodule

Unlike the Tx controller, the Rx controller does not initiate communication with the APB register setting, but rather the FSM operates with the posedge signal (uart_init) of the signal coming from the external controller.

This module needs one modification. First, it doesn't implement a parity check function. In the DATA state, it should calculate parity based on the input signal, and if the parity doesn't match the input parity in the PARITY state, it should output an error intr.

Finally, there's the uart_rx output, which is a signal indicating that the Rx controller is operating. What is this signal used for?

Rx CLK gen RTL design

There is one thing that is special about CLK gen for Rx. Let's take a look at the RTL code first.

module uart_rx_clkgen (
     input  wire        presetn
    ,input  wire        pclk

    ,input  wire        uart_rx //From Rx controller
    ,input  wire [ 8:0] bit_div
    ,input  wire [ 2:0] pre_scale
    ,input  wire [ 4:0] bit_mult

    ,output wire        rx_uclk
);

    reg         r_uclk;
    reg  [ 8:0] r_prescale;
    reg  [13:0] r_divisor_1;
    reg  [22:0] r_divisor_2;
    reg  [21:0] r_dividercnt;

    always @(negedge presetn or posedge pclk) begin
        if (!presetn)    r_prescale  <= 9'd0;
        else begin
            case (pre_scale)
                3'd0   : r_prescale <= 9'd2  ;
                3'd1   : r_prescale <= 9'd4  ;
                3'd2   : r_prescale <= 9'd8  ;
                3'd3   : r_prescale <= 9'd16 ;
                3'd4   : r_prescale <= 9'd32 ;
                3'd5   : r_prescale <= 9'd64 ;
                3'd6   : r_prescale <= 9'd128;
                3'd7   : r_prescale <= 9'd256;
                default: r_prescale <= r_prescale;
            endcase
        end
    end

    //r_divisor_1  =  { 2^(pre_scale+1) x (bit_mult + 1) } ]
    always @(negedge presetn or posedge pclk) begin
        if (!presetn) r_divisor_1 <= 14'd0; 
        else          r_divisor_1 <= r_prescale * (bit_mult + 'b1);
    end

    //r_divisor_2  =  [ (bit_div) x  { 2^(pre_scale+1) x (bit_mult + 1) } ]
    always @(negedge presetn or posedge pclk) begin
        if (!presetn) r_divisor_2 <= 23'd0; 
        else          r_divisor_2 <= r_divisor_1 * bit_div;
    end

    //half cycle of serial clock = r_divisor_2 / 2
    always @(negedge presetn or posedge pclk) begin
        if (!presetn)                          r_dividercnt <= 22'd0; 
        else if (uart_rx & (r_dividercnt !=  r_divisor_2)) 
		                               r_dividercnt <= (r_dividercnt + 'b1);
        else                                   r_dividercnt <= 22'd0; 
    end

    wire [22:0] r_divisor_2_half;
    assign      r_divisor_2_half = r_divisor_2 >> 1;

    //serial clock generation
    always @(negedge presetn or posedge pclk) begin
        if (!presetn)                             r_uclk <= 'b0;
        else if (r_dividercnt < r_divisor_2_half) r_uclk <= 'b0;
        else                                      r_uclk <= 'b1;
    end

    assign rx_uclk = r_uclk;

endmodule

This module only generates uclk when uart_rx is high (when the Rx controller is running). Why is that?

The Rx controller receives data from an external controller, so its baud rate may not perfectly match the external controller's. In this case, as communication continues, the baud rates of the two controllers will gradually diverge over time.

Simulation result
Simulation result

That's why I designed it so that uclk is generated only when the Rx controller is operating to reset the baud rate difference each time, and the internal counter of the CLK gen is reset when 1-byte communication is finished. I hope you understand;;;

Note that UART Rx receives asynchronous signals from an external source. In this case, careful signal processing is required, which I'll explain in more detail in the next article.

Top integration

Now, let's integrate the designed modules to complete the IP.

module uart (
     input  wire        presetn
    ,input  wire        pclk
    ,input  wire        pwrite
    ,input  wire        psel
    ,input  wire        penable
    ,input  wire [ 5:2] paddr
    ,input  wire [31:0] pwdata

    ,output wire [31:0] prdata
    ,output wire        intr

    ,output wire        uart_out
    ,input  wire        uart_in
);

    wire        tx_uclk;
    wire        tx_uclk_en;
    wire        rx_uclk;
    wire        rx_uclk_en;

    //Register signals
    wire [ 8:0] bit_div;
    wire [ 2:0] pre_scale;
    wire [ 4:0] bit_mult;

    wire        uart_rx;
    wire        tx_complete;
    wire        rx_complete;
    wire        tx_clr;
    wire        rx_clr;
    wire        parity_en;
    wire        stop_en;
    wire        uart_en;
    wire [ 7:0] uart_txdata;
    wire [ 7:0] uart_rxdata;

    reg         r_uart_in;
    always @(negedge presetn or posedge pclk ) begin
        if (!presetn) begin 
            r_uart_in <= 1'b1;
        end
        else begin
            r_uart_in <= uart_in;
        end
    end

    reg         r_tx_uclk;
    always @(negedge presetn or posedge pclk ) begin
        if (!presetn) begin 
            r_tx_uclk <= 1'b0;
        end
        else begin
            r_tx_uclk <= tx_uclk;
        end
    end

    reg         r_rx_uclk;
    always @(negedge presetn or posedge pclk ) begin
        if (!presetn) begin 
            r_rx_uclk <= 1'b0;
        end
        else begin
            r_rx_uclk <= rx_uclk;
        end
    end

    uart_apb u_apb (
         .pclk         (pclk        )
        ,.presetn      (presetn     )
        ,.penable      (penable     )
        ,.psel         (psel        )
        ,.paddr        (paddr[5:2]  )
        ,.pwrite       (pwrite      )
        ,.pwdata       (pwdata      )
        ,.prdata       (prdata      )

        ,.bit_div      (bit_div     )
        ,.pre_scale    (pre_scale   )
        ,.bit_mult     (bit_mult    )

        ,.tx_complete  (tx_complete )
        ,.rx_complete  (rx_complete )
        ,.tx_clr       (tx_clr      )
        ,.rx_clr       (rx_clr      )

        ,.uart_en      (uart_en     )
        ,.parity_en    (parity_en   )
        ,.stop_en      (stop_en     )
        ,.uart_txdata  (uart_txdata )
        ,.uart_rxdata  (uart_rxdata )
    );

    uart_tx_clkgen u_tx_clkgen (
         .pclk         (pclk        )
        ,.presetn      (presetn     )

        ,.bit_div      (bit_div     )
        ,.pre_scale    (pre_scale   )
        ,.bit_mult     (bit_mult    )

        ,.tx_uclk      (tx_uclk     )
    );

    uart_tx_ctrl u_tx_ctrl (
         .pclk         (pclk        )
        ,.presetn      (presetn     )
        ,.uclk_en      (tx_uclk_en  )

        ,.parity_en    (parity_en   )
        ,.stop_en      (stop_en     )
        ,.uart_en      (uart_en     )
        ,.uart_data    (uart_txdata )
        ,.complete_clr (tx_clr      )

        ,.complete     (tx_complete )
        ,.uart_out     (uart_out    )
    );

    uart_rx_clkgen u_rx_clkgen (
         .pclk         (pclk        )
        ,.presetn      (presetn     )

        ,.uart_rx      (uart_rx     )
        ,.bit_div      (bit_div     )
        ,.pre_scale    (pre_scale   )
        ,.bit_mult     (bit_mult    )

        ,.rx_uclk      (rx_uclk     )
    );

    uart_rx_ctrl u_rx_ctrl (
         .pclk         (pclk        )
        ,.presetn      (presetn     )
        ,.uclk_en      (rx_uclk_en  )

        ,.uart_in      (r_uart_in   )
        ,.complete_clr (rx_clr      )
        ,.parity_en    (parity_en   )
        ,.stop_en      (stop_en     )

        ,.uart_data    (uart_rxdata )
        ,.complete     (rx_complete )
        ,.uart_rx      (uart_rx     )
    );

    assign tx_uclk_en =  tx_uclk & ~r_tx_uclk;
    assign rx_uclk_en = ~rx_uclk &  r_rx_uclk;
    assign intr       = (tx_complete | rx_complete);

endmodule

What you should note here is that the input signal to the Rx controller is not directly connected to the signal coming from outside, but r_uart_in is used, which is delayed by 1 clk by filling in pclk.

module uart (

    ,input  wire        uart_in
    
);

    reg         r_uart_in;
    always @(negedge presetn or posedge pclk ) begin
        if (!presetn) begin 
            r_uart_in <= 1'b1;
        end
        else begin
            r_uart_in <= uart_in;
        end
    end

    uart_rx_ctrl u_rx_ctrl (
    
        ,.uart_in      (r_uart_in   )

This is because the uart_in coming from outside must be aligned with the pclk of this module (a process of aligning it with pclk sync is required).

I tried UART controller RTL design like this.

References: Realtek UART

Similar Posts