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

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.

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
En el CPC464 no existen más que 64Kb de memoria, y por tanto solo existe 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. Osea, 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 Amstras 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 fueramos 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 GAL.
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.
- 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 CPC1628 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 (Bancos) y estos a su vez en subbloques de 16Kb (Páginas).
De hecho hay combinaciones que permiten sustituir la RAM base en su totalidad por la memoria extendida.
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 (osea 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.
Ejemplo 2:
Si el bit 2 vale 1 tendremos la siguiente disposición de la memoria:
- $0000-$3FFF - Banco 0 de la memoria base (osea 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.
Descripción del hardware en pseudocódigo para implementar con una GAL:
Código: Seleccionar todo
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
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.