00:00:00.560
Okay, I'm just going to wing this one. I'm actually not going to have my presentation notes available, so wish me luck. This talk is about making a Game Boy emulator in Ruby.
00:00:11.280
To start things off, I'm actually going to show you the actual Game Boy. Sorry, this project started about 12 months ago when I first started reading about it, and then I began implementing the actual emulator about seven months ago.
00:00:25.279
It started off as a simple idea of just seeing how the Game Boy actually works, and then it evolved into a full-blown emulator. So, this is Textis. Unfortunately, it's very slow, one reason being that it's written in Ruby. The second reason is that this is running off my laptop, which is on battery, so the CPU is very underclocked right now.
00:00:42.000
It'll take a few seconds for it to actually click. There we go—it's fully functional. Everything, including the CPU, the Picture Processing Unit, and the memory, is implemented. You can actually run games like Tetris, and all the controls are there as well.
00:01:00.559
As I said, it's pretty slow because it's written in Ruby. Some other games that work include Super Mario Bros. and Super Mario Land. However, this one doesn't work exactly—the background is not updating, so it’s a bit buggy. In a moment, you'll see the Goomba, and when the Goomba comes along and hits Mario, it will actually crash the emulator.
00:01:39.360
Now, there's a crash. Lastly, Pokémon sort of works but also sort of doesn't. It takes about 20 minutes to actually get to the load screen, and when it finally brings something up, it's not very pretty. But you do get to the menu screen where you can meet Professor Oak and hear his talk.
00:02:08.039
My name is Colby Swandale. You can follow me on Twitter at @0xColby. If you have any questions after the presentation, you can find me in the hallways or direct message me, as I’m open to the public. I’m from Melbourne, Australia.
00:02:30.400
Melbourne is famous for its sports and also for experiencing four seasons in a single day. I work for a company called Marketplacer, where we do trade e-commerce, and I wouldn’t be here presenting in front of you without their support, so I thank them.
00:03:00.799
Before we get started, I need to explain what an emulator is. Simply put, it's hardware or software—known as a host—that imitates another piece of hardware or software—known as a guest. You’ve probably used one before, such as VirtualBox or other virtual machines, to emulate different systems like processor architectures, memory, and hard drives. You probably use this technology every day without even realizing it.
00:03:41.799
The device I’m emulating today is the original Game Boy. It was developed by Nintendo in Japan and released in April 1989, selling 11.69 million units, including the Pocket and Color editions. It featured games such as Tetris, Super Mario Land, and Pokémon Red and Blue. The Game Boy has an LCD monochrome display and a 15-hour battery life.
00:04:28.479
Today, I will discuss the main components of the Game Boy: the CPU, the Picture Processing Unit, the memory, and the game cartridge. I will explain how they function as individual units, as well as how they work together as part of a more complex machine, that we call the Game Boy.
00:05:57.600
The first component I'm going to discuss is the CPU. The CPU is basically the main part of the device; it is also known as the main integrated circuit. It's responsible for reading and executing the programs that we write. The CPU inside the Game Boy is a Sharp LR35902, clocked at 4.19 MHz, featuring an 8-bit processor and a 16-bit memory bus. It's similar to the Zilog Z80 and Intel 8080 processors.
00:06:49.920
One of the main things CPUs need to do is execute instructions. To do that, they need a way to store and operate upon data, which is where registers come into play.
00:07:01.320
Registers are very small bits of memory physically located inside the CPU. The Game Boy has 10 registers: eight 1-byte registers and two 2-byte registers. Some are available for general use by programs, while others have specific purposes. The A register—an accumulator register—stores the results of mathematical operations such as addition, subtraction, and bit operations. The F register holds status bits for the CPU, such as if the last operation resulted in zero or had a half or full carry.
00:08:19.480
Next is the PC register, also known as the program counter, which stores the address of the next instruction to be executed by the CPU. Lastly, the stack pointer register holds the top item in the stack. Each of the one-byte registers can be paired with the corresponding one-byte register to form a two-byte register, creating pairs like AF, BC, DE, and HL.
00:09:33.080
Implementing a CPU in Ruby isn't as difficult as you might think. We start with a simple class called CPU and implement our registers as instance variables. We initialize registers A, B, C, D, E, H, L, the Program Counter (PC), and the Stack Pointer (SP) to zero.
00:10:22.360
The main function of the CPU, as I explained earlier, is to execute programs. To do that, it follows individual operations known as instructions. The Game Boy has a total of 256 instructions, which are a small subset of the full set. Some examples of instructions include loading data between registers, performing mathematical operations like addition and subtraction, and manipulating bitwise operations.
00:10:55.440
These instructions come together to form what's called an opcode table. This table defines each of the individual instructions that the CPU can execute. In our implementation, I defined this as an array, where each item is a symbol that points to the actual implementation of the instruction in the emulator.
00:12:16.680
For example, the instruction LD B, C simply loads the value of register C into B. Another example is incrementing B, which increments the value of register B by one. Notice that we also do an AND operation on the result of this increment. This is necessary because we're dealing with Ruby, which only handles integers.
00:12:57.520
In the Game Boy, each register is only one byte, so the maximum value for any register is 255. When we exceed this maximum, the register resets back to zero, which is the natural behavior of an 8-bit register.
00:13:34.680
Moreover, instructions take a particular amount of time to execute, measured in cycles. Each instruction's time is specified in cycles, not seconds, since CPUs operate far too quickly for that. For instance, a simple NOOP instruction takes four cycles to run, while more complex instructions, like calling a subroutine, can take up to 16 cycles.
00:14:30.160
The next component we will discuss is memory. The Game Boy has 64 kilobytes of memory, with no hard drive, meaning there’s no way to persistently store data. Memory is used mainly to store data for the games. The first half of the memory is dedicated to the actual program of the game you’re playing, which is stored in the cartridge.
00:15:27.680
A quarter is dedicated to video memory, another portion is general RAM available for use by all, and the last part is for I/O, including sprites. To implement memory management, we create a class that behaves like the memory management unit, holding an array of the same size as the Game Boy's memory.
00:16:24.759
This array is initialized to zero. For the behavior of the regions inside the Game Boy, we define a method for reading and writing that performs checks based on the memory range being accessed.
00:17:12.160
If you're reading from the memory range 0 to 0x8000, it will read data from the cartridge, while data in the range 0x8000 to 0xA000 will be read from memory. The same logic applies for writing into memory. Due to Ruby's lack of pointers, I define the memory management unit as a global variable for easier access.
00:17:59.360
The next component I'm going to discuss is the Picture Processing Unit (PPU). This is the component that takes video memory managed by the program and translates it into signals for the screen. The PPU operates in four modes, changing continuously throughout each second, about a thousand times a second. The first mode is Sprite Read, where it reads the sprites from memory.
00:19:23.040
During this mode, writing to the respective memory region is strictly forbidden by Nintendo's regulations, because of potential hardware damage. The next mode, Video Read, is where the PPU reads the video memory corresponding to the background and window data, and writing in this mode is also prohibited.
00:20:36.760
Then we have the Horizontal Blank mode, where the PPU prepares to read the next line of pixels. The PPU works by reading line by line instead of drawing everything at once, going through each line and updating it as needed.
00:21:36.960
The Vertical Blank mode occurs when the PPU has finished reading all screen lines and is preparing to reset for the first line again. This mode lasts the longest and gives programmers an opportunity to update a lot of video memory if necessary.
00:22:57.360
To implement the PPU, we first set up a frame buffer, which is pretty much an array storing the raw pixels destined for the screen.
00:23:10.880
Despite the PPU reading memory as it goes, I need this frame buffer to work with the drawing library I’m using. The mode the PPU starts in is what's called Vertical Blank. Additionally, we initialize what's referred to as a mode clock, which keeps track of the cycles needed for the PPU to perform its operations.
00:24:34.199
After these initializations, the step function of the PPU happens simultaneously with the CPU. Once the CPU finishes its instruction, the PPU executes its logic immediately afterward, consuming cycles based on its mode.
00:25:47.199
The PPU employs a tile system to manage its memory efficiently. Each tile is an 8x8 pixel block of raw pixel data, allowing for a significant reduction in memory usage. Instead of storing every single pixel for every frame, the Game Boy uses tiles to reference common visuals, allowing repeated use without excessive memory strain.
00:27:15.240
The screen logic of the Game Boy is straightforward, as it relies heavily on the PPU. The Game Boy's screen resolution is 160 by 144 pixels, featuring a 60 Hz refresh rate and limited to displaying shades of black, white, and gray. The current implementation uses a C library called SDL, along with a gem called ffi, to create a window, renderer, and texture for displaying graphics.
00:28:29.919
The final component is the cartridge, which is the removable device that stores each game purchased in stores. There are 29 different cartridge types, with the primary differences relating to the amount of ROM and external RAM they hold. Each cartridge can contain external hardware, like a real-time clock, or additional features like a rumble pack.
00:30:49.560
The memory bank controller plays a crucial role in making the most of the limited 64 KB of memory available in the Game Boy. Larger games that require more memory use a clever trick to swap between different 16 KB chunks of data, making it possible to access larger games without needing to write to the original ROM.
00:32:07.200
The cartridge controller is initialized during the cartridge setup process. Different types of controllers manage memory access, reading from the specific regions based on commands passed from the game program. For external RAM purposes, specific address ranges allow writing functionalities like setting the current bank.
00:33:40.400
Once we have all components initialized, we can bring everything together into an abstract class named Emulator. Upon initializing the Emulator, we create instances for the CPU, PPU, screen, and a global memory management unit.
00:34:59.360
This class will also define a run method that creates an infinite loop. This loop will execute the CPU and PPU cycles, passing the cycle counts for proper synchronization while providing a frame rendering command only when necessary.
00:36:38.880
Some topics I didn't cover include input controls, memory registers, command-line interactions, the built-in timer, hardware interrupts, SDL usage, sound, and the Boot ROM, which runs preliminary code whenever the Game Boy is powered on.
00:37:10.800
You can find the project at github.com/colDWF. Thank you, and I apologize for the presentation; I didn’t have my speaker notes, but I hope you enjoyed it.