Having reviewed the Game Boy’s hybrid CPU, it’s time to take a look at the rest of the hardware that compose this beautiful console, and that we have to emulate. The CPU, though arguably the most important part of the whole system, would undoubtedly be plain useless if not played along with the rest of the symphony.
The memory, interrupt, timer, video and sound systems are among that ‘rest of the console’ that makes the Game Boy possible. Here is a summary of the technical details of the Game Boy, courtesy of the Game Boy Development Community:
- CPU – 8-bit (Similar to the Z80 processor)
- Clock Speed – 4.194304MHz
- Work RAM – 8K Byte
- Video RAM – 8K Byte
- Screen Size – 2.6″
- Resolution – 160×144 (20×18 tiles)
- Max sprites – Max 40 per screen, 10 per line
- Sprite sizes – 8×8 or 8×16
- Palettes – 1×4 BG, 2×3 OBJ
- Colors – 4 grayshades
- Horiz Sync – 9198 KHz
- Vert Sync – 59.73 Hz
- Sound – 4 channels with stereo sound
- Power – DC6V 0.7W
Let us, then, start with the memory system.
MEMORY: BANKS, INPUT/OUTPUT AND MORE
Like most consoles of the era, the Game Boy supports a single address space, where all interactions and mappings take place. When we generically say ‘memory’ or ‘main memory’ we actually refer to this address space. Because the CPU’s address bus was 16 bits wide, memory was limited to 64KB. Also, because of the single address space, everything, from I/O to the Game Boy’s external RAM had to be accessed through this memory. If we consider that there were games with various megabytes of ROM: How is it that the Game Boy only offered 64KB of addressable data? Furthermore, if we consider that I/O devices, Working RAM, External RAM, Video RAM, among other special memory areas, had to be accessed through this 64KB space: How did the Game Boy get things to work with such a small address space?
Essentially, the problem was solved through a technique known as memory banking; the same space was reused to map different parts of the game’s ROM. The games were divided into equally-sized chunks called ‘banks’. Each bank would be 16KB in size. The first bank was permanently mapped to addresses 0x0000-0x3FFF (the first 16KB of the address space), and the rest of them were alternately mapped to addresses 0x4000-0x7FFF. By switching banks, the actual size of the game was irrelevant; the Game Boy could play arbitrary-sized games with this technique. Of course, the game was responsible for all this bank switching, as opposed to modern hardware-based techniques for accessing unlimited amounts of memory, such as virtual memory (a technique that is also driven by the operating system). But, considering such limited (compared to today’s standards) hardware, and lack of an operating system, the Game Boy’s application-driven (do-it-yourself) bank switching approach was the only alternative. (Well, it actually wasn’t that bad, since every cartridge was packed with a special hardware unit which actually took care of the bank mappings. The problem was that this switching wasn’t transparent; the application had to make explicit every bank switch).
This is, of course, a simplified picture, because the games actually had banks representing ROM and banks representing RAM, and the Game Boy had special mechanisms to support both types of memory; as mentioned earlier, each cartridge had a special unit which implemented the mechanisms for memory banking. This unit is the Memory Bank Controller (MBC).
The following is the distribution of the 64KB address space provided by the Game Boy’s memory system:
- 0x0000-0x3FFF: Permanently-mapped ROM bank.
- 0x4000-0x7FFF: Area for switchable ROM banks.
- 0x8000-0x9FFF: Video RAM.
- 0xA000-0xBFFF: Area for switchable external RAM banks.
- 0xC000-0xCFFF: Game Boy’s working RAM bank 0 .
- 0xD000-0xDFFF: Game Boy’s working RAM bank 1.
- 0xFE00-0xFEFF: Sprite Attribute Table.
- 0xFF00-0xFF7F: Devices’ Mappings. Used to access I/O devices.
- 0xFF80-0xFFFE: High RAM Area.
- 0xFFFF: Interrupt Enable Register.
Here we see how the Game Boy actually deals with arbitrary amounts of memory in a really tiny 64KB address space. Note the area at addresses 0x0000-0x7FFF. The very first ROM bank of the cartridge is permanently mapped to area 0x0000-0x3FFF, and all the other ROM banks are alternately mapped to the area 0x4000-0x7FFF. The most demanding cartridges would have up to 4MB of ROM, which split into 256 banks (16KB each). The first bank, then, would be permanently mapped at 0x0000-0x3FFF, but for the remaining 255 banks, only one of them could be mapped at any time in 0x4000-0x7FFF. Because, as we saw earlier, this bank switching is not transparent, the programmer had to handle them explicitly, which makes programming the Game Boy tedious. We will study the mechanisms for memory bank switching provided by the Game Boy when we look at the Memory Bank Controllers.
Memory area 0xA000-0xBFFF was used for mapping the cartridge’s RAM (the Game Boy’s external RAM). Unlike ROM banks, which came in pieces of 16KB, the banks representing the Game Boy’s external RAM present in the cartridge came in chunks of 8KB. A cartridge could support up to 32KB of external RAM, which effectively translates to 4 of this 8KB-sized banks. The Game Boy presented a special mechanism for enabling and disabling this external RAM. Also, this external RAM was non-volatile, meaning that it retained the values written to it even after power down, so this RAM was actually where things such as top scores and game progress were saved.
The space at 0x8000-0x9FFF was used for Video RAM (VRAM). This special memory was used to hold the pixels to display on the screen. We’ll describe in detail the structure of the Video RAM area when we study the LCD Display device.
Area 0xC000-0xCFFF and 0xD000-0xDFFF were used to map the Working RAM (WRAM). This RAM was internal to the Game Boy, as opposed to the RAM banks presented by the cartridges. This area indeed consisted of two permanently-mapped banks internal to the Game Boy. It was used by the games to store temporary data; effectively, a working RAM.
Address range 0xFE00-0xFEFF was used to store sprite’s attributes. Sprites are a common graphical construct in video games. This area was used by the LCD Display device to draw the sprites on the screen.
The area 0xFF00-0xFF7F was where all interactions with the devices took place. Individual device registers were accessed through these addresses. These mappings were actually predefined and definite; no device remapping was possible. Specifically, this space was used to drive the LCD Display, the 4 different Sound Channels, the Link Cable, the internal timers and the joypad. Also, the interrupt system was managed through a couple of these addresses.
The Game Boy also presented a special working RAM space at addresses 0xFF80-0xFFFE. It was used arbitrarily by games for storing temporary values.
Finally, address 0xFFFF was mapped to a special register referred to as the Interrupt Enable Register. It was used to enable/disable interrupts.
THE INTERRUPT SYSTEM
Much like every computer system in existence, the Game Boy’s CPU presented mechanisms for handling hardware interrupts. This facilities allowed the devices to notify the CPU when a special event had occurred. The CPU would then call a special software routine, known as an interrupt handler, that took care of anything that needed to be done for the corresponding event. This interrupt handlers were actually part of the cartridge’s ROM, so the game actually knew what was happening in the system. This way, for example, a game could easily know when the LCD Display had finished scanning all of Video RAM, because the interrupt handler corresponding to the V-Blank interrupt, the one that signals this event, would be called automatically. Because the Game Boy could write data to Video RAM only during this period, such knowledge was indispensable. Also, a game could know when some timer count elapsed, and it would do whatever it was that required such specific timing.
The interrupt system provided great flexibility for the software; for example, it allowed disabling interrupts for particular devices, while remaining enabled for other devices. This way, the game could choose to ignore interrupts from devices it was not interested in.
When an interrupt occurred, the CPU automatically disabled any further interrupts, the address corresponding to the instruction following the current one was pushed onto the stack, and a call was made to the interrupt handler. When finishing servicing an interrupt, a special return instruction would pop the return address from the stack onto the Program Counter Register, interrupts would be enabled again, and execution would continue as if the interrupt never occurred. Although while servicing an interrupt further ones were disabled by hardware, upon calling the interrupt handler, they could be manually enabled by software, through a special instruction present in the Game Boy’s CPU (after all, the interrupt handler is just code). This meant that an interrupt could happen even when an interrupt was already being processed; the Game Boy allowed for nested interrupts. Although not common, I remember at least one game that enabled interrupts inside an interrupt handler (this was verified when debugging the game).
The interrupt system was driven by two special memory-mapped registers:
FFFF – IE – Interrupt Enable (R/W)
Bit 0: V-Blank Interrupt Enable (INT 40h) (1=Enable) Bit 1: LCD STAT Interrupt Enable (INT 48h) (1=Enable) Bit 2: Timer Interrupt Enable (INT 50h) (1=Enable) Bit 3: Serial Interrupt Enable (INT 58h) (1=Enable) Bit 4: Joypad Interrupt Enable (INT 60h) (1=Enable)
FF0F – IF – Interrupt Flag (R/W)
Bit 0: V-Blank Interrupt Request (INT 40h) (1=Request) Bit 1: LCD STAT Interrupt Request (INT 48h) (1=Request) Bit 2: Timer Interrupt Request (INT 50h) (1=Request) Bit 3: Serial Interrupt Request (INT 58h) (1=Request) Bit 4: Joypad Interrupt Request (INT 60h) (1=Request)
The bits in the Interrupt Flag Register, mapped at address 0xFF0F, were set individually by the different devices when requesting an interrupt. The possible interrupt sources were the LCD Display, which drove bit 0 (V-Blank) and bit 1 (LCD STAT); the internal timer system, which drove bit 2; the serial/link port, which drove bit 3 and the joypad, which drove bit 4.
Interrupts had priorities, with the lowest bit (V-Blank interrupt) being serviced first (having the highest priority). This was necessary because multiple interrupts could be requested at the same time. When an interrupt was requested, if it was the only requested interrupt, or if it was the requested interrupt with highest priority, the CPU would check the Interrupt Enable Register, mapped at address 0xFFFF. If the bit corresponding to the requested interrupt was set to 0, the interrupt was ignored; otherwise the interrupt was serviced as described earlier.
MEMORY BANK CONTROLLERS (MBC)
Let’s recall that cartridges had a special chip that helped to implement the mechanisms for bank switching. Indeed, most games included a Memory Bank Controller for handling bank switches. There where a variety of MBC’s that cartridges could use, depending on the game’s complexity, starting with the simplest referred to as ‘MBC1’, that allowed for a maximum of 2MB of ROM and 32KB of RAM, going to the most complex, the ‘MBC3’, which included a Real Time Clock (RTC) and external battery. The most simple of games actually didn’t come with a MBC controller; they only had a couple of ROM banks, which were permanently mapped to 0x0000-0x7FFF.
Bank switching was accomplished through memory writes to specific areas in the address space; these writes would access the MBC’s internal registers, and the MBC would do the remapping. Let’s take, for example, MBC1; it’s registers are accessed writing to specific memory areas, as follows:
- 0000-1FFF – RAM Enable Register
- 2000-3FFF – ROM Bank Number
- 4000-5FFF – RAM Bank Number/Upper Bits of ROM Bank Number
- 6000-7FFF – ROM/RAM Mode Select
As we see, writing to specific memory ranges is the way that software interacts with the different MBC controllers; rules similar to the above also apply to MBC2, MBC3, etc. Note that all these memory writes are done to the area where the cartridge’s ROM banks are mapped (0x0000-0x7FFF). The addresses chosen for interacting with the MBC are indeed very convenient, since writing to ROM memory is never expected (hence the name Read-Only Memory), so all ‘writes’ to these areas are actually forwarded to the Memory Bank Controller’s internal registers.
POST IN PROGRESS…
Continue with: A Look At The Game Boy Bootstrap: Let The Fun Begin!.
HELP US IMPROVE!
Did you like this post? Do you have any suggestions? Please rate this post or leave us a comment so we can improve the quality of our work!