Introdução
Neste artigo mostro uma característica importante do Snes que é a de ficar alterando entre os modos 8 e 16 bits de acordo com os dados que estamos processando. As instruções que trabalham nessas questões são a rep e a sep, que aparecem constantemente em códigos de jogos de Snes. No Mega Drive existe esse tipo de coisa mas de forma diferente do Snes. No Mega a cpu 68000 trabalha com 3 tipos de tamanhos em várias instruções, usando o .b para byte, .w para word e .l para longword, porém a Cpu como um todo permanece na mesma. Já no Snes temos que alterar a Cpu para os modos 8 ou 16 bits, e ao alterar para um modo, todas as instruções passam a trabalhar neste modo, o que muda o acesso ao barramento. Por exemplo, o barramento de dados do Snes é 8 bits, então se estamos no modo 16 bits, uma leitura da memória vai trazer 2 bytes, fazendo duas leituras sequenciais. No modo 8 bits apenas uma leitura é feita. Isso passa a afetar a cpu como um todo, diferente do Mega onde cada instrução é independente nessas questões.
A figura abaixo ilustra os modos, indicando o número de bytes em cada leitura e escrita para os dois tipos de modos:
Instruções REP e SEP
As instruções rep e sep servem para setar ou zerar bits da Status Register. A sep seta bits e a rep zera bits. Nas duas instruções temos que passar uma máscara de 8 bits que indica quais bits setar ou zerar.
Na sep os bits 1 da máscara indicam quais bits da Status Register setar pra 1. Os bits que estão em 0 não alteram os bits correspondentes da Status Register, então o que estava com 0 fica com 0 e o que estava com 1 fica com 1.
Na rep os bits que estão em 1 indicam quais bits zerar na Status Register. Então note que um bit 1 na máscara indica que o bit ficará com o valor 0 na Status Register. O restando é igual ao sep.
A sintaxe então é desta forma:
1
2
sep #mask
rep #mask
Status Register e influência nos modos
No Snes podemos ter os modos 8 bits e 16 bits, e esses modos são configurados por duas flags da Status Register.
As flags m e x da Status Register são as que controla os modos 8 e 16 bits.
A flag m controla se o registrador A trabalha em modo 8 ou 16 bits. Se a flag estiver em 0 o modo é 16 bits. Se estiver em 1 o modo é 8 bits.
A flag x controla se os registradores X e Y trabalham em modo 8 ou 16 bits. Se a flag estiver em 0 o modo é 16 bits. Se estiver em 1 o modo é 8 bits.
Essas flags alteram o tamanho dos registradores e também todas as instruções que trabalham com esses registradores. Então se o A estiver em modo 8 bits, ao ler um dado da memória com a instrução lda, a Cpu lê apenas um byte. Se estiver em modo 16 bits a Cpu lerá 2 bytes. Este é só um exemplo, mas o mesmo vale para todas as instruções que usam esses registradores.
Instrução SEP
A instrução sep serve para alterar para 1 os bits selecionados na máscada passada. Então os bits que estão com 1 na máscara irão alterar pra 1 esses mesmo bits na Status Register. Por exemplo a instrução abaixo:
1
sep #$30
Essa instrução vai jogar pra 1 os bits 3 e 4 da Status register. Os outros bits não são alterados. Portanto os bits que estão com 0 na máscara não causam efeito na Status Register. Com uma análise mais profunda vemos que essa instrução faz um “or” da Status Register com a máscara, conforme pseudocódigo abaixo:
1
SR | mask
Instrução REP
A instrução rep faz o contrário da sep, ou seja ela seta pra 0 na Status Register os bits que estão com 1 na máscara. Nesta instrução a máscara funciona exatamente como na sep, onde os bits com 1 indicam os bits a serem modificados. Por exemplo a instrução abaixo:
1
rep #$30
Essa instrução vai jogar pra 0 os bits 3 e 4 da Status Register e os outros bits não são alterados. Com uma análise mais profunda vemos que essa instrução faz um “and” da negação da máscara com a Status Register, conforme pseudocódigo abaixo:
1
SR & ~(mask)
Modos 8 bits e 16 bits no Snes
Ok, agora que sabemos como essas instruções funcionam, vamos entender agora que o Snes tem suporte aos modos 8 bits e 16 bits, e esses modos são controlados pelas flags M e X da Status Register (veja figura da Status Register acima). A flag M controla se o registrador A trabalha com 8 ou 16 bits. Se a flag M for 1, o registrador trabalha apenas com 8 bits, e se a flag for 0 o registrador fica 16 bits. Mas não é só o registrador que fica 8 ou 16 bits. Se estiver em 8 bits por exemplo, todas as instruções que usam o A vão passar a ser 8 bits. Por exemplo, se temos a seguinte instrução:
1
lda $0000
Se a flag M for 1 a Cpu está em modo 8 bits e apenas um byte é lido e colocado nos primeiros 8 bits do A. Mas se a flag M for 0, a Cpu está em modo 16 bits e dois bytes são lidos e colocados nos 16 bits do A. A mesma lógica vale para a flag X, onde esta flag controla os tamanhos dos registradores X e Y. Se a flag estiver em 1, a Cpu está em modo 8 bits no X e no Y e todas as instruções que usam esses registradores passam a operar em 8 bits. Se a flag for 0 então os registradores e as instruções que operam nesses registradores passam a ser 16 bits.
Alterando entre os modos 8 bits e 16 bits
Ao programar para Snes, sempre nos deparamos com os seis tipos de instruções abaixo:
1
2
3
4
5
6
sep #$20 //Extremamente comum
rep #$20 //Extremamente comum
sep #$30 //Comum
rep #$30 //Comum
sep #$10 //Pouco comum
rep #$10 //Pouco comum
As instruções acima manipulam justamente as flags que controlam os modos 8 bits e 16 bits. A instruções mais comuns são as que manipulam apenas a flag M, pois na maioria das instruções e algoritmos estamos interessados em manipular dados em nível de bytes, e normalmente os registradores X e Y sempre ficam no modo 16 bits. Mas as vezes é necessário mudar o X e Y para 8 bits, e nesses casos também alteramos juntos o A, por isso a máscara #$30 é comum. Raramente deixamos o X e Y em 8 bits e o A em 16 bits, por isso a máscara #$10 é bem menos comum.
Registradores A, B e C
Conforme já aprendemos o Snes é uma Cpu com acumulador, e boa parte das instruções trabalham com o registrador A. O registrador A tem 16 bits, conforme já aprendemos em vídeos passados. O detalhe é que quando a Cpu passa a trabalhar no modo 8 bits (flag M=1), o registrador A passa a ser tratado tendo 8 bits, e os 8 bits mais significativos não são alterados quando fazemos uma leitura, por exemplo. Porém é possível usar os 8 bits mais significativos do registrador A quando estamos em modo 8 bits. Como? É o que veremos na sequência. Quandos estamos em modo 8 bits, o registrador A passa a ser os 8 bits menos significativos, e neste momento os 8 bits mais significativos são chamados de B. Então o registrador B é o byte mais significativo do registrador A quando a Cpu está em modo 8 bits. Quando queremos considerar os 16 bits totais do registrador A, independente do modo da Cpu, chamamos o registrador A de C. Então o registrador C é o registrador A quando estamos manipulando os 16 bits totais do registrador, independente do modo. Alguma instruções usam essa nomenclatura, que é o que veremos a seguir.
Registrador B - Instrução XBA
A instrução xba é usada para trocar os valores dos registradores A e B. Lembrando que o B é 0 byte mais significativo do acumulador. A figura abaixo mostra o layout desses registradores.
Essa instrução é muito usada quando estamos em modo 8 bits, pois podemos salvar o A atual (8bits) no B usando a xba, e depois voltar com o valor original chamando novamente a instrução.
Essa instrução faz a troca e não a cópia.
As flags n e z são alteradas por essa instrução e utilizam o valor A (8 bits) como valor para o cálculo das flags, independente do modo.
Código
O código mostrado no vídeo para exemplificar o assunto está listado abaixo:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
// vim: ft=snes
arch snes.cpu
output "snesapp.sfc/snesapp.sfc", create
fill $200000
macro seek(variable offset) {
origin ((offset & $7F0000) >> 1) | (offset & $7FFF)
base offset
}
include "snes-header.snes.asm" // Include Header & Vector Table
seek($8000)
clc
xce
nop
rep #$30 //Mode full 16 bits (A, X and Y)
lda #$1234 //16 bits mode, so we need a 16 bits immediate
sta $0000 //Writing 2 bytes
ldx $0000 //Reading 2 bytes to X
ldy $0000 //Reading 2 bytes to Y
lda #$0078 //8 bits immediate. What Happens?
sep #$20 //8 bits mode
sta $0010 //Now writes only 1 byte
lda $0020 //Reads 1 byte
lda #$ff //Maximum value for a byte
inc //What Happens?
rep #$20 //Back to 16 bits again. X and Y are already 16 bits
lda #$00ff //Again
inc // And now?
ldx #$0000 //Cleaning X and Y
ldy #$0000
sep #$10 //X and Y now 8 bits
ldx $0000
ldy $0000
ldx #$ff //Same test
ldy #$ff
inx
iny
rep #$10
//Here we are full 16 bits
lda #$1234
xba
lda #$8912
xba
-
bra -
Status das Instruções
Nesta parte da série vimos 3 novas instruções, sep, rep e xba. A imagem abaixo mostra as instruções que aprendemos nesta parte em rosa e em verde as que já foram aprendidas no passado.