Inferir RAM de bloque de doble puerto

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.RAM de bloque de síntesis Xilinx

Esto es parte de un proyecto más grande (que se puede ver aquí) . El módulo que se está discutiendo es el "fontROM"

Informe de síntesis completo

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;
¿Está seguro de haber enviado su informe XST completo aquí?
¿Ha mirado su informe de utilización?

Respuestas (4)

Problema con el Diseño #1

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/ )

Problema con el Diseño #2

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 Bun variablelugar 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   __________|"""|_________

Inferir BlockRam de doble puerto con XST

(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 widthy 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 0x00contiene 0xcafey escribe 0xbabeen 0x00, el ciclo posterior a la escritura se mostrará 0xcafeen 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 0xbabeun 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).

Cortesía de la misma página en la que se inspiró el segundo diseño (solo tenía que desplazarse hacia abajo)

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;