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

2 comentarios:

  1. hola buen dia, ya que menciona la eficiencia del programa.
    Para calcular la eficiencia de un programa en MIPS ¿es Frecuencia/tp o es Frecuencia/ciclosdereloj ? siendo tp el tiempo que tarda en ejecutarse el programa.

    Adrian

    ResponderBorrar
  2. Hola! Para calcular la eficiencia vas a usar la cantidad de ciclos de máquina que insume la totalidad del programa (sumando de la columna correspondiente del Set de instrucciones en el HC11). Luego sabiendo la frecuencia de reloj del procesador (en MHz) hacés el cociente entre ellos. Esto es una visión bastante simplista, pero para el caso sirve.

    Saludos!

    Jaír

    ResponderBorrar

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