Saturday, June 9, 2018

Adding numbers on the 6502

Adding numbers is one of the more important parts of programming so an instruction to add numbers together makes sense. While we can do adding by incrementing many times, this is not very efficient.  Only the accumulator has an add instruction attached to it. The ADC command adds the contents of the accumulator with a value in memory with an extra 1 being added if the carry flag is set to allow for multi-byte numbers. Multi-byte number addition works by simply CLC for the low byte of the value then leaving the carry flag alone for latter bytes of the number as follows:

LDA NumberLowByte
CLC
ADC LowByteToBeAdded
STA NumberLowByte
LDA NumberHighByte
ADC HighByteToBeAdded
STA NumberHighByte
This is a surprising complex command as it not only has to deal with adding numbers, but must do the operation with both binary coded decimal numbers as well as normal binary numbers. As mentioned earlier, you set binary coded decimal using the SED command which enables working with binary coded decimal. Some variants of the 6502 processor, such as the one for the NES, do not support decimal mode but the 2600 does so we are going to have to implement BCD support.

The BCDtoBinary function breaks the provided byte into two nibbles then multiplies the high nibble by 10 to come up with the binary equivalent for the BCD number.

   private fun BCDtoBinary(bcdNum:Int):Int {
       val lowNibble = (bcdNum and 15) % 10
       val highNibble = ((bcdNum ushr 4) and 15) % 10
       return highNibble * 10 + lowNibble
   }

For converting a binary number back into a BCD number, we need to be concerned about numbers that are greater than 100. This is handled by setting the carry flag if the number is greater than 100. The high and low base 10 digits are worked out then converted into the appropriate nibbles.

   private fun binaryToBCD(bin:Int):Int {
       adjustFlag(CARRY_FLAG, bin > 99)
       val lowNibble = bin % 10
       val highNibble = (bin / 10) % 10
       return (highNibble shl 4) or lowNibble
   }
My method of dealing with BCD then is to see if the decimal flag is set and if it is, convert the number into binary, performing the add operation, then converting the binary value back into binary coded decimal.

The zero and negative flags are handled like usual but the ADC command also affects the carry and the overflow flag. Carry is used for unsigned numbers and is set when the number wraps meaning that the byte is too large to hold. The overflow flag is used for signed numbers and is used to indicate when a signed number has become too large. Remember that a signed number has the range -128 to 127 so 64+64 would cause an overflow as would -65+-64.

The overflow flag proved tricky to deal with until you realize that 127+-128 and -128+127 will be valid meaning the only time you will have an overflow is when the numbers being added are the same sign.

   private fun performAdd(first:Int, second:Int, ignoreBCD:Boolean = false):Int {
       if ( ( ! ignoreBCD ) and ((state.flags and DECIMAL_FLAG) == DECIMAL_FLAG) ) {
        return binaryToBCD(performAdd(BCDtoBinary(first), BCDtoBinary(second), true))
       }
       // if we reach here we are now in binary mode
       var addition = first + second
       if ((state.flags and CARRY_FLAG) == CARRY_FLAG)
        ++addition
       // set nev state of carry flag
       adjustFlag (CARRY_FLAG, addition > 255)
       // zero and negative flags
       addition = setNumberFlags(addition and 255)
       // overflow flag is a bit of work
       val firstNeg = (first and 128) == 128
       val secondNeg = (second and 128) == 128
       val resultNeg = (addition and 128) == 128
       if (firstNeg == secondNeg)
        adjustFlag (OVERFLOW_FLAG, secondNeg xor resultNeg)
       else
        adjustFlag (OVERFLOW_FLAG, false)
       return addition
   }
ADCADd Memory with accumulator with Carry
Address Mode
Decimal OPCode
Hexadecimal OpCode
Size
Cycles
#Immediate
105
$69
2
2
Zero Page
101
$65
2
3
Zero Page,X
117
$75
2
4
Absolute
109
$6D
3
4
Absolute,X
125
$7D
3
4-5
Absolute,Y
121
$79
3
4-5
(Indirect, X)
97
$61
2
6
(Indirect),Y
113
$71
2
5-6
Flags affected: CNVZ
Cycle Notes: Indirect and indexed modes take extra cycle if page boundaries crossed
Usage: Used to add numbers together. Memory value is added to value in accumulator. Important to remember that the value in the carry flag is added to the result so use CLC before using instruction.
Test Code:
; #immediate with overflow checked
CLD
LDA #64
CLC
ADC #64
BRK
;expect A=128, V=1, N=1 C=0, Z=0
; zero page using BCD
LDA #$10
SED
CLC
ADC 25
BRK
.ORG 25
.BYTE $15
;expect A=$25, N=0 C=0, Z=0
; Zero page,x with BCD and carry no carry result
LDA #$10
LDX #0
SED
SEC          ; force carry
ADC 25,X
BRK
.ORG 25
.BYTE $15
;expect A=$26, N=0 C=0, Z=0
; absolute with carry resulting in carry result and zero
CLD
LDA #128
SEC
ADC 512
BRK
.ORG 512  
.BYTE 128
;expect A=0 V=1, C=1, Z=1, N=0
; absolute,x overflow resulting in zero
CLD
LDX #2
LDA #128
CLC
ADC 512,X
BRK
.ORG 512  
.BYTE 0 0 128
; A=0, Z=1, C=1
; absolute,y normal add
CLD
LDY #0
LDA #50
CLC
ADC 512,Y
BRK
.ORG 512  
.BYTE 25
; A = 75, V=0, C=0
; (indirect, x) negative but normal add
CLD
LDX #1
LDA #255
CLC
ADC (25,X)
BRK
.ORG 26
.WORD 512
.ORG 512
.BYTE 254
; A=253 V=0 C=1
; (indirect),y negative + positive (carry but no overflow)
CLD
LDY #1
LDA #255
CLC
ADC (25),1
BRK
.ORG 25
.WORD 512
.ORG 512
.BYTE 0 127
; a=126 v=0 c=1
Implementation:
state.acc = performAdd(state.acc, mem.read(state.ip+1))
// zero page
state.acc = performAdd(mem.read(mem.read(state.ip+1)), state.acc)
// zero page,X
state.acc = performAdd(mem.read(mem.read(state.ip+1) + state.x), state.acc)
//absolute
state.acc = performAdd(mem.read(findAbsoluteAddress(state.ip)), state.acc)
//absolute,X
state.acc = performAdd(mem.read(findAbsoluteAddress(state.ip)+state.x), state.acc)
//absolute,Y
state.acc = performAdd(mem.read(findAbsoluteAddress(state.ip)+state.y), state.acc)
// (indirect, X)
state.acc = performAdd(mem.read(findAbsoluteAddress(((mem.read(state.ip+1)+state.x) and 255)-1)), state.acc)
// (indirect),Y

state.acc = performAdd(mem.read(findAbsoluteAddress(mem.read(state.ip+1) -1) + state.y), state.acc)