Inferir RAM de bloque de doble puerto

2

Estoy usando un Basys 2 con 72Kbits de RAM de bloque de doble puerto. Utilicé más del 100% de los segmentos disponibles, por lo que 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 se ha deducido correctamente la RAM de bloque de puerto dual?

¿Necesita dos relojes separados para implementar la RAM de bloque de puerto dual?

He intentado ambos de estos diseños (a continuación) y ambos muestran lo que parecen ser dos elementos de Bloqueo de RAM en lugar de un elemento de Doble Puerto de RAM de Bloque.

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

Informe completo de síntesis

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 por 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;
    
pregunta MLM

4 respuestas

2

Resulta que mis predicciones eran correctas.

Cuando Xilinx sintetice con éxito la memoria RAM del bloque de doble puerto, se generará de la siguiente manera:

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 de la que se inspiró el segundo diseño (solo tenía que desplácese 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 el bloque de doble puerto ram o qué necesita cambiar para hacerlo, pero al menos sé lo que debería mostrar.

    
respondido por el MLM
3

Problema con el Diseño # 1

Me he dado cuenta de que debe especificar los dos puertos en dos procesos separados para que XST deduzca la RAM de doble puerto: si no lo hace, no obtendrá los dos puertos. Los procesos separados también es la forma en que Xilinx sugiere inferir RAM de doble puerto en la Guía del usuario de XST. Por lo tanto, su Diseño # 1 solo inferirá un ram de un solo puerto.

Puedes ver mi VHDL general para inferir RAM de doble puerto con XST al final de esta publicación. (Detalles: enlace )

Problema con el Diseño # 2

En su Diseño # 2, registra la dirección dos veces, probablemente involuntariamente. Las asignaciones de señal <= se realizan al final del proceso , no inmediatamente. Este código es equivalente al suyo, solo con nombres de señal más simples:

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

Aquí C <= B; no asignará a C lo que se asignó a B en la línea anterior, ya que esa asignación solo tiene efecto al final del proceso. Si las señales son bits y los estímulos son un pulso en A , este sería el resultado del código anterior:

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

En su lugar, declarar B a variable y asignar con := se asignará de inmediato:

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

rendimiento

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

Inferir BlockRam de doble puerto con XST

(Más detalles sobre esto en enlace .)

A continuación se muestra mi módulo parametrizado para RAM de puerto dual genérico. Inferirá con éxito la memoria RAM de doble puerto, según lo deseado, con XST.

(Elimine las señales de habilitación de escritura y la lógica de escritura para obtener ROM en lugar de RAM.)

Especifique el ancho y la profundidad con los genéricos width y highAddr (uno menos que la profundidad deseada).

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 comportamiento de lectura en primer lugar . Eso significa que si la dirección 0x00 contiene 0xcafe y escribe 0xbabe en 0x00 , el ciclo después de la escritura mostrará 0xcafe en el puerto de salida de datos ("los datos se leen al puerto de salida antes de escribirlos a la memoria ").

Si desea 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").

    
respondido por el Carl
2

Verifique el archivo de informe MAP (MRP), que le dirá cuántos bloques de bloque se utilizan. Compare eso con sus expectativas (¡o espere!) Para ver si las herramientas las están inferiendo correctamente.

Si desea saber de dónde provienen los rams de bloque, PlanAhead puede darle una vista jerárquica del uso del elemento.

    
respondido por el Martin Thompson
1

La segunda ROM se ve bastante bien, pero ¿por qué la registras dos veces? ¿Qué tal:

-- 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;
    
respondido por el alex.forencich

Lea otras preguntas en las etiquetas