UART(Universal Asynchronous Receiver/Transmitter)是 FPGA 最基礎的串行通信接口。本文系統講解 UART 幀格式、波特率產生器設計、發送器/接收器狀態機邏輯、**中間值過採樣抗抖動**、Verilog 完整可綜合代碼與關鍵時序優化點。
空閒態:高電平 (1)
起始位:1 bit 低電平 (0)
數據位:5-9 bit(常用 8 bit,LSB first)
校驗位:可選(奇/偶/無)
停止位:1/1.5/2 bit 高電平 (1)
典型配置:9600bps, 8N1(8 數據、無校驗、1 停止)。
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Baud Gen │────▶│ TX State │────▶│ TxD │
│ (Tick Gen) │ │ Machine │ │ (Output) │
└─────────────┘ └─────────────┘ └─────────────┘
│ ▲
▼ │
┌─────────────┐ ┌─────────────┐
│ RX Over- │ │ RX State │
│ sampling │────▶│ Machine │
└─────────────┘ └─────────────┘
module baud_tick_gen #(
parameter SYS_CLK_HZ = 50_000_000,
parameter BAUD_RATE = 9600,
parameter OVERSAMPLE = 8 // 過採樣倍數 8/16
)(
input wire clk,
output reg tick
);
localparam integer DIV = SYS_CLK_HZ / (BAUD_RATE * OVERSAMPLE) - 1;
reg [$clog2(DIV+1)-1:0] cnt;
always @(posedge clk) begin
if (cnt == DIV) begin cnt <= 0; tick <= 1'b1; end
else begin cnt <= cnt + 1; tick <= 1'b0; end
end
endmodule
關鍵點:
OVERSAMPLE=8 產生 8× 波特率時鐘(76.8kHz),接收端用中間值採樣(第 4 個 tick)抗抖動。$clog2 自動推導,防止位寬不足導致分頻錯誤。module uart_tx #(
parameter DATA_BITS = 8
)(
input wire clk,
input wire tick, // 來自波特率產生器
input wire [DATA_BITS-1:0] din,
input wire din_valid, // 脈衝啟動發送
output reg txd,
output reg busy
);
typedef enum logic [3:0] {
S_IDLE, S_START,
S_BIT0, S_BIT1, S_BIT2, S_BIT3,
S_BIT4, S_BIT5, S_BIT6, S_BIT7,
S_STOP
} state_t;
state_t state, nxt_state;
reg [3:0] bit_cnt;
always @(posedge clk) begin
if (tick) state <= nxt_state;
end
always @(*) begin
nxt_state = state;
case (state)
S_IDLE: if (din_valid) nxt_state = S_START;
S_START: nxt_state = S_BIT0;
S_BIT0: nxt_state = S_BIT1; S_BIT1: nxt_state = S_BIT2;
S_BIT2: nxt_state = S_BIT3; S_BIT3: nxt_state = S_BIT4;
S_BIT4: nxt_state = S_BIT5; S_BIT5: nxt_state = S_BIT6;
S_BIT6: nxt_state = S_BIT7; S_BIT7: nxt_state = S_STOP;
S_STOP: nxt_state = S_IDLE;
endcase
end
always @(posedge clk) begin
if (tick) begin
case (state)
S_IDLE: txd <= 1'b1;
S_START: txd <= 1'b0;
S_BIT0: txd <= din[0]; S_BIT1: txd <= din[1];
S_BIT2: txd <= din[2]; S_BIT3: txd <= din[3];
S_BIT4: txd <= din[4]; S_BIT5: txd <= din[5];
S_BIT6: txd <= din[6]; S_BIT7: txd <= din[7];
S_STOP: txd <= 1'b1;
endcase
end
end
always @(posedge clk) begin
busy <= (state != S_IDLE);
end
endmodule
module uart_rx #(
parameter DATA_BITS = 8,
parameter OVERSAMPLE = 8
)(
input wire clk,
input wire tick, // 過採樣 tick (8×baud)
input wire rxd,
output reg [DATA_BITS-1:0] dout,
output reg dout_valid
);
typedef enum logic [3:0] {
S_IDLE, S_START,
S_BIT0, S_BIT1, S_BIT2, S_BIT3,
S_BIT4, S_BIT5, S_BIT6, S_BIT7,
S_STOP
} state_t;
state_t state;
reg [$clog2(OVERSAMPLE)-1:0] sample_cnt;
reg [2:0] majority_cnt; // 連續 0/1 計數
reg rxd_sync1, rxd_sync2; // 兩級同步器防亞穩態
// 同步輸入到 clk 域
always @(posedge clk) begin
rxd_sync1 <= rxd;
rxd_sync2 <= rxd_sync1;
end
// 過採樣多數表決:連續 3 個樣本一致才認定位值
always @(posedge clk) begin
if (tick) begin
if (rxd_sync2) begin
if (majority_cnt != 3'd7) majority_cnt <= majority_cnt + 1;
end else begin
if (majority_cnt != 0) majority_cnt <= majority_cnt - 1;
end
end
end
wire bit_val = (majority_cnt >= 3'd4); // 閾值 4/8
// 位計數器
always @(posedge clk) begin
if (tick) begin
if (state == S_IDLE) sample_cnt <= 0;
else sample_cnt <= sample_cnt + 1;
end
end
wire bit_done = (sample_cnt == OVERSAMPLE-1);
// 狀態機
always @(posedge clk) begin
if (tick && bit_done) begin
case (state)
S_IDLE: if (!bit_val) state <= S_START; // 檢測起始位下降沿
S_START: state <= S_BIT0;
S_BIT0: state <= S_BIT1; S_BIT1: state <= S_BIT2;
S_BIT2: state <= S_BIT3; S_BIT3: state <= S_BIT4;
S_BIT4: state <= S_BIT5; S_BIT5: state <= S_BIT6;
S_BIT6: state <= S_BIT7; S_BIT7: state <= S_STOP;
S_STOP: state <= S_IDLE;
endcase
end
end
// 數據移位寄存器
always @(posedge clk) begin
if (tick && bit_done && state >= S_BIT0 && state <= S_BIT7) begin
dout <= {bit_val, dout[DATA_BITS-1:1]}; // LSB first 右移
end
end
// 輸出有效脈衝
always @(posedge clk) begin
dout_valid <= (tick && bit_done && state == S_STOP);
end
endmodule
| 優化點 | 說明 |
|---|---|
| 過採樣 8×/16× | 中間值採樣(第 4/8 個 tick)大幅提高抗抖動能力 |
| 多數表決濾波 | 連續 N 個樣本一致才翻轉,濾除毛刺 |
| 兩級同步器 | 異步 RxD 輸入同步到 clk 域,消除亞穩態風險 |
| 參數化設計 | 波特率、數據位、過採樣倍數皆可參數化,靈活復用 |
| Tick 脈衝而非分頻時鐘 | 避免跨時鐘域,單時鐘域設計,時序收斂簡單 |
| 狀態機單熱編碼 | 綜合器自動優化,面積小、時序清晰 |
module uart_top #(
parameter SYS_CLK_HZ = 50_000_000,
parameter BAUD_RATE = 9600
)(
input wire clk,
input wire rst_n,
input wire rxd,
output wire txd,
// 用戶側接口
input wire [7:0] tx_data,
input wire tx_valid,
output wire tx_busy,
output wire [7:0] rx_data,
output wire rx_valid
);
wire tick;
baud_tick_gen #(.SYS_CLK_HZ(SYS_CLK_HZ), .BAUD_RATE(BAUD_RATE), .OVERSAMPLE(8))
u_baud (.clk(clk), .tick(tick));
uart_tx #(.DATA_BITS(8)) u_tx (
.clk(clk), .tick(tick), .din(tx_data), .din_valid(tx_valid),
.txd(txd), .busy(tx_busy)
);
uart_rx #(.DATA_BITS(8), .OVERSAMPLE(8)) u_rx (
.clk(clk), .tick(tick), .rxd(rxd),
.dout(rx_data), .dout_valid(rx_valid)
);
endmodule
UART 設計核心在於波特率 Tick 產生、發送器定態輸出、接收器過採樣多數表決同步。採用單時鐘域 + Tick 脈衝使能架構,配合參數化、同步器、濾波器,可獲得面積小、時序收斂易、抗干擾強的可復用 IP 核。進階可擴展:FIFO 緩衝、DMA 接口、自動波特率檢測、RS485 方向控制、LIN 協議等。