In this post we will resume our study of the interrupt system. Recall from our post Emulating The Core, Part 2: Interrupts and Timing that we studied the foundations of interrupt systems and showed how RealBoy manages to implement the interrupt system specific to the Game Boy. However, we didn’t have a chance to actually process an interrupt because at such an early stage of emulation we didn’t have any interrupt request from any device. Therefore we limited our study to showing how RealBoy checks for interrupt requests from all the possible sources of interruption. We will now show a particular, real-world example of an interrupt being requested and subsequently processed. (Be sure to take a look at our post Emulating The Core, Part 2: Interrupts and Timing in order to fully understand this one. For this activity we will also make use of GDDB, RealBoy’s internal debugger, so make sure you also take a look at A Small Exercise: Presenting GDDB).
A REAL-WORLD EXAMPLE OF INTERRUPT REQUEST AND PROCESSING
To show how games request interrupts and how RealBoy consequently manages to process them, we will show a live session from a game being executed by RealBoy; we will indeed present a real-world example of how a particular interrupt is requested by a device and the way RealBoy processes this interrupt request. For this purpose, we have to set a very peculiar environment that will enable us to show in great detail the mechanics of the process.
In order not to miss a single detail, we have set up a debugging environment with both the GNU Debugger (gdb) and RealBoy’s internal debugger (GDDB). The first one will give us control of RealBoy’s execution; the second will give us control of the game being executed by RealBoy. The particular case we study is found in the game Pokemon Red.
CASE STUDY EXPLAINED
For the proposed activity we have chosen a very specific example presented in the popular video game Pokemon Red. At some point during execution, the game executes the halt instruction. Recall from previous posts that this instruction effectively halts the CPU until an interrupt is requested; code execution stops and the CPU ‘goes to sleep’ until it is ‘awaken’ by an interrupt request. This halt mechanism is very popular in virtually all Game Boy games, because it helps to save the battery’s life when the game must wait for a particular event before doing any further processing; the alternative to the halt instruction is for the game to continuously check for the particular condition, but this is a more inefficient solution.
To follow this example, let us execute RealBoy as with the following command-line arguments: ./realboy <path-to-file> -d
The following image show what is going on (it assumes that the Pokemon Red ROM is in the current directory, and named Red.gb):
Argument ‘-d’ commands RealBoy to start GDDB debugger.
Note that execution somewhat seemed to stop because the ‘-d’ argument was passed to RealBoy when it was invoked. Indeed, execution stopped right at address 0x100. Let’s explain why this is the case:
Remember that upon power on, the Game Boy executes the bootstrap ROM: a 256 byte program that initializes the console before passing control to the cartridge’s ROM. The bootstrap ROM is mapped to the beginning of the Game Boy’s address space and, since it is 256 bytes long (that is, 0x100 bytes long), this code is effectively mapped to the address range 0-255, or 0x0-0xFF. Therefore, the instruction at address 0x100 is the first instruction that actually belongs to the cartridge’s ROM. By default RealBoy skips execution of the bootstrap ROM; the user must provide the bootstrap ROM in the form of files, and explicitly enable its execution by providing an argument (–with-boot, or -b).
Now we are under control of GDDB, RealBoy’s internal debugger. If you hit the command ‘show regs’, you should see something like this:
Initial values for registers
Let’s step over a few instructions and see what happens:
Almost immediately Pokemon Red disables interrupts
There are a couple of interesting things in this first sequence of instructions.
First, the instruction executed at address 0x0150 compares the value of the A registers to 0x11; it jumps to address 0x0157 if A is indeed equal to 0x11. This is important because the same Pokemon Red cartridge can be used with the Game Boy and with the Game Boy Color models. The bootstrap ROM for Game Boy Color initializes the value of register A to 0x11, so the cartridge can immediately figure out if it is being used in a Game Boy Color model.
Second, the instruction executed at address 0x1F54 deals with interrupts; the ‘di’ (disable interrupts) instruction effectively makes the Game Boy ignore all further interrupts from the different devices.
The way RealBoy interprets this instruction is fairly straightforward; it merely sets a variable to 0 so when interrupts are going to be processed, this processing can be skipped:
FILE: gboy_x86_64.S LINE NUMBER: 4600 /* * di (disable interrupts) */ op_dis_ints: movq $0, ime_flag movq $regs_sets, %r10 incq PC(%r10) incq %r13 # new PC ret
Let’s step over some instructions and see what happens immediately after disabling interrupts:
Zero written to Interrupt Flag Register (IF) and Interrupt Enable Register (IE)
Recall from Emulating The Core, Part 2: Interrupts and Timing that interrupts are controlled by the Interrupt Flag Register (IF) and the Interrupt Enable Register (IE). These registers assign one bit for each possible source of interruption. In order for a device to request an interrupt, it must set its corresponding bit in the IF registers (for example, bit 2 for the timer device). If the interrupt for the timer device is enabled, that is, bit 2 is set in the IE register, then when an interrupt from this device occurs, it is acknowledged and processed. Games can ignore interrupts just by clearing the bits in the IE register (the timer device can be ignored writing a 0 to bit 2 in IE).
Now, note that 0 is being written to both IF and IE in instructions at addresses 0x1F56 and 0x1F58. This is part of the initialization of the game; it also writes initial values to lots of other registers.
We have debugged the game previously, so we have spot where interrupts are enabled again. Let’s establish a breakpoint in GDDB and continue execution until interrupts are enable again: Hit the command ‘break 0x1fd3‘. When the breakpoint is set, just hit enter and the program’s execution will continue until the Program Counter matches our breakpoint.
IF and IE values before enabling interrupts
We can print out the values of some registers with the ‘show ioregs‘ command. Note the values for the IF and IE registers. The IF register is 0x00, indeed showing that the has been no request for interrupts, since interrupts were disabled through the ‘di‘ instruction. The IE register, however, is not 0x00, it is 0x0d, that is, binary 00001101. This was set by the game’s code; it tells the CPU what devices it is interested in receiving interrupts from. In this case, it tells the CPU it is interested in the Vertical Blank Interrupt (bit 0), the Timer Interrupt (bit 2) and the Serial Port Interrupt (bit 3). In particular, note that bit 4 is not set; the game is not interested in interrupts coming from the joypad. However, practically no game make use of this interrupt facility; instead they do polled I/O, or software-driven interrupt, which means that it explicitly tests for any key pressed or depressed, rather than using the automatic hardware-based interrupt mechanism for this (this is because the actual hardware mechanism doesn’t work quite well). Let us take a look at how RealBoy handles the ‘ei‘ instruction, which is again very straightforward:
FILE: gboy_x86_64.S LINE NUMBER: 4611 /* * ei (enable interrupts) */ op_ena_ints: movq $1, just_enabled movq $1, ime_flag movq $regs_sets, %r10 incq PC(%r10) incq %r13 # new PC ret
Note that this does just the opposite from the ‘di‘ instruction; it sets the variable ime_flag which tells RealBoy that interrupts should be processed. You can safely ignore the first instruction, which sets the variable just_enabled, since it is not used anywhere in the rest of RealBoy.
Let’s now jump to the point where we can process an interrupt request; let’s jump to instruction at address 0x20B3 and see what happens:
So, we’ve hit a ‘halt‘ instruction. Let’s see what happens when we step over a couple of times:
Note that apparently nothing happened… And indeed. Let’s check out the code that implements the ‘halt‘ instruction in RealBoy:
FILE: gboy_x86_64.S LINE NUMBER: 4662 /* * Halt CPU */ op_halt: movq $1, cpu_halt ret
Unlike every other instruction simulation in RealBoy, the implementation for the ‘halt‘ instruction doesn’t involve advancing the Program Counter to the next instruction. This is not quite what happens in the Game Boy’s CPU; the Program Counter is actually incremented as usual, but instead the CPU effectively halts execution until an interrupt is requested. When an interrupt arrives, the CPU is ‘awaken’, the interrupt processed, and execution continues with the instruction following the ‘halt‘ instruction (just as usual).
Now, let’s take a look at the IF and IE registers at this point:
IF and IE registers
Note that there are no interrupt requests at the moment (IF = 0), and that still IE = 0xD (binary 0x00001101). At this point interrupts are enabled, so the bitmask at the IE register tells the CPU which interrupts to consider and which ones to ignore. As before, the value 0xd (binary 0x00001101) enables the Vertical Blank Interrupt (bit 0), the Timer Interrupt (bit 2) and the Serial Port Interrupt (bit 3).
We discard the Timer and the Serial Port as possible sources of interruption. Indeed, registers 0xFF07 and 0xFF02 control these devices and, as you can see, both registers are 0. These registers are set up by the software but, because the CPU is asleep, there is nothing being executed that could write to those registers. Therefore, the interrupt that will wake up the CPU will not come from either the Timer or the Serial Port:
Interrupts won’t come from the Timer nor the Serial Port
Therefore, the only possible interrupt that could wake up the CPU is the Vertical Blank (VBLANK) Interrupt. This interrupt comes from the LCD display, once the VBLANK period starts; it signals the CPU that a complete frame has been drawn on the LCD Display, and preparation for the next frame has just started. VBLANK period is very important, since here the LCD Display is not accessing VRAM, so the game is free to update it either directly or through the DMA facilities.
Recall where we left off in Emulating The Core, Part 2: Interrupts and Timing:
Here, we tested every possible source of interruption for an interrupt to process. Unfortunately, we had just started the system emulation, so there weren’t requests for any interrupt. Now, in this example, we can actually follow the interrupt processing code line by line.
Note in the above picture the comment at line 193:
FILE: gboy_x86_64.S LINE NUMBER: 193 /* We have an interrupt */
In Emulating The Core, Part 2: Interrupts and Timing we never actually reached the portion of code from lines 194-225, where interrupt processing takes place; execution continued at line 228.
Before we proceed we need to set a ‘breakpoint’ in RealBoy’s execution. Let us now hit the key combination ‘ctrl+c’ in order to drop to the GNU Debugger (GDB).
Now, because we know that the ‘halt’ instruction will be executed until an interrupt is processed, we can safely set a breakpoint in GDB at line 194, the beginning of the actual processing of the interrupt requested (we know that eventually a VBLANK interrupt will lead to this code):
Command to set the breakpoint
We now command GDB to continue execution and we return to our internal debugger, GDDB:
At this point, just hit ‘enter’ to show again the GDDB prompt:
Hit ‘enter’ to get the prompt
We have successfully set a breakpoint in RealBoy’s execution; this is obviously separate from our current execution instance of Pokemon Red with our internal debugger. We now want to examine RealBoy’s code instead of Pokemon Red’s code, so we need to command GDDB to continue execution of the game. To do this, hit the ‘cont’ command:
After hitting the ‘cont’ command in GDDB
Note that execution has indeed stopped at line 194 in our gboy_x86_64.S file; we are ready to process the VBLANK interrupt by passing execution control to the appropriate interrupt handler.
Let’s command GDB to present us a more comfortable interface, so we can have more information and intuition behind what is happening. To do this, hit the following key combination: ‘ctrl+x’ and let go (unpress them) . Now hit ‘a’:
A more pleasant interface for GDB
In case you are not convinced that the reason for our interruption is the VBLANK period, let’s do a couple of tests. First, the VBLANK interrupt is signaled when the LCD Display begins scanning line 144 (we assume you are familiar with the matrix-like organization of pixels on the screen). Therefore, register LY, which is mapped to address 0xFF44, holds the current scanline number. This must be 144 (0x90). We can print this value as follows:
Printing the value of the LY register
Recall that the addr_sp variable points to the beginning of the Game Boy’s address space. We now have a bit of evidence that the interrupt was actually cause by the VBLANK period. To completely convince ourselves that it is the case, let us just print the value of the %rcx registers; recall that it holds the bitmask of the interrupt source:
Interrupt source is indeed VBLANK period
Now, let’s see how RealBoy manages to process this interrupt request. Let’s remember the necessary steps for servicing any interrupt:
1. Disable further interrupts.
2. Acknowledge the current interrupt, that is, clear the Interrupt Request Bit from the IF register.
3. Push the PC onto the stack.
4. Call the appropriate interrupt handler.
Once the interrupt handler has finished servicing the interrupt, it generally executes the ‘reti’ (return from interrupt) instruction, which performs the following (can be seen as a logical continuation to interrupt service):
5. Enable interrupts again.
6. Pop the PC from the stack and execute a ‘ret’ (return) instruction, effectively resuming execution where the interrupt occurred.
Although this is the general algorithm for every instance of interrupt servicing, note that steps 5 and 6 are not necessary; interrupts need not be enabled, and the address on the stack can just be ignored. (As an anecdote, there was actually some bugs in RealBoy because of the assumption that all interrupt handlers eventually executed the ‘reti’ instruction; this was not always the case).
Now, take a look at lines 195 to 200. This code handles the special case when the interrupt occurs while executing a ‘halt’ instruction. Remember that RealBoy handles the ‘halt’ instruction differently than the rest of instructions; the ‘halt‘ instruction does not increment the PC automatically. However, this increment must happen somewhere, or else the ‘halt’ instruction would be executed forever. This increment is handled here as a special case; when the interrupt handler returns by executing a return instruction (‘reti‘, most likely), execution will continue at the instruction following the ‘halt‘ instruction. Because the ‘halt‘ instruction is just 1 byte long, we need just increment by 1 the PC. Additionally, we need to test ime_flag, since in the special case of a ‘halt‘ instruction, we haven’t checked if interrupts are indeed enabled or not; if interrupts are disabled and a ‘halt‘ instruction is executed, then halt doesn’t suspend operation but it does cause the PC to stop counting for one instruction. This is an odd behaviour, but one we need to take into account for an accurate emulation.
Having taken care of this oddity, we continue execution at line 202. Here we start processing the VBLANK Interrupt:
- DISABLE FURTHER INTERRUPTS
Interrupt source is indeed VBLANK period
The first step in the interrupt processing is to disable any further interrupts. This is done in line 202; the ime_flag variable is actually a boolean that tells whether interrupts are enabled or disabled. We write 0 to this variable in order to disable interrupts.
2. ACKNOWLEDGE THE INTERRUPT
Interrupt request is cleared for VBLANK
This is done by clearing the corresponding bit in the Interrupt Request (IR) register. Recall that the value of %rcx is the bit corresponding to the interrupt set to 1, and the rest of bits set to 0. More specifically, %rcx is 1 (the bit for the VBLANK Interrupt). We want to clear this bit in the IR register, which is mapped to address 0xff0f, so we negate %rcx (every bit except for the first one is set to 1; the first one is cleared) and then we and this with addr_sp[0xff0f]. The result is written to this same address. This is done in lines 203 and 204.
3. PUSH PC ONTO THE STACK
PC pushed on the stack
This part is done in lines 205–215. First, recall that register %r11 points to the register set, so we first subtract 1 word (2 bytes) from the Stack Pointer (SP) as part of the push primitive; this is done in line 205. Next, in lines 206 and 207, the PC and the SP are copied to registers %rdx and %rbp, respectively. The next few lines, 208 to 214 are used to get the real address (on the host machine) where we will copy the PC held in register %rdx. Finally, line 215 does this copy with a simple move instruction.
4. CALL INTERRUPT HANDLER
Now we are ready to pass control to the Interrupt Handler Routine. In the Game Boy, these routines are fixed, both in code and memory location; they take the address space at 0x00 to 0x68. In particular, the VBLANK Interrupt Routine starts at address 0x40, which is where we will transfer program execution. Recall that register %r10 the address of this interrupt routine (%r10 is 0x40). We simply copy this value into the PC so that execution continues at that address. The real (host) address is also computed and register %r13 is updated accordingly. This is done in lines 216–225. Here we have officially ended the interrupt handling.
When the interrupt handlers finishes execution, it normally executes the ‘reti’ instruction, which pops out the previously pushed PC from the stack, enables interrupts and then execution continues at the instruction that was interrupted, or at the instruction that follows in case that instruction is ‘halt‘.
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!