6. Headers and Interrupt Vectors
Last chapter, we covered the "main" section of the test project, which sets the background color and then goes into an infinite loop. Yet that code only accounts for 13 lines out of the 44-line test project source code. In this chapter, we'll explore the remainder of the test project's source code and learn a few more opcodes.
At the very beginning of the test project, we see this curious bit of assembly:
.segment "HEADER" .byte "NES", 26, 2, 1, 0, 0
.segment, since it begins with a period, is an assembler directive - an instruction
for the assembler to follow when it is converting the code to machine code.
as we discussed in Chapter 4, tells the assembler where to put your code in the final output. The
HEADER segment, unsurprisingly, is placed at the very beginning of the resulting .nes
The second line uses another assembler directive that we've seen before.
.byte tells the
assembler to insert literal data bytes into the output, rather than trying to interpret opcodes
from them. The string "NES" is inserted as three ASCII codepoints:
(the ASCII representations of "N", "E", and "S"). The next byte,
26, represents the MS-DOS
"end of file" character (often seen in hex notation as
$1a). These four bytes
are a "magic number" that marks the output as an NES game.
Most file types have a "magic number" at the start of the file to make it easier for an operating
system to know what a file is with certainty. Java bytecode files begin with
PDF files begin with
$25504446 ("%PDF"), and zip files begin with
since the format used to be called "PKZIP"). You can find a larger list of file type magic numbers
The "NES" magic number header specifically identifies the file as an "iNES" NES game. iNES was one of the first NES emulators, and the first to achieve widespread popularity. iNES' author, Marat Fayzullin, created a special header format that provides the emulator with information about the game itself, such as which region (NTSC/PAL) the game is from, how many PRG and CHR ROM banks it has, and more. The full iNES header is 16 bytes long.
Our test project's header, after "NES" and
$1a, specifies that the "cartridge" for our
game contains two 16KB PRG-ROM banks (32KB storage) and one 8KB CHR-ROM bank, and that it uses
"mapper zero". NES cartridges come in many (hundreds) of variations, and the iNES header assigns
each variant a number. Mapper zero, as you might guess, represents the simplest kind of NES cartridge,
generally called "NROM". Some of the earliest NES games used NROM cartridges, including Balloon Fight,
Donkey Kong, and Super Mario Bros.. Due to their relative simplicity, we'll be creating
NROM cartridge games for most of this book.
For more details on the iNES header format, see the article on the
NESDev Wiki. Many of the things that can be
set through the iNES header, like mirroring modes and PRG-RAM, will be discussed in greater detail
later in this book.
Isolating Procedures with
After the header, another
.segment directive switches us to the "CODE" segment,
which begins at 16 bytes into the file (i.e., after the 16-byte header). The first thing
within the segment is a new assembler directive,
.proc. This directive lets you
create new lexical scopes in your code. Basically, this means that the labels you make
inside of a
.proc are unique to that proc. Here's an example:
.proc foo LDA $2002 some_label: LDA #$3f .endproc .proc bar LDA #$29 some_label: STA $2007 .endproc
Here, we use the label
some_label twice, once in
.proc foo and once
.proc bar. However, since they are inside of different procs, the two labels
are treated as totally separate. Using
some_label inside of
bar's version, and there is no way to access
of the label from inside of
bar. Generally, we will wrap independent pieces of
code in their own procs so they can use the same label names without clobbering each other.
This might seem like a lot of effort just to be able to use the same label name in more than
one place, which is true to an extent. The real power of using
when your code is composed of multiple files - some of which you might not have written
yourself! Procs let you safely use a label without having to check through all of the
code to see if you (or another developer) have used the same label before.
The test project uses four procs -
main. We covered the
in the last chapter, but what are the other procs doing?
As discussed earlier, the processor in the NES repeatedly fetches and executes bytes in sequence. When certain events happen, though, we want to interrupt the processor and tell it to do something else. The events that can cause these interruptions are called interrupt vectors, and the NES/6502 has three of them:
- The reset vector occurs when the system is first turned on, or when the user presses the Reset button on the front of the console.
- The NMI vector ("Non-Maskable Interrupt") occurs when the PPU starts preparing the next frame of graphics, 60 times per second.
- The IRQ vector ("Interrupt Request") can be triggered by the NES' sound processor or from certain types of cartridge hardware.
When an interrupt is triggered, the processor stops whatever it is doing and executes
the code specified as the "handler" for that interrupt. A handler is just a collection
of assembly code that ends with a new opcode:
RTI, for "Return from Interrupt".
Since the test project doesn't need to make use of NMI or IRQ handlers, they consist of
.proc irq_handler RTI .endproc .proc nmi_handler RTI .endproc
RTI marks the end of an interrupt handler, but how does the processor
know where the handler for a given interrupt begins? The processor looks to the last
six bytes of memory - addresses
$ffff - to find
the memory address of where each handler begins.
||Start of NMI handler|
||Start of reset handler|
||Start of IRQ handler|
Because these six bytes of memory are so important, ca65 has a specific segment type
.segment "VECTORS". The most common way to use this segment
is to give it a list of three labels, which ca65 will convert to addresses when
assembling your code. Here is what our test project's "VECTORS" segment looks like:
.segment "VECTORS" .addr nmi_handler, reset_handler, irq_handler
.addr is a new assembler directive. Given a label, it outputs the memory
address that corresponds to that label. So, these two lines of assembly set bytes
$ffff of memory to the addresses of the NMI handler,
reset handler, and IRQ handler - exactly the same order as in the table above. Each
label on line 40 is the start of the
.proc for that handler.
When the NES first turns on, rather than starting from memory address
the 2A03 follows a specific series of steps. It fetches the memory address stored in
$fffd (the address of the start of the reset handler).
It places that address into the program counter, which makes the start of the reset
handler the next instruction to be executed. Then it works its way through the reset
handler, instruction by instruction.
The Reset Handler
While the test project doesn't make use of the NMI or IRQ events, it does need a reset handler. The reset handler's job is to set up the system when it is first turned on, and to put it back to that just-turned-on state when the user hits the reset button. Here is the test project's reset handler:
.proc reset_handler SEI CLD LDX #$00 STX $2000 STX $2001 vblankwait: BIT $2002 BPL vblankwait JMP main .endproc
A few things to note about this section of code. First, unlike the other interrupt handlers, it
does not end in
RTI - that's because when the system is first turned on,
the processor wasn't in the middle of doing anything else, so there is nowhere to "return"
to. Instead, it ends with
JMP main. We saw
JMP at the end of
last chapter, where
JMP forever created our infinite loop.
stands for "jump", and it tells the processor to go somewhere else to fetch its next instruction.
The operand for
JMP is a full, two-byte memory address, but it is nearly always
used with a label that the assembler will convert to a memory address at assemble time.
JMP main, here, tells the processor to start
executing the code in
main once it is done with the reset handler.
Second, this code features several opcodes we haven't seen before. Let's learn about them by analyzing the reset handler line-by-line.
Lines 14 and 15 feature two opcodes that are, generally, only found in reset handlers.
SEI is "Set Interrupt ignore bit". After an
that would trigger an IRQ event does nothing instead. Our reset handler calls
before doing anything else because we don't want our code to jump to the IRQ handler
before it has finished initializing the system.
CLD stands for "Clear Decimal mode bit",
disabling "binary-coded decimal" mode on the 6502.
Due to convoluted licensing issues surrounding the 6502 and Ricoh's legal ability
to manufacture it, the 2A03 used in the NES has binary-coded decimal mode circuitry
within it, but all electrical traces that would connect those circuits to the rest
of the chip have been cut.
CLD (and its counterpart,
do nothing on the NES as a result, but calling
CLD as part of a reset
handler just-in-case is considered best practice.
The next three lines go back to familiar loads and stores. We've seen
$2001 before -
it's PPUMASK - but
$2000 is new. This address is commonly referred to as PPUCTRL, and
it changes the operation of the PPU in ways more complicated than PPUMASK's ability to
turn rendering on or off. We won't cover PPUCTRL in detail until later in this book, when
we've seen more of how the NES PPU draws graphics. Like PPUMASK, it is a set of bit fields.
For the purpose of initializing the NES, the main thing to point out is that bit 7 controls
whether or not the PPU will trigger an NMI every frame. By storing
$00 to both
PPUCTRL and PPUMASK, we turn off NMIs and disable rendering to the screen during startup, to
ensure that we don't draw random garbage to the screen.
The remainder of the reset handler is a loop that waits for the PPU to fully boot up before
moving on to our main code. The PPU takes about 30,000 CPU cycles to become stable from
first powering on, so this code repeatedly fetches the PPU's status from PPUSTATUS (
until it reports that it is ready. 30,000 cycles sounds like a long time, but the NES' 2A03 processor
runs at 1.78 MHz, so 30,000 cycles is a tiny, tiny fraction of a second. I'm not going to cover
BPL here, but rest assured that we will come back to them later.
Finally, with all of our setup complete, we jump to
.proc main and execute
our game's actual code.
A Full Reset
The reset handler our test project uses is the most basic handler that will reliably start up the NES in a known-good state. There are many other tasks that the reset handler can take care of, like setting up the Audio Processing Unit (APU) and clearing out RAM. As we add more functionality to our games, we will expand the role of the reset handler as well.
We have now covered all of the code from the test project - congratulations! After the last few chapters, you might be thinking to yourself "this is incredibly complicated, why bother?" The next chapter is for you!