Advanced memory management for 128Kb-based Amstrad CPCs
Most of Amstrad CPC developers (including me) make use of very poor memory design scheme to handle the extendted RAM of the Amstrad CPC. The aim of this article is to present you first the basics, then advanced design scheme in order to improve the overall architecture of your future projects. While many articles tends to be quite technical too quickly, this one will be be kept as accessible as possible to everyone.
Disclaimer
I'm still learning. In none of the cases I consider myself as an expert wih low-level or system programming on the Amstrad CPC. If you encounter mistakes in this article, please report them to me I will be glad to fix it.
The Gate Array
GA is a custom chip developed by Amstrad. One of his features is to allow extended RAM handling. If you want more information about, please have a look on excellent Grimware website as reference.
To keep this article as simple as possible (and don't get you distracted by details), know that value of this chip can only be written (not read).
To set a value to GA, we use the following code :
ld b, &7F ; Gate Array port
ld c, &C0 ; value to send
out (c), c ; validate
To keep this article as simple as possible (and don't get you distracted by details), know that value of this chip can only be written (not read).
To set a value to GA, we use the following code :
ld b, &7F ; Gate Array port
ld c, &C0 ; value to send
out (c), c ; validate
The default memory scheme
Amstrad CPCs can only adress 65536 bytes at a time. It means memory always starts from &0000 and ends at &FFFF. Actually, it's convenient for later explanations to split memory with &4000 bytes-sized blocks (called banks) like that :
bank 0: &0000-&3FFF, bank 1: &4000-&7FFF, bank 2: &8000-&BFFF, bank 3: &C000-&FFFF
Example : if a byte is written to &8123 adress, it will be written in bank 2. If a byte is read from &5423 adress, it will be read from bank 1.
This memory scheme is the standard one when an Amstrad CPC got powered up (even 64Kb-based Amstrad CPCs !).
bank 0: &0000-&3FFF, bank 1: &4000-&7FFF, bank 2: &8000-&BFFF, bank 3: &C000-&FFFF
Example : if a byte is written to &8123 adress, it will be written in bank 2. If a byte is read from &5423 adress, it will be read from bank 1.
This memory scheme is the standard one when an Amstrad CPC got powered up (even 64Kb-based Amstrad CPCs !).
The classical memory scheme usage
On 128Kb-based machines, there is a complete secondary set of other 4 banks (&4000 bytes as size for each banks) : bank 4, bank 5, bank 6 and bank 7.
As previously told, the Amstrad CPC can only adress 64Kb at a time.
In order to access the extended banks, it's possible to set following values to GA to get a new memory scheme :
&C4 : bank 0, bank 4, bank 2, bank 3
&C5 : bank 0, bank 5, bank 2, bank 3
&C6 : bank 0, bank 6, bank 2, bank 3
&C7 : bank 0, bank 7, bank 2, bank 3
&C0 : bank 0, bank 1, bank 2, bank 3 (the default mode at startup)
Example : If we want bank 6 to get accessible, we set &C6 value to GA. As a consequence, every bytes read from/written to &4000-&7FFF will be done through bank 6. Still using &C6 as value to GA, if we write a byte in &3FFF adress, it will be written in bank 0. If we write a byte at &8000 adress, it will be written at bank 2. To get back to default memory scheme, we set &C0 value to GA.
This memory scheme is the one used by most of the programmers on Amstrad CPCs.
As previously told, the Amstrad CPC can only adress 64Kb at a time.
In order to access the extended banks, it's possible to set following values to GA to get a new memory scheme :
&C4 : bank 0, bank 4, bank 2, bank 3
&C5 : bank 0, bank 5, bank 2, bank 3
&C6 : bank 0, bank 6, bank 2, bank 3
&C7 : bank 0, bank 7, bank 2, bank 3
&C0 : bank 0, bank 1, bank 2, bank 3 (the default mode at startup)
Example : If we want bank 6 to get accessible, we set &C6 value to GA. As a consequence, every bytes read from/written to &4000-&7FFF will be done through bank 6. Still using &C6 as value to GA, if we write a byte in &3FFF adress, it will be written in bank 0. If we write a byte at &8000 adress, it will be written at bank 2. To get back to default memory scheme, we set &C0 value to GA.
This memory scheme is the one used by most of the programmers on Amstrad CPCs.
The advanced memory schemes
There are other existing memory schemes :
&C1 : bank 0, bank 1, bank 2, bank 7
&C2 : bank 4, bank 5, bank 6, bank 7
&C3 : bank 0, bank 3, bank 2, bank 7
I knew from a long time these modes existed ; but never got the "catch" for a real use.
Did you notice bank 7 was underlined ? :) If your application's main loop is running from bank 7, you can easily do data transfert from a bank 7 to any other ones (bank 0-6). Think about it : with previously described modes (&C0, &C4, &C5, &C6, &C7), if we want by example copy data from bank 4 to bank 1, we had to first read data from bank 4 (inside &4000-&7FFF), store this data temporarly somewhere else (outside &4000-&7FFF) then write this data back to bank 1 (inside &4000-&7FFF). By using &C1, &C2, &C3, transferring data from bank 7 to bank 0-3 can be done directly, without the need of using a temporary memory section to copy data.
&C1 : bank 0, bank 1, bank 2, bank 7
&C2 : bank 4, bank 5, bank 6, bank 7
&C3 : bank 0, bank 3, bank 2, bank 7
I knew from a long time these modes existed ; but never got the "catch" for a real use.
Did you notice bank 7 was underlined ? :) If your application's main loop is running from bank 7, you can easily do data transfert from a bank 7 to any other ones (bank 0-6). Think about it : with previously described modes (&C0, &C4, &C5, &C6, &C7), if we want by example copy data from bank 4 to bank 1, we had to first read data from bank 4 (inside &4000-&7FFF), store this data temporarly somewhere else (outside &4000-&7FFF) then write this data back to bank 1 (inside &4000-&7FFF). By using &C1, &C2, &C3, transferring data from bank 7 to bank 0-3 can be done directly, without the need of using a temporary memory section to copy data.
Going even further
If you work with 16Kb-sized screen (the default), note how bank 1 and bank 3 are switched thanks to &C1 and &C3 usage. By changing screen adress via CRTC, it's possible to always use the same fixed screen adresses for your drawing routines.
Also, you could eventually redirect interrupts (using IM2 interrupt mode) to bank 7, as also the stack (SP). As a consequence, it's possible to completely make use of the first 64Kb of memory as VRAM (pure 32Kb page-flipping !). Even better, it's also possible to adjust the height of a character from 8 to 7, 6 or even 5 (using Register 9 of CRTC) to get more memory available for data (and less for VRAM). But this is another story :)
Also, you could eventually redirect interrupts (using IM2 interrupt mode) to bank 7, as also the stack (SP). As a consequence, it's possible to completely make use of the first 64Kb of memory as VRAM (pure 32Kb page-flipping !). Even better, it's also possible to adjust the height of a character from 8 to 7, 6 or even 5 (using Register 9 of CRTC) to get more memory available for data (and less for VRAM). But this is another story :)
Conclusion
That's it ! This article did not have to be a large one. I hope you will now eventually evaluate &C1, &C2 and &C3 modes for your next coming projects. At least, I will do that favor for me from now.