Buscar este blog

viernes, 21 de noviembre de 2014

ACCESO A RAM EN HANDEL-C

  • Introducción.
Handel-C permite accesar a diferentes tipos de RAM:

Ø  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