Initialization and low level stuff correct?

Discuss technical or other issues relating to programming the Nintendo Entertainment System, Famicom, or compatible systems.

Moderator: Moderators

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

Initialization and low level stuff correct?

Post by DRW »

Before I go on with my program, I'd like to be sure that all the NES-specific initialization and low level stuff is correct. Therefore, could you please have a look at the below code and tell me if there's still something wrong with it? (I removed all the custom code. This is just the bare bone initialization.)

Code: Select all

.segment "HEADER"

	.byte "NES", $1A
	.byte $02
	.byte $01
	.byte $01
	.byte $00

.segment "STARTUP"

.segment "ZEROPAGE"

	PointerLowByte: .res 1
	PointerHighByte: .res 1

.segment "CODE"

Reset:
	SEI
	CLD
	LDX #$40
	STX $4017
	LDX #$FF
	TXS
	INX
	STX $2000
	STX $2001
	STX $4010

WaitForVBlank1:
	BIT $2002
	BPL WaitForVBlank1

	; Set RAM memory to 0	
	LDA #$00
	STA PointerLowByte
	STA PointerHighByte
	LDX #$00
	LDY #$00
@outerLoop:
@innerLoop:
	STA (PointerLowByte), Y
	INY
	CPY #$00
	BNE @innerLoop
	INC PointerHighByte
	INX
	; The Sprite values are skipped
	CPX #$02
	BNE @notAtSpriteAddress
	INC PointerHighByte
	INX
@notAtSpriteAddress:
	CPX #$08
	BNE @outerLoop
	
	; Set sprite RAM memory to outside the screen
	LDA #$F4
	LDX #$00
@loop:
	STA $0200, X
	INX
	BNE @loop

WaitForVBlank2:
	BIT $2002
	BPL WaitForVBlank2
	
	LDA #%10010000
	STA $2000
	LDA #%00011110
	STA $2001

GameLogic:
	; Custom code:
        ; Checking if game logic may be run.
	; Constoller reading.
	; General algorithms.
	; Then jump back.
	JMP GameLogic

Nmi:
	PHA
	TYA
	PHA
	TXA
	PHA

	; Check if NMI may be accessed in the moment, otherwise jump to NmiEnd
	; ...

	; Sprites
	LDA #$00
	STA $2003
	LDA #$02
	STA $4014

	; Background, palettes, scrolling updates
	; ...
				
NmiEnd:
	PLA
	TAX
	PLA
	TAY
	PLA
	RTI
	
.segment "VECTORS"
	; These three bytes put the next values to address $FFFA
	.word $00, $00, $00
	.word Nmi
	.word Reset
	.word 0

.segment "CHARS"
	.incbin "Graphics.chr"
Available now: My game "City Trouble".
Website: https://megacatstudios.com/products/city-trouble
Trailer: https://youtu.be/IYXpP59qSxA
Gameplay: https://youtu.be/Eee0yurkIW4
German Retro Gamer article: http://i67.tinypic.com/345o108.jpg
User avatar
rainwarrior
Posts: 8062
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: Initialization and low level stuff correct?

Post by rainwarrior »

Add an extra bit $2002 before WaitForVBlank. It's possible for a reset to catch a partial vblank, leaving you with not enough time for the PPU to warm up. Clearing the flag by reading $2002 before you start polling it ensures that you don't get a partial vblank.

You can reserve 2 bytes for a pointer instead of having 2 separate labels for it:

Code: Select all

Pointer: .res 2
sta pointer+0 ; low
sta pointer+1 ; high
lda (pointer), Y
INY sets the Zero flag based on Y, so a CPY #0 after INY is unnecessary.

Code: Select all

iny
cpy #0 ; redundant
Why do you have 3 extra words in your vector segment?
User avatar
Kasumi
Posts: 1293
Joined: Wed Apr 02, 2008 2:09 pm

Re: Initialization and low level stuff correct?

Post by Kasumi »

Looks good, aside from what rainwarrior mentioned. There are smaller/faster ways to zero out your RAM/set that sprite page off screen if you're interested.
User avatar
DRW
Posts: 2070
Joined: Sat Sep 07, 2013 2:59 pm

Re: Initialization and low level stuff correct?

Post by DRW »

Thanks a lot for your help.
rainwarrior wrote:Add an extra bit $2002 before WaitForVBlank.
O.k., I'll do.
Only before the first wait, right?
rainwarrior wrote:You can reserve 2 bytes for a pointer instead of having 2 separate labels for it
Right. In this case, I don't need two variable names.
rainwarrior wrote:INY sets the Zero flag based on Y, so a CPY #0 after INY is unnecessary.
Yup. That's of course right: A BEQ/BNE doesn't have to be preceeded by a comparison if you set the variable and then try to compare to 0. Good observation.
rainwarrior wrote:Why do you have 3 extra words in your vector segment?
This has to do with the addresses in the default nes.cfg file:

Code: Select all

ROMV: file = %O, start = $FFF6, size = $000C, fill = yes;
...
VECTORS: load = ROMV, type = rw;
This puts the code to $FFFA. I got this from here:
https://bitbucket.org/ddribin/nerdy-nig ... at=default
Kasumi wrote:There are smaller/faster ways to zero out your RAM/set that sprite page off screen if you're interested.
Sure. What do you have in mind?


Two more questions:

Is there a specific reason why this code:

Code: Select all

   LDX #$40
   STX $4017
   LDX #$FF
   TXS
   INX
   STX $2000
   STX $2001
   STX $4010
uses the X register instead of the A register?

And why are the sprites put to $0200? It's in the middle of the RAM. Doesn't this mean that it's possible that my general variables go into that location as well if I have enough of them? Why aren't the sprites just put to $0700-$07FF? Why is $0200 even an option?
Available now: My game "City Trouble".
Website: https://megacatstudios.com/products/city-trouble
Trailer: https://youtu.be/IYXpP59qSxA
Gameplay: https://youtu.be/Eee0yurkIW4
German Retro Gamer article: http://i67.tinypic.com/345o108.jpg
User avatar
Kasumi
Posts: 1293
Joined: Wed Apr 02, 2008 2:09 pm

Re: Initialization and low level stuff correct?

Post by Kasumi »

DRW wrote:Sure. What do you have in mind?

Code: Select all

   INX;X is now zero
   STX $2000
   STX $2001
   STX $4010

WaitForVBlank1:
   BIT $2002
   BPL WaitForVBlank1
ClearRam:
   STX PointerLowByte
   LDY #$07
   STY PointerHighByte
   
   TXA
   TAY
ClearLoop:
   STA (PointerLowByte),y
   INY
   BNE cr.loop
   DEC PointerHighByte
   BPL ClearLoop
   
   STA PointerHighByte;This RAM was made $FF by the loop, so now it's 0 again
   
   LDA #$FF
SpriteClearLoop:
   STA $0200,y
   INY
   BNE SpriteClearLoop
Your code adds exceptions to avoid writing zeroes to $0200-$02FF, but there's no reason to do that because you're going to overwrite it later anyway. It's often faster to write one value, and fix it if you need to, rather than adding exceptions. Also mine counts down on the pointer instead of up. This is also often faster. There's a way to check for zero and a way to check for negative. You could can count down from 8, 16 or whatever and use those checks without a cmp. You can't count up to 8 or 16 or whatever without a cmp.
One more question: Is there a specific reason why this code:

Code: Select all

 LDX #$40
STX $4017
LDX #$FF
TXS
INX
STX $2000
STX $2001
STX $4010
uses the X register instead of the A register?
Because of the TXS. You could use A, but then you'd have to use TAX to get the #$FF into X to do TXS. Or do LDX #$FF, which is worse. Then later, it increases to #$00 with INX. To get zero in A you'd have to do LDA #$00 which is an extra byte.

Edit because edit :wink: :
And why are the sprites put to $0200? It's in the middle of the RAM. Doesn't this mean that it's possible that my general variables go into that location as well if I have enough of them? Why aren't the sprites just put to $0700-$07FF? Why is $0200 even an option?
$0000-$00FF is the zero page. This RAM is faster to access than other RAM, it'd be a waste to put your sprites there when you could use it for other things. $0100-$01FF is the stack. While you can use some of the stack RAM for other things, a sprite DMA copies the whole page so that's no good either. So $0200-$02FF is the first free page. But if you don't like that, you can use another page of RAM like $0700-$07FF if you want. You could even use ROM (if your sprites were static).
lidnariq
Posts: 10677
Joined: Sun Apr 13, 2008 11:12 am
Location: Seattle

Re: Initialization and low level stuff correct?

Post by lidnariq »

DRW wrote:uses the X register instead of the A register?
Because INX and TXS exist, and INA and TAS don't.
And why are the sprites put to $0200?
It's arbitrary, and the two conventions are at the "bottom" (at $02xx, since $00xx and $01xx are "reserved", ish) and the "top" (at $07xx)
It's in the middle of the RAM.
Sorta... because the NES's RAM is mirrored, everywhere is sorta the middle.

(My port of Driar to NROM relies on this, and uses a contiguous chunk of RAM from $0096 to $0895, instead of the normal $0000 to $07FF)
Doesn't this mean that it's possible that my general variables go into that location as well if I have enough of them?
You're going to have to reserve 256 bytes of RAM for the OAM shadow regardless, so it doesn't really matter where it is. And it also doesn't really matter unless you happen to need lots of contiguous RAM—fragmentation isn't too much of a problem since the 6502 makes dealing with arrays that are larger than 256 bytes somewhat annoying.
Why is $0200 even an option?
Cartridges that provide extra RAM sometimes use that memory for OAM preparation; there they'd want to write $60 (or whatever)
User avatar
DRW
Posts: 2070
Joined: Sat Sep 07, 2013 2:59 pm

Re: Initialization and low level stuff correct?

Post by DRW »

I'll have a look at your init code later.
Kasumi wrote:Because of the TXS.
O.k., so, X is the only register that can be put to the stack pointer.
Kasumi wrote:$0100-$01FF is the stack. [...] So $0200-$02FF is the first free page.
Alright, I wasn't aware of that. Well, in this case, it is put at the best location.

But how does the compiler know that $0200-$02FF is occupied? If I declare a non-zeropage variable, wouldn't he put it at the first possible location? Doesn't that mean that I have to declare a .res 256 at location $0200, so that the compiler doesn't put my other variables there?

(By the way, is there a way in CC65 to output the values of every label?)
Available now: My game "City Trouble".
Website: https://megacatstudios.com/products/city-trouble
Trailer: https://youtu.be/IYXpP59qSxA
Gameplay: https://youtu.be/Eee0yurkIW4
German Retro Gamer article: http://i67.tinypic.com/345o108.jpg
User avatar
rainwarrior
Posts: 8062
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: Initialization and low level stuff correct?

Post by rainwarrior »

DRW wrote:But how does the compiler know that $0200-$02FF is occupied? If I declare a non-zeropage variable, wouldn't he put it at the first possible location? Doesn't that mean that I have to declare a .res 256 at location $0200, so that the compiler doesn't put my other variables there?
You can .res 256, but it's probably not the best way to solve this problem. When linking many files together, it's easy to neglect the ordering of linked files and the reservation might end up in a different place than you expect. If you want something to be placed in a specific location, it's best to use the .cfg file to make a special segment for it. If you start your "RAM" segment at $300, that will make sure nothing makes reservations in the $200-2FF range.

While you're at it, move your "VECTORS" segment up to $FFFA and take back those lost bytes. There's no reason for it to be at $FFF6. (Where did you get this "nsf.cfg" file?)

If you'd like an alternative example to look at, I made one a while ago: viewtopic.php?t=11151 (example.cfg shows a good method for reserving the OAM, but also demonstrates other things, like my own version of the "standard" reset, good practices for writing to VRAM, etc.)
(By the way, is there a way in CC65 to output the values of every label?)
Yes. When using ca65 to assemble, add -g to your command line to store all labels in the compiled object. Then, when using ld65 to link, add -Ln labels.txt to your command line to export all labels to a text file.
User avatar
Memblers
Site Admin
Posts: 3901
Joined: Mon Sep 20, 2004 6:04 am
Location: Indianapolis
Contact:

Re: Initialization and low level stuff correct?

Post by Memblers »

Something else that's good to do during initialization is clear the nametable memory. Unless your game specifically writes to all PPU addresses before displaying anything on the screen. I always use CHR-RAM, so I clear all of PPU's memory from $0000-$2FFF.
User avatar
DRW
Posts: 2070
Joined: Sat Sep 07, 2013 2:59 pm

Re: Initialization and low level stuff correct?

Post by DRW »

Again, thanks for all your help.

Alright, I had a look at the default NES config file again. (In the moment, I don't use a custom one. I just specify -t nes. I'll hazzle with that later when my game actually displays something other than sample graphics.)
And the config file specifies RAM at $6000 with a size of $2000.
So, the sprite question isn't an issue. But do you know why the RAM is set relatively small? I mean, it's still big enough, but it's not the maximum that's possible.

I'll surely move the VECTORS segment as soon as I set up my own config file. Then I'll also have a look at the threads about this topic.

Memblers wrote:Something else that's good to do during initialization is clear the nametable memory.
This is a possibility, but I don't think it's necessary for me. On startup, the game will show the title screen. And between the title screen and the actual gameplay, I'll disable the NMI again, write the whole image for two screens and then enable it again. So, it gets initialized in time. I don't think the undefined "garbage" data will ever be visible on screen, will it?
Available now: My game "City Trouble".
Website: https://megacatstudios.com/products/city-trouble
Trailer: https://youtu.be/IYXpP59qSxA
Gameplay: https://youtu.be/Eee0yurkIW4
German Retro Gamer article: http://i67.tinypic.com/345o108.jpg
mikaelmoizt
Posts: 120
Joined: Sat Apr 12, 2014 12:11 pm
Location: Gothenburg, Sweden

Re: Initialization and low level stuff correct?

Post by mikaelmoizt »

I dont know if your assembler fills the header with zeroes for you.. but I always have to fill header to 16 bytes.

Edit: hmm.. maybe have to is the wrong word here seeing its probably going to be all zeroes anyway. But I like to think it's better to be certain.
Last edited by mikaelmoizt on Thu Aug 06, 2015 3:50 am, edited 1 time in total.
I´ve got %01100011 problems but the BITs aint one.
User avatar
DRW
Posts: 2070
Joined: Sat Sep 07, 2013 2:59 pm

Re: Initialization and low level stuff correct?

Post by DRW »

The config file in CC65 has a parameter for filling the bytes and it is set to true in most of the segments, so i don't need to write additional zeroes.
Available now: My game "City Trouble".
Website: https://megacatstudios.com/products/city-trouble
Trailer: https://youtu.be/IYXpP59qSxA
Gameplay: https://youtu.be/Eee0yurkIW4
German Retro Gamer article: http://i67.tinypic.com/345o108.jpg
tepples
Posts: 22345
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Re: Initialization and low level stuff correct?

Post by tepples »

DRW wrote:And the config file specifies RAM at $6000 with a size of $2000. [...] do you know why the RAM is set relatively small?
Because the RAM available to a running NES program is "relatively small".

$0000-$07FF is RAM inside the Control Deck.
$0800-$401F is assigned by the system in other ways.
$4020-$5FFF is usually unused, occasionally used for Game Paks' mapper ports.
$6000-$7FFF is optional extra RAM in the Game Pak.
$8000-$FFFF is almost always ROM in the Game Pak (when read) or mapper ports (when written).

Not all Game Paks come with extra RAM. It's an extra cost option. Mostly FDS ports (Metroid, Kid Icarus, The Legend of Zelda, Super Mario Bros. 2) and other games with battery save (Final Fantasy, Zelda II, Earthbound, Koei games) will have it. In other games that have extra RAM (Super Mario Bros. 3, M.C. Kids), the developer may have had to fight with the publisher to approve the cost of extra RAM. Few homebrew PCBs with all new parts will have it, especially because the homebrew game manufacturers are moving toward using the first pages of flash memory for saves.

To get you started, here's the linker config that I use for NROM-128 games (16384 bytes PRG ROM, 8192 bytes CHR ROM):

Code: Select all

MEMORY {
  ZP:     start = $10, size = $f0, type = rw;
  # use first $10 zeropage locations as locals
  HEADER: start = 0, size = $0010, type = ro, file = %O, fill=yes, fillval=$00;
  RAM:    start = $0300, size = $0500, type = rw;
  # $0300 including a carve-out for OAM at $0200-$02FF
  ROM7:    start = $C000, size = $4000, type = ro, file = %O, fill=yes, fillval=$FF;
  CHRROM:  start = $0000, size = $2000, type = ro, file = %O, fill=yes, fillval=$FF;
}

SEGMENTS {
  INESHDR:  load = HEADER, type = ro, align = $10;
  ZEROPAGE: load = ZP, type = zp;
  BSS:      load = RAM, type = bss, define = yes, align = $100;
  DMC:      load = ROM7, type = ro, align = 64, optional = yes;
  CODE:     load = ROM7, type = ro, align = $100;
  RODATA:   load = ROM7, type = ro, align = $100;
  VECTORS:  load = ROM7, type = ro, start = $FFFA;
  CHR:      load = CHRROM, type = ro, align = 16, optional = yes;
}

FILES {
  %O: format = bin;
}
User avatar
Movax12
Posts: 529
Joined: Sun Jan 02, 2011 11:50 am

Re: Initialization and low level stuff correct?

Post by Movax12 »

rainwarrior wrote:While you're at it, move your "VECTORS" segment up to $FFFA and take back those lost bytes. There's no reason for it to be at $FFF6. (Where did you get this "nsf.cfg" file?)
I'm sure there used to be a way to get cc65.exe (I think) to dump its linker configs, but I can't find it now. Anyway, this is the config for -t nes:

https://github.com/cc65/cc65/blob/master/cfg/nes.cfg

For some reason the vectors are off and have been for a very long time. Maybe a misunderstanding or bug from years ago. Someone should fix. (I might submit a patch at some point).

Anyway, this has been covered: viewtopic.php?f=2&t=10481
User avatar
rainwarrior
Posts: 8062
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: Initialization and low level stuff correct?

Post by rainwarrior »

CC65's default NES library and target is pretty useless for games anyway. I think its goal was limited portability of apps from other 6502 platforms, so it's not really set up properly for NES games.

You should use your own config file tailored to your project instead of -t NES. Sooner or later you'll need to do something specific that a generic config could/should never do. (The OAM buffer is one example.)
Post Reply