PRÁCTICAS DE COMPUTADORES II

CUARTA SESIÓN


  1. Saltos

    Cuando vimos en la primera sesión las instrucciones de carga LDx, con x uno de los siguientes registros: A, B, D, X, Y, U o S, quizá reparasteis en que no existen instrucciones de carga para tres registros DP, CC y PC. Del primero nos ocuparemos más adelante. En cuanto al segundo, la carga directa tiene menos sentido pues se trata de flags. Pero, ¿y el contador del programa? ¿No existe una instrucción para cargarlo con un valor determinado?

    La respuesta es sí y no a la vez. La instrucción existe, pero no se llama LDPC, sino JMP (del inglés JuMP, salto). Y es que el cargar el PC con un valor trae como consecuencia inmediata que la ejecución pasa (salta) a la dirección que hemos cargado. Sirva este ejemplo de bucle infinito para ver de lo que hablamos:

            0x100 CC 00 00            ldd #0
            0x103 C3 00 01  bucleinf: addd #1
            0x106 7E 01 03            jmp bucleinf
    

    El fragmento de código carga primero el registro D con 0 y, a continuación, se pone frenéticamente a incrementarlo en una unidad ad infinitum. Cuando D llega a 0xFFFF, pasa a 0x0000, se activa el bit de acarreo y vuelta a empezar.

    Observad que, aunque lo que se carga en el PC es el valor de la etiqueta bucleinf y no el valor contenido en la dirección de memoria apuntada por ella, no se pone el símbolo # delante de ella. Comparad con las instrucciones:

            ...
            0x200 8E 01 03            ldx #bucleinf ; carga X con 0x103
            0x203 9E 01 03            ldx bucleinf  ; carga X con lo que hay en 0x103;
                                                    ;   mirando arriba el código
                                                    ;   ensamblado, X se carga con 0xC300
            ...
    

    No es, sin embargo, esta la instrucción de salto que más se usa. De su familia es la instrucción BRA (del inglés BRAnch, desvío o ramal). El bucle infinito anterior, desde el punto de vista del ensamblador es equivalente al que se muestra a continuación, pero prestad atención al código máquina:

            0x100 CC 00 00            ldd #0
            0x103 C3 00 01  bucleinf: addd #1
            0x106 20 FB               bra bucleinf
            0x108 ...
    

    El código de instrucción de BRA es 0x20, pero ¿De dónde viene el misterioso 0xFB que aparece a continuación? La respuesta es que se trata de un salto relativo. Dicha instrucción, en código máquina suma el siguiente byte, considerado con signo, al contenido del PC.

    0xFB en complemento a dos es -(1+~0xFB) = -(1+0x04) = -5. Luego sumamos al PC -5 para encontrar el destino del salto: 0x108-5 = 0x103. Recordad que, cuando se ejecute la instrucción BRA, el PC ya está apuntando a la siguiente instrucción. Ni que decir tiene que el cálculo de estos desplazamientos los hace el ensamblador por nosotros.

    El utilizar BRA en lugar de JMP tiene tres ventajas y un inconveniente:

    1. primera ventaja: el código ocupa menos
    2. segunda ventaja: el código se ejecuta más rápido
    3. tercera ventaja: el código máquina una vez ensamblado no depende de qué dirección de memoria se use para cargarlo. El primero código (0xCC, 0x00, 0x00, 0xC3, 0x00, 0x01, 0x7E, 0x01, 0x03) solamente funciona si se carga exactamente en la dirección 0x100. El segundo (0xCC, 0x00, 0x00, 0xC3, 0x00, 0x01, 0x20, 0xFB), funciona en cualquier dirección que se cargue
    4. inconveniente: al solamente tener un byte para indicar el deplazamiento, el salto se limita a posiciones situadas entre -128 y +127 del valor actual del PC

    Para solucionar el inconveniente último, existe una versión que permite desplazamientos de 16 bits: LBRA (Long BRAnch), aunque es más lenta que JMP y ocupa lo mismo.

  2. Saltos condicionales simples

    Son saltos, como los anteriores, pero condicionados a que un determinado flag tenga un valor concreto. Existen, pues, ocho saltos condicionales simples, pues hay cuatro flags principales y pueden valer 1 ó 0 cada uno de ellos. Todos los saltos condicionales son relativos (de la familia de BRA). Estos son:

    N Z V C
    Flag activo  BMIBEQBVSBCS
    Flag inactivo  BPLBNEBVCBCC

    El significado de las dos últimas columnas es directo: Branch if C Set, Branch if C Clear, ... En el caso del flag Z quedará claro más abajo por qué BEQ significa Branch if EQual y BNE significa Branch if Not Equal. El bit del signo N se activa cuando el resultado de una operación que le afecta es negativo (Branch if MInus) y se desactiva cuando dicho resultado es positivo o cero (Brach if PLus).

    Cuando se usan estas instrucciones hay que tener muy presentes cuáles de las instrucciones precedentes han afectado a los flags. Es por ello que en los resúmenes de al final de cada sesión se pone los flags afectados por las instrucciones vistas.

    Usemos nuestro nuevo conocimiento para simplificar un poco aquel galimatías de la sesión anterior en el que teníamos que sumar siete al registro B si este era mayor o igual que 10. Recordemos el código original:

              std temp
              clra
              addb #246
              adca #0 ; en A hay un 1 si la primera cifra es mayor o igual que 10
              ldb #'A-'9-1
              mul
              addb temp+1
    

    Con lo que hemos visto, lo podemos simplificar bastante:

              addb #246
              bcc no_sumes
              addb #'A-'9-1
    no_sumes: subb #246
    

    El nuevo código es más rápido, ocupa menos y no necesita hacer uso de una variable temporal ni mancha el registro A. Pues aún lo tenemos que mejorar un poco más en lo que queda de sesión.

  3. Comprobación del valor de un registro o el contenido de una dirección de memoria

    Las órdenes TSTA (TeST A) y TSTB establecen el valor de los flags N y Z en función de lo que valgan los registros A y B respectivamente. También existe una orden TST para comprobar el contenido de una dirección de memoria.
  4. Ejercicio

    Haced un pequeño programa que saque por pantalla los números del 9 al 0. Realizadlo con las instrucciones de salto, no imprimiendo explícitamente los diez números.
  5. Curiosidad

    El 6809 tiene una instrucción de salto, BRN (BRanch Never) que, como su nombre indica, no salta nunca, es decir, no hace nada... Por si esta instrucción no fuera suficiente dispendio, hay otra instrucción que tampoco hace nada: NOP (No OPeration). Bueno, en realidad, sí que hacen algo: gastar tiempo. Tres ciclos de reloj la primera y dos ciclos la segunda.
  6. Saltos condicionales complejos

    Cuanto más próxima es la programación al significado (semántica), mejor. Si, en el ejemplo de arriba, queremos sumar 7 a B si B es mayor que 10, de alguna manera esto se debería reflejar directamente en el código y no mediante un artificio con los flags.

    El ensamblador logra acercarse al significado de los saltos condicionales mediante un sencillo truco. Supongamos que queremos comparar dos números almacenados en las posiciones de memoria valor1 y valor2. Lo que tenemos que hacer es hallar la diferencia entre A y B. El ensamblador tiene una serie de instrucciones que, fijándose en los flags que ha dejado dicha resta, puede efectuar uno u otro tipo de salto condicional.

    El primer ejemplo ya lo hemos visto. Supongamos que queremos saltar a la etiqueta destino si A es igual a B. Al hacer A-B, el resultado nos da cero, por lo que el flag Z se activa. Si miramos a la tabla de saltos de arriba, debemos codificar lo anterior así:

            lda valor1
            suba valor2
            beq destino  ; BEQ corresponde con saltar cuando Z=1
    

    Esta es la razón por la que la instrucción que salta cuando Z vale 1 no se llama BZS (Branch if Z Set), sino BEQ (Branch if EQual): porque están preparadas para ejecutarse después de haber hecho una resta.

    Como todos ya sabemos, un número puede ser mayor o menor que otro dependiendo de si los interpretamos como números con o sin signo. Así, en aritmética de 8 bits, 0xFF corresponde con 255, si se considera sin signo, o a -1, si se considera con signo. Si tomamos que A contiene 0x07 y B contiene 0xFF, A<B si consideramos los números sin signo o A>B, si los consideramos con signo. Por esta razón, existen para todas las posibilidades de salto dos versiones, con signo y sin signo y son las que se muestran a continuación:

    Condición Sin signo Con signo
    Mayor que BHI (higher) BGT (greater than)
    Mayor o igual que   BHS (higher or same  BGE (greater or equal
    Igual que  BEQ (equal)
    Diferente de  BNE (not equal)
    Menor o igual que BLS (lower or same) BLE (less or equal)
    Menor que BLO (lower) BLT (less than)

    El ejemplo anterior en que sumamos 7 al registro B si vale 10 o más, tiene mucho más significado si se programa así:

              subb #10                 ; vamos a comparar con 10
              blo no_sumes             ; saltar si es menor
              addb #'A-'9-1            ; sumarle 7
    no_sumes: addb #10                 ; sumar 10 para compensar los 10 que le quitamos
    

    El acabado profesional de este fragmento de código nos lo da el usar la instrucción CMP. Esta instrucción realiza la resta al igual que hace SUB, activa los flags correspondientes como hace SUB, pero a diferencia de SUB, descarta el resultado y no modifica el registro, por lo que el código anterior, si se usa CMP, quedaría:

              cmpb #10                 ; vamos a comparar con 10
              blo no_sumes             ; saltar si es menor
              addb #'A-'9-1            ; sumarle 7
    no_sumes: ...                      ; continuar con lo que siga
    

    Después de la visita al cirujano plástico, nuestro programa que imprime un número en hexa por la pantalla nos ha quedado bastante rejuvenecido:

    
    	.area PROG (ABS)
    
            ; definimos una constante
    fin     .equ 0xFF01
    pantalla .equ 0xFF00
    
    	.org 0x100
    	.globl programa
    
    programa:
    	lda #28 ; pongamos este nUmero como prueba
    
            ; imprimamos 0x
            ldb #'0
            stb pantalla
            ldb #'x
            stb pantalla
    
            ; primero imprimamos la primera cifra hexadecimal
            tfr a,b
            lsrb
            lsrb
            lsrb
            lsrb ; en B estA la primera cifra, de 0 a 15
            cmpb #10
            blo no1
            addb #'A-'9-1
    no1:    addb #'0
            stb pantalla
    
            ; ahora imprimimos la segunda cifra hexadecimal
            tfr a,b
            lslb
            lslb
            lslb
            lslb
            lsrb
            lsrb
            lsrb
            lsrb ; en B estA la segunda cifra, de 0 a 15
            cmpb #10
            blo no2
            addb #'A-'9-1
    no2:    addb #'0
            stb pantalla
    
            ; imprimamos un salto de lInea al final
            ldb #'\n
            stb pantalla
    
            ; el programa acaba
            clra
    	sta fin
    
    	.org 0xFFFE	; vector de RESET
    	.word programa
    
    
  7. Otro ejemplo

    Vamos a imprimir el contenido del registro A por la pantalla, considerado sin signo, en decimal. Pongamos primero el código, luego lo comentamos:
    	.area PROG (ABS)
    
            ; definimos una constante
    fin     .equ 0xFF01
    pantalla .equ 0xFF00
    
    	.org 0x100
    
    	.globl programa
    programa:
            lda #137 ; un nUmero como cualquier otro, para probar
    
            ; primera cifra
            ldb #'0
            cmpa #100
            blo Menor100
            suba #100
            incb
            cmpa #100
            blo Menor200
            incb
            suba #100
    Menor100:
    Menor200:
            stb pantalla
    
            ; segunda cifra.  En A quedan las dos Ultimas cifras
            clrb
            cmpa #80
            blo Menor80
            incb
            suba #80
    Menor80:lslb
            cmpa #40
            blo Menor40
            incb
            suba #40
    Menor40:lslb
            cmpa #20
            blo Menor20
            incb
            suba #20
    Menor20:lslb
            cmpa #10
            blo Menor10
            incb
            suba #10
    Menor10:addb #'0
            stb pantalla
            adda #'0
            sta pantalla
    
            ; imprimimos un salto de lInea
            ldb #'\n
            stb pantalla
    
            ; el programa acaba
            clra
    	sta fin
    
    	.org 0xFFFE	; vector de RESET
    	.word programa
    
    Expliquémoslo. El algoritmo consiste en ir desgajando al número, reduciéndolo, mientras se van imprimiendo sus cifras:
    1. Primera cifra: en B vamos a almacenar la primera cifra. Lo limpiamos. Si el número es mayor o igual que 200, le quitamos 100 e incrementamos B. Si no, no hacemos nada. El número que queda está claro que es menor que 200 ahora. Si el número que queda es mayor o igual que 100, le quitamos 100 e incrementamos B. El número que queda ahora es menor que 100 y las centenas del número original están en B. Dicho de otro modo, en B está la primera cifra y en A las dos últimas
    2. Segunda cifra: podríamos seguir un esquema como el de la primera, bajando de cada vez una decena y usando como comparación los números 90, 80, 70, etc., pero eso sería demasiado largo. Pensemos en un atajo.

      Está claro que si queremos que el registro B contenga la segunda cifra, esa cifra será un número del 0 al 9 y ocupará por tanto, 4 bits: del 00002 al 10012. ¿Qué números tendrán el bit más significativo de B activo? Los 80s y los 90s. Luego el número con el que debo comparar primero es 80. Al igual que arriba, si el número es mayor o igual que 80, activamos el cuarto bit de B (10002) y le quitamos 80 al número. Si no, no hacemos nada. El número que queda es ahora menor que 80. ¿Qué números menores que 80 tienen el tercer bit de la segunda cifra activado? Los 40s, los 50s, los 60s y los 70s, es decir, los mayores o iguales que 40. El siguiente número para comparar es 40, y así se va prosiguiendo comparando con 20 y con 10.

      Como ya os habréis dado cuenta, los bits de B no se activan directamente, sino que se van haciendo desplazamientos a la izquierda e incrementos con el mismo resultado final.
    3. Tercera cifra: esta es muy fácil. Después de la última etapa anterior, en A quedó un número menor que 10 que, además, es la última cifra del número original
  8. Ejercicio

    Repetir el ejemplo anterior pero para números de tres cifras (del 000 al 999) y usando el registro D
  9. Bucles

    Observad el fragmento de código del ejemplo anterior:
            ; segunda cifra.  En A quedan las dos Ultimas cifras
            clrb
            cmpa #80
            blo Menor80
            incb
            suba #80
    Menor80:lslb
            cmpa #40
            blo Menor40
            incb
            suba #40
    Menor40:lslb
            cmpa #20
            blo Menor20
            incb
            suba #20
    Menor20:lslb
            cmpa #10
            blo Menor10
            incb
            suba #10
    Menor10:addb #'0
    

    En este fragmento de código vemos claramente un patrón que se repite. Es un muy buen candidato para construir un bucle. Para ello, seguimos los siguientes pasos

    1. Reordenamos, añadimos o quitamos líneas de modo que, siendo el funcionamiento del programa igual, el patrón que se repita sea exacto. En el ejemplo, si añadimos la instrucción LSLB antes de la instrucción CMPA #80, logramos un patrón exacto que se repite cuatro veces:
              lslb
              cmpa X
              blo etiqueta
              incb
              suba X
      etiqueta:
      
    2. Identificamos la variable del bucle, esto es, lo que varía en cada iteración. Es lo que hemos marcado con esa X. X tiene que valer 80, 40, 20 y 10.
    3. Para la variable del bucle, escribimos su valor inicial, su condición de continuidad en el bucle y su variación de una iteración a la siguiente. En nuestro ejemplo, el valor inicial es 80, la condición de permanencia es que sea mayor o igual que 10 y la variación consiste en dividir la variable por dos en cada iteración
    4. Construimos el bucle:
              tfr d,x          ; guardamos D en X temporalmente
              lda #80          ; valor inicial:  variable=80
              sta variable     ; variable estarA definida al principio del programa
              tfr x,d          ; recuperamos D          
      
      bucle:  lslb
              cmpa variable
              blo  Menor
              incb
              suba variable
      
      Menor:  lsr variable     ; variaciOn de la variable entre iteraciones
      
              tfr d,x          ; condiciOn de permanencia en el bucle
              lda variable
              cmpa #10
              tfr x,d
              bhs bucle
      
    El código anterior resulta más críptico, si cabe, con el bucle, pero, sobre todo para bucles de muchas iteraciones, el resultado es preferible. El código final, con alguna pequeña optimización, es el siguiente:
    	.area PROG (ABS)
    
            ; definimos una constante
    fin     .equ 0xFF01
    pantalla .equ 0xFF00
    
    	.org 0x100
    
    	.globl programa
    variable: .byte 0
    
    programa:
            lda #137
    
            ; primera cifra
            ldb #'0
            cmpa #100
            blo Menor100
            suba #100
            incb
            cmpa #100
            blo Menor200
            incb
            suba #100
    Menor100:
    Menor200:
            stb pantalla
    
            ; segunda cifra.  En A quedan las dos Ultimas cifras
            ldb #80
            stb variable
            clrb
    
    bucle:  lslb
            cmpa variable
            blo Menor
            incb
            suba variable
    Menor:  tfr d,x
            lda variable
            lsra
            sta variable
            cmpa #10
            tfr x,d
            bhs bucle
    
            addb #'0
            stb pantalla
            adda #'0
            sta pantalla
    
            ; imprimimos un salto de lInea
            ldb #'\n
            stb pantalla
    
            ; el programa acaba
            clra
    	sta fin
    
    	.org 0xFFFE	; vector de RESET
    	.word programa
    

    A esta técnica se le denomina enrollamiento de bucles. A veces, se hace el proceso inverso: cuando se quiere interpretar un código que no se entiende se prueba a ver si desenrollando el bucle se ve la luz

  10. Ejercicio

    Enrollar los bucles del ejercicio anterior.
  11. Ejercicio

    Con ayuda del código del ejercicio anterior prográmese lo el siguiente adivinador mental, marca Acme®:

    El ordenador va a pensar del siguiente modo:

    Ejemplo de una ejecución:

    549
    <
    324
    >
    436
    <
    380
    <
    352
    <
    338
    <
    331
    >
    334
    <
    332
    >
    333
    =
    
  12. Órdenes de ensamblador vistas.

    NOP
    no hace nada
    Flags afectados: ninguno
    JMP x
    carga el PC con la dirección de memoria x o con la dirección apuntada por la etiqueta x. x puede ser una dirección o una etiqueta
    Flags afectados: ninguno
    Bxx y
    salta a la dirección de memoria y o a la dirección apuntada por la etiqueta y. La dirección debe estar en el rango [PC-128,PC+127]. y puede ser una dirección o una etiqueta. Las distintas variantes realizan o no el salto dependiendo del estado de los flags:
    • BCC, BCS: C=0 ó C=1, respectivamente
    • BNE, BEQ: Z=0 ó Z=1, respectivamente
    • BVC, BVS: V=0 ó V=1, respectivamente
    • BPL, BMI: N=0 ó N=1, respectivamente
    • BRA, BRN: siempre o nunca, respectivamente
    Estas otras variantes, también saltan en función de los flags, pero solamente tienen sentido después de una diferencia o una operación CMP
    • BHI: minuendo>sustraendo sin signo (Z=0 y C=0)
    • BHS: minuendo>=sustraendo sin signo (C=0)
    • BLS: minuendo<=sustraendo sin signo (Z=1 ó C=1)
    • BLO: minuendo<sustraendo sin signo (C=1)
    • BGT: minuendo>sustraendo con signo (Z=0 y C=N)
    • BGE: minuendo>=sustraendo con signo (C=N)
    • BLE: minuendo<=sustraendo con signo (Z=1 ó C diferente de N)
    • BLT: minuendo<sustraendo con signo (C diferente de N)
    Flags afectados: ninguno
    LBCC, LBCS, LBNE, LBEQ, LBVC, LBVS, LBPL, LBMI, LBHI, LBHS, LBLS, LBLO, LBGT, LBGE, LBLE, LBLT, LBRA, LBRN
    mismas operaciones que las anteriores, pero permitiendo desplazamientos respecto del PC de 16 bits con signo
    Flags afectados: igual que en los casos de arriba
    CMPx
    resta el contenido del registro x menos el operando indicado, bien indique un número o el contenido de una posición de memoria. El resultado se descarta, pero los flags quedan afectados. x puede ser A, B, D, X, Y, U y S
    Flags afectados: NZVC (H=?, si x es A o B)
    TSTx
    establece el valor de los flags N y Z en función del contenido del registro x. x puede ser A o B
    Flags afectados: NZ, V=0
    TST
    establece el valor de los flags N y Z en función del contenido de la dirección de memoria indicada como operando
    Flags afectados: NZ, V=0


  13. Órdenes de la shell relacionadas.

    ls
    lista el contenido de un directorio
    cd
    cambia el directorio de trabajo
    rm
    borra un fichero
    man
    muestra la página de manual de una orden
    cat
    muestra el contenido de un fichero
    echo $?
    muestra el código devuelto por el último programa ejecutado


  14. LPEs.


© 2010 Guillermo González Talaván.