Xilinx XST wird keinen Block-RAM ableiten

Ich habe Probleme, das Design meines FPGA 80-Computers auf ein Papilio Duo-Board zu bekommen, das ein Spartan 6 - xcs6slx9 ist. Das Problem rührt daher, dass RAM als verteilt statt als Block gefolgert wird.

Kurzversion: Ich verwende eine generische Entität, um die RAM-Blöcke abzuleiten (siehe unten), und stelle fest, dass alles bis zu einer Adressbreite von 11 verteilt zu sein scheint, eine Adressbreite von 12 oder mehr XST ist glücklich, es auszudrücken in Blöcke. Ich habe versucht, Attribute als Block zu markieren, aber das scheint nicht zu funktionieren.

Aktuelle Lösung: Erweitern Sie die Adressbreite einer Instanz, indem Sie das hohe Adressbit auf Null setzen ... jetzt passt das Design.

Lange Version :

Das Design erfordert drei Dual-Port-2048x8-Bit-RAM-Module. Ein Port benötigt Lese-/Schreibzugriff (CPU-Zugriff), der andere nur Lesezugriff (Videocontroller). Die Ports sind asynchron und laufen auf unterschiedlichen Taktdomänen.

Ursprünglich habe ich dieses Modul verwendet: RamDualPort dafür.

entity RamDualPort is
    generic
    (
        ADDR_WIDTH : integer;
        DATA_WIDTH : integer := 8
    );
    port
    (
        -- Port A
        clock_a : in std_logic;
        clken_a : in std_logic;
        addr_a : in std_logic_vector(ADDR_WIDTH-1 downto 0);
        din_a : in std_logic_vector(DATA_WIDTH-1 downto 0);
        dout_a : out std_logic_vector(DATA_WIDTH-1 downto 0);
        wr_a : in std_logic;

        -- Port B
        clock_b : in std_logic;
        addr_b : in std_logic_vector(ADDR_WIDTH-1 downto 0);
        dout_b : out std_logic_vector(DATA_WIDTH-1 downto 0)
    );
end RamDualPort;

architecture behavior of RamDualPort is 
    constant MEM_DEPTH : integer := 2**ADDR_WIDTH;
    type mem_type is array(0 to MEM_DEPTH-1) of std_logic_vector(DATA_WIDTH-1 downto 0);
    shared variable ram : mem_type;
begin

    process (clock_a)
    begin
        if rising_edge(clock_a) then

            if clken_a='1' then
                if wr_a = '1' then
                    ram(to_integer(unsigned(addr_a))) := din_a;
                end if;

                dout_a <= ram(to_integer(unsigned(addr_a)));
            end if;

        end if;
    end process;

    process (clock_b)
    begin
        if rising_edge(clock_b) then

            dout_b <= ram(to_integer(unsigned(addr_b)));

        end if;
    end process;

end;

Ein paar Probleme damit: 1) Abhängig von der Adressbreite werden einige als verteilt gefolgert (das Hauptproblem, nach dem ich frage), aber auch 2) diejenigen, die auf Block-RAMS gefolgert wurden, wurden als Read-First implementiert, wofür async clocks hat Probleme mit Spartan 6.

Die einzige Möglichkeit, das Read-First-Problem zu beheben, bestand darin, beide Ports mit einem neuen Modul „RamTrueDualPort“ wie folgt lesen/schreiben zu lassen:

entity RamTrueDualPort is
    generic
    (
        ADDR_WIDTH : integer;
        DATA_WIDTH : integer := 8
    );
    port
    (
        -- Port A
        clock_a : in std_logic;
        clken_a : in std_logic;
        addr_a : in std_logic_vector(ADDR_WIDTH-1 downto 0);
        din_a : in std_logic_vector(DATA_WIDTH-1 downto 0);
        dout_a : out std_logic_vector(DATA_WIDTH-1 downto 0);
        wr_a : in std_logic;

        -- Port B
        clock_b : in std_logic;
        clken_b : in std_logic;
        addr_b : in std_logic_vector(ADDR_WIDTH-1 downto 0);
        din_b : in std_logic_vector(DATA_WIDTH-1 downto 0);
        dout_b : out std_logic_vector(DATA_WIDTH-1 downto 0);
        wr_b : in std_logic
    );
end RamTrueDualPort;

architecture behavior of RamTrueDualPort is 
    constant MEM_DEPTH : integer := 2**ADDR_WIDTH;
    type mem_type is array(0 to MEM_DEPTH-1) of std_logic_vector(DATA_WIDTH-1 downto 0);
    shared variable ram : mem_type;
begin

    process (clock_a)
    begin
        if rising_edge(clock_a) then

            if clken_a='1' then

                if wr_a = '1' then
                    ram(to_integer(unsigned(addr_a))) := din_a;
                end if;

                dout_a <= ram(to_integer(unsigned(addr_a)));

            end if;

        end if;
    end process;

    process (clock_b)
    begin
        if rising_edge(clock_b) then

            if clken_b='1' then

                if wr_b = '1' then
                    ram(to_integer(unsigned(addr_b))) := din_b;
                end if;

                dout_b <= ram(to_integer(unsigned(addr_b)));

            end if;

        end if;
    end process;

end;

Das hat also das Read-First-Problem behoben und die RAMs, die den RAM blockieren, sind jetzt als Write-First implementiert (NB: Ich interessiere mich eigentlich nicht für Read-First/Write-First, außer für das Spartan 6-beschädigte RAM-Read-First-Problem ).

Jetzt besteht das Problem darin, die kleineren 2k-Instanzen (addrWidth 11) auf Block-RAM zu bekommen. Wie bereits erwähnt, habe ich Attribute ausprobiert, aber es besteht immer noch darauf, es in den verteilten RAM zu stecken. Ich konnte keine Dokumentation zu ram_style für Variablen (im Gegensatz zu Signalen) finden, habe aber Folgendes vermutet: (Beachten Sie das Bit ram:variable)

constant MEM_DEPTH : integer := 2**ADDR_WIDTH;
type mem_type is array(0 to MEM_DEPTH-1) of std_logic_vector(DATA_WIDTH-1 downto 0);
shared variable ram : mem_type;
ATTRIBUTE ram_extract: string;
ATTRIBUTE ram_extract OF ram:variable is "yes";
ATTRIBUTE ram_style: string;
ATTRIBUTE ram_style OF ram:variable is "block";

Jetzt spuckt XST dies aus, was darauf hindeutet, dass die Attributsyntax verstanden wird: (Beachten Sie die Erwähnung von ram_extractund ram_style)

Synthesizing Unit <RamTrueDualPort_1>.
    Related source file is "C:/Documents and Settings/Brad/Projects/fpgabee/Hardware/FPGABeeCore/RamTrueDualPort.vhd".
        ADDR_WIDTH = 12
        DATA_WIDTH = 8
    Set property "ram_extract = yes" for signal <ram>.
    Set property "ram_style = block" for signal <ram>.
    Found 4096x8-bit dual-port RAM <Mram_ram> for signal <ram>.
    Found 8-bit register for signal <dout_b>.
    Found 8-bit register for signal <dout_a>.
    Summary:
    inferred   1 RAM(s).
    inferred  16 D-type flip-flop(s).
    inferred   1 Multiplexer(s).
Unit <RamTrueDualPort_1> synthesized.

Synthesizing Unit <RamTrueDualPort_2>.
    Related source file is "C:/Documents and Settings/Brad/Projects/fpgabee/Hardware/FPGABeeCore/RamTrueDualPort.vhd".
        ADDR_WIDTH = 11
        DATA_WIDTH = 8
    Set property "ram_extract = yes" for signal <ram>.
    Set property "ram_style = block" for signal <ram>.
    Found 2048x8-bit dual-port RAM <Mram_ram> for signal <ram>.
    Found 8-bit register for signal <dout_b>.
    Found 8-bit register for signal <dout_a>.
    Summary:
    inferred   1 RAM(s).
    inferred  16 D-type flip-flop(s).
    inferred   2 Multiplexer(s).
Unit <RamTrueDualPort_2> synthesized.

Die 2k-Blöcke werden jedoch immer noch verteilt:

2048x8-bit dual-port distributed RAM                  : 2
4096x8-bit dual-port block RAM                        : 1

Wenn ich die redundante Adresszeile herausnehme (also: zurück auf addrWidth=11 stelle), landen alle drei Instanzen verteilt und das Design passt nicht mehr:

2048x8-bit dual-port distributed RAM                  : 3

Was zu tun ist? Ich möchte wirklich nicht zurück zu Coregen dafür wechseln.

PS: Ich bin ein Amateur darin - sei sanft!.

Sie könnten unsere RAM-Implementierung PoC.mem.ocram.tdp_wf ausprobieren . Das ist ein echtes Dual-Port-RAM mit Write-First. Es wurde synthetisiert und mit verschiedenen Tools und Boards getestet. Andere Implementierungen befinden sich im selben Ordner. Die Dateien sind mit Kommentaren gefüllt :).
Danke @Paebbels - ich werde es mir ansehen, obwohl es auf den ersten Blick so aussieht, als würde deine eine gemeinsame Uhr für jeden Port verwenden. Ich brauche separate Uhren. (Ich bin auch daran interessiert zu verstehen, warum XST Bram dafür nicht verwendet.)
Hmmm ja, der normale TDP-RAM ist doppelt getaktet, die WF-Variante nicht; weil die Synchronisierer eine höhere Verzögerung verursachen.

Antworten (3)

Wenn Sie genau wissen, was Sie am Ende haben möchten, muss Xst nicht versuchen, es aus einem Verhaltensmodell abzuleiten.

Sie können ein Block-RAM-Objekt direkt im HDL-Code instanziieren. Einzelheiten zur geeigneten Syntax und den beteiligten Optionen finden Sie in Xilinx UG615: Spartan-6 Libraries Guide for HDL Designs , um Seite 274 ("RAMB16BWER"). Sie können auch das Makro BRAM_TDP_MACRO verwenden, das auf Seite 20 desselben Dokuments erläutert wird.

Sie müssen mit der Funktionsweise des Spartan-6-Block-RAM-Elements vertraut sein. Informationen dazu finden Sie in Xilinx UG383: Spartan-6 FPGA Block RAM Resources .

(Beachten Sie, dass das Block-RAM Standardbreiten von 9, 18 oder 36 Bit hat; Sie werden es wahrscheinlich im 9-Bit-Modus verwenden und das zusätzliche Bit einfach ignorieren wollen. Es ist für Designs da, die Paritätsbits benötigen.)

Danke @duskwuff. Ich war mir anderer Ansätze wie diesem bewusst, wollte aber verstehen, ob es einen Grund für das gibt, was ich sehe. Ich dachte, ich hätte vielleicht die Attributsyntax etwas falsch oder etwas anderes Triviales. Nochmals vielen Dank - diese Links werden super praktisch sein.

Erstens sind die unterstützten Codierungsstile im Synthese-Benutzerhandbuch dokumentiert. Für Xilinx ISE wäre dies das XST-Benutzerhandbuch . Ab Seite 200 wird erläutert, was unterstützt wird. Leider verwendet es die alte conv_integerFunktion, aber Sie verwenden bereits die neueren numeric_stdÄquivalente, und diese funktionieren genauso mit XST. Wenn Sie eine ältere Version dieses Dokuments finden, enthält sie viel mehr Beispiele. Es gibt auch Beispiele in ISE; Gehen Sie zu Bearbeiten > Sprachvorlagen > VHDL > Synthesekonstrukte > Codierungsbeispiele > RAM. Dazu gehört ein spezielles „Write-First“-Beispiel unter Block RAM > Dual Port > Asymmetric Ports.

Nun zu deinen Problemen:

Je nach Adressbreite werden einige als verteilt gefolgert

Dies ist Absicht. Es ist in den meisten Fällen nicht effizient, einen Block-RAM zu verwenden, wenn die Adressbreite klein ist. Wie klein „klein“ ist, hängt von der verwendeten Gerätefamilie ab, aber im Allgemeinen, wenn eines der Datenbits unter Verwendung von LUT-Ressourcen implementiert werden kann und in einen kombinatorischen Logikblock (CLB) passt, dann wird auch ein verteilter RAM funktionieren wie ein Block-RAM hätte.

Sehen Sie sich für Ihr spezielles Gerät UG384 an, das einen Abschnitt über „Verteilter RAM“ enthält, der eine Tabelle enthält, die auflistet, welche RAM-Größen effizient in einem einzelnen CLB implementiert werden können. Das heißt nicht, dass größere Speicher kein verteiltes RAM verwenden können, nur dass ein größerer als dieser nicht mit einer so hohen Taktrate arbeitet wie ein Block-RAM.

Darüber hinaus kann verteiltes RAM keinen "echten" Dual-Port-Speicher implementieren. Wenn Sie also mehr als einen Port haben und entweder mehrere Leseadressen separat verwendet werden oder mehrere Schreibfreigaben separat verwendet werden, sollte XST auf Block-RAM schließen. Normalerweise verwenden Sie nur die Schreibschnittstelle eines Ports und die Leseschnittstelle eines anderen, daher ist dies meiner Erfahrung nach selten ein Faktor.

Sie sollten sich normalerweise keine Gedanken darüber machen, ob ein Speicher mit verteiltem oder Block-RAM implementiert wird. Betrachten Sie es als Vorteil; Da Sie den RAM abgeleitet haben, anstatt ein Primitiv zu instanziieren oder CoreGen zu verwenden, können die Tools auswählen, wie es implementiert werden soll, basierend auf den im Gerät verfügbaren Ressourcen, den von Ihnen eingerichteten Einschränkungen, den Syntheseeinstellungen usw.

Es scheint seltsam, dass, wenn Ihr Design nicht passt, es die verfügbaren Block-RAMs nicht nutzt. Ich glaube nicht, dass du etwas falsch machst. Wenn Sie wirklich einen Block-RAM erzwingen möchten, können Sie versuchen, mit der rechten Maustaste auf „Synthesize“ zu klicken, „Prozesseigenschaften“ auszuwählen und dann unter „HDL-Optionen“ den „RAM-Stil“ in „Block“ zu ändern. Beachten Sie, dass die Beschreibung für „Auto“ in der Hilfe lautet:

XST bestimmt die beste Implementierung für jedes Makro.

Im Allgemeinen wäre ich vorsichtig, dies zu ändern, ohne genau zu verstehen, warum das Design durch das Erzwingen von Block-RAM besser wird. Diese Option könnte auch sehr verschwenderisch sein, wenn Sie viele kleine Erinnerungen haben.

Sie können auch versuchen, die Synthese „Optimierungsziel“ auf „Bereich“ zu setzen. Dies kann den Schwellenwert ändern, wann ein Block-RAM über einen verteilten RAM abgeleitet wird.

Stellen Sie abschließend sicher, dass Sie die neueste Tool-Version (14.7) verwenden.

Hallo @scary_jeff - danke für die ausführliche Antwort. Das meiste von dem, was Sie gesagt haben, ist mir bewusst und ergibt für mich absolut Sinn. Normalerweise mache ich mir keine Gedanken darüber, wo Widder landen – es war nur so, dass es nicht passte und ich herausfinden musste, warum. Ich würde die globalen/Projektoptionen für den RAM-Stil nicht ändern - das scheint ein bisschen extrem zu sein. Frustrierend, dass die Attribute, die dies steuern sollen, nicht zu funktionieren scheinen. Ja, die neuesten Tools werden ausgeführt (ISE WebPack 14.7). Danke noch einmal.
@BradRobinson Ich habe zuvor festgestellt, dass das ram_styleAttribut nur zum Erzwingen von verteiltem RAM funktioniert und nicht umgekehrt. Ich spüre deinen Frust!
Rechts! Nun, zumindest nicht nur ich dann. :)

Also ein kurzes Follow-up ... als Teil der Umstellung auf Coregen-Block-RAM habe ich meinen vorhandenen RamTrueDualPort in ein anderes Modul gepackt, das effektiv gerade durchgelaufen ist. (Die Absicht war, Generika zu verwenden, um die tatsächliche zugrunde liegende Implementierung zu wechseln).

Es stellte sich heraus, dass ich das nicht brauchte - einfach das ursprüngliche Modul in ein anderes zu packen, reichte für XST aus, um Block-Ram für die kleineren Blöcke abzuleiten.

Stelle dir das vor...