Inicialización de FPGA BRAM

Necesito crear muchos bloques BRAM en mi diseño (Altera). Cada uno tiene un contenido de memoria único, determinado a priori mediante un algoritmo.

Antes, estaba configurando un parámetro para que cada celda BRAM leyera desde un .MIF, pero esto hizo que mi tiempo de compilación tomara una eternidad.

Otro enfoque que inventé fue permitir la población "dinámica" de la memoria; el controlador de host podría enviar símbolos a la FPGA para llenar sus bloques BRAM. Esto es un poco más complicado de lo que me gustaría.

Esperaba que hubiera una manera de inicializar BRAM con un literal. Cada bloque tiene solo 1 bit x 256, por lo que el código HDL resultante ni siquiera se vería tan espantoso.

¿Alguien sabe cómo hacer esto con la IP BRAM de Altera, o quizás incluso con la de Xilinx?

ACTUALIZACIÓN 31/08/2016:

Hola chicos, en realidad encontré una solución casi "llave en mano" muy fácil para la inicialización de BRAM en Altera. En Quartus, hay plantillas VHDL y Verilog integradas que pueden inferir automáticamente BRAM. Estas plantillas tienen utilidades de inicialización de memoria incorporadas que el usuario puede modificar para completar con los datos que desee (como un vector de bits de un genérico). Consulte esta página de ayuda de Quartus .

Respuestas (5)

Definitivamente es posible inferir bloques de RAM directamente desde HDL. Dependiendo del tamaño y la configuración, la cadena de herramientas podría ubicarlos en RAM de bloque o en RAM distribuida.

Aquí hay un ejemplo de verilog muy simple:

module rom
(
    input clk,
    input [3:0] addr,
    output [7:0] data
);

reg [7:0] mem[2**4-1:0];
reg [7:0] output_reg = 8'd0;

initial begin
    mem[4'h0] = 8'h00;
    // ...
    mem[4'hF] = 8'h00;
end

assign data = output_reg;

always @(posedge clk) begin
    output_reg <= mem[addr];
end

endmodule

He usado un código similar en las cadenas de herramientas de Xilinx y Altera y, en general, ambas herramientas inferirán los componentes de RAM adecuados. Esto también funciona para RAM. Y se puede usar para almacenar valores en una tabla de búsqueda que se calculan en el momento de la síntesis, aunque históricamente Quartus ha tenido algunos problemas serios con esto, a saber, con hacer punto flotante/trigonometría en el momento de la síntesis.

Ejemplo de precomputación de contenidos de ROM en verilog: https://github.com/alexforencich/verilog-dsp/blob/master/rtl/sine_dds_lut.v

Es gracioso que deberías preguntar. Recientemente he estado luchando con la cadena de herramientas Vivado de Xilinx, tratando de migrar un diseño ISE antiguo que incluye algunas ROM basadas en BRAM cuyo contenido está definido por archivos .coe (coeficiente).

Finalmente tuve que renunciar a él: las herramientas generadoras de IP de Vivado seguían perdiendo el rastro de los archivos .coe, lo que provocaba que la síntesis fallara por completo. Nunca encontré una solución que funcionara dos veces seguidas...

Terminé escribiendo una secuencia de comandos Perl relativamente simple que convierte un archivo .coe en una caseinstrucción RTL pura, y ha funcionado bien. Vivado infiere correctamente una ROM basada en BRAM y todo está bien. No tengo el script en esta máquina en este momento, pero si te interesa, lo adjuntaré más tarde. Sería fácil adaptarlo para que también tome archivos .mif como entrada.


Aquí hay un fragmento de la documentación del script que explica lo que hace:

# The standard .coe file contains two statements:
# =========
# memory_initialization_radix=2;
# memory_initialization_vector=
# 0000000000000000,
# 0000010100000010,
# ....
# 0000000000000000;
# =========
# The argument to memory_initialization_radix gives the radix (in decimal)
# used for the individual values of the memory_initialization_vector list.
#
# For now, we're just going to make the following assumptions:
# * The .coe file is laid out exactly as shown above, with one word per line.
# * The radix is either 2 or 16.
# * The number of words indicates the address size of the ROM.
# * The width of the words indicates the data size of the ROM.
#
# We will convert the .coe data into a Verilog source file with the following
# structure:
# 
# module <filename> (
#   input           [5:0] addra,
#   output reg     [15:0] douta,
#   input                 clka
# );
# 
#   always @(posedge clka) begin
#     case (addra)
#     6'h00: douta <= 16'b0000000000000000;
#     6'h01: douta <= 16'b0000010100000010;
#     ....
#     6'h3F: douta <= 16'b0000000000000000;
#     endcase
#   end
# endmodule

Tenga en cuenta que los nombres de los puertos son los mismos que los utilizados por el generador de IP, por lo que este módulo de código de comportamiento es un reemplazo directo del módulo generado.

¿Podría dar más detalles sobre esta declaración del caso? ¡No sabía que Vivado era tan inteligente! Siempre pensé que si quería usar BRAM, tenía que crear una instancia de IP. ¿Podrías enviarme un fragmento de código?
Dado que la propia documentación del script muestra un ejemplo de este tipo, lo he agregado arriba. Sin embargo, quiero modificar algunas cosas menores antes de mostrar el código.

Siempre puede instanciar una BRAM usando el atributo 'bloque' para una declaración de memoria (Esto infiere BRAM en la arquitectura FPGA). No necesita ningún núcleo de IP para hacer esto y puede inicializarlo con los datos que desee, de la misma manera que lo asignaría a una señal o vector.

He estado usando esto para Xilinx FPGA desde hace algún tiempo y creo que esto también funcionará para el dominio de Altera, ya que es una pieza de código y no un parámetro central de IP.

He usado Xilinx BRAM inicializados a constantes contenidas dentro del flujo de programación. Eso fue en los días en que acababa de salir V4, pero espero que la función no se haya eliminado en las herramientas actuales.

Hay un indicador en Xilinx CoreGen en la memoria que establece en 'ROM' en lugar de 'RAM', y luego coloca las tablas de inicialización en línea en el VHDL para compilar.

No sé nada de Altera, pero es una instalación tan obvia que debe estar allí, solo necesito buscarla.

He estado investigando durante una semana y parece que para Altera solo puede inicializar BRAM "en línea" o con archivos MIF/HEX. :\ Sin embargo, seguiré buscando.

Además de las otras respuestas, también puede realizar la inicialización desde un solo parámetro. Algo como este ejemplo de Verilog debería ser suficiente:

module paramInitialisedROM #(
    parameter WIDTH = 1,
    parameter DEPTH = 256,
    parameter MEM_INIT = 256'd120310230123  //Or whatever, key thing is to make it the correct size (WIDTH*DEPTH).
)(
    input                  clock,
    input      [DEPTH-1:0] address,
    output reg [WIDTH-1:0] data
);

localparam WORDS = 1<<DEPTH; //Number of words in ROM.

// Create an inferred ROM of the correct size
reg [WIDTH-1:0] rom [WORDS-1:0]; //It is possible to add an altera derective 
                                 //to this to specify BRAM or MLAB, but I forget
                                 //the syntax of that.

// ROM Initialisation
integer idx;
integer offset;
initial begin
    for (idx = 0; idx < WORDS; idx=idx+1) begin //Count through each word in the rom
        offset = idx * WIDTH; //The offset into the parameter is the current index times the with
        rom[idx] = MEM_INIT[offset+:WIDTH]; //Set the current rom word to the correct chunk in the parameter
    end
end

//Clocked Read from ROM
always @ (posedge clock) begin
    data <= rom[address];
end

endmodule

Básicamente, cuando cree una instancia del módulo, asegúrese de proporcionar un parámetro del tamaño correcto. Por ejemplo, si lo convierte en una ROM de 16x4b, necesitará que el parámetro se especifique como de 64 bits o más, por ejemplo:

paramInitialisedROM aRomInstance #(
    .WIDTH(4),
    .DEPTH(4),
    .MEM_INIT(64'd120310230123)
)(
    .clock(clock),
    .address(address),
    .data(data)
);

Los datos en el parámetro están organizados de modo que los WIDTHLSB se utilicen para la primera palabra. El siguiente WIDTHfragmento es la siguiente palabra, y así sucesivamente hasta completar todas las palabras.

forEl compilador optimizará completamente el bucle durante la inicialización, por lo que no cuesta ninguna lógica. De hecho, todo el initialbloque se convierte en el valor inicial de la memoria.

Como se trata de memoria inferida, debería funcionar perfectamente tanto en las herramientas de Altera como en las de Xilinx.


Nota: no he probado compilar esto, pero el principio debería funcionar bien. Si encuentra algún error de sintaxis, siéntase libre de editar en las correcciones.