Xilinx XST no infiere bloque ram

3

Tengo problemas para conseguir que el diseño de mi computadora FPGA 80's encaje en una placa Papilio Duo que es un Spartan 6 - xcs6slx9. El problema radica en que la RAM se deduce como distribuida en lugar de bloque.

Versión corta: estoy usando una entidad genérica para inferir los bloques de RAM (ver más abajo) y al encontrar que para cualquier cosa hasta un ancho de dirección de 11 parece estar distribuida, un ancho de dirección de 12 o más XST es feliz para ponerlo en bloques. He intentado atributos para marcarlo como bloque, pero eso no parece funcionar.

Solución actual: amplíe el ancho de la dirección de una instancia, poniendo a cero el bit de dirección alta ... ahora el diseño encaja.

Versión larga:

El diseño requiere tres módulos ram de 2048x8bit de doble puerto. Un puerto necesita acceso de lectura / escritura (acceso a la CPU), el otro requiere solo lectura (controlador de video). Los puertos son asíncronos y se ejecutan en diferentes dominios de reloj.

Originalmente utilicé este módulo: RamDualPort para esto.

entity RamDualPort is
    generic
    (
        ADDR_WIDTH : integer;
        DATA_WIDTH : integer := 8
    );
    port
    (
        -- Port A
        clock_a : in std_logic;
        clken_a : in std_logic;
        addr_a : in std_logic_vector(ADDR_WIDTH-1 downto 0);
        din_a : in std_logic_vector(DATA_WIDTH-1 downto 0);
        dout_a : out std_logic_vector(DATA_WIDTH-1 downto 0);
        wr_a : in std_logic;

        -- Port B
        clock_b : in std_logic;
        addr_b : in std_logic_vector(ADDR_WIDTH-1 downto 0);
        dout_b : out std_logic_vector(DATA_WIDTH-1 downto 0)
    );
end RamDualPort;

architecture behavior of RamDualPort is 
    constant MEM_DEPTH : integer := 2**ADDR_WIDTH;
    type mem_type is array(0 to MEM_DEPTH-1) of std_logic_vector(DATA_WIDTH-1 downto 0);
    shared variable ram : mem_type;
begin

    process (clock_a)
    begin
        if rising_edge(clock_a) then

            if clken_a='1' then
                if wr_a = '1' then
                    ram(to_integer(unsigned(addr_a))) := din_a;
                end if;

                dout_a <= ram(to_integer(unsigned(addr_a)));
            end if;

        end if;
    end process;

    process (clock_b)
    begin
        if rising_edge(clock_b) then

            dout_b <= ram(to_integer(unsigned(addr_b)));

        end if;
    end process;

end;

Un par de problemas con esto: 1) dependiendo del ancho de la dirección, algunos se deducen como distribuidos (el problema principal que pregunto) pero también 2) los que se estaban inferiendo para bloquear RAMS se implementaron como lecturas primero, que para los relojes asíncronos tiene problemas en Spartan 6.

La única forma que encontré para solucionar el problema de la lectura en primer lugar fue hacer que ambos puertos lean / escriban con un nuevo módulo "RamTrueDualPort" de la siguiente manera:

entity RamTrueDualPort is
    generic
    (
        ADDR_WIDTH : integer;
        DATA_WIDTH : integer := 8
    );
    port
    (
        -- Port A
        clock_a : in std_logic;
        clken_a : in std_logic;
        addr_a : in std_logic_vector(ADDR_WIDTH-1 downto 0);
        din_a : in std_logic_vector(DATA_WIDTH-1 downto 0);
        dout_a : out std_logic_vector(DATA_WIDTH-1 downto 0);
        wr_a : in std_logic;

        -- Port B
        clock_b : in std_logic;
        clken_b : in std_logic;
        addr_b : in std_logic_vector(ADDR_WIDTH-1 downto 0);
        din_b : in std_logic_vector(DATA_WIDTH-1 downto 0);
        dout_b : out std_logic_vector(DATA_WIDTH-1 downto 0);
        wr_b : in std_logic
    );
end RamTrueDualPort;

architecture behavior of RamTrueDualPort is 
    constant MEM_DEPTH : integer := 2**ADDR_WIDTH;
    type mem_type is array(0 to MEM_DEPTH-1) of std_logic_vector(DATA_WIDTH-1 downto 0);
    shared variable ram : mem_type;
begin

    process (clock_a)
    begin
        if rising_edge(clock_a) then

            if clken_a='1' then

                if wr_a = '1' then
                    ram(to_integer(unsigned(addr_a))) := din_a;
                end if;

                dout_a <= ram(to_integer(unsigned(addr_a)));

            end if;

        end if;
    end process;

    process (clock_b)
    begin
        if rising_edge(clock_b) then

            if clken_b='1' then

                if wr_b = '1' then
                    ram(to_integer(unsigned(addr_b))) := din_b;
                end if;

                dout_b <= ram(to_integer(unsigned(addr_b)));

            end if;

        end if;
    end process;

end;

De modo que se solucionó el problema de lectura en primer lugar y los rams que van a bloquear el ram ahora se implementan como write-first (NB: En realidad no me importa leer-first / write-first, excepto el Spartan 6 corrupting ram read -primera edición).

Ahora el problema es obtener las instancias 2k (addrWidth 11) más pequeñas en el ram del bloque. Como se mencionó, he probado los atributos, pero todavía insiste en ponerlo en un ram distribuido. No pude encontrar ninguna documentación sobre ram_style para las variables (a diferencia de las señales) pero adiviné esto: (Note el bit ram:variable )

constant MEM_DEPTH : integer := 2**ADDR_WIDTH;
type mem_type is array(0 to MEM_DEPTH-1) of std_logic_vector(DATA_WIDTH-1 downto 0);
shared variable ram : mem_type;
ATTRIBUTE ram_extract: string;
ATTRIBUTE ram_extract OF ram:variable is "yes";
ATTRIBUTE ram_style: string;
ATTRIBUTE ram_style OF ram:variable is "block";

Ahora XST escupe esto, lo que sugiere que se entiende la sintaxis del atributo: (Note la mención de ram_extract y ram_style )

Synthesizing Unit <RamTrueDualPort_1>.
    Related source file is "C:/Documents and Settings/Brad/Projects/fpgabee/Hardware/FPGABeeCore/RamTrueDualPort.vhd".
        ADDR_WIDTH = 12
        DATA_WIDTH = 8
    Set property "ram_extract = yes" for signal <ram>.
    Set property "ram_style = block" for signal <ram>.
    Found 4096x8-bit dual-port RAM <Mram_ram> for signal <ram>.
    Found 8-bit register for signal <dout_b>.
    Found 8-bit register for signal <dout_a>.
    Summary:
    inferred   1 RAM(s).
    inferred  16 D-type flip-flop(s).
    inferred   1 Multiplexer(s).
Unit <RamTrueDualPort_1> synthesized.

Synthesizing Unit <RamTrueDualPort_2>.
    Related source file is "C:/Documents and Settings/Brad/Projects/fpgabee/Hardware/FPGABeeCore/RamTrueDualPort.vhd".
        ADDR_WIDTH = 11
        DATA_WIDTH = 8
    Set property "ram_extract = yes" for signal <ram>.
    Set property "ram_style = block" for signal <ram>.
    Found 2048x8-bit dual-port RAM <Mram_ram> for signal <ram>.
    Found 8-bit register for signal <dout_b>.
    Found 8-bit register for signal <dout_a>.
    Summary:
    inferred   1 RAM(s).
    inferred  16 D-type flip-flop(s).
    inferred   2 Multiplexer(s).
Unit <RamTrueDualPort_2> synthesized.

Sin embargo, los bloques de 2k todavía se distribuyen:

2048x8-bit dual-port distributed RAM                  : 2
4096x8-bit dual-port block RAM                        : 1

Si saco la línea de dirección redundante (es decir, la devuelvo a addrWidth = 11), las tres instancias terminan distribuidas y el diseño ya no se ajusta:

2048x8-bit dual-port distributed RAM                  : 3

¿Qué hacer? Realmente no quiero volver a Coregen para esto.

PD: Soy un aficionado a esto, sé amable.

    
pregunta Brad Robinson

3 respuestas

5

Si sabe exactamente con qué quiere terminar, no es necesario que Xst intente deducirlo a partir de un modelo de comportamiento.

Puede crear una instancia de un objeto RAM de bloque directamente en el código HDL. Los detalles sobre la sintaxis apropiada y las opciones involucradas, se pueden encontrar en Xilinx UG615: Spartan-6 Libraries Guía para diseños HDL , alrededor de la página 274 ("RAMB16BWER"). También puede utilizar la macro BRAM_TDP_MACRO, que se explica en la página 20 del mismo documento.

Tendrá que estar familiarizado con cómo funciona el elemento de RAM del bloque Spartan-6. La información sobre esto está disponible en Xilinx UG383: Spartan-6 FPGA Block RAM Resources .

(Tenga en cuenta que la RAM del bloque tiene anchos estándar de 9, 18 o 36 bits; probablemente querrá usarlo en el modo de 9 bits, y simplemente ignorar el bit adicional. Está ahí para los diseños que necesitan bits de paridad. )

    
respondido por el duskwuff
3

En primer lugar, los estilos de codificación admitidos se documentan en la guía del usuario de síntesis. Para Xilinx ISE, este sería el Guía del usuario de XST . En la página 200 y en adelante se explica qué es compatible. Lamentablemente, utiliza la antigua función conv_integer , pero ya estás utilizando los nuevos equivalentes numeric_std , y estos funcionarán igual con XST. Si puede encontrar una versión anterior de este documento, contendrá muchos más ejemplos. También hay ejemplos en ISE; ir a Editar > Plantillas de idioma > VHDL > Construcciones de síntesis > Ejemplos de codificación > RAM. Estos incluyen un ejemplo específico de "escritura primero" en el bloque RAM > Puerto dual > Puertos asimétricos.

Ahora en tus problemas:

  

Según el ancho de la dirección, algunos se deducen como distribuidos

Esto es deliberado. No es eficiente en la mayoría de los casos usar una RAM de bloque cuando el ancho de la dirección es pequeño. El tamaño 'pequeño' dependerá de la familia de dispositivos en uso, pero en general, si uno de los bits de datos se puede implementar utilizando el recurso LUT y se ajusta a un bloque lógico combinatorio (CLB), entonces también funcionará una RAM distribuida como tendría un bloque de memoria RAM.

Para su dispositivo en particular, mire UG384, que tiene una sección en 'RAM distribuida', que contiene una tabla que muestra qué tamaños de RAM se pueden implementar de manera eficiente en un solo CLB. Eso no quiere decir que las memorias más grandes no puedan usar RAM distribuida, solo que una más grande que esto no funcionará a una velocidad de reloj tan alta como la que tendría una RAM de bloque.

Además de esto, la RAM distribuida no puede implementar una memoria de puerto dual "verdadera", por lo que si tiene más de un puerto, y se usan varias direcciones de lectura por separado, o se usan varias habilitaciones de escritura por separado, XST debería inferir bloque de memoria RAM. Normalmente solo usará la interfaz de escritura de un puerto y la interfaz de lectura de otro, por lo que, en mi experiencia, esto rara vez es un factor.

Normalmente, no debería preocuparse si una memoria termina implementándose con RAM distribuida o de bloque. Piense en ello como una ventaja; Como ha inferido la RAM en lugar de instanciar un CoreGen primitivo o usado, las herramientas pueden elegir cómo implementarlo en función de los recursos disponibles en el dispositivo, las restricciones que ha configurado, la configuración de síntesis, etc.

Parece extraño que si su diseño no encaja, no esté haciendo uso de las RAM de bloque disponibles. No creo que estés haciendo nada malo. Si realmente desea forzar una RAM de bloque, puede intentar hacer clic con el botón derecho en "Sintetizar", elegir "Propiedades del proceso" y luego en "Opciones de HDL", cambiar "Estilo RAM" a "Bloquear". Tenga en cuenta que en la ayuda, la descripción de 'Auto' es:

  

XST determina la mejor implementación para cada macro.

En general, desconfiaría de cambiar esto sin una buena comprensión de exactamente por qué el diseño será mejor al forzar el bloque RAM. Esta opción también podría ser un gran desperdicio si tiene muchos recuerdos pequeños.

También puede intentar establecer el 'Objetivo de optimización' de síntesis en 'Área'. Esto podría cambiar el umbral para cuando una RAM de bloque se deduce a través de una RAM distribuida.

Por último, asegúrese de estar utilizando la última versión de la herramienta (14.7).

    
respondido por el scary_jeff
1

Entonces, un rápido seguimiento ... como parte del cambio a coregen block ram para esto, estoy envolviendo mi RamTrueDualPort existente dentro de otro módulo que efectivamente acaba de pasar. (La intención era utilizar los genéricos para cambiar la implementación subyacente real).

Resulta que no era necesario, simplemente envolver el módulo original dentro de otro fue suficiente para que XST comience a inferir el bloque ram para los bloques más pequeños.

Ve a la figura ...

    
respondido por el Brad Robinson

Lea otras preguntas en las etiquetas