Foreword
In the previous post we covered the ADC and SBC instruction.In this section we will be covering Comparison and Branching Instructions.
Comparison Instructions
Let us have a look in Appendix A what a compare instruction does on the 6502:
A - M N Z C I D V + + + - - -
As you can see a compare instruction subtracts two numbers and effects the Negative, zero and carry flag. Also note that this operation doesn't change the value of the Accumulator or memory location.
So, what does the effected flags mean when you do a compare?
Firstly, when the compare instruction sets the zero flag, it means that the two numbers are equal.
The carry flag is set it means that the accumulator is bigger than or equal to the given memory location. If the carry flag is cleared, it means the number in the accumulator is smaller than the number in the memory location.
It is important to note that the carry flag is the result of a unsigned number comparison. This means if you compare -1 and 1, -1 will be the larger number. This is because -1 as an unsigned number is 255, and 255 > 1.
If you would prefer signed number comparison instead, the negative flag is the flag you will need to look at. If it is set it means that the number in the accumulator is less than the number in the memory location. If the negative flag is cleared it means the number in the accumulator is greater than or equal to the number in the memory location.
When doing signed comparison (e.g. checking the negative flag), beware of overflow conditions. I will give you an example.
Lets say you want to compare -30 and 100. Behind the scenes this will translate to the subtraction -30 -100, which will yield -130. This is clearly an overflow condition as it is outside the range of a signed 8-bit number.
How will this effect our signed number comparison? Lets see, -30 is 1110 0010 in two complement and -100 is 1001 1100. Lets add them up:
1110 0010
1001 1100
(1)0111 1110
OK, a carry is generated which means our unsigned comparison is correct. But, look what happened at our signed comparison. Our Negative flag (e.g. most significant bit) is zero, implying the negative number is bigger than the positive number. Our signed comparison failed! So be very careful with signed comparisons.
Lets create a method that will do the comparisons for us:
function CMP(operand1, operand2) { operand2 = ~operand2 & 0xff; operand2 = operand2 + 1; temp = operand1 + operand2; carryflag = ((temp & 0x100) == 0x100) ? 1 : 0; temp = temp & 0xff; zeroflag = (temp == 0) ? 1 : 0; negativeflag = ((temp & 0x80) != 0) ? 1 : 0; }
This function closely resembles SBC with a couple of subtle differences. Firstly we don't subtract the carry and we don't return a value.
/*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, localMem.readMem(effectiveAdrress)); break;
Lets do the same for CPX and CPY
/*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(x, arg1); break; case 0xe4: case 0xec: CMP(x, localMem.readMem(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(y, arg1); break; case 0xc4: case 0xcc: CMP(y, localMem.readMem(effectiveAdrress)); break;
Branching Instructions
All Branching Instructions makes use of an address mode called relative.
Relative to what? Relative to the address pointed to by the program counter. To be more specific, we use the address of the Instruction after the one we are currently busy with.
Let us work with an example again. Lets say you want to execute this instruction:
C000 F0 05
Firstly, we see this instruction starts at location $C000. F0 is the opcode for branch if equal (BEQ).
As we pointed out, the address that we will use is not C000, but C002. 05 is what we need to add to the address to get the effective address. So C000 + 05 = C005. So, if the condition is true, e.g. equal we will jump to C005. If it is not equal we will continue executing at C002.
Note that we are not limited to jumping in a forward direction. We can also jump in a backwards direction. For this we will use a negative number, or, in 6502 terminology use a twos complement number. So lets say if the condition was true, we want to jump to BFF0. So, if you calculate the difference between the addresses, we see that we need to jump 18 Bytes backwards or -18. Twos complement of -18 is EE. So, our instruction will look like this:
C000 F0 EE
Note that the usual limits of twos complement applies. So, at most you can jump 127 bytes in a forward direction and 128 bytes backwards.
Ok, let start coding.
First lets implement the relative address mode in the calculateEffevtiveAdd method:
case ADDRESS_MODE_RELATIVE: tempAddress = (argbyte1 > 127) ? (argbyte1 - 256) : argbyte1; tempAddress = tempAddress + pc; return tempAddress; break;
The first assignment is a quick way to convert a twos complement number to a proper negative number.
When calculateEffevtiveAdd is called the program counter already points to the next instruction, so need to do program counter manipulation.
We should also implement the relative address mode in getDecodedStr:
case ADDRESS_MODE_RELATIVE: addrStr = getAsFourDigit(((argbyte1 > 127) ? (argbyte1 - 256) : argbyte1) + pc + 2); result = result + "$" + addrStr; return result; break;
When getDecodedStr is called still points to the beginning of the current instruction. That is the reason for adding 2 in the first assignment
Next, lets implement the branch Instructions. The description of each in Appendix A is self explanatory, so I will not give a discussion on these 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;
There is one final instruction that is very similar to Branch instructions: Jump. This instruction is however a unconditional jump. Also, this instruction doesn't use relative addressing, but absolute and indirect. Here is the implementation:
/*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;
Short and sweet!
Let's end off this section with a example assembly 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
This assembly program translates to the following set of numbers you can copy into the memory array:
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
While stepping through the program, I actually found a issue with the disassembly mechanism. Both DEX and DEY show up as DEC! Investigation actually let to the conclusion that there is a typo on masswerk website. For both instructions it appears as DEC. I fixed this in the relevant table in CPU.js and updated it on GITHUB.
Summary
In this section we covered Comparison and Branching instructions.In the next section we will cover the stack and related instructions.
Hey Johan - just wanted to say I picked up following your series today with much anticipation. I'm implementing in C# instead of Javascript and it's working out well.
ReplyDeleteThanks so much for the effort you've put into this, I look forward to getting to sprites / hires modes, etc.
Hi spud
DeleteGlad to hear you are enjoying the series and finding it useful. If you have any questions, feel free to ask.