Sunday 1 May 2016

Part 5 : Comparisons and Branching

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.

2 comments:

  1. 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.

    Thanks so much for the effort you've put into this, I look forward to getting to sprites / hires modes, etc.

    ReplyDelete
    Replies
    1. Hi spud

      Glad to hear you are enjoying the series and finding it useful. If you have any questions, feel free to ask.

      Delete