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.
Dies ist Teil eines größeren Projekts (das hier zu sehen ist) . Das besprochene Modul ist das "fontROM"
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;
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/ )
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 ______________|"""|_____
B
Wenn Sie stattdessen a deklarieren variable
und 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 __________|"""|_________
(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 width
und 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 0x00
enthält 0xcafe
und Sie 0xbabe
in schreiben 0x00
, wird der Zyklus nach dem Schreiben 0xcafe
am 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 0xbabe
einen 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).
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;
FarhadA
FarhadA