Why does this RAM initialization code work?

Discuss technical or other issues relating to programming the Nintendo Entertainment System, Famicom, or compatible systems. See the NESdev wiki for more information.

Moderator: Moderators

User avatar
DRW
Posts: 2225
Joined: Sat Sep 07, 2013 2:59 pm

Why does this RAM initialization code work?

Post by DRW »

Today I noticed a little issue with my RAM initialization code (the code that sets all RAM values to 0 at the start).
Since I use a pointer variable to iterate through the RAM addresses, this means the pointer variable itself is eventually also overwritten. A side effect I haven't considered until now.

So, I checked the RAM in fceux: When resetting the game and after fceux itself sets everything to $00 and $FF, I simply fill everything with the character X. Then I check which parts are overwritten with 0 by my code.

For some reason, the whole RAM still gets set to 0. There isn't a chunk of Xs anywhere in the code because of pointer misalignment or something like that, nor does the game run into an infinite loop because of the pointer's high byte value always being set back to 0.
I can set the pointer to any arbitrary address and it always worked.

Why is that the case? Why doesn't the below code produce chunks of RAM that remain untouched?

Code: Select all

	; The value to
	; write into RAM.
	LDA #0

	; The X and Y registers are
	; used as counters for the
	; loops and initialized with 0.
	TAX
	TAY

	; Pointer + 0 always remains 0.
	; The address is calculated
	; by Pointer + 1 and by Y.
	STA Pointer + 0
	STA Pointer + 1

@initializeRamLoop:

	; Set the value of 0
	; to the current address.
	STA (Pointer), Y

	; Increment the low byte part
	; of the address.
	INY

	; If it is set back to 0,
	; increment the high byte part
	; and, of course, the counter.
	; Otherwise, continue with
	; the inner loop.
	BNE @initializeRamLoop
	INC Pointer + 1
	INX

	; The outer loop ends
	; just before address $0800.
	CPX #$08
	BNE @initializeRamLoop

	; Set the pointer itself
	; to 0 as well.
	; Pointer + 0
	; is always 0. So, we only
	; need to set
	; Pointer + 1.
	; A is still 0 from above.
	STA Pointer + 1

	; The RAM has now been
	; initialized with all zeroes.
My game "City Trouble":
Gameplay video: https://youtu.be/Eee0yurkIW4
Download (ROM, manual, artworks): http://www.denny-r-walter.de/city.html
lidnariq
Posts: 11432
Joined: Sun Apr 13, 2008 11:12 am

Re: Why does this RAM initialization code work?

Post by lidnariq »

You actually have the answer in your own comments:
DRW wrote: ; Pointer + 0
; is always 0.
The writes go
Y=0, $0=0, $1=0 thus 0→$0
Y=1, $0=0, $1=0 thus 0→$1

And after that, nothing writes to the pointer memory again.

Since the pointer has to be stored in zero page, and you're initializing RAM with 0, it won't miss anything.
User avatar
mikejmoffitt
Posts: 1353
Joined: Sun May 27, 2012 8:43 pm

Re: Why does this RAM initialization code work?

Post by mikejmoffitt »

Why such a complicated RAM-clearing routine?

Adapted from the wiki:

Code: Select all

	ldx #0
	txa $0

@clrmem_top:
	sta $000, x
	sta $100, x
	; Reserving $200 for OAM display list, sta $200, x if you want
	sta $300, x
	sta $400, x
	sta $500, x
	sta $600, x
	sta $700, x
	inx
	bne @clrmem_top
User avatar
DRW
Posts: 2225
Joined: Sat Sep 07, 2013 2:59 pm

Re: Why does this RAM initialization code work?

Post by DRW »

@lidnariq:

Oh, that's right.

Let me reiterate:

Pointer + 0 is always 0, so that one doesn't matter since Y is the actual low byte value.

The value in Pointer + 1 will go from $00 to $07 during the algorithm.

But since the pointer is in zeropage, its own location in memory will always be guaranteed to be $00xx.
And this means Pointer + 1 is only overwritten with 0 when when Pointer + 1 has the value 0 anyway.

Or to say it like this:
The loop cannot suddenly jump from $1234 to $0034 due to Pointer + 1 being overwritten with a 0 because the pointer will never be in a memory location $12xx, only $00xx.

Is that correct?

mikejmoffitt wrote:Why such a complicated RAM-clearing routine?
Two reasons:

1. I find a loop more elegant than manually writing 0, 100, 300, 400, 500, 600, 700.

2. There's an additional piece of code in my initialization routine that doesn't have anything to do with my original question, so I left it out, but it's required if you want to keep, for example, the highscore when pressing reset:

Code: Select all

	; There is a certain RAM area that
	; shall not be initialized with zeroes
	; because this area shall be persistent
	; when the Reset button is pressed.
	; We check if we reached that RAM area.

	; The high byte of the address is checked.
	LDA Pointer + 1
	CMP #>__NO_RESET_LOAD__
	BNE @noNoResetSkip

	; The low byte is checked.
	CPY #<__NO_RESET_LOAD__
	BNE @noNoResetSkip

	; If we reached the
	; reset-persistent area,
	; we change the address to
	; the first value after the area.
	; This way, the reset-persistent
	; area doesn't get changed.
	LDY #<(__NO_RESET_LOAD__ + __NO_RESET_SIZE__)
	LDA #>(__NO_RESET_LOAD__ + __NO_RESET_SIZE__)
	STA Pointer + 1

@noNoResetSkip:

	; Set the value of 0
	; to the current address.
	LDA #0
	STA (Pointer), Y
My game "City Trouble":
Gameplay video: https://youtu.be/Eee0yurkIW4
Download (ROM, manual, artworks): http://www.denny-r-walter.de/city.html
User avatar
DRW
Posts: 2225
Joined: Sat Sep 07, 2013 2:59 pm

Re: Why does this RAM initialization code work?

Post by DRW »

I just noticed: The "reset-persistent area" code is missing the adjustment of X, am I right?

In the usual code, these values get updated in pairs:

Code: Select all

	INC CommonPointer1AsVar + 1
	INX
So, this needs to be done here as well, right?

Code: Select all

LDA #>(__NO_RESET_LOAD__ + __NO_RESET_SIZE__)
	STA CommonPointer1AsVar + 1
New code after the STA: TAX, right? (Or I guess I could completely do without the X.)
My game "City Trouble":
Gameplay video: https://youtu.be/Eee0yurkIW4
Download (ROM, manual, artworks): http://www.denny-r-walter.de/city.html
User avatar
dougeff
Posts: 3079
Joined: Fri May 08, 2015 7:17 pm

Re: Why does this RAM initialization code work?

Post by dougeff »

I have no idea what your last question is asking.

How about this...the memory persistent area is zeropage 00-03

Start your loop with LDY #4.

EDIT-Alternatively, you could use $7fc-7ff as the memory persistent area, and reverse your loop, starting at $7fb, going down to $000
Last edited by dougeff on Wed Aug 02, 2017 4:37 am, edited 1 time in total.
nesdoug.com -- blog/tutorial on programming for the NES
User avatar
DRW
Posts: 2225
Joined: Sat Sep 07, 2013 2:59 pm

Re: Why does this RAM initialization code work?

Post by DRW »

The last question was just a discovery of mine: X and Pointer + 1 always need to have the same value. Because X counts the loop and Pointer accesses the RAM. And I just discovered that I missed to set X accordingly in one situation.
And I wanted to check if other people confirm this.

It didn't make a practical difference in the past. Because the reset-persistent area is only a few bytes big, so I was lucky that the high byte didn't change between before and after the reset-persistent area. But it could happen, so I'll have to fix it.

Setting this area to address $0000 wouldn't be good. This means I waste a bunch of my precious zeropage variables.

$07FF might work though. I'll have to declare the reset-persistent segment at the end of RAM and then I move the software stack some bytes to the front.
Since I use a CFG file for this, accidental overlapping shouldn't be possible.
Then I'll simply end the loop as soon as the reset-persistent area is reached.
My game "City Trouble":
Gameplay video: https://youtu.be/Eee0yurkIW4
Download (ROM, manual, artworks): http://www.denny-r-walter.de/city.html
Oziphantom
Posts: 1565
Joined: Tue Feb 07, 2017 2:03 am

Re: Why does this RAM initialization code work?

Post by Oziphantom »

The code will take a while, a long while to run, all interrupts/nmis are disabled while it runs ?
User avatar
DRW
Posts: 2225
Joined: Sat Sep 07, 2013 2:59 pm

Re: Why does this RAM initialization code work?

Post by DRW »

Yes.
My game "City Trouble":
Gameplay video: https://youtu.be/Eee0yurkIW4
Download (ROM, manual, artworks): http://www.denny-r-walter.de/city.html
Oziphantom
Posts: 1565
Joined: Tue Feb 07, 2017 2:03 am

Re: Why does this RAM initialization code work?

Post by Oziphantom »

Yes in this case you will need X and Pointer+1 to be equal otherwise you will always write $0800 bytes, and since the NES repeats RAM again after $0800 you end up writing to $0000 again.

so for example $0200 + $0700 = $0900 which is the same as $0100 which will explain why you don't see any X's in the case where you try to only clear part of it.
User avatar
DRW
Posts: 2225
Joined: Sat Sep 07, 2013 2:59 pm

Re: Why does this RAM initialization code work?

Post by DRW »

I was lucky that it works in my game "City Trouble" simply because the reset-persitent area is only 13 bytes big and it isn't positioned in a location where the low byte overflows and the high byte gets incremented. So, Pointer + 1 remains the same before and after that check anyway.
But programming-wise it's of course a bug and I'll correct it for the next game.
My game "City Trouble":
Gameplay video: https://youtu.be/Eee0yurkIW4
Download (ROM, manual, artworks): http://www.denny-r-walter.de/city.html
User avatar
Dwedit
Posts: 4924
Joined: Fri Nov 19, 2004 7:35 pm
Contact:

Re: Why does this RAM initialization code work?

Post by Dwedit »

Using a pointer variable means that everything was cleared to zeroes, except for the pointer itself.
Here come the fortune cookies! Here come the fortune cookies! They're wearing paper hats!
User avatar
DRW
Posts: 2225
Joined: Sat Sep 07, 2013 2:59 pm

Re: Why does this RAM initialization code work?

Post by DRW »

Huh? What are you referring to in this specific context?
My game "City Trouble":
Gameplay video: https://youtu.be/Eee0yurkIW4
Download (ROM, manual, artworks): http://www.denny-r-walter.de/city.html
tepples
Posts: 22708
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Re: Why does this RAM initialization code work?

Post by tepples »

Because read-modify-write operations (ASL, LSR, ROL, ROR, DEC, INC) still set the NZ flags, your RAM clearing routine can be golfed by using the pointer's high byte as the loop counter:

Code: Select all

   LDA #0  ; The value to write to RAM, and the start of a page
   LDY #$07  ; The first page to clear, in reverse order
   STA Pointer+0  ; Write the pointer
   STY Pointer+1

   TAY  ; Y is also the loop counter
   ; If you have reset-persistent data at, say, $700-$70F,
   ; do LDY #$10 instead

@initializeRamLoop:

   STA (Pointer), Y  ; Write 0 to to the current address.
   INY  ; Move to the next address in the page
   BNE @initializeRamLoop  ; If page not done, continue
   
   DEC Pointer+1  ; Go to previous page
   BPL @initializeRamLoop  ; If not wrapped around into ROM, continue
   
   ; Pointer+0 is always 0, Pointer+1 is now $FF
   STA Pointer+1  ; So write 0 there
User avatar
Jarhmander
Formerly ~J-@D!~
Posts: 569
Joined: Sun Mar 12, 2006 12:36 am
Location: Rive nord de Montréal

Re: Why does this RAM initialization code work?

Post by Jarhmander »

mikejmoffitt wrote:Why such a complicated RAM-clearing routine?

Adapted from the wiki:

Code: Select all

	ldx #0
	txa $0

@clrmem_top:
	sta $000, x
	sta $100, x
	; Reserving $200 for OAM display list, sta $200, x if you want
	sta $300, x
	sta $400, x
	sta $500, x
	sta $600, x
	sta $700, x
	inx
	bne @clrmem_top
His version is not very complicated, in fact it's pretty much standard, and note the fact that while your version is faster, his version is more compact: your algorithm takes 27 bytes, his takes 22 bytes, and could take only 20 bytes if we didn't care of the pointer value at the end. For some code initializing things, ran once at the beginning, speed is not important, but size is.
((λ (x) (x x)) (λ (x) (x x)))
Post Reply