cursos:ensamblador:prologo


Prólogo


“Programación en ensamblador de Z80 para el microordenador Sinclair ZX Spectrum.”

Mucha gente se preguntará cómo es posible que, en pleno siglo XXI, exista alguien con interés en escribir un curso sobre esta temática. Apenas un par de centenares o miles de personas en todo el mundo pueden estar realmente interesadas en la lectura de un curso como este.

Sin embargo, he dedicado gran cantidad de horas a escribir y depurar este texto y sus ejemplos. ¿El motivo? Simplemente, no se me ocurre una mejor forma de concentrar en un único elemento mi pasión por el ZX Spectrum, la programación en ensamblador, el desarrollo de programas y los videojuegos, y dejar este texto como un homenaje a este pequeño gran ordenador.

Puedo decir que el ZX Spectrum cambió mi vida. Aquella tarde de viernes de 1989 en la que mis padres aparecieron por la puerta con un Spectrum +2A de segunda mano, junto a una caja llena de revistas Microhobby y cintas de juegos y programas, cambió el que hubiera sido mi futuro profesional, orientándolo hacia el mundo de la Ingeniería, la Electrónica y las Telecomunicaciones.

Como todos, empecé exprimiendo el Spectrum a través de los juegos profesionales que se vendían para la popular máquina de Sinclair. En paralelo a los juegos, comencé a leer los ejemplares de las revistas Microhobby que habíamos adquirido junto al ordenador.

Mi relación inicial con Microhobby fue las que supongo que tendrían muchos usuarios sin interés por la programación: ir directo a las páginas con análisis, fotos y notas de juegos. Como mucho, como curiosidad tecleaba alguno de los listados en BASIC de la sección de trucos, maravillándome con sencillas melodías, o psicodélicos efectos de colores con el borde.

Esos listados en BASIC, tan sencillos, despertaron mi curiosidad por “cómo se hacen estos juegos”. Poco a poco se produjo el cambio: mi interés por jugar pasó a ser interés, mucho interés, por desarrollar.

Microhobby fue la herramienta mediante la cual aprendí BASIC y ensamblador de Z80 (sin dejar de lado el estupendo manual que venía con el +2A). Como la completa revista que era, entre sus páginas de análisis de juegos podías encontrar fantásticos artículos y listados animándote a programar pequeñas rutinas y juegos.

Casi sin darme tiempo para disfrutar de lo que estaba aprendiendo, llegó el fin de la revista Microhobby y el ocaso comercial del Spectrum en España. Las consolas ocuparon el espacio lúdico del Spectrum y el PC se convirtió en la herramienta de programación estándar.

El Spectrum pasó para mí al olvido hasta que la revista Micromanía publicó el emulador “SPECTRUM” de Pedro Gimeno. Este emulador, y todos los que aparecieron en la década de los 90, sirvió para que la gente no olvidara el Spectrum y todo el legado que nos había dejado.

Ya a principios del siglo XXI, el Spectrum volvió a ser mi centro de atención: inicialmente, desarrollé el emulador ASpectrum con el que mejoré en gran parte mis conocimientos sobre la arquitectura del Spectrum y la programación en lenguaje ensamblador de Z80. Una vez ASpectrum fue una realidad, comencé a realizar sencillos juegos con Z88DK en C con pequeñas rutinas en ensamblador integradas en el código. Se despertó de nuevo en mí el interés por desarrollar juegos de Spectrum y de escribir tutoriales y cursos con todo lo que iba rememorando o aprendiendo.

En esa época (años 2002 - 2003) se fundó Compiler Software y se editó la revista MagazineZX en el portal Speccy.org, incluyendo diversos cursos de programación en C con Z88DK y en ensamblador de Z80 con pasmo. Estos cursos, finalmente, se han ampliado y materializado en el texto que estáis leyendo.


El objetivo principal de este curso, libro, o gran tutorial es que un lector con conocimientos básicos de programación pueda aprender fácilmente ensamblador de Z80 aplicado al desarrollo de juegos y utilidades de Spectrum.

Con este curso pretendemos enseñar al lector:


  • La arquitectura del Sinclair ZX Spectrum: se describen sus componentes internos y cómo se interrelacionan.
  • La arquitectura del microprocesador Z80: sus registros y su juego de instrucciones.
  • La sintaxis del lenguaje ensamblador de Z80: mnemónicos del lenguaje.
  • Cómo utilizar un programa ensamblador para ensamblar nuestros programas en ASM de Z80.
  • Acceso a los periféricos del Spectrum: Teclado, Joystick, etc.
  • Gráficos en el Spectrum: Sprites, Fuentes de texto, Impresión de mapeados, etc.
  • Funciones avanzadas de los modelos 128K: paginación de memoria.
  • Rutinas auxiliares: subrutinas de carga, compresión, Interrupciones del procesador, etc.
  • Subrutinas útiles para el desarrollo de programas.


Al escribirlo he intentado ponerme en la piel del programador que desea empezar con el lenguaje ensamblador, por lo que los primeros capítulos describen la arquitectura del Spectrum y del procesador Z80. También veremos de una forma introductoria qué es el lenguaje ensamblador, qué es el lenguaje máquina, y cómo podemos introducir estos programas escritos en ensamblador en nuestro ZX Spectrum.

Los siguientes capítulos tratarán sobre la sintaxis del lenguaje ensamblador, donde el lector aprenderá las “piezas básicas” con las que construir programas en ensamblador para cualquier microordenador basado en el procesador Z80 de Zilog: qué son los registros, de qué instrucciones disponemos y cómo podemos combinarlas para hacer programas.

Continuaremos después centrándonos única y exclusivamente en el Spectrum, profundizando en todas las diferentes áreas que puedan sernos de utilidad para el desarrollo de juegos o programas: lectura del teclado, temporización, impresión de gráficos, técnicas de mapeado, carga desde cinta, etc.


Este curso está dirigido a gente que tenga unos conocimientos mínimos y básicos de programación y de computadoras.

Por un lado no se puede pretender que una persona sin conocimientos básicos de programación se meta de lleno a programar en un lenguaje de bajo nivel como es en ensamblador. En máquinas tan limitadas en recursos como el Spectrum, a veces no sólo se programa con nmemónicos (como “LD A, 10”) sino que hay que tener conocimiento del código máquina (los opcodes) que conforman cada instrucción, por ejemplo para optimizar el tamaño o el tiempo de ejecución de una rutina.

Por el otro, no es factible incluir en el propio curso la teoría más básica necesaria para seguirlo, ya que la extensión del mismo excedería en mucho algo manejable. Hablamos de una serie de conocimientos que debería tener (aunque sean nociones de los mismos) quien quiera seguir este curso desde el punto en que lo tomamos:

  • Sistemas Decimal, Binario y hexadecimal: cómo expresar un valor numérico en diferentes sistemas de representación.
  • Operaciones lógicas y aritméticas entre bits: Qué es una operación AND, OR, XOR y NOT entre 2 bits o 2 valores, cómo se representan valores con signo en binario, y cómo se suman o restan valores representados con este sistema.
  • Nociones de programación al menos en BASIC del Spectrum.
  • Conocimientos básicos sobre compiladores y ensambladores: al menos, saber que se pueden escribir los programas en ficheros de texto y compilarlos/ensamblarlos para obtener programas ejecutables.
  • Conocimientos básicos sobre emulación: ser capaz de ejecutar un emulador de ZX Spectrum y cargar en él los ficheros TAP que obtendremos como resultado de nuestros ejercicios y pruebas.

Existen ya publicaciones de la época en formato PDF (algunas incluso reeditadas a día de hoy, adquiribles en Amazon, aunque en inglés) las cuales son excelentes documentos para introducir al lector en las materias de arriba (y, por qué no, complementar este curso). Quizá las más conocidas y recomendadas son:

  • Spectrum Machine Language For The Absolute Beginner (William Tang - Melbourne House).
  • Codigo Maquina ZX Spectrum Para Principiantes (William Tang - INDESCOMP), traducción del anterior.
  • ZX Spectrum Codigo Maquina Simplificado Volumen 1 y 2 (James Walsh - Colección m/b).

No obstante, asumo que si estás leyendo estas líneas es porque tienes estos conocimientos, ya que no se me ocurre otro motivo por el cual una persona podría acercarse a conocer el lenguaje máquina de un ordenador aparecido en 1982 que ya no se comercializa, con el objetivo de aprender a escribir programas para un procesador de 8 bits, que van a disfrutar apenas unos cientos o miles de personas.

Y para eso he escrito este documento, para dar una introducción al mundo de la programación en ensamblador de Z80 para Sinclair ZX Spectrum y que podáis crear desde pequeñas rutinas a increibles juegos.


A lo largo del texto se presentan múltiples ejemplos y rutinas para que el lector pueda verificar la teoría descrita así como utilizarlas directamente en sus propios programas.

Cuando se escribe una rutina para un procesador tan “limitado” como el Z80 suelen presentarse 2 opciones: escribir una rutina comprensible y legible, o escribir una rutina optimizada. El objetivo del curso es que el lector aprenda programación en ensamblador y por lo tanto debe de poder comprender las rutinas que se presentan, por lo que en el desarrollo de los ejemplos y las rutinas ha primado la comprensión frente a la optimización en aquellos casos en que ambas opciones chocaban.

Esto no quiere decir que las rutinas no sean óptimas: al contrario, se han diseñado para que sean lo más óptimas posible siempre y cuando eso no implique hacerlas incomprensibles para el lector.

Un programador avanzado podrá (y deberá) darles una pequeña vuelta de tuerca adicional para exprimir ciclos de reloj o bytes de tamaño a la rutina y hacerla aún un poco más pequeña, o más rápida. Ese podría ser el objetivo del lector una vez acabado el curso y de cara al diseño de un programa.

Quiero destacar que cualquier tarea que queramos hacer en ensamblador (desde dibujar un gráfico en la videomemoria hasta hacer operaciones matemáticas con variables del programa/juego) tiene múltiples soluciones. Podemos conseguir el mismo resultado con rutinas diferentes: cada programador dará no con una, sino probablemente con varias implementaciones diferentes para conseguir lo mismo.

Algunas soluciones serán evidentemente más óptimas que otras. Pero, ¿qué entendemos por “solución óptima”? En unos casos, la solución más óptima será la que menos bytes ocupe (la más reducida en tamaño), y en otros, la que menos ciclos de ejecución requiera (la más rápida).

La definición de “óptimo” dependerá del contexto.

Si estamos realizando una rutina para realizar el menú del juego, leyendo el teclado y esperando a que el usuario realice la selección de las diferentes opciones, no necesitamos que la rutina sea increiblemente rápida si a costa de eso va a ocupar más espacio. La memoria del Spectrum es muy limitada y cada byte ahorrado en una rutina puede ser valiosísimo para que después quepan en memoria todos los textos, gráficos, sonido y código del juego completo. No, en el caso del menú, puede no merecer la pena estrujarnos la cabeza reescribiendo las rutinas para que sean lo más rápidas posibles a costa de hacerlas más grandes, sino al revés: nos va a sobrar tiempo de CPU para hacerlas lo más reducidas en tamaño posibles.

Sin embargo, la rutina principal que dibuja los gráficos del protagonista o los enemigos sí que debe de ser rápida. Si por ejemplo nuestro sprite es de 16 líneas de altura, en vez de hacer un bucle de 16 iteraciones para pintar cada línea, nos podemos permitir repetir el mismo código de pintado 16 veces para ahorrarnos ese bucle (y evitamos una variable para iterar, una comprobación de si hemos ejecutado el bucle 16 veces y el salto al inicio del bucle cuando todavía no hemos terminado de dibujar esas 16 líneas). Podemos “gastar” unos bytes extra para repetir 16 veces las mismas instrucciones de escritura en videomemoria y ganar unos ciclos de reloj a costa de que nuestra rutina ocupe más, porque en este caso sí que merece la pena optimizar por velocidad y no por tamaño. Este ejemplo concreto de optimización de velocidad a costa de tamaño es lo que se conoce como “desenrollar un bucle”.

Como puede verse en este ejemplo, no existe una definición única de “rutina óptima”. Podemos escribir la misma rutina para que haga esa tarea (como dibujar un gráfico) para que ocupe poco, para que sea rápida, o un punto intermedio entre ambas opciones. Y además, para cualquiera de esas 3 opciones, podemos encontrar múltiples soluciones diferentes de implementación.

Por eso, en las rutinas de ejemplo del libro lo que se ha buscado es que sean comprensibles y que sirvan como ejemplo para la teoría que se está tratando en cada capítulo, sin que por ello sean ineficientes en términos generales.

Si un lector sin conocimientos de ensamblador, tras leer el curso, acaba decidiendo programar un juego o programa y utiliza o mejora las rutinas que se presentan en este texto para su uso específico dentro de su programa, podremos decir que el curso ha conseguido su objetivo.

Eso sí, una advertencia: optimizar es adictivo y hay que ponerle fin en algún momento. Cuando llevas un tiempo programando en ensamblador, prácticamente cada vez que miras una de tus rutinas recién acabadas, se te ocurre una forma de mejorarla o arañar un byte o un ciclo de ejecución. Si no te pones un límite, siempre estarás optimizando tus bibliotecas de rutinas pero nunca realizando los juegos o programas propiamente dichos.

Citando a Jonathan Cauldwell en su documento “How to write Spectrum Games”:

Escribir un juego no trata sobre escribir código Z80 “perfecto”, como si tal cosa existiese. Desarrollar un juego para Spectrum es una tarea considerable y no lograrás terminarlo si estás demasiado obsesionado con escribir los algoritmos más eficientes para puntuación o lectura de teclado. Una vez que hayas creado una rutina que funcione y no cause problemas en otros lugares, pasa a la siguiente rutina. No importa si está un poco desordenada o ineficiente, porque lo importante es lograr que la jugabilidad sea correcta. Nadie en su sano juicio va a desensamblar tu código y señalarle defectos.

Tal y como dice Jonathan Cauldwell, las rutinas siempre pueden ser mejoradas y optimizadas después, por lo que una vez tengamos una rutina funcional, lo mejor (a menos que sea evidentemente mejorable) es pasar a programar otra parte del juego. Tenemos la gran ventaja de poder programar en sistemas modernos con ensambladores cruzados, por lo que al contrario que los programadores de la época (que tenían su código en cinta y para cualquier cambio tenían que cargarlo o teclearlo de nuevo en el propio Spectrum) nosotros tenemos una gran facilidad para revisitar ese código más adelante (abriendo un simple fichero con un editor de texto), mejorarlo, y volver a ensamblar nuestro juego para ver los resultados.

Se recomienda encarecidamente al lector que se cree una “biblioteca” de rutinas reutilizables que incluir en sus programas organizadas de forma que las puedas llamar desde cualquier “programa principal” de juego. Estas rutinas deberían estar separadas en diferentes ficheros por su tipo de funcionalidad, haciéndonos así bibliotecas de gráficos, de sonido, o de texto. Además, si las rutinas están bien escritas y documentadas, siempre podrás dedicar tiempo a optimizarlas en el futuro una vez tu juego esté terminado, o para futuros juegos. Esto hará que el tiempo invertido sea más eficiente, y que cada mejora que hagas en tus rutinas reutilizables pueda ser aprovechada en pasados, actuales y futuros programas.


La escritura de un programa en ensamblador de Z80 para ZX Spectrum es un proceso interdisciplinar, que requiere conocer:

  • El hardware interno del microprocesador Z80: qué registros podemos utilizar para realizar operaciones, qué tamaño tienen, y cómo funciona el procesador a la hora de ejecutar código.
  • El juego de instrucciones del microprocesador Z80: qué instrucciones existen, qué efecto tienen y sobre qué registros u operandos pueden trabajar.
  • El “sistema operativo” del ordenador que tiene dicho procesador: necesitaremos conocer algunos aspectos de la ROM del ZX Spectrum, es decir: qué rutinas útiles y variables del sistema aprovechables nos puede proporcionar.
  • La organización del mapa de memoria del ordenador: dónde podemos encontrar las variables del sistema, la zona de videomemoria, la zona de pila, etc.
  • El funcionamiento de sus dispositivos de entrada/salida (I/O) para, por ejemplo, leer el teclado o el estado de un joystick.
  • La sintaxis del Lenguaje Ensamblador utilizado para generar código binario para el Z80, y cómo podemos ensamblar (“compilar”) un programa en Lenguaje Ensamblador para obtener dicho código binario o “código máquina”.
  • Cómo introducir el código máquina resultante del ensamblado de nuestro código ensamblador en la memoria del Spectrum para poder ejecutarlo.
  • Cómo atajar determinados problemas habituales: sumar, restar, multiplicar, dividir, realizar comparaciones, bucles, acceder a la memoria, y cómo utilizarlo para imprimir texto, imprimir gráficos, leer el teclado, etc.

Debido a que es necesario conocer tanta información de tantas áreas, a veces es complicado ofrecer un camino lineal para aprender, por lo que muchas veces haremos referencia a algunos términos (junto a una breve explicación de ellos) que serán ampliados después en otro momento. Por eso, a veces no bastará una única lectura para ver el todo, sino que el repaso de explicaciones anteriores terminará de rellenar los huecos.

Nuestra recomendación al lector es que haga una primera lectura general del curso para obtener unos conocimientos generales de la materia, y después una segunda lectura en la cual relacionará todos los conceptos entre sí.

Para ensamblar los programas de ejemplo del curso hemos utilizado el ensamblador pasmo, disponible en https://pasmo.speccy.org. Podemos en general usar la versión estable 0.5.5 para todo el curso, (excepto si en algún momento necesitamos utilizar la opción –listing, que requiere la 0.6-beta). También se puede seguir el curso con otros ensambladores como sjasmplus o z80asm con unos cambios mínimos en el código.


[ | | ]

  • cursos/ensamblador/prologo.txt
  • Última modificación: 18-01-2024 06:53
  • por sromero