Programando como se fosse 1982. Parte III – Memória e Endereços
No post anterior vimos que o processador tem três registradores de uso geral e um ciclo de execução simples e ininterrupto. O problema é que três registradores não são suficientes para nenhum programa real. Você precisa de algum lugar para guardar dados enquanto não os está usando, guardar o programa em si, guardar variáveis e resultados intermediários. Esse lugar é a RAM.
A RAM é um array gigante
Se você já usou um array em qualquer linguagem, já entende a RAM. A memória do C64 era exatamente isso:
RAM = [0] * 65536 # 65.536 posições, cada uma guarda 1 byte
Cada posição guarda um byte, um número de 0 a 255. Nada mais. Texto, imagens, som, código: tudo vira bytes. E cada posição tem um endereço, um número que identifica onde ela está, exatamente como o índice de um array.
Para ler ou escrever, você dizia ao processador: “vá até o endereço X e pegue o que está lá” ou “vá até o endereço X e coloque esse valor lá”. Sem camadas, sem abstrações. Direto.
Por que hexadecimal
Endereços no assembly do C64 eram escritos em hexadecimal, com um $ na frente: $0000, $D418, $FFFF. À primeira vista parece arbitrário, mas tem uma razão muito prática.
Hexadecimal usa 16 dígitos em vez dos 10 do decimal: de 0 a 9, e depois A, B, C, D, E, F. A consequência é que um byte, qualquer valor de 0 a 255, sempre cabe em exatamente dois dígitos hexadecimais, de $00 a $FF. Dois bytes, como um endereço de memória, sempre cabem em quatro dígitos, de $0000 a $FFFF.
Em decimal, o mesmo byte precisaria de 1 a 3 dígitos dependendo do valor. Alinhar, comparar e ler endereços seria uma bagunça. Em hexadecimal, tudo tem tamanho fixo e previsível. Uma vez que você se acostuma, fica natural como ler horas num relógio de 12 horas.
| Decimal | Hexadecimal |
|---|---|
| 0 | $00 |
| 15 | $0F |
| 16 | $10 |
| 255 | $FF |
| 256 | $0100 |
| 65535 | $FFFF |
O mapa de memória
Aqui está a diferença central entre programar no C64 e programar hoje: a memória não era toda igual.
Cada faixa de endereços tinha um papel fixo, e você precisava saber de cor o que ficava onde. Alguns endereços eram RAM comum onde você guardava dados e código. Outros eram portas diretas para os chips de hardware. Escrever nesses endereços não guardava nada, acionava o hardware imediatamente. E outros eram ROM, código gravado em chip que você podia ler mas não alterar.
Não havia sistema operacional explicando o que era o quê. Era o mapa de memória, e você decorava.
A Zero Page: 256 bytes de ouro
A faixa de $0000 a $00FF tinha um nome próprio: Zero Page. Apenas 256 bytes, mas valiam muito mais do que qualquer outro byte da RAM.
O motivo era técnico. Um endereço normal precisa de 2 bytes para ser representado. Por exemplo, $0801 ocupa um byte para $08 e outro para $01. Um endereço na Zero Page precisa de apenas 1 byte, porque o processador já sabe que o primeiro byte é zero. Menos bytes na instrução significa instrução menor e mais rápida de executar.
Instrução acessando RAM normal:
LDA $0801 → 3 bytes na memória, 4 ciclos de clock
Instrução acessando Zero Page:
LDA $42 → 2 bytes na memória, 3 ciclos de clock
Um ciclo de clock a menos pode parecer irrelevante. Mas dentro de um loop que roda milhares de vezes por frame de vídeo, e o C64 desenhava a tela 50 vezes por segundo, a diferença era enorme. Programadores tratavam cada byte da Zero Page como recurso escasso. Variáveis críticas iam para lá. Contadores de loop iam para lá. Ponteiros que seriam acessados centenas de vezes por segundo iam para lá.
Era, em essência, um cache manual. Você decidia o que era importante o suficiente para merecer um endereço na primeira página.
No próximo post saímos do mapa de memória e vemos o que o processador realmente faz com tudo isso: como as instruções são representadas em bytes, o que é um opcode e os primeiros comandos reais de assembly do 6510.