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

../_images/20192_ej2_mos7502.jpg

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] en stdout.

6 (GET)

Lee de stdin un carácter y lo guarda en datos[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 de contador_instrucciones al valor contenido en la instrucción siguiente. Si no, debe incrementarse el valor de contador_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 en datos[0]. En este programa, dado que no aparecen las instrucciones 1 y 2, siempre vamos a trabajar sobre datos[0], lo vamos a llamar simplemente valor.

B. ADD: Se incrementa en 1 dicho valor. Es decir, si valía EOF se va a convertir en 0.

C. BEQ-10: Si el valor fuera igual a cero (se leyó un EOF) 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 y hello2.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.