⭅ Previous (Assembly Language) Next (6502 Extras) ⭆

6502 Emulator Design

Also available on Youtube.

Now that we’ve seen the registers used in the 6502 CPU of the NES, we’re ready to start building the CPU emulator. The CPU essentially does the following for each step:

  1. Determine what instruction it is going to run
  2. Read that instruction
  3. Decode it so it knows what to do
  4. Do the requested action
  5. Repeat

Lets talk about each of these steps in more detail. Last time we mentioned that the 6502 has a pc register. This is the “program counter”, and holds the address of the next instruction to run. It needs to be updated when executing an instruction, but otherwise the CPU assumes that it points to the right spot in memory. So step 1 is taken care of.

For step 2, the cpu just needs to read one byte at that address. As we saw earlier, instructions are represented by an opcode followed by any inputs needed by that opcode. Fortunately, the 6502 only uses 1 byte opcodes. This means reading an instruction is simple, as we know to always just read one byte.

Step 3 is where things become a bit more involved. We saw in our first article on number representation that we can hold 256 different values in a byte. Does that mean the CPU needs to handle 256 different instructions?

Yes and no. Though programs can technically use all of these values when representing instructions, in reality only some of them are documented as “valid” instructions in programming manuals for the chip. Some instructions outside this valid list perform useful tasks, and others lock the system. We will start by focusing only on the “valid” instructions that are documented in 6502 manuals. We may revisit this approach in the future.

You can find a table of 6502 instructions at the masswerk 6502 site. In the table, you’ll see that only about half of these 256 opcodes are valid. And of these, you’ll see that a lot of them actually use the same assembly language name for the instruction. Which leads us to…

6502 Addressing Modes

The 6502 instruction set realized that the same sort of operation might want to be applied to different sorts of inputs. Sometimes the value you want to add comes from a register, and sometimes it comes from memory. Rather than having two completely different instructions, it is represented as something like (add, memory) or (add, register).

These are called addressing modes, and there are quite a few. Rather than discuss them all here, we’ll just cover a few. More details can be found at the masswerk page linked above.

Immediate: The input is encoded directly after the opcode, as is interpreted as a value.

We saw this in our earlier discussion of basic instructions:

lda #$20
; assembles the machine code: a9 20
; puts the value 0x20 into the a register.

The pound sign (#) is used to inform the assembler that we want to use the immediate addressing mode for this instruction. What if we omitted the pound sign?

lda $20
; assembles to machine code a5 20
; 0x20 is a zero page address, load the value from memory

Since the 6502 doesn’t have many built in registers, it might otherwise be clunky to constantly swap values from memory to registers in order to work with them. The 6502 makes it efficient to use memory from the so called “zero page”. The instruction takes the same number of bytes to represent, and only runs a little more slowly.

The zero page is what we call the first 256 bytes of memory. These are locations where the left most part of the address is zero, ex:

0x0000
0x00DA
0x00FF and so forth

Only one byte is needed to represent these, since the other byte of the address will be zero. Most instructions support some form of zero page addressing, which allows the cpu to use the first 256 bytes of memory as easily and almost as quickly as if they were built in registers.

You may have noticed that the opcode went from a9 to a5 when changing addressing modes. You may be wondering if this is a coincidence or some pattern responsible for this.

Fortunately, most opcodes are encoded so that the operation part is separate from the addressing mode. This greatly simplifies the task of decoding. Rather than implement each of the ~128 opcodes, we can implement roughly 56 operation types, plus 13 addressing modes.

6502 Instruction Encoding

Full details of instruction decoding are out of scope, but here’s an example to see how we’re able to reduce the amount of code required to implement the cpu.

Take the 8 bits of the instruction encoding, and refer to the bits in chunks:

AAA BBB CC

The right most two bits, cc, tell us which “instruction group” these belong to. Within a group of instructions, all can use the same addressing modes. Group one is the most flexible, and covers the most common instructions. Within instructions with 01 for these rightmost bits, the AAA bits tell us which instruction it is. For the examples above, lda is represented by 5 or 0b101.

And that leaves BBB for the addressing mode. 2 or 0b010 is immediate, and 0b001 is zero page. Putting those together:

; immediate mode:
; op  mode grp
; AAA  BBB  CC
; 101  010  01
; grouped for hex:
; 0b1010 0b1001
; 10     9
; 0xa9
lda #$20

; Zero page
; op  mode grp
; AAA  BBB  CC
; 101  001  01
; grouped for hex:
; 0b1010 0101
; 10 5
; 0xa5
lda $20

Which gives us exactly the opcodes we saw earlier, and matches the opcode tables found in the reference.

Updating PC

That explains decoding. Once we know what instruction it is, the CPU emulator needs to perform the work, and update the program counter.

Updating PC follows one of two rules. Normal instructions allow the CPU to continue executing the next instruction. In this case, we need to know how many bytes were used in the instruction we just executed. Since opcodes are always one byte, length comes down to addressing mode. So to update PC, we just need to do something like:

PC = PC + instruction_length.

Some instructions modify the program counter directly. These are branches, which are used to implement decision making aka conditionals. For the purposes of updating PC, if the instruction set PC directly, then the CPU does not also step forward based on instruction_length.

Wrapping up

Now we have a blueprint for our 6502 CPU emulator, learned about how a CPU reads instruction opcodes, and learned a bit about addressing modes for the 6502.

Next up we’ll see see a few more 6502 features, including the status register and interrupts.

⭅ Previous (Assembly Language) Next (6502 Extras) ⭆

We publish about 1 post a week discussing emulation and retro systems. Join our email list to get notified when a new post is available. You can unsubscribe at any time.