cursos:ensamblador:aritmetica

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:aritmetica [24-01-2024 07:06] – [Divisiones] sromerocursos:ensamblador:aritmetica [02-02-2024 10:12] (actual) – [Números aleatorios] sromero
Línea 1: Línea 1:
 ====== Aritmética con el Z80 ====== ====== Aritmética con el Z80 ======
  
-El Z80 nos provee de funciones aritméticas de 8 bits básicas, como son ''ADD'', ''ADC'', ''SUB'', ''SBC'', ''NEG'', ''CPL'' y ''DAA'', así como instrucciones para rotación/desplazamiento de bits como ''RLC'', ''RRC', ''RL'', ''RR'', ''RLA'', ''RRA'', ''RLCA', ''RRCA'', ''RLD'', 'RRD', ''SRA'', ''SLA'' y ''SRL''.+El Z80 nos provee de funciones aritméticas de 8 bits básicas, como son ''ADD'', ''ADC'', ''SUB'', ''SBC'', ''NEG'', ''CPL'' y ''DAA'', así como instrucciones para rotación/desplazamiento de bits como ''RLC'', ''RRC'', ''RL'', ''RR'', ''RLA'', ''RRA'', ''RLCA'', ''RRCA'', ''RLD'', ''RRD'', ''SRA'', ''SLA'' y ''SRL''.
  
 En muchas de estas operaciones toma especial relevancia ''CF'' (el **Carry Flag**), que podemos modificar mediante ''SCF'' y ''CCF'' (o realizando atajos como ''AND A'' o ''OR A'' para sustituir a ''CCF''). En muchas de estas operaciones toma especial relevancia ''CF'' (el **Carry Flag**), que podemos modificar mediante ''SCF'' y ''CCF'' (o realizando atajos como ''AND A'' o ''OR A'' para sustituir a ''CCF'').
Línea 11: Línea 11:
 En este capítulo vamos a ver rutinas para realizar este tipo de operaciones extraídas de diferentes fuentes (libros, webs dedicadas al Z80, foros de desarrollo, canales de discusión sobre programación en ensamblador), de forma que las podamos incorporar directamente en nuestros programas. En este capítulo vamos a ver rutinas para realizar este tipo de operaciones extraídas de diferentes fuentes (libros, webs dedicadas al Z80, foros de desarrollo, canales de discusión sobre programación en ensamblador), de forma que las podamos incorporar directamente en nuestros programas.
  
-En algunas ocasiones se incluirán comentarios sobre cómo opera esta rutina, y en otras simplemente citaremos la rutina con sus parámetros de entrada y salida para poder usarla en nuestros programas. Para toda rutina, se citará la fuente de la que se ha extraído si es conocida, y su autor (si es posible) para darle sus merecido crédito por ella.+En algunas ocasiones se incluirán comentarios sobre cómo opera esta rutina, y en otras simplemente citaremos la rutina con sus parámetros de entrada y salida para poder usarla en nuestros programas. Para toda rutina, se citará la fuente de la que se ha extraído si es conocida, y su autor (si es posible) para darle su merecido crédito por ella.
  
-Las rutinas se reproducen tal cual aparecen en la web, por lo que es posible que alguna de ellas incluyan ''PUSH''es y ''POP''para preservar registros, y otras no lo hagan. Debido a esto, cuando vayamos a incorporarlas en nuestros programas o librerías se recomienda que se revise la rutina para añadir o quitar la preservación de registros según la necesitemos o no.+Las rutinas se reproducen tal cual aparecen en la web, por lo que es posible que alguna de ellas incluyan varios ''PUSH'' y ''POP'' para preservar registros, y otras no lo hagan. Debido a esto, cuando vayamos a incorporarlas en nuestros programas o librerías se recomienda que se revise la rutina para añadir o quitar la preservación de registros según la necesitemos o no.
  
 Se recomienda al lector que se desarrolle su propia librería ''aritmetica.asm'' con una selección de rutinas de esta página, eligiendo aquellas rutinas que pueda necesitar en sus programas, y las aproveche en lugar de intentar "reinventar la rueda" y escribir rutinas de aritmética que puedan ser más lentas que las presentadas. Se recomienda al lector que se desarrolle su propia librería ''aritmetica.asm'' con una selección de rutinas de esta página, eligiendo aquellas rutinas que pueda necesitar en sus programas, y las aproveche en lugar de intentar "reinventar la rueda" y escribir rutinas de aritmética que puedan ser más lentas que las presentadas.
Línea 47: Línea 47:
     ld b, 0     ld b, 0
     ld c, a     ld c, a
-    adc hl, bc            ; HL = HL+A +    add hl, bc            ; HL = HL+A 
          
     ; Sumas de registros de 8 bits a registros de 16 bits: OPCION 2     ; Sumas de registros de 8 bits a registros de 16 bits: OPCION 2
Línea 163: Línea 163:
 </code> </code>
  
-Si hubiéramos necesitado multiplicar por 21 o por 19 (por ejemplo), podríamos haber guardado el valor de A antes de este proceso (por ejemplo en el registro C) y haber después sumado o restado C a la multiplicación por 20 para encontrar el valor deseado. Para multiplicar por 22 o por 18 podríamos haberlo sumado restado 2 veces al resultado, etc.+Si hubiéramos necesitado multiplicar por 21 o por 19 (por ejemplo), podríamos haber guardado el valor de A antes de este proceso (por ejemplo en el registro C) y haber después sumado o restado C a la multiplicación por 20 para encontrar el valor deseado. Para multiplicar por 22 o por 18 podríamos haberlo sumado restado 2 veces al resultado, etc.
  
  Pero volvamos a ''SLA'' para multiplicar por potencias de 2. Hemos dicho que cada vez que multiplicamos con desplazamientos (o con sumas de A), el bit más significativo (bit 7) pasa al Carry Flag. Si hacemos múltiples operaciones sin utilizar ese bit lo "perdemos", ya que el máximo valor que podemos almacenar en A es 255, así que no podemos multiplicar números muy grandes. Para valores grandes necesitaremos usar una pareja de registros de 16 bits, e ir pasando el bit saliente (el Carry Flag) al registro de la parte alta del valor:  Pero volvamos a ''SLA'' para multiplicar por potencias de 2. Hemos dicho que cada vez que multiplicamos con desplazamientos (o con sumas de A), el bit más significativo (bit 7) pasa al Carry Flag. Si hacemos múltiples operaciones sin utilizar ese bit lo "perdemos", ya que el máximo valor que podemos almacenar en A es 255, así que no podemos multiplicar números muy grandes. Para valores grandes necesitaremos usar una pareja de registros de 16 bits, e ir pasando el bit saliente (el Carry Flag) al registro de la parte alta del valor:
Línea 172: Línea 172:
 </code> </code>
  
- Al igual que pasaba con ''add aa'', las sumas de 16 bits con ''add hlhl'' pueden llegar a ser más rápidas (1 byte, y 11 ciclos de reloj) que la combinación de ''sla'' + ''rl'' (4 bytes y 16 ciclos).+ Al igual que pasaba con ''ADD AA'', las sumas de 16 bits con ''ADD HLHL'' pueden llegar a ser más rápidas (1 byte, y 11 ciclos de reloj) que la combinación de ''SLA'' + ''RL'' (4 bytes y 16 ciclos).
  
 Por ejemplo: Por ejemplo:
Línea 203: Línea 203:
 \\ **Dividiendo por potencias de dos**\\  \\ **Dividiendo por potencias de dos**\\ 
  
- Del mismo modo que desplazar a la izquierda es multiplicar, entonces **los desplazamientos a la derecha equivalen a dividir por potencias de 2 (2, 4, 8...)**, mediante la instrucción ''srl'':+ Del mismo modo que desplazar a la izquierda es multiplicar, entonces **los desplazamientos a la derecha equivalen a dividir por potencias de 2 (2, 4, 8...)**, mediante la instrucción ''SRL'':
  
 <code z80> <code z80>
Línea 449: Línea 449:
 De la misma forma que se puede multiplicar con múltiples sumas, también podemos dividir de forma poco eficiente con múltiples restas. De la misma forma que se puede multiplicar con múltiples sumas, también podemos dividir de forma poco eficiente con múltiples restas.
  
-Sin embargo, como ocurría con la multiplicación, lo idea es utilizar algoritmos que no tengan un tiempo de ejecución que dependa linealmente de los valores a dividir.+Sin embargo, como ocurría con la multiplicación, lo ideal es utilizar algoritmos que no tengan un tiempo de ejecución que dependa linealmente de los valores a dividir.
  
-Mostraremos a continuación una rutinas finales de división que podemos incluír en nuestros programas.+Mostraremos a continuación unas rutinas finales de división que podemos incluír en nuestros programas.
  
 \\  \\ 
Línea 552: Línea 552:
 ===== Raíz cuadrada ===== ===== Raíz cuadrada =====
  
-Veamos una rutina de //Ricardo Bittencourt// para obtener la raíz cuadrada del valor contenido en el registro HL.+A continuación se muestra, lista para usar, una rutina de //Ricardo Bittencourt// para obtener la raíz cuadrada del valor contenido en el registro HL.
  
 +\\ 
 <code z80> <code z80>
 ;;; Square root routine ;;; Square root routine
Línea 588: Línea 589:
 ===== Números aleatorios ===== ===== Números aleatorios =====
  
-Las siguientes rutinas proporcionan números pseudoaleatorios.+Si hay una funcionalidad que necesitaremos casi con total seguridad para desarrollar un juego es la posibilidad de generar números aleatorios. Si queremos que cada partida sea diferente y que ciertas acciones varíen (como por ejemplo, que en un juego de puzzle no aparezcan siempre las mismas piezas), necesitaremos una fuente de aleatoriedad, una rutina que nos devuelva un número diferente cada vez.
  
-La rutina que veremos a continuación, de la librería **libzx** de //Sebastian Mihai//, utiliza diferentes métodos (el último número aleatorio devuelto, bytes de la rom, valores de los registros al llamar a la rutinaetc) para obtener en A un número aleatorio de 8 bits:+Hay varias formas de generar números aleatorios. La primera de ella es utilizar funciones matemáticas que nos devuelven un número diferente de una sucesión cada vez que la llamamos. El problema de las rutinas aleatorias basadas en fórmulas es que la sucesión de números aleatorios suele ser cíclicoes decir, que siempre proporcionan los mismos números y en el mismo orden, a partir de una determinada semilla.
  
 +Veamos una rutina muy simple (extraída de la página //Z80 bits//, de //Milos Bazelides//), que utiliza la fórmula ''x[i + 1] = (5 * x[i] + 1) mod 256''. Es muy simple y de reducido tamaño, pero la serie resultante tiene poca aleatoriedad:
 +
 +\\ 
 <code z80> <code z80>
-;------------------------------------------------------- +Rand8 
-random.asm - part of the ZX Spectrum libzx +Input: none 
-library by Sebastian Mihai2016 +Output: A = pseudo-random numberperiod 256 
-;-------------------------------------------------------+From: https://map.grauw.nl/sources/external/z80bits.html#4.1
  
-; our random numbers are, in part, based on reading bytes +RAND8_SEED EQU 0
-; from the 16kb ROM, whose contents are "pretty random": +
-lastRandomNumber db 33 +
-romPointer       dw 3   +
  
-; Gets an 8-bit random number. +Rand8: 
-It is computed using combination of: +    ld a, RAND8_SEED     Semilla inicial 
-;     - the last returned random number +    ld b, 
-;     byte from ROMin increasing order +    add a, a 
-;     - current values of various registers +    add a, a 
-;     flat incremented value +    add a, b 
-+    inc a                Otra posibilidad es ADD A, 7 
-Output: +    ld (Rand8+1), a      Código automodificable 
-;         A - next random number +    ret 
-get_next_random: +</code>
-    push af+
  
-    push hl +La rutina utiliza una semilla (RAND_SEEDque inserta en el registro A para realizar una serie de operaciones y generar el siguiente valor de la serie en A.
-    ; advance ROM pointer +
-    ld hl, romPointer +
-    ld c, (hl) +
-    inc hl +
-    ld b, (hl)                ; BC := word (romPointer) +
-    ld hl, 3 +
-    add hl, bc                ; HL := ROM pointer advanced by 3 +
-    ld a, h +
-    and %00111111 +
-    ld h, a                   ; H := H mod %00111111 +
-                              ; HL := HL mod 16384, to make sure +
-                              ; HL points at a ROM location +
-    ld (romPointer), hl       ; save new location +
-    pop hl+
  
-    ; now compute the random number+Al final de la rutina podemos ver cómo hay una instrucción ''ld (Rand8+1),a'' la cual **modifica el propio código** para meter el valor resultante obtenido en el ''ld a, X'' inicial. De este modo, la siguiente vez que llamemos a la rutina tendremos en su primer instrucción el último valor generado, que usaremos como origen de los cálculos para obtener el siguiente.
  
-    pop bc                     ; BC := AF +Como la semilla inicial estará definida en el códigoesta rutina siempre devolverá la misma secuencia de valoreshaciendo que nuestro juego obtenga números aleatoriospero los mismos en cada partida. Para evitar estopodemos cambiar el valor de la semilla (alojada en la instrucción ''ld a, X'' inicial) con una función como la siguiente:
-    rlc c +
-    rlc b +
-    ld a(lastRandomNumber) +
-    add ab                   ; current reg values are "random" +
-    add ac                   ; so add them in the mix +
-    add a47 +
-    add a, +
-    add a, e +
-    add a, h +
-    add a, l+
  
-    ld hl, romPointer +\\  
-    add a, (hl)                ; the contents of the ROM are "random" +<code z80> 
-                               so add it in the mix +A = semilla a utilizar para Rand8 
-    ld hl, lastRandomNumber +SetRand8Seed: 
-    ld (hl), a                 save this number+    ld (Rand8+1), a        Establecemos semilla A 
 +    ret 
 +</code> 
 +\\ 
  
 +Y ahora, en nuestro programa, establecer el valor de esa semilla por ejemplo cuando el usuario pulse la tecla para iniciar el juego, y también cambiarla en medio de la partida con valores como por ejemplo: el valor de un determinado registro que no sea el mismo en cada partida, el valor del registro ''R'', o el valor de la parte baja de la variable del sistema ''FRAMES'' (que detallaremos en el capítulo dedicado a la ROM del Spectrum):
 +
 +\\ 
 +<code z80>
 +    ld a, r             ; El registro R cambia con cada opcode
 +    call SetRand8Seed   ; Cambiamos la semilla de generación
 +</code>
 +
 +De la misma forma, la siguiente rutina proporciona un número aleatorio de 16 bits, lo cual hace que la serie de posibles resultados sea mucho mayor (65535 valores) antes de volver a repetir valores de la secuencia.
 +
 +\\ 
 +<code z80>
 +; Rand16
 +; Input: none
 +; Output: HL = pseudo-random number, period 65536
 +; From: https://map.grauw.nl/sources/external/z80bits.html#4.1
 +
 +RAND16_SEED   DW  0
 +
 +Rand16:
 +    ld de, RAND16_SEED
 +    ld a, d
 +    ld h, e
 +    ld l, 253
 +    or a
 +    sbc hl, de
 +    sbc a, 0
 +    sbc hl, de
 +    ld d, 0
 +    sbc a, d
 +    ld e, a
 +    sbc hl, de
 +    jr nc, rand16end
 +    inc hl
 +rand16end:
 +    ld (Rand16+1), hl      ; Código automodificable
 +    ret
 +    
 +; HL = semilla a utilizar para Rand16
 +SetRand16Seed:
 +    ld (Rand16+1), hl        ; Establecemos semilla HL
     ret     ret
 </code> </code>
 +\\ 
  
-Una rutina menos elaborada (con menos "aleatoriedadpero +Aparte de estos 2 ejemplos, hay gran cantidad de rutinas que proporcionan números aleatorios por diferentes métodos. 
 + 
 +Una rutina bastante rápida es la basada en el algoritmo ''Xorshift'', de //George Marsaglia//
 + 
 +Este algoritmo es el usado por, entre otros, la rutina ''srand()'' del compilador de C Z88DK, y es una muy buena rutina generadora de números aleatorios
  
 <code z80> <code z80>
Línea 671: Línea 696:
 ; some alternative shift triplets which also perform well are: ; some alternative shift triplets which also perform well are:
 ; 6, 7, 13; 7, 9, 13; 9, 7, 13. ; 6, 7, 13; 7, 9, 13; 9, 7, 13.
-xrnd+ 
-    ld hl, 1           ; initial seed must not be 0+SRAND_SEED  EQU        ; La semilla inicial **NO** debe de ser 0 
 + 
 +srand
 +    ld hl, SRAND_SEED
     ld a, h     ld a, h
     rra     rra
Línea 687: Línea 715:
     xor h     xor h
     ld h, a     ld h, a
-    ld (xrnd+1), hl    ; Self-modifying code (alter seed)+    ld (srand+1), hl    ; Código automodificable:  
 +                        ; cambiamos la semilla de la primera línea 
 +                        ; de la subrutina, por el último valor 
 +                        ; obtenido.
     ret     ret
 +    
 +; HL = semilla a utilizar para srand
 +SetSrandSeed:
 +    ld (srand+1), hl        ; Establecemos semilla HL
 +    ret
 +    
 </code> </code>
 +\\ 
  
-su variación de Patrik Rak:+la variación de Xorshift implementada por Patrik Rak:
  
 +\\ 
 <code z80> <code z80>
 ; A = Random Number ; A = Random Number
Línea 718: Línea 757:
 </code> </code>
  
 +En el caso del Spectrum algunas rutinas se basan en que la ROM por su naturaleza es "aleatoria" (son 16384 bytes consecutivos de opcodes y datos) salvo algunos bloques concretos donde tenemos zonas vacías, especialmente en la parte superior.
 +
 +Podríamos haber realizado una rutina básica similar en el Spectrum simplemente usando HL para recoger un dato de la ROM usando el valor actual del registro R (que se incrementa con cada instrucción):
 +
 +<code z80>
 +; A = Numero aleatorio 8 bits
 +; Modifica HL
 +Rand8_ROM:
 +    ld a, r           ; Algo de aleatoriedad
 +    ld l, a           ; L = R
 +    and %00111111     ; Máscara para acceder a 0-16384
 +    ld h, a           ; H = R MOD 16384
 +    ld a, (hl)        ; Nuestro número "aleatorio" de 8 bits en A
 +    ret
 +    
 +; DE = Numero aleatorio 16 bits
 +; Modifica A, HL
 +Rand16_ROM:
 +    ld a, r           ; Algo de aleatoriedad
 +    ld l, a           ; L = R
 +    and %00111111     ; Máscara para acceder a 0-16384
 +    ld h, a           ; H = R MOD 16384
 +    ld d, (hl)
 +    inc l
 +    ld e, (hl)        ; Nuestro número "aleatorio" de 16 bits en DE
 +    ret
 +</code>
 +\\ 
 +
 +Estas rutinas tienen la desventaja de que dependen de la existencia de la ROM (no valen para desarrollar juegos en cartucho de IF2, donde no hay ROM) y que hay algunas zonas de la rom "vacías" las cuales, si tenemos "mala suerte", podrían devolver una secuencia continuada del mismo valor (normalmente $FF).
 +
 +La rutina que veremos a continuación, de la librería **libzx** de //Sebastian Mihai//, lo soluciona combinando diferentes métodos (el último número aleatorio devuelto, bytes leídos de la rom, valores de los registros al llamar a la rutina, etc) para obtener en A un número aleatorio de 8 bits:
 +
 +<code z80>
 +;-------------------------------------------------------
 +; random.asm - part of the ZX Spectrum libzx
 +; library by Sebastian Mihai, 2016
 +;-------------------------------------------------------
 +
 +; our random numbers are, in part, based on reading bytes
 +; from the 16kb ROM, whose contents are "pretty random":
 +lastRandomNumber  db 33
 +romPointer        dw 3   
 +
 +; Gets an 8-bit random number.
 +; It is computed using a combination of:
 +;     - the last returned random number
 +;     - a byte from ROM, in increasing order
 +;     - current values of various registers
 +;     - a flat incremented value
 +;
 +; Output:
 +;         A - next random number
 +get_next_random:
 +    push af
 +
 +    push hl
 +    ; advance ROM pointer
 +    ld hl, romPointer
 +    ld c, (hl)
 +    inc hl
 +    ld b, (hl)                ; BC := word (romPointer)
 +    ld hl, 3
 +    add hl, bc                ; HL := ROM pointer advanced by 3
 +    ld a, h
 +    and %00111111
 +    ld h, a                   ; H := H mod %00111111
 +                              ; HL := HL mod 16384, to make sure
 +                              ; HL points at a ROM location
 +    ld (romPointer), hl       ; save new location
 +    pop hl
 +
 +    ; now compute the random number
 +
 +    pop bc                     ; BC := AF
 +    rlc c
 +    rlc b
 +    ld a, (lastRandomNumber)
 +    add a, b                   ; current reg values are "random"
 +    add a, c                   ; so add them in the mix
 +    add a, 47
 +    add a, d
 +    add a, e
 +    add a, h
 +    add a, l
 +
 +    ld hl, romPointer
 +    add a, (hl)                ; the contents of the ROM are "random"
 +                               ; so add it in the mix
 +    ld hl, lastRandomNumber
 +    ld (hl), a                 ; save this number
 +
 +    ret
 +</code>
 +\\ 
 +
 +En realidad, podríamos llenar un capítulo entero de rutinas diferentes para generar números aleatorios, ya que hay decenas en la red usando diferentes técnicas. Por ahora, podemos utilizar cualquiera de las vistas anteriormente en nuestros programas y obtener una más que decente aleatoriedad, ya que alguna de ellas ha sido utilizada en juegos comerciales reales de la época.
  
 \\  \\ 
Línea 724: Línea 860:
 Finalmente, veamos 2 rutinas de WikiTi para "espejar" el registro A, es decir, invertir el orden de los bits, de ''ABCDEFGH'' a ''HGFFEDCBA'': Finalmente, veamos 2 rutinas de WikiTi para "espejar" el registro A, es decir, invertir el orden de los bits, de ''ABCDEFGH'' a ''HGFFEDCBA'':
  
-Primeroveamos la rutina "lógica" con desplazamientos, de 25 bytes y que tarda 96 ciclos de reloj:+Primero veamos la rutina con el algoritmo que podríamos considerar lógico: con desplazamientos. Ocupa 25 bytes y tarda 96 ciclos de reloj:
  
 +\\ 
 <code z80> <code z80>
 ; input: b ; input: b
Línea 749: Línea 886:
     ret     ret
 </code> </code>
 +\\ 
  
-A continuaciónpodemos ver la versión optimizada por **calmaniac84** que tarda sólo 66 ciclos de reloj y ocupa 17 bytes:+Sin embargo, como hemos visto en otras rutinas, siempre se pueden abordar los problemas con algoritmos más óptimos con la idea de ahorrar espacio, ahorrar tiempo de ejecución, o ambos. A continuación podemos ver la versión optimizada por **calmaniac84** que tarda sólo 66 ciclos de reloj y ocupa 17 bytes:
  
 +\\ 
 <code z80> <code z80>
 ;- Reverse a ;- Reverse a
Línea 780: Línea 919:
 ===== Enlaces ===== ===== Enlaces =====
  
 +  * [[https://map.grauw.nl/articles/mult_div_shifts.php|Mult, Div and Shifts]]
   * [[http://cpctech.cpc-live.com/docs/mult.html|Mult routines]]   * [[http://cpctech.cpc-live.com/docs/mult.html|Mult routines]]
   * [[https://wikiti.brandonw.net/index.php?title=Z80_Routines:Math:Multiplication|WikiTi:Math:Multiplication]]   * [[https://wikiti.brandonw.net/index.php?title=Z80_Routines:Math:Multiplication|WikiTi:Math:Multiplication]]
Línea 790: Línea 930:
   * [[https://learn.cemetech.net/index.php/Z80:Advanced_Math|Cemetech - Advanced Math]]   * [[https://learn.cemetech.net/index.php/Z80:Advanced_Math|Cemetech - Advanced Math]]
   * [[https://github.com/Zeda/Z80-Optimized-Routines|Z80 Optimized Routines]]   * [[https://github.com/Zeda/Z80-Optimized-Routines|Z80 Optimized Routines]]
 +  * [[https://map.grauw.nl/sources/external/z80bits.html|Z80 bits (routines)]]
   * [[http://www.andreadrian.de/oldcpu/Z80_number_cruncher.html|Z80 Number Cruncher]]   * [[http://www.andreadrian.de/oldcpu/Z80_number_cruncher.html|Z80 Number Cruncher]]
   * [[http://sebastianmihai.com/libzx.html|Librería LibZX con funciones útiles para Z80+Spectrum]]   * [[http://sebastianmihai.com/libzx.html|Librería LibZX con funciones útiles para Z80+Spectrum]]
Línea 795: Línea 936:
  
 \\  \\ 
-**[ [[.:indice|⬉]] | [[.:introduccion|⬅]] | [[.:esqueleto_programa|➡]] ]**+**[ [[.:indice|⬉]] | [[.:habituales|⬅]] | [[.:ensambladores|➡]] ]**
  
  • cursos/ensamblador/aritmetica.1706079982.txt.gz
  • Última modificación: 24-01-2024 07:06
  • por sromero