It's been close to 25 years since I last worked on writing an emulator. When I undertook that project, it was to prove that a game console (in my case, the Sega Genesis) could be emulated on a PC. I didn't even realize there was an emulation scene and I wasn't the first. I discovered all that after I wrote the emulator. I didn't really even know what I was doing with the emulator, so it wasn't code to be proud of. It did work and I could play a large percentage of Sega Genesis games and even emulated the 32X add-on decently well. Apparently, I was the first to do so. As I tried for more accurate emulation, it was clear my ad hoc experiments weren't the proper method for emulation. I would need to start from scratch.
I do remember briefly starting on a new architecture, but it didn't get far and I got distracted by other things that life throws at you. But, in the intervening 25 years or so, I've never stopped thinking about emulation. I think I've even coded a few in my dreams. It's great therapy when you can't fall asleep.
One week ago, on March 5, 2021, I loaded up Visual Studio and started coding. It's now been seven days and I have a running NMOS 6502 core running at a cycle level and it passed a functional test I found on GitHub.
So what are my goals with this thing? Let's start with a quick list:
- Cycle accuracy. I don't mean I run an instruction and it updates the clock by 3 because it's a 3 cycle instruction. No, I mean I want to emulate what happens during each cycle of an instruction. Typically this means slow emulation, but I have some ideas to make this quick, and let's admit it, today's processors are wicked fast and we have a lot of cores to play with.
- Each device (CPU, I/O, Video Chips, etc) will be self-contained. They will not know anything outside of what the chip can do.
- A bus architecture to connect the devices (chips.)
- An architecture plus a scripting language that will allow the user to quickly connect various devices (chips) together to create a wide variety of different computers and game consoles.
There! That shouldn't be too hard, right?
By my second day of coding, I had a base device class, a bus class, memory devices, RAM, and ROM. I also came up with the idea of a pin class. A device could have any number of pins, such as RES (for reset), IRQ (for interrupt), etc. and the pins could be connected between devices. A DMA chip could lower the RDY pin on the CPU so the processor would stop processing while the DMA happened. These are the sorts of details needed for highly accurate emulation. Along with the Pin class, I added some basic gate classes, such as AND and OR for connecting them like a real digital circuit.
I spent 3 more days and coded up a NMOS 6502 emulator. After bouncing around ideas for months, I ended up with a large table that was 7 TStates wide by 256 opcodes tall. Each entry would be a 'task' that would be completed by that opcode during a specific TState (cycle.) I started coding these up, and I was going to need too many unique tasks that were really similar. Then I realized I could encode the command in the higher bit ranges and the lower bits could encode registers, etc. I had basically just re-invented micro-coding, which is how most CPUs after the 6502 were implemented in silicon. I ended up with 33 microcode ops, but if I was a bit more clever I could write fewer. I haven't yet implemented the illegal 6502 instructions, but due to the nature of how they work, they are basically combinations of existing instructions so they shouldn't need any new micro-ops. I will simply need to update the table.
I do plan on emulating every variant of the 6502 ever made. And since I have this table of microcode, I won't need to hack my actual C++ code, but instead, just have an alternate microcode table for processor variants.
Tonight I finished testing with Klaus Dormann's 6502 Functional Test. I added some timing info and the test is running at the equivalent of a 109 MHz 6502! Just a tad faster than the Apple ][+ I grew up with!
I'll finish up this first (of many, I hope!) development blogs with a small bit of code. This function creates a computer using a 6502 and 64K of RAM then starts running the functional test.
static void DormannFunctionalTest( void ) {
// create a test 6502 computer
RAM ram( 0x10000 );
Bus bus;
MCS650X cpu( MCS650X::MOS6502 );
bus.AddMemory( &ram, 0x0000 );
cpu.SetBus( &bus );
cpu.AttachRDY( &VCC );
bus.LoadBinary( "6502_functional_test.bin", 0x0000 );
bus.Write16( 0xfffc, 0x0400 ); // start vector
// Run for awhile
for (;;) {
cpu.Update();
}
}
For now, I directly called the CPU's Update function. Later, I'll add a Machine class that will have a clock and will automatically call the Update functions for all the devices in the machine.
If anyone knows a nice way to imbed code in blogger.com, let me know!