viernes, 18 de diciembre de 2015

Gestión de memoria en el Amstrad CPC

Introducción


Me he planteado escribir sobre este tema con el objetivo principal de recopilar información para el diseño del Megaflash Plus, y ya que recogía la información porqué no compartirla con vosotros. Si alguien se le ocurre leerla  :o  y encuentra cualquier error, le agradecería muchísimo que me lo hiciera saber.

Probablemente sea un tema insufrible para muchos de vosotros, pero igual le es útil a alguien, ya que la información que hay a este respecto esta dispersa y en inglés. Yo me conformo con que me sirva a mi mismo, pero si le sirve a alguien más, mejor que mejor.  :D 

El texto aborda las dos cosas necesarias para el diseño del Megaflash Plus:

- la gestión del paginado de la ROM (tanto la lowerROM como la UpperROM)
- la gestión del paginado de la RAM 

Comencemos pues con la descripción.

Gestión de la memoria en un CPC

La verdad es que el modelo de gestión de memoria del CPC está muy currado, me ha gustado mucho como está pensado.
Básicamente, en un CPC, como en cualquier otro microordenador de 8 bits, tenemos dos tipos de memoria; memoria ROM y memoria RAM.
Ya sabemos que el espacio de direcciones de un Z80 es de solo 64K, por lo que estamos acostumbrados a que si por ejemplo tenemos 16KB de ROM solo nos quede disponible para la memoria RAM 48KB. Este es el caso, por ejemplo, del Spectrum.
En un CPC, a diferencia de otros modelos de memoria, el espacio de direcciones de la ROM coexiste con el de la RAM, permitiendo que esta ocupe los 64KB completos del espacio de direcciones del Z80. Esto es posible gracias a un estudiado sistema de gestión de la memoria que permite activar y desactivar la ROM, además de otras muchas características adicionales que consiguen una gestión muy flexible.

UpperROM y LowerROM

En nuestros CPCs existen dos posibles tipos de ROM, la lowerROM y la UpperROM.
La lowerROM es la que podemos considerar la ROM tradicional con la que viene cualquier microordenador, donde residen las rutinas esenciales del sistema.
La lowerROM reside en los primeros 16K del espacio de direccionamiento ($0000-$3FFF).
Por otro lado, tenemos un segundo tipo de ROM llamado UpperROM. Esta ROM reside en la zona $C000-$FFFF, y tiene la particularidad de que no tiene porqué ser única, sino que puede tener multiples UperROMs que son activadas y desactivadas por el sistema y que se inicializan al encender el ordenador, asignandoles un número de UpperROM y guardando en una tabla una lista de comandos (RSX) con los que poder acceder a ellas.
En un CPC464 tal y como viene de fábrica existen una lowerROM (el firmware) y una UpperROM (el BASIC o upperROM 0). Ambas coexisten en un único chip de 32KB.
En el caso de un CPC6128 o un 664, además del BASIC tenemos una segunda upperROM en un chip aparte, con lo que denominamos el AMSDOS, que se inicializa en la posición número 7 de la tabla y contiene todos los comandos de acceso a disco.
Con el interface adecuado (por ejemplo el Megaflash) podemos tener muchas más upperROMs y completar el sistema con sistemas alternativos o añadirle juegos de comandos.
Por otro lado decíamos que podemos tener 64K de RAM ocupando todo el espacio de direcciones. Es el caso de un CPC464.
Por defecto tendremos 16KB de RAM situados en el espacio $0000-$3FFF que se solapan con la lowerROM y que solo podrían ser accesibles en lectura desactivando temporalmente dicha lowerROM, mientras que siempre estarán accesibles en escritura. Además tendremos otros 16KB de RAM que por defecto son los que corresponden a la pantalla, situados en la zona $C000-$FFFF y que por tanto se solapan con la upperROM. El caso es exactamente igual al anterior, por lo que para poder acceder a ellos en lectura deberemos desactivar la upperROM, pero nada nos impide escribir en ellos aunque la upperROM esté activa, por lo que escribir en pantalla no será ningún problema.
El resto de la RAM $4000-$BFFF está siempre accesible tanto en lectura como en escritura.
Los 64Kb de memoria RAM existentes en un 464 son lo que llamamos memoria base o principal.
En este modelo tanto la gestión del paginado de RAM como la de la ROM se realiza mediante un chip propietario llamado Gate-Array (40007 en los primeros modelos, y 40008 ó 40010 en los siguientes). Este Gate-Array es el único chip no estándar del CPC 464 y es a éste como la ULA al Spectrum. O sea, el típico chip que si está jodido ya puedes dar por muerto el CPC.

En el CPC6128 se añadió, además de la disquetera, una expansión de memoria RAM de 64Kb. A dicha memoria le llamaremos memoria extendida. Dicha memoria no puede ser manejada por el Gate-Array original, por lo que Amstrad decidió añadir un nuevo chip auxiliar al Gate-Array cuya función principal es implementar los modos de paginado de la RAM. Dicho chip es el que viene rotulado como 40030 más o menos a mitad del esquema del 6128, y no es ni mas ni menos que una PAL.

Si lo que fuéramos a implementar es una ampliación de memoria interna para el CPC6128, no tendríamos más que hacer uso de las funcionalidades implementadas en dicho chip, para conseguirlo, sin necesidad de añadir ninguna lógica adicional. El inconveniente de esto es que tenemos que meter mano dentro del CPC, además de que hemos de usar memoria dinámica. Otro inconveniente adicional es que esta ampliación no nos serviría para el CPC464 ya que éste no posee dicho chip.




En nuestro caso, Megaflash Plus será un interface externo, lo que hace que no podamos aprovecharnos de la lógica de dicho chip. Debemos entonces implementar toda la lógica en un chip ubicado en nuestro interface que haga las veces del mismo. La idea es utilizar para dicho fin una GAL, que no es ni más ni menos que una versión moderna de una PAL.
Bueno, realmente van a ser dos, una para la gestión de las ROMs (que en la primera versión del Megaflash estaba implementada con 5 chips) y la otra para sustituir la PAL que gestiona el paginado de la RAM.

Si lo vemos desde el punto de vista de un programa desarrollado para CPC, éste necesitará acceder a toda la memoria RAM y quizás también puede necesitar acceder a alguna rutina que tengamos en ROM. Todo esto, y algunas cosas más que no son objeto de este texto, se pueden hacer cambiando la configuración de Gate-Array, escribiendo en varios registros internos de éste. (A partir de ahora hablaremos de ambos chips como si fueran una unidad) 

El problema lo tendremos en el caso de las funcionalidades añadidas por nuestro interface, ya que entrarán en claro conflicto con las del Gate-Array. Para poder sincronizar este tipo de cosas necesitaremos utilizar una serie de señales presentes en el bus y que se verán al final del texto.
Por ejemplo, si queremos gestionar la RAM desde un interface externo deberemos utilizar la señal RAMDIS para deshabilitar los accesos a la RAM interna cuando nos interese.

¿Y como narices puedo yo cambiar un registro del Gate-Array si este es un chip que está en el interior del CPC y al que no tengo acceso? (o a las funcionalidades del Megaflash plus si está presente). Pues muy sencillo, escribiendo valores en un puerto destinado a este fin, el puerto $7Fxx.

Cuando escribimos en este puerto, el valor que escribimos nos indicará tanto el registro como el parámetro que queremos cambiar en ese registro.
Indicamos el registro al que queremos acceder con los tres bits superiores (7,6,5).
El resto de los bits (4,3,2,1,0) son el valor que asignamos al registro.

La siguiente tabla muestra los registros a los que podemos acceder por este método:



De entre todos los registros de la tabla, me voy a centrar en el RMR que gestiona, entre otras cosas, el mapeo de las ROM, y en el MMR que gestiona el paginado de la RAM.
El resto, aunque interesante, se sale del objeto de este texto.

- RMR (100) = Controla el contador de interrupción (reset) y selecciona el paginado de la LowerROM, la UpperROM y el modo de vídeo.

Con este registro habilitamos o no el paginado de la ROM y seleccionamos el modo de vídeo.
Si tenemos habilitado el paginado de la ROM, la selección de la UpperROM la haremos con el registro de selección de página de UpperROM, al que se accede en el puerto $DFxx. 
En lo que a mi me interesa, que es su implementación a nivel de hardware para el Megaflash, se hace una decodificación parcial de dicho puerto comprobando que el bit 13 es igual a 0. Si es así leo el contenido del bus de datos que debe contener la UpperROM seleccionada.

Se puede ver esto y el resto de la funcionalidad del registro RMR en la siguiente tabla.

RMRCommands
76543210
100IURLRVM
I - Si está a 1 se incializa el contador de interrupciones
UR - Habilitar (0), Deshabilitar (1) el paginado de la UpperROM (C000-FFFF)
LR - Habilitar (0), Deshabilitar (1) el paginado de la LowerROM (0000-4000) 
VM - Seleccionar el modo de video (modos 0, 1, 2, 3) Tomara efecto en el próximo HSync.

En el siguiente ejemplo se puede ver como haríamos uso de este registro para deshabilitar la lower y la upperROM.

LD BC,7F00 ;Gate array port
LD A,%10000000+001110 ;Mode and ROM selection (and Gate Array function)
OUT (C),A ;Send it

- MMR (11X) = Registro de selección de página de RAM. Gestiona el paginado de la RAM y nos permitirá gestionar la ampliación de memoria de 512Kb que se implementará en el Megaflash plus.


Como veis hay un montón de combinaciones y modos que se pueden seleccionar, mezclándose la memoria principal con la extendida. Este modo de paginar es algo más complejo que en un Spectrum, por lo que su implementación no es tan trivial como en este.

En el cuadro anterior hemos visto como el registro MMR gestiona en paginado de la RAM, pero para esto Hay dos tipos de RAM:

- Los 64Kb de memoria base. Que corresponden con los primeros 64Kb de la memoria interna. 
- La memoria extendida, ya sea la correspondiente a los 128Kb o las expansiones externas de memoria.
Mediante el registro MMR se permite el uso de hasta 512Kb. Las ampliaciones que aportan más RAM suelen usar direcciones de entrada/salida adicionales para el páginado como $7EXX, $7DXX, etc.

En un CPC 464 sólo tenemos memoria base, pero en un 6128 tenemos 64Kb de memoria base y 64Kb de memoria extendida. Añadiendo 512Kb con un interface externo como el Megaflash Plus, tendremos 64Kb de memoria base y 512Kb de memoria extendida, ya que los 64Kb que ya poseen los CPC6128 quedan reemplazados por la ampliación externa. Tendremos entonces un total de 576Kb.

Como podéis ver en la tabla, la memoria (tanto la memoria base como la extendida) se divide en bloques de 64Kb (Páginas) y estos a su vez en subbloques de 16Kb (Bancos). 

De hecho hay combinaciones que permiten sustituir la RAM base en su totalidad por la memoria extendida, o modos (el 1 y el 2) en los que no está accesible la zona de memoria del display (banco 3 de la página 0).

Accederemos al registro MMR escribiendo en el puerto $7Fxx con el valor de selección deseado. Dicho valor deberá estar compuesto de los siguientes valores:

- Bits 7 y 6: deben valer "11" para indicar que estamos accediendo al registro MMR.

- En los bits del 5 al 3 indicaremos el número de la página de 64kb que deseamos utilizar.

- Por último los bits 2, 1 y 0 nos indicarán el modo en el que dispondremos la memoria en el espacio de direcciones del Z80.

Ejemplo 1:
Si dichos bits valen "011" tendremos la siguiente disposición de la memoria:
              - $0000-$3FFF - Banco 0 de la memoria base (o sea la memoria interna del CPC)
              - $4000-$7FFF - Banco 3 de la memoria base
              - $8000-$BFFF - Banco 2 de la memoria base
              - $C000-$FFFF - Banco 3 de la memoria de la página p de la memoria extendida, donde p corresponde a los bits 5, 4, y 3 de lo que hemos escrito en el puerto $7Fxx.
La selección de dicho modo suponiendo la selección de la página 4 (p=100) se podría hacer con la siguiente secuencia de instrucciones:

LD BC,7F00 ;Gate array port
LD A,%11000000 + %10000 + %011; MMR + p=100 + Mode=011
OUT (C),A ;Send it

Ejemplo 2:
Si el bit 2 vale 1 tendremos la siguiente disposición de la memoria:
              - $0000-$3FFF - Banco 0 de la memoria base (o sea la memoria interna del CPC)
              - $4000-$7FFF - Banco b de la página p de la memoria extendida, donde b es el número formado por los bits 1 y 0 de lo que hemos escrito en el puerto $7Fxx
              - $8000-$BFFF - Banco 2 de la memoria base.
              - $C000-$FFFF - Banco 3 de la memoria base.
Como veis es algo complejo, pero nada que no se pueda implementar con algo de lógica discreta.

SEÑALES DE COMUNICACIÓN PRESENTES EN EL BUS

Pero además del paginado propiamente dicho que gestionamos accediendo a los registros del Gate-Array, necesitamos algunas señales adicionales presentes en el bus de expansión para coordinarnos con éste.
Dichas señales son las siguientes:

Relacionadas exclusivamente con la gestión de la RAM
- RAMDIS (Internal RAM Disable; Entrada a la RAM interna)
Cuando RAMDIS="1" se fuerza a inactiva a la RAM interna del CPC. Se puede usar para sustituir la memoria interna con la de la RAM de expansión.
- /RAMRD (Ram Read; Salida desde el Gate-Array)
Cuando /RAMRD="0" está activa una operación de lectura. Esta señal la genera el Gate-Array. Esta señal será 0 cuando:
A15=A14="0" y el bit 2 del registro de configuración de ROM (RMR) del Gate-Array está a 1. (lower rom deshabilitada)
A15=A14="1" y el bit 3 del registro de configuración de ROM (RMR) del Gate-Array está a 1. (upper rom deshabilitada)
A15 es diferente de  A14.

Relacionadas exclusivamente con la gestión de la ROM

- ROMDIS (Internal ROM Disable; Entrada a la ROM interna). Cuando ROMDIS="1" la ROM interna del CPC es forzada a inactiva. Un interface con ROM incorporada como el Megaflash Plus, debería usar esta señal para sustituir la ROM seleccionada con la presente en el interface. La ROM interna será forzada a desactivarse y la ROM del interface se activará.

- /ROMEN (ROM Enable; salida desde el Gate-Array). Cuando /ROMEN="0" una operación de lectura está activa. Esta señal es generada por el Gate-Array. Esta señal será "0" cuando:
A15=A14="0" y el bit 2 del registro de configuración de ROM del Gate-Array es 0, (lower ROM habilitada)
A15=A14="1" y el bit 3 del registro de configuración de ROM del Gate-Array es 0, (upper ROM habilitada)
Un dispositivo de expansión como el Megaflash puede usar esta señal para activar la ROM seleccionada de las que incorpora en su chip de memoria flash.

Señales relacionadas con ambas.

- /MREQ (Memory Request; salida desde la CPU). Cuando /MREQ="0" la CPU está ejecutando una operación de acceso a memoria. A15-A0 contienen la dirección de memoria.
Si /RD es "0" entonces la opración es una lectura desde memnoria y D7-D0 contendrán el dato leido desde la memoria.
Sí /WR es "0" entonces la operación es una escritura a memoria y D7-D0 contendrá los datos a escribir.

- /IORQ (Input/Output Request; Salida desde la CPU). Cuando /IORQ="0" hay dos posibles funciones:
   -Reconocimiento de interrupción: /M1="0". La función de reconocimiento de interrupción se usa para indicar que el dispositivo que solicita la interrupción puede poner un vector de interrupción en D7-D0.
   -operación de entrada/salida (lectura o escritura): /M1="1", /WR="0" o /RD="0". La operación de entrada salida o I/O se usa para leer o escribir a un periférico. Cuando la CPU está ejecutando una operación de este tip A15-A0 contiene la dirección I/O.

Si /RD es "0" entonces la operación es de lectura y en D7-D0 podremos encontrar el dato leido desde el periférico.
Si /WR es "0" entonces la operación es de escritura y D7-D0 contendrá el dato a escribir.

Adjunto descripción del hardware en pseudocódigo para implementar con una GAL al final de la entrada.

Y de momento hasta aquí puedo leer. Si veo que hay algo que deba corregir o ampliar ya os lo dejaré caer por aquí.

Como decía al principio, no espero que os lo leáis, y de hecho, si habéis llegado hasta aquí tengo que deciros que olé vuestra paciencia, porque soy consciente del rollo que acabo de soltar.
Solo utilizo esto como medio de que quede escrito en algún sitio lo que voy descubriendo para tener un sitio fácil donde consultarlo, y si además le sirve a alguien, pues para que pedir más.

De momento tengo implementada con éxito la gestión del paginado de la ROM en la versión existente del Megaflash plus SX, mientras que la implementación del paginado de la RAM tendrá que esperar a la versión sin el "SX" que no tardaré en desarrollar.

DESCRIPCIÓN DEL HARDWARE EN PSEUDOCÓDIGO

Entradas
NO_ROMEN
A14
A15
D0..7
NO_IORQ        
NO_WR
NO_MEMREQ

Salidas
RAM_NOCE
RAM_NOOE     “Sacar fuera
RAM_NOWE    “” Cablear directamente a /WR
RAMA14
RAMA15
RAMA16
RAMA17
RAMA18
RAMDIS
CLKBUFF

Se habilita el acceso a RAM si NO_ROMEN=1.

Cambio de modo de paginado:

Puerto $7FXX
%01xxxxxx xxxxxxxx
Registro MMR = 11R
CLKBUFF = iA14 & !A15 & D6 & D7&!NO_IORQ; 

NO_RAMOE = !NO_WR  “Sacar fuera
Si !O2 & !O1 & !O0 entonces
“ Pagina = Memoria base
RAMA14=A14;
RAMA15=A15;
RAMA16=0;
RAMA17=0;
RAMA18=0;
NO_RAMCE = 1;
RAMDIS = 0;
endsi

Si !O2 & !O1 & O0 entonces
$C000 = O5O4O3

RAMA14=A14;
RAMA15=A15;
Si A14&A15 entonces
NO_RAMCE = !(NO_ROMEN & !NO_MEMREQ);
RAMDIS = 1;
RAMA16=O3;
RAMA17=O4;
RAMA18=O5;
else 
NO_RAMCE = 1;
RAMDIS = 0;

RAMA16=0;
RAMA17=0;
RAMA18=0;
endsi
endsi

Si !O2 & O1 & !O0 entonces
RAMA14=A14;
RAMA15=A15;
RAMA16=O3;
RAMA17=O4;
RAMA18=O5;

NO_RAMCE = !(NO_ROMEN & !NO_MEMREQ);
RAMDIS = 1;

$0000 = O5O4O3 (banco 0)
$4000 = O5O4O3 (banco 1)
$8000 = O5O4O3 (banco 2)
$C000 = O5O4O3 (banco 3)
endsi

Si !O2 & O1 & O0 entonces
Si A14&!A15 entonces
RAMA14=1;
sino
RAMA14=A14;
endsi;
RAMA15=A15;
Si A14&A15 entonces
RAMA16=O3;
RAMA17=O4;
RAMA18=O5;
NO_RAMCE = !(NO_ROMEN & !NO_MEMREQ);
RAMDIS = 1;
sino
RAMA16=0;
RAMA17=0;
RAMA18=0;
NO_RAMCE = 1;
RAMDIS = 0;
end si
$0000 = Memoria base (banco 0)
$4000 = Memoria base (banco 3)
$8000 = Memoria base (banco 2)
$C000 = O5O4O3 (banco 3)
endsi

Si O2 entonces
si A14&!A15 entonces
RAMA14=O0;
RAMA15=O1;
RAMA16=O3;
RAMA17=O4;
RAMA18=O5;
NO_RAMCE = !(NO_ROMEN & !NO_MEMREQ);
RAMDIS = 1;
sino
RAMA14=A14;
RAMA15=A15;
RAMA16=0;
RAMA17=0;
RAMA18=0;
NO_RAMCE = 1;
RAMDIS = 0;
end si


Referencias

The GateArray - Grimware.org
The Amstrad CPC Firmware Guide
The Amstrad CPC firmware guide
Amstrad CPC 16KBs ROM Game Development Competition!