Ejercicio obligatorio 2
Fecha de entrega: Jueves 12 de septiembre
Introducción
En arquitectura de procesadores se dice que una computadora es de arquitectura Harvard cuando su memoria de datos es independiente de su memoria de instrucciones por lo que su unidad de control se comunica diferenciadamente con una o con la otra.
En un procesador la memoria de instrucciones es un arreglo de valores puestos en posiciones numeradas. Cada uno de estos valores, las instrucciones, le indican a la unidad de control qué operación tiene que realizar. Para hacer más legibles las cosas muchas veces en vez de utilizar el código de la instrucción lo sustituimos por su mnemónico que es una codificación (teóricamente) más legible que la representa.
Cuando se inicia un procesador el mismo ejecuta la primera instrucción de la memoria, luego la segunda, luego la tercera, etc. Para llevar la cuenta de cuál es la próxima instrucción a ejecutar se utiliza un registro especial llamado contador de instrucciones. Entonces, lo que se dijo antes es que cuando se inicia la computadora el contador de instrucciones se inicia en 0 y cada vez que se ejecuta una instrucción el mismo se incrementa. Hay instrucciones que puede modificar el valor de dicho contador.
Por otro lado se encuentra la memoria de datos del procesador, la cual permite almacenar valores y recuperarlos después. Dependiendo de la arquitectura del procesador el acceso a la memoria puede ser directamente por su posición o a través de algún registro especial que almacene dicha posición.
En una arquitectura de tipo Harvard la memoria de instrucciones es de sólo lectura (ROM) mientras que la memoria de datos es de lectura/escritura (RAM).
El procesador MOS 7502
Tenemos un procesador que tiene una memoria de instrucciones de 256 posiciones de 1 byte cada una, una memoria de datos de 256 posiciones de 1 byte cada una y dos registros especiales: el contador de instrucciones y el contador de datos.
uint8_t instrucciones[256];
uint8_t datos[256];
uint8_t contador_instrucciones;
uint8_t contador_datos;
Al inicio la memoria de datos se inicializa en 0, al igual que los dos contadores. La memoria de instrucciones se inicializa con el programa a ejecutar.
En cada ciclo de ejecución se debe decodificar la instrucción contenida en
instrucciones[contador_instrucciones]
e incrementar en uno
contador_instrucciones
.
Las instrucciones son las siguientes:
- 0 (
HAL
)Termina la ejecución del procesador.
- 1 (
INC
)Incrementa
contador_datos
en uno.- 2 (
DEC
)Decrementa
contador_datos
en uno.- 3 (
ADD
)Incrementa
datos[contador_datos]
en uno.- 4 (
SUB
)Decrementa
datos[contador_datos]
en uno.- 5 (
PUT
)Imprime el carácter en
datos[contador_datos]
enstdout
.- 6 (
GET
)Lee de
stdin
un carácter y lo guarda endatos[contador_datos]
.- 7 (
JMP
)Se debe modificar el valor de
contador_intrucciones
al valor contenido en la instrucción siguiente.- 8 (
BEQ
)Si el valor de
datos[contador_datos]
es cero se debe modificar el valor decontador_instrucciones
al valor contenido en la instrucción siguiente. Si no, debe incrementarse el valor decontador_instrucciones
.
Ejemplos
Ejemplo 1: cat
Supongamos el siguiente programa:
uint8_t instrucciones[256] = {6, 3, 8, 10, 4, 5, 6, 3, 7, 2, 0};
En primer lugar notar que los valores 10 y 2 en las posiciones 3 y 9 respectivamente no representan instrucciones sino valores del contador de instrucciones, dado que están precedidas por instrucciones 8 y 7 respectivamente.
Para hacer más explícitas las instrucciones podemos reemplazarlas por sus mnemónicos correspondientes:
uint8_t instrucciones[256] = {GET, ADD, BEQ, 10, SUB, PUT, GET, ADD, JMP, 2, HAL};
Este programa implementa el comando cat
el cual repite por
stdout
todo lo que lee por stdin
. (Se asume que EOF
vale -1, lo cual es así generalmente en C.)
Sigamos la ejecución:
A.
GET
: Se lee un valor y se guarda endatos[0]
. En este programa, dado que no aparecen las instrucciones 1 y 2, siempre vamos a trabajar sobredatos[0]
, lo vamos a llamar simplemente valor.B.
ADD
: Se incrementa en 1 dicho valor. Es decir, si valíaEOF
se va a convertir en 0.C.
BEQ
-10: Si el valor fuera igual a cero (se leyó unEOF
) se pondría el contador de instrucciones en 10, seguimos en I. Si fuera distinto de cero (no se leyóEOF
) incrementamos el contador de instrucciones por lo que la próxima instrucción a ser ejecutada no será la 3 sino la 4, seguimos en D.D.
SUB
: Decrementamos en 1 el valor. Es decir, deshacemos el cambio que habíamos hecho en B/G. Ahora tenemos almacenado lo mismo que leímos.E.
PUT
: Imprimimos el valor leído.F.
GET
: Leemos otro valor.G.
ADD
: Incrementamos en uno (lo mismo que hicimos en B).H.
JMP
-2: Ponemos el contador de instrucciones en 2, por lo que volvemos al paso C.I.
HAL
: Termina el programa.
Más ejemplos
Se proveen varios ejemplos más contenidos en el siguiente archivo descargable:
archivos_20192_ej2.tar.gz
.
Los archivos .7502
contenidos en él tienen una instrucción por línea.
Los ejemplos desarrollados son:
fifty.7502
El programa imprime:
"50"
alfabeto.7502
El programa imprime:
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
alfabeto2.7502
El programa imprime:
"A B C D E F G H I J K L M N O P Q R S T U V W X Y Z a b c d e f g h i j k l m n o p q r s t u v w x y z\n"ascii.7502
Imprime todos los valores de la tabla ASCII del 0 al 255.
hello.7502
yhello2.7502
Ambos imprimen:
"Hello World!\n"benchmark.7502
Testea nuestro programa intensivamente y después de un rato imprime:
"OK"
triangle.7502
Imprime el triángulo de Sierpinski:
. * . * * . * * . * * * * . * * . * * * * . * * * * . * * * * * * * * . * * . * * * * . * * * * . * * * * * * * * . * * * * . * * * * * * * * . * * * * * * * * . * * * * * * * * * * * * * * * * . * * . * * * * . * * * * . * * * * * * * * . * * * * . * * * * * * * * . * * * * * * * * . * * * * * * * * * * * * * * * * . * * * * . * * * * * * * * . * * * * * * * * . * * * * * * * * * * * * * * * * . * * * * * * * * . * * * * * * * * * * * * * * * * . * * * * * * * * * * * * * * * * . * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *fibonacci.7502
Imprime la sucesión de Fibonacci hasta que se llenan los 256 bytes de memoria RAM:
0 1 1 2 3 5 8 13 21 34 55 89 144 ...
Trabajo
Mnemónicos
Se debe definir un tipo enumerativo que permita codificar los mnemónicos de las instrucciones.
Leer instrucciones
Se pide programar una función
bool leer_instrucciones(uint8_t instrucciones[256]);
que lea de
stdin
una secuencia de números, que representan instrucciones, de a uno
por vez hasta que se termine la entrada
y los guarde en instrucciones
.
Son instrucciones válidas cualquier valor entre 0 y 255.
Cuando se alcance el EOF
se considera que terminó la entrada y se termina la ejecución. La
función debe retornar true
en ejecución normal, en caso de falla debe
retornar false
. Son fallas posibles leer un valor fuera de los válidos o
excederse en las 256 instrucciones de extensión.
Aplicación
Se debe programar un emulador del procesador 7502 que permita correr un
programa preestablecido a ser leído por stdin
.
Entrega
Deberá entregarse el código fuente del programa desarrollado.
La entrega se realiza por correo a la dirección algoritmos9511entregas en gmail.com (reemplazar en por arroba).
El ejercicio es de entrega individual.