Saturday, May 26, 2018

6502 Basic Branching

My 6502 emulation portion of my 2600 emulator continues with branching this week.  Check previous posts for context.


When a computer must decide between two courses of action, it needs to either continue running code where it is or change the location of memory where it will start running the code at the new instruction pointer (IP) address.  This was given the term branching. There are many branching commands that the 6502 contains so it makes sense to have a special handing function for dealing with it. First, we need to know if the branch is going to be taken, this is the check which is simply the result of checking to see if the flag used for determining if we should branch is in the appropriate state for taking the branch. The offset parameter is simply the amount to adjust the instruction pointer.

    fun processBranch(check:Boolean, offset:Int) {
        if (check) {
            ++state.tick
            val adjOff = if (offset > 127) -( (offset xor 255)+1) else offset
            val target = state.ipNext + adjOff
            pageBoundsCheck(state.ipNext, target)
            state.ipNext = target
        }
    }

The zero flag happens to be handy to use for looping if you are counting down as it is set when you hit zero. It is also handy if you are incrementing a multiple byte number as you know when the byte has wrapped so you know it is time to increment the next byte. BNE takes the branch whenever the last zero-flag modifying command has a non-zero result while BEQ branches when the last result set the zero flag. Counting forward will also work if you are using a compare instruction which sets the zero flag if the comparison matches but the compare instructions won’t be implemented for a while yet.

The high order bit (128) is the negative bit and is used when you are dealing with signed numbers. We will be covering signed numbers next time when we get into the mathematical operations of the 6502 (adding and subtracting). If you are dealing with signed numbers or numbers less than 128 then this is handy as you know that if the negative flag is set then the result of the last action resulted in a value less than zero while if it is not set then the result is greater than or equal to zero. This is commonly used in conjunction with the zero flag checks to give you less than, equal, or greater than.

; operation being checked for here
BMI lessThan
BEQ equals
; code for greater than here

BMI is used to see if the negative bit is set, while BPL is used to see if the negative bit is clear.


BEQBranch if EQual to zero
Address Mode
Decimal OPCode
Hexadecimal OpCode
Size
Cycles
Relative
240
$F0
2
2-4
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: Used to see if the last operation resulted in a zero result which is a very common test in things such as while loops.
Test Code:
; BEQ test
LDY #1
LDX #1
BEQ done
INY        ; Make y 2 if reached which should be
DEX
BEQ done
DEY        ; Make y 1 (or 0) if reached which fails test!
done: BRK
; expect y = 2
Implementation:
processBranch(state.flags and ZERO_FLAG == ZERO_FLAG, mem.read(state.ip+1) )


BNEBranch if Not Equal to zero
Address Mode
Decimal OPCode
Hexadecimal OpCode
Size
Cycles
Relative
208
$D0
2
2-4
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: Used to see if last result was not zero. Often used for looping as by going from high value down to low value you can get the zero flag set for free saving a comparison instruction and the time and memory used for that instruction.
Test Code:
; BNE test 5 + 5 using iny and looping
     LDX #5
     LDY #5
add: INY
     DEX
     BNE add
     BRK
     ; expect y=10

Implementation:
processBranch(state.flags and ZERO_FLAG != ZERO_FLAG, mem.read(state.ip+1) )


BMIBranch on Minus (negative) result
Address Mode
Decimal OPCode
Hexadecimal OpCode
Size
Cycles
Relative
48
$30
2
2-4
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: Check to see if a number is negative (high order bit set). Useful for comparison operations as this gives you the equivalent of a register being less than a number (or alternatively if the number compared is greater than the register)
Test Code:
; BMI -> ABS(-16) the hard way
     LDX #$F0   ; two's complement value for -16
     LDY #0
add: INY
     INX
     BMI add         ; repeat while X is negative
     BRK
     ; expect y=16

Implementation:
processBranch(state.flags and NEGATIVE_FLAG == NEGATIVE_FLAG, mem.read(state.ip+1) )

 BPL   Branch on Result Plus
BPLBranch on result PLus (positive)
Address Mode
Decimal OPCode
Hexadecimal OpCode
Size
Cycles
Relative
16
$10
2
2-4
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: Checking to see if a number is 0 or positive (high order bit not set). Useful for comparison operations as this gives you the equivalent of the register being greater than or equal to the tested value (or the tested value being less than or equal to the register)
Test Code:
; BPL - Count to 28 the hard way
     LDX #100
     LDY #0
count: INY
     INX
     BPL count  ; will count from 100 to 127, quit when 128 hit
     BRK
     ; exect y=28

Implementation:
processBranch(state.flags and NEGATIVE_FLAG != NEGATIVE_FLAG, mem.read(state.ip+1) )

Saturday, May 12, 2018

Incrementing and Decrementing


My 6502 Emulation portion of my 2600 emulator continues with a look at increment and decrement operations.

Moving memory around is often very important, but right now we are doing it one byte at a time. The index operations allow for looping through blocks of memory but that requires the ability to create a loop. Before we can do that, we need to be able to increment or decrement the index registers. Since increasing or decreasing an index register by one is an exceedingly common operation, there are commands specifically for doing this. Strangely enough there is not a way of incrementing or decrementing the accumulator by one, though there is addition and subtraction commands which we will be covering later.

The INX and INY commands are used to increment the X or Y registers. When the register has the value of 255 incrementing it wraps the value making it become 0. DEX and DEY are used for decrementing the X or Y registers. When they are 0, decrementing wraps the register making them 255.

While there is no commands for incrementing or decrementing the accumulator, there is an INC and a DEC command that apply to memory at an indicated address. This is handy to have as it lets you modify a variable in memory without needing to load it into a register.

The most common use of increment and decrement operations, as mentioned above, is looping. This requires branching instructions at a minimum, so we will take a look at branching instructions next.

INCINCrement memory
Address Mode
Decimal OPCode
Hexadecimal OpCode
Size
Cycles
Zero Page
230
$E6
2
5
Zero Page, X
246
$F6
2
6
Absolute
238
$EE
3
6
Absolute,X
254
$FE
3
7
Flags affected: NZ
Usage: Incrementing a byte in memory
Test Code:
; INC, INX, INY tests
     LDX #$10
     LDY #255
     INC $50
     INC $41,X
     INC $110
     INC $101,X
     INX
     INY
     BRK
.ORG $50
.BYTE 1 2
.ORG $110
.BYTE 3 4
; expect M50=2, M51=3, M110=4, M111=5, X=17, Y=0, Z=1

Implementation:
// zero page
val addressToInc = mem.read(state.ip+1)
mem.write(addressToInc, setNumberFlags((mem.read(addressToInc) + 1) and 255))

// zero page,X
val addressToInc = mem.read(state.ip+1) + state.x
mem.write(addressToInc, setNumberFlags((mem.read(addressToInc) + 1) and 255))

// absolute
val addressToInc = findAbsoluteAddress(state.ip)
mem.write(addressToInc, setNumberFlags((mem.read(addressToInc) + 1) and 255))

// absolute,X
val addressToInc = findAbsoluteAddress(state.ip) + state.x
mem.write(addressToInc, setNumberFlags((mem.read(addressToInc) + 1) and 255))


INXINcrement X register
Address Mode
Decimal OPCode
Hexadecimal OpCode
Size
Cycles
Implied
232
$E8
1
2
Flags affected: NZ
Usage: Adding one to value in x register. Useful for looping.
Test Code:
; INC, INX, INY tests
     LDX #$10
     LDY #255
     INC $50
     INC $41,X
     INC $110
     INC $101,X
     INX
     INY
     BRK
.ORG $50
.BYTE 1 2
.ORG $110
.BYTE 3 4
; expect M50=2, M51=3, M110=4, M111=5, X=17, Y=0, Z=1

Implementation:
state.x = setNumberFlags((state.x+1) and 255)


INYINcrement Y register
Address Mode
Decimal OPCode
Hexadecimal OpCode
Size
Cycles
Implied
200
$C8
1
2
Flags affected: NZ
Cycle Notes: remove if none
Usage: Adding one to value in y register. Useful for looping.
Test Code:
; INC, INX, INY tests
     LDX #$10
     LDY #255
     INC $50
     INC $41,X
     INC $110
     INC $101,X
     INX
     INY
     BRK
.ORG $50
.BYTE 1 2
.ORG $110
.BYTE 3 4
; expect M50=2, M51=3, M110=4, M111=5, X=17, Y=0, Z=1

Implementation:
state.y = setNumberFlags((state.y+1) and 255)


DECDECrement memory
Address Mode
Decimal OPCode
Hexadecimal OpCode
Size
Cycles
Zero Page
198
$C6
2
5
Zero Page, X
214
$D6
2
6
Absolute
206
$CE
3
6
Absolute, X
222
$DE
3
7
Flags affected: NZ
Cycle Notes: remove if none
Usage: When you would use this command
Test Code:
; DEC, DEX, DEY tests
     LDX #$10
     LDY #0
     DEC $50
     DEC $41,X
     DEC $110
     DEC $101,X
     DEX
     DEY
     BRK
.ORG $50
.BYTE 1 2
.ORG $110
.BYTE 3 4
; expect M50=0, M51=1, M110=2, M111=3, X=15, Y=255, N=1

Implementation:
// zero page
val addressToDec = mem.read(state.ip+1)
mem.write(addressToDec, setNumberFlags((mem.read(addressToDec) - 1) and 255))

// zero page,X
val addressToDec = mem.read(state.ip+1) + state.x
mem.write(addressToDec, setNumberFlags((mem.read(addressToDec) - 1) and 255))

// absolute
val addressToDec = findAbsoluteAddress(state.ip)
mem.write(addressToDec, setNumberFlags((mem.read(addressToDec) - 1) and 255))

// absolute,X
val addressToDec = findAbsoluteAddress(state.ip) + state.x
mem.write(addressToDec, setNumberFlags((mem.read(addressToDec) - 1) and 255))



DEXDEcrement X register
Address Mode
Decimal OPCode
Hexadecimal OpCode
Size
Cycles
Implied
202
$CA
1
2
Flags affected: NZ
Usage: Decreasing X register by 1 is usually done for countdown loops
Test Code:
; DEC, DEX, DEY tests
     LDX #$10
     LDY #0
     DEC $50
     DEC $41,X
     DEC $110
     DEC $101,X
     DEX
     DEY
     BRK
.ORG $50
.BYTE 1 2
.ORG $110
.BYTE 3 4
; expect M50=0, M51=1, M110=2, M111=3, X=15, Y=255, N=1

Implementation:
state.x = setNumberFlags((state.x-1) and 255)

DEYDEcrement Y register
Address Mode
Decimal OPCode
Hexadecimal OpCode
Size
Cycles
Implied
136
$88
1
2
Flags affected: NZ
Usage: Decreasing Y register by 1 is usually done for countdown loops
Test Code:
; DEC, DEX, DEY tests
     LDX #$10
     LDY #0
     DEC $50
     DEC $41,X
     DEC $110
     DEC $101,X
     DEX
     DEY
     BRK
.ORG $50
.BYTE 1 2
.ORG $110
.BYTE 3 4
; expect M50=0, M51=1, M110=2, M111=3, X=15, Y=255, N=1

Implementation:
state.y = setNumberFlags((state.y-1) and 255)