pwnskar wrote:That part I think I might understand. I should hope so, because I'm doing a lot of pointer increments that way, as well as some 16-bit collision detection that I've decided not to use, as my game has no scrolling. :)
Am I right to believe that the carry from the first adc gets added onto the second one and then cleared?
Yup, correct! But it seems I made a catastrophic mistake in my previous code: I forgot an lda $15 before the adc #0. I've edited my post to fix that.
Something easily overlooked when we get "used" to seeing a particular routine. Quite a major mistake though. :-)
Detailed explanation for future readers who aren't sure:
clears the carry. Next, the adc $14
gets executed. Here, a couple things happen. This is how I mentally envision it:
1. The CPU does: $fe (value in $14) + 4 (accumulator) + 0 (carry) == result of $102
2. Value $102 is too large for an 8-bit register, which is known as "unsigned overflow" in this particular case. Because of this, the carry flag c
3. Likewise, in this situation, the two's complement math done does not
contain an error, so the overflow flag v
is clear. (We aren't using the overflow flag in this operation, so it's irrelevant, but I wanted to note that it does get cleared here -- two's complement math is one of the biggest struggling points there is on the 65xxx architecture, discussed heavily over the years on this forum)
4. The final result: the accumulator holds the value $02, and the carry flag is set
Next, sta $14
writes $02 to $14. Following that, we have lda $15
, so the accumulator now holds $20. Next, we have adc #0
. The same process as described above happens, except this is the resulting math:
1. The CPU does: 0 (value in operand) + $20 (value in accumulator) + 1 (carry) == result $21
2. Value $21 fits into an 8-bit register, so carry is clear. Likewise, overflow is also clear
3. The final result: accumulator holds the value $21, carry flag is clear, overflow flag is clear
Finally we do sta $15
, which writes $21 to $15.
Thus, our 16-bit pointer at $14/$15 now contains the value $2102, which is exactly what we wanted: $20fe + 4 = $2102. In essence, we use the carry flag as a way to handle the "page wrap" (of our math) for us.
pwnskar wrote:If I've understood correctly, this should be possible as long as both pointer variables (in this case some_pointer_lo and some_pointer_hi) are on ZP and y never goes above 255?
The former part of your sentence is correct: basically, ensure that some_pointer_lo
is not ever $ff, otherwise this would cause the CPU to read the high byte of the effective address from $00, not $0100 like your brain might think.
The latter part of your sentence is incorrect: Y can safely be any value (0-255). The CPU, when adding Y to the effective address (calculated from reading the low byte of the address from some_pointer_lo
and the high byte of the address from some_pointer_lo+1
), can handle wrapping (ex. $20ff->$2100) just fine.
For example: in my previous post's first code block, if you changed ldy #5
to ldy #$ff
, it would still work fine (the final effective address would be $ee34 + $ff == $ef33).
The short of it is: when working with ZP, always remember that ZP addressing stays within page 0 (the $00xx region, or $0000-00FF) and can never "wrap" into page 1 ($01xx, or $0100-01FF). Absolute addressing can/will page wrap, for nice/clean/linear 16-bit addressing, but there's 1 CPU cycle penalty when a page wrap happens.