Mostrando las entradas con la etiqueta ensamblado. Mostrar todas las entradas
Mostrando las entradas con la etiqueta ensamblado. Mostrar todas las entradas

domingo, 22 de mayo de 2016

Ensamblado manual (continuación): ¡ya sáquenme de la matrix!

Retomemos el programa que estábamos ensamblando. Lo copio de nuevo:


VEC     EQU    $0040

        ORG    0000
MENOR   RMB    1      Aqui quedara el menor
DIRINI  FDB    VEC    Direccion inicial del vector
CANT    FCB    07     Cantidad de elementos del vector

* El vector lo estoy ubicando en otro lugar de memoria R/W
        ORG    VEC
VECTOR  FCB    $14,$33,$FF,$E0,$09,$11,$10

* Notar que la dir inicial del programa es la que cargo en el vector de reset al final, para que inicialice el PC.


        ORG    $C000
main    LDX    DIRINI   * Cargo IX con la direccion inicial
        LDAA   0,X      * Cargo el primer elemento del vector en A
        DEC    CANT     * Decremento la cant directamente en memoria
SIGO    INX       
        LDAB   0,X      * Cargo el segundo elemento del vector
        CBA             * y los comparo
        BLS    Amenor   * Si el primero ya era menor, no lo cambio
        TBA             * Copio B en A
Amenor  DEC    CANT     * Decremento la cant directamente en memoria
        BNE    SIGO     * Si aun quedan elementos, sigue
        STAA   MENOR    * Sino guarda el menor en donde se pidió
FIN     BRA    FIN

        ORG    $FFFE
RESET   FDB    main


Esta es la tabla de símbolos como la dejamos:

identificador valor (hexadecimal)
VEC           0040
MENOR         0000
DIRINI        0001
CANT          0003 
VECTOR        0040 
main          C000
Amenor
SIGO
FIN
RESET         FFFE

y hasta aquí habíamos llegado ensamblando:

                         ORG    $C000
$C000  DE 01     main    LDX    DIRINI

$C002  A6 00             LDAA   0,X     
$C004                    DEC    CANT

Para continuar tenemos que ensamblar la instrucción DEC. Observemos los modos de direccionamiento que soporta:
En este caso DEC no admite modo directo, sino solo indexado y extendido. No estamos usando indexado porque la referencia CANT es una dirección de memoria, así que el código de operación es 7A. Observe que el operando se debe expresar en 16 bits, por eso en el set de instrucciones aparece como "hh ll", o sea parte alta (High) y parte baja (Low) de la dirección. En la tabla de símbolos vemos que CANT es 0003, por tanto al ensamblarlo queda así:

                         ORG    $C000
$C000  DE 01     main    LDX    DIRINI

$C002  A6 00             LDAA   0,X     
$C004  7A 00 03          DEC    CANT 
$C007            SIGO    INX       

Dado que la instrucción DEC CANT ensamblada ocupa tres bytes, la dirección de la siguiente instrucción es $C007 ($C0004 + 3). Seguramente ya habrá comprendido la dinámica del cálculo de la dirección de la siguiente instrucción. También habrá concluido que no es posible "predecir" la dirección de una instrucción cualquiera sin antes ensamblar todas las anteriores. Hay que tener especial cuidado de no asumir que podemos usar modo directo con todas las instrucciones, ya que existen algunas que no lo admiten (DEC, INC, CLR por ejemplo).

La siguiente instrucción tiene una etiqueta (SIGO) por tanto podemos completar esa entrada de la tabla de símbolos: SIGO equivale a $C007. El código de operación de INX es fácil de determinar porque opera solo en modo inherente. Las dos instrucciones que siguen (LDAB en modo indexado y CBA, que solo admite inherente) no representan mayor dificultad, así que también las ensamblamos sin mayor inconveniente. Llegamos a este punto, donde BLS también admite un solo modo de direccionamiento: relativo.

                         ORG    $C000
$C000  DE 01     main    LDX    DIRINI

$C002  A6 00             LDAA   0,X     
$C004  7A 00 03          DEC    CANT 
$C007            SIGO    INX       
$C00E6 00             LDAB   0,X     
$C00A  11                CBA             

$C00B  23 ??             BLS    Amenor  
                         TBA            
                 Amenor  DEC    CANT     

                         BNE    SIGO    
                         STAA   MENOR    

                 FIN     BRA    FIN
La cuestión es: ¿cómo completamos lo marcado con signos de interrogación? El modo relativo de BLS (o de cualquier branch) utiliza un operando de 8 bits, por eso en el set de instrucciones vemos que dice rr:

Por tanto aunque aun no calculamos el operando, sabemos que es de 8 bits. En un post anterior sobre Direccionamiento relativo hemos visto cómo operan las instrucciones de salto (tal vez merezca un repaso). Como tenemos que calcular el salto a la etiqueta Amenor pero aun no la completamos en la tabla de símbolos, continuemos el ensamblado dejando el lugar para el operando que nos falta. Luego volveremos a ella. Dejaremos también pendientes los otros saltos relativos.


                         ORG    $C000
$C000  DE 01     main    LDX    DIRINI

$C002  A6 00             LDAA   0,X     
$C004  7A 00 03          DEC    CANT 
$C0008        SIGO    INX       
$C00E6 00             LDAB   0,X     
$C00A  11                CBA             

$C00B  23 ??             BLS    Amenor  
$C00D  17                TBA            
$C00E  7A 00 03  Amenor  DEC    CANT     

$C011  26 ??             BNE    SIGO    
$C013  97 00             STAA   MENOR    

$C015  20 ??     FIN     BRA    FIN
$C017
Aunque todavía nos faltan determinar tres operandos de los branch del ejercicio, ya podemos completar la tabla de símbolos:

identificador valor (hexadecimal)
VEC           0040
MENOR         0000
DIRINI        0001
CANT          0003 
VECTOR        0040 
main          C000
Amenor        C00E
SIGO          C007
FIN           C015
RESET         FFFE

¿Cómo calculamos el direccionamiento relativo de los saltos? Tal como vimos en el post sobre ese tema, lo que acompaña la instrucción de salto es el offset que se aplica al PC (claro está, cuando se cumple la condición del branch). Este offset puede ser positivo, lo que provocaría un salto hacia adelante, o negativo, que se traduce en un salto hacia atrás.
En el ejemplo tenemos los dos tipos de saltos. La línea BLS Amenor es un salto hacia adelante. Cuando CPU haya leído la instrucción completa (pero aun antes de ejecutarla) el PC tendrá el valor de la siguiente instruccion $C00D. La etiqueta Amenor corresponde a la dirección C00E -tal como apuntamos en la tabla de símbolos-. Haciendo la resta, C00E - C00D (donde queremos estar menos donde estamos) obtenemos el offset, en este caso 01.
Consideremos otro de los saltos: BNE SIGO. Este salto -de ejecutarse- es hacia atrás. Luego de leer la instrucción completa "BNE SIGO", el valor del PC es C013. La etiqueta SIGO segun la tabla de símbolos corresponde a la dirección C007. El offset lo obtenemos entonces restando donde queremos estar de donde estamos: C007-C013. Esta cuenta en hexadecimal nos da un valor negativo (podemos restar al revés y recordar aplicarle el signo negativo): -12 (en decimal). Nos queda expresar -12 en negativo como complemento a la base, lo cual equivale a 11110100 en binario, o F4 en hexadecimal.
Finalmente hay un salto en la última instrucción: FIN BRA FIN. En este caso el PC queda con el valor C017 y queremos que salte a C015, por tanto el offset es -2 (en decimal). Expresado en hexadecimal sería FE (negativo en complemento a la base).

Completemos el programa:


                         ORG    $C000
$C000  DE 01     main    LDX    DIRINI

$C002  A6 00             LDAA   0,X     
$C004  7A 00 03          DEC    CANT 
$C0008        SIGO    INX       
$C00E6 00             LDAB   0,X     
$C00A  11                CBA             

$C00B  23 01             BLS    Amenor  
$C00D  17                TBA            
$C00E  7A 00 03  Amenor  DEC    CANT     

$C011  26 F4             BNE    SIGO    
$C013  97 00             STAA   MENOR    

$C015  20 FE     FIN     BRA    FIN 
$C017
Note que la última dirección la anotamos para guiarnos en el cálculo del último offset, pero realmente no es parte de la respuesta a la consigna.
¿Qué les pareció el procedimiento? ¿Alguna duda?
En otro post les cuento el método más práctico -a mi criterio- para obtener rápidamente la representación en complemento a la base de un número.

sábado, 21 de mayo de 2016

Ensamblado manual: dentro de la matrix (pero no perdido!)


Programar en assembler es lo más parecido a hacerlo en binario... del assembler al lenguaje que realmente habla CPU hay muy poca distancia. Cuando usamos un entorno de desarrollo (como el del simulador THRSim) puede que no nos demos cuenta de qué tan cerca del hardware estamos. Aprendamos a realizar el proceso de ensamblado y llevar nuestros programas del elegante assembler al rústico binario ¿Estás  listo para entrar en la matrix?
Para esto usaremos como ejemplo la clásica búsqueda del menor valor dentro de un vector de números enteros sin signo de 8 bits (easy cake).:

VEC     EQU    $0040

        ORG    0000
MENOR   RMB    1      Aqui quedara el menor
DIRINI  FDB    VEC    Direccion inicial del vector
CANT    FCB    07     Cantidad de elementos del vector

* El vector lo estoy ubicando en otro lugar de memoria R/W
        ORG    VEC
VECTOR  FCB    $14,$33,$FF,$E0,$09,$11,$10

* Notar que la dir inicial del programa es la que cargo en el vector de reset al final, para que inicialice el PC.


        ORG    $C000
main    LDX    DIRINI   * Cargo IX con la direccion inicial
        LDAA   0,X      * Cargo el primer elemento del vector en A
        DEC    CANT     * Decremento la cant directamente en memoria
SIGO    INX       
        LDAB   0,X      * Cargo el segundo elemento del vector
        CBA             * y los comparo
        BLS    Amenor   * Si el primero ya era menor, no lo cambio
        TBA             * Copio B en A
Amenor  DEC    CANT     * Decremento la cant directamente en memoria
        BNE    SIGO     * Si aun quedan elementos, sigue
        STAA   MENOR    * Sino guarda el menor en donde se pidió
FIN     BRA    FIN

        ORG    $FFFE
RESET   FDB    main


La resolución del ejercicio introduce elementos que hasta ahora no hemos considerado en el blog, como ser el uso de EQU, el vector de reset y alguna mas. Las dudas urgentes sobre esto pueden resolverse consultando en los comentarios. Más adelante habrá artículos que abarquen esos temas.

Ensamblar el programa consiste en transformarlo en el código binario que interpretará CPU. Para ahorrarnos la incomodidad de escribir en binario emplearemos el sistema de numeración hexadecimal (seguro está pensando: sí claro, porque es MUCHO MÁS FÁCIL entender hexadecimal que binario). Sugiero que si desea aprender este tema transcriba el programa en una hoja dejando espacio en el margen izquierdo para poder escribir los códigos hexadecimales. No vale ensamblarl con el THRSim y copiarse!

Como primer paso al ensamblar el programa construiremos la "tabla de símbolos". Esto es lo que hace el ensamblador también. Los símbolos son las palabras que componen el programa y que no son directivas al ensamblador ni instrucciones. Usamos símbolos para:
  • nombrar posiciones de memoria (al reservarlas con RMB o definirlas con DB por ejemplo), 
  • identificar la parte alta -o primera dirección, correspondiente con la porción más significativa- de un valor multibyte en memoria (por ejemplo al crear un word con DW),
  • determinar la primera dirección de un vector -sea de bytes, words, etc- con FCB, FDB,
  • marcar un punto determinado del programa, al que puede que utilicemos en saltos,
  • definir constantes (con EQU).
Todo símbolo deberá existir una vez como etiqueta de una línea. Luego lo emplearemos como operando en una o más ocasiones. No necesariamente tiene que darse en ese orden.

Ejemplo: en el programa anterior definimos
VEC     EQU    $0040

y luego lo usamos:
        ORG    VEC

La tabla de símbolos tiene dos columnas: identificador y contenido. La directiva EQU crea una entrada en la tabla, con la etiqueta como identificador (VEC) y el valor como contenido ($0040). Otras directivas tendrán como contenido la direccion asociada a ellas. Volviendo al ejemplo:

        ORG    0000
MENOR   RMB    1      

DIRINI  FDB    VEC   

Agregamos dos entradas a la tabla con los identificadores MENOR y DIRINI. El valor para MENOR es la dirección que le corresponde a la reserva. La directiva de origen que antecede a esa etiqueta nos marca la ubicación en memoria que se le asigna: 0000. (Podemos escribir $0000 o 0000, sin importar en qué sistema de numeración trabajemos, el cero es el cero). La directiva DIRINI recibe la dirección contigua de MENOR. ¿Por qué? Porque MENOR se usó con RMB -reserve memory bytes- y solo se reservó un byte para ella. Por ende el siguiente byte -la siguiente dirección de memoria- 0001 le corresponde a DIRINI. Note que la dirección de cada símbolo se determina en función de la dirección del símbolo anterior y de la cantidad de bytes que este requiere. 
La siguiente línea es:

CANT    FCB    07 

como DIRINI define un double-byte (16 bits = 2 bytes) y se le asignó la dirección 0001, razonamos que la dirección de CANT será 0001 + 2=0003.

Nuestra tabla de símbolos va quedando así:

identificador valor (hexadecimal)
VEC           0040
MENOR         0000
DIRINI        0001
CANT          0003

VECTOR es un símbolo fácil para agregar, ya que tiene su directiva de origen justo antes, dice:

        ORG    VEC
VECTOR  FCB    $14,$33,$FF,$E0,$09,$11,$10


¡Aquí ya estamos usando uno de los símbolos para determinar otro! Sabemos que VEC = $ 0040, por lo tanto agregamos VECTOR:

identificador valor (hexadecimal)
VEC           0040
MENOR         0000
DIRINI        0001
CANT          0003 
VECTOR        0040

¿Por qué tienen el mismo valor? En el ejemplo VEC se usa para ubicar VECTOR en un lugar de la memoria sin necesidad de escribir la dirección junto a su directiva de origen ORG. En otras palabras, es decisión de diseño, no hay una regla para ello. Entiéndase que la tabla de símbolos no es una tabla de variables, y esto se vuelve más evidente cuando le agregamos las etiquetas: main, Amenor, SIGO, FIN y RESET. Los valores de RESET y main son sencillos de determinar porque nuevamente están junto a directivas ORG:

identificador valor (hexadecimal)
VEC           0040
MENOR         0000
DIRINI        0001
CANT          0003 
VECTOR        0040 
main          C000
Amenor
SIGO
FIN
RESET         FFFE

En rigor tampoco podemos decir que todos los valores de la tabla de símbolos sean direcciones, ya que con la directiva EQU podemos generar entradas con valores de cualquier tipo.
Las otras etiquetas las completaremos durante el ensamblado. Vayamos al segundo paso: ensamblemos.

Al realizar este proceso para grabar el programa en la memoria read-only (ROM) de un microcontrolador HC11 necesitamos determinar cuál es el contenido de cada dirección de memoria que comprende el programa. Aunque el HC11 direcciona 64K de memoria, cierto es que suele implementar bastante menos que eso. De cualquier manera solo nos interesa el contenido de la memoria en la porción que requiere el programa. Por ello sobre el margen izquierdo indicaremos la dirección de memoria y a la derecha el contenido de esa dirección. Para facilitar el trabajo cuando la instrucción (sea el código de operación o el/los operandos) tenga una longitud de más de un byte, los representaremos uno a la derecha del otro. Pero claro está, en la siguiente línea indicaremos la dirección de memoria tomando en consideración la longitud de la instrucción anterior. Por ejemplo, si la instrucción que fuésemos a ensamblar ocupa un byte, como ser el caso de INX, la dirección de la siguiente instrucción será la siguiente dirección. Pero si la instrucción que estamos ensamblando es INY -que ocupa dos bytes- entonces la dirección de la siguiente instrucción la obtendríamos sumando 2 a la dirección de INY.


El HC11 maneja instrucciones de longitud variable, es una de sus características, otros procesadores tienen todas sus instrucciones del mismo largo (o sea misma cantidad de bytes).

Necesitamos un punto de partida, un origen... ¿le suena? La directiva del ensamblador ORG es la respuesta. Ensamblaremos la parte ejecutable del programa, que empieza así:

        ORG    $C000
main    LDX    DIRINI


Entonces anotamos la dirección $C000 a la izquierda, así:

                         ORG    $C000
$C000            main    LDX    DIRINI


Necesitamos determinar cuál es código de operación de la instrucción LDX que aplica en este caso. ¿Cómo lo podemos averiguar? Veamos cuáles son los modos de direccionamiento que soporta LDX, sabiendo que cada modo tiene su propio código de operación:
En rojo vemos los modos de direccionamiento, en azul los códigos de operación de cada uno y en verde se presenta el formato de los operandos para cada modo. ¿Cuál corresponde a la instrucción que estamos ensamblando?
Podemos determinarlo por descarte: no se trata de modo inmediato (IMM) porque no tiene el numeral junto al operando. Tampoco es modo indexado (IND,X o IND,Y) porque no hay una referencia a ningún índice en el operando. Eso nos deja los modos extendido y directo. Hemos visto en un post anterior que siempre que se puede se usa el modo directo. ¿Cuándo se puede usar? Cuando la instrucción lo soporta y el operando se encuentra en la página cero. En este caso comprobamos que LDX soporta modo DIRecto, y en la tabla de símbolos acabamos de anotar que DIRINI equivale a $0001, por tanto tenemos un ganador! El código de operación será DE. Note que el operando tiene el formado dd. Cada par de letras representa un byte. Por tanto el operando será de un byte. Pero DIRINI es de 16 bits ($0001), ¿cómo nos queda en un byte? Justamente por el modo directo, el cual lleva implícito que el operando está en la página cero. Por tanto el operando será 01. Quedaría entonces:


                         ORG    $C000
$C000  DE 01     main    LDX    DIRINI


Dado que la instrucción ocupó dos bytes, la siguiente comenzará en $C000 + 2:

                         ORG    $C000
$C000  DE 01     main    LDX    DIRINI

$C002                    LDAA   0,X    

Para esta instrucción el modo de direccionamiento es evidente: indexado en X. Por tanto el código de operación es A6. El operando (que en el set de instrucciones aparece como ff) corresponde al offset del indexado. En este caso es cero, por tanto ensamblando queda:

                         ORG    $C000
$C000  DE 01     main    LDX    DIRINI

$C002  A6 00             LDAA   0,X     
$C004

¿Se anima a continuar el ensamblado? En el siguiente post lo completamos. Mientras tanto les dejo una pregunta y un desafío: ¿qué cambio habría que hacerle al programa para que realice la búsqueda en un vector de números signados (negativos en complemento a la base)?
Adapte el programa para que funcione correctamente aun si el vector tiene un solo elemento (estrictamente hablando ya no sería un vector).

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)?