Volver
Capítulo 4: Programación ASM de Rutinas Simples

Por Dark-N: hernaldog@gmail.com
http://darknromhacking.com


Versiones

  • 1.2:
       -Pasado a HTML y revisado.
       -Sacada explicación de registro P ya que está en el Capítulo 1.
  • 1.1:
       -Revisado completamente sobre todo la semántica de lo que trato de explicar. Se agregan mas ejemplos.
  • 1.0:
       -Versión original del tutorial.


    Introducción

    Siguiendo con el curso, nos toca ver la programación de ASM de rutinas simples con registros: sumas, restas, multiplicación y división de registros. Algo más practico que la guía anterior.


    Registros del CPU

    Ya hemos hablado de los MD (modos de Direccionamiento) pero ahora necesitamos trabajar directamente con los registros internos del CPU, así que los conoceremos más. Como se dijo en el Capítulo 1 los registros internos del 65816 son:

       -A: Acumulador
       -X: Índice X
       -Y: Índice Y
       -D: Registro de Página Directa
       -S: Stack Register (Registro de Pila)
       -PBR: Program Bank Register (Banco de Programa)
       -DBR: Data Bank Register (Banco de Programa)
       -PC: Contador de Programa
       -P: Status Register (Registro de Banderas/Registro de Estado)

    Recuerda que los registros son zonas de memoria dentro del procesador donde se puede almacenar información, de forma que el acceso a esta información sería inmediato.

    Ya has visto algunas instrucciones básicas con los registros A, X y Y. Pero aun no hemos visto sumas de registros distintos o multiplicaciones de ellos.

    Repasemos los registros básicos A,X e Y y luego veremos los complejos.


    A, X e Y

    A - Registro Acumulador

    El acumulador se usa para cálculo y almacenamiento general. En otras palabras, puede almacenar cualquier valor y luego se le pueden realizar operaciones matemáticas.

    Por ejemplo, si queremos guardar el dato 20 hex dentro del acumulador, entonces se debe hacer:
       LDA #$20 (LDA = Load Accumulator o Cargar Acumulador con algún valor)

    El acumulador puede estar en modo de 8-bits o 16-bit. Si esta en 8 bit, solo puede contener valores de 00 a FF (decimal 00 a 256) y en 16 bit valores de 0000 a FFFF (decimal 0 a 65535).
    Para saber si el acumulador esta en 8 o en 16 bit hay que chequear el bit n° 5 o "m", del registro flag P (Capítulo 1). Si el bit 5 esta seteado (es decir m = 1) el acumulador esta en 8 bit y si el bit 5 esta re-seteado (m = 0) el acumulador está en 16 bit.

    A todo esto, recuerda las diferencias entre #$ y $, por ejemplo LDA #$20 y LDA $20

    -El primero modo carga A con el valor 20 hex. Quedando A = 20.
    -El segundo modo carga A un dato de la dirección 20 hex.

    Si en un log de snes9x trace tenemos:
    $00/8359 8A TXA A:1800 X:0017 Y:0000 D:0000 DB:7E

    Podemos ver en azul el valor que tuvo A justo ANTES de ejecutarse la instrucción TXA, este es 1800h.


    X, Y - Registros Índices

    Los registros X e Y están asociados al acumulador. También pueden guardar valores adentro y luego realizar operaciones matemáticas. Sin embargo, estos se usan para una algo especial.
    Estos registros son usados generalmente para indexar localizaciones de memoria.
    Los registros X e Y también pueden ser de 8 o 16 bits dependiendo del Flag X del registro P. Si X = 1 entonces estos registros están en modo de 8 bit. Y si X=0 están en 16 bits.

    Si en un Log de snes9x trace tenemos:
    $00/8359 8A TXA A:1800 X:0017 Y:0000 D:0000 DB:7E

    Podemos ver en azul el valor que tuvo X e Y al momento de ejecutarse la instrucción TXA, este es X=0017h y Y=0000h.


    D - Registro de Pagina Directa (Direct Page Register)

    Hay que aclarar que existe un Modo de Direccionamiento llamado Direct Page o DP (Pagina Directa) y un Registro llamado Direct Page.
    El primer banco de la memoria que va de 00:0000 a 00:FFFF es conocido como página cero (los primeros 64 Kbytes de memoria). El segundo banco 01:0000 a 01-FFFF página uno, y así sucesivamente. Este registro actúa solo dentro de este rango de la página 0 y ayuda a ahorrar espacio en las instrucciones cuando uses DIRECCIONES QUE SEAN DE 1 BYTE como LDA $01, STA $AB ya que el Banco no se escribe, se asume como Banco 0.

    Ejemplo:
    $81:1234    LDA $11    ;D:0000 (en los LOGs le llaman D a la Direc Page, no confundir con el Flag "Decimal")

    ¿cargas en A lo que está en $81:0011? ¿Cargas en A lo que está en $81:1211?. NO. Cargas en A lo que está en $00:0011.
    Siempre que hagas una instrucción donde uses una dirección de 1 BYTE y donde no indiques el BANCO, estarás usando el registro DP que te da el banco y la dirección base, luego tu dirección se SUMA a está DP para obtener la dirección Final. Así DP +$11 = $0000 + $11 = $00: 0011.

    Ejemplo de Log del juego "Return of the Jedi" de Snes:
    $81/BF45 85 3D    STA $3D [$00:003D] A:0002 X:00F0 Y:0004 D:0000 DB:80 S:1FF6
    Podemos ver DP=0000, entonces entre [ ] se ve la dirección final a donde de accederá, es decir DP + $3D = $0000 + $3D =  $00:0003D. Nota que el Banco 00 te lo da el DP y no el registro Data Bank (DB, en rojo) que es que normalmente te indica el banco a direccionar.

    Ejemplo de NO uso del Direct Page:
    $81/BF2B AD 23 07    LDA $0723 [$80:0723] A:85BD X:00F0 Y:0004 D:0000 DB:80 S:1FF6
    Se ve que como se usa una dirección de 16 bits, no usamos el Direct Page, sino el Banco Actual que se encuentra en el Registro DB=80. Aunque estés parado en $81/FB2B, el banco NO es $81, sino lo que indica el registro DB. Finalmente la dirección final de donde se saca el dato es $80:0723


    S - Registro de Pila (Stack Register/ Stack Pointer)

    La pila o stack es un área de memoria RAM que existe en todos los sistemas y que sirve para almacenar datos de los registros de la CPU que no deben perderse durante la ejecución de una rutina determinada. Es un registro de 16 bits que contiene la dirección de la siguiente posición de memoria libre dentro del área de la PILA.

    Como se explicó en el capitulo 3, en la pila los elementos se almacenan siguiendo un orden tipo LIFO: Last In First Out, el último dato en entrar es el primero en salir. El SP (satck pointer o puntero de Pila) apunta siempre a la última dirección libre de la PILA.

    En el caso del SNES la memoria RAM va desde $0000 hasta $FFFF. El SP se inicializa habitualmente al valor $FFFF:

    Cada vez que ponemos (PUSH) agregamos un valor en el stack, el puntero del stack subirá bajando de índice, pero el stack (como espacio) disminuye.
    Al contrario, si sacamos (POP) valores del stack, el stack se incrementa desde el punto de vista de espacio, pero el puntero se decrementa ya que baja.

    Ejemplo:
    Haremos que se ingrese un valor al Stack y luego se saque al acumulador.
    Supongamos que el puntero del stack esta en $2FFF, entonces si:

    PEA $1000 (PEA = Push Effective Address o Agregar Dirección Efectiva)
    Con esta instrucción agregamos la posición $1000 al stack, el puntero del stack ahora estará en $2FFD (al inicio estaba $2FFF) ya que $1000 ocupa 2 bytes.

    PLA $1000 (PLA = Pop to Accumulator o Sacar al Acumulador -> se asume en 16-bits)
    Reservará el acumulador con $1000 y el puntero del stack se devuelve a $2FFF.


    PC - Contador de Programa (Program counter)

    Este registro mantiene la Dirección de la actual instrucción que se ejecuta. Se usa junto con PBR, así PBR:PC forma la dirección física real de la siguiente instrucción a ejecutar, es decir que se tiene el Banco y la Dirección actual. Por ejemplo $12:3456, PBR

    Por ejemplo en un extracto de un log de snes9x trace tenemos:



    Aquí se muestra en azul el valor actual del PC cuando ejecuto la instrucción ASL A.


    PBR - Registro del Banco de programa (Program Bank Register)

    Este registro mantiene el código actual del banco que esta corriendo. Especifica los límites altos del bloque de 64Kb. También especifica los 8 bits más altos de la dirección efectiva. Puede ser referido con los 8 bit superiores de los 24 del Program Counter. Se usa solo con SALTOS.

    Ejemplo:
    PBR: $80 -> Decimos que la instrucción siguiente opera en el Banco 80.
    JMP $1C00 -> Salta a la posición 1C00 del banco 80 -> $80:1C00
    Seria lo mismo hacer:
    JMP $801C00 -> especificamos el banco en la dirección efectiva mediante los 8 bits mas altos ($801C00 es de 24 bits y de los bits 15 al 23 son los mayores, tenemos en este caso el byte 80).

    Si en un log de snes9x trace tenemos:
    $0C/8359 8A 20 JMP 20 A:1800 X:0017 Y:0000 D:0000 DB:7E

    El registro PBR se visualiza en el PC, en los 8 bits mas altos, en este caso PBR=0C.


    DBR - Registro de Banco de Datos (Data Bank Register)

    Parecido al registro PBR. Especifica el banco pero al momento de hacer una carga o un guardado en memoria que sea de 16 bits (LDA, STA, transferencias).

    Ejemplo:

    LDA $123456;
    DBR: $12, es el banco de la dirección efectiva.

    Ejemplo2:
    DBR: $12 -> seteamos el DBR en $12
    LDA $8000; Carga el acumulador con el valor que esta en la dirección 8000, pero del banco $12. -> $12:8000.


    Tabla Rápida de Opcodes Principales

    Esta tabla me la pasó Jonas cuando me enseñó este tema y la traduje.

    Instrucción

    Función

    Notas

    ADC

    A = A + Operando + Carry

    Limpiar Carry antes de usar esta instrucción (operando no puede ser Y o X)

    AND

    A = A AND Operando

    ASL

    A = A * 2

    El bit más alto pasa a ser Carry (operando no puede ser Y o X)

    CLC

    Carry = 0

    Se usa antes de ADC

    CLD

    Decimal = 0

    CLI

    Interrupt = 0

    Interrupcion=0

    CLV

    Overflow = 0

    Desvordamiento=0

    DEC

    Operando = Operando – 1

    DEX

    X = X – 1

    DEY

    Y = Y – 1

    EOR

    A = A XOR Operando

    INX

    X = X + 1

    INY

    Y = Y + 1

    LDA

    A = Operando

    LDX

    X = Operando

    LDY

    Y = Operando

    LSR

    Operando = Operando \ 2

    El bit más bajo pasa a ser Carry

    NOP

    Nada

    ORA

    A = A OR Operando

    REP

    P = P AND (~Operando)

    Desactiva los Flag del procesador

    ROL

    Operando = Operando * 2 + Carry

    Parecido a ASL, pero el Carry se agrega al lado derecho (el bit menos significativo)

    ROR

    Operando = Operando / 2 +

    Carry (as most sig. bit)

    Parecido a LSR, pero el Carry se agrega al lado izquierdo (bit menos significativo)

    SBC

    A = A – Operand + Carry - 1

    Setear Carry ante de usar esta instruccion (operando no puede ser Y o X)

    SEC

    Carry = 1

    Setea el Carry. Se usa antes de SBC

    SED

    Decimal = 1

    SEI

    Interrupt = 1

    Interrupcion=1

    SEP

    P = P OR Operando

    Activa los Flag del procesador

    STA

    Operando= A

    STX

    Operando = X

    STY

    Operando = Y

    STZ

    Operando = 0

    TAX

    X = A

    TAY

    Y = A

    TCD

    Direct Page Offset = A

    Siempre copia 16 bits

    TCS

    Stack Pointer = A

    Siempre copia 16 bits

    TDC

    A = Direct Page Offset

    Siempre copia 16 bits

    TSC

    A = Puntero de Pila (Stack Pointer)

    Siempre copia 16 bits

    TSX

    X = Puntero de Pila

    TXA

    A = X

    TXS

    Puntero de Pila = X

    TXY

    Y = X

    TYA

    A = Y

    TYX

    X = Y

    WDM

    Nada

    No se usa esta instrucción

    XBA

    A (low) <-> A (high)

    Invierte los bytes altos con los bajos del acumulador

    XCE

    Carry <-> Emulation

    Se usa para setear el Modo de Emulacion



    Operaciones básicas

    Haremos operaciones básicas utilizando solo el registro A.

    1. “Sumar a A el valor 4 y dejar el resultado en A” => A=A+4

    CLC ; dejamos el carry en 0
    ADC #$4 ;se le suma a A el operando, en este caso 4. Esto hace por dentro según la tabla de arriba: A=A+Operando+carry y como dejamos el carry en 0 queda A= A+4+0=> A= A+4.


    2. “Restar a A el valor 5 y dejar el resultado en A” => A=A-5

    SEC ; dejamos el carry en 1
    SBC #$5 ; restamos el operando 5. SBC hace esto según la tabla de arriba A=A-operando+carry-1.
    Carry lo dejamos en 1 para que: A=A-5+1-1=> A= A-5


    3. “Multiplicar el contenido de A por 2 y guardar el resultado en A” => A=A*2

    CLC ; ¿seguro va CLC?
    ASL A ; ¡error! Aquí NO VA CLC según la tabla, la solución seria solo ASL ya que ASL A hace A=A*2.


    4. “Dividir A por 4 y guardar el resultado en A” => A=A/4

    Como no existe una instrucción directa para hacer la división por 4, pero si hay una para hacer una división por 2 (LSR), entonces se hará dos veces una división por 2:

    LSR A ; hace A=A/2
    LSR A ; hace A=(A/2)/2 =>A=A/4


    Operaciones No Tan Básicas

    Estas operación utilizan los registros X, Y, A y S (Registro Pila).

    1. “Multiplicar lo que esta en el registro Y por 4 y el resultado dejarlo en el registro A” => A =4*Y

    Primero multiplicar Y por 2, entonces hacemos:
    ASL Y
    ¡error! No se puede usar ASL con X o Y (esto sale en la tabla de arriba, captan?)
    Entonces hacemos:

    TYA ;pasamos el contenido de Y a A.
    ASL ;A multiplicado por 2, dejando el contenido en A (eso dice la tabla)
    ASL ;A multiplicado por 2, si ya estaba multiplicado por 2, entonces queda 4*A


    2. Y=X-A

    Necesitamos restar X con A, pero no se puede usar la instrucción SBC con X, solo con A, pero A tiene un valor que no se puede perder ya que es necesario para restárselo a X. ¿Qué se puede hacer? Utilizar uno de los registros explicados arriba, me refiero a la Pila o Stack. La pila nos sirve para guardar valores de un registro momentáneamente.

    Entonces:

    PHA ;el contenido de A pasa al stack (se copia al stack)
    TXA ;A = X, es decir, pasemos lo de X hacia A
    SEC
    SBC $01,S ;restamos lo que tiene A (lo de X) con lo del stack (esta A en el tope de arriba del stack,
       recuerda que se acumulan hacia arriba), por eso el S es de Stack. Finalmente el contenido
       pasa a A. Lo del 01 es la pocición de arriba hacia abajo, como es el Tope, es la primera.
    TAY ; Pasamos lo de A hacia Y
    PLA ; lo que esta en el stack pasa a A, es para dejar vació el stack.


    3. X=A+5+Y

    CLC
    ADC #$5
    ADC Y
    TAX
    ¿esta correcto? No. Ya que no se puede usar ADC Y, entonces usaremos el Stack...

    CLC
    ADC #$5
    PHA ;pasamos A a la pila
    TYA ;pasamos lo de Y hacia A
    CLC
    ADC $01,S ;sumamos lo del stack con A
    TAX ;pasamos lo de A hacia X
    PLA ;dejamos la pila como estaba, pasando el valor que tiene almacena en el tope, a A.


    4. Y=STACK*2-Y+3

    En este ejemplo no se opera con A, por lo tanto lo podemos usar para cualquier paso de datos, entre los otros registros Y y Stack.

    TYA ;pasamos lo de Y a A
    PHA ;pasamos el contenido de A a la pila
    TSC ;asignamos a A lo que vale el stack
    ASL A ;multiplicamos x 2
    SEC ;carry=0
    SBC $01,S ;restamos lo que esta en el stack a A
    CLC ;carry=1
    ADC #$3 ;sumamos 3h a A
    TAY ;pasamos lo de A a Y
    PLA ;pasamos lo del stack a A


    5. STACK=X/2-8

    TXA ;pasamos lo de X hacia A
    LSR A ;A=A/2
    SEC
    SBC #$8
    TCS ;pasamos lo A hacia el stack


    6. A=(X+2)/4

    TXA
    CLC
    ADC #$2
    LSR A
    LSR A


    7. X=2*A+2*Y

    ASL A ;hacemos primero 2*A
    PHA ;pasamos el contenido de A a la pila
    TYA ;pasamos Y a A
    ASL A ;A=A*2
    CLC ;falta…
    ADC $01,S ;sumamos lo que hay en A con lo que esta en el tope de la pila
    TAX ;el resultado queda en X
    PLA


    8. Y=(Y-5)*8-X/2

    TXA ;primero hagamos el X/2
    LSR :A=A/2
    PHA ;el resultado de A/2 lo tiramos a la pila
    TYA ;sigamos ahora con (Y-5)
    SEC
    SBC #$5
    ASL A ;multiplicamos A*2, 3 veces
    ASL A
    ASL A ;Hasta ahora todo guardado en A
    SEC
    SBC $01,S ;restamos lo que esta en A( (Y-5)*8 ) menos lo de la pila (x/2)
    TAY ;el resultado queda en Y


    9. X = A * 2:

    ASL A
    TAX


    10. A = X + Y

    TYA
    PHA ; en la pila esta Y
    TXA
    CLC
    ADC $01,S ;sumamos lo que esta en A(X) y lo de la pila (Y) y queda en A
    PLA


    11. X = A + Y

    TAX
    TYA
    PHA
    TXA
    CLC
    ADC $01,S
    TXA
    PLA


    12. X = Y + 2

    TYA
    CLC
    ADC #$2
    TAX

    13. A = 20 + 30

    LDA #$20
    CLC
    ADC #$30


    Operaciones con Direcciones

    1. “Guardar en la dirección 1234 el resultado de X+2” => $1234 = X + 2


    TXA
    CLC
    ADC #$2
    STA $1234


    2. $FF0D = (X + 3) + (A / 2)

    LSR A
    PHA
    TXA
    CLC
    ADC #$3
    CLC
    ADC $01,S
    STA $FF0D


    3. X = #$2BC + Y + A

    CLC ;primero hacemos #$2BC+A
    ADC #$2BC
    PHA ;guardo en le stack el valor la suma TYA
    CLC
    ADC $01,S
    TAX



    Gusto a Poco

    1. PILA = ($1234,Y) + 10 decimal

    LDA ($1234,Y)
    CLC
    ADC #$0A ; 10 dec = 0A hex
    PHA

    2. Simular un CICLO FOR y la condición IF-ELSE.
    A partir de la dirección de memoria $1234 (RAM) sumar 1 a A hasta que sea igual a 0x08 entonces salte a $AB

    $7E:1234: INC ; en los bancos $7E o $7F está la memoria RAM, usamos 7E
    $7E:1235: CMP #$08 ; ¿A = 0x08? Si es así, Flag Z=1 (ver Capítulo 2 para regla de Branches)
    $7E:1237: BEQ $AB
    $7E:1239: BRA $1234

    3. Si en un Log se tiene:
    $80/D34F F0 0B    BEQ $0B [$D35C] A:1900 X:0000 Y:006F D:1928 DB:86
    $80/D35C C2 21    REP #$21           A:1900 X:0000 Y:006F D:1928 DB:86
    ¿Se usa o No el Direct Page? ¿Se usa el registro Data Bank?

    No se usa el DP, aunque el operando $0B sea una dirección de 1 byte, en los SALTOS como BEQ, BCC, etc.. no se usa el DP, sino que se salta a la dirección actual + operando + largo operando = $80D34F + $0B + 2 bytes = $80D35C.
    El DB=86 en este caso no se usa, ya que el salto especifica solo el dirección y no el banco, por lo tanto se asume el banco actual 80.



    Volver

    2003-2009 Copyright (C) La web de Dark-N.