Parte 1 – asm (assembly) BÁsico



Baixar 56,94 Kb.
Encontro18.09.2018
Tamanho56,94 Kb.
ASM BÁSICO E CODIFICAÇÃO DE BLOCOS PARA O GOPHER POPCORN STEW (GPS)

Por Manuz, major Flare

(a.k.a Fierce Deity Manuz OW Hacker)

PARTE 1 – ASM (assembly) BÁSICO

0. Introdução à linguagem 65c816 assembly

Este tutorial destina-se, de forma geral, à aprendizagem, da forma mais intuitiva que eu possa apresentar, da linguagem assembly 65c816, própria para a plataforma SNES. A linguagem assembly possui muitas peculiaridades, fazendo-a diferente de linguagens como C e C++: é uma linguagem de baixo nível, tornando-se, portanto, de difícil entendimento. Porém, com uma intuição básica da abordagem top-down de programação, muitas vezes fica menos difícil programar em assembly. Esse tutorial tentará direcionar os algoritmos montados nessa abordagem.

A linguagem assembly é uma tradução quase “direta” da linguagem de máquina que o processador (no caso, o SNES) entende. Ela é composta de vários símbolos simples, que formam as instruções. Aqui, trataremos cada instrução (ou conjunto) de modo separado, de forma a enfatizar certos aspectos importantes que afetam o andamento do código.

Para facilitar a leitura do tutorial, a partir de agora a linguagem assembly 65c816 será identificada apenas como ASM.



1. Revisão básica das principais bases numéricas

Como qualquer outra linguagem, o ASM permite a manipulação de dados em bases numéricas não-decimais. Como é uma linguagem de nível diretamente acima do código de máquina, as bases mais comuns de trabalho são a hexadecimal (base 16) e a binária (base 2).



Antes de mais nada, base numérica apenas interfere em como um dado é representado. Por exemplo, o número 60 é representado por 60 na base decimal, 3C na base hexadecimal e 0011 1100 na base binária (em representação de 8-bits). A principal característica que diferencia uma base numérica da outra é a quantidade de algarismos que podem representar o número. Na base decimal, usamos 10 algarismos (0 até 9). Na base binária, usamos apenas os algarismos 0 e 1. E, na base hexadecimal, usamos 16 algarismos (0 até 9 e as letras A até F). A tabela a seguir mostra a conversão dos 16 primeiros números nas 3 possíveis bases:

Hexadecimal

Binário (4 bits)

Decimal

0

0000

0

1

0001

1

2

0010

2

3

0011

3

4

0100

4

5

0101

5

6

0110

6

7

0111

7

8

1000

8

9

1001

9

A

1010

10

B

1011

11

C

1100

12

D

1101

13

E

1110

14

F

1111

15

Tabela 1. Principais bases numéricas para os 16 primeiros algarismos.

No ASM, esses valores, quando constantes, serão representados utilizando-se símbolos diferentes, que serão tratados ao decorrer do tutorial. Para se evitar problemas, recomendo a utilização da Calculadora do Windows (ou outro aplicativo) para a conversão entre essas bases.



2. Os 3 registradores de uso geral e (quase) livre do ASM

Sendo um processador, o SNES possui registradores, que são entidades digitais onde certos valores são armazenados de forma a permitir ao processador realizar o conjunto de instruções que lhe é passado. Existem pelo menos 9 registradores no SNES, mas alguns são de propósitos específico. Para esse começo de tutorial de ASM, vou focar em 3 registradores que são de uso comum: o registrador Acumulador (A) e os registradores de Índice, representados por X e Y. Os 3 são registradores de 16-bit, mas são normalmente usados no modo 8-bit. Em outras palavras: embora você possa, teoricamente, carregar valores entre 0 e 65535 nesses registradores, você usará, em pelo menos 90% dos casos, apenas os valores entre 0 e 255. Nessa parte básica, vamos utilizar 8 bits no começo e o registrador Acumulador. O registrador A é o mais importante da lista. Nele são carregados valores, esses valores são trabalhados (por meio de operações matemáticas) e salvos na memória. Praticamente não há código feito sem o uso desse registrador.

Já os registradores de índice, X e Y, são úteis principalmente para criar algoritmos baseados em arrays, ou vetores (conceito tirado do C). Falarei deles mais adiante, quando for preciso.

3. A memória (parte 1): memória RAM e memória ROM

RAM significa Random Access Memory. É uma memória que pode ser tanto lida quanto escrita. No SNES, ela corresponde a um intervalo de endereços. Já ROM significa Read-Only Memory. É uma memória que apenas pode ser lida. Também corresponde a intervalos de endereços no SNES.

Basicamente, o SNES é mapeado byte por byte. Os endereços são representados por números de 24 bits, e variam entre $000000 a $FFFFFF (note o símbolo ‘$’ – indicando valores em base hexadecimal). A memória RAM está mapeada entre os endereços $7E0000 e $7FFFFF. Já a memória ROM é mapeada nos intervalos $xx8000 a $xxFFFF (em que xx pode ser qualquer valor hexadecimal entre 00 e 6F). Além disso, temos uma peculiaridade no SNES: na representação de endereço da forma $xxyyyy, xx denota o banco de memória (bank). Nos bancos de 00 a 3F, os endereços $xx0000 a $xx1FFF são especiais: eles são uma cópia das RAMs entre $7E0000 e $7E1FFF. Além disso, os endereços $xx2000 a $xx7FFF são registradores de hardware, que executam tarefas específicas (DMA, VRAM, etc.). Para fins desse tutorial, não entrarei em maiores detalhes sobre outras áreas da memória do SNES. Para essa parte básica, a ROM e a RAM devem ser suficientes.

4. Carregando e Guardando dados (parte 1) – LDA, STA, STZ

Agora, vamos dar início ao ASM propriamente dito. A coisa mais básica a ser feita é ler e escrever coisas na memória. Para isso, usaremos o registrador acumulador. Primeiramente, precisamos carregar um valor. Mas de onde carregar? Esse valor pode ser uma constante (chamada de imediato) ou um valor guardado em memória. Suponhamos que queremos guardar o valor $0A (em hexadecimal) na posição de memória da RAM dada pelo endereço $7E13CC. O algoritmo básico seria:



$7E13CC = #$0A

Note a simbologia; coloquei um ‘#’ antes do $0A. Isso, no ASM, significa que o valor é uma constante, ou imediato; portanto, não precisa ser lido da memória. Note ainda que o algoritmo acima ainda está em um nível muito alto para o ASM; precisamos refiná-lo:

Acumulador = #$0A

$7E13CC = Acumulador



Note agora que o mesmo algoritmo foi quebrado em dois passos. Esses dois passos são elementares, e podem ser traduzidos cada um em uma única instrução do nosso ASM. No caso acima, usaremos as instruções LDA (Load to Accumulator) e STA (Store Accumulator). O algoritmo acima, utilizando-se essas instruções, se traduz em:

LDA #$0A ; Acumulador = #$0A

STA $7E13CC ; $7E13CC = Acumulador



Agora sim, temos um código ASM perfeitamente válido! Porém, note que podemos fazer uma simplificação: salvo em raríssimas exceções (que estão além desse tutorial, então por ora não se preocupe), o 7E do endereço RAM pode ser omitido, até para economizar espaço. Isso só é possível por causa das cópias de RAM que existem nos bancos de $00 a $3F. Levando isso em consideração, nosso pedaço de código final para o algoritmo proposto fica:

LDA #$0A ; Acumulador = #$0A

STA $13CC ; $13CC = Acumulador ($7E13CC = Acumulador)



Pronto, temos o primeiro algoritmo desse tutorial convertido para o ASM! Não é tão difícil quanto parece, certo? Agora, vamos tentar outro algoritmo: Suponha que queremos guardar o valor que está guardado em $7E0019 nas posições $7FB000 e $7E13CC, e que queremos zerar as posições de RAM $7E0088, $7E1491 e $7FB001. O algoritmo básico (alto nível):

Carregar $7E0019 no Acumulador

Guardar o Acumulador em $7E13CC e em $7FB000

Zerar $7E0088, $7E1491 e $7FB001


Vamos começar a refinar o algoritmo, sabendo de uma coisa crucial: Operações de escrita em memória (com STA) não alteram o valor do acumulador! Refinando-se uma vez, temos:

Acumulador = $7E0019

$7E13CC = Acumulador

$7FB000 = Acumulador

Acumulador = #$00

$7E0088 = Acumulador

$7E1491 = Acumulador

$7FB001 = Acumulador


Agora, vamos fazer um 2º refinamento nesse algoritmo: Vamos simplificar os endereços que comecem com $7E:

Acumulador = $0019

$13CC = Acumulador

$7FB000 = Acumulador

Acumulador = #$00

$0088 = Acumulador

$1491 = Acumulador

$7FB001 = Acumulador


Agora, o 3º refinamento: perceba que o primeiro endereço está escrito como $0019. Esse endereço pode ser simplificado mais uma vez! Agora, trataremos ele apenas como $19. Nesse caso, o endereço lido é $0019, mas SEMPRE no banco $00. O mesmo vale para a RAM $0088 ($88). Ou seja, se você tem um endereço RAM da forma $7E00xx, a simplificação para $xx SEMPRE é válida. Não há exceções. Vamos ao algoritmo:

Acumulador = $19

$13CC = Acumulador

$7FB000 = Acumulador

Acumulador = #$00

$88 = Acumulador

$1491 = Acumulador

$7FB001 = Acumulador


Agora, vamos fazer mais um refinamento, considerando o seguinte: Se seu endereço é representado como $xx ou $xxxx (devido às simplificações), eu posso simplesmente zerá-lo sem precisar do acumulador. O algoritmo acima, refinado e estruturado, fica:

Acumulador = $19

$13CC = Acumulador

$7FB000 = Acumulador

$88 = 0


$1491 = 0

Acumulador = #$00

$7FB001 = Acumulador


Pronto, nosso algoritmo está composto de passos elementares e simples de serem traduzidos. Em ASM, fica:

LDA $19 ; Acumulador = $19

STA $13CC ; $13CC = Acumulador

STA $7FB000 ; $7FB000 = Acumulador

STZ $88 ; $88 = 0

STZ $1491 ; $1491 = 0

LDA #$00 ; Acumulador = #$00

STA $7FB001 ; $7FB001 = Acumulador


Note que usamos outra instrução: STZ (Store Zero). Essa instrução simplesmente carrega o zero para uma determinada posição de memória. Muito mais rápida que uma sequência de LDAs e STAs. Porém, note que ainda precisei fazer algo assim no final do algoritmo, pois não existe STZ $xxxxxx. Simples assim.

Até agora, temos as instruções LDA, STA e STZ. A tabela a seguir mostra o que cada uma faz, dependendo do que for escrito (visto até agora e dois bônus do LDA):



Instrução

Funcionamento

LDA #$xx

Lê ‘xx’ (hexadecimal) em A

LDA #xx

Lê ‘xx’ (decimal) em A

LDA #%xxxxxxxx

Lê ‘xxxxxxxx’ (binário) em A

LDA $xx

Lê a posição $0000xx em A (ou $7E00xx)

LDA $xxxx

Lê a posição $yyxxxx em A ($7Exxxx, se yy estiver entre $00 e $3F, ou for $7E)

LDA $xxxxxx

Lê a posição $xxxxxx em A

STA $xx

Guarda A em $0000xx (ou $7E00xx)

STA $xxxx

Guarda A em $yyxxxx ($7Exxxx, se yy estiver entre $00 e $3F, ou for $7E)

STA $xxxxxx

Guarda A em $xxxxxx

STZ $xx

Zera o endereço $0000xx (ou $7E00xx)

STZ $xxxx

Zera o endereço $yyxxxx ($7Exxxx, se yy estiver entre $00 e $3F, ou for $7E)

Tabela 2. Usos básicos de LDA, STA e STZ (A: Acumulador)

Deve-se notar, até agora, as seguintes coisas sobre as instruções acima estudadas:

- Jamais use STA em um endereço que seja ROM. A ROM não pode ser escrita, apenas lida.

- Como já mencionado, não existe STZ $xxxxxx.

- Não existe STA #$xxxx, STA #%xxxxxxxx, STA #xxxx ou similares. Não faz sentido guardar uma valor em uma constante.

Até aqui, já temos 3 instruções e duas operações básicas do nosso ASM. Sabemos ler e escrever dados na memória, e zerar posições usando STZ. Agora, daremos prosseguimento ao estudo do ASM introduzindo outra classe de operações básicas: Operações matemáticas.



5. Operações Matemáticas Básicas – CLC, ADC, SEC, SBC, INC, DEC, ASL e LSR


©bemvin.org 2016
enviar mensagem

    Página principal