In this post, I'll attempt to explain how emulators fundamentally work.
In general, writing an emulator is considered legal, as long as it's is all your work and all the documentation has been gathered legally, but spreading copyrighted work such as ROMs is highly illegal and I will not provide download links, so please, don't ask.
Different methods of emulating a system
The CPU of any system is pretty much the most important part, it is essentially the brain of the system, assuming the system doesn't have two or more brains (co processors), so your emulator will be based around the CPU core you write. To write a CPU emulator, you need to first understand how a CPU works. A CPU works on the basis of fetch, decode, execute.
- Fetch: Retrieve the instruction from memory.
- Decode: Figure out what the instruction is, this may be simple in an older 8 bit CPU.
- Execute: Execute the instruction and write back any resulting values etc...
An interpreter is the most popular method of emulating chips such as CPU's inside systems. Here is the basic way that an interpreter works.
cyclesThisUpdate = 0;
while (cyclesThisUpdate < cyclesToExecute)
opcode = readMem(PC++); // fetch instruction
executeOpcode(opcode); // decode and execute instruction
// do cyclic tasks
Let me explain, "cyclesThisUpdate" is going to be the amount of cycles that the CPU has executed this update, usually we update the emulator one frame at a time then draw the screen. "cyclesThisUpdate" must be reset at the start of the loop. "cyclesToExecute" is simply the amount of cycles we want to execute for this update, for example, lets say we wanted to update about a frame, we would work out how many CPU cycles a frame takes to emulate. Be aware that "cyclesThisUpdate" will be incremented a certain amount by the instruction, certain instructions will take more cycles than others to execute, not including memory access times. You will need to keep track of these cycles.
As you can see, the first thing we do is fetch the instruction, we then decode and execute the instruction, after this, we do all the cyclic tasks that need to be emulated, such as video, sound and CPU interupts. Fetching the instruction involves memory I/O, which will need to be emulated. For example, your fetch step will probably be some kind of readMemory() function which takes an address as a parameter, and returns the data as that address. This will serve the purpose of figuring out where in memory you are trying to read, and acting appropriately. For example, in the Gameboy Classic, reading addresses 0x4000, to 0x7FFF is reading a memory bank, and you must perform ROM memory banking to return the correct bytes. The "PC" is the program counter, which keeps track of where we are in the program, instructions like branches and jumps will change this register and it is incremented past each instruction everytime one is executed. If this confuses you, don't worry, you will understand eventually.
TL:DR Emulator is updated until cycles per update (usually per frame) is achieved.
Benefits of interpretation
- Easy to debug
- Simple to implement
- Easy to synchronise your system
Drawbacks of interpretation
- Very slow compared to other methods
- Dynamic Recompilation
Recompilation or Dynarec for short, is a complex subject that I'm not really fully qualified to explain. The theory is that instead of executing the instructions, you translate them into mechine code instructions for your system and cache them in code blocks. The idea behind this is for optimization, because most code that is executed might be executed thousands of times, so if it is cached and optimized, it will execute much faster. Here is a great tutorial by one of thr PCSX2 authors, CottonVibes on Dynarec.
Emulators like PCSX2, ePSXe, PJ64, 1964 and Dolphin use Dynamic Recompilation. You can freely download the PCSX2 source and the 1964 source and take a look at there recompilers, 1964 even has a pdf document floating around somewhere with details on there recompiler, ask me if you want it, ill upload it somewhere for you.
Benefits of dynarec
Drawbacks of dynarec
- Not portable
- Difficult to debug
- Hard to impliment
There is another method called static recompilation, but I won't go into that as I feel that the benefits are few and far between.
By the way, i have no idea why this post won't format correctly, sorry. I tried.