Diferencias
Muestra las diferencias entre dos versiones de la página.
Ambos lados, revisión anterior Revisión previa Próxima revisión | Revisión previa Próxima revisiónAmbos lados, revisión siguiente | ||
cursos:ensamblador:lenguaje_1 [24-10-2010 07:58] – sromero | cursos:ensamblador:lenguaje_1 [19-01-2024 16:01] – [Instrucciones LD (instrucciones de carga)] sromero | ||
---|---|---|---|
Línea 1: | Línea 1: | ||
====== Lenguaje Ensamblador del Z80 (I) ====== | ====== Lenguaje Ensamblador del Z80 (I) ====== | ||
- | \\ | + | ====== Arquitectura del Z80 e Instrucciones básicas |
- | ===== La Arquitectura | + | |
- | + | ||
- | En esta tercera entrega del curso explicaremos la sintaxis utilizada en los programas en ensamblador. Para ello comenzaremos con una definición general de la sintaxis del lenguaje para el ensamblador Pasmo, que será el " | + | |
- | + | ||
- | Posteriormente veremos en detalle los registros: qué registros hay disponibles, | + | |
- | + | ||
- | Esta entrega del curso es delicada y complicada: por un lado, tenemos que explicar las normas y sintaxis del ensamblador cruzado | + | |
- | + | ||
- | Además, el lenguaje ensamblador tiene disponibles muchas instrucciones diferentes, y nos resultaría imposible explicarlas todas en un mismo capítulo, lo que nos fuerza a explicar las instrucciones del microprocesador en varias entregas. Esto implica que hablaremos de PASMO comentando reglas, opciones de instrucciones y directivas que todavía no conocemos. | + | |
- | + | ||
- | Es por esto que recomendamos al lector que, tras releer los capítulos 1 y 2 de nuestro curso, se tome esta entrega de una manera especial, leyéndola 2 veces. La " | + | |
- | + | ||
- | Así pues, os recomendamos leer este capítulo dos veces, una para absorber las normas de PASMO, y otra para absorber la sintaxis del lenguaje y las instrucciones que explicaremos (terminando de comprender conceptos de la primera lectura). | + | |
- | + | ||
- | \\ | + | |
- | ===== Sintaxis del lenguaje en PASMO ===== | + | |
- | + | ||
- | En la primera parte de curso se introdujo PASMO, el ensamblador cruzado que recomendamos para el desarrollo de programas para Spectrum. Este ensamblador traduce nuestros ficheros de texto .asm con el código fuente de programa (en lenguaje ensamblador) a ficheros .bin (o .tap/.tzx) que contendrán el código máquina directamente ejecutable por el Spectrum. Suponemos que ya tenéis instalado PASMO (ya sea la versión Windows o la de Linux) en vuestro sistema y que sabéis utilizarlo (os recomiendo que releáis la primera entrega de nuestro curso si no recordáis su uso), y que podéis ejecutarlo dentro del directorio de trabajo que habéis elegido (por ejemplo: / | + | |
- | + | ||
- | El ciclo de desarrollo con PASMO será el siguiente: | + | |
- | + | ||
- | * Con un editor de texto, tecleamos nuestro programa en un fichero .ASM con la sintaxis que veremos a continuación. | + | |
- | * Salimos del editor de texto y ensamblamos el programa con: "pasmo ejemplo1.asm ejemplo1.bin" | + | |
- | * El fichero bin contiene el código máquina resultante, que podemos POKEar en memoria, cargar como un bloque CM (LOAD "" | + | |
- | + | ||
- | Todo esto se mostró bastante detalladamente en su momento en el número 1 de nuestro curso. | + | |
- | + | ||
- | Con esto, sabemos ensamblar programas creados adecuadamente, | + | |
- | + | ||
- | Lo que vamos a ver a continuación son las normas que debe cumplir un programa para poder ser ensamblado en PASMO. Es necesario explicar estas reglas para que el lector pueda consultarlas en el futuro, cuando esté realizando sus propios programas. No te preocupes si no entiendes alguna de las reglas, cuando llegues al momento de implementar tus primeras rutinas, las siguientes normas te serán muy útiles:\\ | + | |
- | + | ||
- | * **Normas para las instrucciones: | + | |
- | * Pondremos una sóla instrucción de ensamblador por línea. | + | |
- | * Como existen diferencias entre los "fin de línea" | + | |
- | * Además de una instrucción por línea podremos añadir etiquetas (para referenciar a dicha línea, algo que veremos posteriormente) y comentarios (con ';' | + | |
- | \\ | + | |
- | * **Normas para los valores numéricos: | + | |
- | * Todos los valores numéricos se considerarán, | + | |
- | * Para introducir valores números en hexadecimal los precederemos del carácter " | + | |
- | * Podremos también especificar la base del literal poniendoles como prefijo las cadena &H ó 0x (para hexadecimal) o &O (para octal). | + | |
- | * Podemos especificar también los números mediante sufijos: Usando una " | + | |
- | \\ | + | |
- | * **Normas para cadenas de texto: ** | + | |
- | * Podemos separar las cadenas de texto mediante comillas simples o dobles. | + | |
- | * El texto encerrado entre comillas simples no recibe ninguna interpretación, | + | |
- | * El texto encerrado entre comillas dobles permite introducir caracteres especiales al estilo de C/C++ como \n, \r o \t (nueva línea, retorno de carro, tabulador...). | + | |
- | * El texto encerrado entre comillas dobles también admite \xNN para introducir el carácter correspondiente a un número hexadecimal NN. | + | |
- | * Una cadena de texto de longitud 1 (un carácter) puede usarse como una constante (valor ASCII del carácter) en expresiones como, por ejemplo, ' | + | |
- | \\ | + | |
- | * **Normas para los nombres de ficheros: | + | |
- | * Si vemos que nuestro programa se hace muy largo, podemos partir el fichero en varios ficheros | + | |
- | \\ | + | |
- | * **Normas para los identificadores: | + | |
- | * Los identificadores son los nombres usados para etiquetas y símbolos definidos mediante EQU y DEFL. | + | |
- | * Podemos utilizar cualquier cadena de texto, excepto los nombres de las palabras reservadas de ensamblador. | + | |
- | \\ | + | |
- | * **Normas para las etiquetas: | + | |
- | * Una etiqueta es un identificador de texto que ponemos poner al principio de cualquier línea de nuestro programa. | + | |
- | * Podemos añadir el tradicional sufijo ":" | + | |
- | * Para PASMO, cualquier referencia a una etiqueta a lo largo del programa se convierte en una referencia a la posición de memoria donde se ensambla la instrucción o dato donde hemos colocado la etiqueta. Podemos utilizar así etiquetas para hacer referencia a nuestros gráficos, variables, datos, funciones, lugares a donde saltar, etc. | + | |
- | \\ | + | |
- | * **Directivas: | + | |
- | * Tenemos a nuestra disposición una serie de directivas para facilitarnos la programación, | + | |
- | * La directiva END permite indicar un parámetro numérico (END XXXX) que "pasmo --tapbas" | + | |
- | usuario tenga que teclear el " | + | |
- | * Una de las directivas más importantes es ORG, que indica la posición origen donde almacenar el código que la sigue. Podemos utilizar diferentes directivas ORG en un mismo programa. | + | |
- | * Iremos viendo el significado de las directivas conforme las vayamos usando, pero es aconsejable [[http:// | + | |
- | \\ | + | |
- | * **[[http:// | + | |
- | * Podemos utilizar los operadores típicos +, -, *. /, así como otros operadores de desplazamiento de bits como >> y <<. | + | |
- | * Tenemos disponibles operadores de comparación como EQ, NE, LT, LE, GT, GE o los clásicos | + | |
- | * Existen también operadores lógicos como AND, OR, NOT, o sus variantes &, |, !. | + | |
- | * Los operadores sólo tienen aplicación en tiempo de ensamblado, es decir, no podemos multiplicar o dividir en tiempo real en nuestro programa usando * o /. Estos operadores están pensados para que podamos poner expresiones como ((32*10)+12), | + | |
- | + | ||
- | \\ | + | |
- | ===== Aspecto de un programa en ensamblador ===== | + | |
- | + | ||
- | Veamos un ejemplo de programa en ensamblador que muestra el uso de algunas de estas normas, para que las podamos entender fácilmente mediante los comentarios incluidos: | + | |
- | + | ||
- | <code z80> | + | |
- | ; Programa de ejemplo para mostrar el aspecto de | + | |
- | ; un programa típico en ensamblador para PASMO. | + | |
- | ; Copia una serie de bytes a la videomemoria con | + | |
- | ; instrucciones simples (sin optimizar). | + | |
- | ORG 40000 | + | |
- | + | ||
- | valor | + | |
- | destino | + | |
- | + | ||
- | ; Aqui empieza nuestro programa que copia los | + | |
- | ; 7 bytes desde la etiqueta " | + | |
- | ; videomemoria ([16384] en adelante). | + | |
- | + | ||
- | LD HL, destino | + | |
- | LD DE, datos ; DE = origen de los datos | + | |
- | LD B, 6 ; numero de datos a copiar | + | |
- | + | ||
- | bucle: | + | |
- | + | ||
- | LD A, (DE) ; Leemos un dato de [DE] | + | |
- | ADD A, valor ; Le sumamos 1 al dato leído | + | |
- | LD (HL), A ; Lo grabamos en el destino [HL] | + | |
- | INC DE ; Apuntamos al siguiente dato | + | |
- | INC HL ; Apuntamos al siguiente destino | + | |
- | + | ||
- | DJNZ bucle ; Equivale a: | + | |
- | ; B = B-1 | + | |
- | ; if (B>0) goto Bucle | + | |
- | RET | + | |
- | + | ||
- | datos DEFB 127, %10101010, 0, 128, $FE, %10000000, FFh | + | |
- | + | ||
- | END | + | |
- | </ | + | |
- | + | ||
- | Algunos detalles a tener en cuenta: | + | |
- | + | ||
- | * Como veis, se pone una instrucción por línea. | + | |
- | * Los comentarios pueden ir en sus propias líneas, o dentro de líneas de instrucciones (tras ellas). | + | |
- | * Podemos definir " | + | |
- | * Podemos poner etiquetas (como " | + | |
- | * Los datos definidos con DEFB pueden estar en cualquier formato numérico, como se ha mostrado en el ejemplo: decimal, binario, hexadecimal tanto con prefijo " | + | |
- | + | ||
- | Podéis ensamblar el ejemplo anterior mediante: | + | |
- | + | ||
- | < | + | |
- | pasmo --tapbas ejemplo.asm ejemplo.tap | + | |
- | </ | + | |
- | + | ||
- | Una vez cargado y ejecutado el TAP en el emulador de Spectrum, podréis ejecutar el código máquina en BASIC con un " | + | |
- | {{ cursos:ensamblador: | + | Empezaremos nuestro periplo por el ensamblador |
- | Los píxeles que aparecen | + | El lenguaje ensamblador tiene disponibles muchas instrucciones diferentes, y resultaría imposible explicarlas todas en un mismo capítulo, lo que nos fuerza a explicar |
- | Si cambiáis el END del programa por END 40000, no tendréis la necesidad | + | Comencemos con los conceptos más importantes y las instrucciones más básicas, como las instrucciones |
\\ | \\ | ||
===== Los registros ===== | ===== Los registros ===== | ||
- | Como ya vimos en la anterior entrega, todo el " | + | Como ya vimos en el capítulo dedicado a la Arquitectura del Spectrum y del Z80, todo el " |
El Z80 tiene una serie de registros de 8 bits con nombres específicos: | El Z80 tiene una serie de registros de 8 bits con nombres específicos: | ||
Línea 149: | Línea 19: | ||
* **B, C, D, E, H, L**: Registros de propósito general, utilizables para gran cantidad de operaciones, | * **B, C, D, E, H, L**: Registros de propósito general, utilizables para gran cantidad de operaciones, | ||
* **I**: Registro de interrupción, | * **I**: Registro de interrupción, | ||
- | * **R**: Registro de Refresco de memoria: lo utiliza internamente la CPU para saber cuándo debe refrescar la RAM. Su valor cambia sólo conforme el Z80 va ejecutando instrucciones, | + | * **R**: Registro de Refresco de memoria: lo utiliza internamente la CPU para saber cuándo debe refrescar la RAM. Su valor cambia sólo conforme el Z80 va ejecutando instrucciones, |
\\ | \\ | ||
Además, podemos agrupar algunos de estos registros en pares de 16 bits para determinadas operaciones: | Además, podemos agrupar algunos de estos registros en pares de 16 bits para determinadas operaciones: | ||
\\ | \\ | ||
- | * **AF**: Formado por el registro A como byte más significativo (Byte alto) y por F como byte menos significativo (Byte bajo). Si A vale FFh y F vale 00h, AF valdrá automáticamente | + | * **AF**: Formado por el registro A como byte más significativo (Byte alto) y por F como byte menos significativo (Byte bajo). Si '' |
- | * **BC**: Agrupación de los registros B y C que se puede utilizar en bucles y para acceder a puertos. También se utiliza como " | + | * **BC**: Agrupación de los registros B y C que se puede utilizar en bucles y para acceder a puertos. También se utiliza como " |
- | * **DE, HL**: Registros de 16 bits formados por D y E por un lado y H y L por otro. Utilizaremos generalmente estos registros para leer y escribir en memoria en una operación única, así como para las operaciones de acceso a memoria como LDIR, LDDR, etc. | + | * **DE, HL**: Registros de 16 bits formados por D y E por un lado y H y L por otro. Utilizaremos generalmente estos registros para leer y escribir en memoria en una operación única, así como para las operaciones de acceso a memoria como '' |
\\ | \\ | ||
Aparte de estos registros, existen otra serie de registros de 16 bits: | Aparte de estos registros, existen otra serie de registros de 16 bits: | ||
\\ | \\ | ||
- | * **IX, IY**: Dos registros de 16 bits pensados para acceder a memoria de forma indexada. Gracias a estos registros podemos realizar operaciones como: "LD (IX+desplazamiento), | + | * **IX, IY**: Dos registros de 16 bits pensados para acceder a memoria de forma indexada. Gracias a estos registros podemos realizar operaciones como: '' |
* **SP**: Puntero de pila, como veremos en su momento apunta a la posición actual de la " | * **SP**: Puntero de pila, como veremos en su momento apunta a la posición actual de la " | ||
- | * **PC**: Program Counter o Contador de Programa. Como ya vimos en la anterior entrega, contiene la dirección de la instrucción actual a ejecutar. No modificaremos PC directamente moviendo valores a este registro, sino que lo haremos mediante instrucciones de salto (JP, JR, CALL...). | + | * **PC**: Program Counter o Contador de Programa. Como ya vimos en la anterior entrega, contiene la dirección de la instrucción actual a ejecutar. No modificaremos PC directamente moviendo valores a este registro, sino que lo haremos mediante instrucciones de salto ('' |
\\ | \\ | ||
- | Por último, tenemos disponible un banco alternativo de registros, conocidos como //Shadow Registers// o //Registros Alternativos//, | + | Por último, tenemos disponible un banco alternativo de registros, conocidos como **Shadow Registers** o //Registros Alternativos//, |
- | En cualquier momento podemos intercambiar el valor de los registros A, B, C, D, E, F, H y L con el valor de los registros A', B', C', D', E', F', H' y L' mediante | + | En cualquier momento podemos intercambiar el valor de los registros A, B, C, D, E, F, H y L con el valor de los registros A', B', C', D', E', F', H' y L' mediante |
Ya conocemos los registros disponibles, | Ya conocemos los registros disponibles, | ||
Línea 177: | Línea 47: | ||
<code z80> | <code z80> | ||
- | LD C, $00 | + | ld c, $00 ; C vale 0 |
- | LD B, $01 | + | ld b, $01 ; B vale 1 |
- | ; con esto, BC = $0100 | + | |
- | LD A, B ; A ahora vale 1 | + | ld a, b |
- | LD HL, $1234 ; HL vale $1234 o 4660d | + | ld hl, $1234 |
- | LD A, (HL) ; A contiene el valor de (4660) | + | ld a, (hl) |
- | LD A, (16384) | + | ld a, (16384) |
- | | + | |
- | ADD A, B | + | add a, b ; Suma: A = A + B |
- | INC B ; Incrementamos B (B = 1+1 =2) | + | inc b |
- | ; Ahora BC vale $0200 | + | |
- | | + | |
- | ; (BC = $0200+1 = $0201) | + | |
</ | </ | ||
- | Dentro del ejemplo anterior queremos destacar el operador " | + | Dentro del ejemplo anterior queremos destacar el operador " |
Cabe destacar un gran inconveniente del juego de instrucciones del Z80, y es que no es // | Cabe destacar un gran inconveniente del juego de instrucciones del Z80, y es que no es // | ||
Línea 199: | Línea 69: | ||
<code z80> | <code z80> | ||
- | LD BC, 1234h | + | ld bc, $1234 |
- | LD HL, BC | + | ld hl, bc |
- | LD SP, BC | + | ld sp, bc |
- | EX DE, HL | + | ex de, hl |
- | EX BC, DE | + | ex bc, de |
- | ADD HL, BC | + | add hl, bc |
- | ADD DE, BC | + | add de, bc |
</ | </ | ||
Línea 211: | Línea 81: | ||
<code z80> | <code z80> | ||
- | LD SP, BC ; NO: No se puede cargar el valor un registro en SP, | + | ld sp, bc ; NO: No se puede cargar el valor un registro en SP, |
- | ; sólo se puede cargar un valor inmediato NN | + | ; sólo se puede cargar un valor inmediato NN |
- | EX BC, DE ; NO: Existe | + | ex bc, de ; NO: Existe |
- | ADD DE, BC ; NO: Sólo se puede usar HL como operando destino | + | add de, bc ; NO: Sólo se puede usar HL como operando destino |
- | ; en las sumas de 16 bytes con registros de propósito | + | ; en las sumas de 16 bytes con registros de propósito |
- | ; general. Una alternativa sería: | + | ; general. Una alternativa sería: |
- | ; | + | ; |
- | ; LD HL, 0 ; HL = 0 | + | ; ld hl, 0 ; HL = 0 |
- | ; ADD HL, BC ; HL = HL + BC | + | ; add hl, bc ; HL = HL + BC |
- | ; EX DE, HL ; Intercambiamos el valor de HL y DE | + | ; ex de, hl ; Intercambiamos el valor de HL y DE |
- | LD BC, DE ; NO:, pero se pueden tomar alternativas, | + | ld bc, de ; NO:, pero se pueden tomar alternativas, |
- | ; | + | ; |
- | ; PUSH DE | + | ; push de |
- | ; POP BC | + | ; pop bc |
+ | : | ||
+ | ; o también: | ||
+ | ; | ||
+ | ; ld b, d | ||
+ | ; ld c, e | ||
- | LD DE, HL ; NO: mismo caso anterior. | + | ld de, hl ; NO: mismo caso anterior. |
- | LD SP, BC ; NO: no existe como instrucción. | + | ld sp, bc ; NO: no existe como instrucción. |
</ | </ | ||
Línea 237: | Línea 112: | ||
No os preocupéis: | No os preocupéis: | ||
+ | |||
+ | \\ | ||
+ | ==== IY Y LA ROM DEL SPECTRUM ==== | ||
+ | |||
+ | Aunque IY es un registro de 16 bits como los demás, en el caso del Spectrum no está disponible para su uso a menos que se cumplan ciertas condiciones. | ||
+ | |||
+ | La ROM del Spectrum (el " | ||
+ | |||
+ | Estas " | ||
+ | |||
+ | Debido a esto, no debemos modificar el registro IY, a menos que cumplamos estas 2 condiciones: | ||
+ | |||
+ | - Cambiamos el modo de funcionamiento del Spectrum de im1 (su modo por defecto) a im2 (un modo " | ||
+ | - No llamamos ni utilizamos ninguna rutina de la ROM para ninguna tarea. | ||
+ | |||
+ | Ambas cosas son el procedimiento habitual en un juego o programa, por lo que podremos utilizar el registro IY en nuestros programas, pero no inicialmente. A lo largo del curso no lo usaremos ya que vamos a utilizar rutinas de la ROM de apoyo para la mayoría de nuestros ejemplos. | ||
+ | |||
\\ | \\ | ||
===== El registro de flags ===== | ===== El registro de flags ===== | ||
- | Hemos hablado del registro de 8 bits F como un registro especial. La particularidad de //F// es que //no es un registro de propósito general// donde podamos introducir valores a voluntad, sino que los diferentes bits del registro F tienen un significado propio que cambia automáticamente según el resultado de operaciones anteriores. | + | Hemos hablado del registro de 8 bits **F** como un registro especial. La particularidad de //F// es que //no es un registro de propósito general// donde podamos introducir valores a voluntad, sino que los diferentes bits del registro F tienen un significado propio que cambia automáticamente según el resultado de operaciones anteriores. |
Por ejemplo, uno de los bits del registro F, el bit nº 6, es conocido como "Zero Flag", y nos indica si el resultado de la última operación (para determinadas operaciones, | Por ejemplo, uno de los bits del registro F, el bit nº 6, es conocido como "Zero Flag", y nos indica si el resultado de la última operación (para determinadas operaciones, | ||
Línea 249: | Línea 141: | ||
Veamos los diferentes registros de flags (bits del registro F) y su utilidad: | Veamos los diferentes registros de flags (bits del registro F) y su utilidad: | ||
+ | \\ | ||
{{ cursos: | {{ cursos: | ||
+ | \\ | ||
+ | \\ | ||
* **Flag S (sign o signo)**: Este flag se pone a uno si el resultado de la operación realizada en complemento a dos es negativo (es una copia del bit más significativo del resultado). Si por ejemplo realizamos una suma entre 2 números en complemento a dos y el resultado es negativo, este bit se pondrá a uno. Si el resultado es positivo, se pondrá a cero. Es útil para realizar operaciones matemáticas entre múltiples registros: por ejemplo, si nos hacemos una rutina de multiplicación o división de números que permita números negativos, este bit nos puede ser útil en alguna parte de la rutina. | * **Flag S (sign o signo)**: Este flag se pone a uno si el resultado de la operación realizada en complemento a dos es negativo (es una copia del bit más significativo del resultado). Si por ejemplo realizamos una suma entre 2 números en complemento a dos y el resultado es negativo, este bit se pondrá a uno. Si el resultado es positivo, se pondrá a cero. Es útil para realizar operaciones matemáticas entre múltiples registros: por ejemplo, si nos hacemos una rutina de multiplicación o división de números que permita números negativos, este bit nos puede ser útil en alguna parte de la rutina. | ||
- | * **Flag Z (zero o cero)**: Este flag se pone a uno si el resultado de la última operación que afecte a los flags es cero. Por ejemplo, si realizamos una operación matemática y el resultado es cero, se pondrá a uno. Este flag es uno de los más útiles, ya que podemos utilizarlo para múltiples tareas. La primera es para los bucles, ya que podremos programar código como: | ||
+ | * **Flag Z (zero o cero)**: Este flag se pone a uno si el resultado de la última operación que afecte a los flags es cero. Por ejemplo, si realizamos una operación matemática y el resultado es cero, se pondrá a uno. Este flag es uno de los más útiles, ya que podemos utilizarlo para múltiples tareas. La primera es para los bucles, ya que podremos programar código como: | ||
<code z80> | <code z80> | ||
; Repetir algo 100 veces | ; Repetir algo 100 veces | ||
- | | + | |
bucle: | bucle: | ||
- | (...) ; código | + | (...) ; código |
- | | + | |
- | | + | |
- | ; Si el resultado de la operación anterior no es cero (NZ = Non Zero), | + | |
- | ; saltar a la etiqueta bucle y continuar. | + | ; Si el resultado de la operación anterior no es cero (NZ = Non Zero), |
- | ; se ponga a 1 cuando B llegue a cero, lo que afectará al JR NZ. | + | ; saltar a la etiqueta bucle y continuar. |
+ | ; se ponga a 1 cuando B llegue a cero, lo que afectará al jr NZ. | ||
; Como resultado, este trozo de código (...) se ejecutará 100 veces. | ; Como resultado, este trozo de código (...) se ejecutará 100 veces. | ||
</ | </ | ||
- | Como veremos en su momento, existe una instrucción equivalente a DEC B + JR NZ que es más cómoda de utilizar y más rápida que estas 2 instrucciones juntas (DJNZ), pero se ha elegido el ejemplo que tenéis arriba para que veáis cómo muchas operaciones (en este caso DEC) afectan a los flags, y la utilidad que estos tienen a la hora de programar. | + | Como veremos en su momento, existe una instrucción equivalente a '' |
Además de para bucles, también podemos utilizarlo para comparaciones. Supongamos que queremos hacer en ensamblador una comparación de igualdad, algo como: | Además de para bucles, también podemos utilizarlo para comparaciones. Supongamos que queremos hacer en ensamblador una comparación de igualdad, algo como: | ||
Línea 282: | Línea 178: | ||
<code z80> | <code z80> | ||
- | LD A, C | + | ld a, c ; A = C |
; Tenemos que hacer esto porque no existe | ; Tenemos que hacer esto porque no existe | ||
- | ; una instruccion | + | ; una instruccion |
; restar un registro al registro A. | ; restar un registro al registro A. | ||
- | SUB B | + | sub b ; A = A-B |
- | JP Z, Es_Igual | + | jp z, Es_Igual |
- | JP NZ, No_Es_Igual | + | jp nz, No_Es_Igual |
(...) | (...) | ||
Línea 298: | Línea 194: | ||
</ | </ | ||
- | | + | |
+ | |||
+ | | ||
* **Flag P/V (Parity/ | * **Flag P/V (Parity/ | ||
Línea 308: | Línea 206: | ||
Así pues, resumiendo: | Así pues, resumiendo: | ||
- | * El registro F es un registro | + | * El registro F es un registro cuyo valor no manejamos directamente, |
* Por ejemplo, si realizamos una operación y el resultado de la misma es cero, se pondrá a 1 el flag de Zero (Z) del registro F, que no es más que su bit número 6. | * Por ejemplo, si realizamos una operación y el resultado de la misma es cero, se pondrá a 1 el flag de Zero (Z) del registro F, que no es más que su bit número 6. | ||
* No todas las operaciones afectan a los flags, iremos viendo qué operaciones afectan a qué flags conforme avancemos en el curso, en el momento en que se estudia cada instrucción. | * No todas las operaciones afectan a los flags, iremos viendo qué operaciones afectan a qué flags conforme avancemos en el curso, en el momento en que se estudia cada instrucción. | ||
Línea 314: | Línea 212: | ||
\\ | \\ | ||
- | ===== Instrucciones LD ===== | + | ===== Instrucciones LD (instrucciones de carga) |
- | Las operaciones que más utilizaremos en nuestros programas en ensamblador serán sin duda las operaciones de carga o instrucciones | + | Las operaciones que más utilizaremos en nuestros programas en ensamblador serán sin duda las operaciones de carga o instrucciones |
* Meter un valor en un registro. | * Meter un valor en un registro. | ||
Línea 324: | Línea 222: | ||
* Asignarle a un registro el contenido de una dirección de memoria. | * Asignarle a un registro el contenido de una dirección de memoria. | ||
- | La sintaxis de LD en lenguaje ensamblador es: | + | La sintaxis de '' |
<code z80> | <code z80> | ||
- | | + | ld DESTINO, ORIGEN |
</ | </ | ||
Línea 333: | Línea 231: | ||
* Asignar a un registro un valor numérico directo de 8 o 16 bits. | * Asignar a un registro un valor numérico directo de 8 o 16 bits. | ||
+ | |||
<code z80> | <code z80> | ||
- | LD A, 10 | + | ld a, 10 ; A = 10 |
- | LD B, 200 ; B = 200 | + | ld b, 200 |
- | LD BC, 12345 | + | ld bc, 12345 ; BC = 12345 |
</ | </ | ||
* Copiar el contenido de un registro a otro registro: | * Copiar el contenido de un registro a otro registro: | ||
+ | |||
<code z80> | <code z80> | ||
- | LD A, B | + | ld a, b ; A = B |
- | LD BC, DE | + | ld bc, de ; BC = DE |
</ | </ | ||
+ | |||
* Escribir en posiciones de memoria: | * Escribir en posiciones de memoria: | ||
+ | |||
<code z80> | <code z80> | ||
- | LD (12345), | + | ld (12345), |
- | | + | ld (hl), 10 |
</ | </ | ||
+ | |||
* Leer el contenido de posiciones de memoria: | * Leer el contenido de posiciones de memoria: | ||
+ | |||
<code z80> | <code z80> | ||
- | LD A, (12345) | + | ld a, (12345) |
- | LD B, (HL) | + | ld b, (hl) ; B = valor en Memoria[valor de HL] |
</ | </ | ||
- | Nótese cómo el operador () nos permite acceder a memoria. En nuestros ejemplos, | + | |
+ | Nótese cómo el operador () nos permite acceder a memoria. En nuestros ejemplos, | ||
En un microprocesador con un juego de instrucciones ortogonal, se podría usar cualquier origen y cualquier destino sin distinción. En el caso del Z80 no es así. El listado completo de operaciones válidas con LD es el siguiente: | En un microprocesador con un juego de instrucciones ortogonal, se podría usar cualquier origen y cualquier destino sin distinción. En el caso del Z80 no es así. El listado completo de operaciones válidas con LD es el siguiente: | ||
Línea 366: | Línea 271: | ||
rr = registro de 16 bits (BC, DE, HL, SP) | rr = registro de 16 bits (BC, DE, HL, SP) | ||
ri = registro índice (IX o IY). | ri = registro índice (IX o IY). | ||
+ | | ||
</ | </ | ||
Línea 371: | Línea 277: | ||
<code z80> | <code z80> | ||
- | ; Carga de valores en registros | + | ; Carga de valores en registros |
- | LD r, N | + | ld r, N |
- | LD rr, NN | + | ld rr, NN |
- | LD ri, NN | + | ld ri, NN |
- | ; Copia de un registro a otro | + | ; Copia de un registro a otro |
- | LD r, r | + | ld r, r |
- | LD rr, rr | + | ld rr, rr |
- | ; Acceso a memoria | + | ; Acceso a memoria |
- | LD r, (HL) | + | ld r, (hl) |
- | LD (NN), A | + | ld (NN), a |
- | LD (HL), N | + | ld (hl), N |
- | LD A, (rr) ; (excepto rr=SP) | + | ld a, (rr) ; (excepto rr=SP) |
- | LD (rr), A ; (excepto rr=SP) | + | ld (rr), a ; (excepto rr=SP) |
- | LD A, (NN) | + | ld a, (NN) |
- | LD rr, (NN) | + | ld rr, (NN) |
- | LD ri, (NN) | + | ld ri, (NN) |
- | LD (NN), rr | + | ld (NN), rr |
- | LD (NN), ri | + | ld (NN), ri |
- | ; Acceso indexado a memoria | + | ; Acceso indexado a memoria |
- | LD (ri+N), r | + | ld (ri+N), r |
- | LD r, (ri+N) | + | ld r, (ri+N) |
- | LD (ri+N), N | + | ld (ri+N), N |
</ | </ | ||
Línea 401: | Línea 307: | ||
<code z80> | <code z80> | ||
- | ; Manipulación del puntero de pila (SP) | + | ; Manipulación del puntero de pila (SP) |
- | LD SP, ri | + | ld sp, ri |
- | LD SP, HL | + | ld sp, hl |
- | ; Para manipular el registro I | + | ; Para manipular el registro I |
- | LD A, I | + | ld a, i |
- | LD I, A | + | ld i, a |
- | ; Para manipular el registro R | + | ; Para manipular el registro R |
- | LD A, R | + | ld a, r |
- | LD R, A | + | ld r, a |
</ | </ | ||
Línea 417: | Línea 323: | ||
<code z80> | <code z80> | ||
- | ; Carga de valores en registros | + | ; Carga de valores en registros |
- | ; registro_destino = valor | + | ; registro_destino = valor |
- | LD A, 100 ; LD r, N | + | ld a, 100 |
- | LD BC, 12345 | + | ld bc, 12345 ; ld rr, NN |
- | ; Copia de registros en registros | + | ; Copia de registros en registros |
- | ; registro_destino = registro_origen | + | ; registro_destino = registro_origen |
- | LD B, C | + | ld b, c ; ld r, r |
- | LD A, B | + | ld a, b ; ld r, r |
- | LD BC, DE | + | ld bc, de ; ld rr, rr |
- | ; Acceso a memoria | + | ; Acceso a memoria |
- | ; (Posicion_memoria) = VALOR o bien | + | ; (Posicion_memoria) = VALOR o bien |
- | ; | + | ; Registro = VALOR en (Posicion de memoria) |
- | LD A, (HL) | + | ld a, (hl) ; ld r, (rr) |
- | LD (BL), B ; LD (rr), r | + | ld (bc), a |
- | LD (12345), | + | ld (12345), |
- | LD A, (HL) | + | ld a, (hl) ; ld r, (rr) |
- | LD (DE), A ; LD (rr), r | + | ld (de), a |
- | LD (BC), 1234h | + | ld (bc), 1234h ; ld (bc), NN |
- | LD (12345), | + | ld (12345), |
- | LD IX, (12345) | + | ld ix, (12345) |
- | LD (34567), | + | ld (34567), |
- | ; Acceso indexado a memoria | + | ; Acceso indexado a memoria |
- | ; (Posicion_memoria) = VALOR o VALOR = (Posicion_memoria) | + | ; (Posicion_memoria) = VALOR o VALOR = (Posicion_memoria) |
- | ; Donde la posicion es IX+N o IY+N: | + | ; Donde la posicion es IX+N o IY+N: |
- | LD (IX+10), A | + | ld (ix+10), a ; LD (ri+N), r |
- | LD A, (IY+100) | + | ld a, (iy+100) ; ld r, (ri+N) |
- | LD (IX-30), 100 ; LD (ri+N), N | + | ld (ix-30), 100 |
</ | </ | ||
- | Haré hincapié de nuevo en el mismo detalle: debido a que el juego de instrucciones del Z80 no es ortogonal, en ocasiones no podemos ejecutar ciertas operaciones que podrían sernos útiles con determinados registros. En ese caso tendremos que buscar una solución mediante los registros y operaciones válidas de que disponemos. | + | |
- | Un detalle muy importante respecto a las instrucciones de carga: en el caso de las operaciones LD, el registro F no ve afectado ninguno de sus indicadores o flags en relación al resultado de la ejecución de las mismas (salvo en el caso de "LD A, I" | + | Un detalle muy importante respecto a las instrucciones de carga: |
- | + | ||
- | Esto quiere decir que una operación como "LD A, 0", por ejemplo, no activará el flag de Zero del registro F. | + | |
+ | < | ||
+ | Flags | ||
+ | | ||
+ | | ||
+ | ld r, r |- - - - - -| | ||
+ | ld r, N |- - - - - -| | ||
+ | ld rr, rr |- - - - - -| | ||
+ | ld (rr), n |- - - - - -| | ||
+ | ld (rr), n |- - - - - -| | ||
+ | ld ri, (NN) |- - - - - -| | ||
+ | ld (NN), ri |- - - - - -| | ||
+ | ld (ri+d), N |- - - - - -| | ||
+ | ld (ri+d), r |- - - - - -| | ||
+ | ld r, (ri+d) | ||
+ | ld a, i |* * 0 * 1 0| | ||
+ | ld a, r |* * 0 * 1 0| | ||
+ | </ | ||
\\ | \\ | ||
- | ===== CPU Z80: Low Endian ===== | ||
- | Un detalle más sobre nuestra CPU: a la hora de trabajar con datos de 16 bits (por ejemplo, leer o escribir | + | Esto quiere decir, y es muy importante, que una operación como '' |
+ | |||
+ | Hay otros dos datos que, como la afectación | ||
+ | |||
+ | Uno es el tamaño en bytes de cada instrucción, | ||
+ | |||
+ | Del mismo modo, tenemos el tiempo | ||
- | ^ Posición ^ Valor ^ | + | En nuestro ejemplo anterior, el '' |
- | | 0000h | 34h | | + | |
- | | 0001h | 12h | | + | |
- | En otro tipo de procesadores | + | En estos capítulos iniciales |
- | ^ Posición ^ Valor ^ | + | En el último capítulo dedicado a las diferentes instrucciones veremos una tabla donde se detallan todos los tamaños y tiempos de las diferentes instrucciones. |
- | | 0000h | 12h | | + | |
- | | 0001h | 34h | | + | |
- | Debemos tener en cuenta | + | Un apunte sobre '' |
\\ | \\ | ||
===== Incrementos y decrementos ===== | ===== Incrementos y decrementos ===== | ||
- | Entre las operaciones disponibles, | + | Entre las operaciones disponibles, |
Por ejemplo: | Por ejemplo: | ||
<code z80> | <code z80> | ||
- | LD A, 0 ; A = 0 | + | ld a, 0 ; A = 0 |
- | INC A | + | inc a |
- | LD B, A | + | ld b, a |
- | INC B | + | inc b |
- | INC B | + | inc b |
- | LD BC, 0 | + | ld bc, 0 |
- | INC BC ; BC = 0001h | + | inc bc ; BC = $0001 |
- | INC B | + | inc b |
- | DEC A | + | dec a |
</ | </ | ||
- | Veamos las operaciones INC y DEC permitidas: | + | Veamos las operaciones |
<code z80> | <code z80> | ||
- | | + | inc r |
- | DEC r | + | dec r |
- | INC rr | + | inc rr |
- | DEC rr | + | dec rr |
</ | </ | ||
Línea 503: | Línea 425: | ||
<code z80> | <code z80> | ||
- | | + | inc (hl) |
- | DEC (HL) | + | dec (hl) |
</ | </ | ||
Línea 510: | Línea 432: | ||
<code z80> | <code z80> | ||
- | | + | inc (ix+n) |
- | DEC (IX+N) | + | dec (ix+n) |
- | INC (IY+N) | + | inc (iy+n) |
- | DEC (IY+N) | + | dec (iy+n) |
</ | </ | ||
Línea 521: | Línea 443: | ||
<code z80> | <code z80> | ||
- | INC A | + | inc a ; A = A+1 |
- | DEC B | + | dec b ; B = B-1 |
- | INC DE ; DE = DE+1 | + | inc de |
- | DEC IX ; IX = IX-1 | + | dec ix |
- | INC (HL) | + | inc (hl) ; (HL) = (HL)+1 |
- | INC (IX-5) | + | inc (ix-5) ; (IX-5) = (IX-5)+1 |
- | DEC (IY+100) | + | dec (iy+100) ; (IY+100) = (IY+100)+1 |
</ | </ | ||
Unos apuntes sobre la afectación de los flags ante el uso de INC y DEC: | Unos apuntes sobre la afectación de los flags ante el uso de INC y DEC: | ||
- | * Si un registro de 8 bits vale 255 (FFh) y lo incrementamos, | + | * Si un registro de 8 bits vale 255 ($ff) y lo incrementamos, |
- | * Si un registro de 16 bits vale 65535 (FFFFh)y lo incrementamos, | + | * Si un registro de 16 bits vale 65535 ($ffff) y lo incrementamos, |
- | * Si un registro de 8 bits vale 0 y lo decrementamos, | + | * Si un registro de 8 bits vale 0 y lo decrementamos, |
- | * Si un registro de 16 bits vale 0 (0h) y lo decrementamos, | + | * Si un registro de 16 bits vale 0 ($0) y lo decrementamos, |
* En estos desbordamientos no se tomará en cuenta para nada el bit de Carry (acarreo) de los flags (registro F), ni tampoco lo afectarán tras ejecutarse. | * En estos desbordamientos no se tomará en cuenta para nada el bit de Carry (acarreo) de los flags (registro F), ni tampoco lo afectarán tras ejecutarse. | ||
- | * Las operaciones INC y DEC sobre registros de 16 bits (BC, DE, HL, IX, IY, SP) no afectan a los flags. Esto implica que no podemos usar como condición de flag zero para un salto el resultado de instrucciones como "DEC BC", por ejemplo. | + | * Las operaciones INC y DEC sobre registros de 16 bits (BC, DE, HL, IX, IY, SP) no afectan a los flags. Esto implica que no podemos usar como condición de flag zero para un salto el resultado de instrucciones como "dec bc", por ejemplo. |
* Las operaciones INC y DEC sobre registros de 8 bits y sobre la memoria no afectan al flag de acarreo, pero sí que pueden afectar al flag de Zero (Z), al de Paridad/ | * Las operaciones INC y DEC sobre registros de 8 bits y sobre la memoria no afectan al flag de acarreo, pero sí que pueden afectar al flag de Zero (Z), al de Paridad/ | ||
- | Lo siguiente que vamos a ver es una tabla de afectación de flags (que podréis ver en muchas tablas de instrucciones del Z80, y a las que conviene | + | Lo siguiente que vamos a ver es una tabla de afectación de flags (que encontraremos |
< | < | ||
Línea 546: | Línea 468: | ||
| | ||
| | ||
- | INC r |* * * V 0 -| | + | inc r |* * * V 0 -| |
- | INC [HL] |* * * V 0 -| | + | inc (hl) |* * * V 0 -| |
- | INC [ri+N] |* * * V 0 -| | + | inc (ri+N) |* * * V 0 -| |
- | INC rr |- - - - - -| | + | inc rr |- - - - - -| |
- | DEC r |* * * V 1 -| | + | dec r |* * * V 1 -| |
- | DEC rr |- - - - - -| | + | dec rr |- - - - - -| |
</ | </ | ||
Línea 577: | Línea 499: | ||
===== Operaciones matematicas ===== | ===== Operaciones matematicas ===== | ||
- | Las operaciones aritméticas básicas para nuestro Spectrum son la suma y la resta, tanto con acarreo como sin él. A partir de ellas deberemos crearnos nuestras propias rutinas para multiplicar, | + | Las operaciones aritméticas básicas para nuestro Spectrum son la suma y la resta, tanto con acarreo como sin él. A partir de ellas deberemos crearnos nuestras propias rutinas para multiplicar, |
\\ | \\ | ||
==== Suma: ADD (Add) ==== | ==== Suma: ADD (Add) ==== | ||
- | Nuestro microprocesador Z80 puede realizar sumas de 8 y 16 bits internamente. La instrucción utilizada para ello es "**ADD**" | + | Nuestro microprocesador Z80 puede realizar sumas de 8 y 16 bits internamente. La instrucción utilizada para ello es '' |
<code z80> | <code z80> | ||
- | | + | add DESTINO, ORIGEN |
</ | </ | ||
Línea 591: | Línea 513: | ||
<code z80> | <code z80> | ||
- | ADD A, s | + | add a, s |
- | ADD HL, ss | + | add hl, ss |
- | ADD ri, rr | + | add ri, rr |
</ | </ | ||
Línea 599: | Línea 521: | ||
< | < | ||
- | | + | |
- | | + | |
- | en complemento a dos), cualquier dirección de memoria apuntada por | + | en complemento a dos), cualquier dirección de memoria apuntada por |
- | HL, y cualquier dirección de memoria apuntada por un registro | + | HL, y cualquier dirección de memoria apuntada por un registro |
| | ||
ss: Cualquier registro de 16 bits de entre los siguientes: BC, DE, HL, SP. | ss: Cualquier registro de 16 bits de entre los siguientes: BC, DE, HL, SP. | ||
ri: Uno de los 2 registros índices (IX o IY). | ri: Uno de los 2 registros índices (IX o IY). | ||
- | rr: Cualquier registro de 16 bits de entre los siguientes excepto el mismo | + | rr: Cualquier registro de 16 bits de entre los siguientes excepto el mismo |
| | ||
</ | </ | ||
Línea 613: | Línea 535: | ||
<code z80> | <code z80> | ||
- | ; ADD A, s | + | ; add a, s |
- | ADD A, B | + | add a, b ; A = A + B |
- | ADD A, 100 ; A = A + 100 | + | add a, 100 |
- | ADD A, [HL] ; A = A + [HL] | + | add a, (hl) |
- | ADD A, [IX+10] | + | add a, (ix+10) ; A = A + (IX+10) |
- | ; ADD HL, ss | + | ; add hl, ss |
- | ADD HL, BC | + | add hl, bc ; HL = HL + BC |
- | ADD HL, SP | + | add hl, sp ; HL = HL + SP |
- | ; ADD ri, rr | + | ; addri, rr |
- | ADD IX, BC | + | add ix, bc ; IX = IX + BC |
- | ADD IY, DE | + | add iy, de ; IY = IY + DE |
- | ADD IY, IX | + | add iy, ix ; IY = IY + IX |
- | ADD IX, IY | + | add ix, iy ; IX = IX + IY |
</ | </ | ||
Línea 633: | Línea 555: | ||
<code z80> | <code z80> | ||
- | ADD B, C | + | add b, c ; Sólo A puede ser destino |
- | ADD BC, DE | + | add bc, de ; Sólo puede ser destino HL |
- | ADD IX, IX | + | add ix, ix ; No podemos sumar un registro índice a él mismo |
</ | </ | ||
La afectación de los flags ante las operaciones de sumas es la siguiente: | La afectación de los flags ante las operaciones de sumas es la siguiente: | ||
- | | + | * Para '' |
- | * Para "ADD HL, ss" | + | |
+ | * Para '' | ||
O, en forma de tabla de afectación: | O, en forma de tabla de afectación: | ||
Línea 649: | Línea 572: | ||
| | ||
| | ||
- | ADD A, s |* * * V 0 *| | + | add a, s |* * * V 0 *| |
- | ADD HL, ss |- - ? - 0 *| | + | add hl, ss |- - ? - 0 *| |
- | ADD ri, rr |- - ? - 0 *| | + | add ri, rr |- - ? - 0 *| |
</ | </ | ||
Línea 657: | Línea 580: | ||
< | < | ||
- | 0 + 0 = 0 | + | 0 + 0 = 0 |
- | 0 + 1 = 1 | + | 0 + 1 = 1 |
- | 1 + 0 = 1 | + | 1 + 0 = 1 |
- | 1 + 1 = 10 (=0 con acarreo) | + | 1 + 1 = 10 (=0 con acarreo) |
</ | </ | ||
Línea 680: | Línea 603: | ||
<code z80> | <code z80> | ||
- | LD A, %10000000 | + | ld a, %10000000 |
- | LD B, %10000000 | + | ld b, %10000000 |
- | ADD A, B | + | add a, b |
</ | </ | ||
Línea 690: | Línea 613: | ||
==== Resta: SUB (Substract) ==== | ==== Resta: SUB (Substract) ==== | ||
- | En el caso de las restas, sólo es posible realizar (de nuevo gracias a la no ortogonalidad del J.I.) la operación " | + | En el caso de las restas, sólo es posible realizar (de nuevo gracias a la no ortogonalidad del J.I. del Z80) la operación " |
<code z80> | <code z80> | ||
- | | + | sub ORIGEN |
</ | </ | ||
Línea 699: | Línea 622: | ||
<code z80> | <code z80> | ||
- | | + | sub r ; A = A - r |
- | SUB N ; A = A - N | + | sub N ; A = A - N |
- | SUB [HL] ; A = A - [HL] | + | sub (hl) ; A = A - (HL) |
- | SUB [rr+d] ; A = A - [rr+d] | + | sub (rr+d) ; A = A - (rr+d) |
</ | </ | ||
Línea 708: | Línea 631: | ||
<code z80> | <code z80> | ||
- | SUB B | + | sub b ; A = A - B |
- | SUB 100 ; A = A - 100 | + | sub 100 |
- | SUB [HL] ; A = A - [HL] | + | sub (hl) |
- | SUB [IX+10] | + | sub (ix+10) ; A = A - (IX+10) |
</ | </ | ||
- | Es importante recordar que en una operación | + | Es importante recordar que en una operación |
- | Por otra parte, con respecto a la afectación de flags, | + | Por otra parte, con respecto a la afectación de flags, |
< | < | ||
Línea 729: | Línea 652: | ||
==== Suma con acarreo: ADC (Add with carry) ==== | ==== Suma con acarreo: ADC (Add with carry) ==== | ||
- | Sumar con acarreo dos elementos (**ADC**) significa realizar la suma de uno con el otro y, posteriormente, | + | Sumar con acarreo dos elementos ('' |
< | < | ||
- | "ADC A, s" | + | "adc a, s" |
- | " | + | "adc hl, ss" |
</ | </ | ||
Línea 744: | Línea 667: | ||
Instrucción | Instrucción | ||
| | ||
- | ADC A,s |* * * V 0 *| | + | adc a,s |* * * V 0 *| |
- | ADC HL,ss |* * ? V 0 *| | + | adc hl,ss |* * ? V 0 *| |
</ | </ | ||
- | La suma con acarreo se utiliza normalmente para sumar las partes altas de palabras | + | La suma con acarreo se utiliza normalmente para sumar las partes altas de elementos |
\\ | \\ | ||
==== Resta con acarreo: SBC (Substract with carry) ==== | ==== Resta con acarreo: SBC (Substract with carry) ==== | ||
- | Al igual que en el caso de la suma con acarreo, podemos realizar restas con acarreo (**SBC**), que no son más que realizar una resta de los 2 operandos, tras lo cual restamos además el valor del bit de Carry Flag: | + | Al igual que en el caso de la suma con acarreo, podemos realizar restas con acarreo ('' |
< | < | ||
- | "SBC A, s" | + | "sbc a, s" |
- | " | + | "sbc hl, ss" |
</ | </ | ||
Línea 766: | Línea 690: | ||
Instrucción | Instrucción | ||
| | ||
- | SBC A,s |* * * V 1 *| | + | sbc a,s |* * * V 1 *| |
- | SBC HL,ss |* * ? V 1 *| | + | sbc hl,ss |* * ? V 1 *| |
</ | </ | ||
Línea 809: | Línea 733: | ||
</ | </ | ||
- | Como veremos en unos minutos, se eligió este sistema para representar los números negativos para que las operaciones matemáticas estándar funcionaran directamente sobre los números positivos y negativos. ¿Por qué no utilizamos directamente la inversión de los bits para representar los números negativos y estamos sumando además 1 para obtenerlos? Sencillo: si no sumáramos uno y simplemente invirtiéramos los bits, tendríamos 2 ceros (00000000 y 11111111) y además las operaciones matemáticas no cuadrarían (por culpa de los dos ceros). La gracia del complemento a dos es que las sumas y restas binarias lógicas (ADD, ADC, SUB y SBC) funcionan: | + | Se eligió este sistema para representar los números negativos para que las operaciones matemáticas estándar funcionaran directamente sobre los números positivos y negativos. ¿Por qué no utilizamos directamente la inversión de los bits para representar los números negativos y estamos sumando además 1 para obtenerlos? Sencillo: si no sumáramos uno y simplemente invirtiéramos los bits, tendríamos 2 ceros (00000000 y 11111111) y además las operaciones matemáticas no cuadrarían (por culpa de los dos ceros). La gracia del complemento a dos es que las sumas y restas binarias lógicas (ADD, ADC, SUB y SBC) funcionan: |
| | ||
Línea 848: | Línea 772: | ||
Como acabamos de ver, en complemento a dos el último bit (el bit 7) nos indica el signo, y cuando operamos con 2 números binarios que nosotros interpretamos como números en complemento a dos no nos basta con el bit de Carry. Es el bit de Overflow el que nos dará información sobre el desbordamiento a un nivel lógico. | Como acabamos de ver, en complemento a dos el último bit (el bit 7) nos indica el signo, y cuando operamos con 2 números binarios que nosotros interpretamos como números en complemento a dos no nos basta con el bit de Carry. Es el bit de Overflow el que nos dará información sobre el desbordamiento a un nivel lógico. | ||
- | En pocas palabras, el bit de Carry se activará si pasamos de 255 a 0 o de 0 a 255 (comportándose como un bit de valor 2 elevado a 8, o 256), y el bit de overflow lo hará si el resultado de una operación en complemento a dos requiere más de 7 bits para ser representado. | + | |
Mediante ejemplos: | Mediante ejemplos: | ||
Línea 878: | Línea 802: | ||
En el ejemplo anterior, V se activa porque no ha habido desbordamiento físico (no es necesario un bit extra para representar la operación), | En el ejemplo anterior, V se activa porque no ha habido desbordamiento físico (no es necesario un bit extra para representar la operación), | ||
+ | |||
\\ | \\ | ||
Línea 884: | Línea 809: | ||
Como ya se ha explicado, disponemos de un banco de registros alternativos (los Shadow Registers), y podemos conmutar los valores entre los registros estándar y los alternativos mediante unas determinadas instrucciones del Z80. | Como ya se ha explicado, disponemos de un banco de registros alternativos (los Shadow Registers), y podemos conmutar los valores entre los registros estándar y los alternativos mediante unas determinadas instrucciones del Z80. | ||
- | El Z80 nos proporciona una serie de registros de propósito general (así como un registro de flags), de nombres A, B, C, D, E, F, H y L. El micro dispone también de unos registros extra (set alternativo conocido como Shadow Registers) de nombre A', B', C', D', E', F', H' y L', que aprovecharemos en cualquier momento de nuestro programa. No obstante, no podremos hacer uso directo de estos registros en instrucciones en ensamblador. No es posible, por ejemplo, ninguna de las siguientes instrucciones: | + | El Z80 nos proporciona una serie de registros de propósito general (así como un registro de flags), de nombres A, B, C, D, E, F, H y L. El micro dispone también de unos registros extra (set alternativo conocido como Shadow Registers) de nombre A', B', C', D', E', F', H' y L', que aprovecharemos en cualquier momento de nuestro programa. No obstante, no podremos hacer uso directo de estos registros en instrucciones en ensamblador. |
- | < | + | < |
- | LD B', $10 | + | ld b', $10 |
- | INC A' | + | inc a' |
- | LD HL', $1234 | + | ld hl', $1234 |
- | LD A', ($1234) | + | ld a', ($1234) |
</ | </ | ||
- | La manera de utilizar estos registros alternativos es conmutar sus valores con los registros estándar mediante la instrucción | + | La manera de utilizar estos registros alternativos es conmutar sus valores con los registros estándar mediante la instrucción |
+ | |< 60% >| | ||
^ Registro ^ Valor ^ Registro ^ Valor ^ | ^ Registro ^ Valor ^ Registro ^ Valor ^ | ||
- | | B | A0h | B' | 00h | | + | | B | $a0 | B' | $00 | |
- | | C | 55h | C' | 00h | | + | | C | $55 | C' | $00 | |
- | | D | 01h | D' | 00h | | + | | D | $01 | D' | $00 | |
- | | E | FFh | E' | 00h | | + | | E | $ff | E' | $00 | |
- | | H | 00h | H' | 00h | | + | | H | $00 | H' | $00 | |
- | | L | 31h | L' | 00h | | + | | L | $31 | L' | $00 | |
- | En el momento en que realicemos un EXX, los registros cambiarán de valor por la " | + | En el momento en que realicemos un '' |
+ | |< 60% >| | ||
^ Registro ^ Valor ^ Registro ^ Valor ^ | ^ Registro ^ Valor ^ Registro ^ Valor ^ | ||
- | | B | 00h | B' | A0h | | + | | B | $00 | B' | $a0 | |
- | | C | 00h | C' | 55h | | + | | C | $00 | C' | $55 | |
- | | D | 00h | D' | 01h | | + | | D | $00 | D' | $01 | |
- | | E | 00h | E' | FFh | | + | | E | $00 | E' | $ff | |
- | | H | 00h | H' | 00h | | + | | H | $00 | H' | $00 | |
- | | L | 00h | L' | 31h | | + | | L | $00 | L' | $31 | |
- | Si realizamos de nuevo EXX, volveremos a dejar los valores de los registros en sus " | + | Si realizamos de nuevo '' |
- | Aparte de la instrucción EXX, disponemos de una instrucción | + | Aparte de la instrucción |
+ | |< 60% >| | ||
^ Registro ^ Valor ^ Registro ^ Valor ^ | ^ Registro ^ Valor ^ Registro ^ Valor ^ | ||
- | | A | 01h | A' | 00h | | + | | A | $01 | A' | $00 | |
- | | F | 10h | F' | 00h | | + | | F | $10 | F' | $00 | |
a: | a: | ||
+ | |< 60% >| | ||
^ Registro ^ Valor ^ Registro ^ Valor ^ | ^ Registro ^ Valor ^ Registro ^ Valor ^ | ||
- | | A | 00h | A' | 01h | | + | | A | $00 | A' | $01 | |
- | | F | 00h | F' | 10h | | + | | F | $00 | F' | $10 | |
- | Realizando de nuevo un EX AF, AF' volveríamos a los valores originales en ambos registros. | + | Realizando de nuevo un '' |
- | De esta forma podemos disponer de un set de registros extra con los que trabajar. Por ejemplo, supongamos que programamos una porción de código donde queremos hacer una serie de cálculos entre registros y después dejar el resultado en una posición de memoria, pero no queremos perder los valores actuales de los registros (ni tampoco hacer uso de la pila, que veremos en su momento). En ese caso, podemos hacer: | + | De esta forma podemos disponer de un set de registros extra Acumulador/ |
<code z80> | <code z80> | ||
Línea 938: | Línea 866: | ||
; Cambiamos de banco de registros: | ; Cambiamos de banco de registros: | ||
- | | + | |
- | | + | |
; Hacemos nuestras operaciones | ; Hacemos nuestras operaciones | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
; (...etc...) | ; (...etc...) | ||
; (...aquí más operaciones...) | ; (...aquí más operaciones...) | ||
; Grabamos el resultado en memoria | ; Grabamos el resultado en memoria | ||
- | | + | |
- | ; Recuperamos | + | ; Recuperamos los registros: |
- | | + | |
- | | + | |
; Volvemos al lugar de llamada de la rutina | ; Volvemos al lugar de llamada de la rutina | ||
- | | + | |
</ | </ | ||
- | Aparte | + | También podríamos utilizar la pila para almacenar valores como resultado |
+ | <code z80> | ||
+ | exx | ||
+ | |||
+ | ; ... Realizamos una serie de operaciones complejas ... | ||
+ | |||
+ | ; Guardamos en la pila el valor de HL | ||
+ | push hl | ||
+ | |||
+ | ; Recuperamos el juego de registros original | ||
+ | exx | ||
+ | |||
+ | ; Obtenemos de la pila el valor calculado | ||
+ | pop hl | ||
+ | ret | ||
+ | </ | ||
+ | |||
+ | Además de '' | ||
+ | |||
+ | |< 50% >| | ||
^ Instrucción ^ Resultado ^ | ^ Instrucción ^ Resultado ^ | ||
- | | EX DE, HL | Intercambiar los valores de DE y HL. | | + | | ex de, hl | Intercambiar los valores de DE y HL. | |
- | | EX (SP), HL | Intercambiar el valor de HL con el valor de 16 bits de la posición de memoria apuntada por el registro SP (por ejemplo, para intercambiar el valor de HL con el del último registro que hayamos introducido en la pila). | | + | | ex (sp), hl | Intercambiar el valor de HL con el valor de 16 bits\\ de la posición de memoria apuntada por el registro SP\\ (por ejemplo, para intercambiar el valor de HL con el\\ del último registro que hayamos introducido en la pila). | |
- | | EX (SP), IX | Igual que el anterior, pero con IX. | | + | | ex (sp), ix | Igual que el anterior, pero con IX. | |
- | | EX (SP), IY | Igual que el anterior, pero con IY. | | + | | ex (sp), iy | Igual que el anterior, pero con IY. | |
- | La primera de estas instrucciones nos puede ser muy útil en nuestros programas en ensamblador, | + | La primera de estas instrucciones nos resultará |
- | Como ya hemos comentado cuando hablamos del carácter Low-Endian de nuestra CPU, al escribir en memoria (también en la pila) primero se escribe el Byte Bajo y luego el Byte Alto. Posteriormente lo leeremos de la misma forma, de tal modo que si los bytes apuntados en la pila (en memoria) son "FF 00h", al hacer el EX (SP), HL, el registro HL valdrá | + | Como ya hemos comentado cuando hablamos del carácter Low-Endian de nuestra CPU, al escribir en memoria (también en la pila) primero se escribe el Byte Bajo y luego el Byte Alto. Posteriormente lo leeremos de la misma forma, de tal modo que si los bytes apuntados en la pila (en memoria) son **$ff $00**, al hacer el '' |
Nótese que aprovechando la pila (como veremos en su momento) también podemos intercambiar los valores de los registros mediante: | Nótese que aprovechando la pila (como veremos en su momento) también podemos intercambiar los valores de los registros mediante: | ||
<code z80> | <code z80> | ||
- | PUSH BC | + | push bc |
- | PUSH DE | + | push de |
- | POP BC | + | pop bc |
- | POP DE | + | pop de |
</ | </ | ||
- | Si queréis comprobarlo, podéis hacerlo mediante | + | O, con el siguiente |
<code z80> | <code z80> | ||
- | ; Ejemplo que muestra el intercambio de registros | + | push hl |
- | ; mediante el uso de la pila (PUSH/POP). | + | ld l, c |
- | ORG 40000 | + | ld h, b |
+ | pop bc | ||
+ | </ | ||
- | ; Cargamos en DE el valor 12345 y | + | En su momento veremos cómo funciona la pila, por ahora basta con saber que tenemos la posibilidad de intercambiar registros |
- | ; realizamos un intercambio de valores | + | |
- | ; con BC, mediante la pila: | + | |
- | LD DE, 12345 | + | |
- | LD BC, 0 | + | |
- | PUSH DE | + | \\ |
- | PUSH BC | + | ===== ¿Sirven de algo los Shadow Registers? ===== |
- | POP DE | + | |
- | POP BC | + | |
- | ; Volvemos, ahora BC=DE y DE=BC | + | Como hemos visto, con los Shadow Registers tenemos un set de registros adicional donde hacer cálculos, algo que parece en principio maravilloso si necesitamos más registros para realizar operaciones y no queremos acceder ni a memoria ni a la pila para almacenar datos o valores intermedios. |
- | RET | + | |
+ | Existen algunas restricciones para el uso de los Shadow Registers (pero que como veremos, no nos afectan en el Spectrum): Si la ROM de nuestro sistema, en su rutina de gestión de interrupciones (ISR) utiliza los Shadow Registers, necesitaremos deshabilitar las interrupciones con **di** (Disable Interrupts) antes de usar " | ||
+ | |||
+ | Pero los Shadow Registers tienen una desventaja muy grande y que ya hemos visto, y es que no podemos utilizarlos directamente (no existen instrucciones para operar con ellos) y tampoco podemos usarlos a la vez que los registros normales. | ||
+ | |||
+ | Lo único que podemos hacer es intercambiar los registros actuales con los alternativos y viceversa, con lo cual nuestra posibilidad de operar entre los registros normales y los Shadow es muy limitada. Sí, tenemos un set de registros extra para hacer operaciones, | ||
+ | |||
+ | Debemos almacenar estos valores en la pila, la memoria, o el registro AF (si ejecutamos '' | ||
+ | |||
+ | Es decir, que usar los Shadow Registers implica utilizar la memoria o la pila, de modo que si ya tenemos que hacer esto... ¿por qué no usar la memoria o la pila directamente para salvaguardar datos de nuestros registros normales cuando lo necesitemos? | ||
+ | |||
+ | Salvo excepciones, | ||
+ | |||
+ | Veamos los 3 ejemplos (sin usar exx): | ||
+ | |||
+ | **Opción 1.- Usar la pila:** | ||
+ | |||
+ | <code z80> | ||
+ | ld b, 8 | ||
+ | push bc ; Necesitamos salvaguardar | ||
+ | ld b, 9 ; (porque vamos a usarlo para algo) | ||
+ | |||
+ | ... hacer algo con BC ... | ||
+ | |||
+ | pop bc ; recuperar el valor de BC | ||
</ | </ | ||
- | Lo ensamblamos: | ||
- | < | + | **Opción 2.- Usar la memoria: |
- | pasmo --tapbas cambio.asm cambio.tap | + | |
+ | < | ||
+ | ld b, 8 | ||
+ | ld (1000), bc ; Necesitamos salvaguardar BC | ||
+ | ld b,9 ; (porque vamos a usarlo para algo) | ||
+ | |||
+ | ..... | ||
+ | |||
+ | ld (1002), bc ; guardamos el resultado | ||
+ | ld bc, (1000) | ||
+ | .... | ||
</ | </ | ||
- | Tras esto lo cargamos en un emulador de Spectrum (como un fichero TAP), nos vamos al BASIC y tecleamos "PRINT AT 10, 10; USR 40000". En pantalla aparecerá el valor "12345", ya que las rutinas llamadas desde BASIC devuelven sus resultados en BC, y nosotros hemos hecho un intercambio mediante la pila, entre DE y BC. | + | **Opción 3.- Usar "automodifying opcodes":** |
- | En su momento veremos cómo funciona la pila, por ahora basta con saber que tenemos la posibilidad | + | Esto es ligeramente más lento que usar PUSH y POP pero nos puede servir si no tenemos la pila disponible. En este caso, vamos a sobreescribir un instrucción |
+ | |||
+ | <code z80> | ||
+ | ld (save_bc+1), | ||
+ | ; opcode "ld bc, NN NN" en memoria | ||
+ | |||
+ | | ||
+ | |||
+ | 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í. | ||
+ | </ | ||
+ | |||
+ | El ejemplo anterior es muy interesante. Cuando hacemos el '' | ||
+ | |||
+ | En este caso '' | ||
+ | |||
+ | Con esto, estamos preservando el valor del registro a cambio | ||
\\ | \\ | ||
===== En resumen ===== | ===== En resumen ===== | ||
- | En esta entrega hemos visto la sintaxis de los programas en ensamblador (o, al menos, la sintaxis general de PASMO, el ensamblador que recomendamos), | + | |
Además, hemos comenzado a ver nuestras primeras instrucciones del lenguaje ensamblador, | Además, hemos comenzado a ver nuestras primeras instrucciones del lenguaje ensamblador, | ||
- | En la próxima entrega | + | En el próximo capítulo |
\\ | \\ | ||
===== Ficheros ===== | ===== Ficheros ===== | ||
- | * {{cursos: | ||
- | * {{cursos: | ||
* {{cursos: | * {{cursos: | ||
* {{cursos: | * {{cursos: | ||
Línea 1042: | Línea 1036: | ||
* [[http:// | * [[http:// | ||
* [[http:// | * [[http:// | ||
- | * [[http:// | ||
+ | \\ | ||
+ | **[ [[.: |