Foreword
In the previous post we implemented all stack operations for our Flutter C64 emulator. This included pushing and popping the Accumulator and the status register. Also, we implemented the JSR/RTS instructions, which also operates on the stack.
In this post we will be implementing the remaining instructions of the 6502, which includes the following:
- BIT
- JMP (Jump)
- NOP
- Register operations
With these instructions implemented, we can start in the next post to run the Klaus Dormann Test suite on our emulator to see if we have implemented all the 6502 instructions correctly.
Enjoy!
The Jump instruction
Implementing the jump instruction is just a straight forward operation of loading the program counter with a new value. Let us add the selectors for these:
/* JMP (JuMP) Affects Flags: none MODE SYNTAX HEX LEN TIM Absolute JMP $5597 $4C 3 3 Indirect JMP ($5597) $6C 3 5 */ case 0x4C: case 0x6C: pc = resolvedAddress;Now, there is two address modes for this instruction: Absolute and Indirect. The absolute address mode we have already implimented in the calculateEffectiveAddress() method, but not the Indirect Address mode. So, within the calculateEffectiveAddress() method, let us add the following selector:
case AddressMode.indirect: var lookupAddress = (operand2 << 8) | operand1; return memory.getMem(lookupAddress) | (memory.getMem(lookupAddress + 1) << 8);
The BIT instruction
Next, let us implement the BIT instruction. From the specs, the BIT instruction is defined as follows:
BIT (test BITs) Affects Flags: N V Z MODE SYNTAX HEX LEN TIM Zero Page BIT $44 $24 2 3 Absolute BIT $4400 $2C 3 4 BIT sets the Z flag as though the value in the address tested were ANDed with the accumulator. The N and V flags are set to match bits 7 and 6 respectively in the value stored at the tested address.We implement this as follows:
case 0x24: case 0x2C: int memByte = memory.getMem(resolvedAddress); _z = ((memByte & _a) == 0) ? 1 : 0; _n = ((memByte & 0x80) != 0) ? 1 :0; _v = ((memByte & 0x40) != 0) ? 1 :0;
The NOP instruction
The NOP instruction is the short for No Operation. It literally does nothing except for consuming CPU cycles. One of the major uses of this instruction is to reserve some slots in memory where in future you might want to add some more instructions.
Strictly speaking you don't need to implement a case selector for this instruction in our big switch statement decoding the different op opcodes. The surrounding mechanism should just skip to the next instruction.
However, by not implementing a selector for NOP, the default selector will be invoked in the switch statement. The default selector is nice to warn us if we forgot to implement some instructions or we encountered some undocumented instructions in the code. By not giving NOP a selector we will get many false positives by hitting the default selector.
So, the selector for NOP will look as follows:
/* NOP (No OPeration) Affects Flags: none MODE SYNTAX HEX LEN TIM Implied NOP $EA 1 2 */ case 0xEA: break;With the Dart language we don't need the break in general. However, when you have blank case like this you will need to add it, otherwise it will fall through to the next case statement with code, which is not what we want.
Register operations
Finally, let us implement the register operations. As per the specs, these are the following Instructions:
Register Instructions Affect Flags: N Z These instructions are implied mode, have a length of one byte and require two machine cycles. MNEMONIC HEX TAX (Transfer A to X) $AA TXA (Transfer X to A) $8A DEX (DEcrement X) $CA INX (INcrement X) $E8 TAY (Transfer A to Y) $A8 TYA (Transfer Y to A) $98 DEY (DEcrement Y) $88 INY (INcrement Y) $C8In previous posts we did implement some of these. Doing some inventory, I found that the following still needs to be implemented:
- TAX
- TXA
- INX
- TAY
- TYA
- INY
case 0xAA: _x = _a; _n = ((_x & 0x80) != 0) ? 1 : 0; _z = (_x == 0) ? 1 : 0; case 0x8A: _a = _x; _n = ((_a & 0x80) != 0) ? 1 : 0; _z = (_a == 0) ? 1 : 0; case 0xE8: _x++; _x = _x & 0xff; _n = ((_x & 0x80) != 0) ? 1 : 0; _z = (_x == 0) ? 1 : 0; case 0xA8: _y = _a; _n = ((_y & 0x80) != 0) ? 1 : 0; _z = (_y == 0) ? 1 : 0; case 0x98: _a = _y; _n = ((_a & 0x80) != 0) ? 1 : 0; _z = (_a == 0) ? 1 : 0; case 0xC8: _y++; _y = _y & 0xff; _n = ((_y & 0x80) != 0) ? 1 : 0; _z = (_y == 0) ? 1 : 0;This covers the instructions we wanted to implement in this post. I am not going to write a test program in this post to test the instructions we have implemented, since in the next post we will start to run the Klaus Dormann Test Suite, which will anyway surface any defects.
In Summary
In this post we implemented the remaining instructions for our emulator.
In the next post we run the Klaus Dormann Test Suite on our Emulator to see if we have some defects in our implementation. This will probably go over to multiple posts depending on how many issues we detect.
Until next time!
No comments:
Post a Comment