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
rainwarrior
Posts: 8732
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: Why does this RAM initialization code work?

Post by rainwarrior »

Jarhmander wrote:His version is not very complicated, in fact it's pretty much standard...
It does seem to be the standard for commercial era games to initialize memory with an indirect Y addressed loop. Here's the memory clear from Super Mario Bros. (doppelganger's disassembly):

Code: Select all

;$06 - RAM address low
;$07 - RAM address high

InitializeMemory:
              ldx #$07          ;set initial high byte to $0700-$07ff
              lda #$00          ;set initial low byte to start of page (at $00 of page)
              sta $06
InitPageLoop: stx $07
InitByteLoop: cpx #$01          ;check to see if we're on the stack ($0100-$01ff)
              bne InitByte      ;if not, go ahead anyway
              cpy #$60          ;otherwise, check to see if we're at $0160-$01ff
              bcs SkipByte      ;if so, skip write
InitByte:     sta ($06),y       ;otherwise, initialize byte with current low byte in Y
SkipByte:     dey
              cpy #$ff          ;do this until all bytes in page have been erased
              bne InitByteLoop
              dex               ;go onto the next page
              bpl InitPageLoop  ;do this until all pages of memory have been erased
              rts
Do any commercial games do the "list of absolute X" style initialization? I see it all the time in homebrew ROMs, but we have kind of an insular code culture here, and a lot of our popular examples are not derived from commercial games at all.
Jarhmander wrote:your algorithm takes 27 bytes, his takes 22 bytes
Tiny nitpick, but technically 26 on ca65, 27 on NESASM as written (i.e. whether that STA $000, X gets turned into a ZP or ABS instruction... though you can force it with < on NESASM).
User avatar
thefox
Posts: 3134
Joined: Mon Jan 03, 2005 10:36 am
Location: 🇫🇮
Contact:

Re: Why does this RAM initialization code work?

Post by thefox »

dougeff wrote: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
Yet alternative way would be to start the memory clearing loop from the end of the persistent area, and loop for 2048 bytes minus the size of the persistent area. Mirrored memory at $800..$FFF makes this possible. Might not be very convenient to implement though because of the non-page-aligned start/end addresses.
Download STREEMERZ for NES from fauxgame.com! — Some other stuff I've done: fo.aspekt.fi
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'm posting my final version of the function for completeness.

I decided against putting the reset-persistent area at the end of RAM after the C stack. Because in this case, I would have to declare an absolute size for it in the cfg file. But I prefer the reset-persistent area to be just a RAM segment that grows and shrinks according to the global variables I actually declared in the program.

Of course, if you can suggest me a way to declare the NO_RESET part after the stack, but without manually declaring a size for it (i.e. the stack location automatically gets shifted to the left when I declare more variables in NO_RESET), I might change it.
This is the relevant part of my config file (I removed all the ROM- and zeropage-related stuff):

Code: Select all

SYMBOLS
{
	__STACKSIZE__  = $0100;
	__RAMSTART__   = $0200;
	__RAMSIZE__    = $0800 - __RAMSTART__ - __STACKSIZE__;
	__STACKSTART__ = __RAMSTART__ + __RAMSIZE__;
}

MEMORY
{
	RAM:     type = rw, start = __RAMSTART__,   size = __RAMSIZE__,   file = "";
	STACK:   type = rw, start = __STACKSTART__, size = __STACKSIZE__, file = "", define = yes;
}

SEGMENTS
{
	SPRITES:  load = RAM, type = bss, align = $0100;
	FAMITONE: load = RAM, type = bss, align = $0100;
	BSS:      load = RAM, type = bss;
	NO_RESET: load = RAM, type = bss,                define = yes;
}

Alright, and here the actual code.
The code is put between the two wait for vblank calls.
X was already set to 0 before.

Code: Select all

	; In the meantime, set the
	; whole RAM memory (i.e. all
	; variables) to 0.
	; The RAM goes from address
	; $0000 to $07FF.
	; The current address value
	; goes into a two bytes pointer.
	; The values are set in two loops.

	; The X and Y registers are
	; used as counters for the
	; loops. A is used for
	; overwriting the RAM values.
	; X is still 0 from above.
	; A and Y are set to 0 now.
	TXA
	TAY

	; A, X and Y all have
	; the value 0 now.

	; Pointer + 0
	; always remains 0.
	; The address is calculated
	; by Pointer + 1
	; and by Y.
	; X, which acts as a
	; counter, always has to be
	; kept in sync with
	; Pointer + 1.
	STY Pointer + 0
	STX Pointer + 1

	; The fact that the pointer
	; will overwrite itself in
	; the following process is
	; not an issue:
	; Pointer + 0 is
	; always 0 anyway, so overwriting
	; it with 0 makes no problems.
	; And since the pointer is in the
	; zeropage, Pointer + 1
	; only gets overwritten with 0
	; when it's still 0 anyway.

@initializeRamLoop:

	; 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.
	CPX #>__NO_RESET_LOAD__
	BNE @noNoResetSkip

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

	; If we reach the reset-
	; persistent area, we change
	; the address and our counters
	; (Pointer + 1, X and Y)
	; to the first value after the area.
	; This way, the reset-persistent
	; area doesn't get overwritten.
	LDY #<(__NO_RESET_LOAD__ + __NO_RESET_SIZE__)
	LDX #>(__NO_RESET_LOAD__ + __NO_RESET_SIZE__)
	STX Pointer + 1

	; Since the C stack always comes
	; after the regular RAM variables,
	; no global variables will ever
	; be right at the end of the RAM.
	; So, we don't need to check here
	; whether writing to the next
	; memory location is even allowed
	; anymore. We can be sure that we
	; haven't reached $0800 yet.

@noNoResetSkip:

	; Set the value of 0,
	; which is still in A,
	; 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 in the pointer and
	; in 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
Post Reply