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

jueves, 3 de agosto de 2017

¿Qué tipo de triángulo es?

Existen conceptos que captamos mejor cuando los ejemplificamos. Hay un ejercicio que considero es lo suficientemente sencillo para que un principiante lo razone pero no tan simple como para que sienta su intelecto mancillado: determinar el tipo de triángulo. Partimos de la suposición de que contamos con la información de la longitud de los tres lados del mismo y queremos determinar qué tipo de triángulo es: isósceles, escaleno o equilátero. Dejaremos de lado la comprobación de que realmente se trata de un triángulo y utilizaremos datos enteros. Después de todo es un ejercicio para principiantes.

Supongamos que en tres direcciones de memoria contamos con las longitudes de los lados y que se trata de números enteros positivos de 8 bits. Las longitudes negativas en complemento a la base las dejamos para alguna tarde de lluvia y alcohol. Ubiquemos las longitudes desde la dirección de memoria 0000 y reservemos también un byte para indicar el tipo de triángulo. 

      ORG 0000    // el cero es cero en hexa y en decimal
lado1 RMB 1
lado2 RMB 1
lado3 RMB 1
tipoT RMB 1

El tipo de triángulo lo indicaremos con un ASCII. Si es equilátero guardaremos una E en tipoT, si es isósceles una I y si es escaleno una K. 

¡IMPORTANTE! Como en tantos órdenes de la vida hay más de una manera válida de resolver el problema. Podríamos hacerlo bien, mal o a la "Max Power". Sería ideal que antes de seguir leyendo intente hacerlo por sí mismo. No importa si le toma un buen rato, haga su mejor intento. Luego puede ver la solución que aparece a continuación, que puede ser distinta a la suya sin que por eso alguna sea incorrecta. Si no logra completar la solución -recordando que hay muchos escenarios posibles- trate de lidiar con algunas de las combinaciones.

El HC11 de alguna manera nos fuerza a utilizar los acumuladores, ya que las operaciones que podemos hacer directamente contra memoria son muy pocas. Por tanto para evaluar los lados vamos a necesitar que siquiera uno de ellos esté en un registro de la CPU. Dado que estamos limitados a realizar comparaciones de dos valores (no podemos hacer lado1==lado2==lado3 o lado1==lado2 AND lado1==lado3) tendremos que realizar varias comparaciones. Tenemos dos caminos para las comparaciones: 
  • podemos cargar los dos registros acumuladores A y B y comparar entre ellos con CBA; 
  • o podemos comparar un registro contra memoria con CMPA / CMPB.
Avancemos gradualmente a la solución. Empecemos por comparar dos lados y saltar si no son iguales, quedaría así:

        ORG     $C000
        LDAA    lado1
        CMPA    lado2
        BNE     salto1  // salta si no son iguales


La instrucción que siga será la que se ejecute si el salto NO se ejecuta. Si la condición se cumple y el salto se ejecuta, el flujo de programa continuará donde esté la etiqueta salto1. Esta forma de razonar los saltos trae algunas dificultades al programador de alto nivel porque básicamente es al revés de lo que solemos pensar. Aquí el "else" es lo que sigue al "if" y el lado verdadero (true o "then") es lo que irá donde ubiquemos la etiqueta. Es por ello que el salto suele ser el opuesto al que razonaríamos en alto nivel.
Luego de cada comparación tenemos un salto. Esto ocurre en el 95% de las situaciones en que usamos comparaciones, básicamente porque después de preguntar algo, tenemos que hacer algo con la respuesta.

¿Qué hacemos si los lados 1 y 2 son iguales? Sabemos que no es escaleno. Vamos a compararlo con el tercero para determinar si es isósceles o equilátero.

        ORG     $C000
        LDAA    lado1
        CMPA    lado2
        BNE     salto1

        LDAB    lado3
        CBA
        BNE     esIso

Hemos comparado el lado 1 (en el acumulador A) con el lado 3 (en el acumulador B). Si son iguales se ejecutará la instrucción que siga... ya sabemos que es equilátero.
Aunque podríamos cargar directamente el valor numérico del ASCII de la "E", vamos a dejar que el ensamblador trabaje por nosotros. Con una comilla simple como prefijo le estamos indicando que lo que sigue debe interpretarse como ASCII:
LDAB #'E
Además dado que queremos guardar el ASCII de la "E" en la posición "tipoT", primero necesitamos cargar el ascii en un acumulador y luego guardarlo con STAA. ¿Por qué no lo guardamos directamente en memoria? 
Respuesta corta: porque con el HC11 no se puede. 
Respuesta larga: el HC11 no admite instrucciones con dos referencias a memoria. Necesitamos que uno de los operandos esté en registro. Así que cargamos el ASCII de la E en el acumulador B (podríamos tranquilamente usar A, pero bueno, para que no se ponga celoso....) y luego lo guardamos en memoria.
Pasemos esto a código:

        ORG     $C000
        LDAA    lado1
        CMPA    lado2
        BNE     salto1

        LDAB    lado3
        CBA
        BNE     esIso
        LDAA    #'E
        STAA    tipoT

Si son distintos se ejecutará el salto "esIso" porque podemos afirmar que es isósceles. Tenemos entonces dos saltos para desarrollar: salto1 y esIso. El segundo es el más fácil, así que lo podemos agregar:


        ORG     $C000

        LDAA    lado1
        CMPA    lado2
        BNE     salto1

        LDAB    lado3
        CBA
        BNE     esIso
        LDAA    #'E
        STAA    tipoT    <- punto 1


esIso   LDAA    #'I
        STAA    tipoT    <- punto 2

¿Qué ocurre si dejamos eso como está? Además de que falta resolver un salto, obviamente.
Recordemos que la ejecución se da en forma secuencial. Luego de que la CPU ejecute la instrucción "STAA tipoT" del punto 1, continuará con la instrucción "LDAA #'I" y el siguiente "STAA tipoT" del punto 2. ¿Por qué? Respuesta corta: porque la CPU continuamente repite el ciclo de instrucción. Respuesta larga: porque si queremos que se altere el flujo de ejecución, debemos hacerlo con instrucciones que impacten en el contador de programa. Una etiqueta no tiene ese efecto. ¡Es solo una etiqueta! Si queremos que luego del punto 1 el programa continúe su ejecución en otra instrucción, debemos agregar un salto. Dado que deseamos que ese salto se ejecute siempre, será un salto incondicional. Veamos cómo queda:


        ORG     $C000

        LDAA    lado1
        CMPA    lado2
        BNE     salto1

        LDAB    lado3
        CBA
        BNE     esIso
        LDAA    #'E
        STAA    tipoT    <- punto 1

        BRA     fin     

esIso   LDAA    #'I
        STAA    tipoT    <- punto 2

Note que luego de ejecutar la instucción del punto 1 el flujo del programa se ve alterado por el branch always.
Aun tenemos que resolver el "salto1". Dicho salto se ejecuta cuando el lado 1 (en A) y el lado 2 son distintos. En ese punto solo podemos decir que el triángulo no es equilátero. Vamos a cargar el lado 3 en el acumulador B y comparar entonces lado 1 con lado 3 y lado 2 con lado 3. Si hallamos que hay alguna igualdad, entonces es isósceles (observar que la carga de lado3 en el acumulador B se produce justo después del salto).

        ORG     $C000

        LDAA    lado1
        CMPA    lado2
        BNE     salto1

        LDAB    lado3
        CBA
        BNE     esIso
        LDAA    #'E
        STAA    tipoT

        BRA     fin     

esIso   LDAA    #'I
        STAA    tipoT
        BRA     fin   <- agrego un salto por lo expuesto antes
salto1  LDAB    lado3
        CMPB    lado2
        BEQ     esIso // salta si lado 3 y lado 2 son iguales
        CBA
        BEQ     esIso // salta si lado 1 y lado 3 son iguales
        LDAA    #'K   // si no salto hasta aca...escaleno
        STAA    tipoT
fin     BRA     fin

Con esto agregamos las comparaciones que faltaban. El BRA del final lo usamos para darle un cierre elegante al programa, pero no cumple ninguna función más que gastar ciclos. Se puede hacer más prolijo: notar que se repite el STAA tipoT en más de un lugar. Eso es justo lo último que hace el programa, así que cambiaremos los saltos a fin por saltos a una nueva etiqueta para que guarde con una misma instrucción:

        ORG     $C000

        LDAA    lado1
        CMPA    lado2
        BNE     salto1  // salta si lado 1 y lado 2 son distintos

        LDAB    lado3   // si son iguales compara con lado 3
        CBA
        BNE     esIso   // salta si lado 2 y lado 3 son distintos
        LDAA    #'E     // si no saltó es porque es equilátero
        BRA     guarda     

esIso   LDAA    #'I
        BRA     guarda
salto1  LDAB    lado3
        CMPB    lado2
        BEQ     esIso // salta si lado 3 y lado 2 son iguales
        CBA
        BEQ     esIso // salta si lado 1 y lado 3 son iguales
        LDAA    #'K   // si no salto hasta aca...escaleno
guarda  STAA    tipoT
fin     BRA     fin


La programación en bajo nivel no tiene por qué ser "estructurada", pero si nos esforzamos un poco podemos dejar un solo punto de finalización para que se vea más claro. 

¿Se puede optimizar más? Seguramente. Estamos ejecutando dos veces la instrucción "LDAB lado3". Podríamos tratar de ubicarla de forma que aparezca solo una vez. ¿Dónde le parece que quedaría bien? Tal vez justo antes del salto "BNE salto1"... ¿qué habría que verificar? Recordemos que la instrucción CMPA actualiza los flags de la palabra de estao. Luego BNE utiliza el flag Z (zero) para determinar si hay que saltar o no. Por lo tanto debiéramos constatar que LDAB no modifique el flag Z para ubicarla entre CMPA lado2 y BNE salto1. Si aun le quedan dudas, revise el set de instrucciones:

Dado que LDAB actualiza el flag Z no podemos ubicarla antes del BNE. Pero sí podríamos acomodarla antes del CMPA. Así nos queda la versión definitiva del programa:

        ORG     0000   
lado1   RMB     1
lado2   RMB     1
lado3   RMB     1
tipoT   RMB     1


ORG     $C000

        LDAA    lado1        LDAB    lado3   
        CMPA    lado2
        BNE     salto1  // salta si lado 1 y lado 2 son distintos


        CBA             // si son iguales compara con lado 3
        BNE     esIso   // salta si lado 2 y lado 3 son distintos
        LDAA    #'E     // si no saltó es porque es equilátero
        BRA     guarda     

esIso   LDAA    #'I
        BRA     guarda
salto1  CMPB    lado2
        BEQ     esIso // salta si lado 3 y lado 2 son iguales
        CBA
        BEQ     esIso // salta si lado 1 y lado 3 son iguales
        LDAA    #'K   // si no salto hasta aca...escaleno
guarda  STAA    tipoT
fin     BRA     fin



Llegó el momento de probar el programa en el simulador, pero es tema de otro post. ¿Se puede optimizar más? Tal vez, pero ya queda a criterio del lector. Acepto ideas en los comentarios!


Sugerencias: partiendo del fuente provisto realice los cambios siguientes:
  • manejar valores de lado de triángulo de 16 bits
  • acceder a los lados del triángulo como elementos de un vector

miércoles, 25 de mayo de 2016

Desensamblemos (continuación): revolcándose en bits

Veamos cómo termina esta historia del "bajo mundo" binario. Habíamos llegado a desensamblar hasta aquí:

$C000 7F 00 03         CLR $0003
$C003 7f 00 04         CLR $0004
$C006 de 00            LDX $00
$C008 4F               CLRA
$C009 E6 00            LDAB 0,x
$C00B

y teníamos aun esta porción por procesar:

$C00B: d3 03 dd 03 08 7a 00 02 26 f3 20 fe

El procedimiento se repite: buscamos D3 en el set de instrucciones, en la columna de códigos de operación. 


Corresponde a la instrucción ADDD en modo directo, por tanto el byte que sigue es la parte baja de la dirección (dd). Completemos el programa:

$C000 7F 00 03         CLR $0003
$C003 7f 00 04         CLR $0004
$C006 de 00            LDX $00
$C008 4F               CLRA
$C009 E6 00            LDAB 0,x
$C00B D3 03            ADDD $03
$C00D

ya nos queda menos:

$C00D: dd 03 08 7a 00 02 26 f3 20 fe

Un par de comentarios: no debemos olvidar que las direcciones se expresan en hexadecimal, mucha atencn a eso. Tampoco debemos confundir los modos de direccionamiento. El modo directo permite expresar los operandos con la parte baja de su dirección real, asumiendo que la parte alta es 00. Por tanto en esta instrucción el operando se encuentra en $0003, pero expresamos solo $03, porque justamente lo que el modo directo permite es mantener implícito el $00 de la parte alta (que sería la página cero).
Continuemos, ahora tenemos el código de operación DD:
Sería la instrucción STD en modo directo. Así que el 03 que sigue es el operando. Eso significa que 08 es el siguiente código de operación, de la instrucción INX (modo inherente). Pasemos en limpio:
$C000 7F 00 03         CLR  $0003
$C003 7f 00 04         CLR  $0004
$C006 de 00            LDX  $00
$C008 4F               CLRA
$C009 E6 00            LDAB 0,x
$C00B D3 03            ADDD $03
$C00D DD 03            STD  $03
$C00F 08               INX
$C010 
ya nos queda menos:

$C010: 7a 00 02 26 f3 20 fe

El código de operación -CodOp- 7A lo encontramos en DEC para modo extendido. Así que los dos bytes siguientes son la dirección del operando (00 y 02). Por tanto 26 es un CodOp, el de BNE. Dado que los saltos operan solo en modo relativo, F3 es el offset del salto. Eso nos deja como siguiente CodOp 20, que corresponde a BRA. Por lo tanto FE es el offset de este último salto relativo. Completemos el programa:

$C000 7F 00 03         CLR  $0003
$C003 7f 00 04         CLR  $0004
$C006 de 00            LDX  $00
$C008 4F               CLRA
$C009 E6 00            LDAB 0,x
$C00B D3 03            ADDD $03
$C00D DD 03            STD  $03
$C00F 08               INX
$C010 7A 00 02         DEC  $0002
$C013 26 F3            BNE  $F3
$C015 20 FE            BRA  $FE
¡Desensamblamos el programa! Ahora nos faltan tres cosas:
  1. Asignar etiquetas de acuerdo a los saltos que pudieran existir
  2. Asignar nombres a las direcciones de memoria de los operandos.
  3. Discernir qué hace el programa 
Hay dos saltos: en $C013 aplica el offset $F3 y en $C015 el offset $FE. Hay al menos dos maneras de determinar a dónde salta. Primero necesitamos interpretar ambos números, vamos primero con F3. Expresémoslo en binario:

 F    3 
1111 0011

De un vistazo nos damos cuenta que es negativo, ya que el bit más significativo (el primero de la izquierda) es un uno. Para determinar su magnitud aplicamos entonces lo que sabemos sobre negativos en complemento a la base. Sumemos los pesos que tienen un uno en su posición, recordando como vimos en Cómo expresar negativos en Cb que el peso del más significativo es negativo. Sería:

       F            3 
    1  1  1  1    0 0 1 1   
-128 64 32 16        2 1

Aunque la cuenta parece horrible, no lo es (tanto). Comience sumando los dos primeros:
-128 + 64 = -64
siga con el tercero:
-64 + 32 = -32
el cuarto:
-32 + 16 = -16
y finalmente le sumamos 2+1
-16 + 3 = -13 
¿No fue tan difícil, no?

El valor que obtuvimos en decimal (-13) es lo que debiéramos aplicar al PC luego de leer la instrucción de salto (C015). Si su primera respuesta fue C002, le cuento que está mal porque C015 es una magnitud hexadecimal. Podemos hacer la cuenta en hexadecimal (-13 sería -D):

C015 - D =  C008. De modo que el salto apunta a la instrucción que hay en C008. Verificamos que en C008 hay un código de operación (porque si hubiera un operando algo hicimos mal):


   $C000 7F 00 03         CLR  $0003
   $C003 7f 00 04         CLR  $0004
   $C006 de 00            LDX  $00
-> $C008 4F               CLRA
   $C009 E6 00            LDAB 0,x
   $C00B D3 03            ADDD $03
   $C00D DD 03            STD  $03
   $C00F 08               INX
   $C010 7A 00 02         DEC  $0002
   $C013 26 F3            BNE  $F3
   $C015 20 FE            BRA  $FE

De modo que podríamos poner una etiqueta (por ejemplo "bucle" para no ser originales) en la dirección C008 y reemplazar BNE $F3 por BNE bucle:
 
   $C000 7F 00 03         CLR  $0003
   $C003 7f 00 04         CLR  $0004
   $C006 de 00            LDX  $00
   $C008 4F       bucle   CLRA
   $C009 E6 00            LDAB 0,x
   $C00B D3 03            ADDD $03
   $C00D DD 03            STD  $03
   $C00F 08               INX
   $C010 7A 00 02         DEC  $0002
   $C013 26 F3            BNE  bucle
   $C015 20 FE            BRA  $FE

Si hacer la resta en hexadecimal le resulta algo desafiante, puede contar los bytes, aprovechando que sobre el margen izquierdo está ensamblado. Cuente desde el C015 hacia atrás. En la imagen marqué los bytes de cada línea desde C015 y anoté la cantidad de bytes de cada una. 
Los 13 bytes hacia atrás (el salto F3 = -13) los debemos contar desde el valor que tendrá el PC después de la instrucción de salto. (Este es tal vez el punto más importante a comprender y recordar). Retrocediendo 13 bytes llegamos a C008, que es la posición en la que pusimos la etiqueta.

El segundo salto del programa es la última línea: BRA $FE. ¿Qué valor decimal está representado en Cb como FE? Pasemos FE a binario:
   F    E 
1111 1110

Sumando los valores de los pesos (nuevamente recordando que el MSB es -128), sería:
-128 + 64 +32 + 16 + 8 + 4 + 2 = -2.
Partiendo del valor C017 y restando llegamos a C015. Esto significa que el último salto apunta a sí mismo, es el clásico FIN BRA FIN del programa. Llegamos a esto:


   $C000 7F 00 03         CLR  $0003
   $C003 7f 00 04         CLR  $0004
   $C006 de 00            LDX  $00
   $C008 4F       bucle   CLRA
   $C009 E6 00            LDAB 0,x
   $C00B D3 03            ADDD $03
   $C00D DD 03            STD  $03
   $C00F 08               INX
   $C010 7A 00 02         DEC  $0002
   $C013 26 F3            BNE  bucle
   $C015 20 FE    fin     BRA  fin
En la próxima entrega intentaremos asignar nombres a las posiciones de memoria que usa el programa, intentaremos determinar qué hace, y veremos cómo cargarlo en el simulador.

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.