El propósito de este proyecto es el de ilustrar el proceso de diseño de un coprocesador en un sistema SoC (ZYBO),soporta el cálculo de la distancia Euclidiana entre dos vectores, con el fin de comparar/caracterizar la latencia entre una implementación en sistema hererogéneo con Programmable System (PS) y Programmable Logic (PL)(FPGA) con respecto a implementación exclusivamente en Processing Subsystem (PS)(ZYNQ7). El proyecto se desarrolló usando las siguientes herramientas:
- Software Vitis HLS 2021.2
- Software Vitis 2021.2
- Software Vivado 2021.2
- Tarjeta de desarrollo ZYBO
El sistema operativo utilizado fue Windows 10 / Windwos 11 / Manjaro Linux (Gnome 41.3).
Los tiempos estimados de síntesis e implementación fueron tomados operando en un computador con las siguientes caracterisiticas:
- Procesador: Intel Core i7-10750H CPU @ 2.60GHz
- Memoria (RAM): 12 GB @ 2933 MHz
Para reproducir la síntesis del coprocesador mediante Vitis HLS se utilizan los archivos fuente que se encuentran en la carpeta \VITIS_HLS_SRC
dentro de este repositorio. Se deben seguir las instrucciones descritas a continuación:
-
Abrir Vitis HLS 2021.2, seleccionando la opción
Create Project
. -
Elegir un nombre y ubicación para el proyecto creado y hacer click en el botón
Next >
. -
Añadir los archivos fuente correspondientes a archivos de diseño, estos son
EucHW.cpp
,EucHW.h
yspecs.h
. En esta misma ventana se debe definir la función principal, en este caso,EucHW
. Luego hacer click en el botónNext >
. -
Añadir los archivos fuente correspondientes a archivos de testbench, estos son
EucSW.cpp
,EucSW.h
yEucTB.cpp
. Luego hacer click en el botónNext >
. -
Elegir nombre para la solución, escoger el periodo del reloj a utilizar en la síntesis (se conserva el valor por defecto de 10 ns para obtener un estimado del timing comparable con el esperado al cargar el diseño en la tarjeta).
-
En la sección
Part Selection
,Parts
, especificar la tarjeta en la cual se implementará el diseño, en este casoXC7Z010CLG400-1
. -
Conservar la configuración por defecto en la sección
Flow Target
y hacer click en el botónFinish
.
El objetivo es experimentar con distintos tamaños de vectores, así como con distintos tipos de variables, realizan versiones trabajando con enteros de 32 bits y flotantes de 32 bits, por lo que, modificando el código fuente en \VITIS_HLS_SRC\specs.h
, se puede configurar los parámetros para obtener el diseño deseado.
- En las lineas 8-9 se define el largo del vector de la siguiente forma, donde se descomenta el largo deseado, o simplemente se modifica la línea para definir el tamaño de los vectores.
#define LENGTH 1024 /* Largo de vectores para 1024 palabras */
//#define LENGTH 128 /* Largo de vectores para 128 palabras */
- En las lineas 5-6 se define el tipo de dato a operar, donde se puede cambiar el tipo de dato deseado.
//typedef int T_in; /* Version entero sin signo */
typedef float T_in; /* Version punto flotante */
En el archivo \VITS_HLS_SCR\eucHW.cpp
debe escoger entre los siguientes pares de líneas dependiendo de los tipos de variable a operar. Dejando descomentada la línea de interés y la linea descartada se comenta (esto último es solo una recomendación para poder migrar de una versión del diseño a la otra sin modificar demasiado el código)
- Líneas 32-31:
*y_sqrt = hls::sqrtf(res); /* raiz cuadrada para números de punto flotante */
//*y_sqrt = hls::sqrt(res); /* raíz cuadrada para números enteros */
- Lineas 24-25:
//#pragma HLS PIPELINE ii=2 /* Operaciones de enteros */
#pragma HLS PIPELINE ii=160 /* Operaciones flotantes */
El proceso de síntesis es el mismo para todos los diseños a implementar.
- Sintetizar el proyecto haciendo click en el botón
Run
de la barra superior del Software oRun C Synthesis
ubicado en la secciónFlow Navigator
.
Si todo ha ido como corresponde, la síntesis debiese entregar resultados satisfactorios cumpliendo con las restricciones de timing (sin slack negativo), y un uso de recursos fisicamente implementable en la tarjeta de desarrollo a utilizar.
En la Figura a continuación se muestra los resultados de síntesis para vectores de 1024 palabras con tipo flotante de 32 bits:
-
Para validar el diseño y comprobar que se comporta como se esperaba, se ejecuta la Cosimulación haciendo click en botón de
Run Cosimulation
ubicado en la secciónFlow Navigator
. -
Exportar IP hacienco click en el botón
Export RTL
en la secciónFlow navigator
. Esta acción genera un archvio .zip, el cual al ser descomprimido puede ser añadido aVivado
como se mostrará más adelante.
En esta sección se explica el uso de los pragmas implementados al realizar la sección anterior, siguiendo la función definida en SRC_VITIS_HLS\EucHW.cpp
.
-
pragma ARRAY_PARTITION
Este comando separa un arreglo de datos y genera arreglos más pequeños o de un solo elemento almacenandolos en bloques de memoria RAM individuales. Sintaxis:c #pragma HLS array_partition variable=<name> <type> factor=<int>
Dondevariable
especifica la variable a la cual se le aplica la directiva,type
sporta block, cyclic y complete, que determinan la forma en que se re ordenan los datos.factor
indica el largo en unidades de cada particion generada. -
pragma INTERFACE
Este comando define como serán creados los puertos de salida y entrada del PL en al momento de la síntesis. Sintaxis:
#pragma HLS interface mode=<mode> port=<name> storage_impl=<name>
Donde mode
especifica el el protocolo a utilizar en el puerto, en este caso será axilite
. El parámetro port
representa el nombre del argumento de la función al cual se le aplicara el protocolo especificado.
pragma PIPELINE
Este comando se asegura resolver la violación de intervalo de iniciación, manteniendo el pipline lleno. Sintaxis:
#pragma HLS pipeline ii=<iterations>
Donde ii
Especifica el intervalo de iniciación deseado para el pipeline.
Si bien el diseño pretende realizar operaciónes entre dos vectores, como argumento de entrada se entrega únicamente un vector, corresondiente a los dos vectores que se operarán, pero concatenados. De esta forma el argumento de entrada es un vector del doble del largo definido:
T_in X[2*LENGTH]
Finalmente la función main
del diseño queda como se muestra a continuación:
void eucHW(T_in *y_sqrt, T_in x[2*LENGTH])
{
#pragma HLS INTERFACE mode=s_axilite port=x storage_impl=bram
#pragma HLS INTERFACE mode=s_axilite port=y_sqrt
#pragma HLS INTERFACE mode=s_axilite port=return
#pragma HLS ARRAY_PARTITION variable=x type=cyclic factor=32
T_in res = 0;
MainLoop: for (int i = 0; i < LENGTH; ++i)
{
#pragma HLS UNROLL factor=32
//#pragma HLS PIPELINE ii=2 /* Operaciones de enteros */
#pragma HLS PIPELINE ii=160 /* Operaciones flotantes */
res += (x[i+ LENGTH] -x[i])*(x[i+ LENGTH] -x[i]);
}
*y_sqrt = hls::sqrtf(res); /* raiz cuadrada para números de punto flotante */
//*y_sqrt = hls::sqrt(res); /* raíz cuadrada para números enteros */
return;
Para cualquier diseño de coprocesador que se desee implementar, los pasos a seguir son equivalentes, solo debe importar el repositorio IP correspondiente:
- Abrir Vivado y crear un nuevo proyecto con
Create Project
. - En la sección
Project name
elegir un nombre y directorio para el proyecto. Avanzar. - En la sección
Project type
conservar las configuracionesRTL project
y todas las demas casillas desmarcadas. - En
Add Source
click en siguiente (no se necesitan fuentes para este proyecto). - En
Add Constraints
click enAdd File
y seleccionar el archivozybo.XDC
presente en el respoitorio. - En
Default Part
,Boards
elegirZYBO
si es necesario, descargar y Finalizar. - En
Flow navigator
,IP INTEGRATOR
seleccionarCreate Block Design
. - Una vez abierto, añadir los siguientes componentes con sus respectivas configuraciones:
- ZYNQ7 Processing System -> Re-customize IP:
- Interrupts, Fabric Interrupts, PL-PS Interrupt Ports, IRQ_F2P.
- Clock Configuration, Input Frequency (MHz) = 50, PL Fabric Clocks, FCLK_CLK0 = 100.
- AXI GPIO -> Re-customize IP:
- IP Configuration, All Output ; GPIO Width = 2 ; Enable Interrupts.
- Concat.
- jb (puerto de salida Pmod) -> Design, click derecho, Create Port:
- Port name = jb; Direction = output; Type = Data; Create vector: from 1 to 0.
- ZYNQ7 Processing System -> Re-customize IP:
- Abrir Flow Navigator, Project Manager, IP Catalog y con click derecho en este abrir la opción
Add Repository
y añadir el bloque ip para el diseño de coprocesador deseado, los cuales se encuentran en la carpetaIP_SRC
de este repositorio. - Conectar manualmente los siguientes puertos:
- Puerto jb[1:0] con la salida de AXI GPIO, gpio_io_o[1:0].
- ip2intc_irpt de AXI GPIO con una de las entradas del modulo Concat.
- interrupt del bloque Euchw a una de las entradas del modulo Concat.
- dout del modulo Concat al puerto IRQ_F2P[0:0] del modulo ZYNQ7.
- Correr
Run Block Automation
,Run Connection Automation
(marcando todas las casillas). - Verificar el diseño con
Validate Design
(dos veces si es necesario). - En Soruce, click derecho en design_1, Create HDL Wrapper, Let Vivado manage wrapper and auto-update, OK.
- En Flow Navigator, Program and debug, Generar bitstream.
- En File, Export, Export hardware, Include bitstream, elige nombre y carpeta de destino.
Para la implementacion de cualquiera de los coprocesadores diseñados, se debe seguir los siguentes pasos:
- Crear un nuevo proyecto en Vitis IDE ( o abrirlo usando
launch vitis
desde Vivado ). - Elegir el directorio del workspace, Launch.
- Del menu de bienvenida, abrir
Create Application Project
,Next >
. - Seleccionar
Create a new aplication from hardware (XSA)
. - En
Hardware Specification
,XSA File
seleccionar el archivo.xsa
creado al exportar el hardware en el tutorial de Vivado, En la carpeta\XCA_SRC
dentro del repositorio se encuentran dos.xsa
disponibles.Next >
. - Elegir un nombre para el proyecto,
Next >
. Next >
.- Del menu de template, elegir
Empty application(C)
,Next >
y finalizar. - Una vez abierto el projecto, dirigirse a
src
, clic derecho,new
,File
. - En el menu de
Create New File
, expandir opción<< Advanced
, seleccionarLink to file in the system
,Browse
, sellecionar el archivo\VITIS_SRC\main.c
disponible en el repositorio.
Importante:
Para lograr construir las librerías en el proyecto es necesario hacer un ajuste en las configuraciones:
- En Explorer, clic derecho en
[standalone_ps7_cortexa9_0]
. - Abrir la opción
C/C++ Build Settings
. - En C/C++ Build, seleccionar Settings.
- En configuration : Debug [Active]
- Navegar a Tool Settings, ARM v7 gcc linker, Libraries.
- En Libraries (-l) añadir la opcion
m
.
Para adaptar el código main.c
a los distintos diseños implementados, se deben modificar los siguientes parametros según corresponda:
- Para vectores de entrada de distintos largos, es necesario modificar entre las líneas 20-25:
#define VECTOR_SIZE 2*1024 /* para vector de 1024 palabras */
//#define VECTOR_SIZE 128*2 /* para vector de 128 palabras */
#define BUFFER_SIZE 64 /* para vector de 1024 palabras */
//#define BUFFER_SIZE 4 /* para vector de 128 palabras */
#define BRAMS 32
Donde BRAMS se mantiene dependiendo del factor de unroll y array partition usado para la sintesis de hls, el valor de BUFFER_SIZE es igual a VECTOR_SIZE/BRAMS
.
- Para vectores de distintos tipo, entre las líneas 43-44, descomentar/comentar según corresponda al diseño:
typedef float T_in; //Para variables de tipo flotante
//typedef int T_in; //Para variables de tipo entero
- Además, modificar entre las líneas 99-100 según corresponda al diseño:
//xil_printf("%d\n", results[0]); // Para variables de tipo entero
xil_printf("%f\n", results[0]); // Para variables de tipo flotante
- Finalmente, es necesario modificar las líneas 181 y 200 para que la generación de vectores aleatorios en el PL sea de
INT
oFLOAT
dependiendo de la necesidad.
generateVector(txbuffer, INT); // INT para vectores int, FLOAT para vectores float.
Para implementar la operación de la distancia euclidiana entre los vectores de entrada se creó la siguiente función en C dentro de el archivo fuente main.c
.
double eucDistSW( T_in X[VECTOR_SIZE]){
double sum = 0;
for (int i= 0; i < VECTOR_SIZE/2; i++){
sum += (X[i]- X[i + VECTOR_SIZE/2])*(X[i]- X[i+VECTOR_SIZE/2]);
}
return sqrt(sum);
}
La cual recibe los vectores de entrada concatenados en X
y realiza la operación correspondiente.
Adicionalmente, se programó el script de Python \PYTHON_SRC\serial_test.py
donde se pueden generan un número variable de instancias de prueba, donde se reciben los resultados obtenidos por el PL y el PS. Para cada instancia se evalúa si el error numérico obtenido cumple con cierto margen de error, en este caso se consideró que un resultado es válido si presenta un error menor al 1 % respecto al resultado que se obtendría realizando la misma operación mediante software.
Las versiónes crítica del proyecto, es decir, cuando se trabajan vectores de 1024 elementos de 32 bits se implementaron con una frecuencia de 100 MHz, para datos enteros y para la versión con flotantes.
Los resultados finales muestran que se cumplen todas las restricciones de timepo, logrando un WNS de 0.081 ns, como se muestra en la Figura siguiente:
Para media la latencia del sistema se dio uso un analizador lógico conectado a un pin de salida de la tarjeta de desarrollo utilizada, el cual indicaba el inicio del proceso de comunicación entre PS y PL así como la finalización de la operación de cálculo. Con este método y varias instancias de prueba se llegó a que la latencia promedio del sistema es de 562.0780 us
para la versión que opera con flotantes y una latencia promedio de ---- us
para la versión que trabaja con enteros de 32 bits.
Considerando que la frecuencia de reloj utilizada es de 100 Mhz y las latencias estimadas,se tiene que el throughtput estimado es de 1779 resultado/s
para la versión que opera con flotantes, entanto la versión que trabaja con enteros de 32 bits tiene un throughtput estimado de --- resultado/s
.
Para operaciones entre vectores de 1024 palabras:
-
Sintesis (HLS) versión enteros = 1.07 min
-
Sintesis (HLS) versión flotantes = 0.51 min
-
Sintesis (Vivado) versión flotantes = 2.57 min
-
Sintesis (Vivado) versión enteros = 2.13 min
-
Implementacion versión flotantes = 4.11 min
-
Implementacion versión enteros = 3.51 min