PRÁCTICAS DE COMPUTADORES II

DÉCIMA SESIÓN


  1. Código reubicable

    Veamos una variación de un viejo conocido nuestro:
                                  1         ; definimos un par de constantes
                         FF01     2 fin     .equ 0xFF01
                         FF00     3 pantalla .equ 0xFF00
                                  4 
                                  5 	.globl programa
                                  6 
       0000 00                    7 temp:   .byte 0 ; una variable temporal
       0001 30 78 00              8 prefijo:.asciz "0x"
                                  9 
       0004                      10 programa:
       0004 86 1C         [ 2]   11 	lda #28 ; pongamos este nUmero como prueba
       0006 BD 00 12      [ 8]   12         jsr imprimir_byte_hexa
                                 13         ; imprimamos un salto de lInea al final
       0009 86 0A         [ 2]   14         lda #'\n
       000B B7 FF 00      [ 5]   15         sta pantalla
                                 16 
                                 17         ; el programa acaba
       000E 4F            [ 2]   18         clra
       000F B7 FF 01      [ 5]   19 	sta fin
                                 20 
                                 21 
       0012                      22 imprimir_byte_hexa:
       0012 8E 00 01      [ 6]   23         ldx #prefijo
       0015 BD 00 40      [ 8]   24         jsr imprime_cadena
                                 25 
                                 26         ; primero imprimamos la primera cifra hexadecimal
       0018 B7 00 00      [ 5]   27         sta temp ; guardamos A en temp
       001B 44            [ 2]   28         lsra
       001C 44            [ 2]   29         lsra
       001D 44            [ 2]   30         lsra
       001E 44            [ 2]   31         lsra ; en temp estA la primera cifra, de 0 a 15
       001F 27 0B         [ 3]   32         beq segunda
       0021 81 0A         [ 2]   33         cmpa #10
       0023 25 02         [ 3]   34         blo menor10
       0025 8B 07         [ 2]   35         adda #7
       0027 8B 30         [ 2]   36 menor10:adda #'0
       0029 B7 FF 00      [ 5]   37         sta pantalla
                                 38 
       002C                      39 segunda:
       002C B6 00 00      [ 5]   40         lda temp ; recuperamos el valor original
       002F 84 0F         [ 2]   41         anda #0b1111
       0031 81 0A         [ 2]   42         cmpa #10
       0033 25 02         [ 3]   43         blo menor10bis
       0035 8B 07         [ 2]   44         adda #7
       0037                      45 menor10bis:
       0037 8B 30         [ 2]   46         adda #'0
       0039 B7 FF 00      [ 5]   47         sta pantalla
                                 48 
       003C B6 00 00      [ 5]   49         lda temp
       003F 39            [ 5]   50         rts
                                 51 
       0040                      52 imprime_cadena:
       0040 34 02         [ 6]   53         pshs a
       0042 A6 80         [ 6]   54 sgte:   lda ,x+
       0044 27 06         [ 3]   55         beq ret_imprime_cadena
       0046 B7 FF 00      [ 5]   56         sta pantalla
       0049 7E 00 42      [ 4]   57         jmp sgte
       004C                      58 ret_imprime_cadena:
       004C 35 02         [ 6]   59         puls a
       004E 39            [ 5]   60         rts
    
    El código no está completo, pues falta el vector de RESET que apunta a una rutina previa que establece la pila y salta al comienzo del programa. Pero esto no importa para nuestra exposición. Lo vamos a enlazar para situarlo en la dirección 0x100:
    aslink -s -b _CODE=0x100 hex4.rel
    
    El código máquina que se almacena a partir de la dirección 0x100 es el siguiente:
    00307800861CBD0112860AB7FF004FB7FF018E0101BD0140B7010044444444270B810A25028B078B30B7FF00B60100840F810A25028B078B30B7FF00B60100393402A6802706B7FF007E0142350239
    
    Es tan largo que se nos acaba hasta el papel perforado... Vamos a enlazarlo ahora para que se cargue en la dirección 0x1234:
    aslink -s -b _CODE=0x1234 hex4.rel
    
    El código máquina en la dirección 0x1234 es ahora:
    00307800861CBD1246860AB7FF004FB7FF018E1235BD1274B7123444444444270B810A25028B078B30B7FF00B61234840F810A25028B078B30B7FF00B61234393402A6802706B7FF007E1276350239
    
    Es muy parecido, ¿verdad? Pero, ¿es igual? Pongámoslos juntos para comparar:
    00307800861CBD0112860AB7FF004FB7FF018E0101BD0140B7010044444444270B810A25028B078B30B7FF00B60100840F810A25028B078B30B7FF00B60100393402A6802706B7FF007E0142350239
    00307800861CBD1246860AB7FF004FB7FF018E1235BD1274B7123444444444270B810A25028B078B30B7FF00B61234840F810A25028B078B30B7FF00B61234393402A6802706B7FF007E1276350239
    
    Son casi idénticos, pero no iguales. Marquemos en rojo las diferencias para que se vea más claro:
    00307800861CBD0112860AB7FF004FB7FF018E0101BD0140B7010044444444270B810A25028B078B30B7FF00B60100840F810A25028B078B30B7FF00B60100393402A6802706B7FF007E0142350239
    00307800861CBD1246860AB7FF004FB7FF018E1235BD1274B7123444444444270B810A25028B078B30B7FF00B61234840F810A25028B078B30B7FF00B61234393402A6802706B7FF007E1276350239
    
    El código que hemos programado cambia según cambiemos la dirección en que se carga. Este código no es reubicable tal cual. Lo podemos cambiar de dirección de carga porque el enlazador aslink se encarga de modificarlo para que funcione en la dirección que le digamos.

    El objetivo de esta sesión es mostrar las técnicas que nos permiten generar código máquina que pueda ser cargado en cualquier posición de memoria, pero sin modificar ni uno solo de sus bytes.

    No se trata de un mero pasatiempo. Cuantas menos modificaciones tenga que hacer nuestro enlazador, más rápido será el enlazado. En otras ocasiones, un cógido ya cargado y ejecutándose en la memoria debe moverse para dejar paso a otro programa que se va a ejecutar a la vez que el primero. A veces, tenemos código máquina almacenado en una ROM y queremos instalar esa ROM en otras posiciones de memoria, sin cambiarla. Si el código es reubicable, es posible en el último caso y muy fácil en los anteriores. Si no, no.

    Transformemos, pues, este programa para que sea código máquina reubicable. Donde primero podemos atacar es en los saltos y saltos a subrutinas absolutos. En el código hay varios:
                                            [...]
       0004                      10 programa:
       0004 86 1C         [ 2]   11 	lda #28 ; pongamos este nUmero como prueba
       0006 BD 00 12      [ 8]   12         jsr imprimir_byte_hexa
                                 13         ; imprimamos un salto de lInea al final
                                            [...]
       0012                      22 imprimir_byte_hexa:
       0012 8E 00 01      [ 6]   23         ldx #prefijo
       0015 BD 00 40      [ 8]   24         jsr imprime_cadena
                                 25 
                                            [...]
       0046 B7 FF 00      [ 5]   56         sta pantalla
       0049 7E 00 42      [ 4]   57         jmp sgte
       004C                      58 ret_imprime_cadena:
       004C 35 02         [ 6]   59         puls a
                                            [...]
    
    Si usamos JMP o JSR, la dirección de memoria a la que voy a saltar es la que aparece en el código máquina y esa dirección varía según donde lo almacenemos. Por ejemplo, la subrutina imprimir_byte_hexa está en la dirección 0x112 cuando el programa se almacena a partir de la 0x100 y en la direccción 0x1246 cuando el programa se almacena en la dirección 0x1234. Cambiemos estas instrucciones por (L)BRA y (L)BSR y veamos el resultado:
                                            [...]
       0004                      10 programa:
       0004 86 1C         [ 2]   11 	lda #28 ; pongamos este nUmero como prueba
       0006 8D 09         [ 7]   12         bsr imprimir_byte_hexa
                                 13         ; imprimamos un salto de lInea al final
                                            [...]
       0011                      22 imprimir_byte_hexa:
       0011 8E 00 01      [ 6]   23         ldx #prefijo
       0014 8D 28         [ 7]   24         bsr imprime_cadena
                                 25 
                                            [...]
       0044 B7 FF 00      [ 5]   56         sta pantalla
       0047 20 F7         [ 3]   57         bra sgte
       0049                      58 ret_imprime_cadena:
       0049 35 02         [ 6]   59         puls a
                                            [...]
    
    Al usar BRA o BSR, la dirección a la que hay que saltar se expresa como un desplazamiento sobre el valor del PC en esos momentos. Al cargar el programa en direcciones diferentes, el valor del PC cambia, pero el desplazamiento o la distancia a la que se encuentra la subrutina a la que queremos saltar es la misma. Lo comprobamos viendo cómo se apagan esas tres diferencias en la comparación de códigos máquinas:
    00307800861C8D09860AB7FF004FB7FF018E01018D28B7010044444444270B810A25028B078B30B7FF00B60100840F810A25028B078B30B7FF00B60100393402A6802705B7FF0020F7350239
    00307800861C8D09860AB7FF004FB7FF018E12358D28B7123444444444270B810A25028B078B30B7FF00B61234840F810A25028B078B30B7FF00B61234393402A6802705B7FF0020F7350239
    
    Lo siguiente que vamos a hacer, seguro que ya se os ha ocurrido. Tenemos una variable temporal temp que, como todas las variables locales y temporales, es muy buena candidata para almacenarse en la pila. Veamos dónde aparece:
                                            [...]
                                  6 
       0000 00                    7 temp:   .byte 0 ; una variable temporal
       0001 30 78 00              8 prefijo:.asciz "0x"
                                            [...]
                                 26         ; primero imprimamos la primera cifra hexadecimal
       0016 B7 00 00      [ 5]   27         sta temp ; guardamos A en temp
       0019 44            [ 2]   28         lsra
                                            [...]
       002A                      39 segunda:
       002A B6 00 00      [ 5]   40         lda temp ; recuperamos el valor original
       002D 84 0F         [ 2]   41         anda #0b1111
                                            [...]
                                 48 
       003A B6 00 00      [ 5]   49         lda temp
       003D 39            [ 5]   50         rts
                                            [...]
    
    Sustituyamos este fragmento de código por este otro que hace uso de la pila:
                                            [...]
                                 26         ; primero imprimamos la primera cifra hexadecimal
       0015 34 02         [ 6]   27         pshs a ; guardamos A en en la pila
       0017 44            [ 2]   28         lsra
                                            [...]
       0028                      39 segunda:
       0028 A6 E4         [ 4]   40         lda ,s ; recuperamos el valor original
       002A 84 0F         [ 2]   41         anda #0b1111
                                            [...]
                                 48 
       0037 35 02         [ 6]   49         puls a
       0039 39            [ 5]   50         rts
                                            [...]
    
    El código que usa la pila no depende de dónde se cargue el programa. La cosa pinta bien... A ver cuánto nos queda por arreglar:
    307800861C8D09860AB7FF004FB7FF018E01008D25340244444444270B810A25028B078B30B7FF00A6E4840F810A25028B078B30B7FF003502393402A6802705B7FF0020F7350239
    307800861C8D09860AB7FF004FB7FF018E12348D25340244444444270B810A25028B078B30B7FF00A6E4840F810A25028B078B30B7FF003502393402A6802705B7FF0020F7350239
    
    Parece que queda una pequeña resistencia numantina. Veamos a qué parte del código corresponde:
                                            [...]
                                  7 
       0000 30 78 00              8 prefijo:.asciz "0x"
                                  9 
                                            [...]
       0010                      22 imprimir_byte_hexa:
       0010 8E 00 00      [ 6]   23         ldx #prefijo
       0013 8D 25         [ 7]   24         bsr imprime_cadena
                                            [...]
    
    Antes de que cojáis el martillo pilón y la aplastéis escribiendo primero un 0 y luego una x, trataremos de ser más sutiles, pues habrá veces en que no podamos deshacernos tan sencillamente de una variable almacenada en la memoria. En estos momentos podemos caer en que, en el fondo, el problema es el mismo que el que ocurría con los saltos JMP y JSR. Si yo fuera capaz de expresar la dirección de acceso de la variable no de un modo absoluto, sino como un desplazamiento respecto a la posición actual del PC, todo estaría arreglado. La buena noticia es que se puede hacer en el ensamblador del 6809. La mala (o buena, según se mire) es que hay que controlar muy bien los modos de direccionamiento para saber qué ocurre entre bambalinas:
                                            [...]
                                  7 
       0000 30 78 00              8 prefijo:.asciz "0x"
                                  9 
                                            [...]
       0010                      22 imprimir_byte_hexa:
       0010 30 8C ED      [ 5]   23         leax prefijo,pcr
       0013 8D 25         [ 7]   24         bsr imprime_cadena
                                            [...]
    
    Partamos primero de lo que sería una aproximación ingenua al problema:
            ldx prefijo,pc
    
    Esto no haría lo que nosotros queremos que haga. Recordemos que la etiqueta prefijo tiene un valor, el que sea, correspondiente con la dirección de memoria donde se almacene su variable. Al escribir prefijo,pc estaríamos pidiendo que se sumara al PC el valor de esa dirección de memoria. Eso no es lo que queremos. Queremos que el ensamblador, considerando dónde estará el PC cuando se ejecute la instrucción, calcule qué hay que sumarle para dar con la variable a la que apunta prefijo. Supongamos que el programa se cargara en la dirección 0x100. En ese caso, prefijo vale 0x100 pues es lo primero que aparece en el programa. El PC, cuando se ejecute la instrucción estará en 0x113 (apuntando ya a la siguiente instrucción), luego el desplazamiento que debe calcular el ensamblador es -0x13 o, lo que es lo mismo, 0xED, que es el que aparece en el código máquina.

    Luego, la sintaxis correcta consiste en usar PCR en lugar de PC. ¿Y por qué se usa LEAX en lugar de LDX?

    Si ponemos LDX, el registro X se va a cargar con el contenido de la variable, es decir, con 0x3078. Nosotros queremos que X se cargue con la dirección donde está la variable. Por eso usamos LEAX.

    Para ver si lo comprendéis, pensad y luego verificad con qué valor se cargaría X en estas dos instrucciones: LDX [prefijo,PCR] y LEAX [prefijo,PCR].

    El resultado del enlazado de este programa vemos que no depende de la dirección de memoria donde se vaya a cargar:

    307800861C8D09860AB7FF004FB7FF01308CED8D25340244444444270B810A25028B078B30B7FF00A6E4840F810A25028B078B30B7FF003502393402A6802705B7FF0020F7350239
    307800861C8D09860AB7FF004FB7FF01308CED8D25340244444444270B810A25028B078B30B7FF00A6E4840F810A25028B078B30B7FF003502393402A6802705B7FF0020F7350239
    

    Esta ristra de código la podemos escribir en cualquier posición de la memoria, que siempre funcionará como debe. Podemos quemar un chip de ROM con el código sin saber todavía dónde irá a parar en el mapa de memoria final.

    Prestad atención, por último, que ciertos accesos a memoria no se deben hacer nunca relativos al PC. Por ejemplo, la dirección de memoria donde se escriben los caracteres que saldrán por la pantalla es, en nuestro sistema, siempre la 0xFF00, independientemente de dónde se cargue el código.

    Todas las instrucciones que admiten el modo indizado/indirecto, admiten la indización sobre el PC. En particular, las instrucciones JMP y JSR.

  2. Ejercicio

    Haced versiones, partiendo de la solución de los ejercicios de la sesión anterior, cuyo código máquina sea completamente reubicable.
  3. Interrupciones

    El 6809 tiene capacidad para gestionar las siguientes interrupciones:
    1. RESET: al encenderse el ordenador
    2. NMI: interrupción no enmascarable (Non-Maskable Interrupt)
    3. SWI: iterrupción de software 1. Se produce al ejecutar la instrucción SWI (que pone el flag E a 1, guarda los registros en la pila, pone los flags F e I a 1 y salta a su vector
    4. IRQ: requerimiento de interrupción
    5. FIRQ: requerimiento de interrupción rápida
    6. SWI2: interrupción de software 2. Como SWI, pero la instrucción es SWI2
    7. SWI3: idem SWI y SWI2
    Las interrupciones de software requieren que se ejecute su instrucción para que se produzca el salto. El resto, tienen asociadas patillas del procesador. Dichas patillas suelen estar conectadas a circuitos de dispositivos que, pueden activarlas y producir, en cualquier momento, la interrupción. Son interrupciones asíncronas.

    IRQ y FIRQ se pueden enmascarar. Significa esto que, si están enmascaradas, aunque se active su señal, no se producirá el salto a la rutina de servicio. Las máscaras se encuentran en dos bits del registro CC, el I y el F, respectivamente.

    FIRQ es especial por cuanto, en un interrupción normal, antes de saltar a la rutina de servicio, se almacenan en la pila todos los registros automáticamente. En una FIRQ, solamente se almacena el PC y es responsabilidad de la rutina de servicio guardar y restaurar los registros que vaya a usar.

    Existe una tabla de salto al final de la memoria para que el procesador sepa dónde debe saltar cuando se produce cada tipo de interrupción. Las direcciones de los vectores de salto son: 0xFFFE (RESET), 0xFFFC (NMI), 0xFFFA (SWI), 0xFFF8 (IRQ), 0xFFF6 (FIRQ), 0xFFF4 (SWI2) y 0xFFF2 (SWI3).

    Al final de una rutina de tratamiento de interrupciones, se debe ejecutar la instrucción RTI (ReTurn from Interrupt). Esta instrucción consulta el flag E del registro CC para ver si tiene que restaurar todos los registros o solamente el PC.

  4. Ejercicio

    Programaremos una cuenta atrás auténtica de 10 a 0 con un intervalo de un segundo entre número y número, basándonos en interrupciones.

    Primero se medirá cuál es el periodo del ciclo de reloj (o la frecuencia, tanto da). Para ello, se hace un programa de bastantes ciclos. Con la orden del sistema operativo time se ejecuta en el simulador:

    time m6809-run periodo.s19
    
    Dividiendo el tiempo que nos indica el sistema operativo entre el número de ciclos de nuestro programa, obtenemos el periodo del reloj del simulador.

    Con la opción del simulador -I 1000, hacemos que el simulador produzca una interrupción IRQ cada 1000 ciclos de reloj. Basándonos en el número calculado más arriba programaremos una rutina de servicio y colocaremos el valor adecuado en la máscara, para que se produzca la cuenta atrás con la cadencia correcta.

    Notas: como las interrupciones suceden mientras se está ejecutando el programa "principal", debéis dejar al procesador ocupado en algo (un bucle infinito, por ejemplo). También debéis imprimir un número por línea:

    10
    9
    8
    7
    6
    5
    4
    3
    2
    1
    0
    
  5. Página cero o página directa

    Cuando hablamos de los modos de direccionamiento, dijimos que existía un modo denominado modo directo del que habaríamos más adelante. Llegó el momento de desvelar el misterio.

    En la terminología del 6809 se denomina página de memoria a cualquiera de las zonas de memoria cuya dirección esté comprendida entre 0xnn00 y 0xnnFF. Así, la primera página es la comprendida entre las direcciones 0x0000 y 0x00FF. La segunda página, entre las direcciones 0x0100 y 0x01FF, y así sucesivamente.

    Existe una página especial denominada página de acceso directo, que es la especificada por el registro DP. Si DP vale 7, por ejemplo, la página de acceso directo es la comprendida entre las direcciones 0x0700 y 0x07FF.

    El modo de direccionamiento directo accede a los bytes de de la página de acceso directo. La ventaja fundamental que tiene es que no hace falta especificar la parte alta de la dirección en la instrucción, pues está en el registro DP. Esto ahora tamaño de código y hace que se ejecute más rápido. Para marcar que un acceso es directo, nuestro ensamblador utiliza el asterisco. Observad la diferencia:

       0005 0F 03         [ 6]   17         clr *bandera
       0007 7F 07 03      [ 7]   18         clr bandera
    

    La página directa se ha puesto en la dirección 0x700 y siguientes. Hay una variable, cuyo nombre es bandera, que está en 0x703. El acceso extendido de la segunda línea incluye la dirección completa 0x7F,0x07,0x03. El acceso directo solamente necesita incluir el byte bajo de la dirección y ocupa, por tanto, un byte menos: 0x0F,0x03. Además consume también un ciclo menos del procesador.

    Para que la pareja de ensamblador y enlazador, se pongan de acuerdo y se coordinen cuando se usa el modo directo, se debe seguir el siguiente esquema:

    1. Se debe declarar un área con el atributo PAG, para contener la página directa (o varias, si se va a usar más de una)
    2. Se debe declarar una variable global con el mismo nombre que el área con la etiqueta .globl
    3. En la zona donde se vaya a acceder en modo directo, se usará la directiva .SETDP, como en este ejemplo en que el área se llamó DIRECTA:
              .setdp 0,DIRECTA
      
    4. Es responsabilidad del programador que el registro DP contenga el valor adecuado a la entrada de la zona. Para facilitarlo es para lo que hemos declarado la variable global, por lo que un código típico podría ser:
              ldd #DIRECTA
              tfr a,dp
      
    5. Las variables a las que queramos acceder en modo directo deben estar precedidas por un asterisco cuando aparezcan como operando
    6. Ensamblaremos el código del modo habitual
    7. Al enlazar, sin embargo, debemos definir tanto la posición del área como la variable global para que apunten al inicio de la página directa. Imaginemos que esa dirección fuera la 0x700. La orden sería:
      aslink -s -g DIRECTA=0x700 -b DIRECTA=0x700 programa.rel
      
    Y desvelado el último secreto que guardaba entre sus viejos transistores el 6809, concluimos estas sesiones.
  6. Ejercicio

    Repítase el ejercicio anterior pero almacenando todas sus variables en la página directa que deseéis.
  7. Órdenes de ensamblador vistas.

    SWI, SWI2, SWI3
    produce una interrupción de software de tipo especificado
    Flags afectados: ninguno.
    RTI
    vuelve de una interrupción, restaurando los registros si el flag E vale 1
    Flags afectados: todos, si se restaura CC. Si no, ninguno
    .SETDP d,a
    indica al ensamblador/enlazador que, a partir de ese punto, la página directa comienza en el área a, desplazamiento d (normalmente cero)
    Flags afectados: es una directiva del ensamblador


  8. Ó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
    time
    ejecuta y devuelve el tiempo consumido por la orden que se le pasa como argumento


  9. LPEs.


© 2017 Guillermo González Talaván.