How to compare to a value larger than $ff?

This is an archive of a topic from NESdev BBS, taken in mid-October 2019 before a server upgrade.
View original topic
How to compare to a value larger than $ff?
by on (#80827)
This question is a lot different than my other questions, so I figured a new topic would be better, but it's basically just the title, how do I compare to a value larger than $ff? I'm loading my background in, and it's saved in a label, backgrounds: and has all the backgrounds stored in .db's. The only problem, is that for the full screen, there is 32x32, 8x8 tiles, which is 1024 bytes of data.. Here's how I'm loading the backgrounds:
Code:
LoadBackgrounds:
  LDA $2002
  LDA #$20
  STA $2006
  LDA #$00
  STA $2006
  LDX $00
LoadBackgroundsLoop:
  LDA backgrounds, x
  STA $2007
  INX
  CPX #$FF ; Here's where I need to compare to 1024, or $0400, but it only allows 1 byte to be compared to
  BNE LoadBackgroundsLoop

So, is there a better to do this, or is there any to compare to a 2 byte value?
How do you guys normally load the background?

by on (#80831)
If you want to access an array bigger than 256 bytes, you first need to store the starting address on zero page (lda #<label sta src_addr lda #>label sta src_addr+1). You may want to load the number of 256-byte "pages" into X, which in the case of an uncompressed nametable is four (ldx #4). Then use the (d),Y addressing mode and step through each page with Y. When Y wraps around to zero (hint: your loop will use a BNE instruction), increase the high byte of the address (inc src_addr+1) and decrease X until you've gone through all four pages.

by on (#80833)
Ahhh I think I understand that, like a double for loop? But I may have trouble translating that to code.. Here's my attempt
Code:
LoadBackgroundsLoopX:
  LDA backgrounds, x ; how do I add 1 to the high byte here?
  STA $2007
  INX
  CPX #$FF
  BNE LoadBackgroundsLoopX
LoadBackgroundsLoopY:
  DEY
  LDX #$00
  CPY #$00
  BNE LoadBackGroundsLoopX

But that's just going to load the first 256 bytes 4 times.. Any more tips? I'd still like to figure this out without someone handing me the code.

by on (#80834)
You need to store the address of 'backgrounds' in a pointer on zero page, and then you need to look at what the (d),y indirect addressing mode does.

by on (#80835)
Since the 6502 is an 8-bit CPU, it can only manipulate 8 bits (which can hold values from 0 to 255) at a time. If you need numbers larger than that, you have to work with the individual bytes that form that larger number. For example, if you want to compare 2 16-bit numbers, you have to first compare the high byte and then the low byte.

But in this particular case, it's not a 16-bit compare you are looking for. Like tepples said, the 6502 has a very useful addressing mode that is meant to allow access data larger than 256 bytes. For this you need a pointer and an index. The pointer is stored in zero page RAM ($0000-$00FF), and the index is the Y register.

A pointer is a 16-bit value that indicates an address. Even though the 6502 can't manipulate 16-bit values internally, it can access 16-bit pointers in RAM. To read data using a pointer you need the instruction "LDA (Pointer), Y". When the CPU sees this instruction, it will go to the ZP location where the variable "Pointer" is, it will see what address is stored there and will then look for your data in that address. Y is used as an offset into that data, so you can access up to 256 bytes starting from the address indicated by the pointer.

If you need to access more than 256, just modify the pointer. By incrementing the high byte of the pointer you effectively add 256 to it, which will allow you to read 256 more bytes. If you keep doing this you can access as many bytes as you want.

by on (#80838)
So like..
Code:
.rsset $0000
pointer .rs 2 ; it needs 2 bytes right?

LDA #<backgrounds ; low byte
STA pointer
LDA #>backgrounds ; high byte
STA pointer + 1 ; now pointer is background's full address
LDX #$04
LDY #$00

BackgroundLoop:
  LDA (pointer), y ; y is the low byte
  STA $2007
  INY
  CPY #$FF ; increase until it hits $FF
  BNE BackgroundLoop
DecX:
  INC pointer +1 ; increase the high byte
  LDY #$00 ; reset the low byte
  DEX ; and decrese x by one
  CPX #$00 ; check if X is down to 0 yet (after 4 loops)
  BNE BackgroundLoop

Ignore the comments, I was just figuring it out one piece at a time.
Man my brain hurts after that.. haha. I think that would work though, right?

by on (#80841)
Canite wrote:
I think that would work though, right?

Almost! You got the right idea, there are just a couple of logic issues:

1. The "CPY #$FF" is not necessary. If you use it, you will actually copy 255 bytes instead of 256. Just let Y wrap back to $00 with the INY, and because the Z flag will be set the branch will not happen, so there's no need to compare anything. As a bonus you also don't need to reset Y later, as it will already be 0.

2. Similar to the above, you don't need the "CPX #$00". This isn't an error though, it's just redundant. If the result of any operation is zero, the Z flag will be set, so there's no need to compare against #$00.

The status flags are kinda mystical for people who have never programmed in assembly before (for me at least that was one of the weirdest things). Just keep in mind that a lot of instructions modify the flags (specially the ones that manipulate values in any way), and after these modifications you can use the branch instructions to make decisions about what to do next. You don't have to "compare" all the time, as most of the time the operations themselves will set the flags for you.

by on (#80842)
tokumaru wrote:
2. Similar to the above, you don't need the "CPX #$00". This isn't an error though, it's just redundant. If the result of any operation is zero, the Z flag will be set, so there's no need to compare against #$00.


Ah, I didn't even know you could do that, thanks. But when I try to compile it, NESASM throws some errors at me :/ It says that the LDA #<backgrounds part is an unknown instruction. Is my syntax wrong?

by on (#80843)
NESASM uses weird syntax. All other assemblers use LDA (Pointer), Y, while NESASM uses LDA [Pointer], Y. For accessing the low and high bytes of a value you have to use LDA #LOW(Value) and LDA #HIGH(Value), I think. NEASM is realy weird.

by on (#80844)
tokumaru wrote:
NESASM uses weird syntax. All other assemblers use LDA (Pointer), Y, while NESASM uses LDA [Pointer], Y. For accessing the low and high bytes of a value you have to use LDA #LOW(Value) and LDA #HIGH(Value), I think. NEASM is realy weird.


Gah, I've tried everything, always gives an error..
I have this now:
Code:
LDA #LOW(backgrounds) ; low byte
STA pointer
LDA #HIGH(backgrounds) ; high byte
STA pointer + 1 ; now pointer is background's full address

but it still gives an error?

by on (#80845)
Did you also change LDA (pointer), Y to LDA [pointer], Y? It should be working...

EDIT: Maybe NESASM doesn't support the "pointer + 1" part? Have you tried declaring your pointer as two individual HI and LO bytes? Or putting "pointer + 1" between parenthesis? I really can't see what's wrong now.

by on (#80846)
tokumaru wrote:
Did you also change LDA (pointer), Y to LDA [pointer], Y? It should be working...

Yeah, I did, here's there error
Image

Quote:
EDIT: Maybe NESASM doesn't support the "pointer + 1" part? Have you tried declaring your pointer as two individual HI and LO bytes? Or putting "pointer + 1" between parenthesis? I really can't see what's wrong now.

If I do it this way, what will I put in in the LDA (pointer), y part?

by on (#80847)
Time to switch to ASM6?

by on (#80848)
Dwedit wrote:
Time to switch to ASM6?


I guess? Haha.
I just started learning assembly yesterday, so I didn't really want to be switching around like that.. How different are assemblers from each other?

by on (#80849)
Canite wrote:
Image


My guess? You didn't indent those instructions.

Code:
sta pointer+1

is different than
Code:
 sta pointer+1;Note the space before the instruction


nesasm gives unknown instruction when you don't indent with whitespace characters. There's no other reason I can think of why STA pointer wouldn't work.
Edit:
tokumaru wrote:
Did you also change LDA (pointer), Y to LDA [pointer], Y? It should be working...

As of the latest nesasm, either of those works fine.
Edit again several months later: Both ASSEMBLE fine, but it with paranthesis it gives the wrong addressing mode. Sorry, I can be so confident when I'm wrong.

by on (#80850)
Kasumi wrote:
My guess? You didn't indent those instructions.


Oh wow, nice catch haha. I didn't even think about that, would've been nicer if it said "Not indented" instead :P

by on (#80865)
Canite wrote:
Kasumi wrote:
My guess? You didn't indent those instructions.


Oh wow, nice catch haha. I didn't even think about that, would've been nicer if it said "Not indented" instead :P


Huh? You don't have to indent in NESASM to my knowledge, but I always put 2 spaces before instructions. So it's worth a try, but I don't remember that being a problem for me. Interesting. But anyway:

I don't really see a problem with your code, the only problem is that your variables aren't spelled the same or capitalized the same, as the code you typed in should of worked. And FYI people who don't use NESASM and bag it, NESASM3 [Current versio everyone should have] supports pointer+1 and such. Use it again before you bag on it for being crap, as in my use, it's never created a bad binary or anything of that nature. It's awesome.

Also, does everyone also code in all caps? I don't know why I do, I think it's so I can tell pointers and stuff away from instructions and numbers. :P

by on (#80869)
Yeah, indenting got rid of the errors, but the code doesn't work as expected still.. its just displaying a screen full of zeroes and near the bottom is a few rows of glitchy zeroes and other random tiles when its supposed to be just all black tiles.
Although I think its more because I'm terrible with nametables and such rather than this loop. Is there any order in which the backgrounds/sprite/pallettes should be loaded or does it not matter? I also read somewhere that the background data needs to start at $2000, but in my code my bank 2 has .org $E000 (I think), could that be a problem?

by on (#80872)
The data in your ROM starts to $E000 I am guessing, not a problem, you upload it to the PPU with this code:

Code:
LDA $2002 ;Reset PPU RAM "pointer"
LDA #$20 ;A=20
STA $2006 ;Store high byte of pointer, $20xx
LDA #$00 ;A=0
STA $2006 ;Store low byte of pointer, PPU ram write to $2007 now to $2000, the first nametable tile/attribute data.


That's what that code does.

Now you need to write 1024 bytes to the nametable. The first $3C0 values are the tiles on the screen [$2000-$23BF] When you assign what table the background uses for tiles, you store the tile number you want here. After $3BF of your nametable, $3C0 starts the "attributes" bytes. Attributes give a 2x2 tile area a color palette. You assign the data from $3C0-$3FF the numbers you want on the palette.

Do you get this? If not, seeing this may help: Wiki Link

As for uploading, I upload the palette and then the nametable, but the screen is off for both so I don't believe it really matters.

by on (#80873)
3gengames wrote:
As for uploading, I upload the palette and then the nametable, but the screen is off for both so I don't believe it really matters.

The only thing that matters regarding uploading palette, is that you might want to make sure you're in VBLANK while uploading it, otherwise the colors you're uploading will show up as visible streaks on the display even if rendering is disabled (because of a feature of the PPU where if rendering is off and PPU address points to the palette area, it'll render with whatever color the address is pointing at). It's OK to use the $2002 flag to check for VBLANK in this case as it doesn't matter if we miss frames.

by on (#80877)
Canite wrote:
Oh wow, nice catch haha. I didn't even think about that, would've been nicer if it said "Not indented" instead :P

That would be nicer, but it actually makes less sense when you look at how nesasm works. See below.
3gengames wrote:
Huh? You don't have to indent in NESASM to my knowledge, but I always put 2 spaces before instructions.

As far as I know, any number of spaces or tabs before actual code starts on a line is called indenting in programming. So two spaces before an instruction is indenting.

This code is valid is nesasm:
Code:
lda:;A label
  lda lda

This code is valid in nesasm:
Code:
lda;a label doesn't need a colon in nesasm
 lda lda;load the value stored at this label

This code is valid in nesasm:
Code:
lda lda lda

All the code above does the exact same thing. So this is what happens when you don't indent an instruction in nesasm.
Code:
STA pointer+1

Is interpreted the same way as:
Code:
STA:
 pointer+1

So STA is a label. Pointer+1 is an unknown instruction. That's why it gives "unknown instruction" as an error, and rightly so. One could make the argument that nesasm should reserve the keywords used for an instruction, but as the program is now, an indentation error doesn't make sense. Another solution would be to make the program only accept labels that terminate with a colon, but this would likely not be backwards compatible with some code.

The ability to place an instruction on the same line as a label is actually extremely useful. We indent so the assembler knows the difference between labels and instructions.

Edit: I found something even MORE interesting. At first I thought, "Oh, wait. You should have actually encountered a 'label multiply defined' error if what I said was true." BUT... nesasm actually doesn't give an error for a label defined twice if it points to the same location.

Code:
Pointer:
Pointer:

gives no errors.
Code:
Pointer:
 lda #$00;or any other random instruction
Pointer:

gives "label multiply defined".

Since an unknown instruction doesn't increment nesasm's internal counter (after all, how many bytes would it know to increment by?), a string of not indented code just means that all the labels point to the same location, so no "label multiply defined". That's fascinating to me. Even if this behavior were changed, (And I can actually think of a few good uses for this behavior) it would only give you "label multiply defined" if you used the same instruction not indented more than once.

To bring this back on topic, the fact that all your instructions are assembled to the same location ($E000) is the clue that you forgot to indent.

by on (#80902)
Ahh, I fixed it :D
The problem was that I had the LDA #HIGH(pointer), etc., stuff right after I set the space for pointer (while the ppu was still at ram $0000), I'm not sure why I put it there in the first place.. So I moved it to right before the background loop and it works :)

Those are some interesting syntax quirks Kasumi, but very informative, thanks. Should also help me interpret NESASM error messages a little better too.

by on (#80915)
I checked how ASM6 handles things. It turns any unrecognized keyword into a label regardless of how many spaces are before it, and treats any recognized keyword as an instruction or assembler command.

So in ASM6, instruction names can't be labels. But instructions do not require any spacing before them, and labels can be defined even with spaces before them.

I also tried it on TASM. In TASM, labels must be left-column aligned, and do not require a colon. You can use instruction names as labels in that assembler.