lunes, 16 de mayo de 2016

Direccionamiento (continuacion). Todo es relativo... bah, no siempre.

La entrada anterior consideró los modos de direccionamiento directo, extendido, inmediato (vagamente, en este post lo desarrollo mejor) e indexado. Llegó el momento de abordar el más sencillo (inherente) y el más difícil (relativo). Aquí viene lo bueno jóvenes...
El modo relativo es el empleado en los saltos. De hecho en el HC11 las instrucciones de la familia BRANCH (no confundir con brunch, eso es una mezcla de almuerzo y desayuno) se manejan solo por direccionamiento relativo, y este modo solo está disponible para los branch. Así que es imposible considerar uno sin el otro. Van juntos, como el cafe con leche y las medialunas.
Las instrucciones de la familia BRANCH (se podría traducir bifurcación) nos permiten alterar el orden de ejecución del programa. CPU continuamente repite el ciclo fetch-decode-execute, por lo tanto al terminar con una instrucción ejecuta la que sigue, y así ad infinitum (o hasta que la apaguen).
Necesitamos comprender bien este hecho para pasar al branch... así que repasémoslo.
Al mismo momento de leer una instrucción, el Program Counter se incrementa, conteniendo en él la dirección del operando que acompaña esa instrucción, o de la siguiente instrucción. Veamoslo en un ejemplo, supongamos esta porción de programa:

ORG  $C000
LDAA  #$03
LDAB   $04

Al ensamblarlo el resultado sería (sugiero tener el set de instrucciones a mano para seguir la explicación):


$C000 86 03
$C002 D6 04

Observe que la instrucción LDAB está en modo directo, por tanto en el acumulador B cargará el contenido de la dirección de memoria 0004. Pero la instrucción LDAA utiliza modo inmediato, por tanto lo que se cargará en el acumulador A es el número 03 (en hexadecimal... y en decimal es igual), y no el contenido de la dirección de memoria 03. Pero ¿De dónde sacó CPU la dirección del operando 03? ¿cómo determina que lo que debe copiar a B es el número 03 en lugar del contenido de la posición de memoria 03?
Al programar le indicamos esto mediante el uso del numeral # para señalar modo inmediato. Este modo implica que lo que acompaña al código de operación es "el" dato. No se trata de una dirección ni una referencia, andamos sin vueltas, ¡es el dato! ¿Cómo lo carga CPU al acumulador?

Veamos nuevamente la instrucción en hexadecimal:

$C000 86 03

¿Cómo se produce el ciclo de instrucción? En primer lugar el PC tiene la dirección $C000. Realiza el fetch y por tanto carga el código de operación 86 en el Instruction Register (IR). Al mismo momento PC se incremente y adopta el valor $C0001. La unidad de control (UC) decodifica el 86 y determina que lo que debe hacer es copiarse el contenido actual del PC al Memory Address Register (MAR). Efectúa un READ de la memoria y lo que toma del Memory Buffer Register (MBR) lo copia al acumulador B. ¿Logró identificar de dónde se toma la dirección del operando? ¡Del PC! Por eso al modo inmediato también se lo llama relativo al PC. La dirección real del operando (DRO) está en el PC.
Ahora bien, una vez que leyó el contenido del PC con el valor $C0001.. ¿qué ocurrió con este registro? Se incrementó nuevamente. De ahí que al concluir el ciclo de esta instrucción el PC queda con el valor $C0002, que es el de la instrucción siguiente. Es importante recordar que el PC se incrementó antes de producirse la búsqueda del operando en memoria. El PC no se incrementa después de haber copiado del MBR al AccA, sino después de haber sido leído.
Habiendo completado la instrucción LDAA #$03, el PC tendrá el valor $C002. Por tanto la UC leerá el valor de la memoria en $C002 y lo copiará en el IR, en este caso el código de operación D6. Ahora al decodificarlo determina que se está empleando direccionamiento directo, por tanto lo que está apuntado por el PC ($C003) es el offset de la página cero. Se copia el PC al MAR, se lee de memoria (el valor 04) y ahora se completa con el número de página para formar la DRO: $0004. Esa dirección se coloca en el MAR y se lee de memoria el operando que se cargará en el acumulador B. Al momento el PC ya está incrementado y tiene el valor $C004, correspondiente a la siguiente instrucción (que no incluimos en el ejemplo).
¿Cómo haríamos para alterar la secuencia del programa? Hemos visto que luego de cada uso del PC (típicamente para copiarlo al MAR y de ahí efectuar la lectura de memoria) el valor del mismo se incrementa. Si quisiéramos que no se ejecute la siguiente instrucción tenemos que modificar el valor del PC antes de que se inicie el siguiente ciclo fetch-decode-execute. Aquí es donde entran a jugar las BRANCH.
Las instrucciones de bifurcación operan sobre el valor del PC. A excepción del salto incondicional (BRA = branch always) y el salto "nunca" (BRN = branch never, o sea "nunca salte") evalúan una condición y si se cumple aplican un offset al valor del PC. Ese offset se representa con un número de 8 bits contemplando negativos en complemento a la base. Por lo tanto cuando la condición del salto se cumple se suma el offset al PC y de esa forma se salta hacia adelante (si el offset es positivo) o hacia atrás (si es negativo).
Cuando empleamos etiquetas en assembler y luego las utilizamos con los branch, dejamos en manos del ensamblador el cálculo del offset. Pero es importante entender cómo funcionan, por ejemplo para comprender por qué los saltos tienen límites.
Si utilizamos 8 bits para representar números enteros y admitimos negativos en complemento a la base (Cb), el rango es de -128 a 127. En otras palabras, desde una instrucción de branch podemos saltar 127 bytes hacia adelante (sumando al PC) o 128 hacia atrás (restando al PC). Peeeeero, aquí el truco: dado que el valor del PC al que se aplica el offset es el que corresponde a la siguiente instrucción -luego de haber leído el branch y el offset- entonces tenemos que descontar esos dos bytes al hacer la cuenta. De tal forma cuando queremos que salte "en el lugar" para formar un bucle infinito, lo que por ejemplo hacemos al cerrar los programas así:

FIN   BRA   FIN

En realidad tenemos que decirle que salte 2 hacia atrás. Porque el PC ya se habrá incrementado pasando por las direcciones de la instrucción BRA y del offset (que es siempre de un byte). Por eso al ensamblarla queda así:
20 FE
Siendo FE el número -2 en Cb. Veamos algunos saltos en el ejemplo del triángulo de un post anterior:
Marqué los saltos repitiendo el color cuando apuntan a la misma etiqueta. Notar que aunque tres veces salta a la etiqueta isos, los tres saltos tienen distinto offset. ¿Por qué? Porque desde esos tres puntos del programa la cantidad de bytes que hay que sumar al PC para que llegue a la dirección $C01B es distinta. En el primer caso es la instrucción:
$C009 27 10
El código de operación del salto BEQ es 27. El offset es 10. Dado que es un número hexadecimal, corresponde al decimal 16. Si sumamos los valores hexadecimales C00B + 10 el resultado es el la dirección de memoria de la etiqueta isos=C01B.
¿Por qué sumé C00B al offset (10) en vez de C009, que es la dirección de la instrucción de salto? Porque el valor del PC luego de realizar el ciclo fetch-decode para BEQ isos (27 10) será C00B, y no C009. Por tanto el offset se aplica al PC incrementado, el PC que apunta a la siguiente instrucción.
Veamos otro ejemplo más fácil de seguir: el salto BRA LISTO, copio la porción de programa desde esa línea hasta la de la etiqueta listo:

$C019 20 02       bra listo
$C01B C6 49 isos  ldab #'I
$C01D D7 03 listo stab tipoT

Cuando la UC ha leído el código de operación 20 y el offset 02 que la acompaña, el valor del PC es C01B. Aun no ejecutó el salto pero ya el PC está incrementado. En este caso el salto se ejecuta siempre (es incondicional), pero si fuera el caso CPU evaluaría la condición correspondiente. Entonces en caso de ejecutar el salto, aplica el offset (02) al valor del PC, el valor ya incrementado (C01B). Por tanto esa cuenta C01B+02 da por resultado la dirección C01D, que corresponde a la etiqueta listo.

El límite a los branch que impone el rango antes mencionado [-128;126] se expresa en bytes, o sea en posiciones de memoria. Si tomamos un promedio de dos bytes por instrucción, significa que tenemos la posibilidad de realizar saltos aproximadamente 60 instrucciones hacia adelante o hacia atrás. ¿Qué pasa si no es suficiente? Podemos recurrir a realizar otra clase de saltos, JUMP, que serán motivo de alguna otra publicación del blog. (Al leer JUMP pueden pensar en el tema de Van Halen, un nerd normalmente lo hace).















¿Qué hay del modo inherente? Contamos con muchas instrucciones que emplean operandos en registros. Tal es el caso por ejemplo de:

¿es correcto decir que no usan direccionamiento? Si y no. Estas instrucciones emplean lo que Motorola llama direccionamiento inherente, en los libros lo encontramos como "modo implícito". Si vamos a hilar finito no se trata de un modo de direccionamiento con todas las letras (podría ser un mdo dirccionamto) porque la misma definición de los modos de direccionamiento refiere a traer operandos de memoria... y si ya está en un registro no se lo trae de memoria.
Estamos en un caso en el dependiendo quién lo diga se trata de la verdad o de "la verdad". 


(El libro que tiene Hutz podría ser perfectamente el manual de Motorola).
Respecto a esto, hay que tener en cuenta -como bien aclaró el Ingeniero F. S.- que lo que alguna bibliografía (Murdocca) llama "modo registro" se asemeja más a lo que en el plano teórico planteamos como modo indexado (porque no refiere a operandos en registros sino a que los la dirección base y/o el offset se hallan en registros). 

En resumen, hemos visto:
* cómo funciona el direccionamiento relativo, cómo se calculan los saltos y qué limitaciones tiene
* a qué llama Motorola modo inherente (nosotros modo implícito)

Trivia: ¿para qué les parece que existe la instrucción BRN (BRANCH NEVER)?
 

8 comentarios:

  1. Me quede pensando en la trivia un rato y ya tenia una idea, pero por las dudas le pregunte a SanGoogle y la pegue bastante.

    ---SPOILERS---

    BRN se puede usar para reservar un lugar para implementar un branch en el futuro o para reemplazar un branch y hacer debugging.

    Saludos!

    ResponderBorrar
  2. Bien ahí! A veces donde había un branch ya ni cenizas quedan.

    abrazo

    Jair

    ResponderBorrar
  3. Cuando explica "¿Cómo se produce el ciclo de instrucción?" ¿no era la instrucción en modo inmediato "LDAA #03"? en la explicación dice "LDAB #$03" y lo mismo para la explicación del modo directo, es que cambio los acumuladores en la explicación A por B y B por A o es que yo me perdí y me perdí mal...
    by SC

    ResponderBorrar
    Respuestas
    1. Efectivamente! Lo corregí (espero). También había algunos ceros de más por ahí. Gracias por la observación.

      Borrar
  4. Me sirvió un montón, gracias profe!

    ResponderBorrar
  5. Esto es nerd en extremo. Cómo te quiero.

    ResponderBorrar
  6. Respuestas
    1. Me alegro mucho Gabriel!

      Todavia sirve! todavía sirve!

      Jair

      Borrar

Aunque no es obligatorio, es de buena educación firmar los comentarios.