Tuesday 26 April 2016

Part 4: The ADC and SBC instuctions


Forward

In the previous section we extended our index html page to show more state of the emulator.

In this section we will implement the ADC and SBC instructions.

Before we go into implementation details I will cover Signed/unsigned numbers and then Twos complement.

Signed and unsigned integers


An 8-bit integer can be either signed or unsigned.

Unsigned integers can only represent positive numbers in the range 0 to 255.

Signed integers can represent both positive and negative numbers in the range -128 to 127. In this type the most significant bit is used to indicate the sign. If it is zero, the number is positive. If it is a one it is negative.

The negative numbers is represented in a form we call Two's complement which we will discuss in the next section.

Two's Complement


How do you the determine the Two's Complement of a number? Here is the algorithm:

  • Take the magnitude of the number and convert it to binary
  • Negate the binary number. This means changing each one to a zero and each zero to a one
  • Add one to the result
Let us take an example. Let's say you want to convert -5 to two's complement.

The magnitude of 5 in binary is 0000 0101.

If you negate this number you get 1111 1010 and adding one you get 1111 1011. This 251 in decimal.

Two's complement gives you the ability to subtraction with addition.

Let's take an example again. Say you want to calculate 5 - 3. You can rewrite this as 5 + (-3).

-3 you can change to two complement. This results to 1111 1101.

We can now add these two numbers together

0000 0101
1111 1101

(1)0000 0010

The end result is a nine digit binary number. This ninth bit is called the carry. The other eight bits have our result 0000 0010 which is 2.

Lets take an example with two negative numbers: -7 - 9. Again we can rewrite this as (-7) + (-9)

In this example we need to do a twos complement on both 7 and 9. This will yield 1111 1001 and 1111 0111. Lets add them together:

1111 1001
1111 0111

(1) 1111 0000

Again, we hot a carry. As we know the result should be a negative number so in this case the answer is also in twos complement form. To evaluate whether this answer is correct we need to do another twos complement conversion to change it to the positive equivalent. This yields 0001 0000 which is 16. So we know know this complement number represent -16 which is correct.

Finally, lets see what happens when we do a calculation that will exceed the signed number range of 127...-128. For this, we do -125 -4

1000 0011
1111 1100
(1) 0111 1111

The eighth bit of the result is 0, implying a positive number, which is incorrect. This condition is called overflow. The 6502 actually have a overflow flag to indicating such a condition.

Implementing ADC and SBC


Time to implement the ADC and SBC instructions in our emulator.

The most tricky part of implementing the ADC and SBC instruction is when how to determine when to set the overflow flag. As usual, Google is your friend :-)

The website http://www.righto.com/2012/12/the-6502-overflow-flag-explained.html gives you a small formula for determining if the overflow flag should b set:

(M^result)&(N^result)&0x80

Just to clarify the notation. ^ means XOR and & means AND.

Basically this formula says set the overflow flag if the signs of both the inputs differ from the sign of the result.

With this in mind we can start coding.

First lets get definition of ADC from Appendix A:

 
ADC  Add Memory to Accumulator with Carry

     A + M + C -> A, C                N Z C I D V
                                      + + + - - +

     addressing    assembler    opc  bytes  cyles
     --------------------------------------------
     immediate     ADC #oper     69    2     2
     zeropage      ADC oper      65    2     3
     zeropage,X    ADC oper,X    75    2     4
     absolute      ADC oper      6D    3     4
     absolute,X    ADC oper,X    7D    3     4*
     absolute,Y    ADC oper,Y    79    3     4*
     (indirect,X)  ADC (oper,X)  61    2     6
     (indirect),Y  ADC (oper),Y  71    2     5*
 

From this definition, we see that we should declare two new flags in our CPU: Carry and Overflow. So, here it goes:

 
    var carryflag =0;
    var overflowflag =0; 
 


Next, a function should be created for performing the addition:

    function ADC(operand1, operand2) {
      temp = operand1 + operand2 + carryflag;
      carryflag = ((temp & 0x100) == 0x100) ? 1 : 0;
      overflowflag = (((operand1^temp) & (operand2^temp) & 0x80) == 0x80) ? 1 : 0;
      temp = temp & 0xff;
      return temp;
    }
 

You will see when we do the addition, we are also adding the carry flag. This is part of the definition of ADC: A + M + C -> A, C

Before the result return we AND it with 0xff. This is to ensure a proper 8-bit value is returned with the carry bit stripped.

Next, we should implement this instruction within our Case statement:


 
/*ADC  Add Memory to Accumulator with Carry

     A + M + C -> A, C                N Z C I D V
                                      + + + - - +

     addressing    assembler    opc  bytes  cyles
     --------------------------------------------
     immediate     ADC #oper     69    2     2
     zeropage      ADC oper      65    2     3
     zeropage,X    ADC oper,X    75    2     4
     absolute      ADC oper      6D    3     4
     absolute,X    ADC oper,X    7D    3     4*
     absolute,Y    ADC oper,Y    79    3     4*
     (indirect,X)  ADC (oper,X)  61    2     6
     (indirect),Y  ADC (oper),Y  71    2     5* */

      case 0x69:
        acc = ADC (acc, arg1);
        zeroflag = (acc == 0) ? 1 : 0;
        negativeflag = ((acc & 0x80) != 0) ? 1 : 0;
      break;
      case 0x65:
      case 0x75:
      case 0x6D:
      case 0x7D:
      case 0x79:
      case 0x61:
      case 0x71:
        acc = ADC (acc, localMem.readMem(effectiveAdrress));
        zeroflag = (acc == 0) ? 1 : 0;
        negativeflag = ((acc & 0x80) != 0) ? 1 : 0;
      break;

Next, we need to implement the SBC instruction. First we write a method for SBC:


 
   function SBC(operand1, operand2) {
      operand2 = ~operand2 & 0xff;
      operand2 = operand2 + (1 - carryflag);
      temp = operand1 + operand2;
      carryflag = ((temp & 0x100) == 0x100) ? 1 : 0;
      overflowflag = (((operand1^temp) & (operand2^temp) & 0x80) == 0x80) ? 1 : 0;
      temp = temp & 0xff;
      return temp;
    }
 
The first two lines performs twos complement on operand2.

Finally, let us create a case statement for SBC:

/*SBC  Subtract Memory from Accumulator with Borrow

     A - M - C -> A                   N Z C I D V
                                      + + + - - +

     addressing    assembler    opc  bytes  cyles
     --------------------------------------------
     immediate     SBC #oper     E9    2     2
     zeropage      SBC oper      E5    2     3
     zeropage,X    SBC oper,X    F5    2     4
     absolute      SBC oper      ED    3     4
     absolute,X    SBC oper,X    FD    3     4*
     absolute,Y    SBC oper,Y    F9    3     4*
     (indirect,X)  SBC (oper,X)  E1    2     6
     (indirect),Y  SBC (oper),Y  F1    2     5*  */

      case 0xE9:
        acc = SBC (acc, arg1);
        zeroflag = (acc == 0) ? 1 : 0;
        negativeflag = ((acc & 0x80) != 0) ? 1 : 0;
      break;
      case 0xE5:
      case 0xF5:
      case 0xED:
      case 0xFD:
      case 0xF9:
      case 0xE1:
      case 0xF1:
        acc = SBC (acc, localMem.readMem(effectiveAdrress));
        zeroflag = (acc == 0) ? 1 : 0;
        negativeflag = ((acc & 0x80) != 0) ? 1 : 0;
      break;

Increment and Decrement Instructions

Instructions very related to ADC and SBC instructions are Increment and Decrement. I will there try to squeeze in a discussion on these instructions in this blog post.

Increment will increase the value of the accumulator or memory location by one.

Decrement decreases the value of the accumulator or memory location by one.

It is important to note that Increment and Decrement Instructions does not effect the Carry or Overflow flag.

Lets implement these instructions.

Firstly the INC instruction. This instruction increment the value of a specific memory location:

/*INC  Increment Memory by One

     M + 1 -> M                       N Z C I D V
                                      + + - - - -

     addressing    assembler    opc  bytes  cyles
     --------------------------------------------
     zeropage      INC oper      E6    2     5
     zeropage,X    INC oper,X    F6    2     6
     absolut      INC oper      EE    3     6
     absolute,X    INC oper,X    FE    3     7 */
 
      case 0xE6:
      case 0xF6:
      case 0xEE:
      case 0xFE:
        var tempVal = localMem.readMem(effectiveAdrress);
        tempVal++; tempVal = tempVal & 0xff;        
        localMem.writeMem(effectiveAdrress, tempVal);
        zeroflag = (tempVal == 0) ? 1 : 0;
        negativeflag = ((tempVal & 0x80) != 0) ? 1 : 0;
      break;

Next up, INX (Increment X register):

/*INX  Increment Index X by One

     X + 1 -> X                       N Z C I D V
                                      + + - - - -

     addressing    assembler    opc  bytes  cyles
     --------------------------------------------
     implied       INX           E8    1     2*/
 
      case 0xE8:

        x++; x = x & 0xff;        
        zeroflag = (x == 0) ? 1 : 0;
        negativeflag = ((x & 0x80) != 0) ? 1 : 0;
      break;

And, we do the same instruction, but for the Y register:

/*INY  Increment Index Y by One

     Y + 1 -> Y                       N Z C I D V
                                      + + - - - -

     addressing    assembler    opc  bytes  cyles
     --------------------------------------------
     implied       INY           C8    1     2*/
 
      case 0xC8:

        y++; y = y & 0xff;        
        zeroflag = (y == 0) ? 1 : 0;
        negativeflag = ((y & 0x80) != 0) ? 1 : 0;
      break;

Now it is time to do a similar exercise for the Decrement instructions:

First the DEC instruction:

/*DEC  Decrement Memory by One

     M - 1 -> M                       N Z C I D V
                                      + + - - - -

     addressing    assembler    opc  bytes  cyles
     --------------------------------------------
     zeropage      DEC oper      C6    2     5
     zeropage,X    DEC oper,X    D6    2     6
     absolute      DEC oper      CE    3     3
     absolute,X    DEC oper,X    DE    3     7 */
 
      case 0xC6:
      case 0xD6:
      case 0xCE:
      case 0xDE:
        var tempVal = localMem.readMem(effectiveAdrress);
        tempVal--; tempVal = tempVal & 0xff;        
        localMem.writeMem(effectiveAdrress, tempVal);
        zeroflag = (tempVal == 0) ? 1 : 0;
        negativeflag = ((tempVal & 0x80) != 0) ? 1 : 0;
      break;

Then, DEX (Decrement X register):

/*DEX  Decrement Index X by One

     X - 1 -> X                       N Z C I D V
                                      + + - - - -

     addressing    assembler    opc  bytes  cyles
     --------------------------------------------
     implied       DEC           CA    1     2*/
 
      case 0xCA:

        x--; x = x & 0xff;        
        zeroflag = (x == 0) ? 1 : 0;
        negativeflag = ((x & 0x80) != 0) ? 1 : 0;
      break;

And finally DEY (decrement Y register):

/*DEY  Decrement Index Y by One

     Y - 1 -> Y                       N Z C I D V
                                      + + - - - -

     addressing    assembler    opc  bytes  cyles
     --------------------------------------------
     implied       DEC           88    1     2*/
 
      case 0x88:

        y--; y = y & 0xff;        
        zeroflag = (y == 0) ? 1 : 0;
        negativeflag = ((y & 0x80) != 0) ? 1 : 0;
      break;

Testing

Let us end of this blog with a small test assembly program to test the functionality we have implemented:

Here is the test program:



LDA #$0a  a9 0a
ADC #0f   69 0f
LDA #$32  a9 32
ADC #$32  69 32
LDA #E7   a9 e7
ADC #$CE  69 ce
LDA #$88  a9 88
ADC #$EC  69 ec
LDA #$43  a9 43
ADC #$3C  69 3c

LDA #$0f  a9 0f
SBC #$0a  e9 0a
LDA #$0a  a9 0a
SBC #$0d  e9 0d
LDA #$78  a9 78
SBC #$F9  e9 f9
LDA #$88  a9 88
SBC #$F8  e9 f8
LDX #$FE  a2 fe
INX       e8
INX       e8
DEX       ca
DEX       ca
LDA #$FE  a9 fe
STA $64   85 64
INC $64   e6 64
INC $64   e6 64
DEC $64   c6 64
DEC $64   c6 64



This translates to the following sequence of bytes you need to add to the memory class:

0xa9, 0x0a, 0x69, 0x0f, 0xa9, 0x32, 0x69, 0x32, 0xa9, 0xe7, 0x69, 0xce, 0xa9, 0x88, 0x69, 0xec, 0xa9, 0x43, 0x69, 0x3c, 0xa9, 0x0f, 0xe9, 0x0a, 0xa9, 0x0a, 0xe9, 0x0d, 0xa9, 0x78, 0xe9, 0xf9, 0xa9, 0x88, 0xe9, 0xf8, 0xa2, 0xfe, 0xe8, 0xe8, 0xca, 0xca, 0xa9, 0xfe, 0x85, 0x64, 0xe6, 0x64, 0xe6, 0x64, 0xc6, 0x64, 0xc6, 0x64



While testing this program I actually picked up that the getDecodedStr() function doesn't return anything for implied mode instructions (like INX). I fixed up the code and fix will be available in the tag for this Blog Post.

In Summary

I this Blog Post I have covered the ADC and SBC instructions together with the related instructions Increment and Decrement.

In the next Blog I will be covering Comparisons and Branching.

As in the previous post I will provide the source code covered this post in a tag. The tag for this post is ch4.

Till Next time!

No comments:

Post a Comment