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.
LDA – LoaD 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)
LDX – LoaD 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)
LDY – LoaD 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)
No comments:
Post a Comment