Beginning Emulator Writing
When
you first begin your emulator the first thing you'd usually do is
choose a language. There are many many programming languages out
there each with their own strengths and weaknesses. My personal
favorite is C++, but emulators have been writing in various other
languages. Of course since C++ is my language of choice the code
contained in this tutorial will also be C++ code. Now you've picked
your langauge right? Good
because its time to start discussing
Whenever you begin an emulator there are
always certain things which must be emulated first. From my experience
these are the memory and CPU subsystems. These two systems are the
heart of most emulators, and taking your time to write them well
can have a huge pay-off in the end. After these two subsystems are
created, all the other subsystems can be built and used effectively.
Now lets go into a little more detail.
The Memory Subsystem
The memory subsystem is relied upon by just
about every part of your emulator. The CPU retrieves opcodes and data
to work on from memory, the graphics device retrieves textures, the
sound devices retrieves encoded sounds. It is basically the core of
your emulator. Memory is emulated usually by controlling access when
someone is either reading or writing to it. This is done by creating
functions or classes which act as interfaces to the memory subsystem
by utilizing functions you can map memory and control access to memory
mapped devices. The typical memory interface would look something
like this:
class EmulatedConsoleMemory
{
public:
//////////////////////////////////////////////////////////////////////////////////////
// Constructors
/** The console memory will be initialized here.
*/
EmulatedConsoleMemory()
{
mConsoleMemory = new unsigned char[CMS_MEMORY_SIZE];
assert(mConsoleMemory != NULL);
}
//////////////////////////////////////////////////////////////////////////////////////
// Destructors
/** The console memory will be deallocated here.
*/
~EmulatedConsoleMemory()
{
if(mConsoleMemory != NULL)
{
delete [] mConsoleMemory;
mConsoleMemory = NULL;
}
}
//////////////////////////////////////////////////////////////////////////////////////
// Byte Reads and Writes
/** Writes a byte of data to memory.
*/
void WriteByte(unsigned short address, unsigned char data)
{
mConsoleMemory[(address&0xFFFE)] = data;
}
/** Reads a byte of data from memory.
*/
unsigned char ReadByte(unsigned short address) const
{
return mConsoleMemory[(address&0xFFFE)];
}
private:
//////////////////////////////////////////////////////////////////////////////////////
// Private Enumerations
enum ConsoleMemorySize
{
CMS_MEMORY_SIZE = 0xFFFF
};
//////////////////////////////////////////////////////////////////////////////////////
// Private Variables
unsigned char* mConsoleMemory; ///< A pointer to the allocated block of memory.
};
|
Before I go
any farther I'd like to state that this example has a flaw. It violates
a programming principle known as the DRY(Don't Repeat Yourself)
principle. This principle simply states that you should avoid needless
repetition at all costs, because it only makes code harder to change
in the future. There are many ways to emulate the memory mappings
and arrangements of the various systems out there, in this article
I'll try to present the ones that I've seen used in order to aid
you later on in emulator development when you will be forced to
decide if you're memory emulation scheme is indeed violating the
DRY principal.
|