Foreword
In the previous post we covered the ADC and SBC instructions and implemented it within our emulator.In this post we will be covering the Comparison and Branching instructions and implement it within our emulator.
Comparison and branching is the basic functionality that is equivalent to if-then statements in a High Level language.
Up to this point in time we had just been adding assembly instructions to our emulator. So, we haven't really been doing exciting stuff at least stretching the capabilities of Android. So, the question might be when will we be doing some more exciting stuff in our emulator?
The answer is after a about two more posts. After this post, I plan to do two further post in which our all the instructions will be implemented within our emulator. The remaining two posts will basically cover the stack and related operations, and finally all other instructions.
Once we have implemented all instructions the real fun will start to begin. We will need to run a Test Suite to test that we have implemented all the instructions correctly. Obviously the Test Suite will be a big bulk of instructions to execute, so single stepping wouldn't do. Running the Test Suite will therefore be the first time we will let our emulator run on its own. Making the GUI responsive during this run, will definitely be fun!
But enough of the future. Let us start with comparisons and branching...
Display order of Flags
Before we start, I just want to mention something from my previous post.BigEd from 6502.org actually make a suggestion regarding the display of the flags.
In a sense, each flag is represented by an appropriate bit within a byte called the status register.
BigEd's suggestion basically was that the flags should be listed in the same order (e.g. Most significant bit to least significant bit) as they appear in this register.
This just makes debugging a bit easier later on. To change the display order of the flags, we just need to modify the method within MainActivity:
public String getFlagDump() { String result =""; result = result + ((getNegativeFlag() == 1) ? "N" : "-"); result = result + ((getOverflowFlag() == 1) ? "V" : "-"); result = result + "-"; result = result + "B"; result = result + ((getDecimalFlag() == 1) ? "D" : "-"); result = result + ((getInterruptFlag() == 1) ? "I" : "-"); result = result + ((getZeroFlag() == 1) ? "Z" : "-"); result = result + ((getCarryFlag() == 1) ? "C" : "-"); return result; }
This will show the status of all eight bits in the status register. You will notice that we hard code two bit positions. First of all, bit 5 of the status register is never used, so for this bit position we will always show a minus (-).
Secondly you will see that we hardcode the letter B for bit 4 of the status register. This is called the break flag. We will only implement functionality for setting and clearing this flag in later posts. For now we will just default it to B (This flag is set like 90% of the time).
Coding the Comparison Instructions
On the 6502 you use the comparison instructions to test for equality between two numbers. This equality relates to the following operators in a High Level Language: Less than, Greater than and equal to.In each compare instruction the first number is implied by the Mnemonic (e.g. With CMP first operand is the accumulator, with CPX first operand is the X-Register and with CPY first operand is the Y-Register). The second number for the comparison is supplied by the operand.
The 6502 achieves the comparison by subtracting the two numbers and then setting the applicable flags. By inspecting the flags after the comparison you would be able to tell what the result of the comparison was.
The following table, as taken from http://www.6502.org, gives an idea how the different flags will be effected depending on the equality result:
Compare Result | N | Z | C |
A, X, or Y < Memory | * | 0 | 0 |
A, X, or Y = Memory | 0 | 1 | 1 |
A, X, or Y > Memory | * | 0 | 1 |
It is important to note that a Compare instruction doesn't alter the contents of a register or memory in any way. A Compare instruction just effects the flags.
Having said all this, let us start to code the compare instructions into out emulator.
First we need to create a method that will do the work of the comparing for us:
void CMP(int operand1, int operand2) { operand2 = ~operand2 & 0xff; operand2 = operand2 + 1; int temp = operand1 + operand2; carryFlag = ((temp & 0x100) == 0x100) ? 1 : 0; temp = temp & 0xff; zeroFlag = (temp == 0) ? 1 : 0; negativeFlag = ((temp & 0x80) != 0) ? 1 : 0; }
This is a generic method that we can use for all the compare instruction. You just can this method with the two number to be compared as parameters. The method starts off by changing the second number into twos complement form. This is just a form that enables you to do subtraction via addition. We could have potentially just have used the minus operator that C provides us, and C would have taken care of all the twos complement stuff under cover. However, this introduces some difficulties like the fact that you cannot get hold of the carry. So, manual two's complement it will be.
Let us implement all the compare instructions now within the instruction case statement in cpu.c:
/*CMP Compare Memory with Accumulator A - M N Z C I D V + + + - - - addressing assembler opc bytes cyles -------------------------------------------- immediate CMP #oper C9 2 2 zeropage CMP oper C5 2 3 zeropage,X CMP oper,X D5 2 4 absolute CMP oper CD 3 4 absolute,X CMP oper,X DD 3 4* absolute,Y CMP oper,Y D9 3 4* (indirect,X) CMP (oper,X) C1 2 6 (indirect),Y CMP (oper),Y D1 2 5* */ case 0xc9: CMP(acc, arg1); break; case 0xc5: case 0xd5: case 0xcd: case 0xdD: case 0xd9: case 0xc1: case 0xd1: CMP(acc, memory_read(effectiveAdrress)); break; /*CPX Compare Memory and Index X X - M N Z C I D V + + + - - - addressing assembler opc bytes cyles -------------------------------------------- immediate CPX #oper E0 2 2 zeropage CPX oper E4 2 3 absolute CPX oper EC 3 4 */ case 0xe0: CMP(xReg, arg1); break; case 0xe4: case 0xec: CMP(xReg, memory_read(effectiveAdrress)); break; /*CPY Compare Memory and Index Y Y - M N Z C I D V + + + - - - addressing assembler opc bytes cyles -------------------------------------------- immediate CPY #oper C0 2 2 zeropage CPY oper C4 2 3 absolute CPY oper CC 3 4*/ case 0xc0: CMP(yReg, arg1); break; case 0xc4: case 0xcc: CMP(yReg, memory_read(effectiveAdrress)); break;
As you can see all variants of the Compare instruction we just invoke the CMP with applicable parameters.
Implementing the branch instructions
In the previous section we have implemented the Compare instructions. On its own Compare instructions is not that useful. If we look again at an if-then statement in a High Level language, we see that an if-then statement does two things: Do the comparison, and then branch to the applicable block of code depending on the result of the comparison. And this what we are missing within our current emulator: Branching instructions.The 6502 provide us with a couple of branching instructions, branching on the state of a particular flag.
Take for example the instruction BCS (e.g. Branch on Carry set). This instruction will branch to the supplied address if the carry flag is set. The inverse of the BCS instruction also exist: BCC (Branch if carry clear). The BCC instruction will branch to the supplied address if the Carry flag is cleared.
There is similar instructions for most of the flags, as you will see in a moment.
There is something special about the address you supply with a branch instruction. The address you specify with a branch instruction is not a absolute address, but a relative one.
What is a relative address? A relative address is in fact an offset you need to add to the current value of the program counter to get to an actual address that we should jump to. A special note: The address of the next instruction after the current branch instruction is the address that will be used in the addition.
This offset address is a single byte. What is special about this offset is that it allows you to jump in a forward direction, as well as in a reverse direction. The reverse direction is indicated by the use of twos complement. The use of two's complement limits the jumping range as follows: -128...127.
We haven't implemented the relative address mode yet within the calculateEffevtiveAdd method, so let us add a case statement to the this method's switch statement:
case ADDRESS_MODE_RELATIVE: tempAddress = (argbyte1 > 127) ? (argbyte1 - 256) : argbyte1; tempAddress = tempAddress + pc; return tempAddress; break;
We can now implement the different branch instructions:
/*BCC Branch on Carry Clear branch on C = 0 N Z C I D V - - - - - - addressing assembler opc bytes cyles -------------------------------------------- relative BCC oper 90 2 2** */ case 0x90: if (carryFlag == 0) pc = effectiveAdrress; break; /*BCS Branch on Carry Set branch on C = 1 N Z C I D V - - - - - - addressing assembler opc bytes cyles -------------------------------------------- relative BCS oper B0 2 2** */ case 0xB0: if (carryFlag == 1) pc = effectiveAdrress; break; /*BEQ Branch on Result Zero branch on Z = 1 N Z C I D V - - - - - - addressing assembler opc bytes cyles -------------------------------------------- relative BEQ oper F0 2 2** */ case 0xF0: if (zeroFlag == 1) pc = effectiveAdrress; break; /*BMI Branch on Result Minus branch on N = 1 N Z C I D V - - - - - - addressing assembler opc bytes cyles -------------------------------------------- relative BMI oper 30 2 2** */ case 0x30: if (negativeFlag == 1) pc = effectiveAdrress; break; /*BNE Branch on Result not Zero branch on Z = 0 N Z C I D V - - - - - - addressing assembler opc bytes cyles -------------------------------------------- relative BNE oper D0 2 2**/ case 0xD0: if (zeroFlag == 0) pc = effectiveAdrress; break; /*BPL Branch on Result Plus branch on N = 0 N Z C I D V - - - - - - addressing assembler opc bytes cyles -------------------------------------------- relative BPL oper 10 2 2** */ case 0x10: if (negativeFlag == 0) pc = effectiveAdrress; break; /*BVC Branch on Overflow Clear branch on V = 0 N Z C I D V - - - - - - addressing assembler opc bytes cyles -------------------------------------------- relative BVC oper 50 2 2** */ case 0x50: if (overflowFlag == 0) pc = effectiveAdrress; break; /*BVS Branch on Overflow Set branch on V = 1 N Z C I D V - - - - - - addressing assembler opc bytes cyles -------------------------------------------- relative BVC oper 70 2 2** */ case 0x70: if (overflowFlag == 1) pc = effectiveAdrress; break; /*JMP Jump to New Location (PC+1) -> PCL N Z C I D V (PC+2) -> PCH - - - - - - addressing assembler opc bytes cyles -------------------------------------------- absolute JMP oper 4C 3 3 indirect JMP (oper) 6C 3 5 */ case 0x4C: case 0x6C: pc = effectiveAdrress; break;
The comments makes this code fairly self explanatory.
You will notice that I have also added the jump instruction. Although not strictly speaking a branch instruction as will always do a jump regardless if a certain condition is met or not. Also take note of the address modes available for jump. Jump doesn't have a relative mode instruction, but absolute and indirect.
Testing
Time to do some testing.We will again use a test program from my previous JavaScript emulator series, Part 5 : Comparisons and Branching
For convenience I am showing the assembly version of the program:
0000 LDA #$FB A9 FB 0002 CMP #$05 C9 05 0004 LDA #$05 A9 05 0006 CMP #$FB C9 FB 0008 LDA #$E2 A9 E2 000A CMP #$64 C9 64 000C LDX #$40 A2 40 000E DEX CA 000F CPX #$3A E0 3A 0011 BNE $000E D0 FB 0013 LDY #$10 A0 10 0015 CPY #$23 C0 23 0017 BCS $001D B0 04 0019 INY C8 001A JMP #$0015 4C 15 00 001D 00
Here is the array declaration of the program:
jchar my_program[] = {0xA9, 0xFB, 0xC9, 0x05, 0xA9, 0x05, 0xC9, 0xFB, 0xA9, 0xE2, 0xC9, 0x64, 0xA2, 0x40, 0xCA, 0xE0, 0x3A, 0xD0, 0xFB, 0xA0, 0x10, 0xC0, 0x23, 0xB0, 0x04, 0xC8, 0x4C, 0x15, 0x00, 0x00 };
The program starts off by doing a couple of comparisons. One can check the flags to see if the comparisons gives the expected results.
Finally the program will do two loops, looping a fixed number of times.
In Summary
In this post we implemented all variants of the Comapre and branching instructions.As mentioned at the beginning of this post, we have about two posts left in order to implement all the instructions within our emulator, after which the real fun will begin.
In the next post we will be covering the Stack and related operations.
Till next time!
No comments:
Post a Comment