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

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.

lunes, 16 de mayo de 2016

Direccionamiento (continuacion). Todo es relativo... bah, no siempre.

La entrada anterior consideró los modos de direccionamiento directo, extendido, inmediato (vagamente, en este post lo desarrollo mejor) e indexado. Llegó el momento de abordar el más sencillo (inherente) y el más difícil (relativo). Aquí viene lo bueno jóvenes...
El modo relativo es el empleado en los saltos. De hecho en el HC11 las instrucciones de la familia BRANCH (no confundir con brunch, eso es una mezcla de almuerzo y desayuno) se manejan solo por direccionamiento relativo, y este modo solo está disponible para los branch. Así que es imposible considerar uno sin el otro. Van juntos, como el cafe con leche y las medialunas.
Las instrucciones de la familia BRANCH (se podría traducir bifurcación) nos permiten alterar el orden de ejecución del programa. CPU continuamente repite el ciclo fetch-decode-execute, por lo tanto al terminar con una instrucción ejecuta la que sigue, y así ad infinitum (o hasta que la apaguen).
Necesitamos comprender bien este hecho para pasar al branch... así que repasémoslo.
Al mismo momento de leer una instrucción, el Program Counter se incrementa, conteniendo en él la dirección del operando que acompaña esa instrucción, o de la siguiente instrucción. Veamoslo en un ejemplo, supongamos esta porción de programa:

ORG  $C000
LDAA  #$03
LDAB   $04

Al ensamblarlo el resultado sería (sugiero tener el set de instrucciones a mano para seguir la explicación):


$C000 86 03
$C002 D6 04

Observe que la instrucción LDAB está en modo directo, por tanto en el acumulador B cargará el contenido de la dirección de memoria 0004. Pero la instrucción LDAA utiliza modo inmediato, por tanto lo que se cargará en el acumulador A es el número 03 (en hexadecimal... y en decimal es igual), y no el contenido de la dirección de memoria 03. Pero ¿De dónde sacó CPU la dirección del operando 03? ¿cómo determina que lo que debe copiar a B es el número 03 en lugar del contenido de la posición de memoria 03?
Al programar le indicamos esto mediante el uso del numeral # para señalar modo inmediato. Este modo implica que lo que acompaña al código de operación es "el" dato. No se trata de una dirección ni una referencia, andamos sin vueltas, ¡es el dato! ¿Cómo lo carga CPU al acumulador?

Veamos nuevamente la instrucción en hexadecimal:

$C000 86 03

¿Cómo se produce el ciclo de instrucción? En primer lugar el PC tiene la dirección $C000. Realiza el fetch y por tanto carga el código de operación 86 en el Instruction Register (IR). Al mismo momento PC se incremente y adopta el valor $C0001. La unidad de control (UC) decodifica el 86 y determina que lo que debe hacer es copiarse el contenido actual del PC al Memory Address Register (MAR). Efectúa un READ de la memoria y lo que toma del Memory Buffer Register (MBR) lo copia al acumulador B. ¿Logró identificar de dónde se toma la dirección del operando? ¡Del PC! Por eso al modo inmediato también se lo llama relativo al PC. La dirección real del operando (DRO) está en el PC.
Ahora bien, una vez que leyó el contenido del PC con el valor $C0001.. ¿qué ocurrió con este registro? Se incrementó nuevamente. De ahí que al concluir el ciclo de esta instrucción el PC queda con el valor $C0002, que es el de la instrucción siguiente. Es importante recordar que el PC se incrementó antes de producirse la búsqueda del operando en memoria. El PC no se incrementa después de haber copiado del MBR al AccA, sino después de haber sido leído.
Habiendo completado la instrucción LDAA #$03, el PC tendrá el valor $C002. Por tanto la UC leerá el valor de la memoria en $C002 y lo copiará en el IR, en este caso el código de operación D6. Ahora al decodificarlo determina que se está empleando direccionamiento directo, por tanto lo que está apuntado por el PC ($C003) es el offset de la página cero. Se copia el PC al MAR, se lee de memoria (el valor 04) y ahora se completa con el número de página para formar la DRO: $0004. Esa dirección se coloca en el MAR y se lee de memoria el operando que se cargará en el acumulador B. Al momento el PC ya está incrementado y tiene el valor $C004, correspondiente a la siguiente instrucción (que no incluimos en el ejemplo).
¿Cómo haríamos para alterar la secuencia del programa? Hemos visto que luego de cada uso del PC (típicamente para copiarlo al MAR y de ahí efectuar la lectura de memoria) el valor del mismo se incrementa. Si quisiéramos que no se ejecute la siguiente instrucción tenemos que modificar el valor del PC antes de que se inicie el siguiente ciclo fetch-decode-execute. Aquí es donde entran a jugar las BRANCH.
Las instrucciones de bifurcación operan sobre el valor del PC. A excepción del salto incondicional (BRA = branch always) y el salto "nunca" (BRN = branch never, o sea "nunca salte") evalúan una condición y si se cumple aplican un offset al valor del PC. Ese offset se representa con un número de 8 bits contemplando negativos en complemento a la base. Por lo tanto cuando la condición del salto se cumple se suma el offset al PC y de esa forma se salta hacia adelante (si el offset es positivo) o hacia atrás (si es negativo).
Cuando empleamos etiquetas en assembler y luego las utilizamos con los branch, dejamos en manos del ensamblador el cálculo del offset. Pero es importante entender cómo funcionan, por ejemplo para comprender por qué los saltos tienen límites.
Si utilizamos 8 bits para representar números enteros y admitimos negativos en complemento a la base (Cb), el rango es de -128 a 127. En otras palabras, desde una instrucción de branch podemos saltar 127 bytes hacia adelante (sumando al PC) o 128 hacia atrás (restando al PC). Peeeeero, aquí el truco: dado que el valor del PC al que se aplica el offset es el que corresponde a la siguiente instrucción -luego de haber leído el branch y el offset- entonces tenemos que descontar esos dos bytes al hacer la cuenta. De tal forma cuando queremos que salte "en el lugar" para formar un bucle infinito, lo que por ejemplo hacemos al cerrar los programas así:

FIN   BRA   FIN

En realidad tenemos que decirle que salte 2 hacia atrás. Porque el PC ya se habrá incrementado pasando por las direcciones de la instrucción BRA y del offset (que es siempre de un byte). Por eso al ensamblarla queda así:
20 FE
Siendo FE el número -2 en Cb. Veamos algunos saltos en el ejemplo del triángulo de un post anterior:
Marqué los saltos repitiendo el color cuando apuntan a la misma etiqueta. Notar que aunque tres veces salta a la etiqueta isos, los tres saltos tienen distinto offset. ¿Por qué? Porque desde esos tres puntos del programa la cantidad de bytes que hay que sumar al PC para que llegue a la dirección $C01B es distinta. En el primer caso es la instrucción:
$C009 27 10
El código de operación del salto BEQ es 27. El offset es 10. Dado que es un número hexadecimal, corresponde al decimal 16. Si sumamos los valores hexadecimales C00B + 10 el resultado es el la dirección de memoria de la etiqueta isos=C01B.
¿Por qué sumé C00B al offset (10) en vez de C009, que es la dirección de la instrucción de salto? Porque el valor del PC luego de realizar el ciclo fetch-decode para BEQ isos (27 10) será C00B, y no C009. Por tanto el offset se aplica al PC incrementado, el PC que apunta a la siguiente instrucción.
Veamos otro ejemplo más fácil de seguir: el salto BRA LISTO, copio la porción de programa desde esa línea hasta la de la etiqueta listo:

$C019 20 02       bra listo
$C01B C6 49 isos  ldab #'I
$C01D D7 03 listo stab tipoT

Cuando la UC ha leído el código de operación 20 y el offset 02 que la acompaña, el valor del PC es C01B. Aun no ejecutó el salto pero ya el PC está incrementado. En este caso el salto se ejecuta siempre (es incondicional), pero si fuera el caso CPU evaluaría la condición correspondiente. Entonces en caso de ejecutar el salto, aplica el offset (02) al valor del PC, el valor ya incrementado (C01B). Por tanto esa cuenta C01B+02 da por resultado la dirección C01D, que corresponde a la etiqueta listo.

El límite a los branch que impone el rango antes mencionado [-128;126] se expresa en bytes, o sea en posiciones de memoria. Si tomamos un promedio de dos bytes por instrucción, significa que tenemos la posibilidad de realizar saltos aproximadamente 60 instrucciones hacia adelante o hacia atrás. ¿Qué pasa si no es suficiente? Podemos recurrir a realizar otra clase de saltos, JUMP, que serán motivo de alguna otra publicación del blog. (Al leer JUMP pueden pensar en el tema de Van Halen, un nerd normalmente lo hace).















¿Qué hay del modo inherente? Contamos con muchas instrucciones que emplean operandos en registros. Tal es el caso por ejemplo de:

¿es correcto decir que no usan direccionamiento? Si y no. Estas instrucciones emplean lo que Motorola llama direccionamiento inherente, en los libros lo encontramos como "modo implícito". Si vamos a hilar finito no se trata de un modo de direccionamiento con todas las letras (podría ser un mdo dirccionamto) porque la misma definición de los modos de direccionamiento refiere a traer operandos de memoria... y si ya está en un registro no se lo trae de memoria.
Estamos en un caso en el dependiendo quién lo diga se trata de la verdad o de "la verdad". 


(El libro que tiene Hutz podría ser perfectamente el manual de Motorola).
Respecto a esto, hay que tener en cuenta -como bien aclaró el Ingeniero F. S.- que lo que alguna bibliografía (Murdocca) llama "modo registro" se asemeja más a lo que en el plano teórico planteamos como modo indexado (porque no refiere a operandos en registros sino a que los la dirección base y/o el offset se hallan en registros). 

En resumen, hemos visto:
* cómo funciona el direccionamiento relativo, cómo se calculan los saltos y qué limitaciones tiene
* a qué llama Motorola modo inherente (nosotros modo implícito)

Trivia: ¿para qué les parece que existe la instrucción BRN (BRANCH NEVER)?
 

domingo, 15 de mayo de 2016

Direccionamiento: ¿dónde estás operando de mi vida que no te puedo encontrar?

Al programar constantemente hacemos referencia a los operandos o variables que usamos. En alto nivel poco (o nada) nos preocupa dónde se ubican tales variables y cómo hará CPU para ubicarlas. Frecuentemente no tenemos mucha idea de cómo lo está logrando, a excepción del manejo de punteros en C y cosas por el estilo. Sin embargo, en el bajo nivel debemos especificar de qué forma CPU deberá obtener los operandos. Aun cuando utilicemos un ensamblador -por ejemplo el THRSim- el control del modo de direccionamiento empleado recae en el programador.
El microcontrolador Motorola 68HC11 dispone de estos modos de direccionamiento:
* extendido
* inmediato
* directo
* indexado
* relativo
* inherente

En el set de instrucciones podemos ver qué modos soporta cada instrucción, por ejemplo LDAA:
En este caso soporta modo inmediato (IMM), directo (DIR), extendido (EXT) e indexado tanto contra el registro IX (IND, X) como IY (IND, Y). Es de notar que el Opcode (código de operación) para modo de direccionamiento es totalmente distinto. (Columna a la derecha de la marcada en rojo) ¿Qué significa? Que para la unidad de control efectuar la misma operación -para el ejemplo cargar el acumulador A desde memoria- en distintos modos de direccionamiento representa realmente distintas instrucciones.
La columna Operand indica el formato del (o los) operandos de la instrucción. Para el modo inmediato (IMM) se trata de 8 bits, representados por dos letras "ii". Para el modo extendido (EXT) son 16 bits, por lo que se presenta con cuatro letras en dos pares "hh ll". Las letras H y L están señalando que se espera un operando de 16 bits donde primero se indica la parte alta H y luego la parte baja L.
Cuando se emplea direccionamiento indexado la misma instrucción indica si se haremos referencia al índice IX o a IY. Lo que acompaña el código de operación es el offset (por es las letras ff). Se puede apreciar que el offset será de 8 bits.
¿Por qué necesitamos comprender bien esto? Bueno, ¿sabría indicar cuál de las siguientes instrucciones son válidas?

LDAA #257
LDAA -1,X
LDAA 258,X
LDX #$2000

Para saber la respuesta busque en el set de instrucciones los últimos cuatro flags afectados por la instrucción MUL. Las cuatro instrucciones se corresponden con ellos (en el orden en que están). Los que tengan un delta indican las instrucciones correctas. (No hay nada particular con MUL, solo que no quería poner las respuestas aquí).

El modo directo es en realidad un modo paginado, solo que al no existir un registro de página, es aplicable solo a la página cero. Dividir la memoria en páginas es un mecanismo muy útil para separar lo que cada proceso puede acceder. En el caso del HC11 no manejamos multiprocesamiento, y tampoco podemos acceder via modo paginado a otra parte de la memoria que no sean los primeros 256 bytes (desde 0000 a 00FF). ¿Cuál es la ganancia entonces? El caso es que la página cero corresponde mayormente a memoria R/W, por tanto es el lugar donde alojamos las variables. Resulta de mucha utilidad acceder a esta porción de memoria con más facilidad. ¿Dónde radica la facilidad? Veámoslo en un ejemplo.
Se trata de una suma de dos numeros de 8 bits, nada extraño:

               ORG     $0000  
resultado      rmb     2      
op1            rmb     1      
op2            rmb     1      

               ORG     $C000      
               clr     resultado   ; Hresultado <= 0
               ldaa    op1         ; A <- op1
               adda    op2         ; A = A + op2
               staa    resultado+1    ; parte baja de resultado=A
               bcc     sale        ; Si no hay carry, chau
               inc     resultado   ; Else, Hresultado++
sale                     
               bra     sale        ; se fini
                        


Aunque no lo parezca estamos determinando qué modo de direccionamiento emplear (al menos hasta cierto punto). Cuando ensamblamos el resultado es este:
Dado que ubicamos los datos a partir de la página cero (observar que antes de la reservas de memoria dice ORG $0000), al acceder a op1, op2 y resultado se puede aprovechar el modo directo. Note que la instrucción
LDAA op1
se ha ensamblado como
96 02
Siendo 96 el código de operación en modo directo. 02 es la dirección del op1 (la parte baja, porque la parte alta es 00, lo que justamente implica el modo paginado a página cero A.K.A. directo).

Se repite lo mismo con las instrucciones ADDA y STAA. Sin embargo en el caso de CLR y INC no fue posible. ¿Por qué? Observe en el set de instrucciones los modos de direccionamiento que soportan estas instrucciones:


(mi set de instrucciones es mágico, por eso CLR está a continuación de INC).
¿Cuál es el que no tenemos? ¡Modo directo! Por eso a falta de directo el ensamblador utiliza modo extendido.
¿Qué pasaría si movemos las reservas de memoria de la página cero a cualquier otra página de R/W? Veamos...

Edité la directiva de origen inicial ORG, ahora es ORG $0100. ¿Resultado? Observer que todas las referencias a memoria, incluidas las de LDAA, ADDA y STAA ahora están en modo extendido. ¿Razón? No disponemos de modo directo para la página 01.


Nos quedan los modos inherente y relativo para un siguiente post. Los dejo con un mensaje de interés general. Fumar es malo, muy malo... sino pregúntenle a humosvaldo ... (gracias @JuanM96 por el aporte)