cursos:ensamblador:habituales

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:habituales [24-01-2024 11:53] sromerocursos:ensamblador:habituales [02-02-2024 18:39] (actual) – [Comparaciones de 8 bits] sromero
Línea 1: Línea 1:
- 
 ====== Operaciones habituales ====== ====== Operaciones habituales ======
  
Línea 6: Línea 5:
 Por ejemplo, las comparaciones de 8 y 16 bits son básicas y tendremos que usarlas decenas (sino cientos) de veces a lo largo de nuestro programa para implementar la lógica no sólo de bucles sino también de condiciones como "//¿El jugador ha colisionado con un objeto, comparando las coordenadas de ambos?//". Por ejemplo, las comparaciones de 8 y 16 bits son básicas y tendremos que usarlas decenas (sino cientos) de veces a lo largo de nuestro programa para implementar la lógica no sólo de bucles sino también de condiciones como "//¿El jugador ha colisionado con un objeto, comparando las coordenadas de ambos?//".
  
-También veremos construcciones u optimizaciones habituales en ensamblador, como utilizar ''xor a'' en lugar de ''ld a, 0'' para poner a 0 el valor del registro A. Estas "construcciones" o "trucos" se utilizan mucho en los programas en ensamblador porque ahorran memoria y además se suelen ejecutar más rápido. Se utilizan mucho hasta el punto en que son casi la "forma estándar" de realizar ese tipo de operaciones, y no es habitual ver un ''ld a, 0'' en ningún programa, ya que no tiene sentido utilizar 2 bytes y 7 ciclos de reloj para hacer algo que puedes hacer con ''XOR'' usando 1 sólo byte y 4 ciclos. Ya no es sólo una cuestión de que el programa sea más rápido, sino de que ocupe mucho menos y por tanto podamos meter más código en la limitada memoria del Spectrum.+También veremos construcciones u optimizaciones habituales en ensamblador, como utilizar ''XOR A'' en lugar de ''LD A, 0'' para poner a 0 el valor del registro A. Estas "construcciones" o "trucos" se utilizan mucho en los programas en ensamblador porque ahorran memoria y además se suelen ejecutar más rápido. Se utilizan mucho hasta el punto en que son casi la "forma estándar" de realizar ese tipo de operaciones, y no es habitual ver un ''LD A, 0'' en ningún programa, ya que no tiene sentido utilizar 2 bytes y 7 ciclos de reloj para hacer algo que puedes hacer con ''XOR'' usando 1 sólo byte y 4 ciclos. Ya no es sólo una cuestión de que el programa sea más rápido, sino de que ocupe mucho menos y por tanto podamos meter más código en la limitada memoria del Spectrum.
  
  
Línea 12: Línea 11:
 ===== Comparaciones de 8 bits ===== ===== Comparaciones de 8 bits =====
  
-Las comparaciones de valores 8 bits son situaciones extremadamente habituales en nuestros programas. En múltiples ocasiones tendremos que verificar si el valor de un determinado registro es igual, distingo, menor, mayor, menor igual o mayor igual que el valor de otro registro o que un valor inmediato.+Las comparaciones de valores de 8 bits son situaciones extremadamente habituales en nuestros programas. En múltiples ocasiones tendremos que verificar si el valor de un determinado registro es igual, distingo, menor, mayor, menor igual o mayor igual que el valor de otro registro o que un valor inmediato.
  
 Como ya vimos en el capítulo dedicado a las instrucciones básicas, las comparaciones se basan en **restas** (''SUB'' y ''SBC'') y en cómo estas afectan a los **flags** (registro F) del procesador. Las comparaciones son tan importantes, que hay una instrucción dedicada a realizar una resta descartando el resultado (evitando así alterar un registro innecesariamente), pero afectando a los flags. Esa instrucción es ''CP'' de "ComPare". Como ya vimos en el capítulo dedicado a las instrucciones básicas, las comparaciones se basan en **restas** (''SUB'' y ''SBC'') y en cómo estas afectan a los **flags** (registro F) del procesador. Las comparaciones son tan importantes, que hay una instrucción dedicada a realizar una resta descartando el resultado (evitando así alterar un registro innecesariamente), pero afectando a los flags. Esa instrucción es ''CP'' de "ComPare".
Línea 37: Línea 36:
 \\  \\ 
  
-Así, tras un ''CP n'' podemos utilizar los flas de la siguiente forma:+Así, tras un ''CP n'' podemos utilizar los flags de la siguiente forma:
  
 \\  \\ 
-   * **A == N** (A igual n): Si se activa **ZF** (flag de Zero), es porque el resultado de la resta es 0, es decir, A < n son iguales.+   * **A == N** (A igual n): Si se activa **ZF** (flag de Zero), es porque el resultado de la resta es 0, es decir, A y N son iguales.
  
    * **A != n** (A distinto de n): Si no se activa **ZF** (flag de Zero), es porque el resultado de la resta no es 0, es decir A y n son diferentes.    * **A != n** (A distinto de n): Si no se activa **ZF** (flag de Zero), es porque el resultado de la resta no es 0, es decir A y n son diferentes.
  
-   * **A < n** (A menor que n): Si no era igual, y se activa **CF** (flag de Carry), es porque el resultado de la resta es negativo, es decir, A es menor que B.+   * **A < n** (A menor que n): Si no era igual, y se activa **CF** (flag de Carry), es porque el resultado de la resta es negativo, es decir, A es menor que B. No es necesario comprobar primero el valor del ZF, si CF está activo, entonces A es menor que N, siempre.
  
    * **A > n** (mayor que): Si no era igual, y no se activa **CF** (flag de Carry), es porque el resultado de la resta es positivo, es decir, A es mayor que B.    * **A > n** (mayor que): Si no era igual, y no se activa **CF** (flag de Carry), es porque el resultado de la resta es positivo, es decir, A es mayor que B.
Línea 51: Línea 50:
 \\  \\ 
  
-La igualdad requiere sólo verificar el flag de Zero, mientras que el resto de comparaciones requiere utilizar también el flag de Carry:+La igualdad requiere sólo verificar el flag de Zero, la comparación "menor que" requiere sólo verificar el flag de Carry, y el resto de comparaciones requiere verificar el estado de ambos:
  
 <code> <code>
Línea 58: Línea 57:
 =       =>     Z=1 =       =>     Z=1
 !=      =>     Z=0 !=      =>     Z=0
-<       =>     Z=0, C=1+<       =>     C=1
 >       =>     Z=0, C=0 >       =>     Z=0, C=0
 <=      =>     Z=1, C=1 <=      =>     Z=1, C=1
Línea 75: Línea 74:
 </code> </code>
  
-Veamos ejemplos de código, comparando A con 50 (podemos usar para los saltos tanto ''jp'' como ''jr''):+Veamos ejemplos de código, comparando A con 50 (podemos usar para los saltos tanto ''JP'' como ''JR''):
  
-Para comparar si **A == 50**, simplemente comprobamos ZF:+Para comparar si **A == 50** o si **A != 50**, simplemente comprobamos ZF:
  
 <code z80> <code z80>
Línea 90: Línea 89:
     ; igual a 50     ; igual a 50
     ; (... codigo para caso a == 50 ...)     ; (... codigo para caso a == 50 ...)
- 
-fin_comparacion: 
-</code> 
- 
-Para comparar si **A != 50**, de nuevo comprobamos ZF: 
- 
-<code z80> 
-    cp 50 
-    jr z, distinto_de_50 
- 
-    ; igual a 50 
-    ; (... codigo para caso a == 50 ...) 
-    jr fin_comparacion 
- 
-distinto_a_50: 
-    ; (... codigo para caso a != 50 ...) 
  
 fin_comparacion: fin_comparacion:
Línea 145: Línea 128:
 <code z80> <code z80>
     cp 50     cp 50
-    jp ncmenor_o_igual_que_50 +    jp cmenor_que_50 
-    jp nz, menor_o_igual_que_50     ; => Si no salto, es que C = 1+    jp nz, mayor_que_50             ; => Si no salto, es que C = 0
                                     ;    Ahora o es '=' o es '<' segun Z                                     ;    Ahora o es '=' o es '<' segun Z
  
-    ; mayor o igual que 50+    ; igual que 50 
 +    ; (... codigo para caso a == 50 ...) 
 +     
 +    jr fin_comparacion 
 + 
 +mayor_que_50:
     ; (... codigo para caso a > 50 ...)     ; (... codigo para caso a > 50 ...)
     jr fin_comparacion     jr fin_comparacion
  
-menor_o_igual_que_50+menor_que_50
-    ; (... codigo para caso a <50 ...)+    ; (... codigo para caso a < 50 ...)
  
 fin_comparacion: fin_comparacion:
 </code> </code>
  
- Otra opción para las comparaciones de ''>='' y ''<='' es comparar sin igualdad con el número anterior o el siguiente, en este ejemplo, con 49 o con 51:+ Como se puede ver en el código, tenemos que añadir un ''JP NZ'' o ''JP Z'' tras el chequeo del carry para comprobar si es "menor o igual". Si queremos ahorrar varios t-estados, podemos hacer las comparaciones de ''>='' y ''<='' sin igualdad, pero utilizando el número anterior o el siguiente. Repitamos el ejemplo anteriorpero con 51 (''>=''o con 49 (''<=''):
  
 <code z80> <code z80>
-    ; comparamos con valor +1 y entonces sí que podemos hacer NC+    ; comparamos con valor +1 y entonces sí que podemos hacer JP NC
     ; y comprobar "mayor_que_50" mediante "mayor_o_igual_que_51".     ; y comprobar "mayor_que_50" mediante "mayor_o_igual_que_51".
     ; saltaría con >=51 es decir, con >50     ; saltaría con >=51 es decir, con >50
-    cp 51 +    cp 50+1 
-    jp nc, mayor_O_IGUAL_que_51 = mayor_que_50+    jp nc, mayor_que_50           ; => mayor O IGUAL que 51 = mayor que 50
  
-    ; aqui A < 50+    ; aqui A <50
     jr fin_comparacion     jr fin_comparacion
  
 mayor_que_50: mayor_que_50:
-    aqui A >= 51 y por tanto A > 50+    aqui A >= 51 y por tanto A > 50 
 + 
 +fin_comparacion: 
 +</code> 
 + 
 +<code z80> 
 +    ; comparamos con valor -1 y entonces sí que podemos hacer JP C 
 +    ; y comprobar "menor_que_50" mediante "mayor_o_igual_que_49"
 +    ; saltaría con <=49 es decir, con <50 
 +    cp 50-1 
 +    jp c, menor_igual_que_49        ; = menor_que_50 
 + 
 +    ; aqui A > 50 
 +    jr fin_comparacion 
 + 
 +menor_igual_que_49: 
 +    ; aqui A <= 49 y por tanto A < 50
  
 fin_comparacion: fin_comparacion:
Línea 290: Línea 294:
    * Si **HL > DE** => Z=0 y C=0.    * Si **HL > DE** => Z=0 y C=0.
  
-De la misma forma, podemos realizar rutinas específicas para compara condiciones como por ejemplo, "mayor o igual" (**>=**):+De la misma forma, podemos realizar rutinas específicas para comparar condiciones como por ejemplo, "mayor o igual" (**>=**):
  
 <code z80> <code z80>
Línea 352: Línea 356:
 \\  \\ 
 **Resetear el Carry Flag** **Resetear el Carry Flag**
 +
 +No existe una instrucción para poner el Carry Flag a 0, aunque sí una para ponerlo a 1, y una para complementarlo. Esto permite ponerlo a 0 usando 2 instrucciones (2 bytes) y 8 t-estados. Sin embargo, es más fácil hacerlo usando cualquier operación lógica que lo resetee, como ''or'':
  
 <code z80> <code z80>
Línea 362: Línea 368:
 </code> </code>
  
-Utilizando instrucciones lógicas, de resta o de comparación, podemos usar las siguientes construcciones:+Utilizando instrucciones lógicas, de resta o de comparación, así como instrucciones específicas del Z80, podemos usar las siguientes construcciones:
  
 <code z80> <code z80>
-    or a          ; ZF=1 si A es 0, y además limpia el CF +    scf                 ; Pone CF=1 
-    and a         ; ZF=1 si A es 0 +    or a                Pone CF=0 y hace ZF=1 si A es 0 
-    xor a         Siempre establece ZF=1 y además hace A=0 +    and a               Pone CF=0 y hace ZF=1 si A es 0 
-    cp a          Siempre establece ZF=1 +    xor a               Hace ZF=1 y además hace A=0 y resetea el flag de signo S 
-    sub a         Siempre establece ZF=1 y además hace A=0+    cp a                Hace ZF=1 y pone CF=0 
 +    sub a               Hace ZF=1 y además hace A=0 
 +    or 1                ; Modifica A, pone CF=0 y altera ZF y S como corresponda. 
 +    or %10000000        ; Pone el flag de Signo a 1: S=1, CF=0 y Z=0 
 + 
 +    xor a               ; Pone el flag P/O=1 
 +    sub a               ; Pone el flag P/O=0
 </code> </code>
  
Línea 375: Línea 387:
  
 <code z80> <code z80>
-   sub 0 +    sub 0 
-   add a, 0 +    add a, 0 
-   cp 0+    cp 0
 </code> </code>
 +
 +\\ 
 +**Complementar A**
 +
 +<code z80>
 +    ; No óptimo:
 +    xor %11111111       ; 2 bytes, 7 t-estados
 +
 +    ; Óptimo:
 +    cpl                 ; 1 byte, 4 t-estados
 +</code>
 +
  
 \\  \\ 
Línea 414: Línea 438:
                         ; Si CF era 0 hace A=0                         ; Si CF era 0 hace A=0
 </code> </code>
 +
  
 \\  \\ 
-**Realizar un ''NEG'' de un registro de 16 bits**+**Asignar valores directos a memoria**
  
 <code z80> <code z80>
-    xor a +    ; No óptimo: 
-    sub Parte_baja_Registro +    ld a$40 
-    ld Parte_baja_Registroa +    ld (hl), a          ; 3 bytes y 14 t-estados 
-    sbc a, a + 
-    sub Parte_alta_Registro +    ; Óptimo: 
-    ld Parte_alta_Registroa+    ld (hl)$40        ; 2 bytes y 10 t-estados
 </code> </code>
  
-Por ejemplo, para simular ''NEG HL'':+\\  
 +**Asignar dos valores a la parte alta y baja de un registro**
  
 <code z80> <code z80>
 +    ; No óptimo:
 +    ld b, $20
 +    ld c, $30           ; 4 bytes, 14 t-estados
 +
 +    ; Opción óptima 1:
 +    ld bc, $2030        ; 3 bytes, 10 t-estados
 +
 +    ; Opción óptima 2:
 +    ld bc,(NUMERO_B*256) + NUMERO_C
 +
 +                       ; 3 bytes, 10 t-estados
 +</code>
 +
 +\\ 
 +**Dividir con ''SLR'' cuando podemos usar ''RRCA''**
 +
 +Para dividir A por potencias de dos, como veremos en el capítulo de operaciones aritméticas, se puede utilizar ''slr a''. Esta operación utiliza 2 bytes y 8 ciclos de reloj.
 +
 +En su lugar podemos usar ''RRCA'' (1 sólo byte y 4 ciclos de reloj) si después eliminamos los posibles bits insertados por la izquierda con un ''AND'' (1 byte y otros 4 ciclos):
 +
 +<code z80>
 +    ; No óptimo:
 +    srl a
 +    srl a
 +    srl a               ; 6 bytes y 24 t-estados
 +
 +    ; Óptimo:
 +    rrca
 +    rrca
 +    rrca
 +    and %00011111       ; Eliminamos los 3 posibles bits
 +                        ; insertados por los 3 rrca.
 +                        ; 4 bytes y 15 t-estados
 +</code>
 +
 +\\ 
 +**Establecer varias variables consecutivas a un valor**
 +
 +Supongamos que tenemos varias variables consecutivas en memoria y queremos asignarles un mismo valor a todas ellas, por ejemplo 0. Para eso, lo mejor establecer la primera de las variables y usar HL, DE y ''LDIR'' con BC =
 +
 +<code z80>
 +variable1  DB  1
 +variable2  DB  1
 +variable3  DB  1
 +variable4  DB  1
 +variable5  DB  1
 +
 +    ; No óptimo:
     xor a     xor a
-    sub l +    ld (variable1), a 
-    ld l, a +    ld (variable2), a 
-    sbc a, a +    ld (variable3), a 
-    sub h +    ld (variable4), a 
-    ld h, a+    ld (variable5),
 + 
 +    ; Óptimo: 
 +    ld hlvariable1 
 +    ld de, variable1+1 
 +    xor a 
 +    ld (hl), a          ; Establecemos el valor del primero 
 +    ld bc, 4            ; Numero de elementos a asignar - 1 
 +    ldir                ; Hacemos el resto copiando BC veces 
 +                        ; el valor del primero, en adelante.
 </code> </code>
 +
 + Con este código, ahorramos 3 bytes por cada ''ld (variableN), a'' que nos evitemos.
 +
 +\\ 
 +**Incrementar el valor de una variable y dejarla en A**
 +
 +<code z80>
 +    ; No óptimo
 +    ld a, (variable)
 +    inc a
 +    ld (variable), a
 +
 +    ; Óptimo:
 +    ld hl, variable
 +    inc (hl)
 +    ld a, (hl)          ; Ahorramos 2 bytes y 2 t-estados
 +</code>
 +
 +\\ 
 +**Copiar (HL) => (DE) sin modificar BC**
 +
 +Como ya sabemos, **LDI** copia el valor de la memoria apuntado por HL en la posición de memoria apuntada por DE, y decrementa BC.
 +
 +Cuando no queremos que BC sea modificado, tendemos a hacer lo siguiente:
 +
 +<code z80>
 +    ; No óptimo
 +    ld a, (hl)
 +    ld (de), A
 +    inc hl
 +    inc de
 +</code>
 +
 +Sin embargo, como ''LDI'' sólo va a decrementar BC en una unidad, podemos ahorrar 2 bytes y 8 t-estados así:
 +
 +<code z80>
 +    ; Óptimo:
 +    ldi                 ; (HL) => (DE) y se decrementa BC
 +    inc bc              ; recuperamos el BC decrementado
 +</code>
 +
 +
 +\\ 
 +**Testear el estado del bit 0 o el 7 de A para un salto**
 +
 +Si queremos comprobar el estado del bit 0 o el bit 7 de A para un salto, lo normal es hacer lo siguiente:
 +
 +<code z80>
 +    ; Con bit 0:
 +    bit 0, a            ; 2 bytes, 8 t-estados
 +    jr z, destino
 +
 +    ; Con bit 7:
 +    bit 7, a            ; 2 bytes, 8 t-estados
 +    call z, destino
 +</code>
 +
 +En su lugar podemos utilizar operaciones de desplazamiento de dichos bits hacia el Carry Flag (1 byte y 4 t-estados) y saltar según el valor de C:
 +
 +<code z80>
 +    ; Con bit 0:
 +    rra                 ; 1 byte, 4 t-estados
 +    jr c, destino
 +
 +    ; Con bit 7:
 +    rla                 ; 1 byte, 4 t-estados
 +    call c, destino
 +</code>
 +
  
 \\  \\ 
Línea 444: Línea 596:
  
 \\  \\ 
-**[ [[.:indice|⬉]] | [[.:introduccion|⬅]] | [[.:esqueleto_programa|➡]] ]**+**[ [[.:indice|⬉]] | [[.:lenguaje_5|⬅]] | [[.:aritmetica|➡]] ]**
  
  • cursos/ensamblador/habituales.1706097218.txt.gz
  • Última modificación: 24-01-2024 11:53
  • por sromero