⭅ Previous (Cartridge Basics) | Next (CHR Graphics) ⭆ |
Now that we know what a simple cartridge looks like, lets see how cartridge data is represented.
Also available on Youtube.
The format most often used for cartridge dumps is called INES, named after the emulator that developed the format. INES files are usually named with a “.nes” extension, and is used both by homebrew developers and players backing up their own physical cartridges.
The format has quite a bit of complexity, as is necessary to represent the wide range of innovations that have been used in cartridge hardware. For now we’ll look at implementing a subset of functionality, that which is needed for loading the nestest cartridge and most common test cartridges.
When we looked at the NROM cartridge(cart) in the previous article, we saw that these carts vary in a few key ways:
In order to differentiate an NROM cart from other circuit board types, the NES developer community has assigned an ID number to each circuit board style. Behavior for carts of a given ID number is similar, besides the parameters provided in the INES file. These ID numbers are called “Mapper IDs”. A mapper can be thought of as the hardware in a cartridge circuit board that connects the interface pins to the rom chips.
NROM is assigned mapper number 0.
The INES format supports a lot of variation, and we don’t necessarily need all of it now. One trick that worked well for me is to add failing assertions whenever unsupported code is reached. It ensures I only add what is currently needed. It also avoids implementing code for which no test is available. This ensures code is only added just before testing, so it is fresh in mind and more easily debugged.
Now lets peek at the header of nestest.nes. If you would like to follow along with the ines specification, you can find it on the nesdev wiki here.
This hexdump is produced with xxd. Addresses along the left represent the offset of the first byte in that line. The middle section is the value of each of the bytes, in hex. On the far right, is a text representation. Non-text bytes show up as a dot.
$ xxd testdata/nestest.nes | head
00000000: 4e45 531a 0101 0000 0000 0000 0000 0000 NES.............
00000010: 4cf5 c560 78d8 a2ff 9aad 0220 10fb ad02 L..`x...... ....
00000020: 2010 fba9 008d 0020 8d01 208d 0520 8d05 ...... .. .. ..
00000030: 20ad 0220 a220 8e06 20a2 008e 0620 a200 .. . .. .... ..
00000040: a00f a900 8d07 20ca d0fa 88d0 f7a9 3f8d ...... .......?.
00000050: 0620 a900 8d06 20a2 00bd 78ff 8d07 20e8 . .... ...x... .
00000060: e020 d0f5 a9c0 8d17 40a9 008d 1540 a978 . ......@[email protected]
00000070: 85d0 a9fb 85d1 a97f 85d3 a000 8c06 208c .............. .
00000080: 0620 a900 85d7 a907 85d0 a9c3 85d1 20a7 . ............ .
00000090: c220 8dc2 a212 2061 c2a5 d54a 4a4a b01c . .... a...JJJ..
The ines file starts with a header, which describes sizes and key information needed to understand the rest of the file. The first 4 bytes are “NES” + 0x1a. Anything else suggests it is not an ines file. These first few bytes are sometimes called a “magic number” or “signature”. Many file formats use something like this to help identify filetypes.
One quick note: when discussing sizes, these are often in terms of power-of-two sizes. So 8K usually means the power of 2 closest to 8000, which is 8192 bytes. 16K is 16384 bytes. I’ll write out full numbers where its relevant to avoid ambiguity.
The next byte after the signature tells us the size of the PRG ROM. The number here is the number of 16KB chunks. We see 0x01, so PRG ROM is 1 * 16384 = 16384 bytes long.
Next up is CHR ROM. This is in 8K chunks. Here again we see 0x01, so CHR ROM is 1 * 8192 bytes long.
The next byte in the file tells us part of the mapper number, as well as how chr rom is connected. In our file, we have 0 for the byte, which means mapper 0 and CHR mirroring mode 0. We’ll come back to this when setting up graphic data.
The full header is 16 bytes long. While you are welcome to read through the full spec, we’ve actually already covered the key details needed to load nestest.nes
After the header, comes the PRG ROM data. And after that, is the CHR rom data. If you’re writing a loader, you’ll want to save the PRG and CHR rom bytes so they can be easily read by other code.
And in the previous article, we already saw how NROM maps PRG data. Once you’ve found the PRG Data, your emulator can copy it into its emulated memory and start executing. There’s just one last detail, which is the reset vector.
How does a real 6502 know where to start running code? We know it probably isnt in the first 2 pages(0x200 bytes) of memory, since this would overlap zero page or the stack. Does it just start afterwards?
6502 systems are expected to put a few special values at the end of memory. These values are sometimes called “vectors”, and are addresses of code to be used in certain situations.
The 6502 has 3 reset vectors, and they start at address 0xFFFA.
0xFFFA, 0xFFFB : NMI (2 bytes)
0xFFFC, 0xFFFD : RESET (2 bytes)
0xFFFE, 0xFFFF : IRQ & BRK (2 bytes)
When reset, the 6502 reads 2 bytes starting at 0xFFFC. This is interpretted as a 16 bit address. The 6502 sets PC to that address, and will begin running from there.
Now you can see why even small (16K) PRG roms need to fill 32K. Without this, they wouldn’t be able to put values in the vector locations, and the 6502 would be able to find its starting location.
Each developer is free to set these vectors however they like, but it is on them to ensure that they have placed their code appropriately within the rom.
Getting your first rom to load can be a little finicky. One subtle bug can result in misinterpretting the bytes in the file, and failing to run the code successfully.
To aid in this first-time emulator setup, I’ve created a very simple rom that just provides the reset vector, and then does an infinite loop.
You might find this useful, since it is even simpler than the nestest rom. I’ve provided source and a packaged .nes file for ease of testing.
You can find it on my NES index page, called run.6502 The reset vector points to 0x8100, and should thus start running there if your loading code is correct.
And with our emulator able to load basic data, we’re ready to start looking at graphics! We’ll peek inside that CHR data and find the tile data used to represent all forms of images on the system.
⭅ Previous (Cartridge Basics) | Next (CHR Graphics) ⭆ |