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

6 comentarios:

  1. hola profe diculpe que lo moleste cuando cargo el segundo elemento en ldab no tendria que ser 1,x?

    ResponderBorrar
    Respuestas
    1. No es molestia! Fijate que antes de cargar el segundo elemento hay un INX. El algoritmo es así: carga el primero y lo toma como el "menor". Luego incrementa IX y empieza a ciclar para comparar a partir del siguiente todos contra el menor. Cuando encuentra uno más chico, actualiza el menor (que lo mantiene en el AccA). Se entiende?

      Borrar
  2. hola profe, para responder el desafió de los valores signados alcanzaría con cambiar el BLS por un BLE para buscar el menor o un BGE para buscar el mayor?

    ResponderBorrar
  3. Buenas Jair,
    Probé resolver el ejemplo "...la clásica búsqueda del menor valor dentro de un vector..." y quisiera saber si de esta manera también es válida:
    ORG $0000
    DIRINI RMB 2
    DIRFIN RMB 2
    MENOR RMB 1

    ORG $C000
    CLR MENOR
    LDX DIRINI
    LDAA 0,X
    STAA MENOR
    LOOP INX
    LDAA 0,X
    CMPA MENOR
    BCC ES_MAYOR
    STAA MENOR
    CPX DIRFIN
    BLO LOOP
    ES_MAYOR CPX DIRFIN
    BLO LOOP
    FIN BRA FIN

    (Queda todo apretado en la vista previa, espero que se logre entender, lo probé en el simulador pero no me queda claro)
    Lorenzo

    ResponderBorrar
    Respuestas
    1. Hola! Si, es correcto. Las dos lineas que siguen a STAA MENOR las podrias eliminar. Son redundantes, ya que estas haciendo la misma comparacion luego (asi que si no salta, se hace dos veces). Bien ahi!

      Borrar

Aunque no es obligatorio, es de buena educación firmar los comentarios.