Basic Example/es

From Qi-Hardware
Jump to: navigation, search

Contents

[edit] Ejecución de aplicaciones en plasma

[edit] Arquitectura del procesador mlite

Plasma es un Sistema sobre Circuito Integrado (SoC por sus siglas en inglés) que incluye dentro del mismo diseño la unidad de procesamiento central (llamada mlite), la memoria de programa, el controlador de interrupciones programable, el decodificador de direcciones y un periférico para la comunicación serial UART, lo que representa la configuración mínima para realizar aplicaciones software. En la siguiente figura podemos observar su arquitectura.

La unidad central de procesamiento mlite es un procesador mips y su diagrama de bloques se muestra a continuación:

Plasma block diagram.png


Nuestro objetivo no es el estudio detallado de la arquitectura mips, ya que existe una extensa bibliografía sobre este tema. Sin embargo, realizaremos el estudio de una instrucción en particular que controla la unidad de memoria y permite la comunicación con periféricos externos. Para estudiarla utilizaremos una aproximación top-down, en la que a partir de un programa en C generaremos una aplicación que escriba diferentes tipos de datos en direcciones de memoria externa. El primer paso para la implementación de este ejemplo, es la escritura de un programa en C que realice las operaciones deseadas. (el código fuente de este ejemplo puede ser descargado de:http://projects.qi-hardware.com/index.php/p/nn-usb-fpga/source/tree/master/plasma/gpio)

  1. int main(void)
  2. {
  3.   volatile unsigned char    *data8;
  4.   volatile unsigned short   *data16;
  5.   volatile unsigned int     *data32;
  6.  
  7.   volatile unsigned char    test8; 
  8.   volatile unsigned short   test16;
  9.   volatile unsigned int     test32, tmp;
  10.  
  11.   data8  = (unsigned char  *)(0x20000410);
  12.   data16 = (unsigned short *)(0x20000210);
  13.   data32 = (unsigned int   *)(0x20000330);
  14.   *data8  = 0x10;
  15.   data8++;
  16.   *data8  = 0x11;
  17.   data8++;
  18.   *data8  = 0x12;
  19.   data8++;
  20.   *data8  = 0x13;
  21.   data8++;
  22.   *data8  = 0x14;
  23.   *data16 = 0x2020;
  24.   data16++;
  25.   *data16 = 0x2121;
  26.   data16++;
  27.   *data16 = 0x2222;
  28.   data16++;
  29.   *data32 = 0x30303030;
  30.   data32++;
  31.   *data32 = 0x31313131;
  32.   test8  = *data8;
  33.   test16 = *data16;
  34.   test32 = *data32;
  35.   data8 += 4;
  36.   data16++;
  37.   data32++;
  38.   test8  = *data8;
  39.   test16 = *data16;
  40.   test32 = *data32;
  41.   data32 = (unsigned int *)(0x20000000);
  42.   *data32 = 0x55555555;
  43.   test32 = *data32;
  44.   data32 = (unsigned int *)(0x20000020);
  45.   test32 = *data32;
  46. }

A continuación generamos los objetos necesarios para generar el ejecutable:


mips-elf-as -o crt0.o ../lib/crt0.S mips-elf-gcc -O -I../include -Wall -c -s gpio.c mips-elf-ld -Ttext 0 -eentry -Map gpio.map -s -N -o gpio crt0.o gpio.o


Creamos el archivo que contiene las instrucciones en lenguaje del procesador para implementar la funcionalidad deseada: mips-elf-objcopy -I elf32-big -O binary gpio gpio.bin

[edit] Set de Instrucciones

[edit] Instrucciones Tipo I

[edit] Instruccion LW (Load Word)

Untitled.png

[edit] Instruccion SW (Store Word)

Sw.png

el siguiente es el código asembly del programa implementado en C del ejemplo anterior, dicho codigo escribe diferentes tipos de datos en memoria externa.

En este segmento de codigo se puede apreciar el direccionamiento y almacenamiento de los datos:

200:	27bdfff8 	addiu	sp,sp,-8
204:	3c032000 	lui	v1,0x2000         //(load upper immediate),
                                                  guarda los primeros bits de la dirreccion en v1. 
208:	34640410 	ori	a0,v1,0x410       //(or immediate),suma v1
                                                 con el resto de la direccion y la guarda en a0.
20c:	24020010 	li	v0,16             //(load immedite), 
                                                  guarda en v0 el dato en hexadecimal.
210:	a0820000 	sb	v0,0(a0)          //(store byte),guarda el dato (v0)
                                                 en la direccion a la que apunta a0.
214:	34640411 	ori	a0,v1,0x411       //se repite el proceso para nuevos datos
                                                 y su respectivo direccionamiento.
218:	24020011 	li	v0,17
21c:	a0820000 	sb	v0,0(a0)
220:	34640412 	ori	a0,v1,0x412
224:	24020012 	li	v0,18
228:	a0820000 	sb	v0,0(a0)
22c:	34640413 	ori	a0,v1,0x413
230:	24020013 	li	v0,19
234:	a0820000 	sb	v0,0(a0)
238:	34650414 	ori	a1,v1,0x414
23c:	24020014 	li	v0,20
240:	a0a20000 	sb	v0,0(a1)

[edit] Instrucciones Tipo R (Operaciones Aritmetico/Logicas)

[edit] Operaciones en la ALU

Instrucciones: addu(suma sin signo), subu(resta sin signo), xor, nor, and, or.

Plasma block diagram alu.png

Cuando las operaciones se dan entre un registro y una constante, se puede obtener la constante directamente del opcode vista como un registro tipo inmediato, siempre que la constante sea menor o igual a 16 bits. Este mismo proceso aplica en las operaciones de multiplicación y división.

Se incluye un ejemplo básico en lenguaje C y su representación en código assembly:

#include "plasma.h"
int main(void)
{
volatile unsigned int  c, cc, C;
c=4+3;
cc=15;
C=cc-c;
C=c+8;
return 0;
}
1fc:	00000000 	nop			//
200:	27bdfff0 	addiu	sp,sp,-16		
204:	24020007 	li	v0,7		//guarda el numero tiene en el registro de retorno v0 ($2). Nótece que en el
                                                ejemplo básico en C, se define la operación 3+4=7, operación que ve necesaria
                                                realizar en registros, se realiza de manera inmediata. 
208:	afa20000 	sw	v0,0(sp)	//se almacena en el apuntador principal de memoria el valor que se tiene en v0
20c:	2402000f 	li	v0,15 		//se almacena 15 en el registro de retorno v0
210:	afa20004 	sw	v0,4(sp)	//lo presente en v0 va al espacio que indica 4(sp) 
214:	8fa20004 	lw	v0,4(sp)	//se carga en v0 el #15
218:	8fa30000 	lw	v1,0(sp)	//se carga en V1 ($3) el #7
21c:	00000000 	nop
220:	00431023 	subu	v0,v0,v1	//realización de operación resta sin signo entre los valores presentes en v0 y v1
224:	afa20008 	sw	v0,8(sp)	//se almacena en memoria, 8(sp), lo presente en v0
228:	8fa20000 	lw	v0,0(sp)	//carga en v0 lo que al inicio se menciona se guarda en 0(sp)
22c:	00000000 	nop
230:	24420008 	addiu	v0,v0,8		//se realiza la operación suma inmediata sin signo entre lo presente en v0 (#7) y 8. 
                                                 Es de notar  que este último al ser una costante no se almacena en un registro
234:	afa20008 	sw	v0,8(sp)
238:	00001021 	move	v0,zero
23c:	03e00008 	jr	ra
240:	27bd0010 	addiu	sp,sp,16

Ejemplo para las operaciones con compuertas:

#include "plasma.h"
int main(void)
{
volatile unsigned int   c, cc, C;
 c= 0x46A1F0B7;
 C= 0;
 cc=c & 0xFFFF0000;
 cc=cc|0x000072FC;
 cc=~(cc|0);
 cc=c^C;
 return 0;
}
200:	27bdfff0 	addiu	sp,sp,-16
204:	3c0246a1 	lui	v0,0x46a1
208:	3442f0b7 	ori	v0,v0,0xf0b7
20c:	afa20000 	sw	v0,0(sp)
210:	afa00008 	sw	zero,8(sp)
214:	8fa20000 	lw	v0,0(sp)
218:	3c03ffff 	lui	v1,0xffff
21c:	00431024 	and	v0,v0,v1           //realiza la operacion logica and bit a bit entre los registros,su uso mas comun es como mascara. 
220:	afa20004 	sw	v0,4(sp)
224:	8fa20004 	lw	v0,4(sp)
228:	00000000 	nop
22c:	344272fc 	ori	v0,v0,0x72fc      //or inmediato, opera el registro con la constante, en este caso uniendolos. 
230:	afa20004 	sw	v0,4(sp)
234:	8fa20004 	lw	v0,4(sp)
238:	00000000 	nop
23c:	00021027 	nor	v0,zero,v0       //nor 0 niega el registro. 
240:	afa20004 	sw	v0,4(sp)
244:	8fa20000 	lw	v0,0(sp)
248:	8fa30008 	lw	v1,8(sp)
24c:	00000000 	nop
250:	00431026 	xor	v0,v0,v1        //realiza la operacion logica xor entre los dos registros(comunmente se usa para operaciones de complemento a dos).
254:	afa20004 	sw	v0,4(sp)

[edit] Operaciones en MULT

Instrucciones de multiplicación y división: mfhi(carga los 32 bits MSB del resultado), mthi(guarda los 32 bits MSB del resultado), mflo(carga los 32 bits LSB del resultado), mtlo(guarda los 32 bits LSB del resultado), multu(multiplicación sin signo), divu(división sin signo).

Plasma block diagram mult.png

Ejemplo en C y su visualización en assembly.

#include "plasma.h"
int main(void)
{
 volatile unsigned int   c, cc;
c=4;
cc=7;
cc=cc*c;
c=cc/c;
return 0;
}
204:	24020004 	li	v0,4
208:	afa20000 	sw	v0,0(sp)
20c:	24020007 	li	v0,7
210:	afa20004 	sw	v0,4(sp)
214:	8fa20004 	lw	v0,4(sp)
218:	8fa30000 	lw	v1,0(sp)
21c:	00000000 	nop
220:	00430018 	mult	v0,v1     //realiza la multiplicación entre los dos registros.
224:	00001012 	mflo	v0        //carga los 32 bit LSB en v0, los cuales contienen la solución de la multiplicacin. 
228:	afa20004 	sw	v0,4(sp)  //guarda la solución de la multiplicacion.
22c:	8fa20004 	lw	v0,4(sp)
230:	8fa30000 	lw	v1,0(sp)
234:	00000000 	nop
238:	14600002 	bnez	v1,0x244  //salta a la linea 244 si v1(dividendo) es igual a cero.
23c:	0043001b 	divu	zero,v0,v1  //realiza la división sin signo entre los registros.
240:	0007000d 	break	0x7
244:	00001012 	mflo	v0        //entrega el resultado(cociente).
248:	afa20000 	sw	v0,0(sp)

[edit] Operaciones en SHIFT

Instrucciones de corrimiento: sll(shift left logical), srl(shift right logical), sra(shift right aritmetic), sllv(shift left logical variable), srlv(shift right logical variable).

Plasma block diagram shift.png

[edit] Saltos

A veces un programa debe cambiar el flujo del programa en forma incondicional o bajo una condición (Para tomar una decisión), por lo tanto debe haber instrucciones que permitan cambiar el flujo de un programa sin ningún requisito, o en caso de que una condición se cumpla. Existen instrucciones para éste propósito. Son las instrucciones de saltos incondicionales y condicionales, que saltan a un determinado punto si se cumpla la condición.

Estas instrucciones permiten interrumpir el flujo normal de un programa saltando a otra instrucción alejada de la actual para ejecutar una determinada tarea. Los saltos se pueden ejecutar condicional o incondicionalmente y el salto puede ser relativo o absoluto.

El ensamblador del MIPS incluye dos instrucciones básicas de ruptura de secuencia condicional beq (branch if equal, saltar si igual) y bne (branch if no equal, saltar si distinto). La sintaxis de estas instrucciones es la siguiente:

beq rs, rt, etiqueta bne rs, rt, etiqueta.

Ambas comparan el contenido de los registros rs y rt y saltan a la dirección de la instrucción referenciada por etiqueta si el contenido del registro rs es igual al del rt (beq) o distinto (bne).

Además dispone de instrucciones de salto condicional para realizar comparaciones con cero. Estas instrucciones son: bgez (branch if greater or equal to zero, saltar si mayor o igual a cero), bgtz (branch if greater than zero, saltar si mayor que cero), blez (branch if less or equal to zero, saltar si menor o igual que cero), bltz (branch if less than zero, saltar si menor que cero), y tienen la siguiente sintaxis:

bgez rs, etiqueta bgtz rs, etiqueta blez rs, etiqueta bltz rs, etiqueta

Todas ellas comparan el contenido del registro rs con 0 y saltan a la dirección de la instrucción referenciada por etiqueta si rs >=0 (bgez), rs > 0 (bgtz), rs<=0 (blez) ó rs<0 (bltz).

Para facilitar la programación, el lenguaje ensamblador del MIPS aporta un conjunto de pseudoinstrucciones de salto condicional que permiten comparar dos variables almacenadas en registros (a nivel de mayor, mayor o igual, menor, menor o igual) y según el resultado de esa comparación saltan o no, a la instrucción referenciada a través de la etiqueta. Estas pseudoinstrucciones son: bge (branch if greater or equal, saltar si mayor o igual ), bgt (branch if greater, saltar si mayor que), ble (branch if less or equal, saltar si menor o igual), blt (branch if less, saltar si menor que). El formato es el mismo para todas ellas:

bxx rs,rt, etiqueta

Instrucciones de salto incondicional

La instrucción j permite romper la secuencia de ejecución del programa de forma incondicional y desviarla hacia la instrucción referenciada mediante la etiqueta. Esta instrucción presenta el siguiente formato:

j etiqueta

[edit] Saltos Condicionales

En este tipo de saltos se transfiere el control a otro punto del programa si se cumple cierta "condición", se dice que el programa "toma una decisión". En esta ocasión nos encontramos con una operación de tipo -I (de tipo inmediato). Existen en la arquitectura MIPS varias instrucciones de saltos condicionales que el lector podrá verificar en la densa bibliografía que hay respecto al tema, ya que para efectos de conocer como funciona la instrucción y como cambia el camino de datos en el procesador PLASMA solo se explicara con una instrucción, la instrucción BEQ salte si es igual (Branch if Equal).


Opcode operaciones tipo-I


Opcode.png


A continuación se muestra un ejemplo básico en Assembler para entender como funciona la instrucción de salto condicional.

0x12 beq $t0, $0, sino
0x16 addi $v0, $0, 1
0x1A addi $sp, $sp, 8
0x1E jr $ra
0x22 sino: addi $a0, $a0,
0x23 jal regis


En la instrucción cuya dirección es 0x12 se ve la instrucción de salto beq donde se va a comparar el contenido de los registros $t0 y $0 si el contenido de estos registros resulta ser el mismo, entonces el programa saltará a la instrucción donde se encuentra la etiqueta "sino" en la dirección 0x22. Lo que ocurre con los 32 bits de la instrucción es lo siguiente:


Campimm.png


El campo inmediato tiene un valor de tres (3) correspondiente al numero de instrucciones que hay entre la instrucción beq en la dirección 0x12 hasta la instrucción donde se encuentra la etiqueta "sino" en la dirección 0x22. En realidad si la condición se cumple en la instrucción 0x12 el programa debería saltar cuatro instrucciones de la 0x12 a la 0x22, es por eso que el procesador debe efectuar la siguiente operación para poner el contador de programa (PC) en la instrucción correcta: PC = PC + 4 + ImmX4.


EL procesador PLASMA sigue el siguiente camino de datos para realizar la operación anteriormente descrita (PC = PC + 4 + ImmX4.).


Plasma1beq.png


En la anterior imagen se ve la forma en que el procesador compara el valor de los dos registros en la ALU y de esta forma genera las señales de control dependiendo de si se cumple o no la condición de igualdad, en este caso consideramos que se cumple la condición y que se debe llevar al contador de programa a la nueva dirección de la instrucción.


Plasma2beq.png


En la anterior imagen se ve el camino de datos que el procesador PLASMA debe seguir para realizar la operación PC = PC + 4 + ImmX4. y poder saltar a la instrucción deseada, se ve que el campo inmediato de los bits 15:0 pasan por el modulo de extensión de signo, de acá pasan por el modulo de corrimiento, el cual hará un corrimiento de dos bits a la izquierda y de esta forma se genera la multiplicación por 4 del campo inmediato, posteriormente este valor se suma con el valor del contador de programa (PC+4) en la ALU y por ultimo se envía al modulo de contador de programa con el valor de la dirección de la nueva instrucción.

[edit] Saltos Incondicionales

Los saltos incondicionales se ejecutan directamente hacia una dirección absoluta de la memoria de instrucciones, los tipos básicos son el jump(J), el jump and link (jal), el jump register (jr) y el jump and link register (jalr), los dos primeros (j y jal) son operaciones tipo j, mientras que las otras dos (jr y jalr) son operaciones tipo R.

Los saltos incondicionales son muy utilizados junto con los condicionales para la implementación de sentencias condicionales en lenguaje de alto nivel como if o if else, en ciclos como for y while y para el llamado a funciones, mas adelante veremos algunos ejemplos.

Las operaciones tipo j

Las operaciones tipo j son operaciones utilizadas para especificar direcciones a las cuales se salta durante la ejecución de un programa constan de un opcode de 6 bits (31:26) y los 26 menos significativos (25:0) forman parte de la dirección:

Opcodej.png

Las operaciones tipo j utilizan un modo de direccionamiento pseudo directo con el cual se arma la dirección a la cual se ejecuta el salto como se observa en la siguiente figura

Jump.png

En la siguiente figura vemos el datapath necesario para las instrucciones tipo j, en las cuales se realiza un corrimiento de 2 bits para agregar los ceros del final y se completa con los 4 bits mas significativos de la siguiente instrucción (pc +4)

Saltosj.jpg

Ejemplo con ciclo while

Se compiló con ayuda del GCC mips el siguiente código en C el cual calcula el logaritmo en base 2 de 128 mediante el uso de del ciclo while

  1. #include "plasma.h"
  2. main(void)
  3. {
  4.   volatile unsigned int pow=1;
  5.   volatile unsigned int x=0;
  6.  
  7.   while(pow!=128){
  8.   pow=pow*2;
  9.   x=x+1;
  10.   }
  11.   return x;
  12. }

Según la teoría que encontramos en los libros, la forma mas intuitiva de realizar el anterior algoritmo en assembler para el mips es:

addi $s0, $0, 1 # pow = 1
addi $s1, $0, 0 # x = 0
addi $t0, $0, 128
while:
beq $s0, $t0, done # si pow = 128 sale del lazo.
sll $s0, $s0, 1 # pow = pow * 2
addi $s1, $s1, 1 # x = x +1
j while
done:

Sin embargo el compilador lo realiza diferente, como observamos en el siguiente fragmento del archivo .lst obtenido:

204:	24020001 	li	v0,1      //Inicializa pow=1
208:	afa20000 	sw	v0,0(sp)  // Guarda pow en la pila
20c:	afa00004 	sw	zero,4(sp)  //inicializa x=1 y lo guarda en la pila en el espacio siguiente al de pow
210:	8fa30000 	lw	v1,0(sp)    // carga en $v1 el valor de pow que es 1
214:	24020080 	li	v0,128  // crea una variable de comparación en $v0
218:	1062000e 	beq	v1,v0,0x254 // si pow=128 salta a la dirección 254
21c:	00000000 	nop
220:	24030080 	li	v1,128 // si pow no es igual a 128 carga 128  en $v1
224:	8fa20000 	lw	v0,0(sp) // carga de la pila pow en $v0
228:	00000000 	nop
22c:	00021040 	sll	v0,v0,0x1 // multiplica pow=pow*2
230:	afa20000 	sw	v0,0(sp) // guarda el nuevo valor de pow en la pila
234:	8fa20004 	lw	v0,4(sp) // carga x de la pila a $v0
238:	00000000 	nop
23c:	24420001 	addiu	v0,v0,1 // incrementa x=x+1
240:	afa20004 	sw	v0,4(sp) //almacena el nuevo valor de x en la pila
244:	8fa20000 	lw	v0,0(sp) // carga pow en $v0
248:	00000000 	nop
24c:	1443fff5 	bne	v0,v1,0x224 // si pow!=128 salta a la dirección 224 y vuelve a ejecutar las instrucciones 224-24c 
250:	00000000 	nop 
254:	8fa20004 	lw	v0,4(sp) // cuando pow=128, el algoritmo ha terminado y se carga el resultado (x) en $v0
258:	03e00008 	jr	ra    // salta a otra dirección para continuar ejecutando otras tareas.

Llamado a funciones

En varias ocasiones es necesario volver a realizar ciertas acciones varias veces en un determinado programa, para ello se crean funciones que son invocadas desde un programa principal para que ejecuten la tarea para la cual se construyeron las veces que sea necesario, una función recibe varios argumentos y retorna un valor.

En la arquitectura MIPS se siguen las siguientes convenciones para el llamado a funciones:

  • Los argumentos se ubican en los registros $a0, $a1, $a2 y $a3.
  • El valor retornado por la función se coloca en los registros $v0 o $v1
  • Cuando hay mas de 4 argumentos se utiliza la pila
  • La ejecución de una función no debe alterar la ejecución ni los datos manejados por otras funciones o por el programa principal
  • En la pila se deben almacenar temporalmente en la pila, los valores de las variables utilizadas por otras funciones o el programa principal para que no se pierda el flujo de ejecución de estos.

En MIPS se utilizan las instrucciones jal y jr principalmente para hacer el llamado a funciones, jal se encarga de saltar a la dirección en donde empieza la función invocada y almacena la dirección de la siguiente instrucción después del jal en el registro $ra para de esta manera poder volver al programa después de ejecutar la función, al final de la función se utiliza la instrucción jr $ra para volver.

En la siguiente imagen se muestra el datapath necesario para el llamado a funciones, se necesita escribir y leer del banco de registros, específicamente en los registros $ra, $a0, $a1, $a2, $a3, $v0 y $v1, también se necesita ejecutar instrucciones tipo R para jr y tipo j para jal para lo cual se deja el bloque de corrimiento, y además se realizan instrucciones de lectura y escritura en memoria ya que se trabaja con la pila.

Diagrama funciones.jpg

Ejemplo llamado a funciones

Se compiló el siguiente código en c, el cual llama a una función con 4 argumentos y esta ejecuta una operación algebraica:

  1. #include "plasma.h"
  2.  
  3. int funcion (volatile unsigned int f, volatile unsigned int g, volatile unsigned int h, volatile unsigned int i)
  4. {
  5. volatile unsigned int result;
  6. result = (f + g) - (h + i);
  7. return result;
  8. }
  9. int main (void)
  10. {
  11. volatile unsigned int y;
  12. y =  funcion (2, 3, 4, 5);
  13. }

En el siguiente fragmento del archivo .lst se muestra la ejecución del programa principal y la función, se observa que aparecen las instrucciones jal y jr para el llamado a la función y el retorno al principal

1dc:	34a20000 	ori	v0,a1,0x0
1e0:	00850019 	multu	a0,a1 // comienza la ejecución de la función
1e4:	00001012 	mflo	v0
1e8:	00002010 	mfhi	a0
1ec:	03e00008 	jr	ra l
1f0:	acc40000 	sw	a0,0(a2)
1f4:	0000000c 	syscall
1f8:	03e00008 	jr	ra // retorna al principal
1fc:	00000000 	nop
200:	27bdfff8 	addiu	sp,sp,-8
204:	afa40008 	sw	a0,8(sp)
208:	afa5000c 	sw	a1,12(sp)
20c:	afa60010 	sw	a2,16(sp)
210:	afa70014 	sw	a3,20(sp)
214:	8fa30008 	lw	v1,8(sp)
218:	8fa5000c 	lw	a1,12(sp)
21c:	8fa20010 	lw	v0,16(sp)
220:	8fa40014 	lw	a0,20(sp)
224:	00651821 	addu	v1,v1,a1
228:	00441021 	addu	v0,v0,a0
22c:	00621823 	subu	v1,v1,v0
230:	afa30000 	sw	v1,0(sp)
234:	8fa20000 	lw	v0,0(sp)
238:	03e00008 	jr	ra
23c:	27bd0008 	addiu	sp,sp,8
240:	27bdffe0 	addiu	sp,sp,-32
244:	afbf0018 	sw	ra,24(sp)
248:	24040002 	li	a0,2
24c:	24050003 	li	a1,3
250:	24060004 	li	a2,4
254:	0c000080 	jal	0x200 // hace el llamado a la función
258:	24070005 	li	a3,5
25c:	afa20010 	sw	v0,16(sp)
260:	8fbf0018 	lw	ra,24(sp)
264:	00000000 	nop
268:	03e00008 	jr	ra

Sin embargo no es muy claro el procedimiento en el archivo .lst por eso mostramos a continuación el archivo .s donde claramente se ve la función de jal y jr en el llamado a funciones.

funcion:
	.frame	$fp,16,$31		# vars= 8, regs= 1/0, args= 0, gp= 0
	.mask	0x40000000,-8
	.fmask	0x00000000,0
	.set	noreorder
	.set	nomacro
	
	addiu	$sp,$sp,-16
	sw	$fp,8($sp)
	move	$fp,$sp
	sw	$4,16($fp)  // procedimiento result = (f + g) - (h + i) usando la pila
	sw	$5,20($fp)
	sw	$6,24($fp)
	sw	$7,28($fp)
	lw	$3,16($fp)
	lw	$2,20($fp)
	nop
	addu	$4,$3,$2  // (f + g)
	lw	$3,24($fp)
	lw	$2,28($fp)
	nop
	addu	$2,$3,$2 //(h + i)
	subu	$2,$4,$2 // (f+g) - (h +i)
	sw	$2,0($fp) // el resultado se almacena en $v0=$2
	lw	$2,0($fp)
	move	$sp,$fp
	lw	$fp,8($sp)
	addiu	$sp,$sp,16
	j	$31 // retorna al principal $ra=$31
	nop

	.set	macro
	.set	reorder
	.end	funcion
	.size	funcion, .-funcion
	.align	2
	.globl	main
	.ent	main
main:
	.frame	$fp,32,$31		# vars= 8, regs= 2/0, args= 16, gp= 0
	.mask	0xc0000000,-4
	.fmask	0x00000000,0
	.set	noreorder
	.set	nomacro
	
	addiu	$sp,$sp,-32
	sw	$31,28($sp)
	sw	$fp,24($sp)
	move	$fp,$sp
	li	$4,2 			# 0x2  // carga primer argumento en $a0=$4
	li	$5,3			# 0x3  // carga segundo argumento en $a1=$5
	li	$6,4			# 0x4  // carga primer argumento en $a2=$6
	li	$7,5			# 0x5  // carga primer argumento en $a3=$7
	jal	funcion // hace el llamado a la función
	nop

	sw	$2,16($fp)
	move	$sp,$fp
	lw	$31,28($sp)
	lw	$fp,24($sp)
	addiu	$sp,$sp,32
	j	$31
	nop 

[edit] Flujo de Diseño

[edit] Requerimientos

[edit] Cadena de Herramientas

[edit] Herramientas de síntesis de Xilinx

HERRAMIENTAS PARA EL PROCESADOR PLASMA
Las herramientas que se describen a continuación permiten compilar, sintetizar y enviar archivos .bit y .bin para el procesador PLASMA, trabajando con el sistema operativo GNU/Linux y el paquete ISE WebPack (originalmente probado en Ubuntu).

Las herramientas se basan en los archivos que se encuentran en el proyecto PLASMA de la página OpenCores, y su autor es Steve Rhoads [1]. Estas fueron modificadas por el Ingeniero Carlos Iván Camargo Bareño, profesor de la Universidad Nacional de Colombia.

Se requiere tener instalado GNU GCC MIPS, las instrucciones para instalar este programa se encuentran en http://sites.google.com/site/digitalcc2/ , además hay que tener instalados los drivers USB para la tarjeta de desarrollo en la cual se esté trabajando, de lo contrario no se podrá enviar el archivo bit a la fpga. Las instrucciones para instalar estos drivers están en http://grupos.emagister.com/documento/isewebpack_en_linux_debian_ubuntu_/1041-400257

En el siguiente link están las carpetas que contienen las herramientas:
http://projects.qi-hardware.com/index.php/p/nn-usb-fpga/source/tree/master/plasma
Todas las carpetas son necesarias.

include: Contiene los archivos .h que permiten compilar los programas en c.

kernel:

lib: Contiene los archivos comunes que se utilizan siempre para compilar los programas. Allí encontramos el archivo crt0.S, que se conoce en las herramientas originales como boot.asm.

logic: Contiene todos los archivos vhd del plasma, el proyecto para ISE y un Makefile que permite sintetizar y generar el archivo .bit directamente desde la consola y enviar este a la fpga sin necesidad de abrir ISE ni IMPACT.

src: Contiene el Makefile que compila los programas en c creando los archivos .bin y ram_image.vhd. Se recomienda copiar esta carpeta, cambiarle el nombre y guardarla en el mismo directorio en donde se encuentran las otras carpetas para compilar distintos archivos.c.

tools: Contiene los archivos fuente de las utilidades ramimage y mlite que se encargan de crear el archivo de la ram para ISE y simular el mips. Tambien contiene el Makefile que compila estos archivos fuente y genera los ejecutables.

bootldr: Contiene los archivos fuente de la utilidad bootldr que se encarga de enviar el archivo .bin a la ram. Tambien contiene el Makefile que compila el archivo fuente y genera el ejecutable.

EJEMPLOS DE USO DE LAS HERRAMIENTAS

1. Se compilará un programa escrito en c y se sintetizará junto a los archivos vhd para crear el .bit. Este se envía a través del impact a la fpga. El programa queda guardado en la ram interna del Plasma. Para este ejemplo usaremos un programa prueba.c (puede ser cualquier programa).

a. Copiar la carpeta src, cambiarle el nombre y pegarla en el mismo directorio en donde tenemos las otras carpetas de las herramientas. Para este ejemplo la carpeta se llamará prueba.

b. Entrar a la carpeta prueba y abrir el archivo Makefile con cualquier editor de texto. Modificar la linea "TARGET = bootldr" por el nombre del programa, en este ejemplo sería "TARGET = prueba"

c. Revisamos la siguiente sección de codigo:

   $(TARGET): crt0.o $(TARGET).o no_os.o ddr_init.o
       $(LD) $(ILDFLAGS) -o $@ $^
       $(OBJCOPY) -I elf32-big -O binary $@ $@.bin



Y nos percatamos que se encuentre la bandera $(ILDFLAGS), esta indica que el programa se guardará en la dirección 0x0, es decir en la RAM interna, si queremos llevarla a la memoria externa, en lugar de poner $(ILDFLAGS) debe estar $(LDFLAGS).

d. Copiamos el programa prueba.c en la carpeta prueba.

e. Entramos a través del terminal a la carpeta tools y tecleamos make. Esto generará los ejecutables ramimage y mlite (simulación).

f. Entramos ahora a la carpeta prueba (en el terminal) y tecleamos make. En este momento se generará el archivos prueba.bin.

g. Para crear nuestro archivo ram_image.vhd, necesario para sintetizar el plasma en la ram interna, desde el terminal y ubicados en la carpeta prueba, tecleamos make vhdl_mem, se generará dicho archivo y se guardará automaticamente en la carpeta logic.

A continuación se creará el archivo .bit sin necesidad de abrir el ISE, sin embargo es claro que se debe tener ISE WebPack instalado.
Se entra a la carpeta logic y se abre el Makefile que allí se encuentra. Se deben revisar las siguientes líneas de código:

         PINS           = $(DESIGN).ucf
         DEVICE          = xc3s500e-fg320-4



La linea pins debe contener el archivo *.ucf que estamos utilizando (tambien debe de estar guardado en la carpeta logic) y la línea DEVICE debe contener la referencia de la fpga para la cual se sintetizará el programa. Si estamos trabajando con los archivos originales del plasma la linea pins se puede dejar como está.

h. Una vez hecho estó, a través del terminal se entra a la carpeta logic y se teclea make clean-build y luego make.

i. Conectamos la tarjeta de desarrollo al pc y cuando termine de generar el archivo .bit se debe utilizar el programa especifico para cada FPGA (xc3sprog o nexys2prog usando el siguiente comando ./nexys2prog -v $(DESIGN).bit) este comando enviará el archivo a la fpga como lo hace el impact, pero desde la terminal (Quizas necesite ser root para hacerlo). El comando make upload envia por el puerto /dev/ttyUSB0 es decir el serial (emulado) el archivo .bin que sera implementado en la memoria RAM del procesador.

Listo, Tenemos el archivo bit con el programa prueba.c en la ram interna del PLASMA.

2. Ahora compilaremos el mismo programa escrito en c (prueba.c) y enviaremos el archivo .bin a la RAM externa del plasma. Para esto se utilizará el programa bootldr que viene con las herramientas, este programa está localizado en la carpeta src.

a. Copiar la carpeta src, ponerle el nombre bootldr y pegarla en el mismo directorio en donde tenemos las otras carpetas de las herramientas.

b. Generar el archivo bootldr.bin y el ram_image.vhd del bootldr, enviar estos archivos a la tarjeta de desarrolo de la misma forma en que fue descrito anteriormente. Este programa se debe guardar en la ram interna del plasma.

c. Luego, procedemos a generar el archivo binario para enviarlo a través del puerto serial al PLASMA. Copiamos la carpeta src, nuevamente le ponemos un nombre distinto y la pegamos junto a las otras carpetas de las herramientas. En este ejemplo será prueba. Guardamos el programa prueba.c en esta carpeta.

d. Entramos a la carpeta prueba y abrimos el Makefile. Cambiamos las siguientes lineas de código:

             TARGET    = bootldr


por:

             TARGET    = prueba



             $(TARGET): crt0.o $(TARGET).o no_os.o ddr_init.o
                      $(LD) $(ILDFLAGS) -o $@ $^
                      $(OBJCOPY) -I elf32-big -O binary $@ $@.bin


por:

             $(TARGET): crt0.o $(TARGET).o no_os.o ddr_init.o
                       $(LD) $(LDFLAGS) -o $@ $^
                       $(OBJCOPY) -I elf32-big -O binary $@ $@.bin



Recordemos que $(ILDFLAGS) y $(LDFLAGS) determinan el punto de entrada del programa, en este caso necesitamos que se ejecute desde la RAM externa

e. En el terminal entramos a la carpeta prueba y tecleamos make clean y luego make, se generará el archivo prueba.bin

f. Finalmente tecleamos make upload. Este comando enviará el archivo binario (.bin) a través del cable usb-serial que se encuentra en el dispositivo ttyUSB0. Y listo, se empezará a ejecutar nuestro archivo binario en el plasma.

[1]Proyecto plasma

[edit] Proyectos

Proyectos Universidad Nacional de Colombia (Procesador Plasma)

Personal tools
Namespaces
Variants
Actions
Navigation
interactive
Toolbox
Print/export