Practica Curso Ingenieria-Industrial Electronica
Practica Curso Ingenieria-Industrial Electronica
Practica Curso Ingenieria-Industrial Electronica
Introduccin
Este boletn contiene la descripcin completa de la prctica de curso de este ao: un simulador de CPU.
Un simulador de CPU es un programa que acta interpretando las instrucciones de una CPU y ejecutndolas en un entorno
virtual. El sistema que se emula puede existir realmente, o bien ser un entorno completamente inventado. El simulador es
una herramienta de uso comn cuando se realizan prototipos de sistemas basados en microcontroladores, ya que por lo general
es ms sencillo depurar el programa de control en un simulador del microcontrolador, y luego, una vez libre de errores, grabar
dicho programa en el microcontrolador.
En la industria informtica, la simulacin va un paso ms all, y existen aplicaciones que pueden emular a un computador
completo. Esto se consigue gracias a una tcnica denominada virtualizacin, y permite, por ejemplo, ejecutar un PC
completo con Windows dentro de un ordenador Mac. Tambin permite tener un nico servidor, es decir, una nica mquina
fsica, ejecutando varias instancias de PCs con Linux o Windows. Dicho de otra forma: en el mismo espacio y con los mismos
recursos con los que se mantiene un nico servidor, pueden existir, de forma virtualizada, mltiples servidores.
En el terreno empresarial y domstico, los emuladores se utilizan para ejecutar aplicaciones que originalmente se
escribieron para versiones obsoletas de un ordenador, o sistema operativo, y que deben seguir funcionando aunque los tcnicos
actualicen los ordenadores o el sistema operativo. Esto ocurre cuando la empresa que desarroll la aplicacin ya no da soporte
para nuevas versiones del sistema operativo, o bien resulta que la propia empresa ya no existe. En el hogar, los emuladores se
utilizan sobre todo en el campo del entretenimiento, al permitir ejecutar en un ordenador videojuegos que originalmente
existan como circuitos electrnicos discretos, o grandes placas con memoria y microprocesador, pero que an siguen siendo
perfectamente vlidos para jugar y divertirse.
Recursos
Para ayudaros en la realizacin de esta prctica se dispone del siguiente material, disponible salvo que se diga otra cosa, en
Enseanza Virtual.
Este boletn.
Libro de teora de la asignatura (Fundamentos de Informtica para la Ingeniera Industrial) disponible en versin
impresa en la biblioteca de la E.P.S.
Todos los ejercicios y plantillas descritos en este boletn, resueltos (en formato ejecutable) para comprobar
vuestras soluciones. Disponible para su descarga como fichero ejercicios_simulador.zip. Cuando en un ejercicio
hable de descargar un fichero o plantilla, aqu estar.
Simulador grfico de la Universidad de Oviedo. Es una versin avanzada del simulador que nosotros mismos
haremos. Su archivo de ayuda contiene una descripcin de la CPU que vamos a simular.
Programa ensamblador y simulador (ste ltimo, slo ejecutable), para hacer vuestros propios programas, o
ensamblar los que se os den ya escritos, para probar en vuestro simulador y en el que se os entrega, a fin de
comprobar su correcto funcionamiento.
La primera columna indica el nmemotcnico de la instruccin. Esta es una abreviatura de la accin que realiza la
instruccin, tal como MOV por move, SUB por substract, JMP por jump, etc.
El formato de la instruccin es la secuencia, escrita en formato binario, de esa instruccin. Es decir, describe qu
campos tiene esa instruccin y qu hay en cada campo. En la prctica 3 se empezar a usar esto.
La columna Excepciones indica si esta instruccin puede generar alguna excepcin. Una excepcin cualquier
suceso no contemplado en el flujo normal de un programa, y habitualmente significa que algo est mal escrito o
mal codificado en ese programa.
La columna Ejemplo muestra un ejemplo de uso de esa instruccin, detallando los contenidos de los registros
antes y despus de ejecutar la instruccin. Cuando as sea necesario, tambin se detallar el contenido de la
memoria y las banderas de condicin.
Nemotcnico
Formato de la
instruccin (binario)
Descripcin
Excepciones
Ejemplo de uso
Nemotcnico
MOV [Ri],Rs
Formato de la
instruccin (binario)
00 010 Ri Rs 00000
Descripcin
Excepciones
Ejemplo de uso
Excepcin de memoria,
si la direccin a la que
se pretende escribir est
fuera de rango.
MOV [R4],R0
Binario: 00 010 100 000 00000b
Hexadecimal: 1400h
Antes de esta instruccin:
R0 = FF12h R4 = 12ABh La posicin
12ABh de memoria contiene el valor 2855h.
Despus de esta instruccin:
La posicin 12ABh de memoria contiene el
valor FF12h
MOVL R5,5Ah
Binario: 00 100 101 01011010b
Hexadecimal: 255Ah
Antes de esta instruccin:
R5 = 1812h
Despus de esta instruccin:
R5 = 185Ah
MOVH R0,EFh
Binario: 00 101 000 11101111b
Hexadecimal: 28EFh
Antes de esta instruccin:
R0 = 6B03h
Despus de esta instruccin:
R0 = EF03h
STOP
Binario: 00 111 00000000000
Hexadecimal: 3800h
MOVL Rd,inm8
00 100 Rd inm8
No tiene.
MOVH Rd,inm8
00 101 Rd inm8
Guarda en el byte ms
significativo del registro Rd el
valor inm8
No tiene.
STOP
00 111 00000000000
No tiene.
Nemotcnico
COMP Rs1,Rs2
Formato de la
instruccin (binario)
01 00111 Rs1 Rs2 000
Descripcin
Excepciones
Ejemplo de uso
No tiene.
COMP R2,R7
Binario: 01 00111 010 111 000b
Hexadecimal: 4EB8h
Antes de esta instruccin:
R2 = AA15h R7 = 79F0h
Despus de esta instruccin:
ZF=0, SF=0, CF=1, OF=1
NOT Rds
No tiene.
NOT R4
Binario: 01 01000 0100 000000b
Hexadecimal: 5080h
Antes de esta instruccin:
R4 = 1234h
Despus de esta instruccin:
R4 = EDCBh
ZF=0, SF=1, CF=0, OF=0
INC R1
Binario: 01 01001 001 000000b
Hexadecimal: 5240h
Antes de esta instruccin:
R1 = 90BFh
Despus de esta instruccin:
R1 = 90C0h
ZF=0, SF=1, CF=0, OF=0
DEC R7
Binario: 01 01010 111 000000b
Hexadecimal: 55C0h
Antes de esta instruccin:
R7 = 4098h
Despus de esta instruccin:
R7 = 4097h
ZF=0, SF=0, CF=0, OF=0
NEG R6
Binario: 01 01011 110 000000b
Hexadecimal: 5780h
Antes de esta instruccin:
R6 = 80B2h (-32590 en decimal)
Despus de esta instruccin:
R6 = 7F4Eh (32590 en decimal)
ZF=0, SF=0, CF=0, OF=0
CLR R3
Binario: 01 01100 011 000000b
Hexadecimal: 58C0h
Antes de esta instruccin:
R3 = F12Fh
Despus de esta instruccin:
R3 = 0000h
ZF=1, SF=0, CF=0, OF=0
INC Rds
No tiene
DEC Rds
No tiene
NEG Rds
Realiza el complemento a 2
del valor del registro Rds
No tiene
CLR Rds
No tiene
10 desplaz
Excepcin de memoria
si la direccin destino
de salto est fuera del
rango de la memoria
disponible.
JMP -23
Binario: 10 11111111101001b
Hexadecimal: BFE9h
Antes de esta instruccin:
PC = 0120h
Despus de esta instruccin:
PC = 0109h
11 cond desplaz
Excepcin de memoria
si la direccin destino
de salto est fuera del
rango de la memoria
disponible.
BR S,+42
Binario: 11 110 00001000010b
Hexadecimal: F042h
Antes de esta instruccin:
PC = 3200h
SF=0
Despus de esta instruccin:
PC = 3200h (no se modifica PC, al no
cumplirse la condicin)
BR NZ,+42
Binario: 11 101 00001000010b
Hexadecimal: E842h
Antes de esta instruccin:
PC = A004h
ZF=0
Despus de esta instruccin:
PC = A02Eh (se modifica PC, al cumplirse la
condicin)
Programa ejemplo
Para comprobar el correcto funcionamiento del simulador, puede emplear el programa de ejemplo mostrado en la siguiente
tabla, que al ejecutarse, guarda en las posiciones de memoria 0000h a 000Fh las potencias de 2, desde 20 hasta 215 inclusive.
El cdigo de este programa est pensado para que comience a ejecutarse a partir de 0010h. En la siguiente tabla mostramos
el cdigo (slo el cdigo, no los datos) del programa, que comienza en 0010h. En las direcciones anteriores, 0000h a 000Fh
estarn los datos que este programa escribe.
Las direcciones de memoria se dan habitualmente en hexadecimal, as que hemos empleado ese formato en este ejemplo.
Direccin
0010
0011
Nmemotcnico
MOVH R0,00h
MOVL R0,00h
Hex.
2800
2000
0012
0013
0014
0015
MOVH
MOVL
MOVH
MOVL
00
00
00
00
2900
2101
2A00
2210
0016
0017
0018
CLR R3
MOV [R0],R1
ADD R1,R1,R1
58C0
1020
4049
0019
INC R0
5200
001A
001B
001C
DEC R2
COMP R2,R3
BR NZ,-6
5480
4E98
EFFA
001D
STOP
00 111 00000000000
3800
R1,00h
R1,01h
R2,00h
R2,10h
Formato binario
101
100
101
100
001
001
010
010
00000000
00000001
00000000
00010000
Comentario
Estas dos instrucciones cargan el registro R0 con el valor 0000h. Como slo puede
cargarse un valor inmediato a una mitad del registro, usamos dos instrucciones, una para
cada mitad. R0 contendr la direccin de memoria donde iremos escribiendo cada
potencia.
Usamos el mismo mtodo para cargar el registro R1 con el valor 0001h. R1 es el valor de
cada potencia. Ir cambiando a 2,4,8,16, Al principio vale 1, que es 20.
Y el mismo mtodo para que R2 valga 0010h (que es 16 en decimal). R2 es un contador
que va decrementndose cada vez que se escriba una potencia. Vamos a escribir 16
resultados.
Ponemos a 0 el registro R3. Lo usaremos ms tarde.
Guardamos el valor actual del registro R1 en la direccin de memoria apuntada por R0
Hacemos R1R1+R1, o lo que es lo mismo, R1R1*2, con lo que R1 contiene ahora la
siguiente potencia de 2 a escribir.
Incrementamos R0 para que apunte a la prxima direccin de memoria en donde hay que
escribir.
Decrementamos R2 para indicar que ya hemos escrito una potencia.
Comparamos R2 con cero (R3). R2 ser cero cuando hayamos escrito las 16 potencias.
Si no es cero, saltamos 6 posiciones hacia atrs contando desde la posicin de memoria
siguiente a esta (001D). Es decir, saltamos a 001D-0006=0017h. Con esto volvemos hacia
atrs al punto donde escribimos la siguiente potencia en memoria.
Fin de la ejecucin. Salimos del simulador.
0000
0000
0000
58C0
0010
0000
0000
1020
0000
0000
2800
4049
0000
0000
2000
5200
0000
0000
2900
5480
0000
0000
2101
4E98
0000
0000
2A00
EFFA
0000
0000
2210
3800
ser esta una mquina Von Neumann, los datos y el cdigo comparten la memoria, as que el contenido de memoria.txt contiene
tanto el cdigo, como las posiciones de memoria donde irn los datos de salida. Los siguientes 16 valores son todos 0000h, y
son los valores iniciales de las posiciones de memoria desde la 0000h hasta 000Fh. En la siguiente posicin de memoria, en la
0010h se ubicar el valor 2800h, que es la versin en hexadecimal de la instruccin MOVH R0,00h tal y como se puede ver en
la tabla del programa de ejemplo. El ltimo nmero que aparece en el archivo es 3800h, que es el cdigo de STOP, la ltima
instruccin de nuestro programa.
A efectos de simplificar la lectura de este fichero, el simulador asumir que el fichero, de existir, es correcto. Es decir, que
se ajusta al formato descrito. Esto significa que una vez abierto el fichero siempre podremos asegurar que habr al menos dos
valores en l: el valor de la primera direccin de memoria, y el valor inicial del PC, y en ese orden.
Las posibles excepciones que pueden ocurrir durante la ejecucin de un programa, y que nuestro simulador debe detectar,
son:
Que se intente ejecutar una instruccin que no existe.
Que se intente leer un valor desde una posicin de memoria ilegal (fuera de rango).
Que se intente escribir un valor a una posicin de memoria ilegal (fuera de rango).
Que se intente realizar un salto (condicional o incondicional) a una posicin de memoria ilegal (fuera de rango).
Para las excepciones que tienen que ver con direcciones ilegales, recordar que la memoria que maneja nuestro simulador
est limitada a TMEMORIA elementos, y TMEMORIA puede ser un nmero pequeo. En la captura de pantalla precedente, el
valor de TMEMORIA que tiene el simulador estaba fijado en 1000 posiciones de memoria. A qu direccin de memoria
pretenda saltar la instruccin modificada?
Es decir, para el simulador grfico, la instruccin STOP es una instruccin invlida y por ello muestra una excepcin.
OJO! es una excepcin para el simulador grfico, para nuestro simulador, STOP es una instruccin vlida. El resto de
instrucciones que no sean vlidas en nuestro simulador tampoco lo sern en el simulador grfico.
Para probar el ejemplo de la seccin anterior, crea un fichero de texto con el Bloc de Notas conteniendo el programa de
ejemplo: un nmero hexadecimal en cada lnea. Grbalo en disco con un nombre sencillo, como por ejemplo potencias.prg.
Este simulador espera que los ficheros de entrada tengan extensin PRG en lugar de TXT como usaremos nosotros.
Una vez tengas este fichero creado, procede de esta manera:
8
Busca la ubicacin del fichero potencias.prg y especifica que quieres buscar archivos con la extensin PRG.
Esta es la ventana del desensamblador. Se puede elegir qu zona de memoria se quiere desensamblar. En nuestro caso,
nos interesa ver el cdigo que hay en la zona de memoria cuya direccin est en el registro PC, que es el registro que
indica cul es la direccin de la prxima instruccin a ejecutar. Ntese como en el listado del desensamblador, la
instruccin cuyo cdigo es 3800h no tiene traduccin nmemotcnica. En el simulador de la prctica esta instruccin es
STOP, pero en esta versin dicha instruccin no existe, por lo que no se puede desensamblar.
Existe una ventana similar, que es el Editor Hexadecimal. Permite mostrar, en hexadecimal, cualquier porcin de la
memoria de la CPU.
A efectos de esta prctica, nos interesa la ejecucin por instrucciones. Pulsando F8 vemos como la Unidad de Control
UC) decodifica y ejecuta cada instruccin que lee. El registro de instruccin IR contiene dicha instruccin, la cual
podemos ver en formato hexadecimal y en formato nmemotcnico. En el grfico se puede ver el momento en el que se
est a punto de ejecutar la instruccin MOV [R0],R1, despus de haber ejecutado (usando varias veces F8) todas las
instrucciones anteriores MOVL,MOVH y CLR, as que los registros R0,R1,R2 y R3 contienen los valores con los que el
programa los ha iniciado.
Ejercicios preliminares
Estos ejercicios estn pensados para realizarse despus de la explicacin en clase de teora de la organizacin de una CPU.
No se necesita programar nada en C, pero s usaremos el simulador grfico de CPU disponible en Enseanza Virtual.
EJERCICIO 1.
Haz de CPU y usando lpiz y papel para representar la memoria y los registros, ejecuta una a una las instrucciones que hay
en el programa de ejemplo de este boletn. Llega al menos dos veces hasta la instruccin BR NZ (a esto se le llama hacer la
traza de un programa).
Este ejercicio, aun pareciendo el ms pueril, es quizs el ms importante para comprender cmo funciona esta CPU, y por
ende, cmo debe simularse. Trabaja siempre que puedas en hexadecimal. Aunque no lo parezca, es ms cmodo que trabajar
en decimal, al menos para CPU y este ejemplo.
EJERCICIO 2.
Carga y realiza la simulacin grfica del programa de ejemplo tal como se ha descrito. Realiza la simulacin hasta que se
ejecute la instruccin STOP y el simulador grfico muestre el error de instruccin no vlida. Entonces, usa la ventana del
Editor Hexadecimal para comprobar que el programa ha escrito los valores que se esperaban a partir de la posicin 0000h de
memoria. Recuerda que, si el programa se ha ejecutado correctamente, a partir de esa posicin vers potencias de 2, es decir: 1,
2, 4, 8, 16, 32, 64, etc, pero como el editor hexadecimal muestra contenidos en hexadecimal, se vern como 0001, 0002, 0004,
0008, 0010, 0020, etc.
EJERCICIO 3.
En la tabla de instrucciones se especifica que la instruccin BR cond desplaz suma el valor desplaz al valor actual del
registro PC. Qu valor tiene el registro PC cuando se realiza esa suma? Sigue apuntando el valor de PC a la instruccin que
estamos ejecutando (el salto condicional) o ya no? Para averiguarlo, durante la simulacin, al llegar a la instruccin BR NZ,
ejectala ciclo a ciclo (F7).
10
Cdigo de clase de instruccin (existe en todas las instrucciones y siempre est situado en el mismo sitio).
Operando destino (hay instrucciones en que este operando tambin es operando fuente).
Valor inmediato (slo en las instrucciones en las que se defina este valor).
El cdigo de clase de instruccin es el nico cdigo que debe existir en todas las instrucciones. Ocupa siempre los dos bits
ms significativos de la instruccin, y por tanto su valor puede ser 00, 01, 10 11 (0,1,2 3). En el enunciado de la prctica se
ofrece ms informacin sobre los cdigos de clase de una instruccin.
El cdigo de instruccin existe slo si el cdigo de clase agrupa ms de una instruccin (en nuestra CPU esto sucede con
los cdigos de clase 00 y 01, ya que los cdigos de clase 10 y 11 definen, cada uno, una nica instruccin). El tamao del
cdigo de instruccin depende de cul sea el cdigo de clase: para el cdigo de clase 00, el cdigo de instruccin ocupa tres
bits; para el cdigo de clase 01, ocupa cinco bits. El tamao del cdigo de instruccin indica cuntas instrucciones diferentes
pueden definirse dentro de una clase de instruccin. As, podemos ver que pueden existir hasta 8 instrucciones de clase 00, y
32 instrucciones de clase 01. En la CPU, no todas las combinaciones posibles del cdigo de instruccin tienen por qu estar
usadas. Puede haber cdigos de instruccin que no identifiquen a ninguna instruccin conocida de la CPU. Por ejemplo, no
existe una instruccin con cdigo de clase 00 y cdigo de instruccin 110. Si la CPU se encontrara con una instruccin as,
tiene que informar de una excepcin.
El cdigo de tipo de instruccin, si existe, aparece en la instruccin ocupando los siguientes bits despus del cdigo de
clase.
Por ejemplo, la instruccin 258Ah se puede expresar en binario como 0010 0101 1000 1010. Los dos bits ms
significativos son 00, as que sta es una instruccin de movimiento (ver enunciado de la prctica).
Si es una instruccin de movimiento, el cdigo de tipo de instruccin existe y ocupa los siguientes tres bits. En nuestro caso
esos tres bits tienen el valor 100. Segn la tabla de instrucciones de la prctica, es una instruccin MOVL Rd,Inm8.
Sabiendo en este punto qu instruccin es, podemos decodificar los operandos que necesita: en este caso, esta instruccin
necesita un operando que es un registro destino (Rd) cuyo cdigo ocupa tres bits, y un valor inmediato de 8 bits, que ocupa
como es de esperar, ocho bits. En la tabla de instrucciones de la prctica vemos dnde estn posicionados cada uno de estos
campos. Para este ejemplo, el valor de Rd ser 101 (5) y el valor inmediato de 8 bits, 1000 1010 (8Ah).
As, la instruccin decodificada es: MOVL R5,8Ah
11
Escribe un programa que lea un nmero entero por teclado, lo desplace 1 vez a la izquierda y guarde el resultado de dicho
desplazamiento a una variable. Imprime el valor original y desplazado, en pantalla. Modifica el programa para que el
desplazamiento sea, por ejemplo de 2 lugares a la izquierda, y luego vuelve a modificarlo para que sean 3 lugares. En cada
caso, anota los valores antes y despus del desplazamiento. A la vista de los resultados, contesta: Cuando desplazamos una
variable N posiciones a la izquierda, qu estamos haciendo en realidad? NOTA: cuando ejecutes el programa y pida un valor
por teclado, usa valores pequeos y positivos, para ver mejor qu est pasando.
El desplazamiento a la derecha es similar. Al desplazar a la derecha, los bits menos significativos se pierden. Los bits que
entran por la izquierda no obstante, pueden ser 0 1:
Si la variable en la que se realiza el desplazamiento tiene signo (char, short int, o int), entonces el bit que entra por la
izquierda es el mismo que el bit de signo del valor original de la variable.
Si la variable en la que se realiza el desplazamiento no tiene signo (unsigned char, unsigned short int, o unsigned int),
entonces el bit que entra por la izquierda es un 0.
Ejemplo: una variable a de tipo short int tiene el valor 843Fh, que expresado en binario es 1000 0100 0011 1111. La
variable b tambin es de tipo short int. Al hacer b = a>>2, b toma el valor en binario: 1110 0001 0000 1111, que en
hexadecimal es E10Fh.
EJERCICIO 5
Re-escribe el programa del ejercicio 1, pero ahora en lugar de desplazar a la izquierda, desplazamos a la derecha. A la vista
de los resultados, contesta: cuando desplazamos una variable N posiciones a la derecha, qu estamos haciendo en realidad?
Qu sentido tiene aqu distinguir el caso en que la variable tenga signo, o no lo tenga?
En la extraccin de campos de una instruccin usaremos una mezcla de instrucciones AND y desplazamientos. La
extraccin de un campo tiene dos operaciones: posicionamiento del campo, y enmascaramiento. Lo veremos con un ejemplo:
Usaremos una variable de tipo unsigned short int (16 bits sin signo) llamada ins que guarda la instruccin completa. En
nuestro ejemplo, el valor que tiene guardado en este momento esa variable es 40B7h. Queremos saber cul es la clase de
instruccin, y si es posible, el tipo de instruccin.
El cdigo de clase de instruccin ocupa los dos bits ms significativos del valor. Grficamente, seran los dos espacios
sombreados de la figura. C1 sera el bit ms significativo del cdigo de clase, y C0 el menos significativo.
C1
C0
Posicionar el campo consiste en llevar estos dos bits al extremo derecho, as:
C1
C0
Para ello habr que desplazar a la el valor de la variable ins 14 posiciones a la derecha:
12
ins>>14
Una vez que tenemos el campo posicionado, hay que enmascarar los bits no usados. El enmascaramiento es una operacin
AND aplicada a un nmero que asla determinados bits de dicho nmero, los cuales quedan con su valor original. El resto de
bits queda a 0.
En nuestro caso, nos interesa dejar con su valor original a los dos bits que contienen el cdigo de clase, y que previamente
hemos llevado hasta el extremo derecho del valor de 16 bits. Es decir, en este ejemplo nos interesa dejar intactos a los bits 0 y
1. El resto estarn a 0. Para ello usaremos este valor de mscara:
0
C0
C1
C0
Supongamos que queremos guardar el cdigo de clase de instruccin en la variable clase. Lo haramos as:
clase = (ins>>14)&3;
Ahora podemos preguntar por el valor de clase, y segn qu valor tenga (0,1,2 3) sabremos si hay o no cdigo de
instruccin y cmo se extrae.
Por ejemplo, supongamos que queremos extraer el cdigo de instruccin para el caso en que el cdigo de clase sea 0, y
guardar el valor de dicho cdigo en la variable codins. Miramos la tabla de instrucciones y vemos que el cdigo de instruccin
en este caso ocupa 3 bits (I2, I1, I0), as:
I2
I1
I0
Escribe un programa en C (descargable en la web como simej6.c) que incluya una funcin que llamaremos obtclase
(obtener clase). Esta funcin toma como argumento un valor entero de 16 bits, sin signo, que representa una instruccin, y
devuelve un nmero entero sin signo como resultado de las operaciones descritas anteriormente para hallar la clase de una
instruccin. Este nmero entero, por fuerza, estar comprendido entre 0 y 3.
Para probar esta funcin, aade al programa en C (a main) dos variables enteras, de 16 bits, sin signo, llamadas: ins, y
clase.
El programa pedir al operador por teclado un nmero en formato hexadecimal (es decir, usa %x dentro de scanf), que se
almacenar en una variable entera (por ejemplo, la podemos llamar aux). A continuacin, el contenido de aux se asigna a la
variable ins. Este nmero guardado en ins ser una instruccin de 16 bits de la CPU virtual. El programa usar la funcin
obtclase para guardar en la variable clase el cdigo de clase de la instruccin, devuelto por la funcin. Finalmente, y en
funcin del valor de clase (que ser 0, 1, 2, 3) mostrar por pantalla un mensaje indicando a qu clase pertenece la
instruccin entrada por teclado (0: movimiento, 1: artimtica, 2: salto incondicional o 3: salto condicional).
La razn de usar la variable aux (por auxiliar) en el scanf() es porque scanf() no reconoce las variables de tipo short int,
como ins, y para ella, una variable de tipo short int es como si fuera de tipo int. Lo malo es que una variable de tipo int ocupa 4
bytes, y una short int, 2, con lo que al guardar el valor que se ha ledo de teclado, en el caso de una variable short int, scanf
escribe fuera del espacio asignado a esa variable. Usando una variable auxiliar que sea int evitamos este error. La asignacin
ins=aux almacena en la variable ins, de forma segura, lo que se ha ledo en aux.
13
Es decir, que cada vez que queramos leer de teclado (o en su momento, de fichero) un dato en formato hexadecimal con la
intencin de guardarlo en una variable que sea de tipo short int, haremos esto:
short int ins;
int aux;
......
scanf (%x, &aux); /* leemos de teclado y guardamos en variable INT */
ins = aux; /* el valor de la variable entera lo copiamos en la variable de tipo SHORT INT */
EJERCICIO 7
Escribe otra funcin a la que llamaremos obtcodigo (obtener cdigo de instruccin). Esta funcin toma dos argumentos: el
primero es un valor entero de 16 bits, sin signo, que representa una instruccin, y el segundo es un nmero entero que
representa su cdigo de clase (que habremos averiguado previamente usando la funcin obtclase con la misma instruccin), y
devuelve un nmero entero con signo como resultado de las operaciones descritas anteriormente para hallar el cdigo de una
instruccin.
En esta primera versin de obtcodigo, slo vamos a calcular el cdigo de la instruccin suponiendo que la clase de la
instruccin sea siempre 0. Esta suposicin es necesaria para simplificar este ejercicio, ya que el cdigo de una instruccin se
calcula de distinta manera segn cul sea la clase de dicha instruccin. As, para esta primera versin de obtcodigo (la
mejoraremos en siguientes ejercicios), sencillamente ignoraremos el segundo argumento por ahora, el que representa el cdigo
de clase de la instruccin.
Incorpora esta funcin al programa que ests escribiendo en el ejercicio 6. La nueva versin del ejercicio 6 es un programa
que pide por teclado una instruccin, calcula su cdigo de clase, y si dicho cdigo de clase es 0, entonces calcula adems su
cdigo de instruccin. Para esto es para lo que usamos las dos funciones que hemos definido. Una vez calculado el cdigo, lo
imprimir en hexadecimal en pantalla. Probad este programa nicamente con instrucciones de la clase 0, ya que para otras
clases, su comportamiento estar indefinido.
EJERCICIO 8
Ampla la funcin obtcodigo para que calcule tambin los cdigos de las instrucciones cuyo cdigo de clase es 1, 2 y 3, e
decir, el de todas las instrucciones. Ahora s que usamos el segundo parmetro de obtcodigo, que en la versin primera era
ignorado, para segn qu clase tenga la instruccin, calcular el cdigo de instruccin de una forma u otra.
Para las instrucciones de la clase 2, en las que no hay cdigo, sencillamente se devuelve 0.
Para las instrucciones de clase 3, el cdigo que se calcula y devuelve ser el cdigo de la condicin. Como este cdigo
ocupa 3 bits, igual que en el caso de las instrucciones de clase 0, en realidad las instrucciones de clase 0 y 3 se tratarn de la
misma formas.
Una vez realizada la ampliacin modifica el programa del ejercicio 6 para que muestre en pantalla la clase y el cdigo de la
instruccin tecleada.
Para probar los programas de los ejercicios 6, 7 y 8, usa los ejemplos de instrucciones que hay en la tabla de instrucciones
del enunciado de la prctica.
Los programas escritos en estos ejercicios, sobre todo el del ejercicio 8, nos permiten discernir, dada una instruccin leda,
saber de qu clase es, y dentro de la clase, saber cul de ellas es. Una vez que tengamos una instruccin identificada, podemos
seguir decodificando el resto de sus campos para obtener los operandos que necesita. Una vez se saben los operandos, estamos
listos para ejecutar la instruccin, es decir, hacer los cambios y operaciones necesarias en los registros de la CPU segn la
instruccin que hemos ledo.
En una CPU real, las instrucciones se leen directamente de memoria y se van decodificando. Por diversas causas, pueden
aparecer instrucciones desconocidas, o instrucciones cuyos operandos no son correctos. Cuando esto pasa, la CPU no puede
ejecutar la instruccin y ocurre una excepcin. Las excepciones ocurren con muchsima frecuencia: la mayora de dichas
excepciones son benignas, es decir, no conducen a tener que abortar ningn programa que se est ejecutando en el
ordenador, pero hay veces en que esto no es as, y una excepcin obliga al sistema operativo a terminar bruscamente un
programa. Quizs el ejemplo ms conocido sea la ventana que aparece en Windows cuando un programa sufre un error (casi
siempre, por estar mal escrito).
14
Usando como partida la plantilla de nombre simej9.c , escribe una funcin que se llamar simulador. De momento, esta
funcin no tiene argumentos de entrada, ni devuelve ningn valor a la salida. Es decir, su prototipo ser:
void simulador (void);
Esta funcin usar un bucle que pedir instrucciones (nmeros hexadecimales de 4 dgitos, o 16 bits) por teclado al usuario.
Incorpora tambin al programa las funciones obtclase y obtcodigo, para que la nueva funcin pueda calcular el cdigo de clase
y el cdigo de instruccin de la instruccin recin introducida y mostrarlo por pantalla. El programa repetir las operaciones de
pedir instruccin y mostrar cdigo y clase mientras la instruccin leda no sea STOP. Cuando la instruccin leda sea STOP, el
bucle terminar y mostrar un mensaje como ste:
Fin de programa, Codigo de operacion ledo es STOP
Para que el programa sea ms claro, se aconseja definir una constante (usando #define), de nombre STOP y de valor
0x3800 (que es el cdigo de operacin de STOP en hexadecimal, tal y como se puede comprobar en la tabla de instrucciones
del enunciado de la prctica.)
EJERCICIO 10
Modifica la funcin simulador del ejercicio anterior para que declare una variable entera llamada pc, inicializada a 0, y la
incremente dentro del bucle. Dentro del bucle debe mostrar un mensaje como:
Dame instruccin en la direccin %4.4X:
Al ejecutarse, %4.4X se sustituir por el valor de la variable pc (aparecer como un nmero hexadecimal de 4 dgitos,
relleno con ceros a la izquierda). La idea es que mientras que el programa se ejecuta, el usuario hace el papel de memoria de
la CPU, y va suministrando instrucciones a medida que la CPU las pide. El nmero que aparece en el mensaje de cortesa sera
la direccin de memoria donde est la instruccin que la CPU quiere leer. Al comenzar esta direccin en 0, el valor de pc se
convierte, de facto, en una cuenta de instrucciones.
Cuando el bucle termina, el mensaje final cambia a ste:
Fin de programa, Codigo de operacion ledo es STOP. Se leyeron n instrucciones
Donde n es el valor de pc tras el bucle (por ahora, coincide con el nmero de instrucciones ledas)
EJERCICIO 11
Vamos a darle algo de cuerpo al simulador: tenemos un programa, el del ejercicio 10, que constituye el bucle principal
de emulacin de la CPU: mientras no se termine la emulacin, la CPU debe seguir leyendo instrucciones. Una vez que
tenemos un nuevo cdigo de operacin de instruccin, tendremos que preguntar cul es, para segn sea uno u otro, realizar las
operaciones pertinentes.
Modificar la funcin simulador para que, dentro del bucle y una vez que tenemos una nueva instruccin introducida por
teclado, el programa la decodifique e imprima por pantalla el mnemotcnico de dicha instruccin (STOP, MOVH, MOVL,
etc). Para ello ser muy til la construccin if-else if-else if vista en la prctica Condiciones. Al principio, decodifica
solamente las instrucciones cuyo cdigo de clase sea 0 (las instrucciones de movimiento). Luego ve ampliando la funcin,
aadiendo ms if-else if para decodificar el resto de instrucciones. El esquema de esta decodificacin, que estara en el cuerpo
del bucle, sera as:
LEE instruccin en variable ins /* usa en realidad, aux para leer la instruccin como se ha avisado */
codclase = obtclase (ins)
codins = obtcodigo (ins, codclase)
SEGN codclase HACER
CASO 0: SEGN codins HACER /* instrucciones de carga y almacenamiento */
CASO 0: ...
CASO 1: ...
CASO 2: ...
CASO 4: ...
CASO 5: ...
CASO 7: fin_simulacion=1 /* se ha leido la instruccin STOP */
POR DEFECTO: EXCEPCION
FIN SEGUN
CASO 1: /* instrucciones aritmtico-lgicas */
CASO 2: /* salto incondicional */
CASO 3: /* salto condicional */
FIN SEGUN
Recuerda que las secuencias if-else if terminan con un else (sin un if a continuacin). Ese ltimo else es el caso que se
ejecuta cuando ninguna de las anteriores condiciones se cumple. Para nosotros esto pasa cuando nos encontramos con un
cdigo de instruccin para el que no hay una instruccin definida. Entonces, el programa debe imprimir la palabra
EXCEPCION.
15
El programa de este ejercicio lo haremos en un fichero aparte, no nos basaremos en ejercicios anteriores. Ms tarde,
juntaremos el cdigo de este ejercicio con los anteriores. Usaremos como plantilla el fichero simej12a14.c
En el programa, define primero una constante (usando #define) que d un valor a TMEMORIA. Para empezar, un valor de
100 no est mal. Declara un vector de TMEMORIA elementos, donde cada elemento ser de 16 bits. Por ejemplo, as:
#define TMEMORIA 100
Ya dentro del cdigo, hay que declarar e inicializar este vector. Para declarar el vector, llmalo mem. Segn las
especificaciones de la prctica, los elementos no usados de la memoria estarn a 0, as que comenzaremos por inicializar toda
la memoria a 0. Como son muchos elementos, conviene hacerlo con un bucle, que inicialice cada elemento.
Escribe una funcin que llamaremos InicializarMemoria, con este prototipo:
void InicializarMemoria (unsigned short m[], int tmem);
Dentro de la funcin, m ser el vector que contiene a la memoria, y tmem, un valor entero que indica cuntos elementos
tiene el vector. La funcin se encargar de poner un 0 en cada elemento de m.
A esta funcin la llamaremos desde main() de esta forma:
InicializarMemoria (mem, TMEMORIA);
Nuestra CPU necesita 8 registros, que en la especificacin estn nombrados como R0, R1, R2, etc. Cmo es mejor
hacerlo? 8 variables llamadas r0,r1,,r7? O un vector de 8 elementos r[8]? Antes de decidirte por uno u otro, piensa en lo
que tendras que hacer para, por ejemplo, implementar una instruccin como ADD Rd,Rs1,Rs2, donde hay que coger el valor
del registro nmero s1, sumarlo con el valor del registro nmero s2, y el resultado guardarlo en el registro de nmero d. Los
valores de s1, s2 y d estaran en sendas variables.
Una vez que lo hayas pensado, declara los registros generales, e inicialzalos todos al valor 0.
EJERCICIO 13
La memoria est inicializada a 0, pero esto slo, no nos vale para el simulador. La memoria debe contener el programa que
se va a ejecutar (y el resto, a 0). En este ejercicio, vamos a rellenar parte de la memoria con un programa. En la prctica final
esto se hace leyendo un fichero. Nosotros de momento vamos a leer desde el teclado.
Escribe una funcin a la que llamaremos CargarMemoria, con este prototipo:
void CargarMemoria (unsigned short m[]);
Esta funcin necesita poder leer y modificar una serie de variables que tambin se necesitan en otros puntos del programa.
Si definimos esas variables dentro de la funcin, no podrn ser usadas por otras funciones. Para remediarlo, usaremos variables
globales, que son variables que pueden ser ledas y modificadas por cualquier funcin.
Una variable global es toda variable que se declare fuera de una funcin. Nosotros definiremos las variables globales
despus de los #includes y #defines, y antes de los prototipos.
Aade una variable global que llamaremos pc. Ya sabes para qu sirve. Aade como variables globales a los registros y al
vector mem.
La funcin CargarMemoria pide por teclado (con su correspondiente mensaje de cortesa) un valor en hexadecimal, que
ser la posicin de memoria a partir de la cual se va a almacenar el programa. Este valor deber guardarse tambin en una
variable global, que llamaremos pos_origen. Copiamos su valor a otra variable (sta, local), que llamaremos pos_actual.
A continuacin, pediremos al usuario por teclado la direccin, en hexadecimal, de la primera instruccin a ejecutar una vez
que comience la simulacin. Dnde hay que guardar este valor? En una variable que represente a un registro del procesador
que seale a la prxima instruccin a ejecutar. Esa tambin es una variable global. Mantendremos adems una copia de este
valor en otra variable global.
A continuacin, el programa ir pidiendo al usuario valores hexadecimales. Cada valor se ir guardando en la memoria, en
el elemento (direccin) que indique la variable pos_actual. A cada nuevo valor introducido en memoria, la direccin
(pos_actual) donde se almcena, se ir incrementando. Esto se repetir MIENTRAS QUE haya an datos que introducir.
16
Cuando se lee de un fichero, existe una forma de detectar que no hay ms datos para introducir. Este sistema tambin se
puede usar para el teclado. Simplemente hay que saber que el teclado se llama stdin . La forma de saber si ya no hay ms datos
para introducir por teclado es usando la funcin feof() con el parmetro stdin. Por ejemplo, el siguiente programa:
#include <stdio.h>
int main()
{
int n;
Pide nmeros por teclado al usuario y los va imprimiendo en pantalla, hasta que el usuario, en respuesta a una peticin de
un nmero pulsa las teclas Ctrl y Z simultneamente. Esta pulsacin va a ser nuestra forma de decirle al ordenador que no
queremos introducir ms nmeros (en otros sistemas operativos, como Linux u OS X, se usa la combinacin Ctrl-D en lugar de
Ctrl-Z).
Al pulsar esta combinacin de teclas, aparecer en pantalla la secuencia
Intro se enva la secuencia a scanf.
^Z
(el carcter
La llamada a la funcin feof(stdin) devuelve VERDADERO si la ltima vez que se pidieron datos por teclado, el usuario
pulso Ctrl-Z. Devuelve FALSO si no fue as. De esa forma, el bucle se lee como Mientras NO se haya pulsado Ctrl-Z
El algoritmo a implementar en la funcin CargarMemoria es ste:
LEER direccion origen de los datos en variable pos_origen
pos_actual pos_origen
LEER direccion inicial de ejecucin en variable pc
pc_inicial pc
LEER instruccin
MIENTRAS QUE en la ultima lectura no se haya pulsado ctrl-z HACER
Copiar a la direccion pos_actual de memoria el valor de instruccin
Incrementar pos_actual
LEER instruccin
FINMIENTRAS
Al finalizar este bucle, en la variable pos_actual estar el valor de la primera direccin de memoria libre, tras haber
introducido el programa y los datos. Este valor lo guardaremos en una variable global a la que llamaremos pos_final. Ahora
tenemos acotada la porcin de memoria que hemos inicializado con los valores que se han ledo de teclado: es la porcin que
va desde la direccin guardada en pos_origen hasta (sin llegar) a la direccin guardada en pos_final. Esta informacin la
usaremos en el siguiente ejercicio.
Atencin: no olvidarse de que las lecturas por teclado de valores que vayan a guardarse en variables de tipo short int deben
hacerse usando una variable auxiliar que sea entera, para acto seguido, asignar el valor de esa variable entera a la que
realmente usaremos, de tipo short int. Ver ejercicio 6 para ms detalles.
EJERCICIO 14
Esta funcin imprime por pantalla en hexadecimal, y un nmero en cada lnea, los siguientes valores:
El valor de origen
El valor de pc
Todos los valores almacenados en memoria, desde la posicin origen, hasta la posicin justo anterior a fin.
Desde main() la llamaremos as:
GrabarMemoria (mem, pc_inicial, pos_origen, pos_final);
Prueba este programa introduciendo el cdigo escrito en el recuadro titulado como memoria.txt de la pgina 5 de este
boletn. Los valores hexadecimales que all hay han de introducirse de izquierda a derecha, y de arriba abajo. Los dos primeros
valores corresponden a la direccin de origen, y direccin inicial de ejecucin. Los contenidos que van a memoria comienzan
en realidad en el tercer nmero (0000). Despus de introducir el ltimo nmero (3800), cuando pida el siguiente, pulsa Ctrl-Z
para indicar que se han terminado los datos de entrada. En ese momento se mostrar la salida impresa generada por
GrabarMemoria, que debe coincidir con el contenido del recuadro.
17
Partiendo del programa del ejercicio 14, vamos a cambiar el comportamiento de la funcin GrabarMemoria. Ahora mismo,
los datos se vuelcan en la pantalla. Modifcalo para que dichos datos se vuelquen en un fichero de nombre resultados.txt tal
y como se pide en los requisitos del simulador (ver boletn del simulador). Al probar esta nueva versin veremos que ya no se
imprimen los datos de memoria al final, como pasaba en el ejercicio 14, sino que aparecen en el fichero mencionado.
Comprueba que el fichero se ha generado correctamente abrindolo con el Bloc de Notas.
EJERCICIO 16
Haremos otra modificacin al programa, partiendo del ejercicio 14. La funcin CargarMemoria ahora no inicializa el
vector de memoria desde teclado, sino desde fichero.
Cambiaremos tambin su prototipo: originalmente esta funcin no devuelve nada. Ahora s devuelve un entero. Modifica el
prototipo para reflejar este cambio. El valor que devuelve se explica a continuacin:
Puede ocurrir que el fichero memoria.txt no pueda abrirse para lectura. En ese caso, la funcin CargarMemoria debe
devolver 0 para indicar error. Si la lectura se realiz con xito, devolver 1. Puedes tomar como gua el diagrama de flujo de la
seccin Programacin modular del tema 3 de teora, donde se ha refinado esta funcin.
Para probar esta nueva funcin junto con la modificada en el ejercicio 13, crea un fichero memoria.txt usando el Bloc de
Notas. Hay un ejemplo de este fichero en el boletn de la prctica del Simulador. Tambin puedes crear el fichero memoria.txt
usando el ensamblador suministrado en las herramientas de testeo del simulador, en Enseanza Virtual.
Si todo va bien, al ejecutarse el programa se crear un fichero resultados.txt cuyo contenido debe ser idntico a memoria.txt
Ha llegado el momento de unir las funciones GrabarMemoria, CargarMemoria, e InicializarMemoria en el programa cuya
plantilla se llama simcpu.c . A partir del siguiente ejercicio se irn incorporando otras funciones que ya tenamos escrita a este
mismo programa. Esta plantilla es la ltima que usaremos, y contiene al simulador completo.
EJERCICIO 17
Del ejercicio 11, rescata la funcin simulador. Esta funcin, recordemos, iba pidiendo instrucciones por teclado al usuario,
y mostrndolas por pantalla, hasta que el usuario tecleaba la instruccin STOP, o bien se produca una excepcin.
Vamos a hacer algunos cambios a esa funcin. Para empezar, copia la funcin a la nueva plantilla donde ya hemos copiado
el resto de funciones. Copia tambin las funciones obtclase() y obtcodigo() que son usadas por esta funcin. En la plantilla ya
estn definidos los prototipos de todas estas funciones, as que slo hace falta copiar las implementaciones.
En segundo lugar, vamos a cambiar el prototipo de la funcin. El original era:
void simulador (void);
Lo que devuelve la nueva versin de Simulador es un 0 si la simulacin se ha producido sin excepciones, y distinto de 0 si
hubo una excepcin. Ahora mismo, la nica excepcin que estamos tratando es la de instruccin incorrecta, as que a esa
excepcin le asignaremos el valor 1. En la funcin Simulador por tanto aadiremos una variable excepcion, que inicialmente
valdr 0, y ocasionalmente, cuando nos encontremos con un cdigo de instruccin que no tiene correspondencia con ninguna
instruccin definida, valdr 1. Luego veremos otros tipos de excepciones que se pueden producir en la ejecucin, que harn
que esta variable tome otros valores diferentes de 0 y 1.
A continuacin viene el cambio ms grande de todos: la nueva funcin Simulador ya no lee instrucciones desde el teclado,
sino que las leer de la memoria, es decir, el vector mem. Eso significa que dentro de la funcin Simulador, donde aparece un
cdigo similar a ste:
printf (Dame instruccin en la direccin %04.4X: , pc);
fflush (stdin);
scanf (%04.4X, &aux);
ins = aux;
Debe cambiarse por una instruccin que lea el elemento pc-simo del vector mem. Por supuesto, ya no hay mensajes de
cortesa ni lectura de teclado en esta funcin. El elemento ledo se pasa directamente a la variable ins. Ya no hace falta aqu
la variable entera aux para poder leer nmeros hexadecimales de teclado sin peligro puesto que ya no leemos de teclado en esta
funcin.
18
La variable pc, por otra parte, ya no se inicializar a 0, sino que se inicializar al valor que tenga la variable global
pc_inicial, y cuyo valor fue ledo del fichero en la funcin CargarMemoria.
Al final de todos estos cambios, lo que tenemos es una funcin que se encarga de, dentro de un bucle, ir leyendo de forma
secuencial instrucciones desde el vector mem. El ndice que se usa para acceder a mem es el valor de pc, que se incrementar
en 1 justo despus de haber ledo la instruccin.
La instruccin leda es decodificada, y su mnemotcnico se imprime en pantalla.
Esto contina as hasta que la instruccin leda es STOP, o bien se produjo una excepcin. En el primer caso, Simulador
devuelve 0. En el segundo caso, devuelve 1. El bucle while que controla la lectura de instrucciones debe terminar no solamente
si se encuentra STOP, sino tambin si la ltima instruccin gener una excepcin.
Al terminar el bucle de simulacin se mostrar un mensaje de que se ha terminado porque se encontr STOP (como
tenamos hasta ahora) o bien mostrar que se ha terminado porque ha ocurrido una excepcin. El mensaje de excepcin
contendr la direccin de memoria donde ha ocurrido dicha excepcin (OJO! Porque la variable pc estar apuntando no a la
direccin de la instruccin incorrecta, sino a la siguiente) y la instruccin en s (en formato hexadecimal ambos valores).
Ya tenemos un programa que es capaz de cargar un programa para nuestra CPU desde un fichero. El programa, una vez
cargado, es ledo por el simulador que va mostrando las instrucciones a medida que las lee y decodifica. Como las
instrucciones no se ejecutan, el registro PC siempre va incrementndose en 1 cada vez, ya que an no se estn ejecutando las
instrucciones de salto que pudieran cambiar su valor.
Por ltimo, nuestro programa es capaz de generar un fichero con el contenido de la memoria. En este momento, repetimos,
como el programa realmente no se ejecuta, el contenido de la memoria no ha cambiado, as que el fichero resultados.txt
debera tener el mismo contenido que memoria.txt, salvo que durante la lectura e identificacin de alguna de las instrucciones
se cometiera alguna excepcin. Si esto pas, vers que el fichero resultados.txt no se genera (mira en main como la funcin
GrabarMemoria slo es llamada cuando el resultado de la funcin Simulador es igual a 0, es decir, cuando no se produjo
ninguna excepcin durante la simulacin).
El resto de los ejercicios hasta el ltimo muestran cmo implementar la ejecucin de las instrucciones del simulador.
Mostraremos paso a paso cmo implementar la ejecucin de alguna de las instrucciones de movimiento (clase 0) y una de las
instrucciones aritmtico-lgicas (clase 1). El resto tendrs que hacerlas t por tu cuenta.
Todas las instrucciones que componen nuestra CPU van a operar sobre registros y/o memoria. Cuando una instruccin
necesite leer o escribir a un registro, acceder a la posicin adecuada del vector de registros. Cuando tenga que escribir o leer a
memoria, tendr que hacer lo propio con el vector mem.
Adems, las instrucciones aritmtico-lgicas, tras su ejecucin, tendrn que actualizar las banderas de la ALU segn el
resultado de la operacin.
Las instrucciones de salto operarn adems sobre el registro PC, modificndolo si es necesario.
EJERCICIO 18
Vamos a implementar el comportamiento de las instrucciones MOV, MOVH y MOVL. Estas son instrucciones de clase 0.
Las modificaciones se harn sobre el cdigo de la funcin Simulacion del ejercicio 17.
Localiza el punto en tu cdigo, dentro de la cadena de if-else if (o switch/case si has optado por implementarlo as) donde
se ha identificado correctamente la instruccin MOV. Seguramente ser la primera instruccin que identificas. El punto donde
se ha identificado es precisamente donde haces el printf (MOV Rd, Rs\n); . Pon ese printf en un comentario (por ejemplo,
poniendo dos barras // delante de l, o encerrando toda la orden entre un /* y un */). Es mejor dejarlo como comentario que
borrarlo, ya que al dejarlo de comentario recordamos que ah es donde se ejecuta esa instruccin. Si ocurre algn error y
tenemos la necesidad de saber qu instrucciones se estn ejecutando en nuestro programa, podemos descomentar la lnea con
printf y volver a ver esa informacin.
Tenemos que averiguar los valores de los operandos Rd y Rs para la instruccin en curso. Rd y Rs hacen referencia a
nmeros de registros, por lo que los valores de cada uno de estos estar entre 0 y 7. Un valor entre 0 y 7 se codifica con 3 bits.
Repasando la tabla con el juego de instrucciones encontramos:
Nemotcnico
Formato
Descripcin
Excepciones
Ejemplo
MOV Rd,Rs
00 000 Rd Rs 00000
No tiene.
MOV R3,R5
Binario: 00 000 011 101 00000b
Hexadecimal: 03A0h
Antes de esta instruccin:
R3 = 1C40h R5 = 9B23h.
Despus de esta instruccin:
R3 = 9B23h
19
Partiendo del formato de la instruccin en binario, obtenemos que Rd y Rs ocupan las posiciones de bit 8 a 10 (siendo la
posicin 0 la de ms a la derecha) para Rd, y las posiciones 5 a 7 para Rs. Usaremos la misma tcnica que para averiguar el
cdigo de clase y de instruccin: desplazamientos y mscaras.
Para averiguar el valor de Rd, habr que desplazar la instruccin leda (que suponemos que est en la variable ins) 8 lugares
a la derecha, y seguidamente aplicarle una mscara que deje slo 3 bits. Usaremos una variable entera llamada rd para guardar
este valor, que quedara as:
rd = (ins>>8)&7;
Anlogo procedimiento para obtener el valor de Rs, que guardaremos en la variable rs:
rs = (ins>>5)&7;
Ahora ya tenemos los nmeros de registro para el registro fuente y el registro destino. La instruccin MOV lo que hace es
copiar el valor almacenado en el registro nmero Rs al registro nmero Rd. Nuestros registros son elementos de un vector que
se llama R, as que la accin de MOV se implementara as:
R[rd] = R[rs];
Esta instruccin no modifica las banderas de la ALU ni genera excepciones, con lo que no hay que hacer nada ms para
implementarla. Ahora nuestro simulador es capaz de ejecutar una instruccin: MOV.
Para MOVL y MOVH (y de hecho, para el resto de instrucciones) el procedimiento es anlogo: se decodifican los
operandos de la instruccin usando desplazamientos y mscaras, y escribiremos en C las acciones que comportan la ejecucin
de la instruccin.
MOVH y MOVL se parecen a MOV en tanto que escriben un nuevo valor en el registro marcado como Rd. Se diferencian
ambas de MOV en que sta ltima modifica el registro completo, mientras que MOVL y MOVH slo modifican una mitad (la
mitad baja L, o la mitad alta H). Esto tambin lo resolveremos con mscaras y desplazamientos.
MOVH. Tiene dos operandos: un nmero de registro Rd, y un valor inmediato de 8 bits. El primero est en las posiciones
de bit 8 a 10, y el segundo est en las posiciones de bit 0 a 7.
Decodificar ambos operandos es sencillo. Ms incluso para el valor inmediato ya que no requiere desplazamiento, slo la
mscara. Supongamos que tenemos los operandos ya decodificados en las variables enteras rd e inm8. La accin de modificar
la parte alta (los bits 8 a 15) del registro de destino se hara as:
R[rd] = (R[rd]&0x00FF) | (inm8<<8);
Recuerda lo que hace el operando | en C (OR bit a bit). La secuencia de operaciones es:
Se coge el valor actual del registro cuyo nmero es Rd y se borran sus 8 bits superiores usando una mscara AND con
ceros en los bits 8 a 15 (0x00FF).
Se coge el valor de inm8 y se desplaza 8 lugares a la izquierda (<<8). Ahora, el valor de inm8 en lugar de estar
ocupando las posiciones de bit 0 a 7, est ocupando las posiciones 8 a 15. Las posiciones de bit 0 a 7 se rellenan con
ceros durante el desplazamiento.
Se combinan ambos valores con la operacin OR. Los valores de los bits 8 a 15 de inm8 desplazado se copian en las
posiciones de bit 8 a 15 del registro Rd. Las posiciones de bit 0 a 7 no se modifican al combinarse con ceros,
provenientes de inm8 desplazado.
En el caso de MOVL, para borrar sus 8 bits inferiores y dejar los 8 superiores intactos usaremos 0xFF00 en lugar de
0x00FF. Haz el resto de operaciones, para implementar esta instruccin.
EJERCICIO 19
Para el resto de instrucciones habr que hacer ms o menos lo mismo, as que para organizarte mejor en la implementacin
de cada instruccin, comienza por determinar la secuencia de mscaras y desplazamientos que necesitars para decodificar
cada operando, as como la accin a realizar para su implementacin. Para ello, puedes rellenar la tabla que te proponemos a
continuacin:
20
Instruccin
MOV Rd,Rs
Formato binario
00 000 Rd Rs 00000
Operando 1
rd = (ins>>8)&7;
Operando 2
Operando 3
rs = (ins>>5)&7;
Accin
NO TIENE
R[rd] = R[rs];
mar = R[ri];
if (mar < TMEMORIA)
R[rd] = mem[mar];
else
excepcion = 2;
MOV Rd,[Ri]
00 001 Rd Ri 00000
rd =
ri =
NO TIENE
MOV [Ri],Rs
00 010 Ri Rs 00000
ri =
rs =
NO TIENE
MOVL Rd,inm8
00 100 Rd inm8
rd =
inm8 =
NO TIENE
MOVH Rd,inm8
00 101 Rd inm8
rd =
inm8 =
NO TIENE
ADD Rd,Rs1,Rs2
rd =
rs1 =
rs2 =
SUB Rd,Rs1,Rs2
rd =
rs1 =
rs2 =
OR Rd,Rs1,Rs2
rd =
rs1 =
rs2 =
AND Rd,Rs1,Rs2
rd =
rs1 =
rs2 =
XOR Rd,Rs1,Rs2
rd =
rs1 =
rs2 =
COMP Rs1,Rs2
rs1 =
rs2 =
NOT Rds
rds =
NO TIENE
NO TIENE
INC Rds
rds =
NO TIENE
NO TIENE
DEC Rds
rds =
NO TIENE
NO TIENE
NEG Rds
rds =
NO TIENE
NO TIENE
CLR Rds
rds =
NO TIENE
NO TIENE
NO TIENE
NO TIENE
R[rd]=(R[rd]&0x00FF) |
(inm8<<8);
op1 = R[rs1];
op2 = R[rs2];
temp = op1 + op2;
R[rd] = temp;
ActualizaALU(...);
op1 = R[rs1];
op2 = (~R[rs2])+1;
temp = op1 + op2;
R[rd] = temp;
ActualizaALU(...);
NO TIENE
desplaz =
JMP desplaz
10 desplaz
(extender en signo de 14 a
16 bits. Ver ejercicio 19)
desplaz =
BR cond desplaz 11 cond desplaz
cond =
(extender en signo de 11 a
16 bits. Ver ejercicio 19)
NO TIENE
Se aconsejan los siguientes tipos de datos en la definicin de las variables que aparecen en esta tabla y el resto de los
ejercicios:
ri, rs, rs1, rs2, rd, rds, inm8, cond, excepcion, temp : de tipo entero (int).
pc, mar, ins, op1, op2, elementos de mem, elementos de R : de tipo entero corto sin signo (unsigned short).
desplaz : de tipo entero corto con signo (short)
21
Hemos aadido un ejemplo de cmo se codifica una instruccin que podra generar una excepcin: en la instruccin de
lectura de memoria, si se lee ms all del lmite de la memoria. La variable excepcion aqu ya no tiene nicamente 0 1, sino
que toma ms valores. Aconsejamos estos:
As, se puede usar un cdigo como el siguiente para mostrar la excepcin causada:
switch (excepcion)
{
case 0:
printf ("Programa terminado con normalidad. Se encontro STOP en %4.4X\n", pc-1);
break;
case 1:
printf ("EXCEPCION. Instruccion (%4.4X) desconocida en %4.4X\n", ins, pc-1);
break;
case 2:
printf ("EXCEPCION. La instruccion (%4.4X) en %4.4X intento leer la direccion %4.4X\n", ins, pc-1, mar);
break;
...
... etc
...
}
Este cdigo se aadir al final de la funcin Simulador; cuando ya ha terminado el bucle de simulacin. Se imprime un
mensaje indicando si la simulacin termin correctamente o no, y la causa. Despus de imprimir, la funcin retornar con el
valor de la variable excepcion.
Despus de rellenar la tabla, implementa sobre la funcin Simulador todas las instrucciones de la clase 0. Para ello, haz
como has hecho en el ejercicio 18, pero para el resto de las instrucciones de esa clase.
Algunas de las instrucciones de esta clase pueden generar una excepcin de tipo 1 (lectura de memoria en direccin fuera
de rango) o 2 (escritura a memoria en direccin fuera de rango). La condicin de fuera de rango se testea con el valor de mar
y la constante TMEMORIA. Si mar es igual o mayor que TMEMORIA, est fuera de rango (mirar ejemplo en la
implementacin de la instruccin MOV rd,[ri] en la tabla anterior).
Para probar tu primera versin del simulador, ya casi completo, escribe algn programa de prueba con el ensamblador
suministrado, o usa alguno de los tests preparados a tal efecto.
EJERCICIO 20
Por otra parte, las instrucciones de la clase 1 (aritmtico-lgicas) deben actualizar las banderas de la ALU despus de la
ejecucin. La operacin de actualizacin se realiza mediante una funcin que hemos llamado ActualizaALU. Este es su
prototipo:
void ActualizaALU (unsigned short op1, unsigned short op2, unsigned short res16, int res32);
Hemos aadido un ejemplo de instruccin de esa clase: la instruccin de suma. En este caso, el resultado de la operacin, es
decir, la suma, se guardar temporalmente en una variable entera de 32 bits (un int) que hemos llamado temp. Acto seguido,
ese valor se traslada al registro de destino, que al ser de 16 bits, se quedar slo con parte del resultado. La actualizacin de la
ALU ocurre en ltimo lugar, llamando a la funcin ActualizaALU.
Normalmente, el resultado almacenado en temp coincidir con el que luego tendr el registro de destino. Esto ocurrir
siempre que el resultado de la operacin no exceda de 16 bits. Pero si ha excedido, tenemos que detectarlo de alguna forma, y
sta es viendo el resultado que debera haber dado la operacin, y el resultado que se ha guardado. De ah que a la funcin
ActualizaALU se le entreguen dos parmetros: el valor de 16 bits guardado en el registro de destino, y el valor de hasta 32 bits,
resultado original de la operacin. Veremos en un momento que esto se usa para averiguar el valor del flag de acarreo (CF).
Esta funcin debe actualizar convenientemente las banderas de la ALU. Recordemos que stas son: ZF (flag de cero), SF
(flag de signo), CF (flag de acarreo) y OF (flag de overflow). Para ello, supondremos que existen, como variables globales, las
variables zf, sf, cf y of. Estas variables valdrn 0 1 durante la ejecucin de un programa. Al iniciarse la simulacin, todas
valen 0 (recuerda aadir esto al principio de la funcin Simulador).
Las operaciones aritmticas que soporta esta ALU son las siguientes: ADD, SUB, COMP, INC y DEC. Recuerda que las
restas (SUB, COMP y DEC) son en realidad sumas, en las que al segundo operando se le ha cambiado el signo.
Para hallar el valor del bit de acarreo recuerda que en una ALU que opera con N bits, el acarreo es el valor del bit N
(contamos las posiciones de los bits comenzando en 0). En nuestro caso, el valor que tenga el bit 16.
El flag de overflow (OF), al igual que el de acarreo, slo se actualiza para operaciones aritmticas. Para otras operaciones
que no sean stas, valdr 0.
22
OF valdr 1 si los signos de los dos operandos op1 y op2 son iguales, pero el signo del resultado de 16 bits no coincide con
ellos. Es decir, que vale 1 si op1 y op2 son positivos, pero res16 es negativo, o si op1 y op2 son negativos, pero res16 es
positivo. Para el resto de casos vale 0.
El flag de signo (SF) se comprueba sobre el valor de 16 bits. En un nmero de 16 bits en formato de complemento a 2, qu
bit guarda el signo y qu valores toma segn el signo del resultado? Usa desplazamientos y mscaras para quedarte con el valor
de ese bit. El valor del flag de signo es el valor que tenga ese bit.
El flag de cero (ZF) es el ms sencillo de averiguar. Se comprueba tambin sobre res16.
En total, esta funcin tendr que realizar cuatro sentencias condicionales, uno para cada flag, y en funcin del resultado,
poner la variable que representa a ese flag a 1 a 0.
Dado que hay instrucciones ALU que no son aritmticas, sino lgicas, y en stas hemos dicho que tanto CF como OF
valdrn 0, simplemente, despus de llamar a ActualizaALU (que a lo mejor ha cambiado estos flags), las pondremos
directamente a 0. Por ejemplo, la parte final de la implementacin de la instruccin XOR rd,rs1,rs2 , que es una instruccin
lgica, quedara as:
op1 = R[rs1];
op2 = R[rs2];
temp = op1^op2; /* el operador ^ es el XOR en lenguaje C */
R[rd] = temp;
ActualizaALU (op1, op2, R[rd], temp); /* aqu es posible que OF y/o CF se pongan a 1 */
of = 0;
cf = 0; /* esta instruccin no es aritmtica, as que ponemos estos dos flags a 0 */
Escribe el cuerpo de la funcin ActualizaALU. El prototipo lo tienes ya escrito. Una vez escrita, ya se pueden implementar
todas las instrucciones de la clase 1 en la funcin Simulador.
Puedes pasar a probar esta nueva versin del simulador escribiendo pequeos programas con el ensamblador, o bien usando
alguno de los programa-test suministrados.
EJERCICIO 21
El operando desplaz en las instrucciones JMP y BR es un poco especial. En el primero, dicho operando ocupa 14 bits, y en
el segundo caso, 11 bits. En C no existen variables que tengan exactamente ese tamao, as que al usar desplazamientos y
mscaras, acabamos guardando este valor en una variable de 16 (short int) 32 bits (int).
El valor, en ambos casos, debe considerarse como en complemento a 2. Es decir, puede ser positivo o negativo. Cuando es
positivo no es problema, ya que un nmero positivo que ocupa 11 14 bits, tiene el mismo valor que si se guarda en una
variable que tenga 16 bits, estando los bits no usados a 0.
Pero si es negativo, s que hay un problema. Pongamos por ejemplo que el valor de desplaz es de 14 bits, y es ste (en
complemento a 2 de 14 bits): 11111011000000 . Si pasamos este valor a decimal sale -320.
Si durante el proceso de decodificacin de este valor lo metemos en una variable de 16 bits (short int) tenemos esto:
0011111011000000 . Tomado como un nmero en complemento a 2, resulta que este valor es positivo (su bit ms significativo
vale 0). Pasado a decimal vale 16064, que no tiene nada que ver con el -320 que debera salir.
El problema est precisamente al guardar este valor en una variable que tiene espacio para ms bits que los que requiere el
valor. Para salvaguardar el signo de un nmero en complemento a 2 que se almacena en una variable de mayor tamao, se
requiere hacer lo que se denomina extensin del signo. Esto consiste en rellenar los bits no usados de la variable con el valor
del bit ms significativo del valor original. Se realiza de esta forma:
Usando desplazamientos y mscaras, averiguamos el valor del bit 10 (o 13, segn el caso que estemos tratando) de desplaz.
Este ser nuestro bit de relleno.
Si el bit es positivo (0) no hacemos nada especial, simplemente dejamos desplaz como est.
Si el bit es negativo (1) hacemos: desplaz = desplaz | 0xC000; . Esto lo que hace es poner los bits 14 y 15 de desplaz a 1. Si
desplaz no es de 14 bits sino de 11, entonces los bits que tenemos que poner a 1 son desde el bit 11 al 15. Calcula qu valor es
el que tendremos que usar en lugar de 0xC000.
Este valor desplaz es al fin, el que sumamos al valor actual de pc, que recordemos, apunta siempre a la prxima instruccin
a leer de memoria. Antes de realizar la suma, hay que comprobar que el resultado final no excede el valor de TMEMORIA, ya
que si es as, debe generarse una excepcin por destino de salto incorrecto (excepcin 4).
Con esta informacin, ya puedes implementar las instrucciones de la clase 2 y 3, con lo que el simulador est completo.
Puedes probarlo con cualquier programa de los suministrados, o bien escribiendo uno propio.
23