Implementation Overview (Very Brief) ------------------------------------ +-----------------+----------------+ | monitor program | O.S. Support | | +--------+------+ | | | 6502 CPU | | | |Emulation Layer| | +--------+---------------+---------+ | RAM/ROM and Memory Mapped H/W | +----------------------------------+ 1. Monitor Program (monitor.c) Development debugging tool. Allows emulated system to be interogated. 2. O.S. Support (atari.c, atari_sio.c, atari_h_device.c) Support routines that allow the Emulated Operating system to talk to the host machines. These are implemented using a pseudo 6502 escape instruction. Support for other operating systems could be built into this section 3. 6502 CPU Emulator Layer (cpu.c) Access memory by calling routines within hardware layer. Not the fastest way of doing this, but it produces a compact flexible system that should be able to emulate other platforms. 4. Hardware Layer (atari_custom.c) RAM / ROM / DEVICES and memory mapped custom chips. Again support for other hardware systems could be inserted into this section. Software Execution Sequence =========================== main () main.c atari_main () atari.c Atari800_Initialise () atari_custom.c Install signal handler Parse Atari 800 specific options SIO_Mount atari_sio.c Atari800_OS () atari.c Load and Patch OS with Escape codes Set memory areas to ROM, RAM and HARDWARE Install CPU Escape code handler CPU_Reset () cpu.c GO (TRUE) /* Start Processor */ cpu.c monitor () /* If Error detected */ monitor.c Link Hardware and Operating System to CPU ========================================= This emulator has been designed to cope with multiple 6502 based computers. In order to add support for another computer you need to implement a hardware emulation layer and link this into the 6502 CPU module. e.g. The Atari 800 is linked in by defining five routines :- UBYTE Atari800_GetByte (UWORD addr); Returns the byte at the memory address. UWORD Atari800_GetWord (UWORD addr); Returns the word at the memory address. void Atari800_PutByte (UWORD addr, UBYTE byte); Stores the byte at the memory address. void Atari800_Hardware (void); Called after every instruction to emulate the machines hardware. void Atari800_Escape (UBYTE code); Called when the CPU module encounters the special emulators additional ESC instruction. The opcode is 0xff and it takes a single immediate operand that contains a user defined value. This instruction is used to patch the operating system. Each of these sections are linked to the CPU module at runtime by assigning the addresses of the functions to the respective CPU pointer functions. #include "cpu.h" GetByte = Atari800_GetByte; GetWord = Atari800_GetWord; PutByte = Atari800_PutByte; Hardware = Atari800_Hardware; Escape = Atari800_Escape; The CPU module will now call the Atari800 specific function for all memory accesses. e.g. Switching between multiple systems. Set up and Patch required O.S. here switch (machine) { case Atari800 : GetByte = Atari800_GetByte; GetWord = Atari800_GetWord; PutByte = Atari800_PutByte; Hardware = Atari800_Hardware; Escape = Atari800_Escape; break; case BBC : GetByte = BBC_GetByte; GetWord = BBC_GetWord; PutByte = BBC_PutByte; Hardware = BBC_Hardware; Escape = BBC_Escape; break; case CBM64 : GetByte = CBM64_GetByte; GetWord = CBM64_GetWord; PutByte = CBM64_PutByte; Hardware = CBM64_Hardware; Escape = CBM64_Escape; break; } Memory Types ------------ For the Atari 800 hardware, 64K of memory is allocated together with 64K of memory attribute space. Every byte of emulated memory has an associated attribute. Valid attributes are ROM, RAM and HARDWARE. Writes to addresses with the ROM attribute are blocked by PutByte. Reads and writes to addresses with the HARDWARE attribute are handled as special cases by both PutByte and GetByte. The RAM attribute allows both reads and writes to the specified address. e.g. UBYTE memory[65536]; UBYTE attribute[65536]; UBYTE Atari800_GetByte (UWORD addr) { UBYTE byte; if (attribute[addr] == HARDWARE) { switch (addr) { case HARDWARE_ADDRESS_1 : case HARDWARE_ADDRESS_2 : case HARDWARE_ADDRESS_x : byte = Special Code; } } else { byte = memory[addr]; } return byte; } Obviously the function call overhead for each memory access is a lot, but it does allow a lot of flexibility for future extensions. If I can increase the screen refresh rate this is likely to be the next target for improvement (Inline function in GCC maybe). Screen Generation ----------------- Each scanline is processed separatly and composes of 384 pixels - enough for a wide playfield. There are several independent graphic elements may which overlap :- 1. The normal playfield (3 bits) - each pixel may be one of :- a. Playfield 0 b. Playfield 1 c. Playfield 2 d. Playfield 3 e. Background 2. Player 0 (1 bit) 3. Player 1 (1 bit) 4. Player 2 (1 bit) 5. Player 3 (1 bit) 6. Missile 0 (1 bit) 7. Missile 1 (1 bit) 8. Missile 2 (1 bit) 9. Missile 3 (1 bit) Provision must be made for collision detection. To do this each of the 384 pixels is represented using a 16 pixel that contains all the above information. The 16 bit pixel is processed according to the contents of PRIOR in order to determine the final pixel colour. f e d c b a 9 8 7 6 5 4 3 2 1 0 --------------------------------------------------------------- M3 M2 M1 M0 P3 P2 P1 P0 --- --- --- --- PF3 PF2 PF1 PF0 Example: Collision detection if (P0) { P0PF |= Pixel & 0x0f P0PL |= (Pixel >> 8) & 0x0f } if (M0) { M0PF |= Pixel & 0x0f M0PL |= (Pixel >> 8) &0x0f } The GTIA modes can be generated directly from the 384 pixel buffer. Unanswered Questions -------------------- 1. In collision detection, does a player collide with itself ? i.e. Does P0PL bit 0 always contain a 0 or a 1. Does P1PL bit 1 always contain a 0 or a 1. Does P2PL bit 2 always contain a 0 or a 1. Does P3PL bit 3 always contain a 0 or a 1.