BIEVENID@S AL CURSO BÁSICO DE ASM 65816 POR DARKWIZARDX v1.1: [30/01/2005 19:16] Corregidos algunos pequeños errores. v1.2: [30/01/2006 19:44] Arreglados varios errores y agregadas algunas cosas. En este tutorial aprenderás lo básico e intermedio para programar en ASM 65816, el lenguaje del procesador 65c816 que se utilizó en la famosa Super Nintendo (Americana) y Super Famicom (japonesa), el aprender este lenguaje te servirá para descubrir algoritmos en los juegos (p.ej: saber cuánto daño hace un ataque en un rpg), cambiar gráficos, textos, tamaño de fuente y muchísimas otras cosas más, pero por ahora aprenderemos solo las cosas más básicas, los registros, los flags, la memoria, etc... Así que, bueno, al índice. :) INDICE: 0) Características de la SNES I) Introducción al ASM. Ia) Sistema Binario. Ib) Operaciones lógicas de binario. 1c) Números negativos II) Registros. IIa) Registro P. III) Modos de direccionamiento. IV) Instrucciones. V) Operaciones aritméticas. VI) Saltos y branchs. VII) Fin. 0) CARACTERÍSTICAS DE LA SNES Procesador: Motorola 65816 (16bits) Endianess: Big-Endian Velocidad de procesador: 2.68 o 3.58h MHz Máximos colores disponibles: 32768 Máximos colores en pantalla: 256 Resolución: 512 x 448 píxeles (64x56 tiles) Máximo tamaño de sprites: 64 x 64 píxeles (8x8 tiles) Máximos sprites: 128 Tamaño de cartucho: 2mbits (256Kilobytes) a 48mbits (6 Megabytes) Chip de sonido: Sony SP700 (8 canales de sonido) I) INTRODUCCION AL ASM El ASM (o assembler o en español, ensamblador) es un lenguaje denominado de bajo nivel porque trabaja a nivel máquina, lenguajes como Visual Basic y C/C++ son considerados de alto nivel ya que el programador opera directamente con el hardware mediante direcciones de memoria, pero esto lo veremos más adelante, en ASM se maneja más que nada el sistema binario, ya que con él se pueden "encender" o "apagar" bits (poner a 1 o a 0), que es lo que más se utiliza para las flags por ejemplo, pero todo eso lo veremos más adelante :) El ensamblador no es precisamente "fácil" de aprender, a decir verdad, si no pones atención al principio y dejas las bases bien puestas después no entenderás nada de nada, es simplemente prestar atención y entender desde el principio como el lenguaje funciona. ¿OK? ¿Si? ¡Entonces vamos a ello! Ia) SISTEMA BINARIO En realidad si eres mayor de 12 años deberías saber el sistema binario, pero por si acaso lo voy a explicar rápidamente. El sistema binario cuenta con solo 2 números, 1 o 0, "encendido" o "apagado", "seteado" o "reseteado", etc. Con esos números debes representar cualquier otro, y se hace de la siguiente manera: Divides cada número por 2 y guardas el resultado, y el resto lo sigues dividiendo por 2 hasta que ya no pueda dividirse, entonces tomas el resultado final y los restos de abajo para arriba y así conformas el número.... ...dicho así es algo complicado, pero veamos un ejemplo para entenderlo bien. (En realidad las fórmulas que doy aquí abajo no es necesario hacerlas, ya que puedes usar la calculadora de Windows en modo binario o alguna otra utilidad, pero es algo útil para saberlo :P) Supongamos que quiero pasar 215 a binario: (Aquí los números de resto los pongo entre paréntesis) Entonces tendría : 215 |_2_ (1) 107 |_2_ (1) 53 |_2_ (1) 26 |_2_ (0) 13 |_2_ (1) 6 |_2_ (0) 3 |_2_ (1) 1 Ya tenemos todos los restos y el resultado final ahora los tomamos de abajo hacia arriba: 11010111 Y ese es 215 en binario, si queremos pasarlo de binario a decimal haremos la siguiente operación: "Tomar cada uno de derecha a izquierda y multiplicarlos por 2 elevado a una potencia que arranca de 0 y suma de a 1." EJEMPLO!!!!! XD 1 1 0 1 0 1 1 1 x 2^7 2^6 2^5 2^4 2^3 2^2 2^1 2^0 -------------------------------------- 128 64 0 16 0 4 2 1 Y ahora sumamos todos los números que nos dio: 128 + 64 + 16 + 4 + 2 + 1 = 215 Voila! ^^ Ahí tenemos el número, o podemos hacerlo de la manera fácil, escribimos 215 con la calculadora de Windows en modo decimal, y hacemos clic en el botón para pasar a binario, y ahí lo tenemos! ^^. OPERACIONES LÓGICAS DE BINARIO: El acceso a información a nivel de bit se usa principalmente para comparaciones y poner flags del registro P a 1 o 0. Ahora veremos cómo se hace. OPERACIÓN AND: (&) Consiste en comparar los bits de los operandos y comparar cuando 2 son 1 da 1 de resultado, pero como yo soy muy malo para explicarlo con palabras pondré un ejemplo :) 01100111 AND 01011101 -------------- 01000101 Como ves solo cuando los dos bits son 1 da de resultado 1. OPERACIÓN OR: (|) Consiste en comparar los bits de los operandos y comparar cuando alguno de los 2 es 1, entonces dará 1 de resultado, pero por el mismo motivo pondré otro ejemplo :P 01100101 OR 01010101 ------------- 01110101 Como ves cuando alguno es 1 el resultado es 1. OPERACIÓN XOR: Igual a un OR excepto que para que el resultado dé 1, solo 1 de los bits debe ser positivo (1). 01100011 XOR 01011001 ---------------- 00111010 Solo cuando un bit es 1, el resultado es 1 :) 1c) NÚMEROS NEGATIVOS Aunque con 8 bits (1 byte) se pueda llegar a un rango de 0-255 se usan también en bucles y otras cosas los números negativos, que se representan como números más arriba del tope de los números positivos (128). Para hacerlo más fácil, si el MSB (Most significant bit), es decir, el bit de mayor peso (¡el de la izquierda! xD) es 1, entonces se trata de un número negativo, los números mayores a $80 (128 decimal) son negativos mientras que todos los demás son positivos. $01 -> +1 $02 -> +2 ... $7D -> +125 $7E -> +126 $7F -> +127 $80 -> +128 * ESTE ES EL TOPE DE LOS POSITIVOS) $81 -> -127 $82 -> -126 $83 -> -125 ... $FE -> -2 $FF -> -1 Espero que se entienda, es realmente simple, igual la SNES tiene un flag negativo para saber cuándo la última operación dio resultado negativo, pero eso se verá después, en el registro P. Bueno, vamos con los registros! II) REGISTROS En ASM no se trabaja con variables (sí con constantes) sino con registros y posiciones de memoria, los registros pueden ser de 16 u 8 bits depende del flag m de P, que eso lo veremos luego, como supongo ya sabrán 16 bits son 2 bytes y lógicamente 8 son 1 byte, en ASM se cuenta con varios registros y cada uno tiene una utilidad específica. A (Acumulador) Éste será el registro que más uses, ya que es el único registro sobre el que se pueden hacer operaciones aritméticas, y además suele usarse para modificar a los que no pueden usarse con tal fin, una vez que veas algo de código (si no lo viste aún) verás lo útil que llega a ser este registro (realmente es vital :P). A es por defecto un registro de 16 bits, si quieres cargarlo con valores de 1 byte tendrás que achicarlo con el flag m de P. X (Indexer X) Es también otro registro muy útil, para "indexar" direcciones (ya veremos qué significa eso más adelante), se usa para hacer bucles (también llamados "ciclos"), X no puede realizar ninguna operación aritmética excepto las de incremento y decremento (por 1), que es lo útil para los bucles, cuando empecemos a ver direcciones indexadas veremos más a fondo este registro. Y (Indexer Y) Es también otro registro muy útil, para "indexar" direcciones (ya veremos qué significa eso más adelante), se usa para hacer bucles (también llamados "ciclos"), y no puede realizar ninguna operación aritmética excepto las de incremento y decremento (por 1), que es lo útil para los bucles, cuando empecemos a ver direcciones indexadas veremos más a fondo este registro. (Si, ya sé que hice un c&p de lo anterior, si sirven para prácticamente lo mismo). D (Direct Page Address) Provee los 8 bits altos para las operaciones con modo de direccionamiento directo, normalmente es $00. P.Ej: D = $00 LDA $8F ;en realidad cargaría de $008F D = $10 LDA $7F ;en realidad cargaría de $107F S (Stack) Es la stack, o pila, que se le dice así porque allí se apilan valores que no debas perder, y sirve para hacer operaciones aritméticas a la vez, imagínate la pila como si apilaras cajas una arriba de la otra, cuando tengas que sacar una caja no sacarás la del fondo porque todo se derrumbará, así que primero sacas la última que colocaste, y así pasa exactamente con la pila de una ROM, es un espacio de memoria reservado para meter valores que no debas perder por nada. VOY A EXPLICAR LA PILA AHORA, PARA QUE QUEDE CLARO DESDE EL PRINCIPIO: (intentaré :P) La pila es un espacio de RAM en la que se guardan datos que no deben perderse para operaciones aritméticas, cuando se quiere hacer una operación que involucra un dato que no debe perderse, se guarda en la pila cargándolo en el Acumulador y usando PHA (Push Accumulator), si el valor estaba en X o Y basta con PHX o PHY respectivamente, cuando se quiere sacar un valor basta con PLA / PLX / PLY, que sacan el valor de la pila y lo guardan en el registro correspondiente. P.Ej: LDA #$15 ;cargamos A con #$15 PHA ;metemos A en la pila (A=$15) PLY ;sacamos el último valor de la pila y lo ponemos en Y ( A=$15 / Y=$15 ) En este caso A e Y valdrán #$15 (cuando se empuja por un registro en la pila el registro NO PIERDE SU VALOR). Sería más fácil hacer TAY, pero esto fue solo para demostrar cómo funciona la pila. También se usa la pila automáticamente en algunas funciones como JSR o RTS, que guardan direcciones para volver de subrutinas, pero eso lo veremos más adelante :) DBR (Data Bank Register) Es un registro que contiene el actual banco de trabajo, para así no tener que andar pasando direcciones de 24 bits (3 bytes) cuando tienes que direccionar un salto o cualquier otra cosa, si para una dirección de memoria o un salto pones por ejemplo JMP $1287 (salta a la posición 0x1287 en memoria) al no darle una dirección del banco utilizará DB en su lugar, o en cambio podrías poner JMP $811287 y saltaría al offset $1287 en el banco 81 :), generalmente no debe tocarse a menos que ya se cierre el programa porque puede traer problemas para leer código (intenta leerlo donde no lo hay :) A diferencia de PBR, DBR se utiliza cuando hay transferencias de datos (STA, LDA, etc.) PC (Program Counter) Contador de programa, este registro contiene la dirección actual de proceso. PBR (Program Bank Register) Es un registro que contiene el banco actual, junto a PC conformaría la dirección total PBR(1 byte):PC(2 bytes) Aún queda un registro, que se merece sección aparte por lo complicado que es (en realidad no tanto, pero es más complicado que los demás :P) IIa) REGISTRO P El registro P no se utiliza directamente para algo, solo lo consulta el CPU para saber determinadas cosas antes de seguir una instrucción. El registro P es de 1 byte, o sea 8 bits, y como todos sabemos, cada bit puede ser 1 o 0, y es así como funciona este registro. Veamos como está compuesto: REGISTRO P E n | v | m | x | d | i | z | c ^ ^ ^ ^ ^ ^ ^ ^ 7 6 5 4 3 2 1 0 Cada uno de estos bits (los famosos flags), al estar "seteados" (1) o "reseteados" (0) controla una cosa diferente. flag n (Negative): Este flag se activa si el resultado de la última operación aritmética da resultado negativo, mayor al límite de +127 uno se da cuenta fácilmente también de esto cuando el resultado en binario de una operación aritmética tiene un 1 a la izquierda de todo, como bit de mayor peso, ya que 10000000 es el menor número que puede hacerse con un 1 como mayor peso y da 128, lo que supera el máximo (127). flag v (Overflow): Este flag se activa si el resultado de la última operación aritmética hay "desbordamiento" de dígitos. Puede ser activado por las instrucciones aritméticas ADC y SBC. El desbordamiento se produce cuando el resultado no entra en un byte. Para desactivarlo: CLV flag m (Memory Select): Este flag controla el largo del Acumulador (Registro A), que puede ser de 8 o 16 bits, el bit está puesto por defecto en 0, lo que significa que A tiene un largo de 16bits, recuerda siempre eso, la SNES tiene los registros por defecto en 16 bits (0) así que si están en 1 está en 8bits, hay que cambiar este flag antes de cargar un valor de tamaño diferente al de A. Para activarlo: SEP #$20 Para desactivarlo: REP #$20 Flag x (Index Register Select): Este flag es igual a m pero en lugar de controlar el largo de A controla el largo de los registros índices X e Y. Para activarlo: SEP #$10 Para desactivarlo: REP #$10 (NOTA SI QUIERES ACTIVAR EL FLAG M Y EL FLAG X MÁS RÁPIDO SIMPLEMENTE HAZ SEP #$30 O PARA DESACTIVAR REP #$30) flag D (Decimal): Flag que activa el modo por defecto de Decimal a Binario o viceversa (cuando no se usa el $ antes del valor). Si el flag está a 1 se está en modo decimal, si está a 0 se está en modo binario. Para activarlo: SED Para desactivarlo: CLD flag i (Interrupt): Si este flag está a 1 se aceptará cualquier interrupción externa (p.ej: enchufar/desenchufar un joypad), si está a 0 la interrupción externa será ignorada y el programa seguirá su funcionamiento normal. Para activarlo: SEI Para desactivarlo: CLI flag z (Zero): Este flag se activa cuando el resultado de la última operación aritmética fue 0, a algunas mentes rápidas se les estará viniendo ya una idea de cómo utilizarlo ;), simplemente para comparar dos valores los restas y si el flag z se activa que haga un salto/bucle/lo que sea... ;D flag c (Carry): Este flag se activa si en la última operación aritmética produjo acarreo, el acarreo influye sobre las sumas y las restas así que a estar atentos a este flag antes de hacer alguna de estas operaciones ;) Para activarlo: SEC para desactivarlo: CLC flag E (Emulation flag): Si este flag está activado el procesador emulará 100% el comportamiento del 6502, el procesador que se utilizó en la NES. para activarlo: SEC ;poner carry en 1 XCE ;intercambia flag c y e para desactivarlo: CLC ;poner carry a 0 XCE ;intercambia flag c y e En algunos de los flags para activarlo o desactivarlo dice que hay que hacer un SEP ?? o un REP ??. Eso es porque esas instrucciones (SEP y REP) hacen operaciones lógicas sobre el registro P). Así que por ejemplo si pongo SEP #$20 (20 hexadecimal = 00100000 binario). Entonces la operación sería así: nvmxdizc OR 00100000 ; %00100000 binario = $20 hex ------------- nv1xdizc Ya que no importa si el otro valor es 0, el OR da 1 de resultado si al menos uno de los dos es 1, los demás no son tocados, así que quedan a los valores que tenían antes. (Por eso puse de nuevo las letras en el resultado excepto para m). Esto entonces pone el flag m (largo de Acumulador) a 1, poniendo A en modo de 8bits. También podríamos hacer: nvmxdizc OR 00010000 ; %00010000 binario = $10 hexadecimal ------------- nvm1dizc Esto entonces pone el flag X (largo de X e Y) a 1, poniendo X e Y en modo de 8bits. Y si queremos poner los dos bytes (x y m), o sea, poner A, X e Y en modo 8bits podríamos hacer. SEP #$30 (30 hexadecimal = 00110000) nvmxdizc OR 00110000 ; %00110000 binario = $30 hexadecimal ------------- nv11dizc Así es como se controla los flags del largo del acumulador y los registros índice X e Y. El REP funciona al revés que el SEP, cuando el SEP los pone a 1, el REP los pone a 0 :) Si tienes alguna duda envíame un mail :) III) MODOS DE DIRECCIONAMIENTO En ASM se cuenta con varios tipos de direcciones, aquí las listaré. ? = Algún registro, puede ser A, X o Y. FORMATO: [Nombre del Modo de Direccionamiento] [Ejemplo] [Descripción] Implied TAY No tienen operando. La función misma afecta cierto(s) registro(s) Inmediate LD? #$15 ? = Operando. Absolute LD? $1234 ? = Contenido del operando (16bits) Absolute Long LD? $123456 ? = Contenido del operando (24bits) Absolute Long Indirect LD? ($123456) ? = Contenido del offset (16bits) al que apunta el operando (24bits) Absolute Long Indexed X LD? $123456,X ? = Contenido de operando (16bits)+X Absolute Long Indexed Y LD? $123456,Y ? = Contenido de operando (16bits)+Y Absolute Indirect Long LD? [$1234] ? = Contenido del offset (24bits) al que apunta el operando (16bits) Direct Page LD? $12 ? = Contenido del operando (8bits) Direct Page Indirect LD? ($12) ? = Contenido del offset (16bits al que apunta el operando (8bit) Direct Page Indirect Long LD? [$12] ? = Contenido del offset (24bits) al que apunta el operando (8bits) Direct Page Indexed X LD? $12,X ? = Contenido de operando (8bits)+X Direct Page Indexed Y LD? $12,Y ? = Contenido de operando (8bits)+Y Direct Page Indirect Long Indexed X LD? [$12],X ? = Contenido del offset (24bits) al que apunta el operando (8bits)+X Direct Page Indirect Long Indexed Y LD? [$12],Y ? = Contenido del offset (24bits) al que apunta el operando (8bits)+Y Stack Relative LD? $12,S ? = Contenido de la pila en la posición del operando. (*) Stack Relative indirect indexed X LD? ($12,S),X ? = Contenido de la pila en la posición del operando +X (*) Stack Relative indirect indexed Y LD? ($12,S),Y ? = Contenido de la pila en la posición del operando +Y (*) Accumulator ASL A Actúa directamente sobre el acumulador. Index X INX Actúa directamente sobre el registro X. Index Y DEY Actúa directamente sobre el registro Y. NOTA: Algo importante para aclarar es que cuando usamos []s accedemos a un offset de 3 bytes, y () a uno de 2. Es decir, cuando por ejemplo hacemos: LDA [$1234] cargará el byte de menos valor de $1234, el byte de mayor valor de $1235 y el byte de banco de $1236. En cambio si hacemos: LDA ($1234) cargará el byte de menos valor de $1234, el byte de mayor valor de $1235 y como byte de banco usará el que se encuentre en el registro DBR. (*) Hago el asterisco para explicar un poco mejor cómo se actúa sobre la pila, cuando guardas un valor en la pila queda "apilado" (duh), y el primer valor que saques/uses será el último que hayas apilado, en las operaciones de suma y resta con pila puedes usar otros valores de la pila, y la posición (de abajo hacia arriba, del último que empilaste hacia el primero) que quieras la especificas en el operando, es decir: ADC $01,S Esa instrucción suma al acumulador el último valor que fue guardado en la pila. LDA $02,S Esa instrucción carga en el acumulador el penúltimo valor que fue guardado en la pila. Si usamos $00 como operando entonces nos devolverá el último valor que SACAMOS. IV) INSTRUCCIONES BÁSICAS Ahora empezaremos a ver las instrucciones, no listaré todas ya que eso tomaría bastante tiempo de mi parte, y muchas instrucciones no se usan para nada (p.ej: WDM). FORMATO: | | INSTRUCCIONES QUE ACTÚAN SOBRE EL ACUMULADOR: LDA | LDA #$14 | Carga el valor en el acumulador, si el valor tiene un $ antes | | significa que se trata de un valor hexadecimal (casi siempre :P). | | Cuando hay un # (numeral) significa que estamos cargando un valor | | constante, si no está significa que estamos cargando el valor que | | está dentro de ese offset. | | En el ejemplo estamos cargando el valor $14, si no estuviera el # | | estaríamos cargando lo que haya dentro del offset $14 en memoria. STA | STA $2112 | Guarda el valor contenido en A en el offset del operando. ASL | ASL A | Multiplica A x 2. LSR | LSR A | Divide A x 2. PHA | PHA | Apila A en la pila (valga la redundancia). PLA | PLA | Saca A de la pila. TAX | TAX | Transfiere A a X. TAY | TAY | Transfiere A a Y. TXA | TXA | Transfiere X a A. TYA | TYA | Transfiere Y a A. AND | AND #$80 | Hace un AND lógico entre A y el operando. ORA | ORA #$80 | Hace un OR lógico entre A y el operando. XOR | XOR #$80 | Hace un XOR lógico entre A y el operando. ROR | ROR A | Rota A una vez hacia la derecha. ROL | ROL A | Rota A una vez hacia la izquierda. TCS | TCS | Transfiere A hacia el puntero de Stack. SBC | SBC #$15 | Hace una resta ( A = A - operando + carry - 1 ), antes de hacer una | | operación de estas hay que "setear" el carry, así la resta final no | | tiene efecto sobre la cuenta y se obtiene el resultado esperado. ADC | ADC #$15 | Hace una suma ( A = A + operando - carry ), antes de hacer una | | operación de estas hay que "resetear" el carry, así la suma final no | | tiene efecto sobre la cuenta y se obtiene el resultado esperad. INSTRUCCIONES QUE ACTÚAN SOBRE Y y/o X. LDX | LDX #$14 | Igual a LDA solo que actúa sobre X. LDY | LDY #$14 | Igual a LDA solo que actúa sobre Y. STX | STX $1248 | Guarda el valor contenido en X en el offset del operando. STY | STY $1248 | Guarda el valor contenido en Y en el offset del operando. TAX | TAX | Transfiere A a X. TAY | TAY | Transfiere A a Y. TXA | TXA | Transfiere X a A. TYA | TYA | Transfiere Y a A. TXY | TXY | Transfiere X a Y. TYX | TYX | Transfiere Y a X. PHX | PHX | Guarda X en la pila. PHY | PHY | Guarda Y en la pila. PLX | PLX | Pone el último valor empilado en X PLY | PLY | Pone el último valor empilado en Y INX | INX | Incrementa X por 1. INY | INY | Incrementa Y por 1. DEX | DEX | Decrementa X por 1. DEY | DEY | Decrementa Y por 1. OTRAS INSTRUCCIONES: SEC | SEC | Pone el Carry a 1. -> Hay que poner el carry a 1 antes de hacer una | | resta. CLC | CLC | Pone el Carry a 0. -> Hay que poner el carry a 0 antes de hacer una | | suma. SEI | SEI | Pone el flag de Interrupción a 1. CLI | CLI | Pone el flag de Interrupción a 0. STZ | STZ $1234 | Guarda 0 en el offset del operando. CLV | CLV | Pone el flag de desbordamiento (Overflow) a 0. NOP | NOP | No hace nada :P. Sirve para anular rutinas. V) OPERACIONES ARITMÉTICAS Ahora empezaremos a hacer operaciones aritméticas, es importante que las hagas por ti mismo (en el bloc de notas por ejemplo) y luego las compares con las soluciones de más abajo, sino no servirá de nada, hay que hacer esto para dejar las bases bien puestas ya que lo que se viene después es muy difícil y necesitaremos saber esto. NOTA: En todos los ejercicios se está suponiendo que los flags x y m están a 1, para poder cargar valores de 8bits. Operaciones solo con Acumulador I) A = A * 2 II) A = A + #$14 III) A = A - #$14 IV) A = A / 2 V) A = A + #$6 - #$4 VI) A = A - #$26 + #$18 SOLUCIONES: I) ASL A (Fácil, ¿no?) xD II) CLC ADC #$14 III) SEC SBC #$14 IV) LSR A (Otro fácil, recuerda que esto son las BÁSICAS) V) CLC ADC #$06 SEC SBC #$04 (Este era un poco más difícil, pero seguramente no hubo problemas para resolverlo. ¿No? :) VI) SEC SBC #$26 CLC ADC #$18 (Este era lo mismo que el anterior, solo que con distintos valores y cambiadas suma por resta y viceversa) Operaciones con Acumulador, índices X e Y. (Recuerda que no se pueden hacer cuentas sobre X e Y ;) I) Y = Y * 2 II) X = A + #$14 III) A = X - #$12 IV) X = Y / 2 V) X = Y - #$12 VI) Y = X + #$6 SOLUCIONES: I) TYA ASL A TAY II) CLC ADC #$14 TAX III) TXA SEC SBC #$12 IV) TYA LSR A TAX V) TYA SEC SBC #$12 TAX VI) TXA CLC ADC #$06 TAY Eran todos fáciles, ahora vamos a enseñar algo nuevo para los ejercicios con pila. Cuando guardamos en la pila (esto ya lo he explicado antes, pero para refrescar un poco la mente...) lo último que guardamos es lo primero que sale, así que si ponemos: LDA #$15 ;Cargamos A con $15 PHA ;Guardamos el valor de A en la pila LDA #$18 ;Cargamos A con $18 PHA ;Guardamos el valor de A en la pila Estamos guardando primero $15 y luego $18, pero si luego para sacarlo ponemos: PLA ;Sacamos lo que se puso en pila y lo guardamos en A TAX ;Transferimos A X PLA ;Sacamos lo que se puso en pila y lo guardamos en A Aquí el primer valor que sacamos lo pusimos en X para no perderlo, y si miramos los valores veremos que X será $18 y A será $15, ya que lo primero que entra es lo último que sale. Si queremos hacer una operación que involucre a un valor que está apilado (guardado en la pila), debemos usar el "registro" S antecedido por el lugar que el valor ocupa en la pila. Por ejemplo: (Aquí siempre usaremos el último valor que se puso en la pila, por lo que el valor que antecede a S será siempre $01.) CLC LDA #$15 PHA LDA #$12 ADC $01,S (Esto haría que A se sume con el antiguo valor de A (que está depositado en la pila), esto sirve para cuando tenemos que hacer operaciones involucrando muchos registros y no tenemos lugar para guardar valores que no debemos perder). En el registro de pila tampoco se pueden hacer operaciones aritméticas directas. Es decir, no se puede hacer por ejemplo "ASL S" o "ASL X" Así que ahora ya debes poder resolver los siguiente ejercicios. Ejercicios involucrando Acumulador, Índices X e Y, y pila (S). I) X = A + Y II) A = X + Y III) Y = X - A IV) A = A + #$12 - X V) X = X - A * 2 VI) Y = A + #$12 - X SOLUCIONES: I) PHA TYA CLC ADC $01,S TAX II) TXA PHA TYA CLC ADC $01,S III) PHA TXA SEC SBC $01,S TAY IV) CLC ADC #$12 TAY TXA PHA TYA SBC $01,S (Mwahahaha, este costó, no?) V) ASL A PHA TXA SBC $01,S TAX Operaciones involucrando Acumulador, índices X e Y y direcciones de memoria. Estas operaciones son las que más utilizarás (especialmente al inicializar los registros de la SNES), consisten en grabar en memoria valores, pero solo se puede hacer a través de LDA, LDX, LDY y STA, STX, STY y STZ. I) $1234 = #$14 II) $1234 = A + #$14 - #$2 III) $1234 = A + Y IV) $1234 = $6B8A - A V) $1234 = A * 2 + $6B8A VI) $1234 = #$06 + #$12 SOLUCIONES: I) LDA #$14 STA $1234 II) CLC ADC #$14 SEC SBC #$02 STA $1234 III) PHA TYA CLC ADC $01,S STA $1234 IV) PHA LDA $6B8A SEC SBC $01,S STA $1234 V) ASL A PHA LDA $6B8A CLC ADC $01,S STA $1234 VI) LDA #$06 CLC ADC #$12 STA $1234 Bien, ya esos ejercicios fueron más complicados, espero que no hayas mirado las soluciones directamente porque esto es imprescindible para seguir. V) SALTOS Y BRANCHS En ASM no existen sentencias de if...then...else, sino saltos, y no existen while, for, etc. sino que se usan branchs. Y eso es precisamente lo que voy a explicar ahora :) Subrutinas: Cuando creamos alguna subrutina en algún lenguaje de programación, (yo aquí me referiré más que nada a C, porque es con el lenguaje con el que soy más familiar :) y tenemos una operación o proceso que se repite varias veces en el programa, lo hacemos separado como un "procedimiento" y cada vez que lo necesitemos lo llamamos por el nombre que le dimos y se ejecuta todo el código. Es algo para abreviar en lugar de escribir todo el código de nuevo :) ¿Cómo se usan? Mediante JSR $xxxx y JSL $xxxx Éstas instrucción (Jump to SubRoutine/Subroutine Long) saltan a una subrutina (duh), pero antes guardan en la pila la dirección de la instrucción +3 o +4 (porque JSR $xxxx es de 3 bytes y JSL de 4) y salta a la dirección que se indica ($xxxx), ejecuta el código de allí, hasta que encuentra un RTS/RTL (Return from subroutine/Subroutine Long), que toma la dirección que se guardó en la pila y salta a esa posición, o sea que la pila tenía + 3 o 4, como JSR/JSL $xxxx ocupa 3 o 4 bytes, la instrucción saltaría a la siguiente instrucción después del JSR/JSL :) (*) y así es básicamente como funciona una subrutina, pero como no se entiende nada por palabras, pondré un ejemplo: (*)en realidad no hace falta que entiendas todo esto de + 3 ni nada, pero es útil para saber si toqueteas la pila :), simplemente cuando usas JSR salta a una ubicación y cuando usas RTS salta a la siguiente instrucción del JSR que la llamó :) .... código .... LDA $7E0000,X ;carga algún valor en el acumulador JSL subrutina_larga ;salta la subrutina larga STA $7E0000,X ;guarda el valor modificado de A .... mucho código xD .... subrutina_larga: CLC ;limpia el flag de carry ADC #$20 ;suma $20 (32 decimal) al valor de A JSR multiplica ;salta a la subrutina (cercana) "multiplica" RTL ;vuelve de la subrutina larga multiplica: ASL A ; ] ASL A ; ]__ Multiplica A por 16 ASL A ; ] ASL A ; ] RTS ;vuelve de la subrutina Lo que se hace cuando hace "subrutina_larga" es sumar $20 al valor de A y multiplicarlo por 16, y una vez que se vuelve de la subrutina el código guarda A modificado en el lugar de donde lo sacó. Un ejemplo mucho más simple sería: LDA #$04 ;carga A con #$04 JSR sub_1 ;salta a la subrutina de $12F7 STA $0100 ;guarda A en $100 .... más código xD.... sub_1: ASL A ;multiplica A por 2 RTS ;vuelve de la subrutina (a $1238) Éste código, en lugar de guardar #$04 en $100, guarda #$08, porque A es multiplicado por 2 en la subrutina. SALTOS COMUNES: Simplemente usa la instrucción JMP $xxxx para saltar a una dirección de memoria y el programa irá, pero ten cuidado, porque no se guarda la dirección en ningún lado, o sea que no hay vuelta atrás, a menos que en alguna parte del código haya un salto hacia allí. BRANCHS: Las instrucciones de Branch se utilizan generalmente después de una comparación. Y vienen a ser los if...then...else del ASM. Cuando uno hace una comparación setea los flags, y las instrucciones de branchs se fijan en los flags para ver si se cumple su condición y hacen el salto. CMP #$04 ;Esta instrucción compara A con #$04 CPX #$04 ;Esta instrucción compara X con #$04 CPY #$04 ;Esta instrucción compara Y con #$04 Dependiendo de la comparación se setean flags: Si el operando es mayor, el flag de carry (c) se pone a 0. Si el operando es menor, el flag de carry (c) se pone a 1. Si el operando es igual, el flag zero (z) se pone a 1. Si el operando no es igual, el flag zero (z) se pone a 0. Si A/X/Y (depende de si es CMP/CPX/CPY) es menor a #$04 en el ejemplo entonces pone el flag de carry a 1. Ten en cuenta que los branches usan el modo de direccionamiento relativo, es decir, que no se le da una dirección absoluta a la que debe saltar (ie. BRA $C8DA). Sino que el modo de direccionamiento relativo se basa en el actual PC (es decir, el offset del que se está ejecutando el código). Por lo tanto la operación para determinar hacia dónde saltará el branch es la siguiente: PC + operando + 2 Siempre se le suma el 2 al final ya que el procesador asume que no se quiere ejecutar de nuevo el branch, aunque igualmente puedes hacerlo usando $FE (-2) como operando. EJEMPLO: (de más está decir que esta rutina no sirve para nada, es solo para demostrar cómo funcionan los modos de direccionamiento relativos) $C85D: LDA $74 (carga A con algún valor de RAM) $C85F: CMP #$20 (en este caso A = $08) $C861: BEQ $03 (si es igual a #$20 salta a -> $C861 + $02 + $03 = $C866 $C863: BCS $05 (si es mayor que #$20 salta a -> $C863 + $02 + $05 = $C86A $C865: RTS ;Acá se saltaría si A = $20 $C866: CLC $C867: ADC #$10 $C869: RTS ;Acá se saltaría si A > $20 $C86A: SEC $C86B: SBC #$05 $C86D: RTS Éstas son las instrucciones de branch: Instr: Notas: BCC Salta si flag Carry = 0 BCS Salta si flag Carry = 1 BEQ Salta si flag Zero = 1 BNE Salta si flag Zero = 0 BMI Salta si flag Negativo = 1 BPL Salta si flag Negativo = 0 BVS Salta si flag Overflow = 1 BVC Salta si flag Overflow = 0 BRA Salta siempre BRL Salta siempre (el operando es de 2 bytes) (BLT y BGR son iguales a BCC y BCS respectivamente) Así que veamos, vamos a hacer un ejemplo algo más complicado :) Quiero que me tome un valor de 8 bits de $1234, lo incremente por uno, y lo vuelva a guardar en $1234, si el valor es 100, que incremente por 1 el valor contenido en $1238 y vuelva poner $1234 a 0. (Intenta hacerlo por ti mismo, si no puedes (aunque DEBERÍAS PODER) mira a continuación :) (Haz de cuenta que llamamos a esta subrutina por un JSR y que tenemos la dirección de regreso en la pila para el RTS :) SEP #$20 ;Ponemos el flag m a 1, por si no estaba puesto antes :) LDA $1234 ;cargamos A con el contenido de $1234 (un valor de 8bits) INC A ;incrementamos A CMP #$64 ;lo comparamos con 64 hex (64 hex = 100 decimal) BCC $07 ;si es menor saltamos a lo guardamos directamente en $1234 STZ $1234 ;Si no guardamos 0 en $1234 (con la instrucción STZ) INC $1238 ;Incrementamos el contenido de $1238 por 1. RTS ;Volvemos de subrutina - Esto ya terminó :) ;Aquí saltaría el branch anterior si el valor fuera menos de #$64 STA $1234 ;Aquí llegamos con el branch, si A es menor a 100 simplemente guarda el valor ;aumentado en $1234. RTS ;Volvemos de subrutina - Esto ya terminó :) Espero que haya quedado claro con el ejemplo, cualquier duda, comentario o correxión (xD) o lo que sea que tengas puedes enviármelo a drkwzrdx@hotmail.com :) VII) FIN Herrar es umano: Gracias a Dark-N por notificarme de algunos errores, repetía las utilidades de ORA,XOR,AND y ponía que AND 1 y 1 daba 0 >_< Conclusión: Bueno, si has leído y entendido todo lo que has visto en ésta guía deberías tener conocimientos suficientes para entender los capítulos más avanzados, como gráficas de ASM, y más adelante SONIDOS, y así podrás crear tu propia ROM de pruebas, el ASM puede servir además para expandir fuentes y muchas otras cosas en las roms. Por el momento yo no he hecho ninguna guía de gráficos o sonidos, así que busca en internet (los mejores están en inglés) y encontrarás de seguro :) Contacto: Si has encontrado algún error en esta guía (Que es muy probable porque soy de equivocarme MUCHO :p) puedes enviarme un mail a drkwzrdx@hotmail.com. DaRKWiZaRDX http://www.dwxtrad.cjb.net 30/01/2006 19:44 =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=--=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=[E o D]=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- =-=-=-=--=-=-=-=-=-=-=-=-=-=-==-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-