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

Program counter [ca65]

Program counter [ca65]
by on (#117292)
My goal here is to write a nes rom in assembly using ca65 which sets the accumulator to $FF and loops forever.

I have been looking over the following list of articles:

http://shiru.untergrund.net/articles/programming_nes_games_in_c.htm
http://oliverschmidt.github.io/cc65/doc/ld65-5.html
http://forums.nesdev.com/viewtopic.php?f=2&t=9896&p=109046&hilit=nes.cfg#p109046
http://forums.nesdev.com/viewtopic.php?t=3816

And based on my understanding (or lack thereof) I have distilled everything to the following two files.

My "nes.cfg" file:

Code:
MEMORY {
   RAM:    start = $0000, size = $1FFF;
   IOREG:  start = $2000, size = $201F;
   EXROM:  start = $4020, size = $1FDF;
   SRAM:   start = $6000, size = $1FFF;
   PRGROM: start = $8000, size = $7FFF;
}

SEGMENTS {
   HEADER:  load = RAM,    type = ro;
   STARTUP: load = PRGROM, type = ro;
   VECTORS: load = PRGROM, type = ro;
   CHARS:   load = PRGROM, type = ro;
}


My assembly code:

Code:
; TESTING
; cl65 -t nes test.s

.segment "HEADER"
   .byte "NES" ; signature
   .byte $1A   ; signature
   .byte $01   ; # of 16kb PRG-ROM banks
   .byte $01   ; # of 8kb VROM banks
   .byte $00   ; ROM control byte one
   .byte $00   ; ROM control byte two
   .byte $01   ; # of 8kb RAM banks
   .byte $00   ; reserved

.segment "STARTUP"

INIT:
   LDA #$FF
LOOP:
   JMP LOOP

.segment "VECTORS"

   
.segment "CHARS"




Using an emulator I am able to see the my "LDA #$FF" instruction is indeed being loaded into memory at the correct location by the accumulator isn't being set - which, given what I know, means the program counter isn't making it to $8000 - where my code is.

Any ideas on what I'm missing here?
Re: Program counter [ca65]
by on (#117293)
You need to add the RESET vector (it tells 6502 what address to jump to when the CPU is reset). You also need to specify a base address $FFFA for the VECTORS segment.

Since you're using an emulator, right now you should see that when the program starts, PC is set to 0 (the value at the RESET vector, since you haven't specified it).
Re: Program counter [ca65]
by on (#117294)
StephenM wrote:
Code:
MEMORY {
   RAM:    start = $0000, size = $1FFF;
   IOREG:  start = $2000, size = $201F;
   EXROM:  start = $4020, size = $1FDF;
   SRAM:   start = $6000, size = $1FFF;
   PRGROM: start = $8000, size = $7FFF;
}

SEGMENTS {
   HEADER:  load = RAM,    type = ro;
   STARTUP: load = PRGROM, type = ro;
   VECTORS: load = PRGROM, type = ro;
   CHARS:   load = PRGROM, type = ro;
}

Your sizes are all one too small. The way you have the segments will cause even the RAM ones to be written to the file. Here's a .cfg that I've used before that's straight forward and sets up all the zeropage, bss areas:
Code:
# ca65 configuration for iNES file

# Defines areas where code/data is put into memory during linking
# fill=yes forces area to be padded to specified size in output
MEMORY
{
    # 2K RAM in NES
    ZP:      start =     0, size =  $100, type = rw;
    # Skip the $100 bytes for stack
    SRAM:    start =  $200, size =  $600, type = rw;
   
    # Pseudo area for iNES header
    HEADER:  start =     0, size =    16, type = ro, fill = yes;
   
    # ROM
    ROM:     start = $8000, size = $7FFA, type = ro, fill = yes;
    VECTORS: start = $FFFA, size =     6, type = ro, fill = yes;
   
    # CHR ROM
    CHARS:   start =     0, size = $2000, type = ro, fill = yes;
}

# Defines named segments you refer to in assembler, and sets up order
# of data in output file
# align=$100 allows use of .align directive with a value up to $100
SEGMENTS
{
    # These make up the iNES file
    HEADER:   load = HEADER, type = ro;
    CODE:     load = ROM,    type = ro, align = $100;
    VECTORS:  load = VECTORS,type = ro;
    CHARS:    load = CHARS,  type = ro;
   
    # These put data into memory
    ZEROPAGE: load = ZP,     type = zp;
    BSS:      load = SRAM,   type = bss;
}
Re: Program counter [ca65]
by on (#117308)
Thank you for the advice, I seem to be making process.

Is it normal for ld65 to throw the following warning messages if "STARTUP" isn't defined in the "nes.cfg" file segments section?

Quote:
ld65.exe: Warning: [builtin config](37): Segment 'STARUP' does not exist


The "buildin config" in that warning message has me worried that ld65 isn't actually using my nes.cfg file (my cfg also doesn't have a line 37)... the nes.cfg is within the same folder as my code and I'm running cl65.exe from this folder as well (enviromental variable). I installed cc65 from an executable so all relevant cc65 folders/binaries are in my "Program Files/cc65/" directory. During my investigation I noticed this directory "/cc65/cfg/" contains what appears to be some default config files but moving these files doesn't make a difference (which I guess makes sense since they all look like they are for the Apple ][ anyway).

Here are me new files, taking into account what thefox and blargg mentioned:

"nes.cfg"
Code:
MEMORY {
   ZP:      start = $0000, size = $0100, type = rw;
   SRAM:    start = $0200, size = $0600, type = rw;
   
   ROM:     start = $8000, size = $7FFA, type = ro, fill = yes;
   VECTORS: start = $FFFA, size = $0006, type = ro, fill = yes;
   
   CHARS:   start = $0000, size = $2000, type = ro, fill = yes;
   
   HEADER:  start = $0000, size = $0016, type = ro, fill = yes;
}

SEGMENTS {
   HEADER:   load = HEADER,  type = ro;
   CODE:     load = ROM,     type = ro,  align = $0100;
   VECTORS:  load = VECTORS, type = ro;
   CHARS:    load = CHARS,   type = ro;
   
   ZEROPAGE: load = ZP,      type = zp;
   BSS:      load = SRAM,    type = bss;
}


"test.s"
Code:
; TESTING
; cl65 -t nes -o test.nes test.s

.segment "HEADER"
   .byte "NES" ; signature
   .byte $1A   ; signature
   .byte $01   ; # of 16kb PRG-ROM banks
   .byte $01   ; # of 8kb VROM banks
   .byte $00   ; ROM control byte one
   .byte $00   ; ROM control byte two
   .byte $01   ; # of 8kb RAM banks
   .byte $00   ; reserved

.segment "CODE"

RESET:
   LDA #$FF
   JMP LOOP
LOOP:
   JMP LOOP

NMI:
IRQ:

.segment "VECTORS"
   .word NMI
   .word RESET
   .word IRQ



The accumulator currently isn't being set, nor is the PC changing its value

EDIT: I just realized this post probably fits better in "Newbie Help Center" section, apologies.
Re: Program counter [ca65]
by on (#117310)
Regarding the linker, or actually the cl65 compile and link utility, you should use: cl65 -t none -C nes.cfg
Apparently ca65 and ld65 will default to none, but cl65 will default to c64, and -t nes will use a default linker config designed for use with C.

I think you need to move your HEADER entry in the MEMORY section to be the first line, or at least the first line with type = ro
Re: Program counter [ca65]
by on (#117312)
Sorry, I won't be helping you with the code here, but rather give you a compliment.

How often do we see (new(b)) people trying to make a game from the start then ask for help here because it doesn't assemble/run? What I admire here is that you begin with the most trivial program and verify that it works. When asking for help, this is very great because we don't have to wade through lot of code, we essentially focus on your specific problem (here your setup). You even use a debugger to check if the code run correctly. I suspect you know already programmed in some other programming language, don't you?

Cheers, man :beer: ― and welcome to the nesdev forums! :wink:
Re: Program counter [ca65]
by on (#117313)
cl65, I never knew about a single-command assemble-and-link, nice!

As for segments not defined, you can make some optional, which is nice for say CHARS if you want the .s file able to decide between CHR RAM and CHR ROM without needing separate .cfg files:
Code:
MEMORY {
    [...]
    CHARS:  start = 0, size = $2000;
}
SEGMENTS {
    [...]
    CHARS: load = CHARS, type = ro, optional=yes;
}

This way if you don't put anything into the CHARS segment, you won't get a warning (the optional=yes part arranges this) and won't get anything besides the iNES header and PRG data in the output file. A caveat is that because you now don't have (and can't have) fill=yes in the MEMORY section, you must be sure to completely fill CHARS if you define any of it, or it won't be the full $2000 bytes. e.g. .incbin "mychars.bin" where that file is 8K.
Re: Program counter [ca65]
by on (#117317)
blargg wrote:
you must be sure to completely fill CHARS if you define any of it, or it won't be the full $2000 bytes. e.g. .incbin "mychars.bin" where that file is 8K.

Tangenting here a bit, but in this case it's a good idea to set define=yes for the segment in the config and use an .assert. Something like (not tested):

Code:
.assert ( __CHARS_SIZE__ & $1FFF ) = 0, error, "CHARS size not a multiple of $2000"
Re: Program counter [ca65]
by on (#117319)
blargg wrote:
cl65, I never knew about a single-command assemble-and-link, nice!


You should look into using make with cc65, it works well with cl65 dependency files. It might require some modifying to get it working as you like/expect, but it's very helpful for larger projects when you get it working well.

http://wiki.cc65.org/doku.php?id=cc65:project_setup
Re: Program counter [ca65]
by on (#117320)
By the way, if you prefer to put the alignment in the SEGMENTS rather than MEMORY, this can also be done. I prefer this, especially when banking, so I can basically just have one MEMORY line per bank, and then as many SEGMENTS as I need to align within each bank. Here's an example NROM config:

Code:
MEMORY {
    ZP:     start = $00,    size = $100,    type = rw, file = "";
    RAM:    start = $0200,  size = $600,    type = rw, file = "";
    HDR:    start = $0000,  size = $10,     type = ro, file = %O, fill = yes;
    PRG:    start = $8000,  size = $8000,   type = ro, file = %O, fill = yes;
    CHR:    start = $0000,  size = $2000,   type = ro, file = %O, fill = yes;
}

SEGMENTS {
    ZEROPAGE:   load = ZP,  type = zp;
    BSS:        load = RAM, type = bss;
    HEADER:     load = HDR, type = ro;
    CODE:       load = PRG, type = ro, start = $8000;
    DPCM:       load = PRG, type = ro, start = $C000, optional = yes;
    VECTORS:    load = PRG, type = ro, start = $FFFA;
    TILES:      load = CHR, type = ro;
}


The other nice feature is that segments can be optional. In this example if you don't use DPCM, that's perfectly fine, and you can also fill CODE up past $C000 if you don't put anything in the DPCM segment.

You also don't have to specify size with segments. They will just fill the space they're given if defined like this.

Edit: CHR size was accidentally $4000 instead of $2000.
Re: Program counter [ca65]
by on (#117328)
I made a fairly minimal working example of using ca65/ld65 with GNU Make.
Re: Program counter [ca65]
by on (#117337)
==
Thank you Jarhmander for a warm welcome!! I have experience with quite a few different programming languages but have always had a sweet spot in my heart for low level languages like C and ASM (strangely I wish more aspects of life required ASM, like my job :lol: ). During the most recent Ludum Dare game jam I participated in, someone was making a NES game and I was instantly found a new hobby (a wonderful mix of challenge and nostalgia).

==
Movax12, thank you! I won't have guessed that cl65 defaults to c64 config, this is extremely good to know.

==
tepples, your examples are very helpful. The "nes.ini/nes.cfg" file in the "nrom-template" is what actually got my code working!! So my original goal is complete but I have a bigger problem.

==
I don't understand the difference between rainwarrior's config file and the config file I got from the "nrom-template" (http://forums.nesdev.com/viewtopic.php?t=7991, tepples' template). There are a few differences in preference but one works for me and the other doesn't:

Works:
Code:
MEMORY {
   ZP:     start = $10,   size = $f0,   type = rw;
   HEADER: start = 0,     size = $0010, type = ro, file = %O, fill=yes, fillval=$00;
   RAM:    start = $0300, size = $0500, type = rw;
   ROM7:   start = $C000, size = $4000, type = ro, file = %O, fill=yes, fillval=$FF;
}

SEGMENTS {
   HEADER:   load = HEADER, type = ro,  align = $10;
   ZEROPAGE: load = ZP,     type = zp;
   BSS:      load = RAM,    type = bss, align = $100,  define = yes;
   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;
}


Doesn't:
Code:
MEMORY {
    ZP:     start = $00,    size = $100,    type = rw, file = "";
    RAM:    start = $0200,  size = $600,    type = rw, file = "";
    HDR:    start = $0000,  size = $10,     type = ro, file = %O, fill = yes;
    PRG:    start = $8000,  size = $8000,   type = ro, file = %O, fill = yes;
    CHR:    start = $0000,  size = $4000,   type = ro, file = %O, fill = yes;
}

SEGMENTS {
    ZEROPAGE:   load = ZP,  type = zp;
    BSS:        load = RAM, type = bss;
    HEADER:     load = HDR, type = ro;
    CODE:       load = PRG, type = ro, start = $8000;
    DPCM:       load = PRG, type = ro, start = $C000, optional = yes;
    VECTORS:    load = PRG, type = ro, start = $FFFA;
    TILES:      load = CHR, type = ro;
}


I agree with rainwarrior and have a preference for putting alignments in SEGMENTS rather than MEMORY.

What is also puzzling for me is - with the working config file I noticed that the code is loaded to $8000 but is being reloaded and executed around $C001. As a bonus my $FFFF is set to OVERFLOW so I'm thinking my alignments are all messed up? I'm not sure..

Also, my ASM since it has changed a little since last time.
Code:
; TESTING
; cl65 -t none -C nes.cfg -o test.nes test.s

.segment "HEADER"
   .byte "NES" ; signature
   .byte $1A   ; signature
   .byte $01   ; # of 16kb PRG-ROM banks
   .byte $00   ; # of 8kb VROM banks
   .byte $00   ; ROM control byte one
   .byte $00   ; ROM control byte two
   .byte $00   ; # of 8kb RAM banks
   .byte $00   ; reserved

.segment "VECTORS"
   .addr NMI
   .addr RESET
   .addr IRQ
   
.segment "CODE"

RESET:
   SEI
   LDA #$FF
LOOP:
   JMP LOOP
   RTI

NMI:
   RTI

IRQ:
   RTI

.segment "CHARS"
.segment "TILES"
Re: Program counter [ca65]
by on (#117392)
Could you explain what isn't working about my example? (I wouldn't have posted it if it didn't work fine for me.) Though I did notice I accidentally had twice as much CHR as necessary (easy to fix though, just changed $4000 to $2000), which apparently wasn't causing problems in the emulators I'd tried (since it's at the end of the file anyway), but it might in some.

If you're using a 16k NROM, your PRG will be mirrored at both $8000 and $C000, so I presume that's what you're seeing.
Re: Program counter [ca65]
by on (#117393)
In your example the memory seems to be set in the emulator just fine. The only problem I'm having is that the program counter isn't pointer to my RESET label at runtime. My current hypothesis is that it has something to do with my header, but I'm not sure. From what I've gathered I seem to be writing a 16kb nrom? Embarrassingly, I'm still not 100% on what that means. Nevertheless, I'm currently fueled with excitement since it is Labor day weekend in the US, thus giving me more time to dig in and figure stuff out.
Re: Program counter [ca65]
by on (#117395)
NROM-128 has 16384 bytes of PRG ROM and 8192 bytes of CHR ROM. NROM-256 has 32768 bytes of PRG ROM and 8192 bytes of CHR ROM. (The numbers in the board names refer to kibibits, which equal 1024 bits or 128 bytes.) You have to make sure that the ROM size in the linker configuration file matches the ROM size in the iNES header.
Re: Program counter [ca65]
by on (#117413)
I didn't realize you were using 16k NROM when I posted my example. I've almost never seen someone target just 16k for a homebrew!

For my example you would need to modify PRG to start at $C000 and be of size $4000, then modify CODE to start at $C000. If you want to use DPCM, maybe still start DPCM at $C000 and put CODE after it with no specified start so that it will just fill space between the DPCM segment and the vector table.

Or alternatively just specify 2 PRG banks in the header instead of 1 to get a 32k NROM.
Re: Program counter [ca65]
by on (#117417)
rainwarrior wrote:
I didn't realize you were using 16k NROM when I posted my example. I've almost never seen someone target just 16k for a homebrew!

A good percentage of homebrew games are NROM-128, such as four out of five NROM entries to the 2011 compo. It has something to do with the asset complexity that a one-man project can produce on schedule in spare time.
Re: Program counter [ca65]
by on (#117419)
Ok, I got it all working now. So by setting my header to NROM-256 (setting the number of 16kb PRG-ROM banks to $02) I'm able to run rainwarrior's example no problem.

==
NROM-256 example:
PRG size is $8000 -> 32,768 bytes
CHR size is $2000 -> 8,192 bytes
Code:
      start  end      size
PRG - $8000, $FFFF -> $8000 (ro)
CHR - $0000, $1FFF -> $2000 (ro)


==
NROM-128 example:
ROM7 size is $4000 -> 16,384 bytes
CHR ROM space -> (not sure where)
Code:
       start  end      size
ROM7 - $C000, $FFFF -> $4000 (ro)


This is good because now I have a choice between 128 and 256 NROM, which I was vaguely aware of before. What are the advantages of having less PRG ROM? Is it a trade off for having more memory for say graphics/music?
Re: Program counter [ca65]
by on (#117423)
There is no advantage unless you can save money manufacturing a cart with smaller ROM chips.
Re: Program counter [ca65]
by on (#117428)
It could help running it on a very limited-memory platform, or a small devcart. Also at some point, a larger PRG ROM will prevent some mappers, e.g. a 512K PRG rules out many mappers.
Re: Program counter [ca65]
by on (#117431)
For future reference, this is my "theoretical minimum", no graphics architecture - basically just using the NES as a 6502 emulator at this point (nrom-256).

nes.cfg
Code:
MEMORY {
   HDR: start = $0000, size = $0010, type = ro, fill = yes;
   PRG: start = $8000, size = $8000, type = ro, fill = yes;
   CHR: start = $0000, size = $2000, type = ro, fill = yes;
}

SEGMENTS {
   CODE:    load = PRG, type = ro;
   HEADER:  load = HDR, type = ro;
   VECTORS: load = PRG, type = ro, start = $FFFA;
}


test.s
Code:
; TESTING
; cl65 -t none -C nes.cfg -o test.nes test.s

.segment "CODE"

RESET:
   SEI
   LDA #$FF
LOOP:
   JMP LOOP
   RTI

NMI:
   RTI

IRQ:
   RTI

.segment "HEADER"
   .byte "NES" ; signature
   .byte $1A   ; signature
   .byte $02   ; # of 16kb PRG-ROM banks
   .byte $00   ; # of 8kb VROM banks
   .byte $00   ; ROM control byte one
   .byte $00   ; ROM control byte two
   .byte $00   ; # of 8kb RAM banks
   .byte $00   ; reserved

.segment "VECTORS"
   .addr NMI
   .addr RESET
   .addr IRQ


I would prefer a config file like this:
Code:
MEMORY {
   ZPG: start = $0000, size = $0100, type = rw, fill = yes;
   PRG: start = $8000, size = $8000, type = ro, fill = yes;
   CHR: start = $0000, size = $2000, type = ro, fill = yes;
}

SEGMENTS {
   CODE:    load = PRG, type = ro;
   HEADER:  load = ZPG, type = ro, align = $0010;
   VECTORS: load = PRG, type = ro, start = $FFFA;
}


But I can't get around defining the header in the memory section. This isn't a problem, it just annoys my OCD a little.
Re: Program counter [ca65]
by on (#117435)
You can pare it down more by putting the header at the beginning of code:
Code:
MEMORY {
   CODE: start = $7FF0, size = $A010, type = ro, fill = yes;
}
SEGMENTS {
   CODE: load = CODE, type = ro;
   VECTORS: load = CODE, type = ro, start = $FFFA;
}

Code:
.code
.byte "NES",$1A ; signature
.byte $02,$01,$00,$00,$00,$00 ; PRG,CHR,MH,HL,reserved,reserved

NMI:
IRQ:
RESET:
   JMP RESET

.segment "VECTORS"
   .addr NMI
   .addr RESET
   .addr IRQ

A downside is that you must define the header first.
Quote:
But I can't get around defining the header in the memory section. This isn't a problem, it just annoys my OCD a little.

Think of the header as another address space read by part of the system it's run on (emulator). That "hardware" reads bytes from it to determine your cartridge configuration, before it starts executing it. It makes sense that it needs its own memory area and segment, since it's no different than say the vectors area.
Re: Program counter [ca65]
by on (#117436)
StephenM wrote:
What are the advantages of having less PRG ROM? Is it a trade off for having more memory for say graphics/music?

The biggest practical advantage is that a smaller ROM can help get your work onto a collaborative multicart. A 512K cart, for example, can fit 12 NROM-256 games or 20 NROM-128 games (assuming 32K menu and no CHR compression). But if you can't cut your 20K PRG ROM to 16K, don't fret just yet, as some advanced multicart engines can compress other games' CHR data into the empty space.
Re: Program counter [ca65]
by on (#117440)
That is interesting, so the iNES header doesn't have to be at the beginning of the file?

I also noticed that CHR-ROM and PRG-ROM are on different memory maps. That is, CHR-ROM->PPU (I'm kind of assuming that CHR-ROM is basically the pattern tables, is that right?) & PRG_ROM->CPU but this distinction isn't being made in the config file. How does the emulator know to put my CHR in PPU and PRG in CPU? Or do I manually move the bytes over?

EDIT: also, I've attached the PDF I've been using as a reference
Re: Program counter [ca65]
by on (#117441)
Quote:
That is interesting, so the iNES header doesn't have to be at the beginning of the file?

No, it must be at the beginning of the output file. Which post suggested to you otherwise? It doesn't have to be at the beginning of the source file, just as the vectors don't have to be at the end, if you've set up appropriate segments.
Re: Program counter [ca65]
by on (#117442)
Ok I see, in your previous post I took it that the header was starting at $7FF0 of the output file.
Re: Program counter [ca65]
by on (#117451)
There's really no advantage to trying to stick the header into another MEMORY block like the PRG or zero page blocks. That's going beyond simplification into the realm of obfuscation. The header is not part of the PRG-ROM or RAM, it's really just data that substitutes for the physical circuit board of the cartridge, and it belongs in its own semantic space.

The MEMORY blocks will appear in the output file in the order they're specified. The data that ends up in the file is exactly the binary data you stick into it via the segments. The only thing the start location is for is to specify the program counter at the start of the block when linking. If you go beyond NROM, and start bankswitching, you will need to have several MEMORY blocks that use the same start location, since you're going to have multiple banks share the same memory space at different times, but the reuse of memory regions isn't a concern to the linker; it will still output each bank on its own in the order specified.

Yes, the iNES header needs to go at the start of the file, followed by the specified amount of PRG-ROM data (16k x the number specified), followed by the CHR-ROM data. If things are in the correct locations, the emulator will know where to load them into memory.

It is also useful to have segments that do not output to the file for ZP/RAM/WRAM so that you can reserve space on them in your assembly files, and the linker can pack them all together for you. If you don't have MEMORY regions for these things in your config, you will have to assign all of your RAM addresses by hand, and prevent conflicts manually.

IMO the "simplest" way is to lay out your MEMORY blocks like your iNES file. There should be a header, 1 block for each PRG bank (not necessarily 1 per 16k, it depends on your mapper, but 1 per bank, for NROM-256 I would suggest 1 x 32k block of PRG), and 1 block for each CHR bank. There should also be a block for each RAM region that does not output to the file (these can go before or after the file blocks). The SEGMENTS can be used to define aligned regions within the blocks as needed (e.g. the vector table).


Another funny trick is you can define a segment that will output in the file in one memory block, but be linked with a program counter as if it belongs in a different memory block. Specifically this is useful for code that you need to copy into RAM before running, e.g.:

Code:
SEGMENTS {
    SWAP: load = PRG, run = RAM, type = rw, define = yes;
}


The "load" is where this segment's data will end up in the output file, but the "run" is the memory location it is intended to run at. The "define=yes" creates symbols __SWAP_LOAD__, __SWAP_RUN__ and __SWAP_SIZE__ that are useful for the part of the code that needs to copy it to RAM and run it.
Re: Program counter [ca65]
by on (#117453)
rainwarrior wrote:
Another funny trick is you can define a segment that will output in the file in one memory block, but be linked with a program counter as if it belongs in a different memory block. Specifically this is useful for code that you need to copy into RAM before running

See also documentation of this ld65 feature. There's a working example in the Action 53 menu, which needs to copy description text and compressed CHR data from another bank before displaying them. Putting the copying code into RAM keeps it running while it switches to another 32K bank.
Re: Program counter [ca65]
by on (#117477)
Thank you for that explanation, it helped a lot! I have decided to add the Zeropage and RAM to my MEMORY/SEGMENTS section as to avoid manually setting my RAM addresses (but I will be sure to set file = "" so these blocks aren't written to my output file). I can also see the "run" attribute for the SEGMENTS section coming in handy down the road. I'll make note of it!
Re: Program counter [ca65]
by on (#117506)
StephenM wrote:
(but I will be sure to set file = "" so these blocks aren't written to my output file).

This is not needed if you set the the segment type to "bss".
Re: Program counter [ca65]
by on (#117543)
Ok, that makes sense. I feel like I have a better understanding of what my setup should look like now. Next I plan to learn more about setting up sprites and the PPU; also, how best to handle NMI interrupts. I need to catch up on my reading and I'll collect all questions for a future post.

Thank you for all the information provided here, without a place like this on the internet the NES learning curve would be near insurmountable.