Crash course on emulating the MOS 6510 CPU
By David Pipkin
Lead Software Developer, code whisperer
Creating an emulator is a very powerful experience. Not only do you become intimately familiar with the target hardware, but you virtually recreate it with simple keystrokes. Emulators aim to recreate every single component of a piece of hardware down to the electrical timing of a single CPU cycle, not to be confused with simulators, which simply mimic a platform. It’s not always a simple task, but it’s very rewarding when you power it up and are presented with a piece of childhood memory. Sure, there are plenty of emulators out there, but creating your own makes using it and tweaking it so much more fun.
The MOS 6510 is a modified version of the popular MOS 6502. The 6502 was used in systems like the Apple IIe, Atari 800 and 2600, Commodore VIC-20, Nintendo Famicom, and others. The biggest difference with the MOS 6510 is the addition of a general purpose 8-bit I/O port.
Why the MOS 6510?
I wanted to emulate a Commodore 64... Why? The Commodore 64 was a staple of my childhood. Before graphical OS’es and the internet, there was just imagination and a command prompt. Pair that with some BASIC programming books that my dad left lying around and I felt like I had the world at my fingertips. I wanted to become more familiar with the first computer I ever used. The C64 is simple and complex at the same time, and its internal workings intrigued me.
The MOS 6510 was the CPU that the C64 used. To emulate a full C64 machine, you would also need to emulate the MOS 6581 SID (sound), MOS VIC-II (display), MOS 6526 CIA (interface adapters), I/O and more, but this article focuses on the heart of it all – the CPU. The memory in a C64 is also outlined, because without memory, the CPU can’t do very much.
Let’s Get Started
First off, this article is, as mentioned in the title, a crash course. So, I won’t be going into a lot of detail. It’s more of a primer for those of you who are interested in MOS 6510 emulation, and something to send you off in the right direction.
The basic cycle your emulator will perform will be the following:
- Read next instruction from memory at the PC (program counter)
- Process instruction
- Process Timers on CIA1 and CIA2 (not covered in this article)
- Update screen via VIC-II (not covered in this article)
- Calculate cycles (for emulators that aren’t cycle-exact)
The last point only applies if you are making an instruction-exact emulator vs. a cycle-exact emulator. Instruction-exact emulation is easier because you simply process an instruction and increment by the number of cycles that instruction is supposed to take, but it is less accurate and may result in some features of the system not working exactly right. Cycle-exact emulation only processes one CPU cycle per loop in your emulator, so one instruction could be performed over multiple loops. That method is very accurate but is more complex to implement as you will need to be more granular in how you process instructions.
MOS 6510 CPU
The CPU is responsible for processing instructions from memory. It’s an 8-bit processor, which means the registers will store 8 bits each, other than the PC (program counter), which is 16 bits (high and low bytes) so that it can store a memory location.
To emulate the processor, you will need to implement the following components...
Registers are small areas of memory located directly in the processor that have extremely fast access. Each register has a purpose and can be used in various ways depending on the context of an instruction.
- PC (program counter)
Stores the active address in memory.
- S (stack pointer)
Pointer to current location in stack, which starts at 0x01FF in memory and grows downward to 0x0100
- P (processor status)
See status flags below
- A (accumulator)
Stores arithmetic and logic results
- X (index register)
Used for modifying effective addresses
- Y (index register)
Used for modifying effective addresses
Status Flags for P Register
The status flags are used in the byte that makes up the P register. They can alter the way certain things behave when set or unset and provide status outcomes for operations.
- N (1 – negative flag)
- V (2 – overflow flag)
- X (4 – unused flag)
- B (8 – break flag)
- D (16 – decimal mode flag)
- I (32 – interrupt disable flag)
- Z (64 – zero flag)
- C (128 – carry flag)
Addressing modes determine where an instruction finds a value to work with. One instruction can have many variations that use different addressing modes, these are called opcodes.
Operand is in accumulator, no addressing needed. This is for one byte instructions that operate on the accumulator
Operand is at byte after instruction, no addressing needed
Address at PC +/- value of byte after instruction (interpreted as signed byte). This is used for branching. It basically allows the PC to branch from -128 to 127 bytes from its current position
- Zero Page
Address at byte after instruction
- Zero Page X
Address at byte after instruction + X register
- Zero Page Y
Address at byte after instruction + Y register
Address at word after instruction
- Absolute X
Address at word after instruction + X register
- Absolute Y
Address at word after instruction + Y register
Address at memory which is pointed to by word after instruction
- Indirect X
Address at memory which is pointed to by word after instruction + X register
- Indirect Y
Address at memory which is pointed to by word after instruction + Y register
There are too many instructions to list in this crash course, but here is a link to an opcode matrix. It shows the value of each opcode, the associated instruction and addressing mode, as well as the logical function of each.
One of the most important aspects of emulating the CPU is the timing. For my C64 emulator, I used the PAL specification of 0.985 MHz, or 985,000 cycles/second. If you are implementing the NTSC specification, then you would use 1.023 MHz. As I said before, if not implementing cycle-exact emulation, you need to determine how many cycles each instruction takes and increment the cycles that have passed. This is important for determining when certain IRQ’s should be fired as well as the progress of the raster line when implementing the VIC-II. The raster line position will have to match the CPU cycles (screen refresh is 50 Hz on PAL, 60 Hz on NTSC) so that programs which rely on raster line position to create certain graphical effects will work.
Also, keep in mind that certain things take extra cycles. For instance, if an instruction uses Absolute Y addressing and crosses the page boundary in memory, that takes an extra CPU cycle.
The MOS 6510 is a little endian chip. This means that when you are reading a word from memory (16 bits on the MOS 6510), you will need to read in the second address position first, followed by the first address position. You can then use the result to create a 16 bit variable in your programming language of choice. A simple example of this is as follows:
(peek(address + 1) << 8) | peek(address)
Where peek() grabs a byte from a memory location. The byte from the second address location is bit shifted 8 positions left and is then bitwise OR’ed with the byte from the first address location.
The C64 has 65,535 bytes of memory, or 64 KB. Bank switching is used to switch the ROM and I/O in and out by changing the latch bits in the first two bytes of memory. A page of memory on the C64 is 256 bytes. The first page is called the Zeropage and is easily addressable with zeropage addressing, which is fast because the C64 is an 8-bit machine.
Here is a basic mapping of the C64’s memory:
- 0x0000-0x00FF: Zeropage – first two bytes contain directional and latch bits that can be set to swap ROM’s and I/O in and out of place.
- 0x0100-0x01FF: Stack
- 0x0200-0x03FF: OS
- 0x0400-0x07FF: Screen
- 0x0800-0x09FF: Free RAM for BASIC programs
- 0xA000-0xBFFF: BASIC ROM or free RAM for machine language programs when ROM switched out
- 0xC000-0xCFFF: Free RAM for machine language programs
- 0xD000-0xDFFF: CHAR ROM or I/O or Sprite data, interrupt register, etc. when CHAR ROM and I/O switched out.
- 0xE000-0xFFFF: KERNAL ROM or free RAM for machine language programs when ROM switched out
When I/O is switched on, the region 0xD000-0xDFFF maps to the following:
- 0xD000-0xD3FF: VIC-II registers
- 0xD400-0xD7FF: SID registers
- 0xD800-0xDBFF: Color memory
- 0xDC00-0xDCFF: CIA1
- 0xDD00-0xDDFF: CIA2
- 0xDE00-0xDEFF: I/O 1
- 0xDF00-0xDFFF: I/O 2
There are two important factors when initializing your emulator – the PC and directional/latch bits in the first two bytes of memory.
The first byte of memory, which contains directional bits, should be initialized to 0xFF. The second byte, which contains the latch bits, should be initialized to 0x07 (00000111). This will enable the KERNAL ROM, BASIC ROM, and I/O. The CHAR ROM and underlying memory of these locations will not be accessible unless the banks are switched.
The PC should be initialized to the word read from memory location 0xFFFC. This will read from the KERNAL ROM due to the latch bits initialization.
That concludes the crash course. Hopefully you’re at least a little more informed about the MOS 6510 than before. The only external pieces you will need to obtain in order to create your emulator are the three ROM’s as mentioned above – BASIC, CHAR and KERNAL. These can usually be obtained somewhere online or from another emulator. It’s a lot of work to emulate anything, but it’s a fun project and worth it in the end.
Article last updated on 4/21/2018