Atari Lynx programming tutorial series:
Atari Lynx memory hardware
The Atari Lynx has 64KB of RAM memory, provided by two 32 KB DRAM modules. 120ns 65535 x 4-Bit Dynamic NMOS Ram 18-pin to be more precise. The type of chips differs for the two Lynx models:
- Model I: SHARP LH2464-10
- Model II: NEC D41454C-10
The memory range that is available for programming covers almost the entire range of 64KB, starting from the address 0x0000 up until 0xFFFF. There are only a few addresses and ranges that cannot be used.
The memory of the Lynx has special ranges at the beginning and at the end of the 64K memory. The first 512 bytes and the last 1024 bytes are treated in a different way than normal memory outside these ranges, and both ranges behave differently from one another.
The first 2 pages of 256 bytes of memory is allocated/reserved for use by the CPU. The 6502 family (including the 65SC02 which is inside the Lynx) uses the first 256 bytes (from 0x0000 to 0x00FF) of memory in a special addressing way. This is called the zero-page memory and there are specific opcodes that make use of this very first 256 byte memory-page. Because all addresses start with 0x00 the CPU can address these locations in an optimized way. The second 256 bytes (0x0100 to 0x01FF) are used as the stack. Again, there are 6502 opcodes that allow you to push registers onto the stack and pull them from it again.
Since the CPU is hardwired to use the second page of memory, you cannot really use this address space. You can use the zero-page memory. Typically you would store often used variables there, both single byte values and double byte addresses, but not actual code (although you could).
The other memory range is from 0xFC00 to 0xFFFF is really special memory. More correctly, the memory is not really special, as the memory at these addresses is normal RAM. As a matter of fact, it is the memory addresses that are special, because depending on a software setting the addresses point to hardware instead of the RAM at the same memory location. This is called memory mapped hardware. In essence you get to “talk” to the hardware of the Lynx at the special memory addresses.
Most of the times you will read from these memory ranges and read values from the hardware, such as the joystick or a timer value. Other times you will write to the address and change the hardware, e.g. change settings of the Suzy sprite engine. Or, when the address is mapped to the underlying memory you will simply access the RAM.
A lot of spaces
The area from 0xFC00 to 0xFFFF is divided into four spaces.
These spaces overlap with the RAM. The spaces give access to the hardware that is mapped to the addresses inside each of the spaces. The blue arrows in the picture above show the routing that can occur for a particular address. Take an address inside Suzy space. Based on the routing (to be discussed later) you will turn left to Suzy space or right to RAM at the blue junction.
Let’s take a look at all four spaces and the particular hardware that is mapped inside each:
- Vector space
Most of vector space is typical for 6502 CPUs. It holds a couple of vectors, or addresses pointers, that have special meaning to the CPU. These are the interrupt, reset, NMI vectors.
The reset vector is used during booting of the CPU and indicates at which address pointer (2 bytes at 0xFFFE (low byte) and 0xFFFF (high byte) respectively) the CPU should start executing code when it is performing a cold boot. The interrupt vector holds the address pointer to jump to when an interrupt request (IRQ) occurs. Likewise, the NMI vector is the pointer for non-maskable interrupts (NMI). It is less relevant for a regular Lynx developer, as the Lynx hardware does not generate any NMI itself. Only the Howard development board used the NMI to trigger stuff inside the Lynx. Mere morals usually do not have a Howard or Pinky board.
Two other memory addresses are inside the vector space. They are not vectors, but rather a reserved address (0xFFF8) and a memory map control (0xFFF9, or MAPCTL) register. We will discuss the MAPCTL address in more detail later.
- ROM space
The area from 0xFE00 to 0xFFF7 is called the ROM space. This area references 512 bytes of ROM memory that is hard-baked into the Lynx CPU. This ROM memory contains the boot code that the Lynx executes during startup. It takes care of the Lynx hardware initialization, decrypting of the cartridge header and loading the first file on the cartridge. The boot code is available on the Internet as the famous LYNXBOOT.IMG file needed for the Handy emulator. It is Atari proprietary code and you are not allowed to distribute it. This is the reason that it is not included in the current emulators like Handy and Mednafen. During booting the addresses point to the ROM inside the CPU and the Lynx will be reading from the ROM instead of the RAM that is also at the same addresses.
- Mikey space
The Lynx’s CPU is a customized 65SC02. It behaves like a normal 65SC02, with additional functionality like the spaces we are discussing now. This chip is nicknamed “Mikey”. Mikey talks to most of the Lynx hardware and has specialized timers and audio registers.
The hardware of the Lynx is accessible through the memory range inside Mikey space. For example, the AUDIN address 0xFD86 is the AUxiliary Data IN and is connected to the same-named pin on the cartridge. By reading and writing to this address 0xFD86 you effectively control the value at the cartridge pin AUDIN and set it to a 0 or 1 value.
- Suzy space
The Lynx has another custom chip and it’s called Suzy. Suzy takes care of the math, joystick and buttons and holds the sprite engine. The functionality of Suzy is used by reading and writing to the Suzy space address range. Again, in effect the address range might map to the hardware or the underlying RAM depending on the memory map control setting we will discuss in the next paragraph.
You have already seen some of the addresses when we talked about the sprite engine. Remember the SPRSYS that was mentioned for sprite collisions? This address equates to 0xFC92 and is the register that has system control bits. One of those bits, bit 5, is the bit that enables or disables collision detection globally. So, by writing either a 0 or a 1 at byte 5 for SPRSYS will enable or disable collisions for all sprites.
You will need to have the Epyx Lynx documentation close at hand to read about all the special addresses inside each of the spaces. You can find those at AtariAge Lynx archives “Lynx Developer Documentation” as the “Handy appendix 2 Hardware Addresses”.
Taking control of memory spaces
You have seen all four memory spaces now. Each of the four ranges have addresses that either map to special registers or hardware, or to the RAM at these locations. You might wonder how it is determined where the addresses point to. The answer to that question is the MAPCTL register at 0xFFF9. The MAPCTL represents the way the address bus is wired and to which side of the memory map the switches are set.
Each of the bits inside the MAPCTL has a particular function and meaning. The 4 least significant bits 0 to 3 are space disable bits. Setting a bit to 1 will disable the corresponding space and give access to the underlying RAM instead of hardware/registers.
The bits 4 to 6 are reserved for future use. Like that is ever going to happen. And the most significant bit 7 is the sequential disable bit, that will make the CPU always use full cycles (5 ticks minimum), never a sequential cycle of 4 ticks. I would encourage you not to mess with that bit and always leave it at 0.
Okay, assume we wrote the value 0x0F to MAPCTL. That means it will write the following bits 0b00001111: 4 zeros for the most significant and 4 ones to the least significant bits. This means that all four spaces are disabled and you will be able to access the full range of RAM memory. Writing 0x00 on the other hand will leave the spaces enabled and you will access the hardware and registers instead.
Most of the time you will need both Suzy and Mikey space enabled to be able to use their functionality. If you are really pressed for memory you could disable ROM pretty safely, and Suzy and/or Mikey selectively whenever you do not need to access the memory mapped hardware addresses.
To illustrate how the Lynx itself uses MAPCTL consider this: during the boot process the MAPCTL is set to 0x80 initially by the boot ROM values. The code that is executed sets it to 0x03 afterwards, effectively disabling Suzy and Mikey and allowing access to the RAM from 0xFC00 to 0xFDFF by setting b0 (Suzy space) and b1 (Mikey space) bits to 1 (disable). This is what the memory map will look like for 0b00000011.
Note that disabling the spaces does not disable the hardware. It just isn’t possible to read and write to the hardware using memory addresses while it is disabled.
The CPU will always execute code that is loaded into RAM memory. The one exception is during booting of the Lynx when ROM space is enabled and the code is executed from the baked-in memory. The Lynx is “notorious” for its inability to map to the ROM memory on a cartridge. This implies that the code on a cartridge must always be loaded into RAM before it can be executed. The same goes for game data (e.g. sprites, other graphics and level data). The loading is made easier with some library functions, but it is not as convenient as simply accessing memory ranges that map to cartridge ROM instead of RAM. Looking on the bright side: it is a simple model where everything is either RAM or potentially hardware (above 0xFC00).
Managing the memory map
With all the above said let’s look back and see what we need to do during development and when our code is executing.
First of all, I think you should check how much memory your program will need. Small programs might not need every single byte from the 64K. When you read the stories of 8-bit developers you will realize that most of them mention the small amount of memory that is available (at all). So, it is safe to assume that you will need as much memory as possible.
Next, you could look into the areas of your game or program that need access to Suzy and Mikey. If there are moments where no access is needed, but memory is used intensively you could disable Suzy and Mikey space and use the memory underneath during those moments.
The include file from CC65 called _suzy.h defines constants for the various bits in the MAPCTL:
/* MAPCTL $FFF9 */ #define HIGHSPEED 0x80 #define VECTORSPACE 0x08 #define ROMSPACE 0x04 #define MIKEYSPACE 0x02 #define SUZYSPACE 0x01
The _suzy.h include file is automatically included by the lynx.h include file that was already a part of your Lynx programs.
Here’s a tiny bit of code that will set MAPCTL to disable Suzy and Mikey space and give access to the RAM from 0xFC00 to 0xFDFF:
POKE(0xFFF9, MIKEYSPACE | SUZYSPACE);
There is no special define for 0xFFF9 inside _suzy.h, _mikey.h or lynx.h, but you could define it yourself like so:
#define MAPCTL 0xFFF9
which would make the previous code sample as follows:
POKE(MAPCTL, MIKEYSPACE | SUZYSPACE);
Other things to note
During the boot process the Lynx boot ROM code will erase all memory and write zeros to all addresses. After that it will load the title screen and the program code. You cannot assume you start off with an zeroed out memory, although the CC65 libraries will take care of most of the ugly details of loading ROM into RAM and zeroing BSS variables in RAM.
Also, the aforementioned include files _suzy.h and _mikey.h hold two struct definitions that nicely wrap the access to the hardware addresses (inside the Suzy and Mikey address space). It is recommended to use the defined values SUZY and MIKEY to access these addresses instead of going for the PEEK and POKE route.
Now that we know what the memory looks like and how it is managed, it will be time to do some loading of ROM data from cartridges. Then again, who knows what the next installment will bring. Wait and see. Till next time.