Saturday, June 23, 2018

Subtracting Numbers on the 6502

Last fortnight I introduced the ADC command. The SBC command used for subtracting numbers is one of the harder commands for newcomers to learn to use as it is the opposite of the ADC command. When you start to subtract you set the carry flag instead of clearing the carry flag. The carry flag represents a borrow so at the end of the instruction the number has borrowed if it is clear. While this seems confusing, when you start looking at it from a binary perspective these strange requirements make sense.

There are a variety of ways for handling signed numbers but today most systems use twos complement. This system takes advantage of the fact that registers naturally wrap  when 0 is reduced by one. If we were using 4 bit numbers 0000 would become 1111 so that is -1. If we continue subtracting we work down until we get 1000 which represents -8. This is the reason that negative numbers have a one greater range than positive numbers. Converting a number between positive and negative or vise-versa is performed by simply inverting all the bits then adding 1 as can be seen here:

1111 -> 0000 +1 -> 0001
0001 -> 1110 +1 -> 1111

Adding is a very complicated circuit. When you think about it subtraction is just a matter of flipping the sign and adding. In two’s complement, flipping the sign requires inverting bits (easy) then adding one. With the ADC command, the carry flag will add one to the number if it is set. This means that  if set the carry flag before performing a subtraction then have the subtraction command simply invert the bits and call the ADC command you will properly handle the two’s complement conversion. This means that all that needs to be done to handle the SBC command is to invert the bits and call the performAdd method.

   private fun performSubtract(first:Int, second:Int, ignoreBCD: Boolean = false):Int {
       if ( ( ! ignoreBCD ) and ((state.flags and DECIMAL_FLAG) == DECIMAL_FLAG) ) {
        return binaryToBCD(performSubtract(BCDtoBinary(first), BCDtoBinary(second), true))
       }
       return performAdd(first, 255 xor second, true)
   }
I was hoping to also use the above command for the  compare instructions as they essentially just perform a subtraction but don’t keep the results. Unfortunately, when I got around to doing the comparison instructions I discovered that they do not alter the overflow flags so I ended up writing a different handler for comparison.
 
SBCSuBtract memory from accumulator with Carry (borrow)
Address Mode
Decimal OPCode
Hexadecimal OpCode
Size
Cycles
#Immediate
233
$E9
2
2
Zero Page
229
$E5
2
3
Zero Page,X
245
$F5
2
4
Absolute
237
$ED
3
4
Absolute,X
253
$FD
3
4-5
Absolute,Y
249
$F9
3
4-5
(Indirect, X)
225
$E1
2
6
(Indirect),Y
241
$F1
2
5-6
Flags affected: CNVZ
Cycle Notes: Indirect and indexed modes take extra cycle if page boundaries crossed
Usage: Used to subtract numbers. Memory value is subtracted from value in accumulator. Important to remember that you SEC instead of CLC with this instruction.
Test Code:
; SBC immediate with overflow
CLD
LDA #64
SEC
SBC #191
BRK
; Expect A=129, V=1, N=1, C=0, Z=0
; SBC zero page using BCD
SED
LDA #$15
SEC
SBC 25
BRK
.ORG 25
.BYTE $10
; expect A=$05, N=0, C=1, Z=0
;SVC Zero page,x with BCD and underflow
SED
LDA #$10
LDX #0
SEC
SBC 25,X
BRK
.ORG 25
.BYTE $95
; expect A=$15, N=0, C=0, Z=0
; sbc bsolute with no carry res zero
CLD
LDA #64
CLC
SBC 512
BRK
.ORG 512
.BYTE 63
; expect A=0, V=0, N=0, C=1,Z=1
; absolute,x overflow (-128-1 = 127?)
CLD
LDX #2
LDA #128
SEC
SBC 512,X
BRK
.ORG 512
.BYTE 0 0 1
; A=127, V= 1, N= 0, C=1, Z=0
; sbc absolute,y normal
CLD
LDY #0
LDA #50
SEC
SBC 512,Y
BRK
.ORG 512
.BYTE 25
; A=25, V=0, N=0, C=1, Z= 0
; sbc (indirect, x) negative but normal
CLD
LDX #1
LDA #254
SEC
SBC (25,X)
BRK
.ORG 26
.WORD 512
.ORG 512
.BYTE 255
; A=255, V=0, N=1, C=0, Z=0
; sbc (indirect),y negative - positive
   CLD
LDY #1
LDA #255
SEC
SBC (25),Y
BRK
.ORG 25
.WORD 512
.ORG 512
.BYTE 0 2
   ; A=253, V=0, N=1, C=1, Z=0
Implementation:
state.acc = performSubtract(state.acc, mem.read(state.ip+1))
// zero page
state.acc = performSubtract(state.acc, mem.read(mem.read(state.ip+1)))
// zero page,X
state.acc = performSubtract(state.acc, mem.read(mem.read(state.ip+1) + state.x))
//absolute
state.acc = performSubtract(state.acc, mem.read(findAbsoluteAddress(state.ip)))
//absolute,X
state.acc = performSubtract(state.acc, mem.read(findAbsoluteAddress(state.ip)+state.x))
//absolute,Y
state.acc = performSubtract(state.acc, mem.read(findAbsoluteAddress(state.ip)+state.y))
// (indirect, X)
state.acc = performSubtract(state.acc, mem.read(findAbsoluteAddress(((mem.read(state.ip+1)+state.x) and 255)-1)))
// (indirect),Y
state.acc = performSubtract(state.acc, mem.read(findAbsoluteAddress(mem.read(state.ip+1) -1) + state.y))