0x6A Logbook

0x6A Logbook
Shi6a的筆記本
  1. 首頁
  2. 程式開發
  3. 正文

FIFO 完全解析:結構、空滿判斷、深度計算與跨時域設計

2026 年 5 月 21 日 3點熱度 0人點贊 0條評論

FIFO(First-In-First-Out,先進先出)是數位電路與嵌入式系統中最基礎也最重要的資料緩衝結構。從 UART 接收資料、SPI 傳輸佇列、DMA 資料流,到跨時域(Clock Domain Crossing, CDC)資料傳輸,FIFO 無處不在。

本文從 FIFO 結構的基本原理出發,深入解說空滿判斷、深度計算與跨時域同步設計,並以 C 語言 Ring Buffer 和 STM32 硬體 FIFO 為例,帶你徹底掌握 FIFO。

什麼是 FIFO?

FIFO 是一種資料緩衝區,資料按照「先進先出」的順序進出——最早寫入的資料會被最早讀出,就像排隊一樣。

FIFO 的應用場景

  • 速率匹配:生產者(寫入)和消費者(讀出)速率不同(例如 ADC 取樣 → 處理器讀取)
  • 突發緩衝:短時間大量資料突發寫入,等待慢速讀出
  • 跨時域同步:兩個不同時鐘域的資料傳輸(Async FIFO)
  • 中斷安全佇列:ISR 寫入、主迴圈讀出,保證資料不衝突

FIFO 的硬體結構

FIFO 結構圖
圖 1:FIFO 結構圖 — 雙口 RAM(Dual-Port RAM) + 讀寫指針 + 狀態旗標 + 格雷碼同步器

一個典型的硬體 FIFO 由以下部分組成:

  1. 雙口 RAM(Dual-Port RAM):儲存資料本體,可同時讀寫
  2. 寫入指針(Write Pointer):下一個要寫入的位置
  3. 讀出指針(Read Pointer):下一個要讀出的位置
  4. 空/滿旗標邏輯(Empty/Full Flags):比較指針判斷狀態
  5. 同步器(Synchronizer):非同步 FIFO 中用來跨時域同步指針

空滿判斷

FIFO 的核心問題是:如何知道 FIFO 是空的還是滿的?

同步 FIFO(Same Clock Domain)

當讀寫時鐘相同時,可以直接比較指針:

  • Empty:Wr_Ptr == Rd_Ptr(讀指針追上寫指針)
  • Full:Wr_Ptr + 1 == Rd_Ptr(在環形架構中,寫指針追上讀指針,或使用額外的 MSB 標誌位)

對於環形 FIFO(Circular FIFO),通常使用 n+1 bit 指針來區分空和滿:

  • 指針寬度 = log₂(Depth) + 1(例如深度 16 需要 5 bit 指針)
  • Empty:所有位元相等(含 MSB)— Wr_Ptr == Rd_Ptr
  • Full:MSB 不同,但其餘低位相等 — Wr_Ptr[MSB] != Rd_Ptr[MSB] && 其餘相等

非同步 FIFO(Different Clock Domains)

當讀寫時鐘不同(Async FIFO),無法直接比較指針,因為指針值在跨時域傳輸時會發生亞穩態(Metastability)問題。

格雷碼跨時域同步
圖 2:二進制計數器跨時域會產生錯誤中間值(左),格雷碼每次只變 1 bit 可避免此問題(右)

格雷碼與跨時域同步

跨時域 FIFO 的標準做法是:

  1. 將指針轉換為格雷碼(Gray Code)
  2. 用二級觸發器同步器(2-stage Flip-Flop Synchronizer)跨時域傳輸
  3. 在目標時域將格雷碼轉回二進制
  4. 比較轉換後的指針判斷空/滿

為什麼用格雷碼?

格雷碼的特性是相鄰數值只有 1 個 bit 變化。當這個 bit 跨時域時,最多只會錯一個 LSB(例如 010 變成 110 或 010 都是合法值)。對比二進制從 011(3) → 100(4) 時 3 個 bit 同時變化,跨時域取樣可能讀到 000~111 的任何值。

格雷碼轉換:

// 二進制 → 格雷碼
gray = binary ^ (binary >> 1);

// 格雷碼 → 二進制(反轉)
binary = gray;
for (int i = 1; i < N; i++)
    binary ^= gray >> i;

FIFO 深度計算

FIFO 深度計算
圖 3:FIFO 深度計算公式與寫入/讀出時序關係

實例計算

軟體 FIFO:Ring Buffer(環形緩衝區)

實作要點

  1. 大小為 2ⁿ:用 bitwise AND 代替除法(取模),速度快 10 倍以上
  2. volatile head/tail:ISR 修改 head,主迴圈修改 tail,防止編譯器優化
  3. SPSC 安全:單生產者(ISR)單消費者(主迴圈),head/tail 各由一方修改,不需鎖
  4. 丟棄策略:上述程式碼使用「Overwrite if full」策略,實際應用可改為「Discard oldest」

STM32 硬體 FIFO

  • USART FIFO:STM32H7/G4 系列有 16 byte TX/RX FIFO,可設定中斷觸發水準(1/4、1/2、3/4、Full)
  • SPI FIFO:F4/H7 系列 SPI 有 4~32 byte FIFO
  • I2S FIFO:音訊串流常用 16 word FIFO
  • DMA FIFO:DMA 控制器內建 FIFO,用於資料寬度匹配(8→16→32 bit)
  • DFSDM FIFO:外接 Σ-Δ ADC 的數位濾波器有專用 FIFO

常見陷阱

深度不足導致 Overflow

空滿判斷邏輯錯誤

跨時域亞穩態

非同步 FIFO 的亞穩態問題

非同步 FIFO 最棘手的問題是亞穩態(Metastability)。當寫入時鐘域的數據在讀出時鐘域被取樣時,如果數據變化正好發生在取樣邊緣附近(Setup/Hold Time 違規),觸發器的輸出會進入亞穩態——既不是 0 也不是 1,而是一個中間電壓值。

亞穩態的後果:

  • 輸出可能在 0 和 1 之間震盪
  • 不同 bit 的亞穩態恢復時間不同,導致多 bit 匯流排讀到錯誤值
  • 亞穩態可能傳播到後續邏輯,造成系統崩潰

標準解法:二級同步器(2-Stage Flip-Flop Synchronizer)

// Verilog 雙級同步器
module sync_2ff #(parameter WIDTH = 1) (
    input  wire             clk,
    input  wire [WIDTH-1:0] async_in,
    output wire [WIDTH-1:0] sync_out
);
    reg [WIDTH-1:0] sync_ff1, sync_ff2;
    
    always @(posedge clk) begin
        sync_ff1 <= async_in;   // 第一級:取樣亞穩態
        sync_ff2 <= sync_ff1;   // 第二級:亞穩態已恢復
    end
    
    assign sync_out = sync_ff2;
endmodule

二級同步器將亞穩態的 MTBF(Mean Time Between Failures)從微秒級提升到數百年。如果時鐘頻率極高(> 500MHz),可能需要三級同步器。

FIFO 在 STM32 中的應用

以 STM32F4 系列為例,USART 的硬體 FIFO 可透過以下方式配置:

// STM32 USART FIFO 配置(部分系列)
USART_CR2_FIFOEN  // 啟用 FIFO
USART_CR1_TCIE    // 傳輸完成中斷
USART_CR3_TXFTIE  // FIFO 觸發水準中斷

// 設定 FIFO 觸發水準(以 STM32G4 為例)
LL_USART_SetTxFIFOThreshold(USART1, LL_USART_FIFO_THRESHOLD_1_4);
LL_USART_SetRxFIFOThreshold(USART1, LL_USART_FIFO_THRESHOLD_1_4);
LL_USART_EnableFIFO(USART1);

啟用硬體 FIFO 後,CPU 不需要在每個 byte 收發時都觸發中斷。例如 TX FIFO 深度為 8,設定 1/4 水準後,FIFO 還有 6 個空間才觸發中斷——中斷頻率降為原來的 1/8。

DMA + FIFO 的配合更強大:DMA 可以在後台持續填充 FIFO,CPU 只在 DMA 傳輸完成時收到一次中斷,完全釋放 CPU 資源。

常用 FIFO 參數速查表

以下是設計 FIFO 時最常用的參數組合,供 PCB 與 FPGA 工程師快速查閱:

應用場景 寫入時鐘 (Write Clock) 讀取時鐘 (Read Clock) 資料寬度 (Data Width) 建議深度 (Depth) FIFO 類型
UART RX 16x baud rate system clock 8 bit 16 Async (Gray code)
SPI slave SCK (max 40 MHz) system clock 8-32 bit 8-16 Async
ADC data stream ADC clock (10 MHz) DMA clock 12-24 bit 64-256 Async
Ethernet MAC GMII/RGMII AHB bus 32 bit 512-4096 Async
CPU instruction queue CPU clock CPU clock 32-64 bit 4-8 Sync
Video line buffer pixel clock pixel clock 24-48 bit 1920+ Sync
Audio I2S stream BCLK system clock 16-32 bit 16-64 Async

這個表格涵蓋了嵌入式系統中最常見的 FIFO 應用場景。從低速的 UART(Universal Asynchronous Receiver/Transmitter)到高速的 Ethernet MAC(Media Access Controller),FIFO 的深度和類型選擇都取決於實際的 clock domain crossing 需求。

注意:Video line buffer 的深度至少要大於一行像素數(例如 1920),否則會導致畫面撕裂(tearing)。Audio FIFO 則需要考慮 sample rate 和 system clock 的比例,避免 audio underflow 或 overflow 導致爆音。

FIFO 設計檢查表

在 FPGA 或 ASIC 中設計 FIFO 時,可以用以下檢查表逐項驗證:

  • 深度 是否為 2ⁿ(便於指針環繞)?
  • 指針寬度 = log₂(Depth) + 1 能否區分空滿?
  • 空滿邏輯:Empty = 所有位元相等;Full = MSB 不同、低位相等?
  • 格雷碼 是否確實用於跨時域指針傳輸?
  • 同步器 是否為二級(或三級)?
  • Almost-Empty / Almost-Full 旗標是否根據應用需求設定?
  • 重置邏輯:重置後指針歸零、Empty 信號有效?
  • 同步器重置:兩級同步器的 FF 都需要正確初始化?

這個檢查表涵蓋了 FIFO 設計中最常見的陷阱。每次 tape-out 前跑一遍,可以省下數天的除錯時間。對於量產產品,建議再加上可測試性設計(DFT),讓 FIFO 的狀態可以透過 JTAG 邊界掃描驗證。

總結

 

標籤: 教學 生產力
最後更新:2026 年 5 月 21 日

shi6a

這個人很懶,什麼都沒留下

點贊
< 上一篇

文章評論

razz evil exclaim smile redface biggrin eek confused idea lol mad twisted rolleyes wink cool arrow neutral cry mrgreen drooling persevering
取消回覆

COPYRIGHT © 2026 0x6A Logbook. ALL RIGHTS RESERVED.

Theme Kratos Made By Seaton Jiang