Wie kann das Timing bei diesem Design mit so viel BlockRAM verbessert werden?

Ich baue eine kleine RISC-FPGA-CPU auf einem Artix-7-Board (Arty A7-100 (xc7a100tcsg324-1). Es läuft gut, die meisten Befehle benötigen drei Takte (nicht Pipeline), können aber aufgrund verrückter Netzverzögerungen nur läuft mit 100/50 MHz. 100 MHz für RAM, das die Geschwindigkeit anderer Signale regelt. Die meisten Pfade mit dem geringsten Schlupf sind diejenigen, die mit dem RAM verbunden sind. Ich habe den Taktgenerator-Assistenten verwendet, um eine Doppelausgangs-Takt-IP zu erstellen.

Das Problem ist, dass ich fast das gesamte BlockRAM auf dem Gerät verwende, also sieht mein Signal-Fanout so aus (z. B. ist dies das Signal-Fanout für ram_dinb zum BlockRAM, andere Verbindungen sind ähnlich):

Fanout für ram_dinb-Signale

Bei 100 MHz beträgt die Zeitanforderung natürlich 10,0 ns. Meine Nettoverzögerung hier ist 8,907, was absurd hoch erscheint ... aber wenn ich diese Pfade hervorhebe und hineinzoome, schlängeln sie sich überall herum, also kein Wunder, dass die Nettoverzögerung so hoch ist ...

Also habe ich ein wenig studiert und sogar versucht, die vielen verfügbaren horizontalen BUFHCEs zu verwenden, aber dies schlug sofort fehl, weil ich den Blockspeichergenerator verwendet habe, um diesen 256-K-Wort-RAM zu erstellen, er sieht monolithisch aus, obwohl er den gesamten Chip umfasst. Wenn ich dies beispielsweise auf einem einzelnen BlockRAM-Grundelement in der Region X1Y2 verwenden möchte, könnte ich Folgendes verwenden:

wire clk_ram_12;    // region X1Y2
BUFHCE #( .CE_TYPE("SYNC"), .INIT_OUT(0) )
BUFHCE_ram_12 (
  .O(clk_ram_12),
  .CE(1'b1),
  .I(clk_ram) // from the MMCM 
);

Abgesehen davon, dass die Block Memory Generator-Schnittstelle nur zwei Clock-Verbindungen auf Modulebene zulässt, ganz zu schweigen von all den anderen Signalen wie Daten, Adresse usw.

RAM256 ram (
    .clka(clk_ram),
    .wea(2'b00), // Never write to port A because it's connected to the program counter.
    .addra(pc),
    .dina(18'b0), // Never write to port A because it's connected to the program counter.
    .douta(ram_douta),
    .clkb(clk_ram),
    .web(ram_web),
    .addrb(ram_addrb),
    .dinb(ram_dinb),
    .doutb(ram_doutb)
);

Ich habe das Gefühl, dass mir hier etwas Grundlegendes fehlt. Kann ich das irgendwie beschleunigen? Oder werde ich mit diesem BlockRAM hoffnungslos in die Ecke gedrängt? Selbst wenn ich irgendwie einen Taktbaum mit BUFHCE- und BUFG-Primitiven erstelle, löst es meine Probleme für andere Signale wie Daten, Adresse, Schreibfreigabe usw. immer noch nicht. Ich lese jetzt seit zwei Tagen Xilinx-Benutzerhandbücher mit keine vielversprechenden Ideen.

Lesen Sie den Synthesizer-Bericht. Unter einer Menge irrelevanter Dinge gibt es möglicherweise Hinweise zum Pipelining des RAM für eine bessere Leistung (was möglicherweise das erneute Pipelining eines Teils der CPU erfordert).

Antworten (1)

Ihre Spezifikation sagt ein "256K-Wort-RAM", und aus dem Code scheinen Sie 18b pro Wort zu verwenden. Das macht es zu einem sehr großen Speicher. Ein Artix 7 BRAM hat 36kbit, also 2kWord. Für einen 256-kWord-Speicher müssen also 128 BRAMs miteinander verbunden werden.

Das Problem ist dann nicht so sehr die Anzahl der BRAMs, sondern die zusätzliche Multiplexerlogik, die zum Verbinden der Datenausgänge erforderlich ist. Sie fordern im Wesentlichen 18 (eine pro Bit) Instanzen eines 128: 1-Multiplexers an, um auszuwählen, welcher BRAM-Ausgang Ihren Datenbus ansteuern soll. Sie benötigen dann tatsächlich zwei Kopien dieser Struktur, da es sich um ein Dual-Port-RAM handelt (eines für jeden Leseport). Das wird körperlich groß.

Wenn wir von einer LUT mit 6 Eingängen ausgehen (gut für Artix 7), die ausreicht, um einen 4:1-Multiplexer (4 Daten, 2 Adressen) zu erstellen, bedeutet dies, dass Sie für jedes Bit eine Kette von 32 LUTs (128:32) benötigen. gefolgt von 8 LUTs (32:8), gefolgt von zwei LUTs (8:2) und dann einer letzten LUT (2:1). Sie haben 36 davon (zwei 18-Bit-Ports).

Das Verketten von Nachschlagetabellen auf vier Ebenen ohne Pipelining wird Ihrem FMax schaden. Tatsächlich wird es sogar noch mehr verletzt, da BRAMs aufgrund der in ihnen enthaltenen Array-Decodierungslogik typischerweise selbst eine nicht unbedeutende Verzögerung aufweisen. Darüber hinaus ist die Kette wahrscheinlich länger, da Sie auch die zugehörige Adressdecodierungslogik benötigen, was bedeutet, dass der Weg des Adresssignals zum Ausgang des Multiplexers noch länger ist.

Was Sie benötigen, um Ihren gewünschten Fmax zu erreichen, sind einige Pipeline-Stufen. Ich bin mit den Block-RAM-Designtools von Xilinx nicht vertraut, aber sie haben möglicherweise die Option, zusätzliche Pipelining-Ebenen anzugeben, die bei einem guten Design des Kerns gut in die externe Decoderlogik eingefügt werden sollten.

Andernfalls könnten Sie einen RAM mit beispielsweise 72 Bit breiten Ports entwerfen, die Sie dann leiten und Ihren eigenen externen 4: 1-Multiplexer hinzufügen können, um diese nach einer Pipeline-Phase auf 18 Bit Breite zu reduzieren. Ich habe diese Technik schon früher für Altera-Tools verwendet, um den Fmax sehr großer Speicher zu verbessern.

Ja, ich verwende 256 K 18-Bit-Worte für den Haupt-RAM und einen separaten Stapel von 4 K 18-Bit-Worten. Ja, ich verwende insgesamt 128+2 BRAMs. Der zeitliche Unterschied zwischen den beiden ist tiefgreifend, aus dem Grund, den Sie zu Recht ansprechen. Ja, das Generator-Tool bietet Pipelining-Optionen. Ich könnte damit rumspielen und werde es mir überlegen. Ein Großteil meines Designs (und der Ausführung mit drei Zyklen ohne Pipeline) basiert auf Dual-Port-RAM, das mit der doppelten CPU-Geschwindigkeit und einer Latenz von 1 Zyklus ausgeführt wird. Wenn ich also das RAM-Modell ändere, muss sich auch viel anderes ändern. Aber interessant, danke!