Buscar este blog

sábado, 22 de noviembre de 2014

UNIÓN VHDL CON HANDEL-C



En este apartado pretendemos explicar la manera de utilizar módulos diseñados en VHDL dentro de código Handel-C que estará escrito y compilado con DK (y por tanto se ejecutará en la FPGA al programarla con él “.bit” resultado de todo este proceso). De esta manera podremos pasar como entrada de un módulo valores obtenidos en el código Handel-C y utilizar los valores de salida de dicho modulo para realizar otros cálculos en el código escrito en Handel-C.


         Para comunicar el código Handel-C con VHDL ha sido necesario utilizar una definición de interface que se ajustara a la entidad de VHDL, con lo que conseguimos una instancia de esta interface así como definir los datos que serán transmitidos, tanto hacia la entidad VHDL como desde ella. La definición de un interface sigue el siguiente formato:

interface nombre_entidad_VHDL
(puerto_salida_entidad [with especificación_puerto]
{, puerto_salida_entidad [with especificación_puerto]})
nombre_instancia
(puerto_salida_entidad [with especificación_puerto]
{, puerto_salida_entidad [with especificación_puerto]})
with {busformat = “tipo_busformat”};

Donde tipo_busformat puede tomar los siguientes valores: “B”, “B[I]”, “B(I)”, “B<I>”, “BI”, “B_I” , “B[N:M]” , “B(N:M)” , “B<N:M>” , “B[N]” , “B(N)” y “B<N>” Así, tendremos que hacer tantas definiciones de interface de una entidad VHDL como instancias de dicha entidad queramos utilizar en nuestro código Handel-C, es decir, si quisiéramos tener dos instancias de un determinado tipo de entidades, tendríamos que hacer dos definiciones de su interface.

Hay que tener muy en cuenta que el nombre de la interface en el código Handel-C y de la entidad en el VHDL, los nombres de los puertos en ambos sitos y el orden de los mismos debe ser idéntico. Otra cosa en la que hay que fijarse mucho (si no, nos dará error al generar el “.bit” en el Project Navigator) es en el valor que pongamos en el “busformat” al crear los archivos VHDL finales o bien un EDIF con el DK.

El “busformat” sirve para indicar el nombre que se va a generar en el EDIF para cada componente de un vector de la entidad VHDL definida en la interface Handel-C. Por ejemplo, si uno de los puertos de nuestra entidad VHDL es de la forma salida std_logic_vector (3 downto 0), para que los archivos de salida sean generados con los nombres correctos (salida(0), salida(1), ...) es necesario poner en la definición del interface with {bus_format = “B(I)”}.

Se tienen 2 maneras para obtener el archivo final .bit del FPGA, las cuales se enuncia a continuación:

1)      Todos los archivos fuentes del proyecto son VHDL
2)      Archivo EDIF por DK y archivo NGC del módulo VHDL a integrar sintetizado por XILINX.

  • Desarrollemos las 2 formas de realizar este proceso por medio de un ejemplo para dos plataformas, una para XILINX y otra para ALTERA.

El ejemplo consistirá en integrar un CORE VHDL de un sumador de 8 bits llamado AddVHDL.vhd cuyo código fuente es:

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;

entity AddVHDL is
    Port ( x : in  STD_LOGIC_VECTOR (7 downto 0);
           y : in  STD_LOGIC_VECTOR (7 downto 0);
           z : out  STD_LOGIC_VECTOR (7 downto 0));
end AddVHDL;

architecture Behavioral of AddVHDL is
begin
                        z <= x + y;       
end Behavioral;


  • Las herramientas necesarias para realizar estos ejemplos son: 

Ø  Xilinx Design Tools ISE Suite 14.7
Ø  DK Desing suite  5.0 SP5
Ø  Synplify Pro H-2013.03
Ø  Quartus II 13.1
Ø  Tarjeta Spartan 3AN (XC3S700AN)
Ø  Tarjeta DE0 ALTERA (EP3C16F484C6N)


  • Ejemplo 1.- Plataforma XILINX todos los archivos son VHDL


  • Pasos:


I.           Hacer un nuevo proyecto en DK Handel-C llamado suma. El código de main.hcc es el siguiente:

#include "delay.hch"
set clock = external "E12";
unsigned 8 LEDS,n,m;
interface bus_out() salida(LEDS) with {data = {"W21","Y22","V20","V19","U19","U20","T19","R20"}};

interface AddVHDL(unsigned 8 z)
AddVHDL(unsigned 8 x = n, unsigned 8 y = m) with {vhdl_type = "std_logic_vector",busformat="B(I)"};
void main(void)
{  
   par
   {
       while(1)
       {
           LEDS = AddVHDL.z;
       }
      
       while(1)
       {
           par
           {
               n++;
               m++;
           }
           delay_ms(C_MHZ,1000); //1000 ms
       }
   }
}

Configuraciones de DK Handel-C




             II.      Hacer un proyecto en ISE de Xilinx llamado suma y agregar los archivos AddVHDL.vhd, main_hcc.vhd, agility.vhd, suma.vhd, suma.ufc.



           


 III.       Cambiar en la línea 197 del archivo main_hcc.vhd I0_AddVHDL_main_32 : AddVHDL por I0_AddVHDL_main_32 : entity work.AddVHDL. Después guardar e implementar todo el proceso.






  • Ejemplo 2.- Plataforma XILINX archivo EDIF por DK y archivo NGC del módulo VHDL.


  • Pasos:

I.                    Utilizando el mismo código del Ejemplo 1 anterior. La única variante es en las propiedades de DK cambiar de VHDL a EDIF. En la pestaña superior cambiar a EDIF y por ultimo construir el proyecto. 






           II.         Crear un nuevo proyecto en ISE de XILINX escogiendo XST como opción de síntesis y agregar el archivo AddVHDL.vhd. En las propiedades de síntesis deshabilitar la opción “Add I/O buffers” y por ultimo correr el proceso solo de síntesis.




   


     III.             Crear otro proyecto con ISE de XILINX para conjuntar los archivos EDIF generado por DK Handel-C y NGC generado a partir del módulo VHDL sintetizado en el paso 2.

·         Seleccionar EDIF como fuente de archivo TOP.
·         Importar el archivo EDIF ubicado en la carpeta de proyecto DK Handel-C
·         Copiar el archivo AddVHDL.NGC que se genero en ISE de XILINX cuando se sisntetizo el archivo VHDL a la nueva carpeta de proyecto.
·         Correr todo el proceso de implementacion.







  • Ejemplo 3.- Plataforma ALTERA todos los archivos son VHDL

  • Pasos:


I.                    Generar los archivos VHDL en DK Handel-C con las opciones para la plataforma Altera. Tarjet Family AletarCycloneIII y HDL output Style SynplifyPro.



         II.   Hacer un proyecto en QUARTUS II de Altera llamado suma y agregar los archivos AddVHDL.vhd, main_hcc.vhd, agility.vhd, suma.vhd, suma.tcl.






III.                 Correr el script suma.tcl




IV.                    Clic derecho en el archivo agility.vhd y en propiedades escribir en Library: agility.



V.                   Cambiar en la línea 195 del archivo main_hcc.vhd I0_AddVHDL_main_32 : AddVHDL por I0_AddVHDL_main_32 : entity work.AddVHDL. Después guardar e implementar todo el proceso de compilación. Y por último programar el FPGA.






Handel-C language reference.

















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]);
      }
   }

}


jueves, 20 de noviembre de 2014

Interfaz Handel-C con Xilinx CoreGen

  • Introducción.

La utilidad de Xilinx CoreGen contiene muchos diseños que a menudo puede ahorrar tiempo para un programador que buscan implementar una determinada función de registro como multiplicadores, sumadores, los controladores de SDRAM, y así sucesivamente. En este apartado se describe, paso a paso, cómo integrar los bloques de Xilinx CoreGen con un programa en Handel-C utilizando como ejemplo un multiplicador core de punto flotante.

  • Prerrequisitos.

Ø  Xilinx Design Tools ISE Suite 14.7
Ø  DK Desing suite  5.0 SP5
Ø  Synplify Pro H-2013.03
Ø  Tarjeta Spartan 3AN (XC3S700AN)

  • Operación Coregen

Iniciar Core generator:
inicio/programas/Xilinx Design Tools/ISE desing suite 14.7/ISE Design Tools/32-bit Tools


  • Hacer un nuevo proyecto en FILE/New Project
  Características del dispositivo FPGA


opciones de generación de bus format VHDL 


·         Seleccion Floating-point 5.0 (multiply)
·         Precisión single

·         Usar MULT18x18 Full usage
·         Seleccionar RDY en Handshaking signals
·         Clic en Generate


  • Creando la interfaz en Handel-C


En el siguiente código se muestra como crear la interface entre la IP core de la multiplicación de punto flotante generada por Coregen y Handel-C:


set clock = external "E12"; // 50 Mhz
unsigned 32 n;
unsigned 32 m;
interface floating_point_v5_0(unsigned 1 rdy, unsigned 32 result)
floating_point_v5_0(unsigned 32 a = n, unsigned 32 b = m, unsigned 1 clk =__clock) with {busformat="B<I>"};
void main(void)
{  
    unsigned 32 c;
   par
   {
       n=0x4091EB85;  // 4.56  formato IEEE 754     (31)Sign   (30-23)Exponent   (22-0)Mantissa http://www.zator.com/Cpp/E2_2_4a1.htm
       m=0xC22ECCCC;  // -43.7
   }
   while(!floating_point_v5_0.rdy){delay;}
    c = floating_point_v5_0.result; // c = a*b = -199.272  c = C34745A1  ok  :)
}


Hay que tener cuidado en la declaración de la interfaz ya que especifica explícitamente el formato de bus y que debe ser igual al generado por coregen de xilinx. Las salidas de bus de Handel-C por default es un formato no reconocido.

·         interface floating_point_v5_0(unsigned 1 rdy, unsigned 32 result) à Crea un tipo de celda llamada floating_point_v5_0 con dos puertos de salida de anchura de 1 bits y 32 bits respectivamente.
·         floating_point_v5_0(unsigned 32 a = n, unsigned 32 b = m, unsigned 1 clk =__clock) à Crea un identificador definido por el usuario con 3 entradas a, b de 32 bits y clk de un  bit, este último se le asigna al reloj global del sistema que son los 50 mhz.
·         with {busformat="B<I>"}; à La definición del busformat define el formato de los nombre de la interfaz y esta debe ser igual que los especificados y generados por Coregen.


Propiedades de DK design suite


  • Síntesis e implementación Xilinx

Ø  Crear un nuevo proyecto en ISE de Xilinx para la tarjeta spartan 3AN
Ø  Especificar como herramienta de síntesis a synplify pro
Ø  Buscar en la carpeta de proyecto de coregen el archivo creado floating_point_v5_0.NGC y copiarlo a la carpeta de proyecto de xilinx.
Ø  Agregar la librería agility.vhd y los códigos vhdl generados por Handel-C.
Ø  Programar la tarjeta spartan 3AN vía iMPACT 

Configuración en Xilinx

Librería y códigos VHDL agregados


Resultado de la síntesis del proyecto


Multiplicación corriendo en la Spartan 3AN