Ableitung von Dual-Port-Block-RAM

Ich verwende ein Basys 2 mit 72 Kbits Dual-Port-Block-RAM. Ich habe über 100 % der verfügbaren Slices verwendet und möchte daher sicherstellen, dass Xilinx sie nicht nur mit den Zeichentabellenwerten füllt, sondern sie an den richtigen Stellen platziert. Ich bin mir sicher, dass ich noch viele weitere Möglichkeiten habe, mein Design zu optimieren, und diese Vorschläge sind sehr willkommen.

Was zeigt Xilinx, wenn es erfolgreich Dual Port Block RAM abgeleitet hat?

Benötigen Sie zwei separate Uhren, um Dual Port Block RAM zu implementieren?

Ich habe diese beiden Designs (unten) ausprobiert und beide geben scheinbar zwei Block-RAM-Elemente anstelle eines Dual-Port-Block-RAM-Elements aus.Xilinx-Syntheseblock-RAM

Dies ist Teil eines größeren Projekts (das hier zu sehen ist) . Das besprochene Modul ist das "fontROM"

Vollständiger Synthesebericht

Entwurf Nr. 1:

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity fontROM is
    generic(
        addrWidth: integer := 11;
        dataWidth: integer := 8
    );
    port(
        clk: in std_logic;
        addr_A: in std_logic_vector(addrWidth-1 downto 0);
        data_A: out std_logic_vector(dataWidth-1 downto 0);

        addr_B: in std_logic_vector(addrWidth-1 downto 0);
        data_B: out std_logic_vector(dataWidth-1 downto 0)
    );
end fontROM;

architecture Behavioral of fontROM is

    signal addr_reg_A: std_logic_vector(addrWidth-1 downto 0);
    signal addr_reg_B: std_logic_vector(addrWidth-1 downto 0);

    type rom_type is array (0 to 2**addrWidth-1) of std_logic_vector(dataWidth-1 downto 0);

    -- ROM definition
    constant ROM: rom_type := (   -- 2^11-by-8
        "00000000", -- 0
        "00000000", -- 1
        "00000000", -- 2
        "00000000", -- 3
        "00000000", -- 4
        "00000000", -- 5
        "00000000", -- 6
        "00000000", -- 7
        "00000000", -- 8
        "00000000", -- 9
        "00000000", -- a
        "00000000", -- b
        "00000000", -- c
        "00000000", -- d
        "00000000", -- e
        "00000000", -- f
        -- redacted...
    );
begin

    -- addr register to infer block RAM
    portDProcess: process (clk)
    begin
        if rising_edge(clk) then
            addr_reg_A <= addr_A;
            addr_reg_B <= addr_B;
        end if;
    end process;

    data_A <= ROM(to_integer(unsigned(addr_reg_A)));
    data_B <= ROM(to_integer(unsigned(addr_reg_B)));


end Behavioral;

Design Nr. 2 (inspiriert von diesem Artikel ):

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity fontROM is
    generic(
        addrWidth: integer := 11;
        dataWidth: integer := 8
    );
    port(
        clk: in std_logic;
        addr_A: in std_logic_vector(addrWidth-1 downto 0);
        data_A: out std_logic_vector(dataWidth-1 downto 0);

        addr_B: in std_logic_vector(addrWidth-1 downto 0);
        data_B: out std_logic_vector(dataWidth-1 downto 0)
    );
end fontROM;

architecture Behavioral of fontROM is

    signal addr_reg_A: std_logic_vector(addrWidth-1 downto 0);
    signal addr_reg_B: std_logic_vector(addrWidth-1 downto 0);

    type rom_type is array (0 to 2**addrWidth-1) of std_logic_vector(dataWidth-1 downto 0);

    -- ROM definition
    constant ROM: rom_type := (   -- 2^11-by-8
        "00000000", -- 0
        "00000000", -- 1
        "00000000", -- 2
        "00000000", -- 3
        "00000000", -- 4
        "00000000", -- 5
        "00000000", -- 6
        "00000000", -- 7
        "00000000", -- 8
        "00000000", -- 9
        "00000000", -- a
        "00000000", -- b
        "00000000", -- c
        "00000000", -- d
        "00000000", -- e
        "00000000", -- f
        -- redacted...
    );
begin

    -- addr register to infer block RAM
    portAProcess: process (clk)
    begin
        if rising_edge(clk) then
            addr_reg_A <= addr_A;
            data_A <= ROM(to_integer(unsigned(addr_reg_A)));
        end if;
    end process;

    portBProcess: process (clk)
    begin
        if rising_edge(clk) then
            addr_reg_B <= addr_B;
            data_B <= ROM(to_integer(unsigned(addr_reg_B)));
        end if;
    end process;


end Behavioral;
Sind Sie sicher, dass Sie Ihren vollständigen XST-Bericht hierher gesendet haben?
Hast du dir deinen Nutzungsbericht angesehen?

Antworten (4)

Problem mit Entwurf Nr. 1

Mir ist aufgefallen, dass Sie die beiden Ports in zwei separaten Prozessen angeben müssen , damit XST auf Dual-Port-RAM schließen kann - wenn Sie dies nicht tun, erhalten Sie die beiden Ports nicht. Durch getrennte Prozesse schlägt Xilinx auch vor, Dual-Port-RAM im XST-Benutzerhandbuch abzuleiten. Daher wird Ihr Design Nr. 1 nur Single-Port-RAM ableiten.

Sie können mein allgemeines VHDL zum Ableiten von Dual-Port-RAM mit XST am Ende dieses Beitrags sehen. (Details: http://www.fpga-dev.com/infering-dual-port-blockram-with-xst/ )

Problem mit Entwurf Nr. 2

In Ihrem Entwurf Nr. 2 tragen Sie die Adresse zweimal ein, wahrscheinlich unbeabsichtigt. <=Signalzuweisungen werden am Ende des Prozesses vorgenommen , nicht sofort. Dieser Code entspricht Ihrem, nur mit einfacheren Signalnamen:

-- sequential context (A, B, C are signals):
if rising_edge(clk) then
  B <= A;
  C <= B;
end if;

Hier C <= B;wird C nicht zugewiesen, was B in der vorherigen Zeile zugewiesen wurde, da diese Zuweisung erst am Ende des Prozesses wirksam wird. Wenn die Signale Bits und die Stimuli ein Impuls sind A, wäre dies das Ergebnis des obigen Codes:

clk _|"|_|"|_|"|_|"|_|"|_|"|
A   ______|"""|_____________
B   __________|"""|_________
C   ______________|"""|_____

BWenn Sie stattdessen a deklarieren variableund mit zuweisen, :=wird sofort zugewiesen:

-- sequential context (A, C are signals; B is variable):
if rising_edge(clk) then
  B := A;
  C <= B;
end if;

nachgeben

clk _|"|_|"|_|"|_|"|_|"|_|"|
A   ______|"""|_____________
B   __________|"""|_________
C   __________|"""|_________

Ableitung von Dual-Port-BlockRam mit XST

(Weitere Details dazu unter http://www.fpga-dev.com/infering-dual-port-blockram-with-xst/ .)

Unten ist mein parametrisiertes Modul für generisches Dual-Port-RAM. Es wird wie gewünscht Dual-Port-RAM mit XST erfolgreich ableiten.

(Entfernen Sie die Schreibfreigabesignale und schreiben Sie die Logik, um ROM anstelle von RAM zu erhalten.)

Geben Sie Breite und Tiefe mit widthund highAddr(eins weniger als gewünschte Tiefe) Generics an.

library IEEE;
use IEEE.STD_LOGIC_1164.all;

entity genRAM is
  generic(
    width     : integer;
    highAddr  : integer -- highest address (= size-1)
  );
  port(
    -- Two sets of ports (A and B), each set having ports Adress, Data in,
    -- Data out and Write enable:
    Aaddr     : in  integer range 0 to highAddr        := 0;
    ADI       : in  std_logic_vector(width-1 downto 0) := (others => '0');
    ADO       : out std_logic_vector(width-1 downto 0) := (others => '0');
    AWE       : in  std_logic                          := '0';
    Baddr     : in  integer range 0 to highAddr        := 0;
    BDI       : in  std_logic_vector(width-1 downto 0) := (others => '0');
    BDO       : out std_logic_vector(width-1 downto 0) := (others => '0');
    BWE       : in  std_logic                          := '0';
    clk       : in  std_logic
  );
end genRAM;

architecture arch of genRAM is
  subtype TmemWord is bit_vector(width-1 downto 0);
  type    Tmem     is array(0 to highAddr) of TmemWord;
  shared variable memory: Tmem;

  process(clk) is
  begin
    if (rising_edge(clk)) then
      ADO <= To_StdLogicVector(memory(Aaddr));
      if (AWE = '1') then
        memory(Aaddr) := To_bitvector(std_logic_vector(ADI));
      end if;
    end if;
  end process;

  process(clk) is
  begin
    if (rising_edge(clk)) then    
      BDO <= To_StdLogicVector(memory(Baddr));
      if (BWE = '1') then
        memory(Baddr) := To_bitvector(std_logic_vector(BDI));
      end if;
    end if;
  end process;
end arch;

Der obige Code implementiert das Read-First-Verhalten . Das heißt, wenn die Adresse 0x00enthält 0xcafeund Sie 0xbabein schreiben 0x00, wird der Zyklus nach dem Schreiben 0xcafeam Datenausgangsport angezeigt ("Daten werden zum Ausgangsport gelesen, bevor sie in den Speicher geschrieben werden").

Wenn Sie ein Write-First-Verhalten wünschen , ändern Sie die Reihenfolge des Lesens und Schreibens für beide Prozesse. Nachfolgend sehen Sie, wie es für Port A wäre:

-- excerpt for write-first behaviour:
if (AWE = '1') then
  memory(Aaddr) := To_bitvector(std_logic_vector(ADI));
end if;
ADO <= To_StdLogicVector(memory(Aaddr));

Im obigen Fall würde data-out 0xbabeeinen Zyklus nach dem Schreiben anzeigen ("Daten werden in den Speicher geschrieben, bevor der Speicherinhalt zum Ausgabeport gelesen wird").

Überprüfen Sie die MAP-Berichtsdatei (MRP), die Ihnen sagt, wie viele Blockrams verwendet werden. Vergleichen Sie das mit Ihrer Erwartung (oder Hoffnung!), um zu sehen, ob die Tools sie richtig ableiten.

Wenn Sie herausfinden möchten, woher die Blockrammen kommen, kann Ihnen PlanAhead eine hierarchische Ansicht der Elementverwendung geben.

Es stellte sich heraus, dass meine Vorhersagen richtig waren.

Wenn Xilinx Dual-Port-Block-RAM erfolgreich synthetisiert, wird es wie folgt ausgegeben:

Synthesizing (advanced) Unit <bram_tdp>.
INFO:Xst:3040 - The RAM <Mram_mem> will be implemented as a BLOCK RAM, absorbing the following register(s): <a_dout> <b_dout>
    -----------------------------------------------------------------------
    | ram_type           | Block                               |          |
    -----------------------------------------------------------------------
    | Port A                                                              |
    |     aspect ratio   | 1024-word x 72-bit                  |          |
    |     mode           | write-first                         |          |
    |     clkA           | connected to signal <a_clk>         | rise     |
    |     weA            | connected to signal <a_wr>          | high     |
    |     addrA          | connected to signal <a_addr>        |          |
    |     diA            | connected to signal <a_din>         |          |
    |     doA            | connected to signal <a_dout>        |          |
    -----------------------------------------------------------------------
    | optimization       | speed                               |          |
    -----------------------------------------------------------------------
    | Port B                                                              |
    |     aspect ratio   | 1024-word x 72-bit                  |          |
    |     mode           | write-first                         |          |
    |     clkB           | connected to signal <b_clk>         | rise     |
    |     weB            | connected to signal <b_wr>          | high     |
    |     addrB          | connected to signal <b_addr>        |          |
    |     diB            | connected to signal <b_din>         |          |
    |     doB            | connected to signal <b_dout>        |          |
    -----------------------------------------------------------------------
    | optimization       | speed                               |          |
    -----------------------------------------------------------------------
Unit <bram_tdp> synthesized (advanced).

Mit freundlicher Genehmigung der gleichen Seite, von der das zweite Design inspiriert wurde (musste nur nach unten scrollen)

Statt zwei ram_type: Block mit Port A in jedem.

Ich habe nicht herausgefunden, wie man die Designs aus dem ursprünglichen Beitrag erfolgreich in Dual-Port-Block-RAM synthetisiert oder was geändert werden muss, damit es das tut, aber zumindest weiß ich, was es zeigen sollte.

Das zweite ROM sieht ziemlich gut aus, aber warum registrierst du es zweimal? Wie wäre es mit:

-- addr register to infer block RAM
portAProcess: process (clk)
begin
    if rising_edge(clk) then
        data_A <= ROM(to_integer(unsigned(addr_A)));
    end if;
end process;

portBProcess: process (clk)
begin
    if rising_edge(clk) then
        data_B <= ROM(to_integer(unsigned(addr_B)));
    end if;
end process;