Monday, 10 February 2025

A Commodore 64 Emulator in Flutter: Part 7

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.

The rest of this method is pretty straight forward. Bit 7 of the result is the negative flag, Bit 8 is the carry flag. Also we use the lower 8 bits to check if the result is zero. 

With this method implemented, we can now implement the individual compare opcodes. Lets start with the CMP instructions:

/*
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.

Lets do the same with CPX and CPY:

/*
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.

This conclude all the compare instructions.

Branching Instructions

Let us now look at the branching instructions. Every branching instruction branch depending on the state of a certain flag, whether it is the Carry flag, Zero Flag, Negative flag and so on.

With a branch instruction we don't supply an absolute address to jump to if the branch condition is true, but a relative address that you need to add to the program register to find the destination address.

Let us write a quick 6502 machine code program to understand the branch instructions better:

4000 A9 05 LDA #$5
4002 38    SEC
4003 E9 01 SBC #$1
4005 D0 FC BNE $FC
4007 A9 22 LDA #$22
Here we have a program where we basically have a loop where the Accumulator starts with value 5, and gets decremented till it reaches zero.

The controller of the loop is at address 4005, the BNE (Branch if not equal) instruction, that will keep branching back to the SBC instruction at address 4003 until the zero flag is set.

Now the paramter of the BNE instruction might look confusing, but in actual fact, it is a 8 bit two's complement number you need to add to the program counter if the branch is to be taken. This means you can jump in the range -128...127.

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.

With all this said, let us see if we can emulate the branch instruction in Flutter. All in all this boils down to adding a byte value to a 16-bit value, with a twist: The byte value is signed! This is a bit tricky to emulate on most platforms, because if you add a byte value to a 16-bit value, the value will always go up, and not down if it is negative.

Lets look at a couple of ways to solve this. Off the bat, one would probably do something like this in Flutter:

    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.

This is indeed a solution, but can't we make it more elegant? Lets look a bit what the famous C64 emulator, VICE do with this:


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.

However, that nifty trick is in C and not in Flutter in which we develop this emulator. So, the question is: Is there a similar nifty trick we can use in Flutter for this? Indeed there is. In Flutter for every int, there is a toSigned() method you can call. As parameter, you pass it the number of bits your number is wide, assuming the last most significant bit is the sign bit. So, if you do something like the following:

0xfe.toSigned(8)
You will get back -2. 

We now have enough info to calculate an address for the relative address mode in the method calculateEffectiveAddress:

 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

Let us end this post where we a write a quick test program doing a compare and branch.

At this point of writing the test program, it really start to be become convenient to have the DEX and DEY commands, which we don't have implemented at the moment. So, I took the liberty to implement them in the emulator. I will not show the implementation here, but you are welcome to look at that on my github page.

So, here is the test program:

4000 A2 0A LDX #$0A
4002 CA    DEX
4003 E0 04 CPX #$04
4005 D0 FB BNE LOOP
4007 A9 15 LDA #$15
This 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:


In summary

In this post we implemented the brnach and compare instructions.

In the next post we will be implementing stack operations in our emulator.

Until next time!

No comments:

Post a Comment