How does CPU emulation work?

What follows is my method for emulating a microprocessor.  This topic has been covered many times on the net, but not necessarily using my specific method.  Instead of explaining the basics of the execution loop, I am going to explain how to simulate the hardware such as video memory, ports and bank switched ROM/RAM.  Since modern PCs have plenty of memory compared to those from 15 years ago, we are afforded the luxury to "waste" a bit.  The way I implement 8-bit CPU memory maps is to allocate 3 x 64K (64K for ROM, 64K for RAM and 64K for flags).  The flags map is used to implement non-standard features.  A 0 indicates normal RAM, a 1 indicates ROM and any other number indicates the 'handler' procedure used to work with that address range.   This way, for any arcade game we can have up to 254 unique handler routines to deal with all of the hardware.  Shown below is a sample function for reading a byte of memory taken from my 6809 emulator:

unsigned char M6809ReadByte(unsigned char *m_map09, unsigned short usAddr)
{
unsigned char c;
switch(c = m_map09[usAddr+MEM_FLAGS])
/* If special flag (ROM or hardware) */
   {
   case 0:
/* Normal RAM */
      return m_map09[usAddr+MEM_RAM];
      break;
   case 1:
/* Normal ROM */
      return m_map09[usAddr+MEM_ROM];
      break;
   default:
/* Call special handler routine for this address */
      return (mem_handlers09[c-2].pfn_read)(usAddr);
   }

}
/* M6809ReadByte() */


Each unique flag number has a pair of handler routines associated with it - one for read and one for write.  The handler routine would get called with the address in question and return or store the appropriate value.  For example, to implement memory mapped video, the address range for the video RAM would be marked with the handler number for video RAM and get called each time a read or write occurred in that range.  This way, time is not wasted in a general memory handler checking for each address range individually.  This method is faster than the method used by some other emulators simply because there are no unnecessary address comparisons.  This method also solves the problem of ROM corruption; the MAME implementation of Joust had this problem because the program was allowed to write onto ROM.  Some games actually try to write into ROM for protection.  Using the above method, bank switched memory is also easy to implement.  For example, in the Williams games the video RAM is mapped to the same address space as some of the ROM.  This is handled by marking that area to use a handler routine which checks the state of the bank switch and acts appropriately.   Shown below is a sample handler routine used for the Asteroids bank switch (player 1/2 score info):

void AstBankSWWrite(unsigned short usAddr, unsigned char ucByte)
{
register unsigned char c;
int i;

   c = ucByte & 4; /* Isolate bank switch bit */
   if (c != (mem_map[MEM_RAM+usAddr] & 4)) /* If bit is changing */
      {
      /* Swap areas at $200 with $300 (player 1/2 info swap) */
      for (i=0; i<0x100; i++)
         {
         c = mem_map[MEM_RAM+0x200 + i];
         mem_map[MEM_RAM+0x200 + i] = mem_map[MEM_RAM+0x300+i];
         mem_map[MEM_RAM+0x300+i] = c;
         }
      }
   mem_map[MEM_RAM+usAddr] = ucByte; /* Store new bit setting */

} /* AstBankSWWrite() */

Back