Bill's Homebrew and Game Jam Blog
Saturday, August 4, 2018
Saturday, July 21, 2018
6502 Comparison operations
A comparison is simply a subtraction that doesn’t save the results but sets the status flags. It doesn’t set the overflow flag however so implementation is a bit harder than it should be. In anticipation of this instruction I wrote the subtract function to return the result instead of saving it so I could just use the subtraction function. Sadly I noticed the missing flag just when I was getting ready to implement this function so ended up writing a new version. This is probably for the best as the subtraction routine would add a lot of inefficiency to the process so this will work a lot faster.
private fun performCompare(first:Int, second:Int) {
val sub = first  second
setNumberFlags(sub)
adjustFlag(CARRY_FLAG, (state.flags and NEGATIVE_FLAG) == 0 )
}
The one reason why you might want to use the proper subtraction method would be in the case of binary coded decimal (BCD) numbers, but when you think about it for a minute it becomes obvious that the flag results would be the same for BCD numbers the only thing that would be different would be the result of the subtraction and as that is discarded anyway this much faster function is ideal for our needs.
The CMP instruction compares the contents of the accumulator with the indicated memory by subtracting the memory from the accumulator while leaving the contents of the accumulator alone. This means that if the accumulator is equal to the memory the zero flag will be set. In cases where the accumulator is greater than the memory the carry flag will be set while the negative flag will be clear. In cases where the accumulator is less than the memory then the carry flag will be clear while the negative flag will be set.
CMP ValueToCompare
BMI lessThan
BEQ equals
; code for greater than here
There are also versions of compare for the x register and the y register, which are CPX and CPY. These work the same as CMP but using their respective register instead of the accumulator. This is good as it allows more control over looping, for instance, here is a routine for copying 20 bytes from Source to Dest.
LDX #0
CopyLoop:
LDA Source,X
STA Dest,X
CPX #20
BMI CopyLoop
CMP – CoMPare accumulator with memory
Address Mode

Decimal OPCode

Hexadecimal OpCode

Size

Cycles

#Immediate

201

$C9

2

2

Zero Page

197

$C5

2

3

Zero Page,X

213

$D5

2

4

Absolute

205

$CD

3

4

Absolute,X

221

$DD

3

45

Absolute,Y

217

$D9

3

45

(Indirect, X)

193

$C1

2

6

(Indirect),Y

209

$D1

2

56

Flags affected: CNZ
Cycle Notes: Indirect and indexed modes take extra cycle if page boundaries crossed
Usage: Compare accumulator with memory setting flags as if memory was subtracted from accumulator. Z set if equals. C set if accumulator greater than or equal to memory. N set if accumulator is less than memory.
Test Code:
; CMP Immediate
CLD
LDA #0
TAX
loop: INX
CLC
ADC #2
CMP #20
BNE loop
BRK
; expect x=10,a=20,z=1
; CMP Zero Page
CLD
LDA #0
TAX
loop: INX
CLC
ADC #2
CMP 200
BMI loop
BRK
.ORG 200
.BYTE 20
; expect x=10,a=20,z=1
; CMP Zero Page,X
CLD
LDA #0
TAY
LDX #10
loop: INY
CLC
ADC #2
CMP 190,X
BCC loop
BRK
.ORG 200
.BYTE 20
; expect x=10,a=20,z=1
; CMP Absolute
CLD
LDA #0
TAX
loop: INX
CLC
ADC #2
CMP 512
BMI loop
BRK
.ORG 512
.BYTE 20
; expect x=10,a=20,z=1
; CMP Absolute,X find 4 index
LDA #4
LDX #255
loop: INX
CMP 512,X
BNE loop
BRK
.ORG 512
.BYTE 1 2 3 4
; expect A=4, X=3, Z=1, N=0, C=1
; CMP Absolute,Y
LDA #5
LDY #255
loop: INY
CMP 512,Y
BNE loop
BRK
.ORG 512
.BYTE 1 2 3 4 5
; expect A=5, X=4, Z=1, N=0, C=1
; CMP (Indirect,X)
LDA #69
LDX #2
LDY #0
CMP (200,X)
BEQ done
LDY 512
done: BRK
.ORG 200
.WORD 0 512
.ORG 512
.BYTE 42
; Expect A=69,Y=42
; CMP (Indirect),Y
LDA #3
LDY #255
loop: INY
CMP (200),Y
BNE loop
BRK
.ORG 200
.WORD 512
.ORG 512
.BYTE 1 2 3 4 5
; expect A=3, X=2, Z=1, N=0, C=1
Implementation:
// #immediate
performCompare(state.acc, mem.read(state.ip+1))
// zero page
performCompare(state.acc, mem.read(mem.read(state.ip+1)))
// zero page,X
performCompare(state.acc, mem.read(mem.read(state.ip+1) + state.x))
//absolute
performCompare(state.acc, mem.read(findAbsoluteAddress(state.ip)))
//absolute,X
performCompare(state.acc, mem.read(findAbsoluteAddress(state.ip)+state.x))
//absolute,Y
performCompare(state.acc, mem.read(findAbsoluteAddress(state.ip)+state.y))
// (indirect, X)
performCompare(state.acc, mem.read(findAbsoluteAddress(((mem.read(state.ip+1)+state.x) and 255)1)))
// (indirect),Y
performCompare(state.acc, mem.read(findAbsoluteAddress(mem.read(state.ip+1) 1) + state.y))
CPX Compare Memory and Index X
CPX – ComPare X register with memory
Address Mode

Decimal OPCode

Hexadecimal OpCode

Size

Cycles

#Immediate

224

$E0

2

2

Zero Page

228

$E4

2

3

Absolute

236

$EC

3

4

Flags affected: CNZ
Usage: Compare X Register with memory setting flags as if memory was subtracted from X. Z set if equals. C set if X greater than or equal to memory. N set if X is less than memory.
Test Code:
; CPX Immediate  compute 3x20 the hard way
CLD
LDA #0
TAX
loop: INX
CLC
ADC #3
CPX #20
BNE loop
BRK
; expect x=20,a=60,z=1
; CPX Zero Page  compute 5x5 the hard way
CLD
LDA #0
TAX
loop: INX
CLC
ADC #5
CPX 200
BCC loop
BRK
.ORG 200
.BYTE 5
; expect x=5,a=25,c=1
; CPX Absolute  Compute 6x7 the hard way
CLD
LDA #0
TAX
loop: INX
CLC
ADC #7
CPX 520
BMI loop
BRK
.ORG 520
.BYTE 6
; expect x=6,a=42,n=0
Implementation:
// #immediate
performCompare(state.x, mem.read(state.ip+1))
// zero page
performCompare(state.x, mem.read(mem.read(state.ip+1)))
//absolute
performCompare(state.x, mem.read(findAbsoluteAddress(state.ip)))
CPY – ComPare Y register with memory
Address Mode

Decimal OPCode

Hexadecimal OpCode

Size

Cycles

#Immediate

192

$C0

2

2

Zero Page

196

$C4

2

3

Absolute

204

$CC

3

4

Flags affected: CNZ
Usage: Compare Y Register with memory setting flags as if memory was subtracted from Y. Z set if equals. C set if Y greater than or equal to memory. N set if Y is less than memory.
Test Code:
; CPY Immediate  compute 3x20 the hard way
CLD
LDA #0
TAY
loop: INY
CLC
ADC #3
CPY #20
BNE loop
BRK
; expect y=20,a=60,z=1
; CPY Zero Page  compute 5x5 the hard way
CLD
LDA #0
TAY
loop: INY
CLC
ADC #5
CPY 200
BCC loop
BRK
.ORG 200
.BYTE 5
; expect Y=5,a=25,c=1
; CPY Absolute  Compute 6x7 the hard way
CLD
LDA #0
TAY
loop: INY
CLC
ADC #7
CPY 520
BMI loop
BRK
.ORG 520
.BYTE 6
; expect y=6,a=42,n=0
Implementation:
// #immediate
performCompare(state.y, mem.read(state.ip+1))
// zero page
performCompare(state.y, mem.read(mem.read(state.ip+1)))
//absolute
performCompare(state.y, mem.read(findAbsoluteAddress(state.ip)))
Saturday, July 7, 2018
Math Branching
The last couple of post seen the ADC and the SBC instructions added to the 6502 emulator. We now have some instructions that cause the carry and overflow flags to be set. For multibyte numbers the use of the carry flag is built into the ADC and SBC instructions and will be used automatically without need for special branching logic. The carry flag is necessary for dealing with adding or subtracting a byte from a multibyte value. In this case you can write the code like this:
LDA NumberLowByte
CLC
ADC ByteToAdd
STA NumberLowByte
BCC noOverflow
INC NumberHighByte
noOverflow: ; additional code here
Probably the most important use for the carry flag branching functions BCC amd BCS is for handling overflow errors when using unsigned numbers. When a number gets too big to be contained in the byte it sets the carry flag and wraps around. In cases where this is the only byte or the last byte in a multibyte number then there is a very good possibility that something bad has just happened. If this is a possibility in your code then you should be using BCC or BCS to handle potential errors.
Signed numbers overflow in a rather different way, which is why we have the overflow flag in addition to the carry flag. If you are using a signed byte then 64 + 64 results in 128 which is not what you want. The overflow flag solves this by being set when the sign of a number changes in cases where it shouldn’t be changing such as adding two positive numbers together or adding two negative numbers together. The BVC and BVS commands are used to detect this problem and handle the error from this.
When dealing with multibyte signed numbers, you need to be concerned with the overflow flag only for the highest byte of the number.
BCC – Branch on Carry Clear
Address Mode

Decimal OPCode

Hexadecimal OpCode

Size

Cycles

Relative

144

$90

2

24

Flags affected: None
Cycle Notes: If branching will take an additional cycle (3). If branch crosses page boundaries then an additional cycle will be added on top of the cycle for branching (4).
Usage: Checks to make sure last math operation has not resulted in a carry (ADC) or has borrowed (SBC). A carry is an indication that the bits in the byte have wrapped so this instruction will branch if the byte has not wrapped.
Test Code:
; BCC test by counting times can add 10 before wrapping
CLD
LDA #0
TAX
count: INX
CLC
ADC #10
BCC count
BRK
; expect A = 6, X=26, C=1
Implementation:
processBranch(state.flags and CARRY_FLAG != CARRY_FLAG, mem.read(state.ip+1) )
BCS – Branch on Carry Set
Address Mode

Decimal OPCode

Hexadecimal OpCode

Size

Cycles

Relative

176

$B0

2

24

Flags affected: None
Cycle Notes: If branching will take an additional cycle (3). If branch crosses page boundaries then an additional cycle will be added on top of the cycle for branching (4).
Usage: Checks to make sure last math operation has not resulted in a carry (ADC) or has borrowed (SBC). A carry is an indication that the bits in the byte have wrapped so with this command the branch will be taken if the byte has wrapped.
Test Code:
; BCS count how many times can subtract 10 before wrapping
CLD
LDA #255
LDX #0
count: INX
SEC
SBC #10
BCS count
BRK;
; expect A 251, X26, C=0
Implementation:
processBranch(state.flags and CARRY_FLAG == CARRY_FLAG, mem.read(state.ip+1) )
BVC Branch on Overflow Clear
BVC – Branch on oVerflow Clear
Address Mode

Decimal OPCode

Hexadecimal OpCode

Size

Cycles

Relative

80

$50

2

24

Flags affected: None
Cycle Notes: If branching will take an additional cycle (3). If branch crosses page boundaries then an additional cycle will be added on top of the cycle for branching (4).
Usage: Checks to make sure the last math operation didn’t cause an overflow. This is for signed numbers when a number changes its sign when it shouldn’t due to too large of numbers being used.
Test Code:
; BVC count how many times can add 10 until become negative
CLD
LDA #0
TAX
count: INX
CLC
ADC #10
BVC count
BRK;
; expect A 130, X13, C=0, V=1
Implementation:
processBranch(state.flags and OVERFLOW_FLAG != OVERFLOW_FLAG, mem.read(state.ip+1) )
BVS Branch on Overflow Set
BVS – Branch on oVerflow Set
Address Mode

Decimal OPCode

Hexadecimal OpCode

Size

Cycles

Relative

112

$70

2

24

Flags affected: None
Cycle Notes: If branching will take an additional cycle (3). If branch crosses page boundaries then an additional cycle will be added on top of the cycle for branching (4).
Usage: Checks if last math operation caused an overflow. This is for signed numbers when a number changes its sign when it shouldn’t due to too large of numbers being used.
Test Code:
; BVS test
CLD
LDA #64
LDX #0
CLC
ADC #32
BVS done
INX
CLC
ADC #32
BVS done
INX
done: BRK
; expect A=128, X=1, V=1
Implementation:
processBranch(state.flags and OVERFLOW_FLAG == OVERFLOW_FLAG, mem.read(state.ip+1) )
Subscribe to:
Posts (Atom)