55 / 63 · Konzept
Synchroner Speicher und Lese-/Schreibkollisionen
Registrierte Antwortzeit, read-first/write-first/no-change sowie getrennte Verantwortung von valid und Initialisierung festlegen.
Lektionen sind frei lesbar. Schreibe dich ein, um deinen Fortschritt zu speichern.
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: 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. bezeichnet das Array vor, nach der Flanke. Ohne Reset gilt für jede Adresse :
Auch bei Kollision wird gelesen, nicht . Ohne Leseanforderung hält rd_data.
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
endmoduleBeide 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.
Wellenformdaten anzeigen
| Signal | Wellenform | Buswerte |
|---|---|---|
| edge | 2345 | E0 → E1 → E2 → E3 |
| wr_en | 1.0. | |
| wr_data | 23.. | 25 → 9A |
| rd_en | 01.0 | |
| M[3] after | 23.. | 25 → 9A |
| rd_data after | 234. | - → 25 → 9A |
| rd_valid after | 01.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.