Altifigence Academy

57 / 63 · 概念

單一時脈 FIFO 的指標與邊界政策

實作深度 4 FIFO 的指標與 occupancy,明確定義 empty、full 下同時 push/pop 的接受條件。

不搬動所有資料,也能保持順序

FIFOFIFO是先進先出的佇列。滿、空,以及是否允許同時讀寫的規則,都是行為契約的一部分。 先進先出。單一時脈中,讀寫由同一上升緣決定。本課固定 DEPTH=4、資料寬 8 位元。Rptr 指下一個取出位置,wptr 指下一個放入位置,兩者各 2 位元,3 後自然回到 0。Occupancy 要表示存量 0~4,需 3 位元。

Rptr=wptr 在 empty、full 都可能成立,若只用地址相等判斷 empty,就會遺失滿載資料。因此另存數量 CC,用 empty=(C=0)\mathrm{empty}=(C=0)、full=(C=4)\mathrm{full}=(C=4) 區分。深度 D=4D=4 時,地址寬 WaddrW_{\mathrm{addr}} 與數量寬 WcountW_{\mathrm{count}} 為:

Waddr=⌈log⁡2D⌉=2Wcount=⌈log⁡2(D+1)⌉=3\begin{aligned} W_{\mathrm{addr}}&=\lceil\log_2 D\rceil=2\\ W_{\mathrm{count}}&=\lceil\log_2(D+1)\rceil=3 \end{aligned}

區分請求與接受的邊界政策

Push、pop 是請求。令 p、q 分別為實際接受的 push、pop,依非 reset 邊緣前的狀態計算:

q=pop∧(C>0)p=push∧((C<4)∨q)C+=C+p−q\begin{aligned} q&=\mathrm{pop}\land(C>0)\\ p&=\mathrm{push}\land((C<4)\lor q)\\ C^{+}&=C+p-q \end{aligned}

Empty 下兩請求同來,沒有既有資料可 pop,因此只接受 push,新資料不會在該邊緣直接輸出。Full 下兩請求同來,可取出舊 head 釋放位置,因此兩者都接受。Full 表示「目前存四個」,在此政策中不是無條件禁止 push。

邊緣前狀態只 push只 poppush 與 pop
C=0儲存一個拒絕只接受 push,C=1
0<C<4C+1C−1兩者接受,C 不變
C=4拒絕C=3兩者接受,C=4

前進時脈,檢查邊界

一開始同時請求 push、pop。因為是空的,只應接受 push。接著關 pop、填滿四格,再提供新值並開啟兩請求。佔用量保持 4,輸出為舊 head,儲存體留下新 tail。

逐時脈操作 FIFO

四格 FIFO,初始為空,讀取與寫入指標皆為 0。非空時接受 pop;有空間或同一邊緣接受 pop 時,接受 push。

圖為上升緣上升緣是時脈由 0 變成 1 的瞬間。它與表示整段 CLK=1 期間的高準位不同。 詳細內容後狀態的教學模型。Pop 接受時 output valid=1;下一邊緣沒有接受 pop,輸出暫存器暫存器儲存多個位元的狀態。本課程中的同步暫存器會在時脈邊緣儲存指定的輸入。 詳細內容值保持但 valid=0。空槽舊位元被隱藏。「重新開始」初始化整個練習,不表示 RTL 的記憶體重設重設將狀態回復至規格指定的初始值。必須一併定義它是同步或非同步,以及是否優先於其他控制訊號。 詳細內容行為。

含暫存輸出的 RTL

SystemVerilog
module fifo4 (
  input  logic       clk, rst, push, pop,
  input  logic [7:0] din,
  output logic [7:0] dout,
  output logic       dout_valid, full, empty,
  output logic [2:0] occupancy
);
  timeunit 1ns;
  timeprecision 1ps;
  logic [7:0] mem [0:3];
  logic [1:0] rptr, wptr;
  logic take_push, take_pop;

  assign empty = (occupancy == 3'd0);
  assign full  = (occupancy == 3'd4);
  assign take_pop  = !rst && pop && !empty;
  assign take_push = !rst && push && (!full || take_pop);

  always_ff @(posedge clk) begin
    if (rst) begin
      rptr <= 2'd0;
      wptr <= 2'd0;
      occupancy <= 3'd0;
      dout_valid <= 1'b0;
    end else begin
      dout_valid <= take_pop;
      if (take_push) begin
        mem[wptr] <= din;
        wptr <= wptr + 2'd1;
      end
      if (take_pop) begin
        dout <= mem[rptr];
        rptr <= rptr + 2'd1;
      end
      case ({take_push, take_pop})
        2'b10: occupancy <= occupancy + 3'd1;
        2'b01: occupancy <= occupancy - 3'd1;
        default: occupancy <= occupancy;
      endcase
    end
  end
endmodule

Occupancy 沒有用兩個獨立 if 分別指定增減。若同時接受時對同一暫存器做兩次 nonblocking非阻塞賦值(nonblocking)在循序 RTL 中以 <= 表示。它先求右側運算式的值,再排程更新,讓同一邊緣的暫存器都能依據先前狀態計算。 詳細內容,最後一次會覆蓋前者。兩個指標各自在請求被接受時前進,數量則一次判斷四種情況。接受條件也防止從 0 減或從 4 加。

Full 時同址讀寫

依序放 A、B、C、D 後,rptr=wptr=0、occupancy=4。此時 pop=push=1、din=E,邊緣後 dout=A、dout_valid=1,邏輯內容為 [B,C,D,E]。兩指標變 1,數量保持 4。Mem[0] 雖更新為 E,dout 的 nonblocking 右側仍讀舊 A。此 read-first 碰撞行為是 full 替換政策必要條件。

下一步 push=0、pop=1,輸出 B、數量為 3。反之 empty 下 din=Z 且兩請求同來,只變成數量 1、dout_valid=0;需下個邊緣請求 pop 才輸出 Z。Empty 立即傳遞與 full 替換是不同設計選擇。

Rst 為高有效同步重設,初始化指標、數量與 dout_valid,不清記憶體與 dout。重設後即使殘留舊位元,empty 會阻止 pop,往後只讀新 push 寫過的位置,因此正常功能不需整個陣列重設。Dout_valid=0 時不得消費資料。Valid=1 是已接受 pop 的回應,輸出端沒有另設 ready,消費者必須接受。

DEPTH 不是 2 的次方時,不能直接沿用 2 位元自然溢位。例如深度 3 要明確從地址 2 返回 0。若合成使用 RAM,須確認同址碰撞支援 old-data。此程式只適用單一時脈,不能直接用於不同時脈 FIFO 的指標同步。

自己試試看

Reset 後逐邊緣給:(1) push A 與 pop、(2) 只 push B、(3) 只 push C、(4) 只 push D、(5) push E 與 pop、(6) 只 pop。列出各步後 queue、occupancy、dout_valid 與有效輸出,並解釋第五步為何需要 read-first。

查看解說

(1) [A],1,valid=0。(2) [A,B],2,valid=0。(3) [A,B,C],3,valid=0。(4) [A,B,C,D],4,valid=0。(5) [B,C,D,E],4,valid=1,dout=A。(6) [C,D,E],3,valid=1,dout=B。第一步無舊項目,拒絕 pop。第五步 rptr=wptr=0,同槽讀寫必須取舊 head A;write-first 若回 E 就破壞 FIFO 順序與內容。valid=0 的輸出值不列入比對。

此選擇適用於本瀏覽器,隨時可從頁尾變更。