Dieser Artikel beschreibt die einfachste Implementierung von RAM in Verilog.
Bevor Sie mit der Code-Analyse fortfahren, sollten Sie die grundlegende Syntax von Verilog kennen.
Hier finden Sie Schulungsunterlagen .
RAM
Schritt 1: Deklarieren des Moduls mit den entsprechenden Eingangs- / Ausgangssignalen
module ram ( input [word_size - 1:0] data, input [word_size - 1:0] addr, input wr, input clk, output response, output [word_size - 1:0] out ); parameter word_size = 32;
- Daten - Daten zum Schreiben.
- Adr - Adresse zum Speicher im RAM.
- wr - status (lesen / schreiben).
- clk - Taktzyklus-System.
- Antwort - Bereitschaft des RAM (1 - wenn RAM die Lese- / Schreibanforderung verarbeitet hat, 0 - andernfalls).
- out - Daten aus dem RAM gelesen.
Diese Implementierung wurde in das Altera Max 10-FPGA integriert, das über eine 32-Bit-Architektur verfügt. Daher beträgt die Größe für Daten und Adressen (word_size) 32 Bit.
Schritt 2: Deklarieren der Register im Modul
Eine Array-Deklaration zum Speichern von Daten:
parameter size = 1<<32; reg [word_size-1:0] ram [size-1:0];
Wir müssen auch die vorherigen Eingabeparameter speichern, um ihre Änderungen im Always-Block zu verfolgen:
reg [word_size-1:0] data_reg; reg [word_size-1:0] addr_reg; reg wr_reg;
Und die letzten beiden Register zum Aktualisieren der Ausgangssignale nach Berechnungen im Always-Block:
reg [word_size-1:0] out_reg; reg response_reg;
Wir initialisieren die Register:
initial begin response_reg = 1; data_reg = 0; addr_reg = 0; wr_reg = 0; end
Schritt 3: Implementieren der Always-Logik des Blocks
always @(negedge clk) begin if ((data != data_reg) || (addr%size != addr_reg)|| (wr != wr_reg)) begin response_reg = 0; data_reg = data; addr_reg = addr%size; wr_reg = wr; end else begin if (response_reg == 0) begin if (wr) ram[addr] = data; else out_reg = ram[addr]; response_reg = 1; end end end
Immer Block wird durch negedje ausgelöst, d.h. Im Moment bewegt sich die Uhr von 1 auf 0. Dies geschieht, um den RAM korrekt mit dem Cache zu synchronisieren. Andernfalls kann es Fälle geben, in denen der RAM keine Zeit hat, den Bereitschaftsstatus von 1 auf 0 zurückzusetzen, und beim nächsten Takt entscheidet der Cache, dass der RAM seine Anforderung erfolgreich verarbeitet hat, was grundsätzlich falsch ist.
Die Logik des Always-Algorithmus des Blocks lautet wie folgt: Wenn die Daten aktualisiert werden, setzen Sie den Bereitschaftsstatus auf 0 zurück und aktualisieren / lesen Sie Daten. Wenn das Schreiben / Lesen abgeschlossen ist, aktualisieren Sie den Bereitschaftsstatus auf 1.
Fügen Sie am Ende den folgenden Codeabschnitt hinzu:
assign out = out_reg; assign response = response_reg;
Die Art der Ausgangssignale unseres Moduls ist Draht. Die einzige Möglichkeit, Signale dieses Typs zu ändern, ist die langfristige Zuweisung, die innerhalb des Always-Blocks verboten ist. Aus diesem Grund verwendet der Always-Block Register, die anschließend den Ausgangssignalen zugeordnet werden.
Direkter Mapping-Cache
Der direkte Mapping-Cache ist einer der einfachsten Cache-Typen. In dieser Implementierung besteht der Cache aus n Elementen, und der RAM wird bedingt durch n in Blöcke unterteilt, dann entspricht das i-te Element im Cache allen solchen k-ten Elementen im RAM, die die Bedingung i = k% n erfüllen.
Das Bild unten zeigt einen Cache der Größe 4 und einen RAM der Größe 16.

Jedes Cache-Element enthält die folgenden Informationen:
- Gültigkeitsbit - ob die Informationen im Cache relevant sind.
- Tag ist die Blocknummer im RAM, in der sich dieses Element befindet.
- Daten - Informationen, die wir schreiben / lesen.
Bei der Aufforderung zum Lesen teilt der Cache die Eingabeadresse in zwei Teile - ein Tag und einen Index. Die Größe des Index ist log (n), wobei n die Größe des Caches ist.
Schritt 1: Deklarieren des Moduls mit den entsprechenden Eingangs- / Ausgangssignalen
module direct_mapping_cache ( input [word_size-1:0] data, input [word_size-1:0] addr, input wr, input clk, output response, output is_missrate, output [word_size-1:0] out ); parameter word_size = 32;
Die Deklaration des Cache-Moduls ist identisch mit dem RAM, mit Ausnahme des neuen Ausgangssignals is_missrate. Diese Ausgabe speichert Informationen darüber, ob die letzte Leseanforderung fehlgeschlagen war.
Schritt 2: Deklarieren der Register und des RAM
Bevor wir die Register deklarieren, bestimmen wir die Größe des Caches und des Index:
parameter size = 64; parameter index_size = 6;
Als nächstes deklarieren wir ein Array, in dem die Daten, die wir schreiben und lesen, gespeichert werden:
reg [word_size-1:0] data_array [size-1:0];
Wir müssen auch Gültigkeitsbits und Tags für jedes Element im Cache speichern:
reg validity_array [size-1:0]; reg [word_size-index_size-1:0] tag_array [size-1:0]; reg [index_size-1:0] index_array [size-1:0];
Register, in die die Eingangsadresse aufgeteilt wird:
reg [word_size-index_size-1:0] tag; reg [index_size-1:0] index;
Register, die die Eingabewerte auf dem vorherigen Takt speichern (zum Verfolgen von Änderungen in Eingabedaten):
reg [word_size-1:0] data_reg; reg [word_size-1:0] addr_reg; reg wr_reg;
Register zum Aktualisieren der Ausgangssignale nach Berechnungen im Always-Block:
reg response_reg; reg is_missrate_reg; reg [word_size-1:0] out_reg;
Eingabewerte für RAM:
reg [word_size-1:0] ram_data; reg [word_size-1:0] ram_addr; reg ram_wr;
Ausgabewerte für RAM:
wire ram_response; wire [word_size-1:0] ram_out;
Deklarieren eines RAM-Moduls und Verbinden von Eingangs- und Ausgangssignalen:
ram ram( .data(ram_data), .addr(ram_addr), .wr(ram_wr), .clk(clk), .response(ram_response), .out(ram_out));
Registerinitialisierung:
initial integer i initial begin data_reg = 0; addr_reg = 0; wr_reg = 0; for (i = 0; i < size; i=i+1) begin data_array[i] = 0; tag_array[i] = 0; validity_array[i] = 0; end end
Schritt 3: Implementieren der Always-Logik des Blocks
Zunächst haben wir für jede Uhr zwei Zustände - die Eingabedaten werden geändert oder nicht geändert. Basierend darauf haben wir die folgende Bedingung:
always @(posedge clk) begin if (data_reg != data || addr_reg != addr || wr_reg != wr) begin end // 1: else begin // 2: end end
Block 1. Falls die Eingabedaten geändert werden, setzen wir zuerst den Bereitschaftsstatus auf 0 zurück:
response_reg = 0;
Als nächstes aktualisieren wir die Register, in denen die Eingabewerte des vorherigen Takts gespeichert sind:
data_reg = data; addr_reg = addr; wr_reg = wr;
Wir teilen die Eingabeadresse in ein Tag und einen Index auf:
tag = addr >> index_size; index = addr;
Um das Tag zu berechnen, wird eine bitweise Verschiebung nach rechts verwendet, für den Index reicht es aus, einfach zuzuweisen, weil Zusätzliche Bits der Adresse werden nicht berücksichtigt.
Der nächste Schritt besteht darin, zwischen Schreiben und Lesen zu wählen:
if (wr) begin // data_array[index] = data; tag_array[index] = tag; validity_array[index] = 1; ram_data = data; ram_addr = addr; ram_wr = wr; end else begin // if ((validity_array[index]) && (tag == tag_array[index])) begin // is_missrate_reg = 0; out_reg = data_array[index]; response_reg = 1; end else begin // is_missrate_reg = 1; ram_data = data; ram_addr = addr; ram_wr = wr; end end
Bei der Aufzeichnung ändern wir zunächst die Daten im Cache und aktualisieren dann die Eingabedaten für den RAM. Beim Lesen überprüfen wir das Vorhandensein dieses Elements im Cache und schreiben es, falls vorhanden, in out_reg, andernfalls wenden wir uns dem RAM zu.
Block 2. Wenn die Daten seit der Ausführung der vorherigen Uhr nicht geändert wurden, haben wir den folgenden Code:
if ((ram_response) && (!response_reg)) begin if (wr == 0) begin validity_array [index] = 1; data_array [index] = ram_out; tag_array[index] = tag; out_reg = ram_out; end response_reg = 1; end
Hier warten wir auf den Abschluss des Zugriffs auf den RAM (wenn kein Zugriff vorhanden war, ist ram_response 1), aktualisieren die Daten, wenn ein Lesebefehl vorhanden war, und setzen die Cache-Bereitschaft auf 1.
Zuletzt aktualisieren Sie die Ausgabewerte:
assign out = out_reg; assign is_missrate = is_missrate_reg; assign response = response_reg;