Wednesday, April 25, 2018

Storing and Transferring Between Registers


To my surprise, storing registers was a lot easier to implement then loading. Partially this was due to the simple fact that I was able to take advantage of the existing findAbsoluteAddress function, but the larger reason is that none of the storing commands have to worry about setting flags and the clock cycles for these commands are consistent.

The three commands for storing registers to memory are STA, STX, and STY. They simply take the value that was in the respective register and stores it in the indicated address which is determined by the address mode used.

The registers are fast since they are on the CPU. Older processors ran at similar speeds to their memory so the cost of memory access was significantly less back then. Today accessing memory that was not in the cash incurs cost of hundreds of cycles. Back then the cost of accessing memory was only a few cycles. Still, there is a slight time advantage of transferring data between memory in all but immediate mode and the register transfer instructions are all a single byte saving memory over the two byte immediate instructions.

Unlike storing from registers to memory, flags are affected by transferring between registers. As there are several instructions that need to determine if the number transferred is zero or negative and set the Z and N flags appropriately, a simple function handles this. For convenience, I have the function return the value being tested to allow it to be chained.

    fun setNumberFlags(num:Int):Int {
        adjustFlag (ZERO_FLAG, num == 0)
        adjustFlag (NEGATIVE_FLAG, (num and 128) > 0)
        return num
    }

There are commands for copying the contents of the accumulator to the x and the y registers (TAX and TAY) as well as from the x register or the y register to the accumulator (TXA, TYA). There is no instruction for transferring between the x and y registers.

Two remaining transfer instructions exist, which are tied to manipulating the stack. We will cover those as well as explain the concept behind the stack next time.

STASTore Accumulator
Address Mode
Decimal OPCode
Hexadecimal OpCode
Size
Cycles
Zero Page
133
$85
2
3
Zero Page,X
149
$95
2
4
Absolute
128
$80
3
4
Absolute,X
144
$90
3
5
Absolute,Y
153
$99
3
5
(Indirect,X)
129
$81
2
6
(Indirect),Y
145
$91
2
6
Flags affected: None
Usage: Stores the contents of the accumulator to memory
Test Code:
; STA tests (all seven modes)
     LDA #123
     LDX #$10
     LDY #5
     STA $AB    ; zero page
     STA $AB,X
     STA $250
     STA $250,X
     STA $250,Y
     STA ($50,X)
     STA ($60),Y
.ORG $60
.WORD $600
;MAB, MBB, M250, M260, M255, M600, M605 = 123

Implementation:
// zero page
mem.write(mem.read(state.ip+1), state.acc)
// zero page,X
mem.write(mem.read(state.ip+1) + state.x, state.acc)
//absolute
m.mem.write(findAbsoluteAddress(m.state.ip), m.state.acc)
//absolute,X
m.mem.write(findAbsoluteAddress(m.state.ip)+m.state.x, m.state.acc)
//absolute,Y
m.mem.write(findAbsoluteAddress(m.state.ip)+m.state.y, m.state.acc)
// (indirect, X)
mem.write(findAbsoluteAddress(((mem.read(state.ip+1)+state.x) and 255)-1), state.acc)
// (indirect),Y
mem.write(findAbsoluteAddress(mem.read(state.ip+1) -1) + state.y, state.acc)

       
STXSTore X register
Address Mode
Decimal OPCode
Hexadecimal OpCode
Size
Cycles
Zero Page
134
$86
2
3
Zero Page,Y
150
$96
2
4
Absolute
142
$8E
3
4
Flags affected: None
Usage: Stores contents of X register to memory
Test Code:
     LDX #22
     LDY #5
     STX $50    ;  Zero Page
     STX $50,Y  ;  Zero Page,Y
     STX $250   ;  Absolute
     BRK
;M50, M55, M250 = 22

Implementation:
// zero page
mem.write(mem.read(state.ip+1), state.x)
// zero page,Y
mem.write(mem.read(state.ip+1) + state.y, state.x)
//absolute
mem.write(findAbsoluteAddress(state.ip), state.x)



STY  STore Y Register
Address Mode
Decimal OPCode
Hexadecimal OpCode
Size
Cycles
Zero Page
132
$84
2
3
Zero Page,X
148
$94
2
4
Absolute
140
$8C
3
4
Flags affected: None
Usage: Stores the contents of the Y Register to memory
Test Code:
     LDX #5
     LDY #33
     STY $50    ;  Zero Page
     STY $50,X  ;  Zero Page,X
     STY $250   ;  Absolute
     BRK
;M50, M55, M250 = 33

Implementation:
// zero page
mem.write(mem.read(state.ip+1), state.y)
// zero page,X
mem.write(mem.read(state.ip+1) + state.x, state.y)
//absolute
mem.write(findAbsoluteAddress(state.ip), state.y)



TAXTransfer Accumulator to X register
Address Mode
Decimal OPCode
Hexadecimal OpCode
Size
Cycles
Implied
170
$AA
1
2
Flags affected: NZ
Usage: Moves value in accumulator to X. Fast and small often used as part of restoring processor state.
Test Code:
; TYA TAX test
     LDY #24
     TYA
     TAX
     BRK
; Acc, X=24, N=0, Z=0

Implementation:
state.x = setNumberFlags(state.acc)


TAYTransfer Accumulator to Y
Address Mode
Decimal OPCode
Hexadecimal OpCode
Size
Cycles
Implied
168
$A8
1
2
Flags affected: NZ
Usage: Moves value in accumulator to Y. Fast and small often used as part of restoring processor state.
Test Code:
; TXA TAY test
     LDX #179
     TXA
     TAY
     BRK
; Acc, Y=79, N=1, Z=0

Implementation:
state.y = setNumberFlags(state.acc)
Place implementation code here


TXATransfer X register to Accumulator
Address Mode
Decimal OPCode
Hexadecimal OpCode
Size
Cycles
Implied
138
$8A
1
2
Flags affected: NZ
Usage: Moves value in X to the accumulator. Fast and small often used as part of saving processor state.
Test Code:
; TXA TAY test
     LDX #179
     TXA
     TAY
     BRK
; Acc, Y=79, N=1, Z=0

Implementation:
state.acc = setNumberFlags(state.x)



TYATransfer Y register to Accumulator
Address Mode
Decimal OPCode
Hexadecimal OpCode
Size
Cycles
Implied
152
$98
1
2
Flags affected: NZ
Usage: Moves value in Y to accumulator. Fast and small often used as part of saving processor state.
Test Code:
; TYA TAX test
     LDY #24
     TYA
     TAX
     BRK
; Acc, X=24, N=0, Z=0

Implementation:
state.acc = setNumberFlags(state.y)


Wednesday, April 18, 2018

Loading stuff into registers


The bulk of the work that a processor does is done through registers. The typical pattern is that you load a value into a register, manipulate it in some way, then store the data back to memory, possibly in a different location. As was discussed in a much earlier article, the LDA, LDX, and LDY instructions for loading memory do so with a variety of address modes. These address modes determine which address is to be accessed. Zero page is easy to deal with as the next byte is the address that is being addressed. The zero page index modes likewise use the next byte as the address but then add the values of the x or y register to this value to come up with the final address.

Absolute addressing does need to manipulate two bytes to determine the address and is very commonly used so there is a utility function that uses the address of the instruction and takes the two bytes after the instruction to come up with the address. Absolute index modes also use this function but then add the values of the x or y register. This function can be used for non-instructions by having the address be one less than the desired first byte of the address, which is a trick that I will be using.

    private fun findAbsoluteAddress(address:Int):Int {
        return (mem.read(address+2) * 256 + mem.read(address+1))
    }

As the load instructions and many other instructions we will be implementing in the future need to set flags, I wrote a quick utility function for handling the setting of a flag bit to a Boolean value. Based on whether the bit is being set or cleared the method will use the appropriate Boolean operation to perform the bit setting or clearing. Setting is a simple or function while clearing requires creation of an inverse mask which then gets ANDed with the flag byte.

    private fun adjustFlag(flag:Int, isSet:Boolean) {
        if (isSet)
            state.flags = state.flags or flag
        else
            state.flags = state.flags and (255 xor flag)
    }

There is also some timing issues that we need to deal with when a page boundary is crossed with the indirect addressing functions. Timing is very important to an emulator, especially one for the 2600 as each scan line has 76 cycles for manipulating the TIA chip so precice processor timing is a necessity to properly emulate the machine.

    private fun pageBoundsCheck(baseAddress:Int, targetAddress:Int) {
        val basePage =  baseAddress / 256
        val actualPage = targetAddress / 256
        if (basePage != actualPage)
            ++state.tick
    }

Which leaves us with a general function for loading a byte from the specified address with the indicated offset. This function is called by the LDA, LDX, and LDY methods setting the parameters to the appropriate base address which was determined by the address mode. If the address mode is one that has an offset value then that offset is included. Finally, for address modes that incur extra cycles on page bound violations, we include a flag to indicate that this needs to be taken into account.

    fun loadByteFromAddress(address:Int, offset:Int = 0, checkBounds:Boolean = false):Int {
        val result:Int = mem.read(address + offset)
        adjustFlag (ZERO_FLAG, result == 0)
        adjustFlag (NEGATIVE_FLAG, (result and 128) > 0)
        if (checkBounds)
            pageBoundsCheck(address, address+offset)

        return result
    }

Here are the LDA, LDX, and LDY instructions that were implemented.

LDALoaD Accumulator
Address Mode
Decimal OPCode
Hexadecimal OpCode
Size
Cycles
#Immediate
169
$A9
2
2
Zero Page
165
$A5
2
3
Zero Page,X
181
$B5
2
4
Absolute
173
$AD
3
4
Absolute,X
189
$BD
3
4-5
Absolute,Y
185
$B9
3
4-5
(Indirect, X)
161
$A1
2
6
(Indirect),Y
177
$B1
2
5-6
Flags affected: NZ
Cycle Notes: Indirect modes take extra cycle if page boundaries crossed
Usage: Loads the accumulator with a value stored in memory. Used for manipulating memory, especially if math or Boolean operations need to be performed on that memory.
Test Code:
; LDA Immediate - zero test
     LDA #0
     BRK
     ; expect a=0; z=1; n=0
; LDA (Indirect,X)
     LDX #200
     LDA (54,X)
     BRK
.ORG 254
.WORD 1024
.ORG 1024
.BYTE 88
     ; expect A=88
    
    
; LDA (Indirect),Y
     LDY #10
     LDA (254),Y
     BRK
.ORG 254
.WORD 1024
.ORG 1034
.BYTE 88
     ; expect A=88

Implementation:
// immediate mode
m.state.acc = m.loadByteFromAddress(m.state.ip, 1)
// (indirect,x)
m.state.acc = m.loadByteFromAddress(m.findAbsoluteAddress(
           // need to deduct 1 from loadByteFromAddress method as it is designed to skip opcode
           ((m.loadByteFromAddress(m.state.ip, 1)+m.state.x) and 255)-1))
// (indirect),y
m.state.acc = m.loadByteFromAddress(m.findAbsoluteAddress(
     // need to deduct 1 from loadByteFromAddress method as it is designed to skip opcode
     (m.loadByteFromAddress(m.state.ip, 1)-1)), m.state.y, true)

LDXLoaD index X
Address Mode
Decimal OPCode
Hexadecimal OpCode
Size
Cycles
#Immediate
162
$A2
2
2
Zero Page
166
$A6
2
3
Zero Page,Y
182
$B6
2
4
Absolute
174
$AE
3
4
Aboslute,Y
190
$BE
3
4-5
Flags affected: NZ
Cycle Notes: Indexed modes take extra cycle if page boundaries crossed
Usage: Loads the X register with a value stored in memory. This is handy for setting offsets and counting values which are what the X register generally is used for.
Test Code:
; LDX Zero Page 0
     LDX 200
     BRK
.ORG 200
.BYTE 0
     ; expect x=0, z=1, n=0

; LDX Zero Page, y
     LDY #15
     LDX 10,X
     BRK
.ORG 25
.BYTE 52
     ; expect x=52

; LDA and LDX Absolute,Y
     LDY #10
     LDA 512,Y
     LDX 513,Y
     BRK
.ORG 522
.BYTE 1 2
     ; expect a = 1, x=2
Implementation:
// ldx – zero page
m.state.x = m.loadByteFromAddress(m.state.ip, 1)
// ldx zero page,y
m.state.x = m.loadByteFromAddress(m.loadByteFromAddress(m.state.ip, 1), m.state.y)
// LDX absolute,Y
m.state.x = m.loadByteFromAddress(m.findAbsoluteAddress(m.state.ip), m.state.y, true)


LDYLoaD index Y
Address Mode
Decimal OPCode
Hexadecimal OpCode
Size
Cycles
#immediate
160
$A0
2
2
Zero Page
164
$A4
2
3
Zero Page,X
180
$B4
2
4
Absolute
172
$AC
3
4
Absolute,X
188
$BC
3
4-5
Flags affected: NZ
Cycle Notes: Indexed modes take extra cycle if page boundaries crossed
Usage: Loads the Y register with a value stored in memory. This is handy for setting offsets and counting values which are what the Y register generally is used for.
Test Code:
; LDY Zero Page, x
     LDX #5
     LDY 5,X
     BRK
.ORG 10
.BYTE 12
     ; expect y=12

; LDA, LDX, LDY Absolute
     LDA 1024
     LDX 1025
     LDY 1026
     BRK
.ORG 1024
.BYTE 1 2 3
     ; expect a = 1, x=2, y=3

; LDA and LDY Absolute,X
     LDX #10
     LDA 512,X
     LDY 513,X
     BRK
.ORG 522
.BYTE 1 2
     ; expect a = 1, y=2  
Implementation:
// LDY zero page, x
m.state.y = m.loadByteFromAddress(m.loadByteFromAddress(m.state.ip, 1), m.state.x)
// LDY absolute
m.state.y = m.loadByteFromAddress(m.findAbsoluteAddress(m.state.ip))
// LDY absolute,Y
m.state.y = m.loadByteFromAddress(m.findAbsoluteAddress(m.state.ip), m.state.x, true)