- Introducción.
Ø RAM distribuida que se implementa en
look-up table.
Ø Bloques de RAM, la cual es disponible en
algunos dispositivos.
Ø RAM en chips externos
- RAM distribuida
Los FPGA virtex
de xilinx entre otros tienen un numero de bloques lógicos configurables (CLBs)
los cuales proveen tanto la lógica como el almacenamiento. Cada CLB está hecho
de un número de slices y cada slice contiene 2 Look-Up Table (LUTs). Cada LUT
puede ser configurada como 16 por 1 bit
de RAM síncrona, la cual es llamada RAM distribuida, debido a su naturaleza
debe ser disponible en cualquier lugar a través del circuito del usuario, en
vez de concentrarse en una área como un bloque de RAM. Provee superficialmente
RAM distribuida a través del chip y es muy adecuada para aplicaciones DSP.
Dos LUTs dentro
de un slice puede ser combinados para crear un 16 por 2-bit o 32 por 1-bit de
RAM síncrona (la dirección es compartida
cuando se lee o escribe puertos) o bien 16 por 1-bit de puerto doble de RAM
síncrona (dirección separada para lectura y escritura de puertos). RAMs mucho más profundas o amplias pueden ser
construidas usando estos elementos en paralelo. Recordar que esto introduce más
retardos en la lógica debido a los multiplexores o a los requerimientos de
ruteo entre CLBs, para diseños de alta velocidad vale la pena considerar
pipelining como acceso de memoria para este tipo de RAM usando registros entre
los CLBs.
Aquí se muestra
un ejemplo de cómo declarar la RAM distribuida en Handel-C:
·
Un
solo puerto, 16 de entrada, 8-bit RAM (4 slice).
ram
unsigned 8 MyRAM[16];
·
Un
puerto doble, 16 de entrada, 8-bit RAM (8 slice).
mpram MyRAM
{
//Escribe puerto
ram
unsigned 8 ReadWrite[16];
// Lee puerto
ram
unsigned 8 Read[16];
};
- Cabe señalar que usando RAM distribuida como puerto doble la RAM desaprovecha la mitad de los bits disponibles de almacenamiento (los datos son escritos en dos localidades simultáneas). Vale la pena considerar usar bloques de RAM para hacer más eficiente el uso del espacio ya que los bloques de RAM tienen un verdadero acceso doble de puertos.
- Bloques de RAM
La
familia virtex de xilinx y otros proveen bloques dedicados de puertos dobles de
RAM verdaderos, conocidos como bloques de memoria SelectRAM. Estos bloques
proveen un uso efectivo de los recursos sin sacrificar la memoria distribuida
SelectRAM existente o recursos lógicos. Los bloques de memoria SelectRAM son
completamente síncronos y fáciles de analizar sus tiempos así como su
inicialización de valores.
La
memoria dedicada está disponible en bloques 4 Kilobits (18 Kilobit en
Virtex-II), con cada bloque operando completamente síncrono y siendo verdadero
puerto doble de memoria (dos puertos, ambos lectura/escritura). Sobre la
tecnología virtex cada puerto permite lecturas y escrituras sobre relojes
independientes y pueden ser configurados como 4K x1, 2K x2, 1k x4, 512 x8 o 256
x16 configuraciones. Esto permite que la RAM pueda ser usada como buffers de
alta velocidad para transferencia de datos.
Los
bloques pueden ser combinados para crear amplias y muy profundas memorias. Un
verdadero bloque de puerto doble SelectRAM tiene capacidad de crear FIFOs con
relojes independientes corriendo por arriba de 250 Mhz. Los bloques SelectRAM
ofrecen muchas ventajas en aplicaciones de redes y comunicaciones las cuales
requieren actualizaciones de memoria sin retardos en los accesos de lectura.
En
Handel-C un bloque de RAM puede ser especificado usando la directiva with {block = 1}, por ejemplo:
·
Un
solo puerto, 8-bit, 512 bloques de entrada RAM (1 x 4kbit bloques de RAM,
recursos de Virtex).
ram
unsigned 8 MyRAM[512] with {block = 1};
·
Un
puerto doble, 8-bit, 512 bloques de entrada RAM (1 x 4kbit bloques de RAM,
recursos de Virtex).
mpram MyRAM
{
ram
unsigned 8 ReadWriteA[512];
ram
unsigned 8 ReadWriteB[512];
};
MyRAM
with {block = 1};
Nótese que el bloque de RAM de
puerto doble no requiere más recursos que una implementación de un solo puerto,
así es como el bloque de doble puerto de RAM es verdadero.
- RAM vs ARRAY
En
la programación en lenguaje C para microprocesadores los array se usan para el
almacenamiento de datos. En Handel-C los arrays son implementados simplemente
como números de registros, cada uno puede ser accesado por varios procesos en
un solo ciclo de reloj (uno pude estar escribiendo y otros procesos puede estar
leyendo). Si tu usas una constante para estar cambiando la posición del array
entonces el compilador sabe en tiempo de ejecución que registro va hacer usado
y puede rutearlo directamente. Si tu usas una variables para estar cambiando la
posición de array entonces el compilador va a construir un multiplexor que
cambiara los registros, esto podría ser potencialmente muy tardado e introducir
retardos significativos en la lógica. Si tú necesitas acceso aleatorio a
algunos datos entonces deberías considerar usar RAM. Tu solo puedes leer o
escribir en RAM una vez por ciclo de reloj (a menos que se utilice RAM multi
puerto), entonces eso tomaría un efecto de como diseñas tu programa. Por
ejemplo el siguiente código construiría un hardware ineficiente:
unsigned
8 MyData[256];
static
unsigned 8 i = 0;
while(1)
{
do
{
par
{
MyData[i] = i;
i++;
}
} while(i
!=0);
}
Mientras que cambiando la
declaración de MyData a tipo RAM producirá un diseño más eficiente:
ram
unsigned 8 MyData[256];
- Inicializando RAMs, ROMs y ARRAYs
Inicializar RAMs,
ROMs y arrays puede ahorrar valiosos ciclos de reloj y lógica en los diseños.
Cuando el FPGA es configurado los contenidos de alguna memoria de acceso
aleatorio o registros pueden ser inicializados con datos indefinidos. Por
ejemplo este código:
ram
unsigned 8 MyRam[8];
void
main()
{
unsigned 4
counter;
counter = 0;
while(counter
!= 8);
{
par
{
counter++;
MyRam[counter] = counter;
}
}
HacerAlgo();
}
Puede ser escrito más eficientemente
como:
ram
unsigned 8 MyRam[8] = {0,1,2,3,4,5,6,7};
void
main()
{
HacerAlgo();
}
Esto elimina toda la lógica y
ciclos de reloj asociados con la inicialización de RAM de algunos datos.
- Usando bloques de RAM entre dos dominios de reloj
Los bloques de
RAM son muy usados para la serializacion/deserealizacion de datos entre
dominios de reloj en los FPGA. Por ejemplo si un FPGA está leyendo datos a una
alta velocidad entre sus pines de entrada no es posible procesarlos a tales
velocidades del reloj, un segmento de código puede estar escribiendo los datos
de lectura que entran de forma serial y escribirlos en un puerto de bloque de
RAM. En otra sección de código posiblemente en un dominio de reloj diferente se
están leyendo los datos en paralelo por el segundo puerto y se están
procesando. Lo contrario puede ser hecho o serializar los datos en el otro
extremo.
En Handel-C esto
puede ser realizado usando dos
diferentes archivos fuentes, cada uno utilizando su propia declaración de reloj
y main principal. En este simple ejemplo, uno de los relojes es cuatro veces
más rápido que el otro; aquí no es necesario que un reloj deba ser múltiplo de
otro, siempre que el buffer sea lo suficientemente grande como para tratar con
él.
- FILE A
//
Declaración de una estructura RAM de puerto doble
mpram
DeSerialiseRAM
{
ram unsigned 1
Write[16];
ram unsigned 1 Read[4];
};
//
Definición de quien va a usar la RAM
mpram
DeSerialiseRAM MyRAM with {block = 1};
// El
reloj es 4 veces la velocidad del otro dominio
set
clock = external “P35”;
// Nuestra
interfaz hacia el mundo real
interfase
port_in(unsigned 1 signals_to_HC) read();
void
main()
{
unsigned 4
IndexCounter; // Apuntador de la RAM
unsigned 1
InputRegister; // Registro para los
datos de entrada
// Primer registro
InputRegister = read. signals_to_HC;
while(1)
{
par
{
// Adquiere algunos datos seriales
InputRegister = read. signals_to_HC;
// Escribe los datos seriales en el
puerto doble de la RAM
MyRAM.Write[IndexCounter] =
InputRegister;
// Incrementa el apuntador
IndexCounter++;
}
}
}
- FILE B
mpram
DeSerialiseRAM
{
ram unsigned 1
Write[16];
ram unsigned 1 Read[4];
};
//
Declaración de la RAM como definición externa
extern
mpram DeSerialiseRAM MyRAM with {block = 1};
// El
reloj es 1/4 la velocidad del otro reloj de dominio
set
clock = external_divide “P35”
4;
macro
proc HacerAlgo(In)
{
delay; // Hacer algo aqui
}
void
main()
{
unsigned 2
IndexCounter; // Apuntador para el
doble puerto de la RAM
unsigned 1
InputRegister; // Registro para los
datos de la RAM
while(1)
{
par
{
// Registro de los datos de RAM
InputRegister =
MyRAM.Read[IndezCounter];
// Incrementa el apuntador de la RAM
IndexCounter++;
/* Cuatro procesos en paralelo para
hacer frente a los datos seriales*/
HacerAlgo(InpuntRegister[0]);
HacerAlgo(InpuntRegister[1]);
HacerAlgo(InpuntRegister[2]);
HacerAlgo(InpuntRegister[3]);
}
}
}
No hay comentarios.:
Publicar un comentario