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

lunes, 18 de julio de 2016

¿Par o impar? ¿Cero o no cero? ¿Multiplo de algo? ¿Bit o no bit? (parte 3)

Hasta este momento hemos visto la mecánica de los flags del CCR y los branch. Verificamos que no cambian su estado ante los branch, permitiéndonos incluir varios consecutivos en un programa. Hemos considerado cómo determinar si un operando o resultado de operación es cero o negativo. Ahora veamos en detalle cómo discernir si un operando es par o impar.

La representación de magnitudes en binario sigue las reglas de un sistema numérico posicional. Por tanto podemos descomponer el número segun el peso de la posición de cada dígito. Sin entrar en mayores detalles recordamos los pesos de un número entero sin signo de 8 bits:
128 64 32 16 8 4 2 1
y si se tratase de un número negativo los pesos son:
-128 64 32 16 8 4 2 1
Para mayor información recomiendo leer Como expresar numeros negativos sin que se te vuele el peluquin

Observe que solo hay un peso de magnitud impar: el LSB (less significant bit, o bit menos significativo). Se trata del bit más a la derecha (si es que puede asignarse izquierda y derecha a los bits!). A diferencia del MSB, que se copia en el flag N, el LSB no se copia en ningun flag. Evidentemente para la gente que diseñó el HC11 no es un bit tan glamoroso.  ¿Cómo podríamos evaluar su estado? Queda claro que sin importar el resto de los bits que compongan una palabra binaria, alcanzaría con saber el estado del LSB para determinar si es par o impar. Empecemos por resolverlo en 8 bits.

¿Cómo podemos determinar el valor del LSB de un byte? Hay varias formas:
  • desplazamiento/rotación
  • AND lógica
  • dividir por dos y evaluar el resto
Veremos que las dos primeras formas son mucho más elegantes y sencillas que la tercera. ¿Raro no? Porque para un programador de alto nivel saber si un número es par se reduce a dividirlo por dos y observar el resto... pero aquí esa es la peor solución. Vayamos por partes, como dijo Jack el Destripador...

Desplazamiento/rotación

La primer forma de evaluar el LSB que veremos es haciendo que vaya a Carry. Luego podremos usar algun branch que use Carry como criterio (BCS branch if carry set; BCC branch if carry clear). Tenemos a nuestro alcance tres familias de instrucciones:
LSR: logical shift right
ASR: arithmetic shift right
ROR: rotate right
Las tres familias permiten trabajar directamente con operandos en memoria o hacerlo con los acumuladores A y B. Incluso LSR permite operar con el acumulador D (en 16 bits). Todas ellas copian el valor del LSB a Carry. Lo que las diferencia es la forma en que completan el MSB, pero para el caso de determinar si un número es par o impar, convengamos en que no nos interesa.
Supongamos que la consigna es recorrer un vector y contar los números pares, podríamos entonces hacer algo así (extracto el código):

        CLR CuentaP
        ...
        LDAA 0,x
        LSRA
        BCS impar
        INC CuentaP
impar

Si lo que necesitásemos fuese la cantidad de impares, bastaría con cambiar BCS por BCC. En este ejemplo puntual daría lo mismo usar ASRA y RORA. Tal vez la diferencia más significativa sea con esta última, ya que la rotación nos permitiría volver al número original con solo ejecutar una rotación en el sentido inverso (ROLA).

AND lógica

Una segunda forma de resolver el problema de examinar el LSB sería empleando la operación de disyunción o Y (and) lógica. En bajo nivel podemos realizar operaciones lógicas con mucha facilidad, y la verdad es que muchos lenguajes alto nivel también lo permiten... pero no solemos estar al tanto de ello. 
Sabemos que la disyunción solo genera un valor verdadero cuando ambas proposiciones son verdaderas. Si lo aplicamos a palabras de 8 bits, cada par de bits del mismo peso de las dos palabras se evaluaría simultáneamente. Llamando a los operandos A y B y a sus bits a7, a6, a5..., a0 y b7, b6, b5... b0, el resultado de la operación AND (Y) se compondría bit a bit de esta forma:
y7 = a7 AND b7
y6 = a6 AND b6
y5 = a5 AND b5
y4 = a4 AND b4
y3 = a3 AND b3 
y2 = a2 AND b2
y1 = a1 AND b1
y0 = a0 AND b0


Si lo que deseamos es "despejar" el valor del LSB, podríamos realizar un AND contra una palabra binaria que tenga todos sus bits en cero, salvo el LSB: 0000 0001. De esta forma cualquier operación AND contra 0000 0001 daría por resultado cero si el LSB del operando era cero y uno si el LSB del operando era uno (puse un espacio entre nibbles para claridad).
Veámoslo en assembler:

        CLR CuentaP
        ...
        LDAA 0,x
        ANDA #%00000001 ; podría usar tambien #$01 o #01
        BNE impar       ; si no es cero es porque era impar
        INC CuentaP
impar 

 
¿Fácil, verdad? Dado que el número 1 es igual en binario, hexadecimal, decimal u octal, en este caso lo importante es recordar que se usa modo inmediato y no tanto el sistema de numeración. Me gusta indicarlo en binario para mayor claridad, pero es solo cuestión de gustos. (Gracias Martin Greco por avisarme que tenía un error. Eso pasa por copiar-pegar, menos mal que hay amigos que avisan.)

Dividir por dos y evaluar el resto

Este método se hace complejo en primer lugar porque el HC11 solo permite división de números de 16 bits. Por tanto debemos llevar el operando de 8 a 16 bits. Además utiliza el registro IX para guardar el resto de la división, así que si estamos recorriendo un vector debemos usar el índice IY a fin de dejar libre IX (o guardar su valor en otro lado y luego recuperarlo). Una vez hecha la división todavía debemos hacer una comparación contra el resto, expresado en 16 bits. El CCR se actualiza de acuerdo al resultado de la división y no del resto, por ello debemos hacer una comparación adicional.
Quedaría algo así (es un extracto del código como los anteriores):

        CLR CuentaP
        ...
        LDAB  0,y     ; cargo en la parte baja de D
        CLRA          ; y limpio la parte alta
        LDX   #02     ; en IX guardo el divisor
        IDIV          ; divide D/IX, resultado -> IX resto -> D
        CPD   #0001
        BEQ  impar
        INC  CuentaP impar
Más allá de que es un código más extenso, nos vemos obligados a emplear más registros que en los anteriores y la eficiencia es más baja (observe la cantidad de ciclos de reloj que insume ejecutar IDIV). De ahí que las dos primeras alternativas sean preferibles.

En la próxima y última entrega de esta serie veremos cómo determinar si un número es múltiplo de otros (con restricciones). También está en la cocina dorándose un post sobre shift y rotate...

lunes, 20 de junio de 2016

¿Par o impar? ¿Cero o no cero? ¿Multiplo de algo? ¿Bit o no bit?

Existen herramientas y técnicas en bajo nivel con las que no se cuenta -o no se conocen popularmente- en alto nivel. Es por ello que para el programador que viene del alto mundo suponga un reto hacer cosas que en el bajo fondo de assembler ... son muy sencillas (tanto como lo puede ser resolver cosas en assembler).
Para comprender lo que sigue a continuación es necesario que tenga un manejo medianamente fluido del sistema binario. Los ejemplos serán de operandos en 8 bits, aunque por supuesto las técnicas aplicarán a palabras de cualquier longitud.
La unidad aritmético-lógica (ALU) cuenta con una palabra de estado donde cada bit corresponde a una bandera o indicador (flag) que se actualiza en función de la última operación. 
A esta palabra de estado se la conoce como CCR (Condition Code Register), Status Word, etc. En el Motorola 68HC11 tiene una longitud de 8 bits, en el gráfico a continuación vemos el significado de cada bit:

¿Cuándo se actualizan los bits de la palabra de estado? La respuesta -citando a una autoridad en la materia- es: depende. ¿De qué depende? De la instrucción. En el set de instrucciones encontramos columnas con los flags de la palabra de estado y una indicación del efecto de la instrucción en ellos. Tomemos como ejemplo la primera de las instrucciones del set: ABA:
En este caso cada flag tiene un guión o un delta (eso que parece un triángulo). El guión significa que la ejecución de la instrucción no cambia el estado del flag, por tanto mantiene el valor que tenia. El delta indica que la ejecución actualizará el flag según el resultado de la operación. Por tanto adoptará el valor 0 o 1 según lo que resulte de la operación. Algunas instrucciones provocan que un determinado flag adopte el valor 0 o 1 sin importar los operandos. Observe por ejemplo la instrucción CLR:
Al ejecutar CLR los valores de N, Z, V y C serán siempre los mismos. Este es un caso particular en el que cuatro flags podríamos decir que se actulizan de forma determinística (sin importar el operando), la gran mayoría de las instrucciones cambian los valores de los flags en función del resultado, por lo que pueden adoptar el valor 0 o 1.
Nos centraremos en dos de los flags: Z (cero) y N (negative). El primero será 1 si la ejecución de la instrucción dio por resultado cero. En el caso de las instrucciones aritméticas es sencillo de comprender: si una suma o resta dan cero, entonces Z=1. Lo mismo ocurre si realizamos una comparación, ya que no se trata de otra cosa que una resta de la que no nos importa el resultado. Pero note que las instrucciones de carga (Load) también actualizan Z, como podemos ver en LDAA:
Por tanto al ejecutar alguna instrucción de carga (LDAA, LDAB, LDD, LDX, LDY) lo que se carga -sea en A, B, D, IX, IY- es cero, el flag Z tomará el valor 1. No es necesario comparar lo cargado con cero para actualizar el flag. 
Entonces, ¿cómo determinar si un byte o palabra es cero? Basta con cargarlo en un acumulador y emplear el flag Zero para saberlo. Si estuviéramos recorriendo un vector y contando los ceros en "ceros" podríamos por ejemplo hacer algo así:

        LDX dirIni
        LDAA 0,x
        BNE noCero
        INC ceros
noCero  ...


Si la consigna fuera contar los que no son cero, alcanza con cambiar BNE por BEQ en esta porción de programa de ejemplo.

En la próxima veremos como seguir contestando las preguntas trascendentales que hemos planteado.