[Verilog] UART RTL design 1

This time, we will design a simple communication IP, UART (Universal Asynchronous Receiver/Transmitter).

UART overview

Characteristic

Unlike I2C or SPI, UART communication is asynchronous and doesn't use a clock. What's the difference between asynchronous and synchronous communication?

Advantage

  • Since it does not use a clock, there is no need for a clock pin and no need for a slave select pin, only two pins are used to transmit and receive data.
  • Supports full-duplex communication as Tx and Rx are separated
  • It has been used for a long time, so it is not complicated, the related documentation is well organized, and many devices are supported.

Disadvantage

  • Because it is 1:1 communication, the more controllers there are, the more wires are needed.
  • The baud rate must be set correctly for communication, otherwise data loss occurs.
연결 방법
How to connect

The UART control module has Rx/Tx pins, so the signal for sending data should be connected to Tx, and the signal for receiving data should be connected to Rx.

Protocol

UART Protocol
Protocol
  • Baud rate

UART communication is an asynchronous, full-duplex communication method that doesn't use a clock signal for synchronization. Therefore, the data transmission speed (baud rate) must be determined before transmitting or receiving data. A commonly used baud rate is 38400, which equals 38.4 Kbps.

  • Start bit

Now that we've set the baud rate, how do the two modules sending and receiving data know when communication has begun? UART communication uses a "Start bit." Before communication begins, this bit remains high, and when communication begins, it's lowered to low, allowing the receiving module to know that communication has begun.

  • Stop bit

If the high state remains after exchanging 8-bit data, it means the communication is finished. This is called a stop bit and can be set to 1 or 2 bits.

  • Parity bit
Tera term setting
Tera term setting

The photo above shows a program called Tera Term, which facilitates serial communication between a PC and a user. Before communicating, the settings are as shown above. The baud rate is set to 38,400, 1 packet is 8 bits, and 1 stop bit is set.

And you also need to set a parity bit. The parity bit is an optional bit that the user can choose to use or not use. Its purpose is to check for errors during communication. So why do errors occur?

For controllers used in SoC, the baud rate is set through internal register settings.

Baud rate 설정 예시
Baud rate setting example

This UART controller module uses a 10MHz clock and a desired baud rate of 38400. The register setting value is determined by dividing the controller's clock frequency by the desired baud rate. The result is rounded and input to the module, but this process inevitably introduces errors.This can lead to communication errors.

To summarize, UART is an asynchronous communication that does not use a clock, so it does not require a clock pin, but errors can occur, so the user must optionally set the parity bit.

Specification

First, let me make it clear: the UART I'm designing differs from the IP actually used. I'll explainthis in detail

Block diagram

글 설명 이미지, UART block diagram
Block diagram

There is an APB interface and a CLK gen (clock generator) is attached to the Tx/Rx controller. The baud rate is set in the CLK gen.

Register map

The register map is set as follows:

  • CTRL register (address: 0x0)
Signal nameR/WDefault valueBit
Reserved31:21
Pre_scaleRW0x020:18
Bit_multRW0x017:13
Bit_divRW0x012:4
Reserved3
Two_stop_enRW0x02
Parity_enRW0x01
EnableRW0x00
CTRL register
  • Status register (address: 0x4)
Signal nameR/WDefault valueBit
Reserved31:2
Rx_completeW1C0x01
Tx_completeW1C0x00
Status register

Actually, you should add a parity error to the status. When communicating via UART with parity, the rx controller should perform a parity check, and if the received parity does not match the calculated parity, an error should be generated and the CPU should be notified. But I left it out because it was annoying;;;

It would be nice to try adding a parity check function directly to the RX controller!!

  • Tx data register (address: 0x8)
Signal nameR/WDefault valueBit
Reserved31:8
Tx_dataRW0x07:0
Tx data register
  • Rx data register (address: 0xC)
Signal nameR/WDefault valueBit
Reserved31:8
Rx_dataRO0x07:0
Rx data register

UART RTL design

APB interface RTL design

Let's design the APB interface based on the register map above.

module uart_apb (
     //APB Interface
     input  wire        pclk
    ,input  wire        presetn
    ,input  wire        penable
    ,input  wire        psel
    ,input  wire [ 5:2] paddr
    ,input  wire        pwrite
    ,input  wire [31:0] pwdata
    ,output wire [31:0] prdata
 
     //Baud rate
    ,output wire [ 8:0] bit_div
    ,output wire [ 2:0] pre_scale
    ,output wire [ 4:0] bit_mult

     //Register Interface
    ,input  wire        tx_complete
    ,input  wire        rx_complete
    ,output wire        tx_clr
    ,output wire        rx_clr
    ,output wire        uart_en
    ,output wire        parity_en
    ,output wire        stop_en
    ,input  wire [ 7:0] uart_rxdata
    ,output wire [ 7:0] uart_txdata
);

    //===================================================================
    // Local Parameters
    //===================================================================
    localparam INVALID_DATA = 32'hDEAD_DEAD;
    
    //===================================================================
    // Internal Signals
    //===================================================================
    wire        we = psel and ~penable and   pwrite;
    wire        re = psel and ~penable and  ~pwrite;
    reg  [31:0] RD ;
      
    //===================================================================
    // Address Decode 
    //===================================================================
    wire        ctrl    = (paddr[ 5:2] == 4'h0);
    wire        st      = (paddr[ 5:2] == 4'h1);
    wire        tx_data = (paddr[ 5:2] == 4'h2);
    wire        rx_data = (paddr[ 5:2] == 4'h3);
     
    //===================================================================
    // Write Enable
    //===================================================================
    wire        we_ctrl = we and ctrl;
    wire        we_data = we and tx_data;
    wire        we_st   = we and st;
    
    //===================================================================
    // Register Files 
    //===================================================================
    reg         r_enable;
    always @(posedge pclk or negedge presetn) begin
        if(!presetn)      r_enable <= 1'b0;
        else if (tx_clr)  r_enable <= 1'b0;
        else if (we_ctrl) r_enable <= pwdata[0];
        else              r_enable <= r_enable;
    end

    reg         r_parity;
    always @(posedge pclk or negedge presetn) begin
        if(!presetn)      r_parity <= 1'b0;
        else if (we_ctrl) r_parity <= pwdata[1];
        else              r_parity <= r_parity;
    end

    reg         r_stop;
    always @(posedge pclk or negedge presetn) begin
        if(!presetn)      r_stop <= 1'b0;
        else if (we_ctrl) r_stop <= pwdata[2];
        else              r_stop <= r_stop;
    end

    reg  [16:0] r_bitcfg;
    always @(posedge pclk or negedge presetn) begin
        if (!presetn)     r_bitcfg <= 17'h0;
        else if (we_ctrl) r_bitcfg <= pwdata[21:4];
        else              r_bitcfg <= r_bitcfg;
    end

    reg  [ 7:0] r_txdata;
    always @(posedge pclk or negedge presetn) begin
        if(!presetn)      r_txdata <= 8'h0;
        else if (we_data) r_txdata <= pwdata[7:0];
        else              r_txdata <= r_txdata;
    end
 
    //===================================================================
    // Read Decode
    //===================================================================
    always @(*) begin
        if(re) begin
            case (paddr[5:2])
                4'h0 : RD = {11'h0, r_bitcfg, 1'b0, r_stop, r_parity, r_enable};
                4'h1 : RD = {30'b0, rx_complete, tx_complete};
                4'h2 : RD = {24'b0, r_txdata};
                4'h3 : RD = {24'b0, uart_rxdata};
                default : RD = INVALID_DATA;
            endcase
        end
        else RD = INVALID_DATA;
    end
    
    assign  prdata = RD;
    
    //===================================================================
    // Output Assign
    //===================================================================
    assign bit_div     = r_bitcfg[ 8: 0];
    assign bit_mult    = r_bitcfg[13: 9];
    assign pre_scale   = r_bitcfg[16:14];

    assign parity_en   = r_parity;
    assign stop_en     = r_stop;
    assign uart_en     = r_enable;
    assign uart_txdata = r_txdata;

    assign tx_clr      = we_st and pwdata[0];
    assign rx_clr      = we_st and pwdata[1];

endmodule

I will explain further in the next article.

References: Realtek UART

Similar Posts