Logo MagazineZX
> ÍNDICE DE REVISTAS <
Número 13 - Enero 2006 ZX Certified webmaster speccy.org
 

Arquitectura y funcionamiento del Spectrum

LA ARQUITECTURA DEL SPECTRUM

Antes de comenzar a programar el Spectrum necesitamos conocer su arquitectura: ¿qué hay dentro de nuestro pequeño ordenador y cómo funciona? En BASIC muchas veces podemos olvidarnos de los detalles a nivel de hardware (para eso es un lenguaje de Alto Nivel), pero en ensamblador no: al hablar directamente un lenguaje que se traduce a código máquina, necesitamos conocer exactamente cómo funciona internamente.

Aquí veremos una visión simplificada de la arquitectura hardware del Spectrum pero que en el fondo es todo lo que necesitaremos para la mayoría de programas. Para empezar veremos un esquema de cómo es internamente nuestro Spectrum a nivel de hardware, y después comentaremos uno a uno los elementos que lo componen:

Esquema del hardware de un ZX Spectrum
Esquema del hardware de un ZX Spectrum

En un vistazo general, podemos ver que el microprocesador Z80 se conecta mediante los puertos de entrada/salida de la CPU a los periféricos externos (teclado, cassette y altavoz de audio), pudiendo leer el estado de los mismos (leer del teclado, leer del cassette) y escribir en ellos (escribir en el altavoz para reproducir sonido, escribir en el cassette) por medio de estas conexiones conocidas como "I/O Ports".

Al mismo tiempo, los Buses de Datos y de Direcciones conectan al microprocesador con la memoria. Esta conexión es la que permite que el Z80 pueda leer y escribir en cualquier posición de la memoria. Cuando encendemos el Spectrum, lo que éste lee de la memoria son instrucciones. Empezando por la posición 0000 (0 Kb), el Spectrum comienza a leer instrucciones y a ejecutarlas, una a una. En la primera parte de la memoria tenemos la ROM del Spectrum, que contiene instrucciones de programa preprogramadas y que no podemos modificar: es el menú del Spectrum y el intérprete de BASIC.

Por otro lado, nuestro microprocesador tiene una serie de registros internos con los que trabaja y que son los que manipula y utiliza para ejecutar las instrucciones almacenadas en la memoria.

Algo muy importante sobre la memoria es que hay una zona de ella que se conoce como videomemoria. Es memoria RAM normal y corriente, sólo que los datos que contiene son leídos por un chip llamado ULA muchas veces por segundo, y conforman la imagen que vemos en el televisor de nuestro Spectrum. Escribiendo un valor en una de estas direcciones de memoria (poniendo a 1 uno de sus bits), veremos aparecer en el televisor un punto. La ULA es, pues, el chip encargado de representar en el televisor el contenido de la videomemoria y nosotros, cuando queramos escribir o dibujar algo en pantalla, ya no utilizaremos funciones como PLOT o DRAW, sino que escribiremos directamente valores en esta zona de memoria.

Por último, el puerto de expansión del Spectrum permite conectar nuevos periféricos (como el adaptador de Joystick Kempston o el Interface 1 ó 2) directamente a las patillas de la CPU, ampliando las funcionalidades del ordenador.

Veamos más detalladamente los diferentes componentes de la arquitectura del Spectrum, y cómo funcionan.

EL MICROPROCESADOR Z80

Como podemos distinguir en el esquema, el cerebro de nuestro Spectrum es un microprocesador Zilog Z80 a 3,54Mhz. Un microprocesador es un circuito integrado que consta (principalmente) de registros, microcódigo, puertos de entrada/salida, un bus de datos y uno de direcciones.

Imagen de un C.I. Z80 de Zilog
Imagen de un C.I. Z80 de Zilog

Los registros son variables (igual que cualquier variable de BASIC) que residen dentro de la misma CPU. En el caso del Z80, tiene 2 juegos de registros con unos nombres concretos: entre otros, lo forman registros de un byte como A, F, B, C, D, E, H, L, I y R, y los registros de dos bytes IX, IY, SP y PC. Veremos los registros en detalle en su momento (así como el segundo juego de registros disponible), pero podemos hacernos a la idea de que los registros son simples variables de 8 ó 16 bits que utilizaremos en nuestros programas en ensamblador. Así, podremos cargar un valor en un registro (LD A, 25), sumar un registro con otro (ADD A, B), activar o desactivar determinados bits de un registro (SET 7,A), etc.

Parte del debugger de FUSE mostrando los registros de la CPU
Parte del debugger de FUSE mostrando los registros de la CPU

El juego de registros es todo lo que tenemos (aparte de la memoria) para realizar operaciones en nuestro programa: siempre que estemos operando con datos o utilizando variables, tendrá que ser por fuerza un registro, o una posición de memoria que usemos como variable. Por ejemplo, podemos escribir el siguiente programa en ensamblador, que sumaría dos números:

  LD A, 10
  LD B, 20
  ADD A, B

El anterior programa, una vez ensamblado y ejecutado en un Z80, vendría a decir:

  • Carga en el registro A el valor "10".
  • Carga en el registro B el valor "20".
  • Suma el valor del registro A con el del registro B y deja el resultado en el registro A (A=A+B).

Tras ejecutar el anterior programa en un Z80, el contenido del registro A sería 30 (10+20). Cuando comencemos a explicar las diferentes instrucciones del Z80 veremos en más detalle los registros, su tamaño, cómo se agrupan, y de qué forma podemos usarlos para operar entre ellos y realizar nuestras rutinas o programas.

Existe un registro especial llamado PC (Program Counter, o Contador de Programa). Este registro de 16 bits puede contener un valor entre 0 y 65535, y su utilidad es la de apuntar a la dirección de memoria de la siguiente instrucción a ejecutar. Así, cuando arrancamos nuestro Spectrum, el registro PC vale 0000h, con lo que lo primero que se ejecuta en el Spectrum es el código que hay en 0000. Una vez leído y ejecutado ese primer código de instrucción, se incrementa PC para apuntar al siguiente, y así continuadamente. Los programas se ejecutan linealmente mediante (como veremos) un ciclo basado en: Leer instrucción en la dirección de memoria apuntada por PC, incrementar registro PC, ejecutar instrucción. Posteriormente veremos más acerca de PC.

Ya hemos visto lo que son los registros del microprocesador. Ahora bien, en el ejemplo anterior, ¿cómo sabe el microprocesador qué tiene que hacer cuando se encuentra un comando "LD" o "ADD"? Esto es tarea del microcódigo. El microcódigo del microprocesador es una definición de qué tiene que hacer el microprocesador ante cada una de las posibles órdenes que nosotros le demos.

Por ejemplo, cuando el microprocesador está ejecutando nuestro anterior programa y lee "LD A, 10" (en realidad, lee de la memoria los opcodes 62 y 10), el Z80 utiliza el microcódigo encargado de mover el valor 10 al registro A. Este microcódigo no es más que una secuencia de señales hardware y cambios de estados electrónicos cuyo resultado será, exactamente, activar y desactivar BITs en el registro A (que no es más que una serie de 8 biestables electrónicos que pueden estar a 0 voltios o a 5 voltios cada uno de ellos, representando el estado de los 8 bits del registro A). Lo mismo ocurrirá cuando se lea la instrucción "LD B, 20", sólo que se ejecutará otra porción de microcódigo que lo que hará será modificar el registro B.

Este microcódigo está dentro del microprocesador porque sus diseñadores implementaron todas y cada una de las operaciones que puede hacer el Z80. Cuando pedimos meter un valor en un registro, leer el valor de un registro, sumar un registro con otro, escribir el valor de un registro en una dirección de memoria, saltar a otra parte del programa, etc, para cada una de esas situaciones, hay un microcódigo (implementado mediante hardware) que realiza esa tarea. Nosotros no tendremos que preocuparnos pues de cómo hace el Z80 las cosas internamente a nivel de microcódigo, aunque es bueno que conozcáis cómo llega el Spectrum a ejecutar nuestros comandos: gracias al microcódigo.

PUERTOS DE ENTRADA/SALIDA

Si cogemos un microprocesador Z80, podremos distinguir muchas patillas de conexión. Además de las patillas que se utilizan para alimentar el Z80 desde la fuente de alimentación, existen otra serie de patillas para "Puertos de Entrada/Salida" y "Bus de datos y de direcciones". Esas patillas son la conexión del microprocesador con el resto de elementos del ordenador.

Las 40 patillas del microprocesador Z80. donde vemos el bus de direcciones (A0 a A15) y el bus de datos (D0 a D7), que se conectan a los puertos de entrada/salida y a la memoria
Las 40 patillas del microprocesador Z80. donde vemos el bus de direcciones (A0 a A15) y el bus de datos (D0 a D7), que se conectan a los puertos de entrada/salida y a la memoria

Los registros del microprocesador y el microcódigo son internos al procesador, y ninguna de las patillas que podemos ver en la imagen anterior nos comunica con ellos. Es el procesador (internamente) quien lee/modifica los registros o quien ejecuta microcódigo cuando nosotros se lo decimos con las instrucciones de nuestro programa.

Pero nuestro procesador, como hemos dicho, necesita conectarse con elementos del exterior, entre ellos (y casi exclusivamente en el caso del Spectrum) tenemos la memoria, el teclado, el altavoz y la unidad de cinta.

Visto de un modo muy simple, el Z80 puede acceder a través de una serie de patillas hasta a 65536 elementos externos. Esos elementos se conectan a la CPU Z80 a través de las patillas de "Buses de datos y direcciones" (que podemos leer en los puertos de Entrada/Salida). Una vez conectados (la conexión se realiza físicamente a nivel de hardware, con pistas en la placa base), el microprocesador Z80 puede leer los valores que el dispositivo pone en esas pistas, o puede escribir valores en esas pistas para que el dispositivo los utilice.

Supongamos el ejemplo del teclado: un teclado es (a un nivel muy simple) una matriz de pulsadores. Al final de toda esa matriz de pulsadores, lo que acabamos teniendo es un número de 8 bits (0-255) cuyos bits nos dicen la teclas pulsadas. Pues bien, ese número de 8 bits es lo que el mismo teclado pone en los "cables" que lo unen con el Z80. El teclado se conecta a la CPU mediante una conexión a uno de los puertos (una serie de patillas de datos y direcciones) del microprocesador y gracias a esto el microprocesador (y nuestro programa) puede leer en todo momento el estado del teclado (y saber qué teclas están pulsadas y cuales no) leyendo del puerto correspondiente.

Matriz de teclado del Spectrum y conexiones al Z80
Matriz de teclado del Spectrum y conexiones al Z80

Así, en nuestros programas podemos leer el estado del teclado mediante (por ejemplo):

5  REM Mostrando el estado de la fila 1-5 del teclado
10 LET puerto=63486
20 LET V=IN puerto: PRINT AT 20,0; V ; "  " : GO TO 20

Este ejemplo lee (en un bucle infinito) una de las diferentes filas del teclado, mediante la lectura del puerto 63486 (F7FEh) con la instrucción de lectura de puertos de BASIC "IN". El estado de las 5 teclas desde el 1 hasta el 5 del teclado del Spectrum está conectado a 5 "hilos" de los 8 que llegan al puerto 63486 del micro Z80. Cuando se pulsa una tecla, el teclado pone a 0 (a 0 voltios) el "hilo" correspondiente de esos 8, y nosotros podemos conocer el estado de la tecla leyendo dicho puerto y mirando el bit en cuestión. Al ejecutar el ejemplo anterior, veremos que la pulsación de la tecla "1" cambiará el valor que aparece en pantalla. Si pasamos los diferentes valores que aparecen a binario y nos fijamos en el estado de los 5 últimos bits, nos daremos cuenta como al pulsar y soltar las diferentes teclas del 1 al 5 estaremos variando esos bits entre 0 (al pulsarlas) y 1 (al liberarlas). Los 3 bits más altos del byte debemos ignorarlos, ya que no tienen relación con el teclado.

Si no hay ninguna tecla pulsada, los 5 bits más bajos del byte que hay en el puerto estarán todos a 1, mientras que si se pulsa alguna de las teclas del 1 al 5, el bit correspondiente a dicha tecla pasará a estado 0. Nosotros podemos leer el estado del puerto y saber, mirando los unos y los ceros, si las teclas están pulsadas o no.

Pulsando
Pulsando "1", ponemos a 0 el bit 0, pasando de 191 a 190

Así, leyendo del puerto 63486 obtenemos un byte cuyos 8 bits tienen como significado el estado de cada una de las teclas de la semifila del "1" al "5".

Bits D7 D6 D5 D4 D3 D2 D1 D0
Teclas XX XX XX "5" "4" "3" "2" "1"

Los bits D7 a D5 no nos interesan en el caso del teclado (por ejemplo, D6 tiene relación con la unidad de cinta), mientras que los bits de D5 a D0 son bits de teclas (0=pulsada, 1=no pulsada). Tenemos pues el teclado dividido en filas de teclas y disponemos de una serie de puertos para leer el estado de todas ellas:

Puerto Teclas
65278d (FEFEh) de CAPS SHIFT a V
65022d (FDFEh) de A a G
64510d (FBFEh) de Q a T
63486d (F7FEh) de 1 a 5 (and JOYSTICK 1)
61438d (EFFEh) de 6 a 0 (and JOYSTICK 2)
57342d (DFFEh) de P a Y
49150d (BFFEh) de ENTER a H
32766d (7FFEh) de (space) a B

A la hora de leer estos puertos, el bit menos significativo (D0) siempre hace referencia a la tecla más alejada del centro del teclado ("1" en nuestro ejemplo), mientras que el más significativo de los 5 (D5) lo hace a la tecla más cercana al centro del teclado.

En ensamblador también hay disponibles 2 instrucciones para leer el contenido de un puerto de Entrada/Salida y para escribir un valor en un puerto determinado, las instrucciones se llaman igual que en BASIC: IN y OUT:

 ; Cargamos en BC el valor del puerto a leer
 LD BC, F7FEh

 ; Leemos en el registro A el valor del puerto
 IN A, (C)

Queríamos mostraros el ejemplo del teclado, aunque estamos todavía empezando y puede haber sido algo complicado de entender, por diferentes motivos:

  • El primero, explicar qué son los puertos de E/S y cómo están conectados a nivel de hardware con el microprocesador. Como habéis visto, del teclado salen una serie de "hilos" o pistas de circuito impreso que van directamente al Z80, a través de sus diferentes buses y puertos.
  • Nosotros podemos, en cualquier momento, leer y escribir en los puertos de E/S. Con esto conseguimos comunicarnos con los periféricos externos. En este caso, podemos leer del teclado (o escribir o leer del cassette, o en el altavoz) con simples operaciones de lectura y escritura.

El lector debería extraer una conclusión extra del ejemplo del teclado: la gran diferencia de proceso que hay entre programar en ensamblador y programar en BASIC. Supongamos que nos interesa leer el estado del teclado para saber si unas determinadas teclas están pulsadas o no. Para esto, en ensamblador (aunque esto también podemos hacerlo en BASIC) leemos directamente el estado del teclado con un par de simples instrucciones (LD + IN). En BASIC, por contra al leer de un INKEY$ estamos esperando la ejecución de un código que, además de leer TODAS las filas del teclado (no sólo aquellas de las teclas que nos interesen), realiza una conversión de todos los bits pulsados o no pulsados mediante una tabla ASCII para al final proporcionarnos el código ASCII de la tecla pulsada. Lo que son varias simples instrucciones IN en ASM, en BASIC se realiza mediante cientos de instrucciones en ensamblador que nos acaban dando la última tecla pulsada. Es por eso que el intérprete BASIC es tan lento: cada operación BASIC son decenas, cientos o miles de instrucciones en ensamblador que nosotros ni vemos ni controlamos. Programando directamente en ASM, el microprocesador hara EXCLUSIVAMENTE lo que nosotros le digamos que haga. He aquí la "mágica" diferencia de velocidad entre ambos lenguajes.

Podéis encontrar más información sobre los puertos de Entrada y Salida en el capítulo 8 sección 32 del manual del +2A y +3, que tenéis disponible online en World Of Spectrum.

MEMORIA

Al igual que en el caso de los puertos de entrada/salida, nuestro microprocesador está también conectado a los diferentes chips de memoria (hay más de uno). La conexión se realiza siguiendo unas normas definidas por los ingenieros de Sinclair, de tal forma que la memoria se mapea linealmente. ¿Qué quiere decir esto? Que aunque tengamos varios chips de memoria, vemos la memoria como una gran y única memoria de 64KB.

El Spectrum básico (48KB de RAM y 16KB de ROM) tiene disponibles 64KB de memoria, es decir, 65536 bytes a los cuales podemos acceder. Podemos pensar en esta memoria como un gran baúl con 65536 cajones, uno encima de otro. El primer cajón es el cajón 0 (posición de memoria 0), el segundo el cajón 1 (posición de memoria 1), y así hasta el cajón 65535 (posición de memoria 65535).

Nuestro Spectrum no puede tener más de 65536 cajones porque el "bus de direcciones" del microprocesador Z80 es de 16 bits, es decir, las líneas que conectan al microprocesador con la memoria sólo permiten 16 "conexiones"; lo que nos da la posibilidad de acceder a 2 elevado a 16 bytes de memoria, exactamente 65536 bytes.

Aspecto de nuestras 65536 celdillas de memoria
Aspecto de nuestras 65536 celdillas de memoria

Cada uno de estos cajones (más técnicamente, "celdillas de memoria" o "posiciones de memoria") puede contener un número de 8 bits, con un valor, por tanto, entre 0 y 255. Esto es así porque el "bus de datos" del microprocesador Z80 es de 8 bits, lo que implica que "sólo hay 8 conexiones" entre la salida de datos de la memoria y nuestro procesador.

El microprocesador Z80 puede acceder a cualquier posición de memoria tanto para leer como para escribir. Internamente, cuando le pedimos al microprocesador que meta en el registro A el contenido de la celdilla de memoria 1234h, mediante una instrucción de ensamblador "LD A, (1234h)" (nota, el operador () en ensamblador significa acceso a memoria, y equivaldría en este caso a un "LET A=PEEK 4660" en BASIC) lo que hace el microprocesador internamente es:

Ejecución de LD A, (1234h):

  • 1234h en binario es 00010010 00110100, de modo que el Z80 coge las 16 líneas que conectan al microprocesador con la memoria y las pone a esos estados (0 = 0 voltios, 1 = 5 voltios).
  • A continuación, el microprocesador utiliza una conexión especial que le conecta con la memoria donde le indica qué operación quiere realizar. Poniendo al valor apropiado la línea de control que le comunica con la memoria, el Z80 informa al chip de memoria de que quiere realizar una operación de lectura.
  • La memoria recibe la señal de "quiero leer un dato" por esta señal de control, y mira el bus de direcciones que le conecta con el Spectrum. Mirando el estado de las 16 líneas encuentra el "00010010 00110100" (1234h). Con eso, la memoria sabe a qué "casilla" o "cajón" quiere acceder el microprocesador.
  • La memoria lee el valor de la celdilla de memoria 1234h (por ejemplo 0Fh, que es 00001111 en binario) y cambia las 8 conexiones del Bus de Datos para que contengan 00001111 (4 "líneas" las pone a 0 voltios, y las otras 4 a 5 voltios).
  • El Z80 consulta el bus de datos y ve el estado de las líneas, con lo que lee el "00001111" o 0Fh.
  • El Z80 coloca en el registro A el valor 0Fh.

El procedimiento para escribir es similar, salvo que la línea de control entre el Z80 y la memoria en lugar de indicar "lectura" indica "escritura", y que es el Z80 quien pone en el bus de datos el valor que quiere escribir en la celdilla indicada en el bus de direcciones:

Ejecución de LD (1234h), A

  • Supongamos que A contiene el valor 15 (0Fh): el Z80 coloca las líneas del bus de datos a los valores 00001111.
  • El Z80 coloca las líneas del bus de direcciones a 00010010 00110100 (1234h).
  • A continuación, el microprocesador pone la línea de control READ/WRITE a tal valor que la memoria sabe que el micro le pide una operación de escritura.
  • La memoria recibe la señal de "quiero escribir un dato", y mira el bus de direcciones que le conecta con el Spectrum. Mirando el estado de las 16 líneas encuentra el "00010010 00110100" (1234h). Con eso, la memoria sabe a qué "casilla" o "cajón" quiere acceder el microprocesador para escribir.
  • La memoria lee el valor del bus de datos para saber qué dato tiene que escribir.
  • La memoria escribe en su cajón número 1234h el valor 0Fh.

Estas son las 2 operaciones básicas que el Z80 puede realizar con la memoria: leer una posición de memoria y escribir en una posición de memoria. Nosotros no tenemos que preocuparnos de ninguna de las señales necesarias para realizar lecturas y escrituras, de eso se encarga el microprocesador. Para nosotros, a nivel de ensamblador, nos bastará con ejecutar "LD A, (1234h)" o "LD (1234h), A", por ejemplo.

Las celdillas desde la nº 0 a la 16383 están ocupadas por un chip que es la ROM del Spectrum. Este chip es de sólo lectura (ROM = Read Only Memory), lo cual quiere decir que si intentamos escribir en las celdillas desde la 0 a la 16383 no conseguiremos cambiar el valor almacenado en ellas. ¿Por qué no se puede escribir aquí? Porque es la ROM del Spectrum, es un chip que contiene el sistema operativo del Spectrum, su intérprete BASIC, como veremos posteriormente.

ROM y RAM
ROM y RAM

Para trabajar (ejecutar programas, realizar operaciones y tareas) podemos utilizar el resto de la memoria. Desde el cajón o celdilla número 16384 hasta el 65535 podremos escribir y leer.

La memoria RAM (celdillas 16384 a 65536) es muy importante para el Spectrum. Para empezar, en ella es donde se almacenan los datos y donde se cargan los programas, y de ella es de donde lee el microprocesador estos programas (como veremos posteriormente) para ejecutarlos instrucción a instrucción. Cuando en nuestra anterior entrega del curso pokeamos la rutina en código máquina en la dirección 40000, estabamos escribiendo un programa en memoria para después ejecutarlo.

Hay una parte de la memoria RAM que es especial. El trozo de 6912 bytes que va desde la dirección 16384 hasta la 23296 es conocida como VideoRAM. Esta porción de la memoria no se utiliza para almacenar programas ni datos, sino que es una representación numérica de los gráficos que aparecen en nuestro televisor. La ULA (un chip que hay dentro de nuestro Spectrum) lee continuamente esta zona de memoria y transforma los unos y ceros que en ella encuentra en puntos y colores en el televisor.

Visto de una manera simple (pero real): al escribir un valor numérico (por ejemplo un 1) en alguna dirección de esta parte de la RAM, de forma inmediata aparece un punto en nuestro televisor, ya que la ULA está continuamente "escaneando" la videoram (de forma independiente del Z80) para reflejar en el televisor todos los valores numéricos que introduzcamos en ella.

Por ejemplo, el siguiente programa pinta 2 píxeles en el centro de la pantalla escribiendo en la videomemoria:

10 REM Pintando 2 pixeles en pantalla mediante POKE
20 LET DIRECCION= 16384 + 2000
25 REM 129 = 10000001
30 POKE DIRECCION, 129
40 PAUSE 0

Si ejecutamos el programa, veremos 2 puntos en el centro de la pantalla. Estos 2 puntos aparecen al escribir el byte de valor 129 en la dirección 18384. El número 129, en binario, es 10000001. Esos 2 unos son los que se convierten en 2 puntos cuando la ULA lee la videomemoria y transforma los unos en colores de tinta y los ceros en papel.

Lo que nos tiene que quedar claro al respecto de la memoria es lo siguiente:

  • El microprocesador puede acceder a la memoria tanto para leer como para escribir, y lo hará cuando nosotros se lo pidamos (para leer o escribir datos en memoria). También lo hará para leer las instrucciones a ejecutar, como veremos posteriormente.
  • La memoria está formada por la ROM, la VideoRAM y la RAM.
  • En la ROM no podemos escribir (pero sí leer). Almacena el "código" de arranque del Spectrum, así como el intérprete de BASIC. En ella existen rutinas que usa BASIC que podremos aprovechar en nuestros programas en ensamblador.
  • En la videoRAM sólo leeremos y escribiremos cuando queramos dibujar cosas en pantalla (puntos y colores).
  • En el resto de la RAM (a partir de la dirección 23296) es donde realizaremos todo el trabajo: allí situaremos nuestros programas, nuestras variables, datos, gráficos que después copiaremos a la videoRAM, etc.

EL PUERTO DE EXPANSIÓN

El puerto de expansión del Spectrum no será importante en nuestro curso, ya que no lo utilizaremos para nada. Para vuestra información, basta con conocer que los diferentes pines del puerto de expansión de nuestro ZX están conectados directamente a diferentes patillas del microprocesador. Es decir, cada una de las líneas que podemos ver físicamente en el puerto de expansión es una pista de circuito (como si fuera un cable) que va directamente a alguna de las patillas del Z80.

Es por eso que mediante el puerto de expansión se puede implementar casi cualquier cosa hardware en el Spectrum sin tener que abrirlo: estamos conectando cosas directamente al micro y tras hacerlo, podemos realizar programas que accedan a esos elementos recién conectados: podemos leer los joysticks (porque los estamos conectando a puertos de Entrada/Salida mediante los buses de datos y direcciones), podemos leer cartuchos (porque "ocultamos" la memoria del Spectrum y la reemplazamos con otra memoria que contiene el juego ya cargado en ella, poniéndo esta memoria en el Bus de datos y Direcciones), etc.

CÓMO FUNCIONA EL SPECTRUM

Una vez hemos visto todas las partes funcionales, veamos cómo funciona el Spectrum a nivel de ciclo de instrucción.

Para empezar, los diferentes dispositivos externos (teclado, altavoz, cassette, elementos conectados al puerto de expansión, joysticks) se comunican con la CPU por medio de puertos de Entrada/Salida (Puertos E/S o I/O Ports). Para acceder a ellos simplemente leemos o escribimos en el puerto correspondiente mediante las instrucciones IN y OUT del Z80.

Por otra parte, nuestro Z80 puede leer y escribir de la memoria mediante instrucciones LD ("LD valor, (direccion)" y "LD (direccion), valor"), pudiendo utilizar también otro tipo de instrucciones para hacerlo (que veremos en su momento).

En realidad el Z80, visto de una forma simplificada, sólo puede hacer 3 cosas: leer/escribir en la memoria, leer/escribir en los dispositivos de Entrada/Salida y decodificar/ejecutar instrucciones. La parte de lectura/decodificación/ejecución es el funcionamiento principal del microprocesador, y es lo que trataremos a continuación.

CICLO DE EJECUCIÓN DE UN SPECTRUM

Como ya hemos visto, la parte central del Spectrum es un microprocesador Z80 el cual ve la ROM y la RAM de forma continuada como la totalidad de su memoria. Es decir, ve 64KB de memoria de los cuales los primeros 16K son el contenido de chip de ROM, y los siguientes 48K los del chip de RAM. Recordemos que el Spectrum puede leer el contenido de cualquiera de estas 65536 celdillas (así como escribir en ellas, pero sólo a partir de la 16384, ya que los primeros 16KB son de ROM).

Al encender el Spectrum éste se inicializa y muestra el BASIC en pantalla. ¿Por qué ocurre esto? Esto ocurre porque el microprocesador Z80 comienza a ejecutar instrucciones desde la dirección de memoria 0, donde está el principio de la ROM de 16K, es decir, el intérprete BASIC.

Al alimentar con corriente eléctrica el ordenador se ejecuta la ROM: al encender un Spectrum (que perdió en su apagado toda alimentación eléctrica) todos los registros del microprocesador Z80 valen 0 (sin alimentación eléctrica, todos los bits de la CPU están a 0 Voltios, es decir, a 0 lógico), incluído el registro PC (Program Counter o Contador de Programa), que es el que apunta a la siguiente instrucción que el Z80 debe leer y ejecutar. Un microprocesador funciona a grandes rasgos de la siguiente forma:

  • Leer instrucción apuntada por el registro PC.
  • Incrementar PC para apuntar a la siguiente instrucción.
  • Ejecutar la instrucción recién leída.
  • Repetir continuadamente los 3 pasos anteriores.

Visto en pseudocódigo, como si fuera un programa (de hecho, es el pseudocódigo de cualquier emulador de Spectrum), un Z80 actúa así:

  • Encendido de ordenador:
    • Todos los registros (A, B, C, ..., SP, PC) valen 0.
  • Mientras No Se Apague el Ordenador:
    Leer de la memoria la siguiente instrucción, mediante (contenido de la dirección apuntada por PC):
    • Instruccion = [PC] (Instrucción es un opcode, un número que indica la operación a realizar. Podría ser de más de un byte.)
    • PC = PC + 1
    • Si la instrucción necesita algún operando, leerlo:
      • Operando1 = [PC]
      • PC = PC + 1
      • Operando2 = [PC] (Opcional)
      • PC = PC + 1 (Opcional)
    • Decodificar la instrucción (mirar en una tabla interna y ver qué microcódigo hay que ejecutar para la instrucción leída).
    • Ejecutar la instrucción
    Fin Mientras

Así pues, al encender el ordenador, PC vale 0. Al estar la ROM mapeada en la posición de memoria 0 (mediante cableado hardware de los chips de memoria en la placa del Spectrum), lo que pasa al encender el ordenador es que ese contador de programa (PC) está apuntando al principio de la ROM, y es por eso que se ejecuta la ROM paso a paso, instrucción a instrucción, cada vez que lo encendemos. No hay misterio: para el Spectrum todos los chips de memoria de su interior (porque hay varios chips, no sólo 2) son como si fuera un gran baúl de 64KB continuados, algo que se consigue mediante cableado de los diferentes chips a las patillas correctas del microprocesador (como hemos visto en el apartado dedicado a la Memoria). A grandes rasgos, las patillas de datos y de direcciones del microprocesador están conectadas a los diferentes chips de memoria de forma que cuando el micro lee datos de la memoria, lo ve todo como si fuera un sólo chip de memoria de 64KB. Esto se consigue con un sencillo proceso de diseño (al hacer el esquema del ordenador antes de fabricarlo) conocido como "mapeado de memoria".

En el mapa de memoria del Spectrum, los primeros 16KB son la ROM (que está en un chip aparte, pero que como acabamos de ver es algo que el Spectrum no distingue, ya que la visualiza como una sección de memoria continua desde la posición 0 hasta la 16383 de su "baúl total" de 64KB) y luego viene la RAM, a partir de la posición 16384. Ahí es donde se almacenan los programas, los gráficos de la pantalla (en un trozo determinado de esa memoria), etc. En esta RAM es donde el intérprete de BASIC introduce los programas para su ejecución.

Estos programas pueden entrar desde los diferentes dispositivos de entrada/salida (gestionados por el Z80) como el teclado, la cinta o disco, etc.

Cabe hacer una mención especial (como ya hemos visto) a que una parte de la memoria RAM (desde el byte 16384 hasta el 23296) está conectada con la ULA, el chip "gráfico" del Spectrum, y encargado de convertir el contenido de esta "videoram" o VRAM a señales de vídeo para la televisión. Cuando los juegos dibujan gráficos, sprites o cualquier otra cosa en pantalla, en realidad están escribiendo bytes en estas posiciones de memoria, que la ULA muestra en la TV en el siguiente refresco de la pantalla.

Así pues, nuestro Z80 en el momento del arranque lo que hace es comenzar a ejecutar uno a uno los opcodes que hay a partir de la dirección 0000h de la memoria, que se corresponde con la ROM. ¿Y qué es la ROM? No es más que un programa realizado por la gente que creó el Spectrum. Ese programa es, entre otras cosas, el intérprete BASIC. Los señores de Sinclair programaron un intérprete BASIC en lenguaje ensamblador de Z80, lo ensamblaron con un ensamblador de Z80 y grabaron el código binario resultante ensamblado en un CHIP ROM de 16KB. Por eso al encender nuestro Spectrum aparece el intérprete de BASIC; el Sistema Operativo de nuestro ZX. Nada nos impediría realizar nuestro propio "sistema operativo" para Spectrum creando una ROM nueva (mirando siempre la compatibilidad con la ROM vieja, de forma que contenga las mismas rutinas de ROM y variables en memoria que utilizan muchos programas) y reemplazando el chip ROM del Spectrum por nuestro propio chip de ROM.

OPCODES Y CÓDIGO MÁQUINA

Nuestro microprocesador Z80 no entiende los comandos en ensamblador que hemos estado viendo en estos 2 primeros capítulos del curso de código máquina; el Z80 sólo entiende números binarios, números de 8 bits de 0 a 255 (o de 00h a FFh en hexadecimal).

De entre los registros del microprocesador hay uno llamado PC (Program Counter o Contador de Programa), que es el "puntero" que apunta a la instrucción actual que se está ejecutando. Cuando ejecutamos un programa, lo que hacemos es meterlo en memoria (por ejemplo, como cuando en la primera entrega del curso POKEábamos nuestra rutina a partir de la dirección 40.000) y después saltar al inicio del mismo.

Supongamos por ejemplo que pokeamos el siguiente programa en la dirección 40000:

 LD A, 0
 INC A
 LD B, $FFh
 INC B
 LD DE, $AABB
 RET

Si ensamblamos este programa obtendremos los siguientes números (técnicamente llamados "código máquina"):

 3e 00 3c 06 ff 04 11 bb aa c9

Al pokear en memoria estos valores, dejaremos la memoria así:

Dirección Valor
40000 3e
40001 00
40002 3c
40003 06
40004 ff
40005 04
40006 11
40007 bb
40008 aa
40009 c9

Para nosotros estos números no quieren decir nada, pero para el Spectrum tienen un total significado. Concretamente:

Dirección Valor Significado
40000 3e LD A,
40001 00 00h
40002 3c INC A
40003 06 LD B,
40004 ff FFh
40005 04 INC B
40006 11 LD DE,
40007 bb BBh
40008 aa AAh
40009 c9 RET

A la hora de ejecutar el programa, nuestro RANDOMIZE USR 40000 lo que hace en realidad es cambiar el valor del registro "PC" del microprocesador. Hace PC igual a 40000. Así, el "bucle" del programa que hemos visto arriba en pseudocódigo lo que hace es:

  • Leer el byte contenido en la dirección de memoria "PC" (40000).
  • Incrementar PC (PC=PC+1).
  • El byte es "3eh", con lo cual el Spectrum sabe que tiene que meter en A un valor numérico.
  • El valor extra para "LD A," está a continuación en memoria, así que se lee la memoria de nuevo:
    • operando = [PC] = 00h
    • Incrementar PC (PC=PC+1)
  • Ya se tiene el "código de instrucción completo", así que se ejecuta: "LD A, 00". (se ejecuta el microcódigo correspondiente dentro de la CPU).

Esto que hemos visto es el proceso de "Lectura de Instrucción (fetch)", "decodificación (decode)", y "ejecución (execute)". Pero recordemos que este proceso se ejecuta una y otra vez, sin parar, de modo que el procesador sigue con la siguiente instrucción (INC A):

  • Leer el byte contenido en la dirección de memoria "PC" (40002).
  • Incrementar PC (PC=PC+1).
  • El byte es "3ch", con lo cual el Spectrum sabe que tiene que incrementar A.
  • No hacen falta operandos extra, INC A no requiere nada más.
  • Ya se tiene el "código de instrucción completo", así que se ejecuta: "INC A".

Y este ciclo se vuelve a repetir, una y otra vez, hasta que llegamos al RET:

  • Leer el byte contenido en la dirección de memoria "PC" (40009).
  • Incrementar PC (PC=PC+1).
  • El byte es "c9h", con lo cual el Spectrum sabe que tiene que hacer un RET.
  • No hacen falta operandos extra, RET no requiere nada más.
  • Ya se tiene el "código de instrucción completo", así que se ejecuta: "RET".

Un par de detalles a tener en cuenta:

  • Como véis, el microprocesador no entiende el lenguaje ensamblador, sólo la traducción de este a Lenguaje Máquina (los números u opcodes que estamos viendo).
  • La primera parte leída de la instrucción es el OPCODE (código de operación), y es lo que permite al Spectrum, mediante una "tabla interna", saber qué tarea exacta tiene que realizar. Si la instrucción necesita datos extra para leer de memoria, se almacenan tras el opcode, y se conocen como "operandos". Así, "LD A, 00" se corresponde con la instrucción "3E 00", donde "3E" es el código de operación (opcode) y "00" es el operando.
  • Cuando un operando es de 16 bits (2 bytes), primero encontramos el byte bajo y luego el byte alto. Así, nuestro "LD DE, $AABB" no se codifica como "11 AA BB" sino como "11 BB AA". El opcode para "LD DE" es "11", y "BB AA" los operandos (en este caso, un valor numérico directo). Esta forma de almacenamiento se denomina técnicamente "little endian".
  • Para el Spectrum, no hay diferencia entre instrucciones y datos. Un "3Ch" puede ser un "INC A" o un valor númerico "3Ch". ¿Cómo distingue el Spectrum uno de otro? Sencillo: todo depende de si se encuentra al principio de un ciclo de decodificación o no. Es decir, si cuando vamos a empezar a leer una instrucción leemos un "3Ch", es un INC A. Pero si lo leemos en el proceso de lectura de un operando, su significado cambia. Pensad en por ejemplo en "LD A, 3Ch", que se codificaría como "3E 3C", pero no ejecutaría un INC A porque la lectura del "3Ch" se realiza como operando para el "LD A,".
  • Al no existir diferencia entre instrucciones y datos, si cambiamos PC de forma que apunte a una zona de la memoria donde hay datos y no código, el Z80 no se enterará de ello y tratará de ejecutar los números que va leyendo como si fuera código (con resultados imprecedibles, seguramente con el cuelgue del Spectrum o un reset).
  • Por último, existen una serie de opcodes compuestos (dejando de lado los operandos) que ocupan más de 1 byte. Esos opcodes suelen comenzar por CB, ED o FD, de forma que, por ejemplo el opcode "CB 04" se corresponde con la operación "RLC L". Si sólo pudieramos utilizar un byte para representar el opcode, sólo tendríamos disponibles 256 posibles instrucciones en el procesador. Para poder disponer de más instrucciones se utilizan códigos de instrucción de más de un byte. Así, cuando nuestro procesador encuentra un CB, ED o FD sabe que el próximo código que lea después tendrá un significado diferente al que tendría sin el CB, ED o FD delante. Es por eso que "04h" significa "INC B", y "CBh O4h" significa "RLC L" (otra instrucción diferente).

ENSAMBLADO MANUAL

Al igual que el Spectrum consulta una tabla interna para saber a qué instrucción corresponde cada número (cada opcode) nosotros podemos consultar una tabla para ensamblar manualmente nuestros programas en ensamblador y obtener los valores de código máquina. Si no tenemos a mano un programa ensamblador que lo haga por nosotros (que, al fin y al cabo, no es más que un traductor con una tabla similar), podemos utilizar tablas para traducir el programa manualmente. Cabe decir que es una labor repetitiva y larga, y se recomienda encarecidamente la utilización de un programa ensamblador para ello.

Cuando lleguemos a la parte de definición del lenguaje veremos que ensamblando manualmente resulta bastante costoso calcular las direcciones de los saltos relativos y absolutos, cosa que el programa ensamblador hace con bastante facilidad.

Aún así, quien quiera intentar ensamblar manualmente podrá hacerlo incluso con la tabla de "Juego de caracteres" que tiene disponible en el manual del +2A/+3, capítulo 8, sección 28 (además de no ser la única tabla de ensamblado manual que existe, ya que hay varias disponibles en Internet).

Tabla de opcodes

TIEMPOS DE EJECUCIÓN

Cada instrucción necesita un tiempo diferente para ejecutarse. No es lo mismo un simple "INC A", que requiere leer un único byte como opcode, no requiere parámetros, y sólo realiza un incremento en un registro, que un complejo "LD A, (1234h)", que requiere leer el opcode, a continuación leer 2 bytes para el operando "1234h", después acceder a la memoria y extraer el dato contenido en (1234h) para, finalmente, depositarlo en A.

Los tiempos de ejecución de cada instrucción son, pues, diferentes, y para conocerlos tendremos que consultar cualquier tabla de tiempos (t-states o t-estados). Podéis acceder a alguna de estas tablas en los enlaces que veréis al final de este artículo.

EL SOFTWARE DE SPECTRUM

A estas alturas ya debemos tener claro cómo funciona el Spectrum, con su microprocesador Z80 continuamente ejecutando el código apuntado por "PC", incrementando este y de nuevo repitiendo el ciclo.

Cuando encendemos nuestro Spectrum, PC vale 0000h y se ejecuta la ROM que, como ya hemos comentado, no es más que un programa hecho por los ingenieros que desarrollaron el Spectrum. Dicho programa está disponible en formato código fuente para consultas ya que usuarios de Spectrum realizaron un desensamblado (a partir de los opcodes, obtener el código fuente original) y comentaron todas las rutinas, variables del sistema y procedimientos que se ejecutan en nuestro Spectrum nada más arrancarlo. El libro "The Complete Spectrum ROM Disassembly" (El desensamblado Completo de la ROM del Spectrum) contiene este desensamblado, y podemos obtenerlo en Internet, por si tenemos curiosidad en conocer las interioridades de la ROM del Spectrum y cómo está programado el intérprete BASIC y las diferentes funciones de la ROM del mismo (con el objetivo de poder "usarlas" en nuestros programas en ensamblador).

Pero aparte de la ROM del Spectrum, ¿cómo llega a la memoria de nuestro ordenador (o emulador) los programas que ejecutamos?. Veamos las diferentes maneras:

  1. Desde cinta: Nuestro LOAD "" provoca en BASIC la llamada a una rutina de la ROM que carga desde cinta el código y los datos de los programas. Lo único que se hace es leer de la cinta los opcodes y sus operandos, así como cualquier otro dato (gráficos, sonidos) del programa, e introducirlos en memoria en una zona a la que luego saltaremos (cambiaremos PC a ella). Cuando grabamos a cinta, lo que hacemos es leer el contenido de un trozo de memoria y escribirlo en cinta (escribir los valores numéricos de los opcodes, operandos y datos).
  2. Desde disco: exactamente igual que en el caso de la cinta, pero el medio de almacenamiento es un disco de 3" o de 3.5".
  3. Ficheros TAP y TZX: son ficheros de ordenador que almacenan los datos exactamente igual que si fuera una cinta real: almacenan opcodes, datos y operandos, que luego serán cargados en memoria.
  4. Ficheros .SP, .SNA y .Z80 (en general, cualquier fichero de snapshot). No son más que volcados de la memoria. Por ejemplo, un fichero .SP o .SNA contiene el contenido de las 49152 celdillas de memoria desde 16384 hasta 65536. Para cargar ese .SNA en un emulador, lo que realiza el emulador es un simple "POKEado" del contenido del fichero en las celdillas de memoria. Así, un fichero snapshot no es más que una "copia" de la memoria (de su contenido) que volcamos a fichero.

EN RESUMEN

Hemos visto cómo funciona internamente nuestro ordenador Spectrum y el microprocesador Z80. A partir de la próxima entrega comenzaremos ya con la sintaxis del lenguaje ensamblador y una descripción de las diferentes instrucciones disponibles. No obstante, creemos que los conceptos introducidos ya en estas 2 primeras entregas del curso deben de haber llevado ya al lector a un punto en el cual podrá realizar sus primeras pruebas en ensamblador mediante la documentación a la cual nos referimos en los enlaces. Basta con consultar el juego de instrucciones del Spectrum en la página oficial del Z80 o de Zilog para poder realizar ya nuestros primeros programas en ensamblador para nuestro querido Sinclair ZX Spectrum.

FICHEROS

LINKS



SROMERO
 
Volver arriba
> MAPA WEB <
2003-2009 Magazine ZX