This page is a mirror of Tepples' nesdev forum mirror (URL TBD).
Last updated on Oct-18-2019 Download

Initialization and low level stuff correct?

Initialization and low level stuff correct?
by on (#152483)
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:
.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"
Re: Initialization and low level stuff correct?
by on (#152484)
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:
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:
iny
cpy #0 ; redundant


Why do you have 3 extra words in your vector segment?
Re: Initialization and low level stuff correct?
by on (#152485)
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.
Re: Initialization and low level stuff correct?
by on (#152486)
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:
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-nights/src/e3d439692ddab25f3ba8bf8bafc3533b64e88b30/06-backgrounds2/background2.asm?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:
   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?
Re: Initialization and low level stuff correct?
by on (#152487)
DRW wrote:
Sure. What do you have in mind?

Code:
   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.

Quote:
One more question: Is there a specific reason why this code:
Code:
 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: :

Quote:
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).
Re: Initialization and low level stuff correct?
by on (#152488)
DRW wrote:
uses the X register instead of the A register?
Because INX and TXS exist, and INA and TAS don't.

Quote:
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)
Quote:
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)

Quote:
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.

Quote:
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)
Re: Initialization and low level stuff correct?
by on (#152489)
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?)
Re: Initialization and low level stuff correct?
by on (#152490)
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: http://forums.nesdev.com/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.)

Quote:
(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.
Re: Initialization and low level stuff correct?
by on (#152491)
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.
Re: Initialization and low level stuff correct?
by on (#152492)
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?
Re: Initialization and low level stuff correct?
by on (#152493)
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.
Re: Initialization and low level stuff correct?
by on (#152494)
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.
Re: Initialization and low level stuff correct?
by on (#152497)
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:
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;
}
Re: Initialization and low level stuff correct?
by on (#152498)
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
Re: Initialization and low level stuff correct?
by on (#152499)
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.)
Re: Initialization and low level stuff correct?
by on (#152501)
Thanks again for the answers.

Yeah, the config file is something that I have to take a look into. Same with the nes.lib. I'll put this thread into my todo list and get back to those specific things later. But before I do external configurations, I want my game to at least do something game-related.
Re: Initialization and low level stuff correct?
by on (#152521)
I feel that getting things like your segments and memory layout correct are pretty important first steps.
Re: Initialization and low level stuff correct?
by on (#152523)
Sure, but the defalt cfg file is already "correct enough" for the game to run, isn't it? Therefore, everything else would just be optimization.
Re: Initialization and low level stuff correct?
by on (#152539)
DRW wrote:
Sure, but the defalt cfg file is already "correct enough" for the game to run, isn't it? Therefore, everything else would just be optimization.

Not really. The config is as much a part of your program as the other source code. Specifically it is the part of the program that tells the compiler exactly where to put things in memory/ROM.

There are plenty of cases on the NES where you need something in a specific place (OAM page alignment and vectors are examples we've already seen). DPCM samples are another one. If you ever need more than 32k of PRG or 8k of CHR (i.e. bankswitching) you will need a custom config for the mapper used.

Memory layout isn't really an optimization, it's a fundamental part of your program. (Technically if you're really counting cycles, sometimes aligning some code so that it doesn't cross a 256 byte page boundary will save a cycle, so, yes, sometimes it is an optimization.)

A proper config file might only really contain 10 or 15 lines of code. The example I linked earlier is relatively minimal, and probably has a lot less confusing cruft than nes.cfg, which is filled with all sorts of things that have to do with a C compiler and aren't related to what you need to do at all.
Re: Initialization and low level stuff correct?
by on (#152549)
Well, as I said: I will use an own config file. It's not that I want to avoid this. So, I will definitely get back to this thread and read your suggestions.
It's just that in the moment, I'd rather put something useful on screen and do the interesting stuff, like letting my character walk through the level. After having cleaned up the whole low level stuff, I'm eager to do something more creative now.
That's why I'll take care of the config file in a while and use the default one in the meantime.