Addressing a two byte memory location with the registers

This is an archive of a topic from NESdev BBS, taken in mid-October 2019 before a server upgrade.
View original topic
Addressing a two byte memory location with the registers
by on (#118596)
How can I address a two byte memory location with the help of the registers?
Let's say I want to write the value $AA into the memory from address $0200 to $04FF. I would write the following loop. But in the line with the command STA, how do I correctly concatenate the two address values or what else do I have to do here?

Code:
  LDA #$AA  ; The value that is written.
  LDY #$02  ; Counter for the outer loop.
  LDX #$00  ; Counter for the inner loop.

Loop:

  STA (Y << 8) + X  ; What's the correct syntax here?
  INX
  BNE Loop

  INY
  CPY #$05
  BNE Loop
Re: Addressing a two byte memory location with the registers
by on (#118600)
Your best choice for indexing into arrays bigger than 256 bytes is to put the address on zero page and use the (d),Y addressing mode.
Re: Addressing a two byte memory location with the registers
by on (#118610)
In this particular case, these fixes will do what you want:

Code:
LDA #$AA  ; The value that is written.
LDX #$02  ; Counter for the outer loop.
LDY #$00  ; Counter for the inner loop.
STX Pointer+1
STY Pointer+0

Loop:

STA (Pointer), Y
INY
BNE Loop

INX
STX Pointer+1
CPX #$05
BNE Loop
Re: Addressing a two byte memory location with the registers
by on (#118630)
Note that said code will fail if the low 8 bits of the starting address aren't 0, and the low 8 bits of the ending address aren't $FF. The wiki covers the more general approach between any two addresses.
Re: Addressing a two byte memory location with the registers
by on (#118632)
blargg wrote:
Note that said code will fail if the low 8 bits of the starting address aren't 0, and the low 8 bits of the ending address aren't $FF.

Which is why I said "in this particular case". =)
Re: Addressing a two byte memory location with the registers
by on (#118684)
Well, I of course need a general solution that works always. Because the next thing I want to do is writing some functions for loading data. And the one thing that breaks my neck is the addressing. For example, when I want to load sprite data, then it's pretty easy:
Code:
  LDX #$00
@Loop:
  LDA Sprites, X
  STA $0200, X
  INX
  CPX #$20
  BNE @Loop

At least as long as "Sprites" is a constant location. But as soon as I want to be able to parametrize this code and make it a sub routinge, the problems begin. Even if I don't work with the stack and whatever and simply put the data length value (#$20) into a global variable, I still don't know how to deal with the starting location in memory which is two bytes.
Besides, could anybody please explain me what's the deal with using < and > in assembly code?
Re: Addressing a two byte memory location with the registers
by on (#118687)
Sprites is always in a constant area, it is assembled and assigned a value.

If you write one set of sprites initially, then yes it will work fine. But if you have to select the sprites you want to use per level or something, loading from sprites, which points to one 2 byte location, will ALWAYS point to that location! It will not change.

To dynamically load sprites, levels, or basically any data based on a number, you either 1. Need to build a pointer table. 2. Use math based on the number and multiply based on the base value. Let's assume we have 4 screens, screen 0 to 3. You want to load one each different level, you would have this:

Code:
Screen0:
(1KB of data)
Screen1:
(1KB of data)
Screen2:
(1KB of data)
Screen3:
(1KB of data)


if your code uses Screen0 to move it, it will ALWAYS point to the same screen. Here is some code to get around it:

Code:
ScreenPointersHigh:
  .db HIGH(Screen0),HIGH(Screen1),HIGH(Screen2),HIGH(Screen3)

ScreenPointersLow:
  .db LOW(Screen0),LOW(Screen1),LOW(Screen2),LOW(Screen3)


what this does, is let us use objectified code like this:

Code:
//Code expects X to be the value of the nametable we are uploading.
  LoadScreen:
  LDA ScreensHigh,X //Load high pointer.
  STA Pointer+1 //Pointer is a ZP value.
  LDA ScreensLow //Load low pointer.
  STA Pointer //Store low pointer value, so the zeropage variable Pointer points to the first byte of the nametable.
  LDY #$00
  LDX #$03
  -Set the PPU pointer to $2000 here-
@Loop:
  LDA [Pointer],Y //Load the location the pointer points to+Y
  STA $2007 //Store to PPU.
  INY //Y=Y+1
  BNE @Loop //Loop around if 0.
  INC Pointer+1 //Make it point to the next 256 byte block by increment the high byte of the pointer.
  DEX //Since 0, test if this was the last round of 256 bytes we need to do by subtracting 1 from X.
  BPL @Loop //If not 4 times, loop again.
  RTS //Subroutine is done.


If you need objectifying explained more, or any questions here, just PM or ask. :)
Re: Addressing a two byte memory location with the registers
by on (#118689)
DRW wrote:
But as soon as I want to be able to parametrize this code and make it a sub routinge, the problems begin. Even if I don't work with the stack and whatever and simply put the data length value (#$20) into a global variable, I still don't know how to deal with the starting location in memory which is two bytes.

The routine is so short it can be made a macro. If it were a parameterized subroutine, it'd take 7-9 bytes to call, whereas the loop itself can be an inline macro and only need 11 bytes:
Code:
.macro SMALL_COPY src,dest,count
.if count > 128
    .error Can't copy more than 128 bytes
.endif
  .local @Loop
  LDX #count-1
@Loop:
  LDA src, X
  STA dest, X
  DEX
  BPL @Loop
.endmacro

<addr gives the low 8 bits of addr, >addr gives the next higher 8 bits. If you code in C, <addr is the equivalent of addr&0xFF, and >addr is addr>>8&0xFF.
Re: Addressing a two byte memory location with the registers
by on (#118692)
3gengames wrote:
Sprites is always in a constant area, it is assembled and assigned a value.

Yes, the actual sprites are in a constant area. I'm talking about my read only data that simply gives the information which sprites are to be loaded next. Imagine this:
Code:
SpritesLevel1:
  .byte $03
  .byte $01
  .byte $05
  .byte $02
  .byte $FF

This code tells you that level 1 will contain three Goombas (ID 1) and 5 Koopa Troopas (ID 2). And that's it. ($FF is the constant for end of data.) Another function that contains some logic would then build the actual on-screen sprites from that abstract data.
Level 4 would then maybe look like this:
Code:
SpritesLevel4:
  .byte $01
  .byte $01 ; Goomba
  .byte $10
  .byte $02 ; Koopa Troopa
  .byte $02
  .byte $05 ; Lakitu
  .byte $04
  .byte $06 ; Hammer brother
  .byte $01
  .byte $0F ; Bowser
  .byte $FF


3gengames wrote:
To dynamically load sprites, levels, or basically any data based on a number, you either 1. Need to build a pointer table.

Sounds good. How do I do this in 6502 Assembly, specifically for my example loop?

3gengames wrote:
2. Use math based on the number and multiply based on the base value.

Doesn't sound so good. As you see above, there's no guarantee that data will have a fixed length. Besides, the same function shall be used for various unrelated data sections. So, any solution based on a fixed memory location of the data won't work in the long run.

blargg wrote:
The routine is so short it can be made a macro. If it were a parameterized subroutine, it'd take 7-9 bytes to call, whereas the loop itself can be an inline macro and only need 11 bytes:

Yeah, but if I want to call it various times (four times alone for background, sprites, palettes and attributes which would already make 44 bytes), it can get relatively very big.

blargg wrote:
<addr gives the low 8 bits of addr, >addr gives the next higher 8 bits. If you code in C, <addr is the equivalent of addr&0xFF, and >addr is addr>>8&0xFF.

If there's a way to take 8 bits from a 16 bit address, isn't there also a way to store two eight bit values next to each other and let the program treat it as a 16 bit address value?
Re: Addressing a two byte memory location with the registers
by on (#118693)
DRW wrote:
3gengames wrote:
2. Use math based on the number and multiply based on the base value.

Doesn't sound so good. As you see above, there's no guarantee that data will have a fixed length. Besides, the same function shall be used for various unrelated data sections. So, any solution based on a fixed memory location of the data won't work in the long run.


Yeah, but math is more efficient+saves space, it's always the #1 option really, unless your data is like levels or maps, which has no set structure except a decompression stream. The code above is how you do that, the 1KB levels don't HAVE to be 1KB, because of the fact that we don't touch the level's data, we us the lookup pointer table which is changed every time it is assembled, so no matter the size, the pointer table will point to the data, that's it. It's like an array of pointers in C, in essence. See the above code, like I said, the table has to be added and the code written will take advantage of it, in 1KB chunks, but you just have to understand the idea, all the code will need rewritten for your needs.
Re: Addressing a two byte memory location with the registers
by on (#118694)
DRW wrote:
blargg wrote:
<addr gives the low 8 bits of addr, >addr gives the next higher 8 bits. If you code in C, <addr is the equivalent of addr&0xFF, and >addr is addr>>8&0xFF.

If there's a way to take 8 bits from a 16 bit address, isn't there also a way to store two eight bit values next to each other and let the program treat it as a 16 bit address value?

Yes, pointers. If you store the high byte after the low byte in ZP you can use them as a 16-bit pointer. You're limited to the addressing modes that work with pointers though.

< and > are provided by the assembler, the 6502 doesn't know you're taking parts of a 16-bit value. When you do LDA #>$FFEE, what the 6502 sees is LDA #$EE. The 6502 is an 8-bit CPU that unlike some other 8-bit CPUs (Z80, for example) doesn't have 16-bit registers available to the program. Because of this, 16-bit values can only exist in memory (RAM or ROM), and must be manipulated byte by byte. The only way to address more then 256 bytes is with pointers, there's no way around that.
Re: Addressing a two byte memory location with the registers
by on (#118695)
Pointers in 6502, concisely:
Code:
addr = 0 ; 0 <= addr <= $FE

LDA #<$1234 ; LDA #$34
STA addr
LDA #>$1234 ; LDA #$12
STA addr+1

LDY #$10
LDA (addr),Y  ; LDA $1244


EDIT: whoops, < before $
Re: Addressing a two byte memory location with the registers
by on (#118844)
I guess I understand that example now. In the moment I work with the standard neslib from CC65, so that's not an issue anymore, but when I remove it and write all the code myself, I guess I'll need it.