[Verilog] UART RTL design 1

이번에는 간단한 통신 IP인 UART(Universal Asynchronous Receiver/Transmitter)를 설계해보겠습니다.

UART overview

특징

UART 통신은 I2C나 SPI와 달리 클럭(clock)를 사용하지 않는 비동기 통신입니다. 비동기 통신과 동기 통신은 어떤 차이가 있을까요??

장점

  • 클럭을 쓰지 않기 때문에 클럭 핀이 필요 없고 slave select 핀도 필요 없음, 데이터 송수신하는 2개의 핀만 사용
  • Tx, Rx가 나뉘어져 있어서 전이중 통신 지원
  • 오랫동안 사용되었기에 복잡하지 않고 관련 문서도 잘 정리되어 있음, 많은 장치가 지원

단점

  • 1:1 통신이기 때문에 controller가 많아질수록 wire가 많이 필요함
  • 통신을 위해 전송속도(baud rate)를 잘 맞춰줘야 하고 틀리면 데이터 손실이 일어남
연결 방법
연결 방법

UART control 모듈에는 Rx/Tx 핀이 있어서 데이터가 나가는 신호는 Tx, 데이터를 수신하는 신호는 Rx에 연결하면 됩니다.

Protocol

UART Protocol
Protocol
  • Baud rate

UART 통신은 비동기 방식의 전이중 통신으로 동기 신호인 클럭을 사용하지 않기 때문에 데이터 송/수신 전에 데이터 전송 속도(baud rate)를 정해야 합니다. 많이 사용하는 baud rate는 38400인데요, 이는 38.4 Kbps를 의미합니다.

  • Start bit

Baud rate를 정했지만, 데이터를 주고받는 두 모듈은 통신의 시작을 어떻게 알 수 있을까요?? UART 통신에는 Start bit가 존재합니다. 통신하기 전에는 High 상태를 유지하다가 통신이 시작될 때, Low로 내려주면 데이터를 받는 쪽에서 통신이 시작됨을 알 수 있게 됩니다.

  • Stop bit

8-bit의 데이터를 주고받은 뒤 High 상태를 유지하면 통신이 끝났다는 것을 의미합니다. 이는 Stop bit라고 하는데 1~2-bit로 설정할 수 있습니다.

  • Parity bit
Tera term setting
Tera term setting

위의 사진은 PC와 유저가 시리얼 통신을 할 수 있게 도와주는 Tera term이라는 프로그램입니다. 통신하기 전에 위와 같은 설정을 하게 되는데요, baud rate는 38400, 1 패킷은 8 -bit, 1 stop bit로 설정했습니다.

그리고 parity bit도 설정해야 합니다. Parity bit는 유저가 옵션으로 설정하는 비트입니다, 안 쓸 수도 있고 쓸 수도 있는 거죠. 역할은 통신 중 오류가 있는지 검사하는 것입니다. 그렇다면 왜 오류가 발생하는 걸까요??

SoC에서 사용되는 controller의 경우 내부 레지스터 세팅을 통해 baud rate를 설정하게 됩니다.

Baud rate 설정 예시
Baud rate 설정 예시

이 UART controller 모듈의 사용되는 클럭이 10MHz이고 원하는 baud rate는 38400입니다. Controller의 사용되는 클럭의 주파수를 원하는 baud rate로 나눠 register 세팅 값을 정합니다. 계산 결과를 반올림해서 모듈에 입력하는데 여기서 어쩔 수 없이 오차가 발생하게 됩니다. 이에 따라 통신 중 오류가 발생할 수 있습니다.

정리하자면 UART는 클럭을 사용하지 않는 비동기통신이어서 클럭 핀이 필요하지 않지만, 오차가 발생할 수 있어서 유저가 parity bit를 optional 하게 설정해야 합니다.

Specification

먼저 분명히 할 것은, 제가 설계하는 UART는 실제 사용하는 IP와는 차이점이 있습니다. 이에 대해서는 차근차근 설명하도록 하겠습니다.

Block diagram

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

APB interface가 있고 Tx/Rx controller에 CLK gen(clock generator)가 붙어있습니다. CLK gen에서 baud rate를 설정해 줍니다.

Register map

Register map은 다음과 같이 설정했습니다.

  • 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

사실, status에 parity error를 추가해야 합니다. Parity가 있는 UART 통신일 때, rx controller에서 parity check를 하고 수신된 parity와 계산된 parity가 일치하지 않으면 error를 발생시키고 CPU에 이를 알려야 합니다. 근데 귀찮아서 뺐습니다;;;

직접 rx controller에 parity check 기능을 넣어보는 것도 좋겠네요!!

  • 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

위의 register map을 기반으로 APB interface를 설계해 봅시다.

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 & ~penable &   pwrite;
    wire        re = psel & ~penable &  ~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 & ctrl;
    wire        we_data = we & tx_data;
    wire        we_st   = we & 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 & pwdata[0];
    assign rx_clr      = we_st & pwdata[1];

endmodule

다음 글에서 이어서 설명하겠습니다.

참고: Realtek UART

Similar Posts