Vous êtes sur la page 1sur 22

Codiseo Hardware/Software

Ingeniera Superior en Informtica

Tutorial Handel-C y MicroBlaze


Pablo Huerta Pellitero

NDICE
1. 2. 3. 4. 5. 6. 1- Estructura de directorios para el perifrico 2- Diseo del perifrico en Handel-C 3- Creacin de un proyecto en DK para el perifrico 4- Aadiendo el perifrico al sistema MicroBlaze 5- Aplicacin de prueba 6- Un perifrico ms complejo

1. 1- Estructura de directorios para el perifrico

Para crear un nuevo perifrico en Handel-C y poder integrarlo en un diseo con el procesador MicroBlaze son necesarios una serie de archivos y una estructura de directorios dentro del proyecto de Xilinx Platform Studio, como la que se muestra en la figura.

El directorio pcores es un directorio que existe en todo proyecto de Xilinx Platform Studio. Dentro de ese directorio, se debe crear un directorio por cada perifrico que se vaya a crear. En este ejemplo se va a crear un perifrico para controlar los dos leds de la placa RC200/RC203. Se llama por tanto al directorio que va a contener el perifrico con el nombre del perifrico ms un nmero de versin, en este ejemplo led_driver_v1_00_a. Dentro de este directorio, deben existir dos directorios: data y netlist: El directorio data debe contener dos archivos, con extensiones .bbd y .mpd. El nombre de los archivos debe ser nombre_perifrico_v2_1_0.extension: en este ejemplo seran led_driver_v2_0_0.bbd y led_driver_v2_0_0.mpd. Estos archivos no se deben completar hasta llegar al final del punto 3 de este tutorial. El archivo .bbd contiene simplemente la ruta al archivo EDF que implementa la funcionalidad del perifrico, relativa al directorio netlist. En el apndice A se incluye una plantilla para este tipo de archivo. El archivo .mpd contiene informacin sobre el nombre del perifrico, parmetros, seales que se conectarn al bus OPB, y puertos de entrada o salida al exterior que tiene el perifrico. En el apndice B se incluye una plantilla genrica para este archivo, donde slo se deben introducir los valores de nombre, direcciones base y final donde se mapear el perifrico y en caso de tener el perifrico puertos de entrada o salida, el nombre, tamao y direccin de estos siguiendo la siguiente nomenclatura: o PORT nombre = , DIR = direccin , VEC = [num1:num2] nombre: nombre del puerto. Debe coincidir con el nombre que se le de al puerto en el diseo en Handel-C que se explicar ms adelante. direccin: puede valer IN, OUT o INOUT segn el puerto sea de entrada, salida o entrada-salida. num1:num2: este parmetro es opcional, y slo se usa en el caso de que el puerto sea de varios bits de anchura. Por ejemplo, para un puerto de 8 bits, se tendra: VEC = [0:7] El directorio netlist contendr los archivos del perifrico que se generarn desde Handel-C.

Una vez creada esta estructura de directorios, ya se puede pasar a disear el perifrico en Handel-C.

2. 2- Diseo del perifrico en Handel-C Handel-C proporciona una librera para poder disear perifricos conectables al bus OPB de forma rpida y sencilla. El primer ejemplo que se va a mostrar en este tutorial es un sencillo perifrico que permite controlar los dos leds que hay en las placas de prototipado RC200/RC203. El perifrico consistir en un registro de 32 bits accesible desde el procesador MicroBlaze. Los dos ltimos bits del registro sern los que enciendan o apaguen los leds de la placa. En el apndice C, se incluye el cdigo de este perifrico, que se explica a continuacin: Lo primero que aparece en el cdigo, es la inclusin de un archivo de cabecera, opb_bus_slave.hch que es el que proporciona las funciones necesarias para comunicarse con el bus OPB. A continuacin aparecen las siguientes lneas:
DECL_OPB_BUS (opb_bus); set clock = OPB_BUS_CLOCK (opb_bus);

Estas sentencias son comunes a cualquier perifrico en Handel-C que vaya a ser conectado a un bus OPB. Simplemente declaran un bus OPB, y conectan el reloj del perifrico al reloj del bus. A continuacin se declaran dos macro procesos WriteProc y ReadProc:
macro proc WriteProc (Address, DataIn, ByteEnable, Ready, myData); macro proc ReadProc (Address, DataOut, Ready, myData);

Estos dos procesos son los que describirn ms adelante las acciones que debe realizar el perifrico cuando se reciba un peticin de escritura o lectura en el perifrico por parte del bus. A continuacin, se define un nuevo tipo de datos de tipo struct y llamado Datos_Periferico. Esta estructura incluir todos los elementos necesarios del perifrico, como registros direccionables del perifrico, registros auxiliares, etc. En este ejemplo el perifrico slo tendr un registro direccionable:
typedef struct { unsigned int 32 Registro; } Datos_Periferico;

A continuacin, se describe el funcionamiento del proceso de escritura. Este proceso, es el que se ejecuta cuando el bus intenta escribir en el perifrico. Recibe varios parmetros del bus: direccin, dato de entrada, byte enable, y myData que es una referencia a la estructura de datos del perifrico descrita en el punto anterior. El parmetro Ready es un parmetro de escritura que se debe poner a 1 cuando se terminen de hacer las actividades relacionadas con la escritura.
macro proc WriteProc (Address, DataIn, ByteEnable, Ready, myData) { par

{ myData.Registro = DataIn; Ready = 1; } }

En este ejmplo, el proceso de escritura simplemente copia el valor de DataIn del bus en el nico registro direccionable del bus y se activa la seal de Ready, independientemente del valor de Address. Si hubiese varios registros direccionables, se tendra que escribir en uno u otro dependiendo del valor de Address. El siguiente cdigo es el del proceso de lectura, que es el que se ejecuta cuando el bus realiza una operacin de lectura sobre el perifrico. Como solo hay un registro direccionalbe, simplemente se escribe el valor de ese registro en DataOut y se activa la seal de Ready.
macro proc ReadProc (Address, DataOut, Ready, myData) { par { DataOut = myData.Registro; Ready = 1; } }

Una vez descritos estos procesos, se pasa al mtodo main() del perifrico. En ste, lo primero que se hace es declarar una variable del tipo de datos Datos_Periferico descrito anteriormente.
Datos_Periferico myDataStructure;

Luego se declara un interfaz de salida, que tendr dos bits de ancho y de nombre OutLeds. En este puerto de salida se escriben los dos ltimos bits del registro del perifrico:
interface port_out () MyLedDriver (unsigned 2 OutLeds = myDataStructure.Registro<-2) with { busformat="B(N:0)" };

Por ltimo, se lanzan en paralelo dos procesos que proporciona la librera: RunOPBBus, y RunOPBSlave. ste ltimo recibe como parmetros el bus opb creado al principio del archivo, las macros de escritura y lectura, la variable myDataStructure creada previamente, y las direcciones base y alta donde ir mapeado el perifrico:
par { RunOPBBus (opb_bus); RunOPBSlave (opb_bus, WriteProc, ReadProc, myDataStructure, 0x70000000, 0x700000FF);

Si el perifrico tuviese algn funcionamiento extra, este se describira con un macro proceso que tambin se lanzara en paralelo con estos dos ltimos. Esto se mostrar en el segundo ejemplo. En el siguiente punto, se explicar como crear un proyecto en Handel-C en el que crear este perifrico.

3. 3- Creacin de un proyecto en DK para el perifrico Para poder implementar el perifrico que se ha explicado en el punto anterior, es necesario crear un proyecto en Handel-C desde la herramienta DK de Celoxica, desde el que poder compilar el cdigo del perifrico:

El proyecto se crear para un chip gnerico, y ms adelante se cambiar en las opciones para que coincida con la FPGA que vamos a utilizar. Se aade al proyecto un archivo de cdigo Handel-C, en el que se copiar el cdigo que se incluye en el Apndice

Una vez aadido el archivo, se proceder a cambiar las opciones del proyecto. Lo primero que se debe cambiar es el modelo de FPGA que se va a utilizar. En la siguiente imagen se muestran las opciones que se deben introducir si se est utilizando la placa de prototipado RC203 de Celoxica:

Si se est utilizando la placa RC200, los valores que se deben introducir son:

En la pestaa Linker se debe indicar que libreras se quieren enlazar, y la ruta de estas libreras, que es: C:\archivos de programa\celoxica\pdk\hardware\lib:

Una vez cambiadas las opciones del proyecto, ya se puede contruir para EDIF el diseo

Una vez construido el diseo, se habr creado un directorio de nombre EDIF. Dentro de este directorio hay 3 archivos con extensiones .edf .hco y .ncf. Estos 3 archivos se deben copiar al directorio pcores/led_driver_v1_00_a/netlist del proyecto de EDK, explicado en el primer punto.

Ahora que ya se conoce el nombre del perifrico, que puertos tiene, direcciones donde se mapear, etc ya se pueden completar los archivos .bbd y .mpd mencionados en el primer punto. La estructura final del directorio pcores del proyecto EDK debera ser: -pcores -led_driver_v1_00_a -data led_driver_v2_1_0.bbd led_driver_v2_1_0.mpd -netlist led_driver.edf led_driver.hco led_driver.ncf Una vez hechos estos pasos, se puede pasar a incluir el nuevo perifrico en el diseo con MicroBlaze.

4. 4- Aadiendo el perifrico al sistema MicroBlaze Una vez arrancada la herramienta Xilinx Platform Studio, se puede abrir el proyecto usado en el tutorial anterior sobre MicroBlaze, o crear un proyecto nuevo. En este ejemplo se va a continuar con el sistema que ya se haba utilizado en el tutorial previo. Para que el nuevo perifrico aparezca en la lista de perifricos disponibles para incluir en el diseo, la estructura del directorio pcores , mencionada en el punto anterior, debe estar preparada y con todos los archivos necesarios ya creados. Seleccionando Project>Rescan User Repositories la herramienta buscar en ese directorio los nuevos perifricos que se han aadido. Una vez hecho esto, en la lista de perifricos del Ip Catalog ya debe aparecer el nuevo perifrico que se ha desarrollado:

Como se ha hecho ya en el tutorial anterior sobre MicroBlaze, aadimos el perifrico, y lo conectamos al bus OPB.

En el filtro Ports, se conecta el puerto de reset del perifrico al reset del sistema, y se hace externo el puerto OutLeds.

Y por ltimo, en el filtro Addresses se asignan las direcciones al perifrico que se haban utilizado en el diseo en Handel-C, que eran 0x70000000 y 0x700000FF.

Por ltimo, solo queda aadir al archivo de restricciones system.ucf los pines del perifrico, mediante las dos siguientes lneas:
NET led_driver_0_OutLeds_pin<0> LOC = L8; #Para RC200 LOC = J6 NET led_driver_0_OutLeds_pin<1> LOC = M8; #Para RC200 LOC = K6

Una vez realizados estos pasos, se puede generar el bitstream desde Hardware>Generate Bitstream. En unos minutos, si no ha habido errores, el diseo estar ya sintetizado, y se podr pasar a escribir una aplicacin de prueba para el perifrico.

5. 5- Aplicacin de prueba Para probar el correcto funcionamiento del nuevo perifrico, se incluye en el apndice D el cdigo C de una sencilla aplicacin que enciende y apaga los leds alternativamente primero, y simultneamente despus. Basta con crear una nueva aplicacin software con este cdigo, cambiando las opciones de compilacin para que se compile sin optimizacin, y posteriormente inicializar la memoria de bloque con el ejecutable generado y descargar el fichero de programacin a la placa, tal como se explicaba en el tutorial anterior sobre MicroBlaze.

6. 6- Un perifrico ms complejo En este punto se va a presentar un perifrico un poco ms complejo que permita entender mejor el concepto de registros direccionables del perifrico, as como que realice algn tipo de operacin. El primer perifrico visto en el tutorial, solamente tena un registro direccionable, y no realizaba ninguna accin: simplemente sacaba a una interfaz externa los dos ltimos bits del registro. El nuevo perifrico que se va a estudiar ahora, es un sencillo sumador/restador, que tendr los siguientes registros internos: Reg_1: registro del primer operando. Reg_2: registro del segundo operando Reg_result: registro del resultado Reg_opmode: registro que indica que operacin se debe realizar Los registros estarn mapeados en la memoria en las direcciones: Reg_1 => 0x10000000 Reg_2 => 0x10000004 Reg_result => 0x10000008 Reg_opmode => 0x1000000C En el apndice E se incluye el cdigo Handel-C de este perifrico. Se explicarn a continuacin las partes ms relevantes del cdigo. Estructura de datos para almacenar los registros internos del perifrico:
typedef struct { unsigned int unsigned int unsigned int unsigned int } Datos_Periferico;

32 32 32 32

Reg_1; Reg_2; Reg_result; Reg_opmode;

Como el perifrico va a tener 4 registros, se declaran dentro de la estructura de datos Datos_Periferico. Proceso de escritura:
macro proc WriteProc (Address, DataIn, ByteEnable, Ready, myData) { par { if(Address == 0x10000000) myData.Reg_1 = DataIn; else if (Address == 0x10000004) myData.Reg_2 = DataIn; else if (Address == 0x10000008) delay; //no permitimos escribir en el registro de resultado else if (Address == 0x1000000C) myData.Reg_opmode = DataIn; else delay; //para que todos los caminos duren un ciclo

Ready } }

= 1;

Como el perifrico tiene varios registros direccionables, escribiremos en uno u otro dependiendo del valor de la seal Address. Proceso de lectura:
macro proc ReadProc (Address, DataOut, Ready, myData) { par { if(Address == 0x10000000) DataOut = myData.Reg_1; else if (Address == 0x10000004) DataOut = myData.Reg_2; else if (Address == 0x10000008) DataOut = myData.Reg_result; else if (Address == 0x1000000C) DataOut = myData.Reg_opmode; else delay; //para que todos los caminos duren un ciclo Ready } } = 1;

Al igual que en escritura, como el perifrico tiene varios registros direccionables, se escribir en DataOut el valor de uno u otro dependiendo del valor de la seal Address. Proceso que realiza la operacin:
macro proc Operacion(myData) { while(1) { if(myData.Reg_opmode == 0x1) myData.Reg_result = myData.Reg_1 + myData.Reg_2; else if(myData.Reg_opmode == 0x2) myData.Reg_result = myData.Reg_1 - myData.Reg_2; else delay; // todos los caminos duran un ciclo } }

Este proceso realiza la operacin de suma o resta, segn lo que valga el cdigo de operacin. El proceso correr indefinidamente, y escribir el resultado en el registro de resultado. Proceso main:
void main (void) { Datos_Periferico myDataStructure; par { RunOPBBus (opb_bus); RunOPBSlave (opb_bus, WriteProc, ReadProc, myDataStructure, 0x10000000, 0x100000FF); Operacion(myDataStructure); } }

Como este perifrico no tiene ninguna interfaz externa a la FPGA, no hay declaraciones de ese tipo. Al igual que en el ejemplo anterior, se crea una variable del tipo de datos del perifrico, myDataStructure. Luego se lanzan en paralelo los dos procesos que controlan la comunicacin con el bus OPB: RunOPBBus y RunOPBSlave. En paralelo con estos dos procesos, se lanza tambien el proceso Operacin que es el que implementa la funcionalidad del perifrico. Una vez explicado el funcionamiento del perifrico, queda como trabajo para el alumno crear el proyecto Handel-C para implementar el perifrico, preparar la estructura de directorios para integrar el perifrico en XilinxPlatformStudio y aadirlo al sistema con MicroBlaze tal como se ha explicado en el ejemplo anterior. En el apndice F, se encuentra el cdigo de una aplicacin en C para probar el funcionamiento del nuevo perifrico. Queda tambin como trabajo para el alumno, el crear un nuevo perifrico. El enunciado de este trabajo se encuentra en la pgina web de la asignatura como Prcticas EDK 2.

Apndice A Plantilla para el archivo .bbd


FILES #Path relativo al directorio netlist del archivo EDF del perifrico. #ejemplo: mi_perifrico.edf

Apndice B Plantilla para el archivo .mpd


#Introducir nombre del perifrico BEGIN nombre_preifrico OPTION IPTYPE=PERIPHERAL OPTION STYLE=BLACKBOX OPTION EDIF=TRUE # Define bus interface BUS_INTERFACE BUS=SOPB, BUS_STD=OPB, BUS_TYPE=SLAVE # NO CAMBIAR. Generics for vhdl or parameters for verilog. parameter C_OPB_DWIDTH = 32, DT=integer, BUS=SOPB parameter C_OPB_AWIDTH = 32, DT=integer, BUS=SOPB #Introducir direcciones Base y Lmite donde se mapear el perifrico parameter C_BASEADDR = 0xfff00000, BUS=SOPB parameter C_HIGHADDR = 0xfff000ff, BUS=SOPB # NO CAMBIAR. Seales PORT OPB_Clk PORT OPB_Rst PORT OPB_ABus BUS=SOPB PORT OPB_BE 1], BUS=SOPB PORT OPB_DBus BUS=SOPB PORT OPB_RNW BUS=SOPB PORT OPB_Select BUS=SOPB PORT OPB_SeqAddr BUS=SOPB PORT Slave_DBus BUS=SOPB PORT Slave_xferAck BUS=SOPB PORT Slave_errAck BUS=SOPB PORT Slave_retry BUS=SOPB PORT Slave_toutSup BUS=SOPB del bus OPB. NO CAMBIAR = "", DIR=IN, BUS=SOPB, SIGIS=CLK = "", DIR=IN = OPB_ABus, DIR=IN, VEC=[0:C_OPB_AWIDTH-1], = OPB_BE, = OPB_DBus, = OPB_RNW, = OPB_select, DIR=IN, DIR=IN, DIR=IN, DIR=IN, VEC=[0:C_OPB_DWIDTH/8VEC=[0:C_OPB_DWIDTH-1],

= OPB_seqAddr, DIR=IN, = Sl_DBus, = Sl_xferAck, = Sl_errAck, = Sl_retry, = Sl_toutSup, DIR=OUT, VEC=[0:C_OPB_DWIDTH-1], DIR=OUT, DIR=OUT, DIR=OUT, DIR=OUT,

#Aadir puertos de entrada o salida aqu #Ejemplo de puerto de salida #PORT Puerto_Salida = "", DIR=OUT, VEC=[0:7] #Ejemplo de puerto de entrada #PORT Puerto_Entrada = "", DIR=IN, VEC=[0:3]

END

Apndice C Cdigo Handel-C del controlador de los leds.


#include "opb_bus_slave.hch"

DECL_OPB_BUS (opb_bus); set clock = OPB_BUS_CLOCK (opb_bus); macro proc WriteProc (Address, DataIn, ByteEnable, Ready, myData); macro proc ReadProc (Address, DataOut, Ready, myData);

typedef struct { unsigned int 32 Registro; } Datos_Periferico; macro proc WriteProc (Address, DataIn, ByteEnable, Ready, myData) { par { myData.Registro = DataIn; Ready = 1; } } macro proc ReadProc (Address, DataOut, Ready, myData) { par { DataOut = myData.Registro; Ready = 1; } }

void main (void) { Datos_Periferico myDataStructure;

interface port_out () MyLedDriver (unsigned 2 OutLeds = myDataStructure.Registro<-2) with { busformat="B(N:0)" };

par { RunOPBBus (opb_bus); RunOPBSlave (opb_bus, WriteProc, ReadProc, myDataStructure, 0x70000000, 0x700000FF); } }

Apndice D Cdigo C para la aplicacin de prueba del perifrico.


#include "xparameters.h" #include "xutil.h" //==================================================== int main (void) { volatile unsigned int *ptr; unsigned int i; unsigned int j; ptr = (volatile unsigned int *)0x70000000; print("\033[H\033[J"); //Clear Screen print("Probando el funcionamiento de los leds. . .\r\n"); print("Encendiendo alternativamente.\r\n"); for(j = 0; j < 16; j++) { *ptr = 0x2; //izquierdo apagado, derecho encendido for(i = 0; i < 500000; i++); *ptr = 0x1; //izquierdo encendido, derecho apagado for(i = 0; i < 500000; i++); } print("Encendiendo/apagando simultaneamente.\r\n"); for(j = 0; j < 16; j++) { *ptr = 0x3; //ambos encendidos for(i = 0; i < 500000; i++); *ptr = 0x0; //ambos apagados for(i = 0; i < 500000; i++); } }

Apndice E Cdigo Handel-C del sumador restador.


#include "opb_bus_slave.hch"

DECL_OPB_BUS (opb_bus); set clock = OPB_BUS_CLOCK (opb_bus); macro proc WriteProc (Address, DataIn, ByteEnable, Ready, myData); macro proc ReadProc (Address, DataOut, Ready, myData);

typedef struct { unsigned int unsigned int unsigned int unsigned int } Datos_Periferico;

32 32 32 32

Reg_1; Reg_2; Reg_result; Reg_opmode;

macro proc WriteProc (Address, DataIn, ByteEnable, Ready, myData) { par { if(Address == 0x10000000) myData.Reg_1 = DataIn; else if (Address == 0x10000004) myData.Reg_2 = DataIn; else if (Address == 0x10000008) delay; //no permitimos escribir en el registro de resultado else if (Address == 0x1000000C) myData.Reg_opmode = DataIn; else delay; //para que todos los caminos duren un ciclo Ready } } macro proc ReadProc (Address, DataOut, Ready, myData) { par { if(Address == 0x10000000) DataOut = myData.Reg_1; else if (Address == 0x10000004) DataOut = myData.Reg_2; else if (Address == 0x10000008) DataOut = myData.Reg_result; else if (Address == 0x1000000C) DataOut = myData.Reg_opmode; else delay; //para que todos los caminos duren un ciclo Ready } } macro proc Operacion(myData) { while(1) { if(myData.Reg_opmode == 0x1) myData.Reg_result = myData.Reg_1 + myData.Reg_2; else if(myData.Reg_opmode == 0x2) myData.Reg_result = myData.Reg_1 - myData.Reg_2; else delay; // todos los caminos duran un ciclo } } void main (void) = 1; = 1;

{ Datos_Periferico myDataStructure; par { RunOPBBus (opb_bus); RunOPBSlave (opb_bus, WriteProc, ReadProc, myDataStructure, 0x10000000, 0x100000FF); Operacion(myDataStructure); } }

Apndice F Cdigo C para probar el funcionamiento del sumador-restador


#include "xparameters.h" #include "xutil.h" //==================================================== int main (void) { volatile unsigned int *reg_1, *reg_2, *reg_opmode; volatile unsigned int *reg_result; reg_1 = (volatile unsigned int *)0x10000000; reg_2 = (volatile unsigned int *)0x10000004; reg_opmode = (volatile unsigned int *)0x1000000C; reg_result = (volatile unsigned int *)0x10000008; print("\033[H\033[J"); //Clear Screen *reg_2 = 100; *reg_1 = 250; *reg_opmode = 0x1; xil_printf("%d + %d *reg_opmode = 0x2; xil_printf("%d - %d *reg_1 = 1235; *reg_2 = 5; *reg_opmode = 0x1; xil_printf("%d + %d *reg_opmode = 0x2; xil_printf("%d - %d }

= %d\r\n", *reg_1, *reg_2, *reg_result); = %d\r\n", *reg_1, *reg_2, *reg_result);

= %d\r\n", *reg_1, *reg_2, *reg_result); = %d\r\n", *reg_1, *reg_2, *reg_result);

Vous aimerez peut-être aussi