Idiomas disponibles:

Programando como si fuera 1982. Parte VI – Hardware en Memoria

En los posts anteriores de esta serie construimos el modelo completo. Registros, memoria, instrucciones, decisiones y bucles. Ya sabes cómo un programa se mueve por la memoria y cómo el procesador ejecuta cada byte que encuentra. Lo que falta es la parte más sorprendente de la historia del C64, y probablemente la idea más elegante de toda la serie.

Escribir en memoria era activar hardware

En el post sobre el mapa de memoria vimos que ciertas regiones no eran RAM normal. El rango de $D000 a $DFFF pertenecía a los chips de hardware. El VIC-II, el SID y el CIA. Pero lo que eso significa en la práctica solo queda claro cuando ves el código.

Para cambiar el color del borde de la pantalla en el C64, escribías:

LDA #$02    ; 2 = rojo en la paleta del C64
STA $D020   ; escribe en el registro de color del borde

Dos instrucciones. Sin llamada al sistema. Sin driver. Sin API. Escribiste un byte en una dirección y el borde se puso rojo inmediatamente, en el siguiente ciclo de reloj. Esto es memory-mapped I/O: los chips de hardware vivían en el mismo espacio de direcciones que la RAM, y escribir en esas direcciones no guardaba datos. Comandaba el hardware directamente.

No había ninguna capa entre el código y el efecto. Ninguna.

Chips de hardware del C64

VIC-II, vídeo sin tarjeta gráfica

El VIC-II exponía sus 47 registros en la región $D000 a $D02E. Cada uno controlaba un aspecto de lo que aparecía en pantalla. Colores, posición de sprites, modo gráfico, scroll, sincronización con el haz de vídeo.

La paleta del C64 era fija en 16 colores, numerados del 0 al 15. Color 0 era negro, 1 blanco, 2 rojo, y así sucesivamente. Para cambiar el fondo:

LDA #$06    ; 6 = azul
STA $D021   ; registro de color del fondo

Los sprites seguían el mismo patrón. Cada sprite tenía dos registros para posición, uno para X y otro para Y, más bits de control repartidos por otros registros. Para habilitar el sprite 0 y posicionarlo:

LDA #$01
STA $D015   ; bit 0 = sprite 0 habilitado

LDA #$60    ; posición X = 96
STA $D000   ; registro X del sprite 0

LDA #$80    ; posición Y = 128
STA $D001   ; registro Y del sprite 0

Un detalle que muestra bien el nivel de control disponible: el VIC-II dibujaba la pantalla línea a línea, de arriba a abajo, 50 veces por segundo en el estándar PAL. El registro $D012 informaba en qué línea estaba el haz en ese exacto momento. Los programas que querían efectos visuales sincronizados con el raster, como cambiar colores a mitad de pantalla, tenían que esperar que el haz llegara a la línea correcta antes de escribir en el registro. Los programadores expertos hacían esto en bucles que contaban ciclos de reloj, sabiendo exactamente cuántas instrucciones cabían entre una línea y otra.

SID, un sintetizador dentro de un chip

El SID tenía tres voces independientes. Cada una podía tocar una frecuencia diferente con su propia forma de onda y envolvente de amplitud. Para tocar una nota, escribías la frecuencia en los registros de la voz, elegías una forma de onda y activabas el gate:

LDA #$0F
STA $D418       ; volumen master = 15 (máximo)

LDA #$25        ; frecuencia byte bajo (nota La aproximada)
STA $D400
LDA #$1D        ; frecuencia byte alto
STA $D401

LDA #$A0        ; attack rápido, decay medio
STA $D405
LDA #$F0        ; sustain alto, release largo
STA $D406

LDA #$11        ; forma de onda triangular + gate ON
STA $D404       ; la nota empieza a sonar aquí

Ocho escritas en memoria y tenías un oscilador triangular tocando una nota con envolvente completa. Para silenciar, apaga el gate. LDA #$10 / STA $D404. Para tocar una nota diferente, solo cambia los registros de frecuencia mientras el gate está activo.

La envolvente ADSR, Attack, Decay, Sustain, Release, es un concepto de la síntesis analógica. Define cómo se comporta el volumen del sonido a lo largo del tiempo. Cuánto tarda en alcanzar el volumen máximo cuando una nota comienza, cómo decae después, qué volumen mantiene mientras la nota está activa y cuánto tarda en silenciarse al soltarla. Los cuatro parámetros se dividían entre los dos bytes en $D405 y $D406, cuatro bits cada uno.

CIA, leyendo el joystick

El CIA manejaba las entradas, y leer el joystick era una de las operaciones más comunes en los juegos. El registro $DC00 contenía el estado de los dos puertos de control:

LDA $DC00       ; lee el puerto del joystick
AND #$10        ; aísla el bit del botón de disparo
BNE no_disparo  ; si bit = 1, botón NO está presionado
; llegó aquí: botón está presionado
no_disparo:

El detalle contraintuitivo es la lógica invertida. Bit 0 significa que el botón está presionado, bit 1 significa suelto. Esto se llama active-low, común en el hardware de la época donde el pin físico se llevaba a nivel bajo cuando estaba activo. Cada dirección del joystick correspondía a un bit:

Bit 0 → arriba
Bit 1 → abajo
Bit 2 → izquierda
Bit 3 → derecha
Bit 4 → botón de disparo

Verificar si el jugador iba hacia arriba y a la derecha al mismo tiempo era solo cuestión de comprobar los dos bits con operaciones lógicas. No había abstracción de “evento de entrada”. Era lectura de hardware, en bruto.

El bucle principal de un juego

Con todo esto en mano, la estructura de un juego en el C64 era directa:

game_loop:
        JSR leer_joystick
        JSR actualizar_jugador
        JSR verificar_colisiones
        JSR actualizar_sprites
        JSR esperar_raster      ; sincroniza con $D012
        JMP game_loop

La subrutina esperar_raster era el corazón de la sincronización. Se quedaba en un bucle leyendo $D012 hasta que el haz llegaba a una línea específica, generalmente debajo del área visible de la pantalla. Solo entonces el bucle avanzaba al siguiente frame. Esto garantizaba que todas las actualizaciones de sprites y colores ocurrieran cuando el haz no estaba dibujando, evitando el desgarro visual que ocurre cuando actualizas un sprite mientras el haz lo está atravesando.

Este patrón de sincronización manual con el hardware de vídeo es el ancestro directo del VSync que conoces en los motores de juego modernos. La diferencia es que en el C64 lo implementabas a mano, sabiendo exactamente qué hacías y por qué.


En este punto conoces los tres chips y sabes lo que hace cada dirección. El VIC-II cambia lo que aparece en pantalla. El SID genera sonido. El CIA lee el mundo exterior. Cada uno vive en el mapa de memoria y responde a escrituras como si fueran comandos.

Lo que falta es ver los tres funcionando juntos en un programa real, no en ejemplos aislados, sino coordinados por un bucle que lee una tecla, toca una nota y cambia un color al mismo tiempo. Eso es exactamente lo que haremos en el próximo post.