License

This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.


Processor Interface

To use the emu65xx interface, include the emu65xx.h header file and call the Create() function to create a new emulator. Start the processor with a call to Reset().

Note that the emulator can't emulate the memory properly because each different computer architecture treats memory in a different way. Thus, the memory needs to be emulated by you. In order to work, the emulator calls functions defined in an object which supports the emulator callback class. The following shows you a very simple example of a computer running with 64Kb of RAM & ROM without any page managment.

Load/Store sample:
	class MyComputer : public emu65xx::Callback
	{
		...
		void OnLoad(unsigned short addr, unsigned char& value)
		{
			// TODO: add code to handle I/O

			value = f_ram[addr];
		}
		void OnStore(unsigned short addr, unsigned char value)
		{
			// TODO: add code to handle I/O

			// from 0xD000 to 0xFFFF it's ROM
			if(addr >= 0xD000) {
				return;
			}
			f_ram[addr] = value;
		}
	}

For a computer like the Apple computer, the I/O is between 0xC000 and 0xC0FF inclusive. The device ROM was 256 bytes per device and it was found in 0xC100 to 0xCFFF. The computer ROM was between 0xD000 and 0xFFFF. The addresses between 0xD000 and 0xDFFF were mapped thrice (2x RAM and 1x ROM) and the addresses between 0xE000 and 0xFFFF were mapped twice (1x RAM and 1x ROM). On the Apple //c, you had up to 6 banks of 64Kb of RAM. This means that all the addresses could be mapped 6 times to RAM, except for the addresses between 0xD000 and 0xDFFF which were mapped 12 times to different RAM chips. This was near impossible to use anyway! And I won't talk about the video text area...

As you can see, this means the coding of the RAM is really very specific to the computer you are trying to emulate. In other words, to avoid reinventing the wheel each time you want to create an emulator, the processor itself was concealed in a seperate library which can be used to emulate hundreds of computers.


INTERFACE

emu65xx

FUNCTION

Create -- create a new emulator

SYNOPSIS

	enum id_t {
		ID_65C02
	};

	enum processor_t {
		CPU_6502,
		CPU_65C02,
		CPU_65C02S
	};

	static emu65xx *Create(id_t id, processor_t processor, Callback& callback);

PARAMETERS

id -- the identifier of the emulator to be created
processor -- the processor to be emulated
callback -- an implementation of callbacks

DESCRIPTION

The Create() static function is called to create a processor. This function allocates an instance of some emu65xx implementation and returns a pointer to the object. Internally, the function will create a thread. The thread will be asleep until the Reset() function is called. Other functions can be called before to call the Reset() to setup the emulator as required. However, the NMI() and Interrupt() functions will have no effect.

To choose which implementation to create, use a different identifier (id_t). At this time, only one is available: ID_65C02.

SEE ALSO

SetTrace(), SetMhz(), SetSpeed(), Reset()


INTERFACE

emu65xx

FUNCTION

SetTrace -- print out the instructions as they are executed

SYNOPSIS

	virtual void SetTrace(bool trace = true) {}

PARAMETERS

trace -- whether the emulator should send trace events

DESCRIPTION

The SetTrace() function will be called to turn on the tracing of instructions as they are executed. The trace will generate a message which is sent to your callback using the OnTrace() function.

The format for the trace message is left to the emulator implementation. However, we suggest something similar to this:

	FCAA: E9 01     SBC #$01     A:0F  X:60  Y:4C  S:03  P:--1B-I-C
	FCAC: D0 FC     BNE $FCAA    A:0E  X:60  Y:4C  S:03  P:--1B-I-C

This includes PC, the opcode and operand data, the instruction, the optional address/data and the current content of the other registers.

The tracing can be turned on or off with this function. By default, the trace mode is off.

NOTE

The trace flag is used because creating the trace message takes some time. It is better to avoid it altogether if you don't want it.

Also, the overloading (and thus functionality) of the SetTrace() function is optional. This means some emulators may not have a trace feature.

SEE ALSO

Create(), Callback::OnTrace()


INTERFACE

emu65xx

FUNCTION

GetMhz -- get the current processor clock speed in Mhz
SetMhz -- setup the processor clock speed in Mhz
GetSpeed -- get the current speed used by the emulator SetSpeed -- changes the speed to be used by the emulator

SYNOPSIS

	enum speed_t {
		SPEED_FAST,
		SPEED_PROCESSOR,
		SPEED_SLOW
	};

	virtual unsigned int GetMhz(void) const = 0;
	virtual void SetMhz(unsigned int mhz) = 0;
	virtual speed_t GetSpeed(void) const = 0;
	virtual void SetSpeed(speed_t speed) = 0;

PARAMETERS

mhz -- the speed at which cycles are executed
speed -- the new speed the emulator should run at

DESCRIPTION

The SetMhz() function defines the speed at which the clock of the emulated processor is running at. It is expected to be 1, 2, 4, 6, 8 or 10. However, larger values can be used. Of course, much larger (such as 1000) won't be realistically reached since your current Pentium processor will most certainly not be fast enough to emulate such a 6502 processor. The Apple ][ clock was set at 1Mhz until the Apple //c came out which was supposedly running at 2Mhz. Strange since the sound effects seemed to work all the same on either systems.

NOTE: At this time I plan to have my emulator stop between each instruction. This should be fine if you have a fast Pentium processor (1Ghz or more). On slow machines, it may be a problem. We will see...

The SetSpeed() function is called to setup the speed of the processor. You can change the speed as you see fit. For instance, on an Apple ][, it is likely fine to run at full speed until the processor accesses either the speaker (C030) or the keyboard (C010). At that time, it certainly needs to run at processor speed. Thus, whenever these memory locations are read, you can force the speed to be SPEED_PROCESSOR.

The valid speeds are like this:

SPEED_FAST -- run as fast as possible
SPEED_PROCESSOR -- run at the speed the processor is supposed to run
SPEED_SLOW -- run at the speed the slowest 65xx would normally run

Use the GetMhz() and GetSpeed() to retrieve the current clock and speed of the emulator.

SEE ALSO

Create()


INTERFACE

emu65xx

FUNCTION

GetStats -- get the statistics

SYNOPSIS

	struct cpu_stats_t {
		unsigned int	f_instructions;
		unsigned int	f_unknown_opcode;
		unsigned int	f_cycles;
		unsigned int	f_read;
		unsigned int	f_write;
		unsigned char	f_inst_used[256 / 8];
		unsigned char	f_page_read[256 / 8];
		unsigned char	f_page_written[256 / 8];
	};

	virtual void GetStats(cpu_stats_t& st, bool reset = true) const = 0;

PARAMETERS

st -- a CPU statistics structure
reset -- whether the statistics should be reset

DESCRIPTION

Calling the GetStats() function requests the emulator to fill a CPU statistics structure. The emulator is supposed to keep track of what is being executed to be capable of returning these statistics. Since this isn't a feature available on such processors, this function is optional and thus some implementations may do nothing at all in this function.

The counters and flags defined in the statistics structure are defined below:

f_instructions -- count the total number of instructions being executed, excluding the unknown opcodes
f_unknown_opcode -- count the number of unknown opcodes encountered while running the different programs on the emulator; good programs should never run any unknown opcodes and thus this counter should stay at zero.
f_cycles -- count the total number of cycles spent running the instructions; these are the cycles of the program run on the emulator, not the cycles it takes to run the emulator on your computer.
f_read -- the number of bytes read; note that is whatever byte; the emulator can't distinguish between RAM, ROM and I/O reads; if you need/want such a decimation, create your own statistics in your callback OnLoad() and OnStore() functions.
f_write -- the number of bytes written; just like the f_read counter, the emulator can't distinguish between RAM, ROM and I/O reads.
f_inst_used -- an array of bits which are set as instructions are run; in the end, this gives you a map of all the instructions which have been used in a session. Note that unknown instructions will also be marked.
f_page_read -- an array of bits which are set as a page of memory is accessed in read mode; note that bits 8 to 15 in an address determine the page number.
f_page_written -- an array of bits which are set as a page of memory is accessed in write mode.

SEE ALSO

Create()


INTERFACE

emu65xx

FUNCTION

NMI -- Sends a non-maskable interrupt to the processor
Reset -- Resets the processor
Interrupt -- Sends a hardware interrupt the processor

SYNOPSIS

	virtual void NMI(void) = 0;
	virtual void Reset(void) = 0;
	virtual void Interrupt(void) = 0;

DESCRIPTION

The NMI() function asserts a non-maskable interrupt to the processor. The current instruction finishes execution and the next thing will be a regular NMI procedure (i.e. stacking P, stacking PC, loading PC with the address at FFFA/B and resuming execution). It is possible to return from an NMI using the RTI instruction.

The Reset() function stops the processor (because in software it is easier to do it after the current instruction finishes execution, it will), reads the address at FFFC/D and resume execution at that address. You can't return to where you were before a reset.

The Interrupt() function generates a high signal on the interrupt line. This means, as soon as the processor can accept an interrupt, it will load the address at FFFE/F and start executing the code there. The processor will ignore the interrupt if the interrupt flag (I) in the status register (P) is set to 0. When the processor enters the interrupt procedure, it stacks P, then PC, then loads the vector at FFFE/F in PC. You can return from an interrupt using the RTI instruction.

When you are in an interrupt handler, the break flag (B) in the status register (P) is set to 0. When you enter the interrupt handler because of the BRK instruction, the break flag is set to 1.

SEE ALSO

Create() Callback::OnReset()


INTERFACE

emu65xx::Callback

FUNCTION

OnNMI -- an NMI occured
OnReset -- someone pressed the reset button
OnInterrupt -- a hardware interrupt occured

SYNOPSIS

	virtual void OnNMI(void) {}
	virtual void OnReset(void) {}
	virtual void OnInterrupt(void) {}

DESCRIPTION

These functions will be called by the emulator thread whenever they are to be executed. This gives you a chance to run some initialization code necessary in these circonstances.

For instance, on the Apple ][, the Reset() instruction will force a reset of the memory banks to all defaults. There is no NMI or Interrupt on Apple ][ so these will be ignored on that emulator.

By default, these functions don't do anything.

SEE ALSO

emu65xx::NMI(), emu65xx::Reset(), emu65xx::Interrupt()


INTERFACE

emu65xx::Callback

FUNCTION

OnLoad -- request data from memory
OnStore -- write data to memory

SYNOPSIS

	virtual void OnLoad(unsigned short addr, unsigned char& value) = 0;
	virtual void OnStore(unsigned short addr, unsigned char value) = 0;

DESCRIPTION

The emulator doesn't emulate the memory. This is left to the computer implementer because each computer has its own way to handle RAM, ROM and I/O systems.

Thus, the emulator expects a callback object with the OnLoad() and OnStore() functions implemented (these two are mandatory).

The OnLoad() function reads memory and manages the I/O which responds to reads (keyboard, speaker, parallel port, etc.).

The OnStore() function writes to memory and manages the I/O which responds to writes (disks, parallel port, etc.). Also, the OnStore() should ignore writes to any ROM in the computer being emulated.

The emulator really doesn't do anything more than calling these functions when a Load() or Store() request is received.

SEE ALSO

Create(),
Load/Store sample


INTERFACE

emu65xx::Callback

FUNCTION

OnUnknownOpcode -- called whenever an unknown opcode is found
OnTrace -- receives the trace messages

SYNOPSIS

	virtual void OnUnknownOpcode(unsigned short& pc) {}
	virtual void OnTrace(const char *msg) {}

DESCRIPTION

The emulator may encounter some instructions that it can't recognize. These instructions can be emulated by the computer emulator instead. The OnUnknownOpcode() gives you a chance to do that. Also, it can be used for debug purposes to see whether some unknown opcode are being executed.

The OnTrace() function is called with a static string (i.e. you can't keep and/or delete the string pointer it gives you). This way, the computer emulator has a chance to do whatever it wants with the trace data (like writing it in a console).

SEE ALSO

SetTrace(),

Copyright (c) 2005-2006 Alexis Wilke