cursos:ensamblador:avanzadas2

Diferencias

Muestra las diferencias entre dos versiones de la página.

Enlace a la vista de comparación

Ambos lados, revisión anterior Revisión previa
Próxima revisión
Revisión previa
cursos:ensamblador:avanzadas2 [14-01-2024 07:54] – borrado sromerocursos:ensamblador:avanzadas2 [24-01-2024 17:25] (actual) sromero
Línea 1: Línea 1:
 +====== Consideraciones avanzadas (II) ======
  
 +En este capítulo vamos a ver algunas consideraciones relativas a optimizaciones y mejoras que no hemos querido incluír dentro de sus respectivos capítulos para evitar confundir al lector con código o ideas complejas en un momento en que está aprendiendo los fundamenos..
 +
 +\\ 
 +===== Optimizaciones generales =====
 +
 +Veamos una serie de "optimizaciones" o construcciones de código algo más avanzadas que las que hemos visto hasta ahora para determinadas tareas:
 +
 +\\ 
 +**Copiar el flag Zero al Carry**
 +
 +<code z80>
 +    scf              ; CF = 1
 +    jr z, $+3        ; Si ZF=1, saltamos y dejamos CF=1
 +    ccf              ; Si ZF=0, al no saltar ejecutamos ccf
 +                     ; y hacemos CF=0
 +                     ; $+3 es la instrucción posterior al ccf
 +</code>
 +
 +**Copiar el flag de Carry al Zero flag**
 +
 +<code z80>
 +    ccf
 +    sbc a, a
 +</code>
 +
 +\\ 
 +**Realizar un ''NEG'' de un registro de 16 bits**
 +
 +<code z80>
 +    xor a
 +    sub Parte_baja_Registro
 +    ld Parte_baja_Registro, a
 +    sbc a, a
 +    sub Parte_alta_Registro
 +    ld Parte_alta_Registro, a
 +</code>
 +
 +Por ejemplo, para simular ''NEG HL'':
 +
 +<code z80>
 +    xor a
 +    sub l
 +    ld l, a
 +    sbc a, a
 +    sub h
 +    ld h, a
 +</code>
 +
 +
 +\\ 
 +===== Optimizaciones de bucles =====
 +
 +\\ 
 +==== Bucles de 16 bits optimizados ====
 +
 +
 +Es posible hacer loops de 16 bits que son tan rápidos como los de 8, tal y como se explica en la siguiente URL 
 +
 +  * https://map.grauw.nl/articles/fast_loops.php
 +
 +La forma normal de realizar loops de 16 bits sería algo como:
 +
 +<code z80>
 +    ld de, NNNN     ; veces a iterar
 +
 +loop:
 +    ; ... código de nuestro bucle ...
 +    dec de
 +    ld a, d
 +    or e
 +    jp nz, loop
 +</code>
 +
 +El problema es que tenemos que hacer operaciones costosas como el ''dec de'' (de 16 bits), perder el valor de A para poder comprobar si hemos llegado al final del bucle.
 +
 +Para eso, esta página nos propone aprovechar ''DJNZ''. Cuando usamos ''DJNZ'', el valor de B se decrementa hasta que alcanza 0. La comprobación de si el bucle ha acabado (si B == 0) se hace después del decremento, por lo que podemos aprovechar esto para hacer un bucle de 256 iteraciones si ponemos B a 0.
 +
 +Poniendo B a 0, tras ejecutar el código 1 vez, se decrementa B, con lo que pasa a valer $ff (255), así que nos aseguramos 255 ejecuciones más la inicial, total 256.
 +
 +Debido a esto, podemos hacer algo como lo siguiente: partimos el número de 16 bits a iterar en 2 registros e iteramos el primero un total de veces almacenado en el segundo. Cada vez que el primer registro llegue a cero, volveremos a iterar 256 veces, salvo la primera vez que iteramos el "módulo" de 256.
 +
 +Ejemplo:
 +
 +<code z80>
 +    ld b, 10        ; loop LSB
 +    ld d, 3         ; loop MSB => $050a => 522 iteraciones
 +
 +loop:
 +    ; ... código de nuestro bucle ...
 +    djnz loop
 +    dec d
 +    jp nz, loop
 +</code>
 +
 +Este bucle iterará un total de 522 veces ($050a).
 +
 +Ahora la pregunta es ¿cómo calculamos los valores que tenemos que poner en D y en B?
 +
 +Con el siguiente código:
 +
 +<code z80>
 +    ld de, NNNN     ; veces a iterar
 +    ld b, e
 +    dec de
 +    inc d           ; D y B listos para usar en nuestro bucle
 +</code>
 +
 +Es decir, el bucle completo sería:
 +
 +<code z80>
 +    ld de, NNNN     ; veces a iterar
 +    ld b, e         ;
 +    dec de          ; Calculate DB value (destroys B, D and E)
 +    inc d
 +loop:
 +    ; ... código de nuestro bucle ...
 +    djnz LOOP
 +    dec d
 +    jp nz, LOOP
 +</code>
 +
 +Si queremos usar B y C en lugar de D y B:
 +
 +<code z80>
 +    ld de, NNNN     ; veces a iterar
 +    ld b, e         ;
 +    dec de          ; Calculate DB value (destroys B, D and E)
 +    inc d
 +    ld c, d
 +
 +loop:
 +    ; ... código de nuestro bucle ...
 +    djnz LOOP
 +    dec c
 +    jp nz, LOOP
 +</code>
 +
 +Al respecto de los tiempos:
 +
 +El bucle de 16 bits estándar que hemos visto en el primer ejemplo utiliza 4 instrucciones para hacer el bucle, lo que suma un total de 28 ciclos de reloj o T-states por iteración.
 +
 +En cambio, el bucle con ''DJNZ'' + ''jp'' (o ''jr'' si el código del bucle es menor de 128 bytes) utiliza ''DJNZ'', que son sólo 14 ciclos de reloj, es decir la mitad. Sólo cada 256 iteraciones se ejecutará el bucle exterior utilizando un total de 25 ciclos de reloj.
 +
 +El bucle exterior se ejecuta de forma poco frecuente (1 vez cada 256 iteraciones), por lo que estamos ahorrando 14 ciclos en cada iteración y perdiendo uno cada 256.
 +
 +Hemos necesitado 25 ciclos de reloj para precalcular inicialmente los valores del bucle, pero como vemos, se han recuperado a partir de la tercera iteración del bucle si lo comparamos con un bucle de 16 bits normal.
 +
 +
 +\\ 
 +===== Uso de los Shadow Registers =====
 +
 +Cuando explicamos los //Shadow Registers//, dijimos que no eran excesivamente aprovechables puesto que no podíamos acceder a ellos directamente ni podían convivir junto a los registros normales, por lo que quedaban relegados a su uso en ISRs o rutinas "finales" que no tuvieran parámetros ni devolvieran valores, o que lo hicieran a través de variables en RAM. También vimos que es cierto que hay determinados casos donde sí podemos aprovechar los 2 pares de registros a la vez, y es usando la pila. Por ejemplo, podríamos trabajar con todos los pares de registros utilizando después ''PUSH'' en uno de ellos y después de un ''EXX'' un ''POP'' para recuperarlo donde nos convenga.
 +
 +Un ejemplo de uso de los registros alternativos es multiplicar vectores 16 bits por escalares, aritmética de punto flotante, como se puede ver en la siguiente URL:
 +
 +  * https://github.com/artyom-beilis/float16
 +
 +Finalmente, un caso muy interesante del uso de ''ex af, af<nowiki>'</nowiki>'' es el de preservar AF en alguna operación, para poder aprovechar ese flag después.
 +
 +Por ejemplo, supongamos que hacemos un scroll de un bitmap así:
 +
 +<code z80>
 +    rl (hl)
 +    dec l         ; este bloque RL + DEC se repite 16 veces
 +</code>
 +
 + En este caso, rotamos el byte apuntado por HL a la izquierda, de forma que el Carry Flag entra por la derecha con el nuevo pixel entrante en la pantalla.
 +
 +¿Qué ocurre si queremos scrollear 2 pixeles, y necesitamos no perder el Carry Flag del primer RL cuando hacemos el segundo? Pues que podemos preservarlo dejándolo en AF' y recuperándolo después:
 +
 +<code z80>
 +    rl (hl)
 +    ex af, af'      ; Nos guardamos AF en AF'
 +    rl (hl)
 +    ex af, af'      ; Recuperamos AF desde AF'
 +    dec l           ; este bloque se repite 16 veces
 +</code>
 +
 +
 +\\ 
 +**[ [[.:indice|⬉]] | [[.:avanzadas1|⬅]] | [[.:asmz88dk|➡]] ]**
  • cursos/ensamblador/avanzadas2.1705218876.txt.gz
  • Última modificación: 14-01-2024 07:54
  • por sromero