[Verilog] UART RTL design 3

이 글로 UART RTL design을 마무리하겠습니다.

UART RTL design

Rx controller RTL design

Tx controller를 이해하셨다면 Rx controller는 이해하기 쉬우실 겁니다.

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

Rx controller는 Tx controller와 다르게 APB register setting으로 통신을 시작하는 것이 아니라, 외부 controller로부터 들어온 signal의 posedge signal(uart_init)로 FSM이 동작합니다.

이 module은 한 가지 수정이 필요한데요, 먼저 parity check 기능이 구현되어 있지 않습니다. DATA state일 때 input 된 signal로 parity를 계산하고 만약 PARITY state에 input 된 parity와 일치하지 않는다면 error intr를 내보내야 합니다.

마지막으로 output으로 uart_rx가 있는데요, Rx controller가 동작하고 있음을 나타내는 signal입니다. 이 signal은 어디에 사용될까요?

Rx CLK gen RTL design

Rx 용 CLK gen에는 한 가지 특징이 있습니다. 우선 RTL code를 살펴볼까요?

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

이 module은 uart_rx가 high일 때(Rx controller가 작동할 때)만 uclk를 생성합니다. 왜 그럴까요?

Rx controller는 외부의 controller에서 data를 받습니다, 그렇기 때문에 외부 controller와 baud rate가 완전히 일치하지 않을 수 있습니다. 이럴 경우, 통신을 계속 진행하면 시간이 지날수록 두 controller의 baud rate는 점점 차이가 납니다.

Simulation result
Simulation result

이렇기 때문에 baud rate의 차이를 매번 초기화시켜 주기 위해 Rx controller가 작동할 때만 uclk를 생성하고, 1-byte 통신이 끝나면 CLK gen 내부 counter가 reset 방식으로 설계했습니다. 이해가 되셨으면 좋겠네요;;;

참고로, UART Rx는 외부에서 비동기 신호(Asynchronous)를 받게 됩니다. 이런 경우 신호 처리에 주의가 필요한데요, 다음 글에서 좀 더 자세히 설명하도록 하겠습니다.

Top integration

이제 설계한 모듈을 integration 하여 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

여기서 주의할 점은, Rx controller에 들어갈 input signal은 외부에서 들어온 signal을 바로 연결한 것이 아니라 pclk로 채서 1 clk 미룬 r_uart_in을 사용했습니다.

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   )

이는 외부에서 들어온 uart_in을 이 module의 pclk에 맞춰야 하기 때문입니다(pclk sync에 맞추는 과정이 필요한 겁니다).

이렇게 UART controller RTL design을 해봤습니다.

참고: Realtek UART

유사한 게시물