Estoy usando un Basys 2 con 72 Kbits de RAM de bloque de puerto dual. Utilicé más del 100 % de los segmentos disponibles y, por lo tanto, quiero asegurarme de que Xilinx no solo los llene con los valores del mapa de caracteres en lugar de colocarlos en los lugares apropiados. Estoy seguro de que tengo muchas más formas de optimizar mi diseño y esas sugerencias son muy bienvenidas.
¿Qué muestra Xilinx cuando ha inferido con éxito la RAM de bloque de puerto dual?
¿Necesita dos relojes separados para implementar RAM de bloque de puerto dual?
Probé ambos diseños (a continuación) y ambos generan lo que parecen ser dos elementos de RAM de bloque en lugar de un elemento de RAM de bloque de puerto dual.
Esto es parte de un proyecto más grande (que se puede ver aquí) . El módulo que se está discutiendo es el "fontROM"
Diseño #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;
Diseño #2 (inspirado en este artículo ):
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;
Me di cuenta de que debe especificar los dos puertos en dos procesos separados para que XST infiera la RAM de doble puerto; si no lo hace, no obtendrá los dos puertos. Procesos separados también es la forma en que Xilinx sugiere inferir RAM de puerto dual en la Guía del usuario de XST. Por lo tanto, su Diseño n. ° 1 solo inferirá ram de un solo puerto.
Puede ver mi VHDL general para inferir RAM de doble puerto con XST en la parte inferior de esta publicación. (Detalles: http://www.fpga-dev.com/infering-dual-port-blockram-with-xst/ )
En su Diseño #2, registra la dirección dos veces, probablemente sin querer. <=
las asignaciones de señales se realizan al final del proceso , no inmediatamente. Este código es equivalente al tuyo, solo que con nombres de señales más simples:
-- sequential context (A, B, C are signals):
if rising_edge(clk) then
B <= A;
C <= B;
end if;
Aquí noC <= B;
se le asignará a C lo que se le asignó a B en la línea anterior, ya que esa asignación solo surte efecto al final del proceso. Si las señales son bits y el estímulo es un pulso , este sería el resultado del código anterior:A
clk _|"|_|"|_|"|_|"|_|"|_|"|
A ______|"""|_____________
B __________|"""|_________
C ______________|"""|_____
Declarar B
un variable
lugar y asignar con :=
asignará inmediatamente:
-- sequential context (A, C are signals; B is variable):
if rising_edge(clk) then
B := A;
C <= B;
end if;
flexible
clk _|"|_|"|_|"|_|"|_|"|_|"|
A ______|"""|_____________
B __________|"""|_________
C __________|"""|_________
(Más detalles sobre esto en http://www.fpga-dev.com/infering-dual-port-blockram-with-xst/ ).
A continuación se muestra mi módulo parametrizado para RAM genérica de doble puerto. Inferirá con éxito la memoria RAM de doble puerto, según se desee, con XST.
(Elimine las señales de activación de escritura y la lógica de escritura para obtener ROM en lugar de RAM).
Especifique el ancho y la profundidad con width
y highAddr
(uno menos que la profundidad deseada) genéricos.
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;
El código anterior implementa el comportamiento de lectura primero . Eso significa que si la dirección 0x00
contiene 0xcafe
y escribe 0xbabe
en 0x00
, el ciclo posterior a la escritura se mostrará 0xcafe
en el puerto de salida de datos ("los datos se leen en el puerto de salida antes de escribirse en la memoria").
Si desea un comportamiento de escritura primero , cambie el orden de lectura y escritura para ambos procesos, a continuación se muestra cómo sería para el puerto A:
-- excerpt for write-first behaviour:
if (AWE = '1') then
memory(Aaddr) := To_bitvector(std_logic_vector(ADI));
end if;
ADO <= To_StdLogicVector(memory(Aaddr));
En el caso anterior, la salida de datos mostraría 0xbabe
un ciclo después de la escritura ("los datos se escriben en la memoria antes de leer el contenido de la memoria en el puerto de salida").
Verifique el archivo de informe MAP (MRP), que le indicará cuántos arietes de bloque se utilizan. Compare eso con su expectativa (¡o esperanza!) para ver si las herramientas las están infiriendo correctamente.
Si desea averiguar de dónde provienen los arietes de bloques, PlanAhead puede brindarle una vista jerárquica del uso de elementos.
Resulta que mis predicciones eran correctas.
Cuando Xilinx sintetice con éxito la memoria RAM de bloque de doble puerto, se generará así:
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).
En lugar de dos ram_type: bloque con puerto A en cada uno.
No he descubierto cómo sintetizar con éxito los diseños de la publicación original en un bloque de RAM de doble puerto o qué debe cambiar para que lo haga, pero al menos sé lo que debería mostrar.
Esa segunda ROM se ve bastante bien, pero ¿por qué la registras dos veces? Qué tal si:
-- 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