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

Problem with the "nes.cfg" and 1 x 16 KB PRG

Problem with the "nes.cfg" and 1 x 16 KB PRG
by on (#117873)
In the moment, I'm a bit lost with that "nes.cfg" file. My problem is: The default one is built for a game with 2 x 16KB PRG code. But I want a game with 1 x 16KB PRG code.
For some reason, the config files from this link always give me some overflow errors.

So, I'm straight out asking: What do I have to do?

This is my "nes.cfg":
Code:
MEMORY
{
   ZP:     start =    $2, size =   $1A,               type = rw,  define = yes;
   HEADER: start =    $0, size =   $10, file = %O,    fill = yes;
   ROM0:   start = $8000, size = $7FF4, file = %O,    fill = yes, define = yes;
   ROMV:   start = $FFF6, size =    $C, file = %O,    fill = yes;
   ROM2:   start = $0000, size = $2000, file = %O,    fill = yes;
   SRAM:   start = $0500, size = $0300,                           define = yes;
   RAM:    start = $6000, size = $2000,                           define = yes;
}

SEGMENTS
{
   HEADER:   load = HEADER,            type = ro;
   STARTUP:  load = ROM0,              type = ro,  define = yes;
   LOWCODE:  load = ROM0,              type = ro,                optional = yes;
   INIT:     load = ROM0,              type = ro,  define = yes, optional = yes;
   CODE:     load = ROM0,              type = ro,  define = yes;
   RODATA:   load = ROM0,              type = ro,  define = yes;
   DATA:     load = ROM0,   run = RAM, type = rw,  define = yes;
   VECTORS:  load = ROMV,              type = rw;
   CHARS:    load = ROM2,              type = rw;
   BSS:      load = RAM,               type = bss, define = yes;
   HEAP:     load = RAM,               type = bss,               optional = yes;
   ZEROPAGE: load = ZP,                type = zp;
}

FEATURES
{
   CONDES: segment = INIT,   type = constructor, label = __CONSTRUCTOR_TABLE__, count = __CONSTRUCTOR_COUNT__;
   CONDES: segment = RODATA, type = destructor,  label = __DESTRUCTOR_TABLE__,  count = __DESTRUCTOR_COUNT__;
   CONDES: segment = RODATA, type = interruptor, label = __INTERRUPTOR_TABLE__, count = __INTERRUPTOR_COUNT__;
}

SYMBOLS
{
   __STACKSIZE__ = $0300;
}

And yes, I know, there might be some things in there that can be thrown out later. But I will take care of this as soon as everything works. In the moment, I'd only like to solve my problem. I will take care of the cleanup afterwards.

Alright, if you take this source code: background2.asm and this chr file: mario.chr and compile it with
Code:
cl65 -t none -C nes.cfg -o Test.nes background2.asm
then everything works fine.

But as I said, I want 1 x 16 KB PRG code. So, I change the header in the asm file from
Code:
.segment "HEADER"
   
   .byte   "NES", $1A   ; iNES header identifier
   .byte   2      ; 2x 16KB PRG code
   .byte   1      ; 1x  8KB CHR data
   .byte   $01, $00   ; mapper 0, vertical mirroring
to
Code:
.segment "HEADER"
   
   .byte   "NES", $1A   ; iNES header identifier
   .byte   1      ; 1x 16KB PRG code
   .byte   1      ; 1x  8KB CHR data
   .byte   $01, $00   ; mapper 0, vertical mirroring
(Line 4 was changed.)

Now, the nes file doesn't work anymore. It only shows a gray screen in fceux and a black screen in Nestopia.

What do I have to change in the above "nes.cfg" so that the program works again?
I tried various things, like cutting down memory areas or using one of the sample cfg files. But it didn't work. So, could you please tell me which values I have to edit to make the program work as a 24 KB ROM instead of a 40 KB ROM?
Re: Problem with the "nes.cfg" and 1 x 16 KB PRG
by on (#117875)
Try changing the start of memory area ROM0 to $C000 and subtracting $4000 from its size.
Re: Problem with the "nes.cfg" and 1 x 16 KB PRG
by on (#117876)
Thanks. That worked. Even though it was already enough to subtract the value. Why is it necessary to set the start location from $8000 to $C000? I mean, according to the Nerdy Nights tutorial's graphic, $8000 is the location where the cartridge ROM starts. Why should the whole ROM code be moved to the center of the cartridge ROM section? Wouldn't the start location on a real cartridge be at byte $8000 too? Or does it indeed start at $C000 if there's only 1 x 16 KB PRG? And if yes, why? Why is the lower part cut away instead of the upper part?
Re: Problem with the "nes.cfg" and 1 x 16 KB PRG
by on (#117878)
The output file will still be 32k PRG if all you did was change the header. I believe most emulators would read the header and load the first 16KB PRG chunk only into $C000 (mirrored to $8000), which means all the vectors point to $0000 or whatever your fill value for ROM is.
Re: Problem with the "nes.cfg" and 1 x 16 KB PRG
by on (#117879)
DRW wrote:
Why is it necessary to set the start location from $8000 to $C000?

It isn't. When you have only 16KB of PRG, it doesn't matter if it's assembled as $8000-$BFFF or $C000-$FFFF, both will work just fine.

Quote:
I mean, according to the Nerdy Nights tutorial's graphic, $8000 is the location where the cartridge ROM starts.

Yes, but since there's only 16KB of data to fill a space of 32KB, it gets mirrored to fill the whole space, so your PRG does in fact get mapped to both $8000-$BFFF and $C000-$FFFF, which is why assembling it either way works.

Quote:
Why should the whole ROM code be moved to the center of the cartridge ROM section?

Not the center, the whole upper half. But it's not moved, just mirrored, it's still at $8000 as well.

Quote:
Why is the lower part cut away instead of the upper part?

I guess that by now you understand that nothing gets cut away... =)

Anyway, the reason why some people prefer to assemble 16KB of PRG at $C000-$FFFF is because of the interrupt vectors. The CPU will ALWAYS look for them at $FFFA-$FFFF, while $8000 is just the arbitrary location chosen by Nintendo to be the beginning of PRG ROM. In the end it doesn't matter, because even if your vectors are assembled at $BFFA-$BFFF, because of mirroring they will also show up at $FFFA-$FFFF.
Re: Problem with the "nes.cfg" and 1 x 16 KB PRG
by on (#117880)
Both addresses in $8000-$BFFF and addresses in $C000-$FFFF work. But for one thing, DMC assumes $C000-$FFFF. And for another, it's traditional to use $C000-$FFFF.

Advanced: Say you're making a multicart of NROM-128 games. For some common boards, either they must all be in $8000-$BFFF (if UNROM configured as mapper 2) or they must all be in $C000-$FFFF (if UNROM configured as mapper 180). But for other mappers, mixing the two can be advantageous.
Re: Problem with the "nes.cfg" and 1 x 16 KB PRG
by on (#117883)
You should note that the start address in the memory section do not matter for how the ROM file is output (sizes do). Start address only tell the linker the offset in ROM for things like absolute addresses. For example, your start address for the ROMV is wrong, it should be $FFF4, but it doesn't matter since there is no code in the segment. (Ignoring the mistake that the vectors start at $FFFA).
Re: Problem with the "nes.cfg" and 1 x 16 KB PRG
by on (#117931)
Thanks for the answers.

I put ROM 0 to start = $C000, size = $3FF4 and corrected ROMV start to $FFF4 because I want the values to be the correct and common ones, even if the game would work with other values as well.

But why does ROMV start at $FFF4? The Nerdy Nights tutorial said it's from $FFFA to $FFFF. So, instead of
Code:
ROM0: start = $C000, size = $3FF4, file = %O, fill = yes, define = yes;
ROMV: start = $FFF4, size = $C, file = %O, fill = yes;

wouldn't this be more correct:
Code:
ROM0: start = $C000, size = $3FFA, file = %O, fill = yes, define = yes;
ROMV: start = $FFFA, size = $6, file = %O, fill = yes;
?
Or is the tutorial wrong?
(Yes, I know it might not make a practical difference to the game file. But I'd like to have the correct values anyway.)

Now it's time for cleaning up. Here, I've got a bunch of questions as well:

What's LOWCODE and INIT good for? Is this something that should actually be used because there is a difference to normal code? Or can I throw it out completely?

CODE and RODATA are both declared with the same parameters and they both appear right after another. So, isn't RODATA completely unnecessary?
Or is there any special thing that has to do with the fact that it's referenced in the FEATURES section again? What's the deal with this CONDES command anyway? In the manual, it's written: "CONDES is used to tell the linker to emit module constructor/destructor tables." O.k., what does that mean in practice? Do I need it? Does anybody use it?

Why does ZP (zero page) start at 2 with a size of $1A? Shouldn't it start at $00 with a size of $FF?

As far as I know, the zero page is there for quicker access. Is this used in real NES games? If yes, what for? To store the most commonly accessed variables?
So, if I write LDA $05 that's a quicker command than if I write LDA $0005 or LDA $0505?
Do I have to pay attention to anything if I use the zero page?

In the assembly file, what kind of code would go into the STARTUP segment? Or is this always empty, like in the example code?

The memory location that is declared simply as RAM has the values start = $6000, size = $2000. This looks like the WRAM in the NES. Since my game will be built like a first generation game and those didn't have WRAM, I assume I can just delete it and don't need to declare it at all?

Why does the SRAM section go from $0500 to $07FF (i.e. size = $0300)? Shouldn't RAM go from $0000 to $07FF?

In the Nerdy Nights examples that are for NESASM, the command
Code:
  .bank 1
  .org $E000
is put right before the constants that define palette, sprite, background and attribute data. (The ones that are declared with .db in NESASM and .byte in CC65.)
Does the address $E000 have a special meaning so that, in CC65, I should declare such a memory location (by splitting ROM0 into two values: one from $C000 to $DFFF and one from $E000 to the end) and then write the corresponding .segment command right before the constant data definitions? Or was $E000 just an arbitrary value? Because in the CC65 example, there is no special segment declaration before it and the constants appear right after the RTI command.

Does the symbol __STACKSIZE__ have any meaning for an NES program?

I hope you don't mind that I ask so many questions, but those are the things that I didn't manage to find out by looking through that manual. And I finally want to finish my nes.cfg stuff so that I can continue concentrating on the actual source code.
Re: Problem with the "nes.cfg" and 1 x 16 KB PRG
by on (#117933)
DRW wrote:
wouldn't this be more correct:
Code:
ROM0: start = $C000, size = $3FFA, file = %O, fill = yes, define = yes;
ROMV: start = $FFFA, size = $6, file = %O, fill = yes;


Yes.
Even better: (skipping over other MEMORY and SEGMENT definitions)

Code:
MEMORY
{
   ROM0:   start = $C000, size = $4000, file = %O,    fill = yes, define = yes;
}

SEGMENTS
{
   CODE:     load = ROM0,              type = ro,  define = yes;
   VECTORS:  load = ROM0, start = $FFFA
}


If you are coding in assembler, and you are not using C (CC65), you don't need most of those things. You can use constructors, but I wouldn't worry about that yet.

If you create an identifier in zeropage, ca65 will use zeropage instructions when accessing it.

This is my NROM config, it has some extra things that probably aren't really needed, like the stack segment.

There is nothing special about $E000 that I am aware of.
Re: Problem with the "nes.cfg" and 1 x 16 KB PRG
by on (#117954)
What's special about $E000 will become clear once you start using the MMC3. It splits the ROM into 8K banks, and $E000-$FFFF is the only bank guaranteed to be available at all times. But for now it's just a quirk of NESASM having been originally written to target the TurboGrafx-16, which also uses 8K banks.
Re: Problem with the "nes.cfg" and 1 x 16 KB PRG
by on (#117957)
DRW wrote:
What's LOWCODE and INIT good for? Is this something that should actually be used because there is a difference to normal code? Or can I throw it out completely?

You can throw it out.

Quote:
CODE and RODATA are both declared with the same parameters and they both appear right after another. So, isn't RODATA completely unnecessary?

It's unnecessary, but it can make your program somewhat easier to understand because you'd have a clear separation of data from code. RODATA stands for "Read Only Data". Mostly it's there just because the C compiler separates the data from the code.

Quote:
Why does ZP (zero page) start at 2 with a size of $1A? Shouldn't it start at $00 with a size of $FF?

The author of the config may have wanted to reserve some values for other uses. But yes, generally it should start from $00 with a size of $100 (off by one, once again! :)).

Quote:
As far as I know, the zero page is there for quicker access. Is this used in real NES games? If yes, what for? To store the most commonly accessed variables?
So, if I write LDA $05 that's a quicker command than if I write LDA $0005 or LDA $0505?
Do I have to pay attention to anything if I use the zero page?

Zero page is quicker to access, and it's also the only way to access memory indirectly (you'll place the 16-bit pointer on zero page). Note that in CA65's eyes "LDA $05" and "LDA $0005" are the exact same thing; it will always use zero page addressing for it. To force absolute addressing, the syntax was something like LDA a:$05 (not tested).

Quote:
The memory location that is declared simply as RAM has the values start = $6000, size = $2000. This looks like the WRAM in the NES. Since my game will be built like a first generation game and those didn't have WRAM, I assume I can just delete it and don't need to declare it at all?

Yes, delete it.

Quote:
Why does the SRAM section go from $0500 to $07FF (i.e. size = $0300)? Shouldn't RAM go from $0000 to $07FF?

SRAM is a very misleading name for this segment because in NES circles SRAM is usually taken to mean "Save RAM" (at $6000-7FFF). So you should probably rename it to "RAM". I have no idea why they chose that start address and size, probably once again to reserve certain areas of memory for something else. But you can't make it start from $0000, that would overlap with zero page ($00-$FF) and stack ($100-1FF). You can make it start from $200, unless you want to reserve the page at $200 for shadow OAM, in which case it should start at $300.

Quote:
In the Nerdy Nights examples that are for NESASM, the command
Code:
  .bank 1
  .org $E000
is put right before the constants that define palette, sprite, background and attribute data. (The ones that are declared with .db in NESASM and .byte in CC65.)
Does the address $E000 have a special meaning so that, in CC65, I should declare such a memory location (by splitting ROM0 into two values: one from $C000 to $DFFF and one from $E000 to the end) and then write the corresponding .segment command right before the constant data definitions? Or was $E000 just an arbitrary value? Because in the CC65 example, there is no special segment declaration before it and the constants appear right after the RTI command.

It's not arbitrary, in that $E000 is the starting address of the upper 8 KB of a 16 KB ROM. NESASM requires it, because it requires the ROM to be split into 8 KB banks (due to its legacy as a PC-Engine assembler). CA65 doesn't have such limitations, so you can put everything in a nice, linear "bank" (segment).

Quote:
Does the symbol __STACKSIZE__ have any meaning for an NES program?

Only if you write your program in C.
Re: Problem with the "nes.cfg" and 1 x 16 KB PRG
by on (#117975)
Thanks for all the help. I altered the cfg file accordingly. But unfortunately, it's still not finished since I have some remaining questions.

Movax12 wrote:
Even better: (skipping over other MEMORY and SEGMENT definitions)

Code:
MEMORY
{
   ROM0:   start = $C000, size = $4000, file = %O,    fill = yes, define = yes;
}

SEGMENTS
{
   CODE:     load = ROM0,              type = ro,  define = yes;
   VECTORS:  load = ROM0, start = $FFFA
}

Why do you consider it better? Wouldn't it be better if only the MEMORY section has absolute address values while the SEGMENTS is just there to group it logically? Somehow, from an abstraction point of view, it doesn't look ideal to me to place the memory location at the SEGMENTS. Why should the VECTORS be a segment inside the ROM0 memory instead of a separate memory area?

Movax12 wrote:
If you are coding in assembler, and you are not using C (CC65), you don't need most of those things. You can use constructors, but I wouldn't worry about that yet.

Well, my game will be in C. In the moment, I use only assembly to understand the inner workings. But the game logic shall be written in C as soon as I understand the assembly program completely.

tepples wrote:
What's special about $E000 will become clear once you start using the MMC3.

I guess that won't happen in the near future. In the moment, all I wanna do is write a simple 24 KB, non-scrolling mapper 0 game.

thefox wrote:
It's unnecessary, but it can make your program somewhat easier to understand because you'd have a clear separation of data from code.

Yeah, that makes sense. Alright, I'll keep it so that when you look at the source code, you can differentiate between it.

thefox wrote:
But yes, generally it should start from $00 with a size of $100 (off by one, once again! :)).

You're right: $00 to $FF is $100 bytes. I totally overlooked that.

thefox wrote:
I have no idea why they chose that start address and size, probably once again to reserve certain areas of memory for something else. But you can't make it start from $0000, that would overlap with zero page ($00-$FF) and stack ($100-1FF). You can make it start from $200, unless you want to reserve the page at $200 for shadow OAM, in which case it should start at $300.

So, when I declare variables, may I use them in the memory location of $100-$1FF? Or what's the difference between stack and general RAM on the NES?

I looked at the commented version from the source code and it says this:
Code:
# $0100-$0200 cpu stack
# $0200-$0500 3 pages for ppu memory write buffer
# $0500-$0800 3 pages for cc65 parameter stack

Is this correct or do you still say I can make the RAM start at $0200?

thefox wrote:
Quote:
Does the symbol __STACKSIZE__ have any meaning for an NES program?

Only if you write your program in C.

Which I'm going to do. So, I assume the value has to be equal to my RAM size? (Or better yet: Declare the RAM size as size = __STACKSIZE__.)
What does the C compiler do when I omit the stack size, but the RAM section has a size of $300? Can the compiler define the stack size according to the RAM section?

I still have no idea what this actually does:
Code:
CONDES: segment = RODATA, type = destructor, label = __DESTRUCTOR_TABLE__, count = __DESTRUCTOR_COUNT__;
CONDES: segment = RODATA, type = interruptor, label = __INTERRUPTOR_TABLE__, count = __INTERRUPTOR_COUNT__;

"CONDES is used to tell the linker to emit module constructor/destructor tables."
"type: Describes the type of the routines to place in the table."

What does that mean? I don't get it. Especially for stuff like RODATA, i.e. constant readonly data. Why does that need a constructor or a destructor or whatever?


What I also noticed: How can both, the header and the CHARS/ROM2 data (i.e. the graphic tiles) start at location zero?


And why are VECTORS and CHARS declared as read/write? I don't want to change the tiles or the interrupt addresses during gameplay, do I?


What would get into the STARTUP code? In the moment, it's empty.


Alright, finally I know why people say that setting up a CC65 project can be a hassle.
But I'm really greatful for the detailed answers that I always get here. Those manuals can be a bit confusing sometimes, but when someone explains me specifically the one thing that I was asking for, I usually understand it immediately.
Re: Problem with the "nes.cfg" and 1 x 16 KB PRG
by on (#117977)
"Why should the VECTORS be a segment inside the ROM0 memory instead of a separate memory area?"

Because it's in the same bank of the same chip, for one thing. In projects for mappers with 16K banks (UNROM, MMC1), I typically use one MEMORY per bank.

"Well, my game will be in C. In the moment, I use only assembly to understand the inner workings. But the game logic shall be written in C as soon as I understand the assembly program completely."

I get it: you're writing C with assembly drivers, and you want to understand what's going on well enough to write the assembly drivers.

"So, when I declare variables, may I use them in the memory location of $100-$1FF? Or what's the difference between stack and general RAM on the NES?"

The instructions PHA, PHP, PLA, PLP, JSR, RTS, BRK, and RTI are hardwired to use the stack. But I've been known to use $0100-$01BF of the stack as general memory, usually for buffers that will be copied to video memory during the next vertical blank, and use only $01C0-$01FF for the stack.

"$0200-$0500 3 pages for ppu memory write buffer"

My guess is that's meant for the implementation of stdio that ships with cc65.

"$0500-$0800 3 pages for cc65 parameter stack"

That's used for local variables in C functions, and I think it can be made smaller. I'm under the impression that cc65's default config is designed for C programs that store the heap in on-cartridge work RAM at $6000-$7FFF.

"Why do I have to declare zero page and RAM location in the config file anyway?"

So that you can use the .RES keyword to allocate space for variables in those segments.

"If I declare one, don't I have to tell the assembler to store it at a specific location anyway, let's say, $0612?"

Not if you use .RES to state that the variable will be allocated at the next available space within the current segment.

"Will the assembler warn me if I try to declare a variable that's outside of the defined RAM area?"

If you have too much .RES for a given segment, the linker will warn you.

"Or are these values necessary for when I program in C (which I'm going to end up with) so that the compiler knows where to put something when I declare it as unsigned char status;?"

Yes. The C compiler translates those to .RES.

"CONDES"

Segments like this appear to be used for initialization before main() starts. For example, <stdio.h> wants to set up stdin and stdout, and C++ programs want to run constructors for global objects.

"And why are VECTORS and CHARS declared as read/write?

It might just be an error in a particular linker script.

"I don't want to change the tiles or the interrupt addresses during gameplay, do I?"

In a game with CHR ROM, you won't be changing tiles during gameplay. But if you use UNROM or SGROM/SNROM, you'll end up uploading tiles to the PPU fairly often. For example, Hatris and Qix modify CHR RAM to draw game objects smaller than one tile to the background, and Blargg and I wrote a library that renders a string to a set of tiles using a proportional font. But CHR RAM games don't use the CHARS segment; instead, they store tile data in the RODATA segment and upload the data to the PPU. And some games do route NMI and IRQ vectors to trampoline code in RAM so that they can use different vectors for different parts of the game.

A lot of the segments that you don't understand are more useful for programs that run on computers with a tape or disk drive and a keyboard, such as the Commodore 64, than on computers with an execute-in-place ROM and a joystick, such as the Nintendo Entertainment System.
Re: Problem with the "nes.cfg" and 1 x 16 KB PRG
by on (#117980)
tepples wrote:
Because it's in the same bank of the same chip, for one thing.

Yeah, that makes sense. I changed it.

tepples wrote:
The instructions PHA, PHP, PLA, PLP, JSR, RTS, BRK, and RTI are hardwired to use the stack. [...] My guess is that's meant for the implementation of stdio that ships with cc65.

So, I'll use start = $0200, size = $0600. I don't wanna run into the CPU stack, so I won't do anything funny with it. But since I don't intend to use any external source code files for the C project, I will use the RAM from $0200 as I don't have to worry about any functions clashing with my code.

tepples wrote:
"$0500-$0800 3 pages for cc65 parameter stack"

That's used for local variables in C functions, and I think it can be made smaller.

Why should it be made smaller? Isn't it better to make it bigger to have more memory? Or does this make the program slower even if the stack is not fully used?

Where in the RAM will the global variables in a C program be stored? Am I correct to assume that local variables will be stored in StackStartAddress while global variables are stored in StackStartAddress + StackSize?

Also, what happens if I exceed the stack by declaring too many local variables? (I know, this won't happen in practice. But let's just assume it does.) Will they produce a glitch or will they just be declared in the RAM section outside the stack? If the latter is the case, will this have any influence on how the program works (like for example that they're not freed after the function is left)?

I'm sorry that you got to see the questions about zero page and RAM declaration. After I checked one of my other threads and learned how to declare variables in CC65, it was suddenly clear to me and I deleted the questions here. (In NESASM you declare an absolute address first with .rsset, so I didn't make the connection to the segments.)

tepples wrote:
"CONDES"

Segments like this appear to be used for initialization before main() starts. For example, <stdio.h> wants to set up stdin and stdout, and C++ programs want to run constructors for global objects.

Is there even a C++ compiler for 6502 programs?
However, since I won't use any libraries, I assume it's safe to remove that stuff, right? Especially since in the default config file, after removing the WRAM stuff, the FEATURES section only has CONDES for RODATA anyway. And not for, like, STARTUP.

tepples wrote:
"And why are VECTORS and CHARS declared as read/write?

It might just be an error in a particular linker script.

I set it to readonly now since my game won't have anything that has to do with CHR RAM.

What I still like to know: How can both, the header and the CHARS/ROM2 data (i.e. the graphic tiles) start at location zero?

tepples wrote:
A lot of the segments that you don't understand are more useful for programs that run on computers with a tape or disk drive and a keyboard, such as the Commodore 64, than on computers with an execute-in-place ROM and a joystick, such as the Nintendo Entertainment System.

Yeah, it's good that the NES seems to be rather simple in comparison to the other systems.
Re: Problem with the "nes.cfg" and 1 x 16 KB PRG
by on (#117986)
DRW wrote:
What I still like to know: How can both, the header and the CHARS/ROM2 data (i.e. the graphic tiles) start at location zero?


1. The MEMORY locations are simply blocks of data that are output to the file in the other specified. The output file layout has nothing to do with their intended location on the CPU's memory bus.

2. CHR data is on the PPU's bus, not the CPU's, so its memory address is unrelated to the ones used for PRG.

3. If you use a mapper that supports PRG bank switching, you will create several PRG banks that have to use the same memory locations. The specified memory address is telling the linker where the code needs to be run from. Obviously you can't have two banks using the same memory location at the same time, but that is what bank switching is for. The PRG code still needs to be linked to run in the correct location.
Re: Problem with the "nes.cfg" and 1 x 16 KB PRG
by on (#117994)
DRW wrote:
tepples wrote:
"$0500-$0800 3 pages for cc65 parameter stack"

That's used for local variables in C functions, and I think it can be made smaller.

Why should it be made smaller? Isn't it better to make it bigger to have more memory? Or does this make the program slower even if the stack is not fully used?

Where in the RAM will the global variables in a C program be stored?

I was under the impression that at least one of the linker scripts floating around assumed that BSS (the segment containing global variables) would be stored in $6000-$7FFF, which isn't available on NROM unless you solder in a couple more ICs, namely a 6264 SRAM and a 74HC20 to decode it. (Family BASIC, for example, includes these ICs.) Making the stack smaller would allow moving global variables into internal memory.

Quote:
Is there even a C++ compiler for 6502 programs?

There are C++ compilers that output C. In fact, the first C++ compiler was such.

Quote:
However, since I won't use any libraries, I assume it's safe to remove that stuff, right? Especially since in the default config file, after removing the WRAM stuff, the FEATURES section only has CONDES for RODATA anyway. And not for, like, STARTUP.

CONDES, STARTUP, and LOWCODE aren't needed for simple freestanding NES use. LOWCODE is useful for carts using 32K bank switching so that the code to go from one bank to another can be copied into RAM so that it's always available.
Re: Problem with the "nes.cfg" and 1 x 16 KB PRG
by on (#118025)
Again, thanks a lot for your answers.

tepples wrote:
I was under the impression that at least one of the linker scripts floating around assumed that BSS (the segment containing global variables) would be stored in $6000-$7FFF, which isn't available on NROM unless you solder in a couple more ICs, namely a 6264 SRAM and a 74HC20 to decode it. (Family BASIC, for example, includes these ICs.) Making the stack smaller would allow moving global variables into internal memory.

So, is this still a fact or have you only been under this impression.

From the point of view of my own configuration now, what's the best value for __STACKSIZE__?
And where will each variable be declared later? Where will the local variables be declared and where the global ones?
Re: Problem with the "nes.cfg" and 1 x 16 KB PRG
by on (#118029)
DRW wrote:
tepples wrote:
I was under the impression that at least one of the linker scripts floating around assumed that BSS (the segment containing global variables) would be stored in $6000-$7FFF

So, is this still a fact or have you only been under this impression.

I try not to state something as authoritative fact unless I have some sort of citation or a test program or something to back it up. I used to write using more confident language on another forum for a while until I was called out for giving answers that turned out false. Adding extra RAM to mapper 0 with 6264+7420 is fact, backed by PCB shots of Family BASIC v3 and discussions linked from the page about this circuit.

The bit about the default ld65 linker script for NES was only my recollection because I have never used the default ld65 linker script for NES, instead using my own much simpler linker script. So I went and dug up nes.cfg on GitHub. It turns out that the part I was referring to was the following in the MEMORY section:
Code:
    # additional 8K SRAM Bank
    # - data (run)
    # - bss
    # - heap
    RAM: file = "", start = $6000, size = $2000, define = yes;

So yes, the default NES linker script does assume that global variables and heap will be placed in extra memory on the cartridge mapped at $6000-$7FFF. If you don't plan to put extra memory on the cartridge, you'll have to shrink the stack or use a different output method or both in order to gain enough memory for storing your variables.
Re: Problem with the "nes.cfg" and 1 x 16 KB PRG
by on (#118036)
Since I'm not into the programming in C part yet, I still really don't understand the inner workings of all that stuff that the C compiler and linker will do to the program. (I know how the C programing language works and I basically know about 6502 assembly now, but all those specific linker options for C-to-6502 programs are still really abstract to me.) If assembly was a bit more user-friendly, I guess I would just write it right there.*

So, what value should I finally set for the stack if I want to write a game with the same memory outline as a simple first generation game like "Donkey Kong" with no extra memory or any of that stuff? And what would happen if I just omit __STACKSIZE__ at all or if I simply set it to 0?


* Isn't there some in-between language? A langauge that feels like a high level language, but where every command directly maps to the corresponding assembly commands without overhead and without the compiler doing things where you can't predict what will happen?
A language where you can write value = status + 3 and the compiler/converter maps it into the shortest possible assembly code and clears the carry flag, while during a subtraction, it would set the carry flag.
And where you can write expressions like if (value1 < value2).
But where you can't declare local variables that are automatically cleaned up when you leave a function, but every variable is global and takes a specified memory location (maybe with automatic increment like in the segments area of CC65 assembly code).
And where sub functions can't have real parameters, but where you can directly access the X and Y register if you want to. And so, the X register, the Y register and the accumulator would be the only three possible parameters in a function, knowing that if you call a function inside another function and both use a certain register as a parameter, the inner function could overwrite the value that the outer function uses.

You know, some language that let's you mostly write short and readable C-like code without that whole LDA, STA, INC, BNE etc., but that can transform 1:1 into assembly code without using any internal, hidden compiler operations that bloat the binary file. (In fact, the converter should be merely a source code converter that transforms your high level code into an assembly text file.)
Re: Problem with the "nes.cfg" and 1 x 16 KB PRG
by on (#118037)
You'll want to look into the library that Shiru made for writing simple NES games.
Re: Problem with the "nes.cfg" and 1 x 16 KB PRG
by on (#118039)
"In-between languages" that I remember having shown up here before:
uc65, by qbradq.
Movax12 has had a series of "writing enough macro glue to make ca65 nice" posts: assignment, comparing, blog, wiki
Re: Problem with the "nes.cfg" and 1 x 16 KB PRG
by on (#118041)
Your stack needs to be as big as required by your program. This depends on how deep the call tree needs to go, and how many variables you put on the stack in those functions.

Probably your call tree doesn't need to go very deep. If you need to do something that needs a deep call stack, like maybe if it's recursive, I'd recommend just writing it in assembly and using the hardware stack. CC65's stack is heavyweight and slow.

For the same reason, it's generally good to try to keep variables on the stack to a minimum, too. I like to put some general purpose global variables into the zero page (e.g. ints i and j for for loops) so that I don't have to take the performance hit of placing them on the C stack.

I made this music ROM a while ago using CC65, and it's open source. Feel free to take a look if you think it might be useful:

http://rainwarrior.ca/music/coltrane.nes
http://rainwarrior.ca/music/coltrane_src.zip

It's not a good example of a game, but it's written mostly in C, and I've whittled down the CC65 CRT to what I think is the minimum. It's an NROM game with no additional memory. (For this project I reserved 128 bytes for the C stack, but I don't think it really uses all of that. I didn't have too much trouble fitting in RAM though, so I never needed to cut it down.)
Re: Problem with the "nes.cfg" and 1 x 16 KB PRG
by on (#118051)
DRW wrote:
So, what value should I finally set for the stack if I want to write a game with the same memory outline as a simple first generation game like "Donkey Kong" with no extra memory or any of that stuff? And what would happen if I just omit __STACKSIZE__ at all or if I simply set it to 0?

As far as I know, __STACKSIZE__ is only required to be valid if you use the heap (i.e. malloc() and friends). Other than that, its value shouldn't matter.

Quote:
* Isn't there some in-between language? A langauge that feels like a high level language, but where every command directly maps to the corresponding assembly commands without overhead and without the compiler doing things where you can't predict what will happen?

Movax's CA65 macros are probably the closest thing (see lidnariq's post). NESHLA is an option as well (google it).

There's also Atalan, but it doesn't map to assembly 1:1.
Re: Problem with the "nes.cfg" and 1 x 16 KB PRG
by on (#118189)
I didn't get to have a look at the in-between languages and the libraries yet, so I'll have a look at it later.

So, let's get back to the "nes.cfg" first.

rainwarrior wrote:
Your stack needs to be as big as required by your program. This depends on how deep the call tree needs to go, and how many variables you put on the stack in those functions.
I'd still like to know the following things about it:

1. If I declare a local variable in a function, where in memory is it saved? At the next free spot of the RAM? Or on the next free spot at (RAM - STACKSIZE)? Or somewhere else?
2. Where is a local variable stored if I just omit the stack size in the cfg file?
3. Where is a local variable stored if the stack is full?
4. Where is a global vaiable stored? Next free spot of RAM?

thefox wrote:
As far as I know, __STACKSIZE__ is only required to be valid if you use the heap (i.e. malloc() and friends). Other than that, its value shouldn't matter.
5. Can somebody else confirm this? (I won't use malloc() and free() in my NES program.)


Alright, this is my current "nes.cfg" (I used some names that fit my style more, making the names in the SEGMENTS section explaining what they are used for while the ones in MEMORY are what they actually are hardware-wise):
Code:
MEMORY
{
   ZERO_PAGE: start =    $0, size =  $100, type = rw,                        define = yes;
   RAM:       start =    $0, size =  $800,                                   define = yes;
   HEADER:    start =    $0, size =   $10,            file = %O, fill = yes;
   PRG_ROM:   start = $C000, size = $4000,            file = %O, fill = yes, define = yes;
   CHR_ROM:   start =    $0, size = $2000,            file = %O, fill = yes;
}

SEGMENTS
{
   COMMON_VARIABLES: load = ZERO_PAGE,                type = zp;
   VARIABLES:        load = RAM,       start =  $200, type = bss, define = yes;
   HEADER:           load = HEADER,                   type = ro;
   LOGIC:            load = PRG_ROM,                  type = ro,  define = yes;
   DATA:             load = PRG_ROM,                  type = ro,  define = yes;
   INTERRUPTS:       load = PRG_ROM,   start = $FFFA, type = ro;
   GRAPHICS:         load = CHR_ROM,                  type = ro;
}

# We will have to see about this:
# SYMBOLS
# {
   # __STACKSIZE__ = $300;
# }

Some further questions that I have about it:

6. Is everything correct?
7. Do the variable names make sense?
8. Is this sufficient for a program written in C?
9. Why does ZERO_PAGE has type = rw even though for everything else the type is only declared in SEGMENTS? Isn't type = zp in SEGMENTS enough for the compiler to know that it's writable?

10. Since the MEMORY section declared the hardware parts and the zero page is just a defined area in the RAM, instead of this here:
Code:
# Memory
ZERO_PAGE: start = $0, size = $100, type = rw, define = yes;
RAM:       start = $0, size = $800,            define = yes;

#Segments
COMMON_VARIABLES: load = ZERO_PAGE,               type = zp;
VARIABLES:        load = RAM,       start = $200, type = bss, define = yes;
wouldn't it be possible and better to write this instead:
Code:
# Memory
RAM: start = $0, size = $800, define = yes;

#Segments
COMMON_VARIABLES: load = RAM, start =   $0, size = $100, type = zp;
VARIABLES:        load = RAM, start = $200,              type = bss, define = yes;
? (You know, like it's done with the vectors/interrupts.)
Re: Problem with the "nes.cfg" and 1 x 16 KB PRG
by on (#118191)
DRW wrote:
1. If I declare a local variable in a function, where in memory is it saved? At the next free spot of the RAM? Or on the next free spot at (RAM - STACKSIZE)? Or somewhere else?
2. Where is a local variable stored if I just omit the stack size in the cfg file?
3. Where is a local variable stored if the stack is full?
4. Where is a global vaiable stored? Next free spot of RAM?


1. Local variables go into a register if possible, but on the stack otherwise. (Is up to the compiler's optimization process.)
2. I don't know what happens if you omit __STACKSIZE__ in the cfg file. I will presume that either the CRT you are using will define a default value if undefined, or your CRT will fail to link. Personally, I made my own crt0.s (see the example I posted above) and just directly reserved stack space there. If it compiles and links, see 1 for the answer.
3. If the stack is full, it overflows, and your program will crash. This is simply something you have to prevent, by not letting your call stack get too deep. (Remember, stack variables are removed from the stack when the function returns. This is part of the reason why they're relatively slow as well, compared to globals.)
4. Global variables will be statically allocated into RAM. You can also make function-local static variables, which will also go into RAM instead of the stack.

DRW wrote:
thefox wrote:
As far as I know, __STACKSIZE__ is only required to be valid if you use the heap (i.e. malloc() and friends). Other than that, its value shouldn't matter.

5. Can somebody else confirm this? (I won't use malloc() and free() in my NES program.)

I don't know how malloc() or free() are implemented in the CC65 CRT but I wouldn't recommend using them, and there should be a separate heap, they can't be on the stack... I dunno, I'd have to check its CRT implementation.

DRW wrote:
...
6. Is everything correct?
7. Do the variable names make sense?
8. Is this sufficient for a program written in C?
9. Why does ZERO_PAGE has type = rw even though for everything else the type is only declared in SEGMENTS? Isn't type = zp in SEGMENTS enough for the compiler to know that it's writable?

6, 7, 8. No, the segments need to have specific names for CC65's code generation to be able to use them.
9. They are separate definitions. You can place a read only segment into a read-write section of memory, for example.

DRW wrote:
10. Since the MEMORY section declared the hardware parts and the zero page is just a defined area in the RAM, instead of this here: ...
wouldn't it be possible and better to write this instead: ...
(You know, like it's done with the vectors/interrupts.)

Yes, you could create a single memory block from 0 to 800 and avoid placing a segment on the stack (or create a segment for the stack) but I don't understand the motivation to do it that way. If you like it, sure, but you still end up with one segment for ZP and one segment for RAM to avoid accidentally placing stuff on the hardware stack. My own preference is one memory block for each of these three regions (0-FF, 100-1FF, 200-7FF) but if you want them all in one, that can work fine.

In your examples where there is a separate memory block for ZP and RAM, however, I do not understand what you start RAM at 0 instead of $200. If you start it at 0 obviously it overlaps with ZP and the hardware stack, which I think is error prone;if you start needing other segments in RAM, you have to be careful none of them end up in the forbidden region-- isn't it better to just define RAM without that region in it?
Re: Problem with the "nes.cfg" and 1 x 16 KB PRG
by on (#118193)
rainwarrior wrote:
1. Local variables go into a register if possible, but on the stack otherwise.

But where is the stack in the NES program? In the RAM section? Beginning, end, middle? Somewhere else?

rainwarrior wrote:
DRW wrote:
thefox wrote:
As far as I know, __STACKSIZE__ is only required to be valid if you use the heap (i.e. malloc() and friends). Other than that, its value shouldn't matter.

5. Can somebody else confirm this? (I won't use malloc() and free() in my NES program.)

I don't know how malloc() or free() are implemented in the CC65 CRT but I wouldn't recommend using them, and there should be a separate heap, they can't be on the stack... I dunno, I'd have to check its CRT implementation.

I was rather asking: If I don't use malloc(), is it true that __STACKSIZE__ isn't required, as thefox assumed? And what does the stack size have to do with variables that are declared with malloc() (i.e. on the heap) anyway?

rainwarrior wrote:
6, 7, 8. No, the segments need to have specific names for CC65's code generation to be able to use them.

I thought the names can be defined at will. They really should have mentioned that this doesn't count for the C compiler.
So, just the segments or also the memory names?

rainwarrior wrote:
9. They are separate definitions. You can place a read only segment into a read-write section of memory, for example.

But according to that logic, shouldn't every MEMORY location have a type declaration? Why only the zero page?

rainwarrior wrote:
Yes, you could create a single memory block from 0 to 800 and avoid placing a segment on the stack (or create a segment for the stack) but I don't understand the motivation to do it that way.

It's because of what Movax12 and tepples said about the vectors location:
Movax12 wrote:
DRW wrote:
wouldn't this be more correct:
Code:
ROM0: start = $C000, size = $3FFA, file = %O, fill = yes, define = yes;
ROMV: start = $FFFA, size = $6, file = %O, fill = yes;


Yes.
Even better: (skipping over other MEMORY and SEGMENT definitions)

Code:
MEMORY
{
   ROM0:   start = $C000, size = $4000, file = %O,    fill = yes, define = yes;
}

SEGMENTS
{
   CODE:     load = ROM0,              type = ro,  define = yes;
   VECTORS:  load = ROM0, start = $FFFA
}

tepples wrote:
"Why should the VECTORS be a segment inside the ROM0 memory instead of a separate memory area?"

Because it's in the same bank of the same chip, for one thing. In projects for mappers with 16K banks (UNROM, MMC1), I typically use one MEMORY per bank.


rainwarrior wrote:
isn't it better to just define RAM without that region in it?

Yeah, in this combination, you're probably right.
So, after reading the above quotes, what is more "natural" for CC65: One MEMORY definition for each SEGMENT? Or one MEMORY definition for each separated part of the hardware and various SEGMENTS with their own start address and sizes?
Re: Problem with the "nes.cfg" and 1 x 16 KB PRG
by on (#118199)
I'm not sure how useful it is to answer all these questions before you have even started using CC65. If you want to set up a .cfg that will work with CC65, there are a couple of examples already linked for you (like shiru's guide). It might be a better approach for you to start using one of them, and ask about problems you encounter trying to get them working.

DRW wrote:
But where is the stack in the NES program? In the RAM section? Beginning, end, middle? Somewhere else?

The C stack is defined somewhere, and this depends on whose example you are working with. The only way to answer this question is to look at the CRT implementation you are using. In mine it is in a file called crt0.s, and I place it in its own segment that I called "CSTACK". In the CC65 library it is also defined in a file called crt0.s and it looks like it just goes at the top of a segment called "SRAM" and grows down into it, but I don't recommend using the NES library that comes with CC65.

DRW wrote:
I thought the names can be defined at will. They really should have mentioned that this doesn't count for the C compiler. So, just the segments or also the memory names?

There are a small number of segments that are required to have a specific name. Actually, you can rename them as a command line option, but this is cumbersome.
http://www.cc65.org/doc/cc65-2.html#ss2.1
You will need at least these four segments defined:
* BSS (variables in RAM)
* CODE (compiled C code)
* DATA (writable data, is copied from ROM to RAM at startup)
* RODATA (read only data).
All of the examples have these already defined, and you should just use them.

DRW wrote:
But according to that logic, shouldn't every MEMORY location have a type declaration? Why only the zero page?

Every MEMORY block can have a type declaration, yes, and probably should. If you don't specify you will get a default type. The point of specifying the type is that the linker will prevent you from doing things improper to the type of memory; if you don't bother you just don't get that protection, that's all. It will still build.

DRW wrote:
So, after reading the above quotes, what is more "natural" for CC65: One MEMORY definition for each SEGMENT? Or one MEMORY definition for each separated part of the hardware and various SEGMENTS with their own start address and sizes?

I use MEMORY for hardware regions, and SEGMENTs to align/fill code/data in them. As far as what CC65 needs, only the segments matter. The MEMORY is just telling the linker what to do with the SEGMENTs when you build your ROM file.
Re: Problem with the "nes.cfg" and 1 x 16 KB PRG
by on (#118201)
rainwarrior wrote:
2. I don't know what happens if you omit __STACKSIZE__ in the cfg file. I will presume that either the CRT you are using will define a default value if undefined, or your CRT will fail to link. Personally, I made my own crt0.s (see the example I posted above) and just directly reserved stack space there. If it compiles and links, see 1 for the answer.

rainwarrior wrote:
DRW wrote:
thefox wrote:
As far as I know, __STACKSIZE__ is only required to be valid if you use the heap (i.e. malloc() and friends). Other than that, its value shouldn't matter.

5. Can somebody else confirm this? (I won't use malloc() and free() in my NES program.)

I don't know how malloc() or free() are implemented in the CC65 CRT but I wouldn't recommend using them, and there should be a separate heap, they can't be on the stack... I dunno, I'd have to check its CRT implementation.

I checked the library sources and it seems it kind of depends. Some platform's default crt0.s initializes "sp" based on __STACKSIZE__ (e.g. C64) but others (like NES) don't use it at all.

Here's the entire list of references to __STACKSIZE__ in the "libsrc" directory:
Code:
e:\dev\cc65-xofeht-fork\libsrc>grep -R __STACKSIZE__ *
atmos/crt0.s:        .import         __ZPSAVE_LOAD__, __STACKSIZE__
atmos/crt0.s:        lda     #<(__RAM_START__ + __RAM_SIZE__ + __STACKSIZE__)
atmos/crt0.s:        lda     #>(__RAM_START__ + __RAM_SIZE__ + __STACKSIZE__)
c128/crt0.s:        .import         __RAM_START__, __RAM_SIZE__, __STACKSIZE__
c128/crt0.s:        lda     #<(__RAM_START__ + __RAM_SIZE__ + __STACKSIZE__)
c128/crt0.s:        lda     #>(__RAM_START__ + __RAM_SIZE__ + __STACKSIZE__)
c64/crt0.s:        .import         __STACKSIZE__                   ; Linker generated
c64/crt0.s:        lda     #<(__RAM_START__ + __RAM_SIZE__ + __STACKSIZE__)
c64/crt0.s:        lda     #>(__RAM_START__ + __RAM_SIZE__ + __STACKSIZE__)
common/_heap.s:        .import         __BSS_RUN__, __BSS_SIZE__, __STACKSIZE__
common/_heap.s:        sbc     #<__STACKSIZE__
common/_heap.s:        sbc     #>__STACKSIZE__
geos-common/system/crt0.s:            .import __STACKADDR__, __STACKSIZE__        ; Linker generated
geos-common/system/crt0.s:        lda #<(__STACKADDR__ + __STACKSIZE__)
geos-common/system/crt0.s:        ldx #>(__STACKADDR__ + __STACKSIZE__)
lynx/crt0.s:        .import         __RAM_START__, __RAM_SIZE__, __STACKSIZE__
lynx/crt0.s:        lda     #<(__RAM_START__ + __RAM_SIZE__ + __STACKSIZE__)
lynx/crt0.s:        lda     #>(__RAM_START__ + __RAM_SIZE__ + __STACKSIZE__)
plus4/crt0.s:        .import         __STACKSIZE__                   ; Linker generated
plus4/crt0.s:        lda     #<(__RAM_START__ + __RAM_SIZE__ + __STACKSIZE__)
plus4/crt0.s:        lda     #>(__RAM_START__ + __RAM_SIZE__ + __STACKSIZE__)
runtime/stkchk.s:        .import         __STACKSIZE__                   ; Linker defined
runtime/stkchk.s:        sub     #<__STACKSIZE__
runtime/stkchk.s:        sbc     #>__STACKSIZE__
sim6502/crt0.s:        .import         __STACKSIZE__                   ; Linker generated
sim6502/crt0.s:        lda     #<(__RAM_START__ + __RAM_SIZE__ + __STACKSIZE__)
sim6502/crt0.s:        ldx     #>(__RAM_START__ + __RAM_SIZE__ + __STACKSIZE__)
supervision/crt0.s:        .import         __STACKSIZE__                   ; Linker generated
supervision/crt0.s:        lda     #>(__RAM_START__ + __RAM_SIZE__ + __STACKSIZE__)
supervision/crt0.s:        stz     sp              ; #<(__RAM_START__ + __RAM_SIZE__ + __STACKSIZE__)
vic20/crt0.s:        .import         __STACKSIZE__                   ; Linker generated
vic20/crt0.s:        lda     #<(__RAM_START__ + __RAM_SIZE__ + __STACKSIZE__)
vic20/crt0.s:        lda     #>(__RAM_START__ + __RAM_SIZE__ + __STACKSIZE__)


So whether you need __STACKSIZE__ is a combination of 1) whether you supply your own startup code (crt0.s) 2) whether you use the heap (_heap.s uses __STACKSIZE__ to calculate the end of the heap: heapend = sp - STACKSIZE).

stkchk.s also references __STACKSIZE__, it's presumably called iff the stack overflow checks are enabled with compiler switches.
Re: Problem with the "nes.cfg" and 1 x 16 KB PRG
by on (#118215)
rainwarrior wrote:
I'm not sure how useful it is to answer all these questions before you have even started using CC65.

Yeah, I guess I should try to convert my sample program to how it would look in C now.

By the way, are there any tutorials that explain you how you have to set up such a project? I know there are example programs. But I'd rather have a step-by-step explanation. I mean, looking at a huge collection of example code files, you don't really know how to begin. And I'm not a fan of the "This is initialization code. You don't need to understand it and can just use it as it is" mentality. I want to understand what I'm doing and not paste my project together from various different sources. And I understand it better if it's explained chronologically. Otherwise, I have to look through source code and research a whole lot of lines.
Re: Problem with the "nes.cfg" and 1 x 16 KB PRG
by on (#118236)
CC65 is a hobbyist compiler, and it also targets many platforms at once, so NES development of it is a niche of a niche. It doesn't have as large a support base as you may be used to, DRW.

There is documentation that explains all of the assembler and linker syntax, if you read it all. However, the workings of its C code generation and the CRT library are not documented much at all. That's kind of normal for a C compiler, though-- the CRT is usually a bit of a black box. This problem is also compounded by the need to customize the CRT library a little if you want to make an NES game with it.

Shiru's guide is as close as you'll get to a tutorial, at the moment. My example is open source, but it's not a tutorial. If you want to learn about how CC65 works, start using it and take a look at the assembly code it produces.

If you need an explanation of how some particular thing works in CC65 you can ask here, but if you want to know where we find the answers so that you can learn on your own, they're found by using CC65 and seeing what it does, and browsing the CRT library source.
Re: Problem with the "nes.cfg" and 1 x 16 KB PRG
by on (#118237)
Even after going through the docs, I've spent much time learning about ca65 with printf debugging: .out .sprintf("what is foo: %d, bar: %d", foo, bar)
Re: Problem with the "nes.cfg" and 1 x 16 KB PRG
by on (#118250)
O.k., I guess I'll put the "nes.cfg" file aside for now, go through the regular source code and see what I still don't understand. Then I'll have a look at the in-between languages and decide if I write the game in assembly, C or one of those languages.

So, thanks to your answers so far. I'll write another post here if there are any further questions about the "nes.cfg".