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.
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(®, CARRY, false);
set_status(®, ZERO, true);
set_status(®, INTERRUPT_DISABLE, false);
set_status(®, DECIMAL, true);
set_status(®, BREAK, false);
set_status(®, OVERFLOW, true);
set_status(®, NEGATIVE, false);
assert((reg & 0xDF) == 0x4A);
assert(!get_status(®, CARRY));
assert(get_status(®, ZERO));
assert(!get_status(®, INTERRUPT_DISABLE));
assert(get_status(®, DECIMAL));
assert(!get_status(®, BREAK));
assert(get_status(®, OVERFLOW));
assert(!get_status(®, NEGATIVE));
set_zero(®, 1);
assert(!get_status(®, ZERO));
set_zero(®, 0);
assert(get_status(®, ZERO));
set_negative(®, -1);
assert(get_status(®, NEGATIVE));
set_negative(®, 127);
assert(!get_status(®, NEGATIVE));
set_negative(®, 128);
assert(get_status(®, NEGATIVE));
set_carry(®, 168 + 33);
assert(!get_status(®, CARRY));
set_carry(®, 168 + 97);
assert(get_status(®, CARRY));
set_overflow(®, 76, 41, 76 + 41);
assert(!get_status(®, OVERFLOW));
set_overflow(®, 108, 41, 108 + 41);
assert(get_status(®, OVERFLOW));
set_overflow(®, -10, -20, (-10) + (-20));
assert(!get_status(®, OVERFLOW));
// El casteo está sólo para callar el warning de overflow ;)
set_overflow(®, -100, -29, (uint8_t)((-100) + (-29)));
assert(get_status(®, OVERFLOW));
set_overflow(®, 127, -128, 127 + (-128));
assert(!get_status(®, OVERFLOW));
set_overflow(®, -128, 127, (-128) + 127);
assert(!get_status(®, OVERFLOW));
uint8_t x = 0xBF;
set_status(®, CARRY, false);
rotate_left(®, &x);
assert(get_status(®, CARRY));
assert(x == 0x7E);
rotate_left(®, &x);
assert(!get_status(®, CARRY));
assert(x == 0xFD);
rotate_right(®, &x);
assert(get_status(®, CARRY));
assert(x == 0x7E);
rotate_right(®, &x);
assert(!get_status(®, 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.