Thursday, 11 September 2008

And So It Begins

Where do you start when writing an emulator for a CPU? I guess the obvious place is 'The School of Hard Knocks', because in order to emulate the thing, you've got to have used it in a real practical way for long enough that you know it pretty much inside-out, including all its foibles and quirks. I qualify for that in spades with the 6502, as it's a chip I've been intimately familiar with since about 1981, and I've also read a whole lot of stuff about the inner workings of the silicon as well.

But on a practical level, it makes sense to start with the elementary stuff, and it seemed to me that the registers of the chip would make a good place to begin. There are five 8-bit registers (the Accumulator, X-Register, Y-Register, Status Register and Stack Pointer) and one 16-bit register (the Program Counter - really just two 8-bit registers jammed together). The Accumulator (.A) is a general-purpose register that also sees a lot of action as a component in numerical operations; .X and .Y are also general-purpose, but have special roles to play in a number of the addressing modes the chip supports for indexing; .SR is a special register that holds the PSW (Program Status Word, sometimes also known as the PSR or Program Status Register) where various processor-state bits live; and .SP is another special register that points to the top of the Processor Stack. The Program Counter (.PC) is the 16-bit address of the next memory location that the CPU will read or write to - and being comprised of two 8-bit units (the PC Lo-byte and PC Hi-byte) we can see that the 6502 can address 256*256 bytes of memory, or 65536 bytes, or 64K.

So we need a few classes to represent these objects: a base class of 8-bit registers for .A, .X, .Y and .SP; a derived class from that with additional functionality to handle Processor State bits individually for .SR; and another base class incorporating two 8-bit register objects as a 16-bit pair for .PC. Now, my original implementation of these objects exposed their contents as Byte values, because after all a byte is 8 bits, and thus neatly replicates the hardware. For .PC, I used a Ushort type, which is a 16-bit entity and again handily represents the actual hardware. But as the emulator progressed to the point where it was actually starting to process instructions, I found I was utilising quite a lot of explicit casts (and a fair number of implicit casts too) to go from Int to Byte or Ushort (an explicit cast is where the programmer needs to help the compiler to accurately convert one type to another) and this slows things down. More on this later, but for now I'll just say that after a half-day spent testing various performance scenarios, I've decided to make all the registers Int and do the 'capping' manually (i.e. instead of letting the Byte and Ushort types automatically handle rollover from FF to 00 - or FFFF to 0000 - I'm doing it in code).

We join the project at a critical juncture - the basic CPU framework is complete, and undergoing a little rework to convert those registers to Int. I have a memory emulation framework in place and working as well, and this lets me hook the CPU to a facsimile of a real machine in which the processor would really exist - I've decided to use the Commodore VIC-20 as the testbed, so the memory configuration is mapped the way it was in that microcomputer, and the appropriate ROMs are loaded into the right regions. More on this later - but don't let the choice of computer worry you as I'm only using the VIC-20 because of my familiarity with it, so I can see and prove that the 6502 emulation is working correctly - when we're done, that CPU code will be just as much at home in any other simulation.

Here are a couple of screenshots from the console output of the emulator as it stands today - click them to see larger versions. In the first, we're seeing the initialisation stage of the test harness and the CPU itself - the memory emulation has been populated to simulate an unexpanded VIC-20 (shown in the Memory Block Allocation section) and we have a little 'dump' of raw memory from the start of the Kernal followed by a simple disassembly of the first few commands of the operating system ROM. I use the term 'Simple Disassembly' on purpose, because this is really just a decoded memory dump - there's no labelling, no automatic matching to memory-map descriptor tables, no branch address calculation... all that will come later.



In the second, we see a relatively uninteresting stream of executed instructions. The CPU is reading and decoding the Kernal, and then executing the instruction just as the real hardware chip would do. This is for debugging purposes - you wouldn't normally see this; it shows me the current value of .PC and the instruction (and addressing mode) being executed, alongside which we see the rest of the register values both before and after the instruction was performed.



As can be readily observed, I'm in the middle of working on the ADC instruction, as the emulator is telling me it has been asked to execute an ADC but there's no implementation for it yet! But actually, I've been distracted by a knotty little problem to do with my test harness - ironically, the CPU emulation is working fine so far (even though incomplete) but I appear to have run into a peculiarity of the VIC-20 ROM initialisation code. More to follow.

0 comments: