Minimal NES example using ca65

Are you new to 6502, NES, or even programming in general? Post any of your questions here. Remember - the only dumb question is the question that remains unasked.

Moderator: Moderators

User avatar
cpow
NESICIDE developer
Posts: 1097
Joined: Mon Oct 13, 2008 7:55 pm
Location: Minneapolis, MN
Contact:

Re: Minimal NES example using ca65

Post by cpow »

rainwarrior wrote:So, we can attach .nesproject files now, but then I realized I should probably just add it to the zip. :P
Yeah I suppose I could have added it to your ZIP and re-uploaded the ZIP. But then there'd be two ZIPs floating around one with and one without.
User avatar
thefox
Posts: 3134
Joined: Mon Jan 03, 2005 10:36 am
Location: 🇫🇮
Contact:

Re: Minimal NES example using ca65

Post by thefox »

Off topic:

There's one small feature that I really miss from WinRAR after switching to 7zip: when the Extract button is pressed, it doesn't automatically fill the destination path with the name of the package (with extension stripped), instead it defaults to extracting to the directory where the package is. Usually not a problem since I mostly use the context menu to extract stuff, but sometimes it's more natural to open the archive in the program (e.g. when the file has been downloaded with a browser).
Download STREEMERZ for NES from fauxgame.com! — Some other stuff I've done: fo.aspekt.fi
thenendo
Posts: 28
Joined: Mon Oct 06, 2014 5:09 pm
Location: New Joisey (NTSC)

Re: Minimal NES example using ca65

Post by thenendo »

rainwarrior, thanks, this is super useful for someone getting started with ca65 on the NES.

I have a question, though. (Probably dumb, as I'm new to both the cc65 suite and 6502 programming.)

In example.cfg, you have:

Code: Select all

MEMORY {                                                                                                  
    ZP:     start = $00,    size = $0100, type = rw, file = "";                                           
    OAM:    start = $0200,  size = $0100, type = rw, file = "";                                           
    RAM:    start = $0300,  size = $0500, type = rw, file = "";                                           
    HDR:    start = $0000,  size = $0010, type = ro, file = %O, fill = yes, fillval = $00;                
    PRG:    start = $8000,  size = $8000, type = ro, file = %O, fill = yes, fillval = $00;                
    CHR:    start = $0000,  size = $2000, type = ro, file = %O, fill = yes, fillval = $00;                
}  
Because CHR starts at $0000, doesn't this mean that the TILES segment (full of data from `.incbin "background.chr"', etc.) will overlap the zero page and the stack page ($0100 - $01FF), as well as the OAM and RAM areas (not to mention the iNES header)? Doesn't this mean that if I, say, push something onto the stack, I'll be overwriting random parts of the background tiles data? I must not understand how ld65 behaves when you declare overlapping memory areas... why not declare all of the memory areas as strictly disjoint regions? (looks like none of the examples in the ld65 docs have overlapping memory areas)
lidnariq
Posts: 11432
Joined: Sun Apr 13, 2008 11:12 am

Re: Minimal NES example using ca65

Post by lidnariq »

The NES has two completely unrelated notions of address: one is for the CPU, and has zero page, stack, other RAM, &c.
The other is for the PPU and (almost always) contains just tile data.
They both start at 0. They're just different 0s.
User avatar
rainwarrior
Posts: 8734
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: Minimal NES example using ca65

Post by rainwarrior »

As lidnariq said, in this case these are two different memory regions (CPU vs PPU), but I will also explain the linker.

Each line of the MEMORY section describes a block of memory space that can be used by a SEGMENT, and also may be output to a file. MEMORY regions will be output in the same order as specified.

Each line of the SEGMENTS section describes an allocation of space in one of the previously defined MEMORY blocks. A segment doesn't have to fill up all of any particular MEMORY region, and will be assigned space contiguously in the order listed, sometimes with padding if a start address is specified.

Assembly code may specify which SEGMENT to use with the .segment directive, and the SEGMENT will be filled contiguously in the order the code/data appears.


All that said, blocks in MEMORY can refer to memory regions in different spaces, like in this case, or even the same space. What it really controls is whether and where that region will appear in the output file. A space that represents RAM doesn't normally go in the file. In an iNES file, there is a 16 byte header (HDR), a PRG block (PRG), and a CHR block (CHR), so I have specified 3 MEMORY regions for these three parts of the file.

If you are doing banking, you will typically want to have one MEMORY region per bank. In this case, many of them will overlap the same address space, but what matters is they will go into the file as separate blocks, and you can use SEGMENTS to specify which bank things need to go into.
thenendo
Posts: 28
Joined: Mon Oct 06, 2014 5:09 pm
Location: New Joisey (NTSC)

Re: Minimal NES example using ca65

Post by thenendo »

Thanks, guys, I think I get it now. What I failed to appreciate is that with the MEMORY areas that are being written to the file (%O), we are actually describing the layout of the iNES file, not the runtime layout of memory -- the iNES format has its own conventions about how its contents will determine the initial state of memory at power-on. And, on the other hand, the MEMORY areas with file = "" (ZP, OAM, RAM) are referring to the runtime address space, but this is for purposes of the symbolic assembler knowing how to interpret what we put in our source code, so it knows, e.g., that ".res 1" means to reserve a byte in one region if it appears under ".segment "ZP"", but in another region if it appears under ".segment "RAM"".
thenendo
Posts: 28
Joined: Mon Oct 06, 2014 5:09 pm
Location: New Joisey (NTSC)

Re: Minimal NES example using ca65

Post by thenendo »

One more question: is there a particular reason that you define the gamepad_poll subroutine in the DATA segment, rather than in the CODE segment? Changing it to CODE compiles and works fine. Is it more efficient somehow to have this subroutine located in DATA? (Not sure how that would be, unless you were depending on relative addressing in such a way that you wanted to be close to certain addresses, but I don't see how that's the case here.)
User avatar
thefox
Posts: 3134
Joined: Mon Jan 03, 2005 10:36 am
Location: 🇫🇮
Contact:

Re: Minimal NES example using ca65

Post by thefox »

thenendo wrote:One more question: is there a particular reason that you define the gamepad_poll subroutine in the DATA segment, rather than in the CODE segment? Changing it to CODE compiles and works fine. Is it more efficient somehow to have this subroutine located in DATA? (Not sure how that would be, unless you were depending on relative addressing in such a way that you wanted to be close to certain addresses, but I don't see how that's the case here.)
Looks like a mistake. It's not more efficient.

In this case it doesn't matter which segment the code is placed in, because both the CODE and the DATA segments are ROM and end up in the same memory area. If one wanted to place code at certain addresses, the correct way to do it in cc65/ca65 would be to define a new segment (and possibly a new memory area as well) with the desired starting address.

...

On an unrelated note, it goes somewhat against convention to name the read-only data segment DATA. Typically RDATA or RODATA is used for that, and DATA is used for initialized data in RAM. Not saying that it should be changed, only noting it here since some other source code might be using different conventions.
Download STREEMERZ for NES from fauxgame.com! — Some other stuff I've done: fo.aspekt.fi
User avatar
rainwarrior
Posts: 8734
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: Minimal NES example using ca65

Post by rainwarrior »

Yeah, just a mistake. I will correct that.

Also, that is a good point. I forgot that RODATA is the cc65 convention for read-only data, though the name might be a little more obscure to new users. Not sure if I want to correct this. The main reason I bother to use separate CODE and DATA segments is that it keeps them nicely separated (good for debugging disassembly if all the code is in one contiguous place) and so the map statistics the linker generates will list them separately.
thenendo
Posts: 28
Joined: Mon Oct 06, 2014 5:09 pm
Location: New Joisey (NTSC)

Re: Minimal NES example using ca65

Post by thenendo »

Awesome, gotcha. While you're at it, I think you could also fix a typo in the comment on line 52 (byte 5 of the iNES header):

Code: Select all

.byte $01 ; 4k CHR bank count
That should say 8k, right?
User avatar
rainwarrior
Posts: 8734
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: Minimal NES example using ca65

Post by rainwarrior »

Yeah.
thenendo
Posts: 28
Joined: Mon Oct 06, 2014 5:09 pm
Location: New Joisey (NTSC)

Re: Minimal NES example using ca65

Post by thenendo »

rainwarrior wrote: Also, that is a good point. I forgot that RODATA is the cc65 convention for read-only data, though the name might be a little more obscure to new users. Not sure if I want to correct this. The main reason I bother to use separate CODE and DATA segments is that it keeps them nicely separated (good for debugging disassembly if all the code is in one contiguous place) and so the map statistics the linker generates will list them separately.
Relatedly, I think you might want to use "ZEROPAGE" instead of "ZP". Using your linker config (with "ZP"), I found that this code makes assembler fail with "Error: Range error":

Code: Select all

.segment "ZP"
addr0: .res 2

.segment "CODE"
; ... in some subroutine
sta addr0+1
But after I renamed "ZP" to "ZEROPAGE" in the linker config and assembly source, it seems to work as expected. I guess without it, ca65 isn't smart enough to figure out that addr0+1 is a constant zero-page address, or something.

Edit: It makes sense that ca65 isn't smart enough, because the assembler runs before the linker, so it has no knowledge of the segment layouts and types; so it's forced to rely on hard-coded conventions for certain things, like "ZEROPAGE".
User avatar
thefox
Posts: 3134
Joined: Mon Jan 03, 2005 10:36 am
Location: 🇫🇮
Contact:

Re: Minimal NES example using ca65

Post by thefox »

thenendo wrote:Edit: It makes sense that ca65 isn't smart enough, because the assembler runs before the linker, so it has no knowledge of the segment layouts and types; so it's forced to rely on hard-coded conventions for certain things, like "ZEROPAGE".
Your analysis is correct. However, it's also possible to explicitly specify a segment as a zero page segment:

Code: Select all

.segment "ZP" : zeropage
foo: .res 1
; ...
Download STREEMERZ for NES from fauxgame.com! — Some other stuff I've done: fo.aspekt.fi
User avatar
rainwarrior
Posts: 8734
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: Minimal NES example using ca65

Post by rainwarrior »

thenendo wrote:
rainwarrior wrote:Relatedly, I think you might want to use "ZEROPAGE" instead of "ZP". Using your linker config (with "ZP")
Ack, yeah I forgot about that special name too. Okay, I've renamed ZP > ZEROPAGE, and DATA > RODATA to keep with ca65 convention.
User avatar
rainwarrior
Posts: 8734
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: Minimal NES example using ca65

Post by rainwarrior »

Fixed a bug with the emphasis bits, accidentally had emphasize-red set on.
Post Reply