How Emulators Work, How Cpus Work - Dig It :)


skeezix

Internal Development
Joined
Mar 11, 2003
Messages
8,063
Website
www.codejedi.com
This comes up once in awhile, so I wrote up this monster blog entry about how emulators work, how to write a CPU emulator, etc.

Hope you enjoy it!

http://www.codejedi.com/cgi-bin/blog.cgi/t....blog?seemore=y

Pasted here:

Coding: How to write a CPU emulator
Tue, 18 Oct 2005

Fairly regularly folks will ask me how a CPU emulator works, or how to write one.. or even the whole shbang - how an emulator (for a game console or arcade machine, say) is written. I'm sorry to report its not really all that hard or mysterious (just tedious and painstaking to do from scratch!) as in this day and age the whole virtual machine (VM) concept, and emulation in general, has gotten pretty widespread and well understood. Its like anything else - a car is pretty magical until you look under the hood and start taking pieces off, and writing an emulator is no different. Let me know if you'd like me to post another article about porting or writing a whole emulator as its too large a topic for just one post.. but here I'll give you the high-level overview of CPU emulation.. the heart/engine of any emulation project.

What is a CPU emulator?

An arcade game, a console game machine, a desktop or mobile computer.. all have a CPU at their core - the brain of the operations. For instance, your desktop PC might have a Intel Pentium III in it, while your good old Commodore Vic-20 would have a variant of the 6502 in it. Each CPU processor has a specific set of specifications it fulfills - it understands certain command sets and executes each command in a very carefully defined way in a specific period of time. For instance, a chip might read memory and see a "ADD 3 + 3" operation there, and perform it in some specific number of "cycles" (time), resulting in some specific set of changes (the result written to memory, or registers in the chip.) A CPU is like a magic 'black box' that performs all the calculations and work in a machine, but without doing all the leg-work.

A CPU emulator or CPU core emulates this functionality. Its a software implementation (some source code) that will ask some other code to get data from emulated memory, and process it, and return the modifications to emulated memory or emulated registers. So while a real 6502 chip fetches data across wires in hardware, an emulated 6502 merely asks a function to 'get me the piece of data that would be across the wire at this address'.

A mailman goes to an address '50 Bloor St' to find 'Mr Smith' there; an emulated mailman might just say 'if I was to go to 50 Bloor, who would I see there?'.

What a CPU emulator is not.

A CPU emulator is a very specific component, that although being very important to a whole emulation system, isn't really all that useful on its own. If you pry open our mythical Vic-20 again, you'll find a whole pile of chips on the motherboard, only one of which is the 6502 CPU. Some of the chips know how to turn digital instructions into sound, or process digital instructions and operate a floppy disk or tape drive, or fetch or put into memory. Theres a lot going on, and en emulator must emulate each component. The CPU emulation is the heart of the machine, but is otherwise just one small piece of the pie.

Component programming

It is common for a CPU core to be written as one distinct unit, just like its real world chip package would be. This is a common programmer trick - to build a software component with as few ties to the outside world as possible. It lends itself to reusable code, saving both time and money down the road as others re-use the component. So the developer establishes a contract from the component to the outside world -- "hey you, world, all you need to use me is some functions to get and put to emulated memory, and handle interrupts for me, and we're good.. okay?" You don't need to care so much, but I mention it because you'll often here some developer say "hey, you using the Musashi core, or the UAE core, or the CaSTaway 68000 CPU?" when talking about making or porting a Sega Genesis (Megadrive) emualator. Why? Because some people like to write the emu from scratch (like a fine pasta sauce), including the CPU emulation (arguably one of the harder or at least more tedious components, especially once you pass the 8-bit era of CPUs), while others want to write the machine emulation without worrying about the CPU specifics, while still others are just porting an existing emulator. These latter two cases may want easier to use CPU emulations, easier to debug ones, or faster ones.. or perhaps dynarec instead of VM implementations. This is why its important to use component coding styles -- so you can be halfway through developing your own 6502 core, and then swap in someone elses and compare notes to see where a parallel run diverges. Find the bugs in both :)

What kinds are there?

There are numerous 'flavours' of CPU core, and many variations, but to me it really boils down to these three main approaches:

* Virtual machine
* Dynamic recompilation ("dynarec")
* Static recompilation ("statrec")

Most common are the plain old VM CPU cores -- just a software component that emulates the function a CPU would take in a PCB (printed circuit board.) A VM implementation will fetch data from emulated memory, transform it, and write back results into the emulated memory and emulated CPU registers. Less common (and more complex to implement) are both dynarec and statrec emulators - these bad-boys are akin to "JIT" (just in time) Java Virtual Machines - in which the code being run through the emulator is translated into the instructions the local ("real") architecture needs. By example, if you're running a dynarec Super Nintendo emulator on a typical Windows PC, the emulator would sneakily translate the SNES code into Intel x86 code. This is complex, but obviously during the runtime theres no real time emulation going on, so it can be very very fast. Dynamic recompilation (performing this magic translation from emulated CPU to local CPU during runtime) is getting more and more common (especially with the Playstation 1 emulators I'd say), though you do see static recompilators around on occasion. Lastly, a static recompilator performs its translations during off-time (not live during runtime) -- for instance, a developer might write a translator and run it once to convert a specific PSX game to C code for compiling on any platform, or perhaps the statrec translator runs when you select a game and not while the game runs. A dynarec will translate bits and pieces of the code "on demand", while a VM emulator just emulates real time all the time.

Alright, lets get to it -- how do you write a CPU core? How do they work? Whats going on in there?

Theres a lot going on in a CPU emulator, and I'm not going to cover it all here (already a very long posting, no?), but heres the basic cycle. The CPU (emulated or real) will loop through this sequence over and over -- modern, old, they all do it.

* Fetch (get some data from memory)
* Decode (analyze, decompose; figure out what the data means.)
* Process (do something)
* Put (store; put output into memory, registers, flags, whatever.)
* (And other stuff; deal with interupts, serial input, etc.)

In case you're curious, a modern CPU does essentially the same things but with all sorts of cool tricks going on. ie: An old CPU would begin a Fetch after everything else is done and the CPU is back to its 'ready' state, while a modern CPU knows better - rather than have the Fetch circuitry idle while in the Process phase, it fetches something else. So as each phase is passed, a whole new cycle can start.. so you get many more things going on at once. Some modern CPUs will re-order operations as they see them, knowing it can do independant operations faster in some specific order (utilizing more pieces of the CPU at once), or fetching from cache instead of across the wire.. etc etc. Whacky stuff.

Looking a little more closely..

A Fetch is where the CPU figures out what to do next. If its got nothing on the go, it can fetch another operation (at the next 'PC' (program counter) address in memory. For instance, memory might be laid out like this:

ADD into-A from B and C
MOVE A into D

After the ADD is completed, the CPU is idle and so does a Fetch to get the next thing to do.. a MOVE in this case.

A Decode is where the CPU figures out what to do with the just fetched data. For instance, an instruction is not the string "ADD" as it would be wasteful for a computer to figure that out. Instead, while a programmer might write "+", the programmers tools (compiler, assembler, interpreter) might encode it as an "ADD" instruction to the CPU in question. (A 6502 ADD instruction is encoded differently than a Motorola 68000 ADD instruction.) Lastly, when the poor CPU receives a piece of data in binary, it has to figure out whats in it -- it receives a package in the mail, and has to open it up.

For instance, on an 8-bit CPU like the 6502, there are many ADD instructions - one for adding two small memory values together, while another to add a value from memory to seomthing in a specific piece of memory. While to a programmer these might both be "ADD", to the CPU they're very different, since one might require a couple of subsequent Fetches (to get values to add), while the other needs only one (one value from memory, another from a register or specific memory area.) So the CPU receives the ADDxxxx instruction and Decodes it into "this ADD" or "that ADD" so it knows what to do.

Finally, Processing occurs. Now that the CPU knows it has to ADD a couple values, it Fetches the data needed (if needed), and then does the addition. The output from the addition routine in the chips circuitry has a few outputs -- depending on the operation it might stuff the result of the ADD into a register or into some piece of programmer specified memory. Additionally, the CPU will set some 'flags' (like on a mailbox) so you can find out what may have occurred - do we overflow? is the result value now a zero? Just some handy bits the programmer can refer to later (such as if to say, "if we overflowed, jump over to this other code so we can react accordingly rather than just crash the machine.")

Lastly, a Put occurs, to store the values into the various places the instruction suggests (during its decode phase.) This is what gets us the goods. Whew!

And this is done in code, how? Here is how RAM is emulated.

Glad you asked!

First, lets put together a really wimpy emulation of a small bank of RAM. To keep it simple, lets refer to it as an array with some access functions (to keep OOP folks from frothing at the mouth, and because it gives us that well defined contract piece software components I wrote about above.)

// A wimped-out memory bank emulator:
typedef unsigned char UInt8;
typedef unsigned short int UInt16;

UInt8 ram_bank [ 1024 ]; // should memset to all 'zero'

UInt8 fetch_ram ( UInt16 address ) {
if ( address < 1024 ) {
return ( ram_bank [ address ] );
}
// if we get here, an error has occured!
return ( 0 );
}

void store_ram ( UInt16 address, UInt8 value ) {
if ( address < 1024 ) {
ram_bank [ address ] = value;
return;
}
// if we get here, an error has occured!
return;
}


This is pretty short and pretty naive, but something very much like this is in most emulators - this piece of code allows for a simple 1K buffer of memory and protects access to it, letting you read only from within the bank, or write to within the bank. It could be 1K or 1MB.. nomatter. Of course, I'm using UInt16 for the address range so it can only handle 64k at a time, but more modern machines would use UInt32 say.

A more complex emulation would return special values when certain memory addresses are read from (as in the motherboard, certain addresses are wired to return (for example) the pixel colour at a certain location, while other addresses are just pure read-write-RAM. Also, some writes could be captured in order to perform certain things in the emulated hardware, such as display a pixel on the emulated screen somewhere. So you could easily modify the fetch_ram() function thusly:

UInt8 fetch_ram ( UInt16 address ) {
if ( address >= 838 && address <= 848 ) {
return ( audio_system_volume );
} else if ( address < 1024 ) {
return ( ram_bank [ address ] );
}
// if we get here, an error has occured!
return ( 0 );
}

This works fine and is quite common, though a more organized approach has come into vogue (years and years ago, as most of these ideas were worked out by devs in the early mid-1990's.):

typedef struct {
UInt16 begin;
UInt16 end;
FuncPtr handler;
UInt32 mydata;
} handler_t;

handler_t fetch_regions[] = {
{ 838, 848, fetch_audio_system_volume, 1 /* audio chip 1 */ },
{ 0, 1024, fetch_any_ram, 0 /* no special data */ },
{ 0, 0xFFFF, NULL /* error */, 0 /* no special data */}
};

In this setup, an array is used to define the various regions in memory, rather than if-statements. This is easier to manage for the developer, since the CPU core would just use a while() or for() loop over the array and use the first matching "from -> to" handler for the memory region its looking for. Further, if one was nutty, the CPU core emulator could re-sort the array contents to keep most-used regions at the top for faster access etc.. but thats not generally bothered with. Still, an array is easier to read then 10 screenfulls of if-statements!

NOTE: A "store" memory handler is pretty much the same; rather than report on values in a memory region, they store into memory regions and react accordingly.

Still with me? Finally - how a CPU emulator is written.

OKay, I know this has been long and complex, but you're nearly there :) Above I've shown how basic RAM is emulated. Its similar in a Java virtual machine, so this is actually useful to know.. sure, really :)

Remember the basic cycles an old CPU goes through -- Fetch, Decode, Process, Store. They're directly represented in code:

UInt16 PC = 0; // the 'program counter' - the current instruction we look at
UInt16 reg_A, reg_B; // some registers - internal CPU variable holders
UInt8 flag_zero; // some flags - internal CPU flags

typedef enum {
ADD = 1,
SUB = 2,
MUL = 3,
CMP = 4 // compare
} opcode_t;

// pass in the number of cycles to run; you could pass a big number to
// let the CPU emulator run all day, but its usually cool to pass in a
// low number.. so as the number of cycles one 'frame' of the display
// would take, so that when the CPU core returns, you can draw the
// screen. For instance, if a CPU runs at 5MHz (5000000 cycles per second),
// then you might invoke as "CPU_main_loop ( 5000000 / 60 )" to let it
// run for 1/60th of an emulated second.
void CPU_main_loop ( UInt16 cycles_to_run ) {

// run the number of cycles we were told
while ( cycles_to_run ) {

// FETCH some data (opcode + junk) from emulated RAM
UInt8 data = fetch_ram ( PC );
PC++; // move PC to next address, so next fetch gets the
// next instruction or piece of data

// DECODE it - in this fake CPU emulator, I'll assume the
// bottom 5-bits (out of 8-bits in a byte!) are used for the
// opcode (the real instruction), while the other 3 bits are
// some encoded data for that opcode. ("addressing mode" and
// such.)
UInt8 opcode = data & 0x1F;
UInt8 mode = data >> 5; // shift down to knock off the opcode bits

// decrement our cycles-to-run counter
cycles_to_run--;

// PROCESS the opcode; now that we know which opcode it is,
// lets do something with it. STORE the results right in the
// opcode handler.
if ( opcode == ADD ) {

if ( mode == memory ) {
// In this fake ADD mode we're implementing, the opcode wants
// to get the addresses to find the values to add, so we first
// fetch the addresses (which costs a program cycle (PC) to
// do!) and then add the values!
UInt16 address_a = fetch_ram ( PC ++ );
cycles_to_run--;
UInt16 address_b = fetch_ram ( PC ++ );
cycles_to_run--;
UInt16 value_a = fetch_ram ( address_a );
cycles_to_run--;
UInt16 value_b = fetch_ram ( address_b );
cycles_to_run--;

// In this emulated fake instruction we return the result
// in a register. It could do a memory put (with associated
// cost top the cycles_to_run) - you'd have to check the
// CPU specification for the CPU you're emulating.
reg_A = value_a + value_b;

// set any flags we need to set in the emulated CPU; there
// is opcodes to test these flag values and react accordingly!
if ( reg_A == 0 ) {
flag_zero = 1;
} else {
flag_zero = 0;
}

} else if ( mode == registers ) {
// perform an ADD instruction, like above, but working in
// some other method; perhaps add two registers together
// without fetching.. as you can see, this would be faster
// without having to hit the system bus to get addresses or
// values stored there.
// Obviously, each CPU has a different set of opcodes and
// addressing modes and other things encoded in the 'data'
// first fetch above, so when writing a CPU emulator you have
// to be careful to implement the real hardware spec very
// carefully.
// As you can see, theres a lot going on in each 'opcode',
// which is why timing is usually buggered up in a CPU
// emulator for a few versions :)

// ... assorted code
// ... at the end, sotre to ram instead of register for this
// example opcode. reg_B has been filled in "..." above with
// an address to store to, and value has been calculed from
// registers or memory..
store_ram ( reg_B, value );

}

} else if ( opcode == SUB ) {
// implement the subtraction series of opcodes
} else if ( opcode == CMP ) {
// implement the comparison series of opcodes
}

} // while

}


Whew; I tried to comment that so you can follow whats going on without writing up a paragraph per line, so let me know if I need to break it up more.

Of course, there are lots of tricks you can do to make this easier to read -- handle read or write arrays (as discussed above, or leave that in the fetch_mem and put_mem functions.), handle endianness of your emulated CPU to your local CPU, etc etc.

For example, you can see that an 8-bit CPU could well have a full 256 opcodes available to it. Usually some 10 of them or so could be ADD variations, with the 'mode' bits defining which specific ADD they are. So you might have 256 different 'instructions', of which theres really only 20 'opcodes' with a few 'modes' each. If this is true, you might have 20 if-statements, each with an if-statement to handle by mode.. lots of if-statements. And if you recall your coding, each if-statement is evaluated until one matches.. so if all your opcodes happen to be the last of the 20, then your emulation is 20 times slower than it could be.. right off the top, without even getting into the emulation!

This is easily remedied. Some folks make a separate function for each opcode, and then make an array of all 256 possible values, with function pointers to the appropriate opcode handler function. Then each opcode is evaluated with the same 'cost' - look up function in array (fast), and call the function to handle it (relatively fast.) Still, it'd be nice to avoid all that function calling jazz, since when your real CPU sets up a funciton call, and returns from one, theres lots of 'business' going on.

So a common approach is to, instead of setting up a separate function for each opcode, a separate 'goto' or 'case' label, and then do a 'switch' on the opcode or data, and 'goto' or 'case' to the right piece of code. The compiler/assemble can do wonders with this, and you avoid all those if-statements and function-calls.

Back in the day, I implemented one CPU emulator _entirely_ without function calls.. it was all switch-case and if-goto, and it was seriosuly goddamned fast (for a Intel 486 chip :)

Whew.

So there you have it. Probably my longest blog entry yet. Hope you found it entertaining.. comments welcome!

--

jeff
 
Please pin this post, it should be absolutly essential reading for anyone asking for "Emulator-X" so they understand what goes into making these things...
 
Cool. It really can't be stressed enough just how long winded the process of going through the data sheets and implementing every last instruction is. But it's damn satisfying watching things come together as more and more parts start working. :)
 
sixxie - yeah, for sure; I love biulding a CPU emu and watching a program come more and more to life :) Add a couple opcodes, run, it crashes; implement opcode, run, gets a few more opcodes in.. crash. Implement, repeat, watch.. love it :)

jeff
 
Thanks alot for this - very informative. Do you have any suggestions as to something we could use for practice? Like, a well-documented CPU that wouldn't be impossibly hard to emulate for an amatuer?
 
Hmm, rough one; there are various 'fake cpus' out there for academics sake, but I never looked for that sort of thing.

I really should clean up and document my own 6502 emu core and post it perhaps, so you could take a look.

You could nab the MAME code and look at the many cpu cores there, and zero in on a smaller one (again, the 6503 is a good choice.)

To start with emus 'at all', its best to port something simple (like Marat's ColEm, which is a walk in the park; took me one hour to port it to Palm OS :), or write a simple emu; ie: Write a new emu, using someone elses z80 core (such as Marats, as its not the fastest, but its easy to work with) for Space Invaders the arcade game.

Perhaps I should post an article about that.. I made a 'text only' Phoenix arcade emu.. it uses 'curses' (unix) to display.. its just Marat's z80 (this is old stuff, test code) with a flimsy framework (like in this blog entry above) to run.. shows how simple it is, in extreme cases.

There used ot be an Emu-HowTo faq.. I even submitted a few things to it (this is way back, likely in 1997 or 1998), which had some of this in it. Hard to find now I bet :)

jeff
 
Heh, just took a look at Marat's code. It's pretty hard for me to understand. I should probably get more comfortable with C though before going any further. Seems... well, important, to say the least. :rolleyes:
 
skeezix posted on Oct 19 2005 at 05:03 AM said:
Hmm, rough one; there are various 'fake cpus' out there for academics sake, but I never looked for that sort of thing.

I really should clean up and document my own 6502 emu core and post it perhaps, so you could take a look.

You could nab the MAME code and look at the many cpu cores there, and zero in on a smaller one (again, the 6503 is a good choice.)

To start with emus 'at all', its best to port something simple (like Marat's ColEm, which is a walk in the park; took me one hour to port it to Palm OS :), or write a simple emu; ie: Write a new emu, using someone elses z80 core (such as Marats, as its not the fastest, but its easy to work with) for Space Invaders the arcade game.

Perhaps I should post an article about that.. I made a 'text only' Phoenix arcade emu.. it uses 'curses' (unix) to display.. its just Marat's z80 (this is old stuff, test code) with a flimsy framework (like in this blog entry above) to run.. shows how simple it is, in extreme cases.

There used ot be an Emu-HowTo faq.. I even submitted a few things to it (this is way back, likely in 1997 or 1998), which had some of this in it. Hard to find now I bet :)

jeff


Thankyou sooooooo much for this info. You'll prolly remember replying to my topic on: do developers hide their secrets?. I took your advice and tried to port ColEm, but I can't find the source code for win32 anywhere.

I know it would be asking alot, but I think the community including myself, would love to see a tutorial on emulating space invaders. Is it totally out of the question you could do this for us?? It would defo give us a good foundation to build upon.

I suppose in a way, this could be the tutorial that gets alot of people started, as they could see for themselves the process, where to start e.c.t.

I'd even be prepared to make a donation towards your efforts!! ;)

I just want to build my very first emulator, and see it running in all it's glory and I would appreciate all the help you could give us.

Regards
Dave
 
Last edited by a moderator:
The text Phoenix was unix with ncurses based, so should compile pretty easily on any linux or BSD-unix variation (I built it under FreeBSD or NetBSD likely.) It was pretty silly.. showed the score and such at the top, and had 'b' for birds coming down and such :)

This actually made sense, since Phoenix is pure character based.. no sprites per se; ie: Its like a 20x30 character grid or somesuch, so when they draw character 37 (or whatever), it actually maps to ASCII '1' say (for example), so once I figured that out it was a simple trick to map its private 'character set' to ASCII where possible (numbers and letters) and the rest to 'b' for bird, space for backgrounds, etc. Was actually playable :)

The way they did smooth animation was to have many characters, with sprites offset a pixel to the left or right in each.. brutal :p They did have some good vertical scrolling in the hardware though, to have smooth scrolling backgrounds.

So I coded it first for ASCII with curses, then worked out the decoding for the graphics and made the graphical version, which went into XCade (the earlier original XCade).

Its a lot of work to write a tutorial on how to deduce all that, but perhaps sometime I'll have time to write up a basic SI or Phoenix emu as a tutorial.

jeff
 
Back
Top