News: 11 March 2016 - Forum Rules

Author Topic: NES Question - Is it possible to change the LDA TableRead using an indirect LDA?  (Read 3650 times)

RetroRain

  • Sr. Member
  • ****
  • Posts: 287
    • View Profile
Here is some code to illustrate what I'm talking about.

Code: [Select]
; Code to be assembled with ASM6
; This is a routine which will handle all of my text writing.

texthandler:

jsr wait_for_vblank

lda text_coordinate1
sta $2006

lda text_coordinate2
sta $2006

ldx #$00
- lda __, x   ; this line is what I need help with.
cmp #$ff      ; ff is a line terminator.  When ff is reached, the text line is finished.
beq +
sta $2007
inx
jmp -
+ rts

text_line1:
db $00,$16,$0e,$10,$0a,$16,$0a,$17,$00,$12,$1c,$00,$0a,$00,$0c,$18,$19,$22,$1b,$12,$10,$11,$1d,$00,$18,$0f,$00,$00,$00,$ff

text_line2:
db $0c,$0a,$19,$0c,$18,$16,$24,$00,$1d,$11,$12,$1c,$00,$12,$1c,$00,$0a,$00,$17,$18,$17,$25,$19,$1b,$18,$0f,$12,$1d,$00,$ff

Is there a way I can make the LDA __,X above have an address that changes, so it will read a given line when I set it to it?

The only thing I can think of are the LDA Indirect opcodes, but I researched them, and I wasn't sure if they would do the trick.  I know that I can read a value that is stored in an address, and get output that way, but I was just wondering if it is possible to change the table address that is read by the LDA, so I can use this routine for any line of text?

For instance, if I have:

LDA text_line1, x

Later on, I want to change that text_line1 to text_line2.  Can that be done using the indirect LDA?

Thanks.

Disch

  • Hero Member
  • *****
  • Posts: 2814
  • NES Junkie
    • View Profile
Yes this is exactly what Indirect,Y mode is for:

Code: [Select]
lda #pointer_low
sta temp
lda #pointer_high
sta temp+1

ldy #0
-  lda (temp),Y
   cmp #$FF
   beq +
   sta $2007
   iny
   jmp -     ; <- bne would probably be better than jmp, here.  Save a byte, prevent infinite loops
+  rts


EDIT:  note that you have to use Y, as (Indirect,X) works differently and is not anywhere near as useful.

RetroRain

  • Sr. Member
  • ****
  • Posts: 287
    • View Profile
Thank you Disch.  I appreciate that confirmation.

I searched in the ASM6 readme, but it didn't exactly say what a # by itself is.

EDIT - Nevermind, I figured it out.  The STA temp + 1 is simply taking on the next RAM address to the right.
« Last Edit: July 24, 2016, 04:06:35 pm by Rockman »

Disch

  • Hero Member
  • *****
  • Posts: 2814
  • NES Junkie
    • View Profile
I searched in the ASM6 readme, but it didn't exactly say what a # by itself is.

# is immediate mode
$ is hexadecimal

Code: [Select]
lda #16
; is the same as
lda #$10

Quote
Also, although I have seen it done, I don't understand how that addition works, like with the STA temp + 1.  I always thought that any kind of arithmetic had to be done with other opcodes, such as ADC, SBC, or the other ones for multiplication and division.

runtime math has to be done with ADC/SBC.  But assemble-time math can be done by the assembler by way of + and similar operators

Example:
Code: [Select]
temp = $10

lda #temp   ; assembles to 'lda #$10' or 'A9 10'
lda #temp+1 ; assembles to 'lda #$11' or 'A9 11'

Quote
If I were to look in the debugger of FCEUX to look at STA Temp + 1, I would simply get the sum of those two values.  If you were a person hacking that game, you wouldn't be able to tell that there was an addition going on there would you?

No you would not.  The addition is done by the assembler before binary output is produced, and therefore it would be lost in the ROM (much like variable names and the like)

RetroRain

  • Sr. Member
  • ****
  • Posts: 287
    • View Profile
Thank you for that clarification.

So, if I want to read my text tables, do I change the labels "line1_text" to an actual number like org $8510 or something like that?  The pointers read the address, but I don't know exactly what the address is of my line1_text label.

Disch

  • Hero Member
  • *****
  • Posts: 2814
  • NES Junkie
    • View Profile
Use the label name.

I don't know how ASM6 does it, but most assemblers have an operator to give you low and high bytes of a given value.

ca65 uses < for low byte and > for high byte:

Code: [Select]
lda #<mylabel   ; low byte
sta temp
lda #>mylabel   ; high byte
sta temp + 1

..

mylabel:
  ...

I'm sure asm6 has something similar.  Check the docs to see what operator/keyword it uses.

If not, you can get the low/high bytes yourself with bitwise arithmetic:

Code: [Select]
lda # (mylabel & $FF)  ; low byte
sta temp
lda # (mylabel >> 8)  ; high byte
sta temp+1

RetroRain

  • Sr. Member
  • ****
  • Posts: 287
    • View Profile
Thanks Disch!  That was very helpful! :)

Disch

  • Hero Member
  • *****
  • Posts: 2814
  • NES Junkie
    • View Profile

RetroRain

  • Sr. Member
  • ****
  • Posts: 287
    • View Profile
Hey Disch, is there a way to check to see if a number falls within a certain range of numbers?

For instance, if my number happens to be #$00 or anything up to and including #$0f , I want it to branch to a routine to carry out some other code.

Right now I'm doing a bunch of CMP instructions, but I feel like that is being inefficient.

Thanks.

Disch

  • Hero Member
  • *****
  • Posts: 2814
  • NES Junkie
    • View Profile
You only need 1 CMP.  CMP sets N,Z,C ... Z will tell you if they're equal, C will tell you inequality (greater/less)

Z=1  ->  A == mem
Z=0  ->  A != mem

C=1  ->  A >= mem
C=0  ->  A < mem

Code: [Select]
lda #some_value
cmp #$10
bcs some_value_was_greater_than_or_equal_to_10

RetroRain

  • Sr. Member
  • ****
  • Posts: 287
    • View Profile
Awesome!  I've used SEC and BCS in the past on my Megaman 4 hack, but I never fully understood them.  Thanks again Disch! :D

Disch

  • Hero Member
  • *****
  • Posts: 2814
  • NES Junkie
    • View Profile
OVERLY VERBOSE AND UNNECESSARY EXPLANATION


Flash back to elementary school math.  You have to add two 2-digit numbers together.  For example:
Code: [Select]
  38
+ 24
----

The first thing you do is add the "ones" column.  8+4 gives you 12... so you write "2" in the ones sum and put the '1' as the carry:

Code: [Select]
  1     <-  the carry
  38
+ 24
----
   2

You then add 3+2 plus an additional 1 for the carry... giving you 6.  So the answer is "62"

The important concept here is the "carry".  Because 6502 has the exact same concept in the form of the "carry flag" -- commonly just called "C".  When you add two 8-bit numbers together, you actually get a 9-bit result... the 9th bit gets put in the C flag.

Code: [Select]
CLC            ; C=0
LDA #$81
ADC #$81
  ; at this point:
  ; A=$02
  ; C=1
  ;
  ; because:
  ;   81
  ; + 81
  ; ----
  ;  102
  ;  ^^^
  ;  |||
  ;  |\+----  A
  ;  |
  ;  \---- C


The reason you generally want to CLC before you ADC is because ADC will add an additional 1 in the result if C is set.  That is, it's adding the carry.  In the above code sample, we're not really adding $81 + $81 .... we're ACTUALLY adding $81 + $81 + C.  It just happens that C=0 because we did the CLC

You will want to include the carry when doing multi-byte addition.  Say, for example, if we want to add 1000+1000 ($03E8 + $03E8)... we would do it the same way as the elementary school example above... only instead of adding the "ones" column and include any additional carry into the "tens" column.... we would add the "low bytes" and include any additional carry into the "high bytes":

Code: [Select]
CLC             ; CLC here because we don't want carry for first addition
LDA #$E8
ADC #$E8        ; A=$D0    C=1
STA low_result

LDA #$03        ; no CLC here because we WANT carry here
ADC #$03        ; A=$07    C=0    (3+3+1 = 7)
STA high_result

; end result = $07D0 (2000), which is the proper sum of $03E8 + $03E8 (1000+1000)


Subtraction (SBC) is basically identical to addition, only in complete reverse.  When you subtract $10, you are doing the same thing as adding its inverse, $EF.  So C has the same function here, only it is backwards.

SBC with C=1 will perform subtraction normally
SBC with C=0 will subtract an extra 1 for the "borrow"

Hence you will typically want to SEC (C=1) before you do an SBC, unless you specifically want the borrow.


Likewise, the result of C is backwards.  Meaning after an SBC, C=1 if there was no borrow necessary (A >= value being subtracted)... or C=0 if borrow was necessary (A < value being subtracted)


Notice how the result of C after SBC is the same as the result of CMP.  That's because CMP also does subtraction!  CMP basically takes A, subtracts a value, sets flags, and then discards the result of the subtraction.  So...

- Z ("Zero" flag) becomes equality since the only way the subtraction could result in zero is if the two numbers were the same
- C becomes greater/less than since there will only be borrow if A < value

RetroRain

  • Sr. Member
  • ****
  • Posts: 287
    • View Profile
Wow.  Thanks for all of that additional information.  That clears up the carry a lot more for me.  You definitely know your stuff.

This would make another great reference thread.  It should be stickied.

Thank you Disch! :)