cursos:ensamblador:avanzadas1

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:avanzadas1 [19-01-2024 12:33] – [Cómo aprovechar el área entre 25000 y 32768] sromerocursos:ensamblador:avanzadas1 [21-01-2024 10:31] (actual) – [Obtener el valor de PC (Contador de Programa)] sromero
Línea 432: Línea 432:
  
 Con esto el mismo juego 48K podrá tener música en modo 128K con unos pequeños añadidos muy sencillos, y ser compatible con ambos modelos. Con esto el mismo juego 48K podrá tener música en modo 128K con unos pequeños añadidos muy sencillos, y ser compatible con ambos modelos.
 +
 +
 +\\ 
 +\\ 
 +===== Código Automodificable =====
 +
 +Ya vimos en un capítulo anterior un ejemplo de **código automodificable**. Consiste en sobreescribir en memoria un opcode o un dato en función de una condición, para que cuando lleguemos a ese punto del programa, se ejecute el opcode modificado.
 +
 +En el ejemplo que ya vimos, utilizamos código automodificable para sobreescribir un instrucción de carga "futura" que nos permitirá recuperar el valor del registro BC más adelante, por ejemplo porque vamos a modificarlo con un bucle, y no tenemos la pila disponible:
 +
 +<code z80>
 +    ld (save_bc+1), bc    ; Escribimos BC en la parte NN NN del
 +                          ; opcode "ld bc, NN NN" en memoria
 +
 +    ... hacer cosas con BC, perdiendo su valor ...
 +
 +save_bc:
 +    ld bc, $0000          ; En el LD anterior cambiamos $000
 +                          ; por el valor de BC, así que cuando
 +                          ; el z80 llegue aquí no es ya ld bc, 0
 +                          ; sino ld bc, valor_que_tenia_BC
 +                          ; así que recuperaremos BC aquí.
 +</code>
 +
 +Cuando se ejecuta la instrucción ''ld (save_bc+1), bc'', estamos sobreescribiendo nuestro propio programa, cambiando el opcode que estaba ensamblado en memoria (''ld bc, $0000'', es decir "**$01 $00 $00**") por **$01 XX XX**, siendo **XX XX** el nuevo valor que queremos introducir en el opcode.
 +
 +Cuando se ejecuta el ''ld (save_bc+1), bc'', estamos escribiendo el valor de BC en ese momento encima de "XX XX" de forma que cuando la ejecución llegue a ese punto, el comando ''ld bc, XX'' se ejecutará y recuperará en BC el valor que tenía BC cuando se almacenó en esa posición de memoria.
 +
 +La escritura en memoria (''ld (nn), bc'' = 20 ciclos de reloj) y su posterior asignación (10 ciclos) cuesta un total de 30 ciclos de reloj, mientras que si hubiéramos usado PUSH/POP costaría sólo 21 ciclos en total (11 el ''PUSH'' y 10 el ''POP''). Sin embargo, si estamos en una rutina de impresión de Sprites donde usamos la pila para recuperar datos del Sprite, por ejemplo, es una forma de recuperar valores de registros en una situación donde no podemos hacer ''PUSH'' y ''POP'' para ello.
 +
 +Y no sólo podemos modificar datos, también podríamos modificar opcodes, cambiando ''JR Z''s por ''JR NZ''s, por ejemplo, para cambiar la condicionalidad de los saltos de alguna forma.
 +
 +\\ 
 +===== Obtener el valor de PC (Contador de Programa) =====
 +
 +Utilizando ''CALL'' y la pila del Z80, podemos obtener en un registro el valor del Contador de Programa o ''PC'':
 +
 +<code z80>
 +    call NextInstr       ; CALL mete PC en la pila
 +NextInstr:
 +    pop hl               ; HL contiene ahora PC
 +</code>
 +
 +Al ejecutarse ''call NextInstr'', el Z80 mete en la pila la dirección de retorno para el ''CALL'', en este caso, justo la dirección en la que está el contador de programa. Una vez hecho eso, no hacemos un ''RET'' para volver sino que hacemos ''POP HL'' para extraer PC de la pila sin volver al punto de llamada. El flujo de ejecución continuaría tras el ''pop hl'', con el valor de PC introducido en HL.
 +
 +¿Para qué podría valer algo así? Nuestro compañero **<nowiki>Xor_A [Fer]</nowiki>** nos proporciona un ejemplo con el cual podemos utilizar el valor de PC para imprimir cadenas sin tener que cargar en HL cada vez la posición de la cadena, intercalando el código y los datos:
 +
 +<code z80>
 +Imprimir_Mensajes:
 +    call MyPrintString
 +    DB "cadena1", $ff
 +    call MyPrintString
 +    DB "cadena2", $ff
 +    call MyPrintString
 +    DB "cadena3", $ff
 +    (...)
 +
 +MyPrintString:
 +    pop hl          ; obtenemos la direccion de la cadena (PC)
 +mpr_loop:
 +    ld a, (hl)
 +    cp $ff
 +    Jr z, mprexit
 +    rst $10
 +    inc hl
 +    jr mpr_loop
 +mpr_exit:
 +    push hl         ; introducimos direccion de retorno
 +    ret             ; PC continuará después de la cadena impresa
 +</code>
 +
 +La llamada a ''MyPrintString'' mete en la pila la dirección de PC, que es la dirección del primer carácter de la cadena.
 +
 +La rutina extrae ese valor de la pila y lo usa como "puntero a la cadena" en HL. Incrementa HL tras imprimir cada carácter, y al acabar de imprimir la cadena, mete en la pila el valor de HL para que el ''RET'' vuelva al flujo principal del programa, pero justo después de los datos de la cadena, es decir, al siguiente ''call MyPrintString''.
 +
 +Esto nos evitaría lo siguiente, que suponen 3 bytes por cada instrucción ''ld hl, NNNN'':
 +
 +<code z80>
 +    ld hl, cadena1
 +    call MyPrintString
 +    ld hl, cadena2
 +    call MyPrintString
 +    ld hl, cadena3
 +    call MyPrintString
 +</code>
  
 \\  \\ 
 **[ [[.:indice|⬉]] | [[.:compresion_rle|⬅]] | [[.:avanzadas2|➡]] ]** **[ [[.:indice|⬉]] | [[.:compresion_rle|⬅]] | [[.:avanzadas2|➡]] ]**
  • cursos/ensamblador/avanzadas1.1705667616.txt.gz
  • Última modificación: 19-01-2024 12:33
  • por sromero