Altifigence Academy

55 / 63 · Konzept

Synchroner Speicher und Lese-/Schreibkollisionen

Registrierte Antwortzeit, read-first/write-first/no-change sowie getrennte Verantwortung von valid und Initialisierung festlegen.

Vor der Adresse kommt der Antwortzeitpunkt

Ein Speichervertrag braucht neben Kapazität auch Leselatenz und Kollisionsregeln. Wir bauen 16 Adressen zu acht Bit, mit je einer Lese- und Schreibannahme pro steigender Flanke. Beide nutzen denselben Takt, aber unabhängige Adressen. Kapazität: 16×8=128 Bit; Adressbreite: log⁡216=4\log_2 16=4 Bit.

Liegen Leseadresse und rd_en vor der Flanke stabil an, erscheinen danach registrierte rd_data und rd_valid: eine registrierte Lesestufe. Ein anderes always_ff sieht an derselben Flanke noch den alten Ausgang; das konsumierende RegisterRegister Speichert einen Zustand aus mehreren Bits. Die synchronen Register dieses Kurses übernehmen festgelegte Eingaben an Taktflanken. Mehr erfahren übernimmt die Antwort erst an der nächsten. Markieren Sie Anforderung und Antwortbeobachtung getrennt. Neue Anforderungen sind jeden Takt möglich; eine Stufe Latenz halbiert den DurchsatzDurchsatz Anzahl angenommener oder abgeschlossener Aufgaben je Zeiteinheit. Eine Pipeline kann ihn erhöhen, ohne die Latenz einer einzelnen Aufgabe zu verkürzen. Mehr erfahren nicht.

Dieselbe Adresse lesen und schreiben

Vor der Flanke sei M[3]=8'h25; gleichzeitig wird Adresse 3 gelesen und 8'h9A geschrieben. Read-first antwortet 25 und speichert 9A; write-first antwortet 9A. No-change hält das Ausgangsregister, dessen Wert nicht unbedingt der alte Inhalt von Adresse 3 ist. Dies sind drei legitime Verträge, auch in AMDs RAM-Synchronisationsmodi unterschieden.

Hier gilt read-first. M−M^{-} bezeichnet das Array vor, M+M^{+} nach der Flanke. Ohne Reset gilt für jede Adresse 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}

Auch bei Kollision wird M−M^{-} gelesen, nicht M+M^{+}. Ohne Leseanforderung hält 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

Beide rechten Seiten lesen alte Werte; erst danach wirken nichtblockierende ZuweisungenNonblocking Sequenzielle RTL-Zuweisung mit <=. Nach Auswertung der rechten Seite wird die Aktualisierung geplant, sodass Register derselben Flanke den alten Zustand verwenden. Mehr erfahren. Schreiben vor Lesen im Code erzeugt daher kein write-first. Dafür wäre etwa (wr_en && wr_addr==rd_addr) ? wr_data : mem[rd_addr] als Leseausdruck nötig. Das Ausgangsregister bleibt auch mit diesem Bypass erhalten.

Valid und Initialisierung sind verschiedene Zusagen

rst ist synchron active-high. Bei rst=1 werden beide Anforderungen ignoriert und nur rd_valid auf 0 gesetzt. Array und rd_data werden nicht gelöscht. Noch nie beschriebene Adressen sind nach dem Einschalten nicht definiert; Simulation kann X zeigen und Hardware erlaubt ebenfalls keinen angenommenen Zahlenwert.

rd_valid=1 bedeutet „Antwortzyklus einer Leseanforderung“, nicht „Adresse initialisiert und Wert bekannt“. Lesen Sie nur zuvor geschriebene Adressen oder führen Sie eine Initialisierungstabelle. Auch ein read-first-Lesen gleichzeitig mit dem ersten Schreiben liefert unbekannten alten Inhalt. Zusätzlich dürfen Daten bei valid=0 nicht verwendet werden. Initialisierungsprüfung und Valid-Guard ersetzen einander nicht.

Weil Reset den Speicher nicht löscht, bleiben beschriebene Werte im RTL erhalten. Verwirft das System alte Inhalte nach Reset, ist die externe Initialisierungstabelle entsprechend zu führen. Das Löschen sensibler Daten ist eine eigene Entwurfsanforderung neben Steuerreset.

Jede Spalte dokumentiert eine steigende FlankeSteigende Flanke Übergang des Takts von 0 auf 1. Nicht zu verwechseln mit dem gesamten hohen Pegelintervall CLK=1. Mehr erfahren. Eingänge gelten unmittelbar davor, Zustände mit „after“ unmittelbar nach der Aktualisierung. Die Spalten zeigen die Reihenfolge der Abtastungen, keine physische LaufzeitLaufzeit Zeit nach einer Eingangsänderung, bis der Ausgang auf dem richtigen Wert stabil ist. Logische Äquivalenz und zeitliche Eigenschaften sind getrennte Aussagen. Mehr erfahren. Alle Adressen sind 3. E0 schreibt erstmals 25, E1 überschreibt mit 9A und liest gleichzeitig. Ein Strich bei data bedeutet unbestimmt; valid=0 zählt nicht als Antwort. Daten sind hexadezimal.

Bei Read-first-Kollision den bisherigen Wert lesen
Wellenformdaten anzeigen
Wellenformdaten: Jedes Zeichen steht für ein Intervall, ein Punkt hält den vorherigen Zustand und p ist ein Taktzyklus.
SignalWellenformBuswerte
edge2345E0 → E1 → E2 → E3
wr_en1.0.
wr_data23..25 → 9A
rd_en01.0
M[3] after23..25 → 9A
rd_data after234.- → 25 → 9A
rd_valid after01.0

Vier Flanken von Hand verfolgen

E0 schreibt 25 nach Adresse 3 bei rd_en=0. E1 schreibt dort 9A und liest: danach valid=1, data=25. E2 liest ohne Schreiben und liefert 9A. E3 mit rd_en=0 ergibt valid=0 und hält data=9A. Wer den Buswert bei E3 als neue Antwort zählt, verarbeitet dieselben Daten zweimal.

Verschiedene Adressen arbeiten unabhängig: Lesen von 7 und Schreiben von 3 liefert den bisherigen Inhalt von 7. Welche FPGA-RAM-Ressource dieses RTL realisiert und welche Kollisionen sie unterstützt, ist mit Synthesebericht und Gerätedokumentation zu prüfen. RTL-Semantik festzulegen und eine konkrete Implementierung zu verifizieren sind getrennte Schritte.

Selbst ausprobieren

M[6]=8'h10. E0 liest 6 und schreibt 8'h20; E1 liest ohne Schreiben, E2 hat rd_en=0. Bestimmen Sie data/valid bei read-first. Was ändert write-first? Liefert die erste read-first-Kollision einer uninitialisierten Adresse einen definierten Wert?

Erklärung lesen

E0: data=10, valid=1, Array neu 20. E1: data=20, valid=1. E2 hält data=20, valid=0 und ist keine Antwort. Write-first ändert nur E0 auf data=20. Eine erste read-first-Kollision liefert den uninitialisierten alten Inhalt, keinen festgelegten Sollwert. Auch valid=1 garantiert keine Initialisierung.

Deine Auswahl gilt für diesen Browser. Du kannst sie jederzeit in der Fußzeile ändern.