Altifigence Academy

55 / 63 · 概念

同步記憶體與讀寫碰撞契約

區分暫存讀取的回應時點及 read-first、write-first、no-change,設計 valid 與位址初始化各自的責任。

在位址之前先定義回應時點

連接記憶體不只要定容量,也要定讀取延遲與碰撞行為。此處建立 16 個位址、每個 8 位元的同步記憶體,在同一上升緣接受一個讀與一個寫。兩請求使用同一時脈,位址彼此獨立。容量 16×8=128 位元,位址寬 log⁡216=4\log_2 16=4 位元。

讀位址與 rd_en 在上升緣前穩定,該邊緣後就產生暫存的 rd_data、rd_valid,稱為一級暫存讀取延遲。另一 always_ff 若在同一邊緣讀此輸出,看到的是更新前值,因此消費回應的暫存器暫存器儲存多個位元的狀態。本課程中的同步暫存器會在時脈邊緣儲存指定的輸入。 詳細內容在下一上升緣取得。波形須分別標請求與回應觀察時點。每個時脈都可接新請求,所以一級延遲不表示吞吐量吞吐量是單位時間內可接受或完成的作業數。管線即使不縮短單筆作業的延遲,也能提高吞吐量。 詳細內容只有 1/2。

讀寫同一位址時

假設邊緣前 M[3]=8'h25,讀位址 3 同時寫 8'h9A。Read-first 回應 25,再將儲存更新為 9A;write-first 回應新值 9A;no-change 保持輸出暫存器,並不保證其值是位址 3 的舊內容。三者是不同的正常行為契約。AMD RAM 同步模式文件也區分這三種。

本例採 read-first。以 M−M^{-} 表示邊緣前陣列、M+M^{+} 表示邊緣後,在非 reset 邊緣對任意位址 aa:

M+[a]={wr_data,wr_en=1∧a=wr_addrM−[a],otherwiseM^{+}[a]=\begin{cases} \mathrm{wr\_data}, & \mathrm{wr\_en}=1\land a=\mathrm{wr\_addr}\\ M^{-}[a], & \text{otherwise} \end{cases}
rd_data+={M−[rd_addr],rd_en=1rd_data−,rd_en=0rd_valid+=rd_en\begin{aligned} \mathrm{rd\_data}^{+}&=\begin{cases} M^{-}[\mathrm{rd\_addr}], & \mathrm{rd\_en}=1\\ \mathrm{rd\_data}^{-}, & \mathrm{rd\_en}=0 \end{cases}\\ \mathrm{rd\_valid}^{+}&=\mathrm{rd\_en} \end{aligned}

即使碰撞,讀取仍用 M−M^{-},不是 M+M^{+}。沒有讀請求時保持 rd_data。

SystemVerilog
module sync_mem16x8 (
  input  logic       clk, rst,
  input  logic       wr_en, rd_en,
  input  logic [3:0] wr_addr, rd_addr,
  input  logic [7:0] wr_data,
  output logic [7:0] rd_data,
  output logic       rd_valid
);
  timeunit 1ns;
  timeprecision 1ps;
  logic [7:0] mem [0:15];

  always_ff @(posedge clk) begin
    if (rst) begin
      rd_valid <= 1'b0;
    end else begin
      rd_valid <= rd_en;
      if (wr_en)
        mem[wr_addr] <= wr_data;
      if (rd_en)
        rd_data <= mem[rd_addr];
    end
  end
endmodule

兩個指定的右側皆讀更新前值,之後才套用 nonblocking非阻塞賦值(nonblocking)在循序 RTL 中以 <= 表示。它先求右側運算式的值,再排程更新,讓同一邊緣的暫存器都能依據先前狀態計算。 詳細內容 更新。因此即使把寫入敘述放在讀取前,也不會變成讀新值。需要 write-first 時,可明寫 (wr_en && wr_addr==rd_addr) ? wr_data : mem[rd_addr]。這條旁路仍保留讀取輸出暫存器。

Valid 與初始化是不同承諾

Rst 是高有效同步重設。上升緣 rst=1 時,忽略兩請求,只把 rd_valid 設為 0,不清零陣列與 rd_data。此契約未定義上電後從未寫過位址的內容;模擬可出現 X,實際硬體也不能假設某個數字。

Rd_valid=1 表示「讀請求的回應週期」,不表示「位址已初始化且值已知」。使用端必須先寫再讀,或維護位址初始化表。即使首次寫入的同一邊緣做 read-first,舊內容仍未定。也需 valid guard,避免 valid=0 時使用 rd_data。只讀已初始化位址與 valid guard 不能互相取代。

Reset 不清除儲存,所以 RTL 上已寫值仍保留。若系統決定 reset 後丟棄舊內容,外部初始化表也須重新管理。敏感資訊清除是獨立設計需求,不等於控制重設。

每欄是一次上升緣上升緣是時脈由 0 變成 1 的瞬間。它與表示整段 CLK=1 期間的高準位不同。 詳細內容的觀察紀錄。輸入為邊緣前值,名稱含「後」的狀態為更新後值。欄位對齊表示取樣順序,並未繪出實體傳播延遲傳播延遲是輸入改變後,輸出穩定至正確值所需的時間。邏輯式是否等價與時間特性是兩個不同問題。 詳細內容。
所有讀寫位址都是 3。E0 首次寫 25,E1 改寫 9A 並讀取。Data 的破折號是未定值,valid=0 的匯流排不算回應。資料採十六進位。

read-first 衝突時讀取舊值
查看波形資料
波形資料:每個字元表示一個區間;句點保持先前狀態;p 表示一個時脈週期。
訊號波形匯流排值
邊緣2345E0 → E1 → E2 → E3
wr_en1.0.
wr_data23..25 → 9A
rd_en01.0
M[3] 後23..25 → 9A
rd_data 後234.- → 25 → 9A
rd_valid 後01.0

手動追蹤四個邊緣

E0 在位址 3 寫 25,rd_en=0。E1 同址寫 9A 並讀,E1 後 valid=1、data=25。E2 不寫、再讀 3,data=9A。E3 若 rd_en=0,valid=0,data 保持 9A。不能只因 E3 匯流排是 9A 就計一次新回應,否則會重複處理同一資料。

位址不同時讀寫獨立,例如讀 7 同時寫 3,回應位址 7 的舊值。實際 FPGA 中 RTL 映射哪種 RAM、該資源如何處理同址碰撞,須查合成報告與元件文件。定義 RTL 語意與驗證特定裝置實作,是不同階段。

自己試試看

M[6] 已存 8'h10。E0 讀位址 6 並寫 8'h20,E1 不寫只再讀,E2 設 rd_en=0。求 read-first 每個邊緣後 data/valid。改為 write-first 有何不同?從未寫過位址的第一次 read-first 碰撞會給定義值嗎?

查看解說

E0 為 data=10、valid=1,陣列更新成 20;E1 為 data=20、valid=1。E2 保持 data=20,但 valid=0,不計為回應。Write-first 只把 E0 data 改成 20。與首次寫入重疊的 read-first 會傳回寫入前未初始化內容,不能指定某數值為答案;valid=1 也不保證初始化。

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