PRÁCTICAS DE COMPUTADORES II

PRIMERA SESIÓN


  1. 6809

    CPU 6809
    El MC6809 es un procesador creado por Motorola en 1977 como sustitución y mejora del 6800. Es un procesador de 8 bits, con algunas características de 16 bits. Presenta como ventajas para aprender ensamblador que:
  2. Configuración del entorno de trabajo.

    Las prácticas se realizarán en el Sistema Operativo GNU/Linux. Para poder trabajar en vuestra casa, es muy conveniente que os instaléis una distribución de Linux. Podéis copiar e instalar en casa los programas que usaremos para ensamblar y ejecutar nuestro código: as6809, aslink y m6809-run. Los ficheros se encuentran en el directorio /usr/bin de vuestros ordenadores de clase. No olvidéis colocarlos en un directorio donde se puedan ejecutar y darles los permisos de ejecución adecuados, una vez copiados en vuestro ordenador.

    Para los que esta tarea les resulte complicada, vuestros compañeros del curso 2015-16, El equipo bAsh, han escrito un programa para descargárselos e instalarlos:

         instaladorcii.sh

    El programa comprobará que tenéis la distribución de Linux compatible y se descargará y configurará los programas adecuadamente, siempre que dispongáis de una conexión a Internet en vuestro ordenador.

    Aparte de estos programas, se necesita tambíen un editor de texto para generar los programas. Cualquiera que genere texto plano puede ser válido: vi, kate, gedit, etc.

  3. Primer programa en ensamblador

    Como ya conocéis, manda la tradición que el primer programa que se prueba en un nuevo lenguaje de programación sea uno que imprima en la pantalla las palabras Hola mundo.

    Abrid, pues, el editor de textos para generar el siguiente fichero al que nombraréis hola.asm:

    
    ; Programa de ejemplo: hola.asm
    
            .area PROG (ABS)
    
            .org 0x100
    cadena: .ascii "Hola, mundo."
            .byte   10      ; 10 es CTRL-J: salto de lInea
            .byte   0       ; 0 es CTRL-@: fin de cadena
    
            .globl programa
    programa:
            ldx #cadena
    bucle:  lda ,x+
            beq acabar
            sta 0xFF00      ; salida por pantalla
            bra bucle
    acabar: clra
            sta 0xFF01
    
            .org 0xFFFE     ; Vector de RESET
            .word programa
    
    

    Una vez hayáis guardado el programa, debéis abrir una ventana con la línea de órdenes. Observad que habéis creado bien el programa tecleando ls -l. Os ha de aparecer una línea similar a la siguiente:

    -rw-r--r-- 1 gyermo gyermo 327 2010-08-03 23:35 hola.asm

    Procedamos ahora a ensamblar, enlazar y ejecutar el programa:

    1. Primero lo ensamblamos:
      El proceso de ensamblado lo realiza un programa que se llama ensamblador. Consiste en generar código máquina (también código objeto) binario entendible por el procesador. Para ello, damos la orden:
      as6809 -o hola.asm
      Si todo ha funcionado correctamente, debéis tener un fichero acabado en .rel con el código objeto. Miradlo con ls -l:
      -rw-r--r-- 1 gyermo gyermo    327 2010-08-03 23:35 hola.asm
      -rw-r--r-- 1 gyermo gyermo    608 2010-08-03 23:43 hola.rel
      Si os pica la curiosidad, podéis mirarlo con el editor. Enseguida notaréis que no se trata de un fichero para consumo humano. Podéis pedirle al ensamblador que os genere un listado más comprensible de la tarea que ha realizado. Hacedlo así:
      as6809 -l hola.asm
      El fichero que ahora aparece se llama hola.lst y este es su contenido:
      
      ASxxxx Assembler V05.00  (Motorola 6809), page 1.
      Hexidecimal [16-Bits]
      
      
      
                                    1         .area PROG (ABS)
                                    2 
         0100                       3         .org 0x100
         0100 48 6F 6C 61 2C 20     4 cadena: .ascii "Hola, mundo."
              6D 75 6E 64 6F 2E
         010C 0A                    5         .byte   10      ; 10 es CTRL-J: salto de lInea
         010D 00                    6         .byte   0       ; 0 es CTRL-@: fin de cadena
                                    7 
                                    8         .globl programa
         010E                       9 programa:
         010E 8E 01 00      [ 3]   10         ldx #cadena
         0111 A6 80         [ 6]   11 bucle:  lda ,x+
         0113 27 05         [ 3]   12         beq acabar
         0115 B7 FF 00      [ 5]   13         sta 0xFF00      ; salida por pantalla
         0118 20 F7         [ 3]   14         bra bucle
         011A 4F            [ 2]   15 acabar: clra
         011B B7 FF 01      [ 5]   16         sta 0xFF01
                                   17 
         FFFE                      18         .org 0xFFFE     ; vector de RESET
         FFFE 01 0E                19         .word programa
                                   20 
      
      ASxxxx Assembler V05.00  (Motorola 6809), page 2.
      Hexidecimal [16-Bits]
      
      Symbol Table
      
          .__.$$$.       =   2710 L   |     .__.ABS.       =   0000 G
          .__.CPU.       =   0000 L   |     .__.H$L.       =   0001 L
        2 acabar             011A R   |   2 bucle              0111 R
        2 cadena             0100 R   |   2 programa           010E GR
      
      
      ASxxxx Assembler V05.00  (Motorola 6809), page 3.
      Hexidecimal [16-Bits]
      
      Area Table
      
      [_CSEG]
         0 _CODE            size    0   flags C080
         2 PROG             size    0   flags  908
      [_DSEG]
         1 _DATA            size    0   flags C0C0
      
      
      No os preocupéis, por ahora en ver qué significa cada cosa. Tiempo habrá. Pasemos a la siguiente fase
    2. Luego, lo enlazamos:
      El proceso de enlazado consiste básicamente en reunir todos los módulos objeto de un programa, establecer las conexiones entre ellos y añadir una cabecera. El enlazador construye así un fichero ejecutable que, como su nombre indica, puede ser ejecutado por el usuario.

      El enlazador que usaremos se llama aslink. Es capaz de generar su fichero ejecutable en diferentes formatos. Elegimos uno que entiende el simulador que usaremos para ejecutar el programa: el formato S de Motorola. La línea que tenemos que teclear es:

      aslink -s hola.rel
      Saldrán un par de líneas por la pantalla y, si funciona, tendremos nuestro módulo ejecutable, acabado en s19:
      -rw-r--r-- 1 gyermo gyermo    327 2010-08-03 23:35 hola.asm
      -rw-r--r-- 1 gyermo gyermo   1653 2010-08-03 23:47 hola.lst
      -rw-r--r-- 1 gyermo gyermo    608 2010-08-03 23:43 hola.rel
      -rw-r--r-- 1 gyermo gyermo     97 2010-08-03 23:58 hola.s19
    3. Finalmente, lo ejecutamos:
      Como no disponemos físicamente de una máquina con un 6809 para probar nuestros programas, lo haremos con un simulador. El que vamos a usar es también depurador, lo que nos vendrá muy bien más adelante. Ejecutamos el programa del siguiente modo:
      m6809-run hola.s19
      Después de tanto trabajo, obtenemos el bien merecido y flamante resultado:
      Hola, mundo
      El programa simulador, ofrece varios modos de ejecución. Si, por ejemplo, deseamos saber cuántos ciclo de reloj se demoró la ejecución, hay que introducir:
      m6809-run -C hola.s19
      Si lo que se desea es depurar el programa, la opción es -d como veremos más adelante.
  4. Formato de un programa en ensamblador

    Un programa en ensamblador está formado por una serie de líneas, todas con el siguiente formato:
         etiqueta: operador operando ;comentario
    La parte de la etiqueta y del comentario son opcionales, así como también es posible una línea en blanco o que solo contenga el comentario. Pasemos a describir cada uno de los campos por separado:
    1. Etiqueta: según progresa el proceso de ensamblado, cada línea va a generar una serie de bytes que acabarán almacenándose en la memoria. Así, por ejemplo, la segunda línea de código del programa anterior:
      bucle: lda ,x+
      genera los bytes 0xA6 y 0x80. Estos bytes se almacenarán en la posición de memoria actual de ensamblado en ese momento. En el ejemplo, esa posición de memoria es la 0x111.

      Ocurre que, en ocasiones, interesa referirse a la posición de memoria donde se almacenó el ensamblado de una determinada línea, Es ahí donde se usa una etiqueta. Por consiguiente, la etiqueta bucle hace referencia a la dirección de memoria 0x111 y puede usarse para referirse a esa dirección.

      Las etiquetas en ensamblador se usan mucho pues hasta que no se completa el propio ensamblado no se sabe a ciencia cierta qué posición de memoria puede ocupar una línea concreta. Fundamentalmente se usan para referirse a datos (el equivalente a variables en otros lenguajes) o a posiciones de memoria de instrucciones (para poder saltar a ellas en bucles, subrutinas, condiciones, etc.)

      El ensamblador que estamos usando impone los siguientes requerimientos para las etiquetas:
      • Se pueden usar los caracteres de la A a la Z, de la a a la z (ambos del abecedario inglés), del 0 al 9, el punto (.), el signo de dólar ($) y el carácter de subrayado (_)
      • Pueden tener cualquier longitud, pero solamente los 79 primeros caracteres son significativos
      • No puede empezar una etiqueta por un número ni por la secuencia $$
    2. Operador: puede ser un código de instrucción del procesador (nemónico) o una directiva de ensamblador. Estas últimas comienzan por un punto. Así como los nemónicos son universales en todos los ensambladores de un mismo procesador, las directivas no lo son y pueden variar de ensamblador a ensamblador.
    3. Operando: algunos operadores requieren de argumentos. Estos argumentos forman el campo del operando.
    4. Comentario: se incluyen en este campo los comentarios que el programador desee especificar

    En general, y para evitar problemas, se debe usar solamente los caracteres del código ASCII de siete bits (por lo tanto, se debe evitar usar caracteres acentuados, eñes, aperturas de exclamación e interrogación, º, ª, etc.)

  5. Registros del 6809

    El 6809 tiene los siguientes registros accesibles para el programador:

    Registros del 6809

    Existen dos acumuladores de 8 bits, A y B que se pueden combinar en un único acumulador AB, cuyo nombre es D, a discreción del programador. En los acumuladores será donde se realicen las principales operaciones ariméticas y lógicas.

    El registro CC es el registro de códigos de condición. Su significado no es global, sino bit a bit. Cada uno de sus bits se activarán cuando se cumplan determinadas condiciones, que iremos viendo, en el procesador. El nombre de sus bits es el siguiente:

    Registros del 6809

    El significado de los códigos de condición (también banderas o flags) es el que sigue:

    1. E (entire state): marca si, en una interrupción, se guardaron todos los registros en la pila o solamente PC y CC
    2. F (FIRQ interrupt mask): sirve para inhibir o permitir las interrupciones rápidas
    3. H (half carry): marca si hubo acarreo en el bit 3 de una instrucción de suma de ocho bits. Lo usa internamente el procesador en la instrucción DAA
    4. I (IRQ interrupt mask): sirve para inhibir o permitir las interrupciones normales
    5. N (negative): es una copia del bit más significativo del resultado (bit que marca el signo en complemento a dos)
    6. Z (zero): activo si el resultado de una operación es cero
    7. V (overflow): activo si existe desbordamiento en una operación en complemento a dos
    8. C (carry): usado para almacenar el bit de acarreo en las sumas/restas. También se usa como apoyo en las operaciones de rotación o desplazamiento de bits

    El contador de programa (PC) es el registro que contiene la dirección de la siguiente instrucción que se va a ejecutar. En concreto, es muy importante entender que para que el procesador pueda ejecutar una instrucción es necesario ir leyendo uno a uno todos los bytes de la instrucción. El PC se usa como índice en la lectura. Cuando ya se ha leído la instrucción y se va a pasar a ejecutarla, el PC ya está apuntando a la siguiente instrucción.

    Del resto de registros nos ocuparemos más adelante en detalle.

  6. Instrucciones de carga

    Llegó el momento de ver nuestras primeras instrucciones (nemónicos) en ensamblador. Vamos a cargar un registro con un valor. Las instrucciones de carga son muy fáciles: LDA (load A), LDB, LDD, LDX, LDY, LDU y LDS, dependiendo del registro que queramos cargar. Por ejemplo, para cargar el registro A con un 7, la instrucción en ensamblador es:
         lda #7
    El carácter # es muy importante, pues si no se pone, se está especificando que se quiere cargar el registro A no con 7, sino con el valor del byte que se encuentre en la dirección de memoria 7.

    El ensamblador admite como prefijo 0x para indicar hexadecimal, 0b, para binario y 0o, para especificar octal.

    Podemos especificar que queremos cargar el código ASCII de un determinado carácter anteponiendo un apóstrofo ('). Si lo que queremos es cargar un registro de 16 bits con los códigos ASCII de dos caracteres, se usa las comillas ("). También se permiten para indicar los códigos las secuencias usadas en C. Ejemplos:

         lda #'a    ; Cargamos A con el cOdigo ASCII de la a: 97
         ldd #"AB   ; Cargamos D con el cOdigo ASCII de AB: 0x4142
         ldb #'\n   ; Cargamos B con el cOdigo de salto de lInea (CTRL+J): 10
         ldx #'\010 ; Cargamos X con el cOdigo de retroceso (CTRL+H): 8
    
  7. Instrucciones de carga y almacenamiento en memoria

    Cuando lo que se desea es cargar un registro con un valor almacenado en una dirección de memoria o almacenar su contenido en una dirección de memoria, las instrucciones son, para la carga, las mismas que las vistas en el apartado anterior. Para el almacenamiento, son: STA (store A), STB, STD, STX, STY, STU, STS. Luego, si queremos, por ejemplo, pasar el contenido de la dirección de memoria 0x1000 a la 0x2222, podemos hacerlo con estas dos instrucciones:
         lda 0x1000
         sta 0x2222
    No es habitual referirse a las direcciones de memoria por su número. Es más normal usar una etiqueta donde hicimos la reserva de la memoria. La función es muy similar a las de las variables de otros lenguajes de programación. Por ejemplo, para definir dos variables de 8 bits zipi y zape y pasar el contenido de zipi a zape. Haríamos:
    
         ; definiciOn en el preAmbulo del programa
         zipi: .byte 4 ; reservamos un byte para zipi y le damos valor inicial 4
         zape: .byte 0 ; idem zape, valor inicial 0
         ...
    
         ; zona del programa
         lda zipi
         sta zape
    
    

    Si el registro que se quiere leer de memoria o almacenar en ella es de 16 bits, es necesario conocer que el 6809, como todos los procesadores de Motorola de la época son procesadores que usan en convenio big endian. Esto significa que, de tener que almacenar algo que ocupe varias posiciones de memoria consecutivas, en la dirección especificada y sucesivas se almacenan los bytes de más significativos a menos significativos. Veámoslo con un ejemplo:

    
         ; cargamos D con el valor 0x1234 y, luego,
         ; lo almacenamos en la direcciOn 0x2000
    
         ldd #0x1234
         std 0x2000
    
         ; despuEs de ejecutar las instrucciones
         ; anteriores, el contenido de la memoria es:
         ;
         ;  0x2000:  0x12
         ;  0x2001:  0x34
    
    
  8. La máquina simple

    Todo programa, escrito en el lenguaje que sea, tiene que interactuar con el exterior. Es normal que reciba datos del teclado o escriba en la pantalla. También debe haber algo que coloque el programa binario en memoria y cargue el PC con la dirección inicial para que el procesador (que es un autómata) comience a ejecutarlo. Cuando el programa acaba, debe pasar el control a otro programa para que pregunte al usuario qué hacer a continuación. Todas estas tareas son misión de un programa en concreto, el sistema operativo, el cual se estudiará más adelante en la carrera.

    De momento, y para no complicar el estudio de ya de por sí difícil del lenguaje ensamblador, trabajaremos con una máquina simulada y ficticia muy simple. Las cuatro tareas que hemos enunciado más arriba no las realizará el sistema operativo, sino el programa simulador, del modo siguiente:

    1. Carga y ejecución del programa: el simulador carga el programa en memoria y carga el PC con el valor de 16 bits almacenado en la dirección 0xFFFE
    2. Finalización del programa: el simulador finaliza el programa automáticamente cuando se escribe cualquier valor de 8 bits en la dirección 0xFF01. El valor escrito será el devuelto a la shell. Se puede inspeccionar con la orden de la shell: echo $?
    3. Escritura de un carácter en la pantalla: si se escribe un valor en la dirección 0xFF00, el carácter cuyo código ASCII se ha escrito aparecerá por la pantalla
    4. Lectura del teclado: se puede obtener el código ASCII de un carácter tecleado leyendo de la dirección 0xFF02
  9. Plantilla de programa

    En lo que vemos el significado de todas las órdenes que aparecen en el programa de ejemplo, podéis usar el siguiente código como plantilla para hacer los ejercicios:
    
            .area PROG (ABS)
    
            .org 0x100
            .globl programa
    
            ;; definid en esta zona las variables que querAis usar,
            ;; como en estos ejemplos
    var1:   .byte 3  ; una variable de 8 bits con valor inicial 3
    var2:   .word 12 ; una variable de 16 bits con valor inicial 12
            ;; fin de la zona de variables
    
            ;; comienzo del programa
    programa:
            ;; escribid aquI las instrucciones del programa
    
    
            ;; aNadid estas lIneas para que el programa acabe y empiece
    acabar: clra
            sta 0xFF01
    
            .org 0xFFFE     ; Vector de RESET
            .word programa
    
    
  10. Ejercicio

    Hacer un programa que lea dos caracteres del teclado. Una vez leIdos, los sacará en orden inverso por la pantalla. También sacará un salto de línea y, finalmente, devolverá el código ASCII del primero de los caracteres tecleados a la shell. Comprobad que el código devuelto es el correcto (p. ej. para la A es 65).

    Usad la plantilla de programa como apoyo y las instrucciones en ensamblador que se han explicado en esta sesión.

  11. Intercambio o transferencia entre registros

    Nota: Los apartados que, como este, tengan un fondo más oscuro, son más difíciles y deben ser intentados solamente si se va bien de tiempo.

    Se puede transferir un registro a otro. La instrucción es TFR. Para transferir el contenido del registro A al B escribimos:

         tfr a,b
    En la transferencia pueden participar cualquiera de estos registros: D, X, Y, U, S, PC, A, B, CC, DP. No obstante, los registros que participen han de ser del mismo tamaño.

    La pareja de la instrucción TFR es la instrucción EXG (exchange, intercambio). Como su nombre indica intercambia el contenido de dos registros con las mismas condiciones que la instrucción TFR en cuando a los registros que pueden participar y su tamaño.

  12. Ejercicio

    Sin ayuda de posiciones de memoria o variables auxiliares, repetid el ejercicio anterior de modo que, en lugar de dos, sean diez los caracteres que se teclean antes de que se impriman al revés y acabe el programa devolviendo el código del primero.
  13. Órdenes de ensamblador vistas.

    LDx
    carga el registro x. x puede ser A, B, D, X, Y, U y S
    Flags afectados: NZ, V=0
    STx
    almacena el registro x. x puede ser A, B, D, X, Y, U y S
    Flags afectados: NZ, V=0
    TFR x,y
    transfiere el contenido de un registro a otro. x e y pueden ser D, X, Y, U, S, PC, A, B, CC o DP
    Flags afectados: ninguno, a no ser que CC sea destino de la transferencia
    EXG x,y
    intercambia el contenido de dos registros. x e y pueden ser D, X, Y, U, S, PC, A, B, CC o DP
    Flags afectados: ninguno, a no ser que CC participe en el intercambio


  14. Ó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


  15. LPEs.


© 2010 Guillermo González Talaván.