UART Architecture

Seungyun Lee·2026년 1월 27일

Comm_Accel(UART/SPI/I2C)

목록 보기
4/15

1. UART Core 블록 다이어그램

크게 3가지 서브 모듈로 나누어 설계합니다.

  1. Baud Rate Generator: 시스템 클럭을 분주하여 통신 속도에 맞는 타이밍 신호(tick) 생성.
  2. UART Transmitter (TX): 병렬 데이터를 직렬로 변환 (PISO).
  3. UART Receiver (RX): 직렬 데이터를 병렬로 변환 (SIPO) + Oversampling(과샘플링).
graph LR
    CLK[System Clock] --> BAUD[Baud Rate Gen]
    BAUD -- "Tx_Tick (1x)" --> TX[UART TX]
    BAUD -- "Rx_Tick (16x)" --> RX[UART RX]
    
    DATA_IN[Parallel Data In] --> TX
    TX --> TX_OUT[tx bit]
    
    RX_IN[rx bit] --> RX
    RX --> DATA_OUT[Parallel Data Out]

2. 세부 스펙 및 설계 포인트

A. 통신 프로토콜 (Frame Format)

가장 표준적인 설정을 따릅니다.

  • Start Bit: 1 bit (Logic 0)
  • Data Bits: 8 bits (LSB First)
  • Parity: None (복잡도 감소)
  • Stop Bit: 1 bit (Logic 1)

UART TOP MODULE

uart_core.sv

`timescale 1ns / 1ps

module uart_core (
    input  logic        clk,
    input  logic        reset_n,
    input  logic [15:0] divisor,
    
    // UART Physical Interface
    input  logic        rx_pin,
    output logic        tx_pin,
    
    // --- User Interface (FIFO 제어용) ---
    
    // TX FIFO Write (CPU가 데이터를 넣음)
    input  logic        tx_wr_en,
    input  logic [7:0]  tx_wr_data,
    output logic        tx_full,
    
    // RX FIFO Read (CPU가 데이터를 꺼냄)
    input  logic        rx_rd_en,
    output logic [7:0]  rx_rd_data,
    output logic        rx_empty,
    
    output logic        rx_err // 에러 상태
);

    // Internal Signals
    logic       tick;
    
    // TX Channel Signals
    logic       tx_fifo_empty;
    logic [7:0] tx_fifo_out;
    logic       tx_core_start;
    logic       tx_core_done;
    logic       tx_core_busy;
    
    // RX Channel Signals
    logic       rx_core_done;
    logic [7:0] rx_core_data;
    logic       rx_core_err;

    // -------------------------------------------------------
    // 1. Baud Rate Generator
    // -------------------------------------------------------
    baud_gen u_baud_gen (
        .clk(clk), .reset_n(reset_n), .divisor(divisor), .tick(tick)
    );

    // -------------------------------------------------------
    // 2. TX Channel (FIFO + UART TX)
    // -------------------------------------------------------
    
    // [TX FIFO]
    fifo #(.DATA_WIDTH(8), .ADDR_WIDTH(4)) u_tx_fifo (
        .clk(clk), .reset_n(reset_n),
        .wr_en(tx_wr_en), .wr_data(tx_wr_data), .full(tx_full), // CPU Side
        .rd_en(tx_core_done), .rd_data(tx_fifo_out), .empty(tx_fifo_empty) // UART Side
    );
    // 주의: 여기서는 간단하게 구현하기 위해 UART가 전송을 마치면(tx_core_done)
    // 자동으로 FIFO의 다음 데이터를 꺼내도록(rd_en) 연결했습니다.
    // 하지만, 첫 데이터를 시작시키기 위한 트리거 로직이 필요합니다.

    // [TX Control Logic]
    // FIFO에 데이터가 있고(not empty), UART가 바쁘지 않으면(not busy) 전송 시작!
    assign tx_core_start = (!tx_fifo_empty) && (!tx_core_busy) && (!tx_core_done);

    // [UART TX Core]
    uart_tx u_uart_tx (
        .clk(clk), .reset_n(reset_n), .tick(tick),
        .tx_start(tx_core_start), .tx_data(tx_fifo_out),
        .tx_pin(tx_pin), .tx_done(tx_core_done), .tx_busy(tx_core_busy)
    );


    // -------------------------------------------------------
    // 3. RX Channel (UART RX + FIFO)
    // -------------------------------------------------------
    
    // [UART RX Core]
    uart_rx u_uart_rx (
        .clk(clk), .reset_n(reset_n), .tick(tick), .rx_pin(rx_pin),
        .rx_done(rx_core_done), .rx_data(rx_core_data), .rx_err(rx_core_err)
    );
    
    // [RX FIFO]
    // UART가 수신을 완료하면(rx_core_done) 자동으로 FIFO에 Write(wr_en) 합니다.
    fifo #(.DATA_WIDTH(8), .ADDR_WIDTH(4)) u_rx_fifo (
        .clk(clk), .reset_n(reset_n),
        .wr_en(rx_core_done), .wr_data(rx_core_data), .full(), // UART Side (full은 무시)
        .rd_en(rx_rd_en), .rd_data(rx_rd_data), .empty(rx_empty) // CPU Side
    );
    
    assign rx_err = rx_core_err;

endmodule

B. Baud Rate Generator (타이밍 생성기)

  1. 왜 더 느리게 만드나요? (속도 맞추기)
    FPGA나 프로세서의 시스템 클럭은 보통 50MHz(초당 5천만 번)나 100MHz(초당 1억 번) 정도로 매우 빠릅니다. 하지만 우리가 통신하려는 UART(예: 9600 bps)는 상대적으로 매우 느립니다.

시스템 클럭: 100,000,000 Hz (엄청 빠름)

UART 목표 속도 (16x tick): 153,600 Hz (상대적으로 느림)

만약 시스템 클럭 속도 그대로 데이터를 보내버리면, 상대방 기기는 너무 빨라서 데이터를 읽지도 못하고 놓쳐버립니다. 그래서 divisor만큼 카운트하며 기다렸다가 한 번씩만 tick을 줘서 속도를 늦추는 것입니다.

  1. "오히려 느려지는 거 아니야?"에 대한 답변
    네, 의도적으로 느리게 만드는 것이 맞습니다!
    clk가 divisor 횟수만큼 요동칠 때, tick은 딱 한 번만 튑니다.즉, tick의 주파수는 fclk/divisorf_{clk} / divisor 가 됩니다.
    이 느려진 tick을 기준으로 데이터를 한 비트씩 전송하거나 수신하게 됩니다.

baud_gen.sv

`timescale 1ns / 1ps

module baud_gen (
    input  logic        clk,        // System Clock
    input  logic        reset_n,    // Active Low Reset
    input  logic [15:0] divisor,    // 분주비 = Sys_Clk / (Baudrate * 16)
    output logic        tick        // 16x Oversampling Tick (1 cycle pulse)
);

    logic [15:0] counter;

    always_ff @(posedge clk or negedge reset_n) begin
        if (!reset_n) begin
            counter <= '0;
            tick    <= 1'b0;
        end else begin
            if (counter >= divisor) begin
                counter <= '0;
                tick    <= 1'b1; // 설정한 주기마다 1클럭 펄스 발생
            end else begin
                counter <= counter + 1;
                tick    <= 1'b0;
            end
        end
    end

endmodule

C. UART Receiver (RX) - 가장 중요!

RX는 언제 데이터가 들어올지 모르기 때문에 비동기(Asynchronous) 입력입니다.

  1. Metastability 방지: rx 입력 신호는 외부 세상에서 오므로, 2-stage Flip-Flop을 거쳐서 클럭 도메인에 동기화(Synchronization)시켜야 합니다.

  2. 16x Oversampling:

  • Start Bit가 감지되면(High -> Low), 카운터를 돌려 8번째 tick(비트의 정중앙)에서 값을 읽어야 데이터가 가장 확실합니다.
  • 이후 매 16 tick마다 다음 비트를 샘플링합니다.

D. FSM (Finite State Machine) 상태 정의

SystemVerilog의 enum(enumerated type, 열거형)을 활용하여 상태를 정의할 겁니다.

TX FSM State

TX 모듈은 PISO (Parallel In Serial Out) 구조입니다. 즉, CPU가 8비트 데이터를 한 번에 주면(Parallel), 이 모듈이 타이밍에 맞춰 한 비트씩 쪼개서(Serial) 밖으로 내보냅니다.

TX FSM 동작 원리 (State Machine)
우리는 baud_gen에서 만든 tick 신호를 사용합니다. 데이터 한 비트(1 bit)를 전송하기 위해 tick을 16번 셉니다. (안정성을 위해 16x 오버샘플링 구조를 통일해서 사용합니다.)

  1. IDLE: 전송 요청(tx_start)이 올 때까지 대기 (TX Pin = 1 유지).
  2. START: Start Bit(0)를 내보내고 16 tick을 기다림.
  3. DATA: 8비트 데이터를 LSB(0번 비트)부터 하나씩 내보냄. (각 비트당 16 tick 유지).
  4. STOP: Stop Bit(1)를 내보내고 16 tick을 기다린 후 tx_done 신호 발생.

RX FSM State

  1. IDLE: Start Bit(0)가 들어오는지 감시
  2. START: Start Bit의 중앙(tick 7)까지 대기하여 진짜 Start인지 확인 (Glitch 방지)
  3. DATA: 데이터 비트 8개를 중앙(tick 15)에서 샘플링
  4. STOP: Stop Bit가 1인지 확인 (Framing Error 체크용)

3. SystemVerilog 모듈 뼈대 (Skeleton Code)

// uart_core.sv (Top Wrapper for Logic)
module uart_core (
    input  logic        clk,
    input  logic        reset_n,
    
    // Configuration
    input  logic [15:0] divisor,    // Baud rate 설정값
    
    // UART Physical Interface
    input  logic        rx_pin,     // 외부에서 들어오는 시리얼
    output logic        tx_pin,     // 외부로 나가는 시리얼
    
    // User Interface (to FIFO/AXI)
    input  logic        tx_start,   // 전송 시작 신호
    input  logic [7:0]  tx_data,    // 전송할 데이터
    output logic        tx_done,    // 전송 완료
    
    output logic        rx_done,    // 수신 완료 (데이터 유효)
    output logic [7:0]  rx_data,    // 수신된 데이터
    output logic        rx_err      // Stop bit 에러 등
);
    // 내부 서브 모듈 인스턴스 예정
    // baud_gen, uart_tx, uart_rx
endmodule
profile
Design Verification engineer

0개의 댓글