Foreword
In the previous post we implemented the Logic operator and bit shifting instructions for our Flutter C64 emulator.
In this post we will be implementing the Compare and branching instructions.
The Compare instructions
The comparison instructions remind us of the if statement you get in almost every programming language where you test two numbers to see which one is the biggest or if they are equal.
In machine language, like on the 6502, we mimic the if statement via a compare instruction which subtract the two numbers effecting either the Carry, negative or zero flag. The then part of the if statement we mimic with a branch instruction, where you can say branch to a particular address depending on a certain state of one these flags. More on the branch instructions in another section.
One concern when mentioning the fact that the compare instruction does a subtract, is whether an overflow is a possibility as we experience with an SBC (subtract with carry). Checking the documentation and seeing that no Overflow flag is set by the compare instruction, is indeed confusing.
There is, however, two facts that make the overflow flag not relevant with a compare instruction. One fact is that a compare does an unsigned comparison. The other reason is we only consider the Carry or Zero flag when doing a comparison, and don't really look at the Negative flag.
Let us now see how we can implement the compare instructions in our flutter emulator.
One can get into temptation just to in Flutter to just do a physical subtraction when implementing a compare instruction. However, one would not be able to accurate emulate a 6502 compare instruction when you do this. Let us go into a bit more detail into why this is.
Lets take an example. If a number in the accumulator is bigger than the other number compare. The carry flag needs to set. The carry flag corresponds to bit 8 of the result, or have a weight of 256. So, suppose you compare 2 with 1, you should get the value 257, which is this value in binary:
(1) 0000 0001
If you just do a subtraction in flutter for this, you will just get 1. Strictly speaking, there will still be a carry in the background of Flutter and your CPU the operation is performed, but because the numbers have much more bits in modern day CPU's than 8 bits, like 64 bits, the carry bit will probably be at bit 65 or so.
So, let us see if we can emulate the 6502 compare instructions more accurately. To do this, we meed to understand how the 6502 does subtraction. All this boils down to Two's complement for representing negative numbers. Two's complement basically saus in order to make a number negative, you first need to negate the number (that is making every 1 a zero and every zero a one) and add one to the result.
Say for instance you want to represent -1. First, in binary you have:
0000 0001
Now do the negation:
1111 1110
And then add 1:
1111 1111
At first glance, this number doesn't look meaningful, but lets take an analogy that will shed new light on the meaning of such a number. Everyone knows about an odometer of a car. It starts at 000000 and counts till 999999. When it goes past 999999, it goes back to 000000.
Suppose we could do something interesting. With the odometer at 000000, we go back 3 units, then you are at 999997. Now, if you add 5, you get to (1)000002. The one is in brackets because the digit doesn't really exists on the odometer. But, what we have actually done here was subtracting 3 from 5 using addition! The 999997 is the ten's complement representation of -3.
We can use the same analogy in binary. Lets say you have the 8 bit binary number 0000 0000. If you move back one unit, you get 1111 1111, where everything is zero. This actually corresponds to -1, which we determined earlier.
If you move back one further unit you get 1111 1110 for -2 and 1111 1101 for -3. All this you can verify using 2's complement.
Let us now use this knowledge with a compare. Suppose you want to compare 2 and 1. So, we do 2-1 in binary, with the -1 converted to two's complement:
0000 0010
+ 1111 1111
(1)0000 0001
We can see we have a carry indicating the first number is bigger than the second. If we swop the number around, i.e. 1-2, we get this:
0000 0001
+1111 1110
1111 1111
In this case 1111 1110 is the two's complement of -2. In this case we dont get a carry with the addition, meaning the first number is smaller.
Let us now do some coding to implement these instructions in our flutter emulator. We create the following compare method which we can use among the different flavours of compare instructions:
void compare(int operand1, int operand2) { operand2 = ~operand2 & 0xff; operand1 = operand1 + operand2 + 1; _n = ((operand1 & 0x80) == 0x80) ? 1 : 0; _c = (operand1 & 0x100) != 0 ? 1 : 0; _z = ((operand1 & 0xff) == 0) ? 1 : 0; }This method starts off with doing twos complement, but we and the result of the negate with 0xff, so we just sit with the lower 8 bits. Our flutter emulator will probably run on 64 bit machines, which meand if we do a negate, we will probably sit with a 64-bit number where bits 8 to 63 are ones, which will probably give us a result which we don't want.
/* CMP (CoMPare accumulator) Affects Flags: N Z C MODE SYNTAX HEX LEN TIM Immediate CMP #$44 $C9 2 2 Zero Page CMP $44 $C5 2 3 Zero Page,X CMP $44,X $D5 2 4 Absolute CMP $4400 $CD 3 4 Absolute,X CMP $4400,X $DD 3 4+ Absolute,Y CMP $4400,Y $D9 3 4+ Indirect,X CMP ($44,X) $C1 2 6 Indirect,Y CMP ($44),Y $D1 2 5+ + add 1 cycle if page boundary crossed */ case 0xC9: compare(_a, arg0); case 0xC5: case 0xD5: case 0xCD: case 0xDD: case 0xD9: case 0xC1: case 0xD1: compare(_a, memory.getMem(resolvedAddress));Pretty straightforward, and we pass the value of the accumulator in, in both cases.
/* CPX (ComPare X register) Affects Flags: N Z C MODE SYNTAX HEX LEN TIM Immediate CPX #$44 $E0 2 2 Zero Page CPX $44 $E4 2 3 Absolute CPX $4400 $EC 3 4 */ case 0xE0: compare(_x, arg0); case 0xE4: case 0xEC: compare(_x, memory.getMem(resolvedAddress)); /* CPY (ComPare Y register) Affects Flags: N Z C MODE SYNTAX HEX LEN TIM Immediate CPY #$44 $C0 2 2 Zero Page CPY $44 $C4 2 3 Absolute CPY $4400 $CC 3 4 */ case 0xC0: compare(_y, arg0); case 0xC4: case 0xCC: compare(_y, memory.getMem(resolvedAddress));So, we pass in the value of register x for CPX instructions and the value of register y for CPY instructions.
Branching Instructions
4000 A9 05 LDA #$5 4002 38 SEC 4003 E9 01 SBC #$1 4005 D0 FC BNE $FC 4007 A9 22 LDA #$22Here we have a program where we basically have a loop where the Accumulator starts with value 5, and gets decremented till it reaches zero.
In our example, the parameter $FC is the two's complement for -4. Now when we want to execute the BNE, program counter is just after this instruction, which in this case is 4007. Subtract 4 from this, and you are at address 4003, which is where we want to be.
if (operand1 > 127) { operand1 = operand1 | 0xff00; } return (pc + operand1) & 0xffff;So, if we see our offset is negative, e.g. our byte value is bigger than 127, we pad bits 8-15 with ones. If we then add this to the program counter, the lower 16 bits of the result would indeed be the result of a subtraction.
So, in VICE, if the branch is to be taken it does something very fancy when calculating the destination address. It casts the byte to a signed char! This is a very nifty trick which the C language provides. By casting a byte as a signed value, the C compiler honours the fact that this byte is a twos complement value, and thus if the value of the byte is negative, it will do the subtraction for you.
0xfe.toSigned(8)You will get back -2.
int calculateEffectiveAddress(int mode, int operand1, int operand2) { var modeAsEnum = AddressMode.values[mode]; switch (modeAsEnum) { ... case AddressMode.relative: return (pc + operand1.toSigned(8)) & 0xffff; } ... return 0; }Next, we implement the following method:
branchConditional(bool doBranch, branchAddress) { if (doBranch) { pc = branchAddress; } }And finally, we can implement all the branch instructions:
/* BPL (Branch on PLus) $10 */ case 0x10: branchConditional(_n == 0, resolvedAddress); /* BMI (Branch on MInus) $30 */ case 0x30: branchConditional(_n == 1, resolvedAddress); /* BVC (Branch on oVerflow Clear) $50 */ case 0x50: branchConditional(_v == 0, resolvedAddress); /* BVS (Branch on oVerflow Set) $70 */ case 0x70: branchConditional(_v == 1, resolvedAddress); /* BCC (Branch on Carry Clear) $90 */ case 0x90: branchConditional(_c == 0, resolvedAddress); /* BCS (Branch on Carry Set) $B0 */ case 0xB0: branchConditional(_c == 1, resolvedAddress); /* BNE (Branch on Not Equal) $D0 */ case 0xD0: branchConditional(_z == 0, resolvedAddress); /* BEQ (Branch on EQual) $F0 */ case 0xF0: branchConditional(_z == 1, resolvedAddress);
A Test program
4000 A2 0A LDX #$0A 4002 CA DEX 4003 E0 04 CPX #$04 4005 D0 FB BNE LOOP 4007 A9 15 LDA #$15This program will loop with values from $a to $4 in register X.
You can find this program as binary as well as the state of our emulator as per this post, via this tag:
No comments:
Post a Comment