viernes, 25 de enero de 2013

Curso Ensámblalo! Cap. 5.2 - Lenguaje Máquina del 6502. Las Instrucciones Aritméticas



5.2 Instrucciones Aritméticas



Si sólo pudieramos almacenar o recuperar números hacia, o desde la memoria, los programas que podriamos hacer con nuestro micro serian terriblemente limitados, por no decir totalmente inútiles. Todo programa, por muy simple que sea implica la manipulación/procesamiento de datos numéricos.

Cuando programamos, resulta vital contar con la posibilidad de operar aritméticamente con números. Y al chip de nuestro C64 se le dan fenómeno estas cosas.

Aunque por motivos de economia en la miniaturización del chip, ¡sólo sabe sumar y restar!. Para realizar cualquier otro tipo de operación matemática deberemos enseñarle nosotros como se hace, paso a paso, mediante un programa. Éste es uno de los inconvenientes del lenguaje máquina frente a otros de alto nivel como seria el BASIC, por ejemplo.

Lo de enseñarle a la CPU a multiplicar o dividir, ya lo veremos con detenimiento más adelante, en capitulos más avanzados. Centrémonos por ahora en lo básico... la suma y la resta.



En el interior de la CPU se encuentra un subsistema llamado ALU, cuyas siglas corresponden a Arithmetic Logic Unit, en español: Unidad Aritmético-Lógica. Éste subsistema es el encargado de realizar cualquier operación aritmética (en realidad sumas y restas) que se realice en el sistema, pero no sólo eso...

Fijaos en su nombre, no es una Unidad Aritmética sólamente, se llama unidad Aritmético-Lógica. Tiene un apartado que trata sobre todo lo relacionado con operaciones aritméticas, pero también se ocupa de todo lo que tenga que ver con el apartado lógico en los programas, tales como las decisiones, bifurcaciones condicionales, comparaciones entre valores, etc.

La parte que se ocupa de la lógica, para la ALU no deja de ser otro trabajo puramente aritmético ya que lo único que se hará es una resta entre dos números y del resultado obtenido se actualizarán las banderas correspondientes en el registro P ("Palabra de Estado del Procesador"), las cuales serán tenidas en cuenta por intrucciones que actúan según el estado de éstas. Fijaros que restar dos cantidades para saber si una de ellas es mayor, menor o igual que la otra, es exactamente lo que hacemos cuando comparamos dos cantidades. De momento, simplemente interesa que os vayáis quedando con el concepto, en el próximo apartado profundizaremos sobre esto.

En la figura anterior (Fig.1), vemos el diagrama funcional de bloques del 6502 (simplificado). La ALU, representada con esa caracteristica forma en V, tiene conexión con los registros y los buses de datos y direcciones. Es desde el bus de datos que la ALU toma un dato para colocarlo en su "brazo" izquierdo, reservándose siempre su derecho para cargarlo con el contenido del acumulador.

El Acumulador y el Puntero Contador de Programa tienen conexiones directas con la ALU, esto es así para acelerar al máximo las operaciones aritméticas. Observad que el incremento del registro PC es muy importante que se realice a la mayor velocidad posible ya que ésta es una operación que se efectúa continuamente.

Que el acumulador tenga una conexión directa, es la principal razón de que las operaciones aritméticas de suma y resta estén tan íntimamente ligadas a este registro. Ya lo veréis cuando lleguéis a la parte donde se detallan las instrucciones de suma y resta.

Sobre este registro ya se dijo en capitulos anteriores que es uno de los más usados ya que interviene en casi todas las instrucciones que realicen operaciones aritméticas ó de tipo lógico.Las instrucciones de adición y substracción, siempre sumarán o restarán el acumulador de un número contenido en memoria, y su resultado quedará en el acumulador.

Veámoslo detalladamente:

Previamente a toda operación aritmética, un número contenido en una celda de memoria, a través del bus de datos, se coloca encima de uno de los "búfferes" de la ALU (aquí un búffer se refiere a una memoria temporal), concretamente se coloca en la parte izquierda de la "V", y el otro operando (si se requiriese) es siempre tomado del contenido del acumulador y cargado en su "buffer" derecho. Con esos valores se realiza la operación dictada por la instrucción, devolviendo el resultado nuevamente a través del bus de datos bien sea en dirección al registro adecuado, o bien hacia la memoria (gracias a la conexión con el bus de direcciones) en el caso en que en la operación no interviniese para nada el acumulador (por ejemplo cuando la operación fue un simple incremento de una celda de memoria), ya que en caso contrario el resultado residirá en el acumulador, y de ahi pasará a donde pertoque.

No todas las instrucciones de este grupo usan el acumulador, las instrucciones de INCremento y DECremento suman o restan respectivamente, de uno en uno, el contenido de los registros índice, o el contenido de una posición de memoria. Curiosamente no existe una instrucción para incrementar o decrementar el acumulador directamente. Pero es que en el 65xx, el acumulador no está pensado para perder el tiempo incrementando de uno en uno (lo tipico al recorrer elementos de una lista) si no para sumar y restar cualesquiera que sean las cantidades, ¿a que ya vais entendiendo mejor el rol del acumulador?

En realidad, cuando nos pongamos a programar, todos estos detalles sobre como trabaja la CPU por dentro, no es obligatorio que los sepamos, pero siempre nos resultará más ventajoso conocerlos porque es bien seguro que nos ayudarán a crear programas más eficientes.

A continuación aprenderemos todas las instrucciones que el 65xx nos ofrece para operar aritméticamente con números, y en el próximo capitulo veremos las instrucciones que hacen trabajar la ALU en su apartado lógico.

INC

 
Incrementa memoria en uno
(INCrement memory by one)
Operación: M + 1 => M
N Z C I D V
x x - - - -
 
Modo de direccionamiento   Sintaxis Cód. Oper.  Nº. Bytes Nº. Ciclos
Página Cero inc   Oper $e6  2  5
Página Cero, x inc   Oper, x $f6  2  6
Absoluto inc   Oper $ee  3  6
Absoluto, x inc   Oper, x $fe  3  7


Incrementa el contenido de una posición de memoria en uno.

Como se ve en la tabla esta instrucción afecta a las banderas N y Z. La bandera de signo (N) se pondrá a 1 si el resultado de incrementar esa posición de memoria tiene el bit de signo encendido (su bit 7), y la bandera de Cero (Z) se pondrá a cero si el resultado del incremento fuera igual a cero, lo cual sucederá cuando la posición anterior era igual al máximo valor que cabe en un byte, o sea 255. Como se verá más adelante este detalle será de gran utilidad cuando estudiemos los bucles.

Ejemplo: Incrementar la posición de memoria que guarda el color del borde de la pantalla



De forma análoga a esta instrucción tenemos las que incrementan el registro X e Y:

INX

 
Incrementa registro x en uno
(INcrement index X by one)
Operación: X + 1 => X
N Z C I D V
x x - - - -
 
Modo de direccionamiento   Sintaxis Cód. Oper.  Nº. Bytes Nº. Ciclos
Implícito inx $e8  1  2


INY

 
Incrementa registro y en uno
(INcrement index Y by one)
Operación: Y + 1 => Y
N Z C I D V
x x - - - -
 
Modo de direccionamiento   Sintaxis Cód. Oper.  Nº. Bytes Nº. Ciclos
Implícito iny $c8  1  2


Hacen exactamente la mismo operación que hacia INC pero para los registros de indice X e Y. Lógicamente estas instrucciones sólo tienen un código de operación, el del direccionamiento implícito.

Si acaso observad la diferencia de ciclos del procesador que toma efectuar un INC con lo que toma efectuar un INX, o un INY... Recordar esto: ¡Siempre es mucho más rápido trabajar con un registro que con la memoria! ¿Por que? Simplemente porque la memoria interna del procesador, los registros, se acceden de forma muchisimo más rápida que la memoria convencional del sistema (pura economia en el diseño).

Todas estas instrucciones tienen su contrapartida, las que decrementan la memoria o los registros indices:

DEC

 
Decrementa memoria en uno
(DECrement memory by one)
Operación: M - 1 => M
N Z C I D V
x x - - - -
 
Modo de direccionamiento   Sintaxis Cód. Oper.  Nº. Bytes Nº. Ciclos
Página Cero dec   Oper $c6  2  5
Página Cero, x dec   Oper, x $d6  2  6
Absoluto dec   Oper $ce  3  6
Absoluto, x dec   Oper, x $de  3  7


DEX

 
Decrementa registro x en uno
(DEcrement index X by one)
Operación: X - 1 => X
N Z C I D V
x x - - - -
 
Modo de direccionamiento   Sintaxis Cód. Oper.  Nº. Bytes Nº. Ciclos
Implícito dex $ca  1  2


DEY

 
Decrementa registro y en uno
(DEcrement index Y by one)
Operación: Y - 1 => Y
N Z C I D V
x x - - - -
 
Modo de direccionamiento   Sintaxis Cód. Oper.  Nº. Bytes Nº. Ciclos
Implícito dey $88  1  2


Sólo tener presente, respecto a lo que comentábamos antes acerca del control de bucles, que si antes pásabamos de 255 a 0, cuando se decrementa el valor 0, ahora obtenemos un 255.

Ahora veremos las dos últimas instrucciones de éste grupo, las encargadas de sumar o restar cualquier número (con el acumulador): Add with Carry (ADC) y Substract with Carry (SBC), en español: sumar con acarreo y restar con acarreo. Ahora bien... ¿Que es esto del acarreo?

El acarreo hace referencia a "las que nos llevamos", cuando sumamos ó a "las que tomamos" cuando restamos (al restar, la cpu interpreta la bandera C invertidamente). Veamos en un ejemplo una simple suma binaria:


Fijaos que sucede conforme vamos sumando (igual que cuando sumamos números decimales) de izquierda a derecha, 1+0 = 1, 1+1=0 y nos llevamos 1, 1+0=1 + la que nos llevavamos = 0 y nos llevamos una... y asi hasta llegar al final, en la última cifra nos llevamos 1, pero no cabe dentro de las ocho cifras...

El número entre paréntesis indica el acarreo que no cabia en la 8 cifras. Sin éste, el resultado seria incorrecto, ¿¡255+2=2!?, pero teniendo en cuenta el acarreo final (1), el resultado es el correcto, 257. Como véis, el acarreo actúa como si fuera el noveno bit. En el 65xx la bandera de acarreo contiene precisamente esta misma información, es decir, se pone=1 cuando en el bit 7 nos llevábamos 1. Este mecanismo nos permitirá que podamos operar con números mayores de 8 bits y a eso se debe su existencia.

A diferencia de otros micros (como el Z80 por ejemplo), el 65xx no tiene instrucciones de suma sin acarreo (del tipo ADD, ó SUB), y éstas se harán siempre teniendo en cuenta el estado de la bandera de acarreo. Por ello, cuando programemos para un micro 65xx, al iniciar una suma o una resta, se deja a la responsabilidad del programador haber ajustado correctamente la bandera de acarreo. Hay dos instrucciones para ello: SEC (Set Carry) para ponerla a 1, y CLC (Clear Carry) para ponerla a cero. Por cierto, siempre que se habla de bits, la palabra Set, hace referencia a poner un digito a uno, y Clear a cero.

Para sumar sin tener en cuenta el acarreo emulando la instrucción ADD de otros procesadores, hariamos:



RESULT contendrá 128 tras la ejecución.

Para una resta en lugar de CLC, pondriamos C=1 con SEC, por que en la resta, el acarreo actúa como un acarreo invertido... ¿Lioso? No es para tanto, es cuestión de acostumbrarse...

Ejemplo de SBC sin tener en cuenta el acarreo, (emulando la instrucción SUB de otros micros):



RESULT contendrá 10 tras la ejecución.

Tanto en uno como en otro ejemplo, si no hubieramos puesto a punto la bandera de acarreo, los resultados podrian ser erróneos ya que dependerian del estado de ésta en el momento que se ejecutasen adc ó sbc... El resultado seria uno más o uno menos del que debiera, ¡Tened mucho cuidado con esto!

Veamos detenidamente estas dos últimas instrucciones:

ADC

 
Suma de la memoria al acumulador con acarreo
(Add Memory to Accumulator with Carry)
Operación: A + M + C => A, C
N Z C I D V
x x x - - x
 
                     * Añadir 1 si cruza límite de página.
Modo de direccionamiento   Sintaxis Cód. Oper.  Nº. Bytes Nº. Ciclos
Inmediato adc # Oper $69  2  2
Página Cero adc   Oper $65  2  3
Página Cero, x adc   Oper, x $75  2  4
Absoluto adc   Oper $6d  3  4
Absoluto, x adc   Oper, x $7d  3  4*
Absoluto, y adc   Oper, y $79  3  4*
(Indirecto, x) adc  (Oper, x) $61  2  6
(Indirecto), y adc  (Oper),y $71  2  5*


SBC

 
Substrae memoria del acumulador con toma (acarreo inverso)
(SuBstract memory from accumulator with borrow)
Operación: A - M - !C => A
N Z C I D V
x x x - - x
!C es acarreo inverso (o la "toma")
                     * Añadir 1 si cruza límite de página.
Modo de direccionamiento   Sintaxis Cód. Oper.  Nº. Bytes Nº. Ciclos
Inmediato sbc # Oper $e9  2  2
Página Cero sbc   Oper $e5  2  3
Página Cero, x sbc   Oper, x $f5  2  4
Absoluto sbc   Oper $ed  3  4
Absoluto, x sbc   Oper, x $fd  3  4*
Absoluto, y sbc   Oper, y $f9  3  4*
(Indirecto, x) sbc  (Oper, x) $e1  2  6
(Indirecto), y sbc  (Oper),y $f1  2  5*


En las tablas de referencia de estas dos instrucciones se observa que se actualizarán, de acuerdo al resultado, todas las banderas excepto la de interrupción (I) y la del modo decimal (D). Ya deberiamos saber como funciona la bandera Z, la de acarreo (C), y la de signo (N), las demás banderas las comprenderéis cuando hablemos de las instrucciones que modifican las banderas. Sólo comentaré de paso, que la bandera de Rebasamiento (V, de oVerflow en inglés), indica si el número cambió incorrectamente de signo debido a que no cabia en un byte. Esta bandera sólo nos resulta de utilidad si estamos trabajamos con números negativos (en complemento a dos).

Para terminar, veremos como se efectúan sumas de números mayores de 8 bits, concretamente estudiaremos el caso de la suma de números de 16 bits (2 bytes).

Para estos casos se deben sumar los bytes de menor peso y los de mayor peso por separado ayudándonos de la peculiaridad de que adc suma añadiendo el acarreo.

Ejemplo:



Para la resta se haria exactamente igual sólo que con un sec en lugar de un clc al restar la primera pareja de bytes (la parte baja).

Aplicando este método podemos sumar números con la precisión de bits que deseemos (siempre que quepan en la memoria, claro).

Como ejercicio, para comprobar que lo habéis entendido todo perfectamente, os propongo que hagáis un programa que realice una suma con números de 32 bits (una doble palabra).

Para comprobar que lo habéis hecho bien, podeis ensamblarlo con un monitor de código máquina, por ejemplo el que viene integrado en el emulador del Winvice tal como se explicó en capitulos anteriores.

4 comentarios:

Jose Zanni dijo...

Vuelve el curso de ensamblador!! :)

Lobogris dijo...

vuelve, vuelve! :)

Bieno Marti dijo...

Eres un p_to crak. ¿Para cuando un PDF con todo ? ;D

Lobogris dijo...

El PDF saldrá en cuantito que lo termine ;)

Publicar un comentario