There are certainly more scholarly and accurate articles and tutorials about programming a 6502 emulator (which are cited at the end of this one) If you are a really good programmer or want to get your information from a competent source, you should look there first. However, if you're one of the average joe programmers, like me, you might find this article useful.
Some Thoughts on Programming a 6502 Emulator
First, you might want to consider why on Earth you could possibly want to learn to write 6502 emulator. Go ahead and ask yourself that question now. Honestly, there's only three possible reasons I can see for taking on this task :
- To create a much needed emulator for education.
- To learn 6502 assembly yourself.
- To prove you can do it.
- (If you can think of any more, please let me know on the forum)
It was with these goals I began my beloved project Mad 6502. It is planned to be a generic 6502 emulator with useful capabilities for learning and teaching 6502 assembly code so that the learner could easily move onto better and more popular 6502 hardware. As I write this article, Mad 6502 has said it's name and nothing more — but it used 6502 machine language to do it.
It was slow, only implemented a few instructions, and frankly, if things like this didn't make me wet my pants it would have been downright boring. There's nothing like being able to say "It's Freekin' Aliiiive!!" like a mad scientist.
So, let's get right down to it, shall we. Mad 6502 is only the bare minimum of an emulator. One that can easily be expanded on to make a full 6502 emulator — if I had the time. With my programming time being consumed by GDN, I gladly pass on my limited experience to you.
First, you need to know a bit about the 6502 microprocessor itself. In comparison with modern-day CPUs, it is lovingly simple. Let's start with the RAM.
The 6502 has access to #FFFF memory locations, that is memory locations numbered from 0 to 64,000 + or so. You can simulate the ram with an array. This is slow, at least in FreeBASIC, and there has got to be a better way. That will be up to you to figure out, but in the meantime, you can declare your ram array like this :
Notice I used the hex number for the upper bound of the array. You don't need to know what this number is, only that it is the hex number #FFFF. You will be thinking mostly in hex (and binary) for the entire time you are writing your emulator.
Each memory location of the 6502 can hold one byte, which amounts to a number between 0 and 255. In binary, it amounts to eight bits 11111111. Therefore, each element in your array is a memory address of the 6502. If you want to put something into a "memory location" you simply use the hex address with the array like so :
ram(&h1024) = &hFF
This will put the number 255 or #FF in hex in that memory location. Reading the memory location is equally as simple :
someValue = ram(&h1024)
Don't get scared at this point. I can't translate between hex, binary and decimal to save my life, it's sufficient most of the time to know that #FF is a bigger number than #FA. When I did have to translate between number bases, I used a program to do it or simply printed out the value using print :
' freebasic, please tell me what the number #FA is in decimal print &hFA
OpCodes are the machine language instructions that the 6502 obeys like a loyal dog. Opcodes are just numbers stored in RAM along with numbers and letters in a big mash. It looks a bit like this :
|Memory Location||#800||#801||#802||#803||#804||#805||#806||etc …|
|Thing Stored||opcode||number||opcode||number||number||opcode||letter||etc …|
The 6502 can tell the difference between OpCodes and these numbers (but not letters, they are just numbers) by their order. Did you notice that there is a pattern in the above line? The 6502 knows the difference between a number and an opcode because it knows that the numbers after an opcode are the parameters for that opcode.
To keep this simple, lets say opcode A requires two numbers. The 6502 starts at opcode A, gets the two numbers, does whatever the opcode says to do, and then jumps forward to opcode B.
|Thing Stored||opcode A||number||number||opcode B||number||etc..|
However, if you screw things up and the 6502 lands on a number rather than an opcode, it will take that number to be an opcode. Then the crap really hits the fan. The 6502 just goes berserk following commands you didn't write (it's following the numbers). You should always keep this in mind when you are writing your machine language :
- The 6502 'skates' on the opcodes.
To help the 6502 know which opcode it needs to execute, it has an internal register called the program counter. The program counter is actually what does the skating. You can simulate the program counter by a simple variable :
dim program_counter as integer
For example, in the example table above, the 6502 knows that it is to execute opcode A by the fact that the program counter tells it that is the next opcode to execute. After it has executed opcode A, the program counter is incremented by 3, telling the 6502 where the next opcode is.
Executing opcodes is easy. You simply take the opcode from the current position of the program counter and then run it through a select case to determine what action should be taken.
opcode = ram(program_counter) select case opcode case opcodeA ' actual opcode hex number here ' simulated opcode action here end select