
크게 3가지 서브 모듈로 나누어 설계합니다.
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]
가장 표준적인 설정을 따릅니다.
`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
시스템 클럭: 100,000,000 Hz (엄청 빠름)
UART 목표 속도 (16x tick): 153,600 Hz (상대적으로 느림)
만약 시스템 클럭 속도 그대로 데이터를 보내버리면, 상대방 기기는 너무 빨라서 데이터를 읽지도 못하고 놓쳐버립니다. 그래서 divisor만큼 카운트하며 기다렸다가 한 번씩만 tick을 줘서 속도를 늦추는 것입니다.
`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
RX는 언제 데이터가 들어올지 모르기 때문에 비동기(Asynchronous) 입력입니다.
Metastability 방지: rx 입력 신호는 외부 세상에서 오므로, 2-stage Flip-Flop을 거쳐서 클럭 도메인에 동기화(Synchronization)시켜야 합니다.
16x Oversampling:
SystemVerilog의 enum(enumerated type, 열거형)을 활용하여 상태를 정의할 겁니다.
TX 모듈은 PISO (Parallel In Serial Out) 구조입니다. 즉, CPU가 8비트 데이터를 한 번에 주면(Parallel), 이 모듈이 타이밍에 맞춰 한 비트씩 쪼개서(Serial) 밖으로 내보냅니다.
TX FSM 동작 원리 (State Machine)
우리는 baud_gen에서 만든 tick 신호를 사용합니다. 데이터 한 비트(1 bit)를 전송하기 위해 tick을 16번 셉니다. (안정성을 위해 16x 오버샘플링 구조를 통일해서 사용합니다.)
// 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