FPGA-BRAM-Initialisierung

Ich muss viele BRAM-Blöcke in meinem (Altera-)Design erstellen. Jeder hat einzigartige Speicherinhalte, die a priori unter Verwendung eines Algorithmus bestimmt werden.

Früher habe ich einen Parameter für jede BRAM-Zelle gesetzt, um aus einer .MIF-Datei zu lesen, aber das hat dazu geführt, dass meine Kompilierungszeit ewig gedauert hat.

Ein weiterer Ansatz, den ich mir ausgedacht habe, bestand darin, eine "dynamische" Bestückung des Speichers zuzulassen; Der Host-Controller wäre in der Lage, Symbole an das FPGA zu senden, um seine BRAM-Blöcke damit zu füllen. Das ist etwas komplizierter als ich möchte.

Ich hatte gehofft, es gäbe eine Möglichkeit, BRAMs mit einem Literal zu initialisieren. Jeder Block ist nur 1 Bit x 256 groß, sodass der resultierende HDL-Code nicht einmal so gruselig aussehen würde .

Weiß jemand, wie man das mit BRAM IP von Altera oder vielleicht sogar Xilinx macht?

UPDATE 31.8.2016:

Hey Leute, ich habe tatsächlich eine sehr einfache, fast "schlüsselfertige" Lösung für die BRAM-Initialisierung auf Altera gefunden. In Quartus gibt es integrierte VHDL- und Verilog-Vorlagen, die automatisch auf BRAM schließen können. Diese Vorlagen verfügen über integrierte Speicherinitialisierungsdienstprogramme, die der Benutzer ändern kann, um sie mit beliebigen Daten zu füllen (z. B. einem Bitvektor von einem generischen). Siehe diese Quartus-Hilfeseite .

Antworten (5)

Es ist definitiv möglich, Block-RAMs direkt von HDL abzuleiten. Je nach Größe und Konfiguration könnte die Toolchain sie im Block-RAM oder im verteilten RAM platzieren.

Hier ist ein sehr einfaches Verilog-Beispiel:

module rom
(
    input clk,
    input [3:0] addr,
    output [7:0] data
);

reg [7:0] mem[2**4-1:0];
reg [7:0] output_reg = 8'd0;

initial begin
    mem[4'h0] = 8'h00;
    // ...
    mem[4'hF] = 8'h00;
end

assign data = output_reg;

always @(posedge clk) begin
    output_reg <= mem[addr];
end

endmodule

Ich habe ähnlichen Code sowohl in der Xilinx- als auch in der Altera-Toolchain verwendet, und im Allgemeinen leiten beide Tools die richtigen RAM-Komponenten ab. Dies funktioniert auch für RAMs. Und es kann verwendet werden, um Werte in einer Nachschlagetabelle zu speichern, die zur Synthesezeit berechnet werden, obwohl Quartus in der Vergangenheit einige ernsthafte Probleme damit hatte, nämlich mit Gleitkomma/Trigger zur Synthesezeit.

Beispiel für die Vorberechnung von ROM-Inhalten in Verilog: https://github.com/alexforencich/verilog-dsp/blob/master/rtl/sine_dds_lut.v

Komisch, dass du fragst. Ich habe kürzlich mit der Vivado-Toolchain von Xilinx gekämpft und versucht, ein altes ISE-Design zu migrieren, das einige BRAM-basierte ROMs enthält, deren Inhalt durch .coe-Dateien (Koeffizienten) definiert ist.

Ich musste es schließlich aufgeben – die Vivado-IP-Generator-Tools verloren ständig den Überblick über die .coe-Dateien, was dazu führte, dass die Synthese insgesamt fehlschlug. Ich habe noch nie eine Problemumgehung gefunden, die zweimal hintereinander funktioniert hat ...

Am Ende habe ich ein relativ einfaches Perl-Skript geschrieben, das eine .coe-Datei in eine reine RTL- caseAnweisung konvertiert, und das hat gut funktioniert. Vivado schließt richtigerweise auf ein BRAM-basiertes ROM und alles ist gut. Ich habe das Skript im Moment nicht auf diesem Computer, aber wenn Sie daran interessiert sind, werde ich es später anhängen. Es wäre einfach, es so anzupassen, dass es auch .mif-Dateien als Eingabe akzeptiert.


Hier ist ein Ausschnitt aus der Skriptdokumentation, der erklärt, was es tut:

# The standard .coe file contains two statements:
# =========
# memory_initialization_radix=2;
# memory_initialization_vector=
# 0000000000000000,
# 0000010100000010,
# ....
# 0000000000000000;
# =========
# The argument to memory_initialization_radix gives the radix (in decimal)
# used for the individual values of the memory_initialization_vector list.
#
# For now, we're just going to make the following assumptions:
# * The .coe file is laid out exactly as shown above, with one word per line.
# * The radix is either 2 or 16.
# * The number of words indicates the address size of the ROM.
# * The width of the words indicates the data size of the ROM.
#
# We will convert the .coe data into a Verilog source file with the following
# structure:
# 
# module <filename> (
#   input           [5:0] addra,
#   output reg     [15:0] douta,
#   input                 clka
# );
# 
#   always @(posedge clka) begin
#     case (addra)
#     6'h00: douta <= 16'b0000000000000000;
#     6'h01: douta <= 16'b0000010100000010;
#     ....
#     6'h3F: douta <= 16'b0000000000000000;
#     endcase
#   end
# endmodule

Beachten Sie, dass die Portnamen mit denen identisch sind, die vom IP-Generator verwendet werden, sodass dieses Verhaltenscodemodul ein direkter Ersatz für das generierte Modul ist.

Könnten Sie diese Fallaussage näher erläutern? Ich wusste nicht, dass Vivado so schlau ist! Ich dachte immer, wenn Sie BRAM verwenden wollten, müssten Sie eine IP instanziieren. Könntest du mir ein Code-Snippet schicken?
Da die eigene Dokumentation des Skripts ein solches Beispiel zeigt, habe ich es oben hinzugefügt. Ich möchte jedoch ein paar kleinere Dinge optimieren, bevor ich den Code zeige.

Sie können ein BRAM immer mit dem Attribut „Block“ für eine Speicherdeklaration instanziieren (dies leitet BRAM in der FPGA-Architektur ab). Sie benötigen dazu keinen IP-Core und können ihn mit beliebigen Daten initialisieren, ähnlich wie Sie ihn einem Signal oder Vektor zuweisen würden.

Ich verwende dies seit einiger Zeit für Xilinx FPGA und ich denke, dass dies auch für die Altera-Domäne funktionieren wird, da dies ein Stück Code und kein IP-Core-Parameter ist.

Ich habe Xilinx-BRAMs verwendet, die mit Konstanten initialisiert wurden, die im Programmierstrom enthalten sind. Das war damals, als V4 gerade herausgekommen war, aber ich hoffe, die Funktion wurde in den aktuellen Tools nicht entfernt.

Es gibt ein Flag in Xilinx CoreGen auf dem Speicher, den Sie auf 'ROM' anstelle von 'RAM' setzen und dann die Initialisierungstabellen in der zu kompilierenden VHDL aneinanderreihen.

Ich weiß nichts über Altera, aber es ist so eine offensichtliche Einrichtung, um es bereitzustellen, es muss da sein, man muss nur danach suchen.

Ich habe jetzt seit einer Woche herumgegraben und es scheint, als ob Sie für Altera BRAM nur entweder "online" oder mit MIF / HEX-Dateien initialisieren können. :\ Ich werde aber weitersuchen.

Neben den anderen Antworten können Sie die Initialisierung auch von einem einzelnen Parameter aus durchführen. So etwas wie dieses Verilog-Beispiel sollte ausreichen:

module paramInitialisedROM #(
    parameter WIDTH = 1,
    parameter DEPTH = 256,
    parameter MEM_INIT = 256'd120310230123  //Or whatever, key thing is to make it the correct size (WIDTH*DEPTH).
)(
    input                  clock,
    input      [DEPTH-1:0] address,
    output reg [WIDTH-1:0] data
);

localparam WORDS = 1<<DEPTH; //Number of words in ROM.

// Create an inferred ROM of the correct size
reg [WIDTH-1:0] rom [WORDS-1:0]; //It is possible to add an altera derective 
                                 //to this to specify BRAM or MLAB, but I forget
                                 //the syntax of that.

// ROM Initialisation
integer idx;
integer offset;
initial begin
    for (idx = 0; idx < WORDS; idx=idx+1) begin //Count through each word in the rom
        offset = idx * WIDTH; //The offset into the parameter is the current index times the with
        rom[idx] = MEM_INIT[offset+:WIDTH]; //Set the current rom word to the correct chunk in the parameter
    end
end

//Clocked Read from ROM
always @ (posedge clock) begin
    data <= rom[address];
end

endmodule

Stellen Sie grundsätzlich sicher, dass Sie beim Instanziieren des Moduls einen Parameter mit der richtigen Größe angeben. Wenn Sie beispielsweise ein 16x4b-ROM erstellen, müssen Sie den Parameter als 64-Bit oder größer angeben, z. B.:

paramInitialisedROM aRomInstance #(
    .WIDTH(4),
    .DEPTH(4),
    .MEM_INIT(64'd120310230123)
)(
    .clock(clock),
    .address(address),
    .data(data)
);

Die Daten im Parameter sind so organisiert, dass die WIDTHLSBs für das erste Wort verwendet werden. Der nächste WIDTHBlock ist das nächste Wort und so weiter, bis alle Wörter gefüllt sind.

Die forSchleife während der Initialisierung wird vom Compiler vollständig optimiert, kostet also keine Logik. Tatsächlich initialwird der gesamte Block in den Anfangswert des Speichers umgewandelt.

Da es sich um abgeleiteten Speicher handelt, sollte er sowohl in Altera- als auch in Xilinx-Tools einwandfrei funktionieren.


Hinweis: Ich habe dies nicht testkompiliert, aber das Prinzip sollte gut funktionieren. Wenn Sie Syntaxfehler finden, können Sie diese gerne in Korrekturen bearbeiten.