Ejercicio obligatorio 3

Fecha de entrega: Jueves 26 de septiembre

Introducción

El procesador MOS 6502 fue un procesador de 8 bits lanzado en 1975 que gracias a su bajo coste (USD 25) y su alta eficiencia tuvo una fuerte influencia en la revolución de las computadoras personales de finales del '70 y principios del '80. El MOS 6502 fue usado en la Apple II, la Atari 2600, la Commodore vic 20, la NES o la Bending Unit entre otras computadoras, consolas o robots famosos.

../_images/20192_ej3_bender.jpg

Cuando se dice que un procesador es de 8 bits se quiere indicar que su unidad aritmético-lógica es capaz de operar nativamente con operandos de 8 bits cada uno.

El operador MOS 6502 implementaba, entre otras, las operaciones de suma, resta, incremento, decremento y varias operaciones de bits, entre otras.

Como es de esperar el resultado de operar entre números de tamaño fijo puede dar origen a distintos casos de borde. Por ejemplo, la suma de dos números de 8 bits puede necesitar 9 bits para ser representada. Cuando el resultado de una suma activa el noveno bit se dice que ocurrió un carry (acarreo).

Un procesador 6502 contaba con un registro especial donde en un único byte almacenaba los diferentes estados de ejecución y de borde de la última operación ejecutada. Estos estados representaban si había ocurrido un carry, si el resultado era cero o si era negativo, o si había ocurrido un overflow (desbordamiento) y también almacenaban estados o modos del procesador como el modo de operaciones en decimal, si se encontraban deshabilitadas las interrumpciones o se estaba atendiando una interrupción.

Números en 8 bits

Advertencia

No es requisito entender cómo se realizan las operaciones de los ejemplos que se muestran en esta sección. Lo importante es entender qué representan los bits de interés. Las operaciones las realizamos con los operadores de C + o -. Lo que nos interesa es conocer cuánto valen algunos bits relevantes antes y después de realizar dichas operaciones.

Como ya sabemos en una cantidad determinada de bits se pueden almacenar números según su representación en binario. Dependiendo de si se almacenan números con signo o sin signo el bit más pesado del número representa el signo o una cifra más respectivamente. En el caso de números de 8 bits el octavo bit será el que almacenará el signo en el caso de resultados signados.

Números sin signo

En los números sin signo los 8 bits del número se utilizan para representar el número y el octavo bit no tiene ningún significado particular. Cuando se opera con números sin signo de 8 bits nos interesa mirar si el resultado de la operación necesitaría de un noveno bit, este bit es el bit de carry.

Por ejemplo en la siguiente suma:

C 7654 3210   bits
- 1010 1000   0xA8    168
- 0010 0001   0x21     33
+++++++++++   ++++    +++
- 1100 1001   0xC9    201

el resultado entra perfectamente en 8 bits por lo que no se activa el bit de carry. En cambio en la siguiente suma:

C 7654 3210   bits
- 1010 1000    0xA8   168
- 0110 0001    0x61    97
+++++++++++   +++++   +++
1 0000 1001   0x109   265

el resultado de la operación no entra dentro de 8 bits por lo que se activa el bit de carry.

Numeros con signo

En los números con signo el octavo bit del número se utiliza para representar el signo del número. No es de nuestro interés explicar cómo exactamente se representan los números negativos, los cuales se almacenan en una convención llamada complemento a la base (o complemento a 2 siendo que la base es 2), así que sólo nos centraremos en dicho bit. Lo único que podemos mencionar es que en complemento a la base, sin importar cómo es esta representación las operaciones se realizan de tal manera que pueden sumarse bit a bit, sin prestarle atención a su signo, utilizando el algoritmo clásico de la suma.

Cuando se opera en dos dígitos signados se dice que ocurrió un overflow si el resultado de la operación modifica el signo de forma anómala.

Por ejemplo, en la siguiente suma de números positivos:

S654 3210   bits
0100 1100   0x4C     +76
0010 1001   0x29     +41
+++++++++   ++++    ++++
0111 0101   0x75    +117

se operó con dos números positivos y el resultado fue un tercer número positivo. Entonces no hubo overflow.

En cambio la siguiente operación:

S654 3210   bits
0110 1100   0x6C    +108
0010 1001   0x29     +41
+++++++++   ++++    ++++
1001 0101   0x95     -21

operó sobre dos números positivos y el resultado fue un tercer número negativo. Entonces hubo un overflow.

Al sumar un número positivo con un número negativo nunca hay overflow. Puede haber overflow sólo al sumar dos números positivos o sumar dos números negativos.

Rotación

Una rotación o desplazamiento circular es similar a los corrimientos del lenguaje C de los operadores << y >> pero el bit que se cae al desplazar entra por el extremo opuesto. Es decir, si se tuviera el siguiente registro:

| C || 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | bit
+---++---+---+---+---+---+---+---+---+
| 1 || 0 | 0 | 1 | 1 | 0 | 0 | 1 | 0 |
+---++---+---+---+---+---+---+---+---+

y se aplicara una rotación a izquierda el resultado sería:

| C || 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | bit
+---++---+---+---+---+---+---+---+---+
| 0 || 0 | 1 | 1 | 0 | 0 | 1 | 0 | 1 |
+---++---+---+---+---+---+---+---+---+

El registro de status del MOS 6502

El registro de status del procesador MOS 6502 tiene los siguientes flags:

| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | bit
+---+---+---+---+---+---+---+---+
| N | V | - | B | D | I | Z | C | flag
+---+---+---+---+---+---+---+---+

Los cuales son:

N, Negative:

Indica si el resultado es un número negativo.

V, Overflow:

Indica si hubo un desborde en la última operación.

B, Break:

Indica si un pedido de interrupción fue disparado por una instrucción BRK.

D, Decimal:

Indica si el procesador está operando en modo decimal.

I, Interrupt disable:

Indica si se deshabilitaron las interrupciones.

Z, Zero:

El resultado es el número cero.

C, Carry:

La última operación generó un acarreo.

El bit 5 no se utiliza.

Trabajo

Status

Se debe definir un tipo enumerativo flag_t que permita codificar los flags de status del MOS 6502.

Modificación del registro de status

Se pide programar una función bool get_status(uint8_t *reg, flag_t flag); que dado un puntero al registro de status reg y un flag devuelva si dicho flag está activado o no.

Se pide programar una función void set_status(uint8_t *reg, flag_t flag, bool status); que dado un puntero al registro de status reg, un flag y un valor de status almacene ese status en el registro.

Funciones de test

Se pide programar una función void set_zero(uint8_t *reg, uint8_t res); que dado un puntero al registro de status reg y el resultado de una operación, setee el status Zero de acuerdo a dicho resultado.

Se pide programar una función void set_negative(uint8_t *reg, uint8_t res); que dado un puntero al registro de status reg y el resultado de una operación, setee el status Negative de acuerdo a dicho resultado.

Se pide programar una función void set_carry(uint8_t *reg, uint16_t res); que dado un puntero al registro de status reg y el resultado de una adición de dos unsigned de 8 bits, setee el status Carry de acuerdo a dicho resultado.

Se pide programar una función void set_overflow(uint8_t *reg, uint8_t a, uint8_t b, uint8_t res): que dado un puntero al registro de status reg, los operandos a y b que participaron de la operación y el resultado de la misma res, setee el status Overflow de acuerdo a dicho resultado.

Funciones de desplazamiento

Se pide programar una función void rotate_left(uint8_t *reg, uint8_t *x) que reciba un puntero al registro de status reg y un puntero a una variable x y realice una rotación a izquierda de x. El bit que entra en la posición 0 de x debe ser el Carry de reg mientras que el bit que se cae de la posición 7 de x debe quedar como nuevo Carry de reg.

Se pide programar una función void rotate_right(uint8_t *reg, uint8_t *x) que reciba un puntero al registro de status reg y un puntero a una variable x y realice una rotación a derecha de x. El bit que entra en la posición 7 de x debe ser el Carry de reg mientras que el bit que se cae de la posición 0 de x debe quedar como nuevo Carry de reg.

Programa principal

Se provee el siguiente main() el cual corre pruebas automatizadas sobre las funciones implementadas. El mismo debe ser utilizado para verificar la salida de las funciones desarrolladas y entregado como parte del trabajo.

#include <assert.h>

int main(void) {
    uint8_t reg = rand();


    set_status(&reg, CARRY, false);
    set_status(&reg, ZERO, true);
    set_status(&reg, INTERRUPT_DISABLE, false);
    set_status(&reg, DECIMAL, true);
    set_status(&reg, BREAK, false);
    set_status(&reg, OVERFLOW, true);
    set_status(&reg, NEGATIVE, false);

    assert((reg & 0xDF) == 0x4A);

    assert(!get_status(&reg, CARRY));
    assert(get_status(&reg, ZERO));
    assert(!get_status(&reg, INTERRUPT_DISABLE));
    assert(get_status(&reg, DECIMAL));
    assert(!get_status(&reg, BREAK));
    assert(get_status(&reg, OVERFLOW));
    assert(!get_status(&reg, NEGATIVE));


    set_zero(&reg, 1);
    assert(!get_status(&reg, ZERO));
    set_zero(&reg, 0);
    assert(get_status(&reg, ZERO));


    set_negative(&reg, -1);
    assert(get_status(&reg, NEGATIVE));
    set_negative(&reg, 127);
    assert(!get_status(&reg, NEGATIVE));
    set_negative(&reg, 128);
    assert(get_status(&reg, NEGATIVE));


    set_carry(&reg, 168 + 33);
    assert(!get_status(&reg, CARRY));
    set_carry(&reg, 168 + 97);
    assert(get_status(&reg, CARRY));


    set_overflow(&reg, 76, 41, 76 + 41);
    assert(!get_status(&reg, OVERFLOW));
    set_overflow(&reg, 108, 41, 108 + 41);
    assert(get_status(&reg, OVERFLOW));
    set_overflow(&reg, -10, -20, (-10) + (-20));
    assert(!get_status(&reg, OVERFLOW));
                // El casteo está sólo para callar el warning de overflow ;)
    set_overflow(&reg, -100, -29, (uint8_t)((-100) + (-29)));
    assert(get_status(&reg, OVERFLOW));
    set_overflow(&reg, 127, -128, 127 + (-128));
    assert(!get_status(&reg, OVERFLOW));
    set_overflow(&reg, -128, 127, (-128) + 127);
    assert(!get_status(&reg, OVERFLOW));


    uint8_t x = 0xBF;
    set_status(&reg, CARRY, false);

    rotate_left(&reg, &x);
    assert(get_status(&reg, CARRY));
    assert(x == 0x7E);
    rotate_left(&reg, &x);
    assert(!get_status(&reg, CARRY));
    assert(x == 0xFD);

    rotate_right(&reg, &x);
    assert(get_status(&reg, CARRY));
    assert(x == 0x7E);
    rotate_right(&reg, &x);
    assert(!get_status(&reg, CARRY));
    assert(x == 0xBF);


    printf("Todo OK\n");

    return 0;
}

En caso de haber errores las instrucciones assert() abortarán el programa indicando qué verificación falló. De no haber errores el programa imprimirá Todo OK.

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.