6
votes

In the spare time I'm starting writing a very simple C++ emulator for the 6502 CPU. I used to write down a lot of assembly code for this CPU so all the opcodes, addressing modes and other stuff are not a big deal.

The 6502 has 56 different instructions plus 13 addressing modes giving a total of 151 different opcodes. To me speed is not an issue so instead of writing a huge switch-case statement and repeat the same code again and again (different opcodes can refer to the same instruction using a different addressing mode) I'd like to separate actual instruction code from the addressing mode code: I found this solution very neat as it would require to write only 13 addressing mode functions and 56 instruction functions without repeat myself.

here the addressing mode functions:

// Addressing modes
uint16_t Addr_ACC(); // ACCUMULATOR
uint16_t Addr_IMM(); // IMMEDIATE
uint16_t Addr_ABS(); // ABSOLUTE
uint16_t Addr_ZER(); // ZERO PAGE
uint16_t Addr_ZEX(); // INDEXED-X ZERO PAGE
uint16_t Addr_ZEY(); // INDEXED-Y ZERO PAGE
uint16_t Addr_ABX(); // INDEXED-X ABSOLUTE
uint16_t Addr_ABY(); // INDEXED-Y ABSOLUTE
uint16_t Addr_IMP(); // IMPLIED
uint16_t Addr_REL(); // RELATIVE
uint16_t Addr_INX(); // INDEXED-X INDIRECT
uint16_t Addr_INY(); // INDEXED-Y INDIRECT
uint16_t Addr_ABI(); // ABSOLUTE INDIRECT

they all returns the actual memory address (16 bit) used by the instruction to read/write the operand/result

the instruction function prototype is:

void Op_ADC(uint16_t addr);
void Op_AND(uint16_t addr);
void Op_ASL(uint16_t addr);
    ...

it takes the 16 bit address, perform its own operations, update the status flags and/or registers, and commit the results (if any) on the same memory address.

Given that code framework I found difficult to use the ACCUMULATOR addressing mode which is the only one to return the actual value of the A internal register instead of a memory address. I could return the value of A using the uin16_t return type and add a boolean flag for such addressing mode but I find it an extremely ugly solution.

The instruction functions should be completely addressing-mode agnostic.

1
Internally using 16-bit addressing mode, you can just map the complete context (A,X,Y,SP,ST,PC, what else?) to address 0x10000; your functions should be modified to accept uint32_t addr, out of which only the first 0x10008 or so bytes are actually used.Aki Suihkonen
If the instructions are addressing mode agnostic, then why do they need a flag to know the source of the data? You may find it helpful to look up or re-create a diagram of the processor data paths; typically there are multiplexors feeding into the input ports of the ALU, so you could focus on functional blocks which obtain that data, and those which use it. Given that all of this state, both programming-model-visible and hidden inside is factually global, using global variables (or a "machine" struct which you pass around a pointer to) is OK if that makes your implementation easier.Chris Stratton
1) I think you'll find that speed matters as soon as you get it to work and try to run it on something interesting and suddenly the giant switch statement will matter 2) 151 routines of 5 lines? I think I'd just bit the bullet and do it; I suspect you'll be done in a 2 hours. (This exchange is probably just a distraction). You'll surely have common subroutines that implement the "addressing modes" and that's where you get leverage.Ira Baxter
Have the addressing mode functions return a pointer to the data.brian beuning
The "ACCUMULATOR" mode is in fact some kind of "IMPLIED" mode. Both modes do not use an address. You should implement such instructions like separate instructions. Example: Implement two instructions for "ROL": "ROL A" and "ROL memory" (which may be ABSOLUTE or INDEXED-X ABSOLUTE).Martin Rosenau

1 Answers

4
votes

In Sharp6502 (my 6502 emulation engine written in C#) I treat internal registers and external memory both as first-class objects - the MemoryManager class instantiates an object for external memory, and another for internal registers, mapped to a different numeric range. Consequently, memory access and register access are identical at the functional level, since they are both referenced through MemoryManager according to what is basically an index.

Address-mode differentiation is simply a matter of filtering the bit-pattern of the instruction under emulation and performing a very simple calculation to determine the index to be passed to the MemoryManager - this might be implied, or require one or two further bytes, but the underlying mechanism is identical for every instruction.