cc65 - 32KiB PRG banks and segments

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
NOOPr
Posts: 64
Joined: Tue Feb 27, 2018 10:41 am
Location: Brazil
Contact:

cc65 - 32KiB PRG banks and segments

Post by NOOPr » Tue Oct 16, 2018 3:39 am

I'm trying to figure out how to compile a game which swaps prg banks in 32KiB (no fixed bank) and have all the imported functions properly writen to the current segment.
An example:

Given the C code

Code: Select all

#pragma code-name(push, "MY32KBANK");
char func(unsigned char x){
    return x;
}
#pragma code-name(pop);
It's compiled to

Code: Select all

.segment	"MY32KBANK"

.proc	_func: near

.segment	"MY32KBANK"

;
; char func(unsigned char x){
;
	jsr     pusha
;
; return x;
;
	ldx     #$00
	lda     (sp,x)
;
; }
;
	jmp     incsp1

.endproc
But the functions pusha and incsp1 are stored into the "CODE" segment

Code: Select all

runtime.lib(incsp1.o):
    CODE              Offs=000C90  Size=000007  Align=00001  Fill=0000
runtime.lib(pusha.o):
    CODE              Offs=000C97  Size=000016  Align=00001  Fill=0000

It's possible to have those imported functions stored on the segment of the caller or there're other way to code a game in C with no fixed bank?

Thanks in advance

User avatar
dougeff
Posts: 2616
Joined: Fri May 08, 2015 7:17 pm
Location: DIGDUG
Contact:

Re: cc65 - 32KiB PRG banks and segments

Post by dougeff » Tue Oct 16, 2018 4:52 am

Compile, assemble, and link each bank separately. Then concatenate them together at the end.

Each bank will have Its own runtime library and CODE segments.
nesdoug.com -- blog/tutorial on programming for the NES

User avatar
NOOPr
Posts: 64
Joined: Tue Feb 27, 2018 10:41 am
Location: Brazil
Contact:

Re: cc65 - 32KiB PRG banks and segments

Post by NOOPr » Tue Oct 16, 2018 6:04 am

Greentings dougeff, thank you for your reply!
Then concatenate them together at the end
Like this?

Code: Select all

$ cat bank1.nes bank2.nes...bankN.nes > game.nes


This approach solves the described problem very whell, but what to do when the banks are interdependent?

Example (farcall/trampoline functions are omitted):

funcs.h

Code: Select all

void func1(void);
void func2(void);
bank1.c

Code: Select all

#include "funcs.h"

void foo(void) {
	func2(); // <-- func2 implemented into bank 2
}

void func1(void) {
	...
}
bank2.c

Code: Select all

#include "funcs.h"

void bar(void) {
	func1(); // <-- func1 implemented into bank 1
}

void func2(void) {
	...
}

User avatar
dougeff
Posts: 2616
Joined: Fri May 08, 2015 7:17 pm
Location: DIGDUG
Contact:

Re: cc65 - 32KiB PRG banks and segments

Post by dougeff » Tue Oct 16, 2018 7:30 am

This is why I recommend a mapper with a fixed bank.

But, for 32k banks...

...my solution would be to copy some code into the RAM and JSR there, where banks are switched, and the calling bank is saved for later return.

Ideally, you would write that code in ASM.
nesdoug.com -- blog/tutorial on programming for the NES

User avatar
NOOPr
Posts: 64
Joined: Tue Feb 27, 2018 10:41 am
Location: Brazil
Contact:

Re: cc65 - 32KiB PRG banks and segments

Post by NOOPr » Tue Oct 16, 2018 7:45 am

dougeff wrote:my solution would be to copy some code into the RAM and JSR there, where banks are switched, and the calling bank is saved for later return.
This is for bank switching ... what I'm asking is how do you compile/assemble/link the code I mentioned above (funcs.h, foo.c, bar.c) separated if they depend on each other?

User avatar
dougeff
Posts: 2616
Joined: Fri May 08, 2015 7:17 pm
Location: DIGDUG
Contact:

Re: cc65 - 32KiB PRG banks and segments

Post by dougeff » Tue Oct 16, 2018 7:56 am

I don't know. Array of function pointers to extern function, that you would have to patch the addresses of in later?

That does seem a bit complicated.
nesdoug.com -- blog/tutorial on programming for the NES

calima
Posts: 1018
Joined: Tue Oct 06, 2015 10:16 am

Re: cc65 - 32KiB PRG banks and segments

Post by calima » Tue Oct 16, 2018 10:55 am

You could put CODE and RODATA in RAM, along with the trampoline. Though that probably won't work unless you have the extra 8kb cart RAM, and if you do, then why would you use a mapper that requires 32kb switches...

User avatar
rainwarrior
Posts: 7677
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: cc65 - 32KiB PRG banks and segments

Post by rainwarrior » Tue Oct 16, 2018 11:29 am

If you want to run C code in multiple 32k banks I think the only practical way is to duplicate the runtime library in each bank that has code. As pointed out, this requires each bank to be linked separately.

Alternatively, with a mapper that has a fixed bank you can just put the runtime in the fixed bank. That might be a better option, depending on what you need/have.

For bankswitching 32k, you can do it in RAM, but it's perfectly fine to do in ROM if you just put a bankswitch routine at the same location in each bank (use the "start" attribute for a segment), that way when the bankswitch happens it will still be inside that same routine, just in a different bank.

russellsprouts
Posts: 54
Joined: Sun Jan 31, 2016 9:55 pm

Re: cc65 - 32KiB PRG banks and segments

Post by russellsprouts » Tue Oct 16, 2018 11:50 am

This approach solves the described problem very whell, but what to do when the banks are interdependent?
You'll have to compile and link them separately, but make sure to include all object files in all builds. Even though you are generating 1 bank at a time, the linker needs to know the contents of every bank to do it.

In your example above, you'll have two linker configs:

Code: Select all

# pseudo code for bank1.cfg
MEMORY {
...
CODE:      start=$8000, size=$xxxx, fill=yes, file=%O; # make this just big enough to fit the library/common code
BANK1CODE: start=$xxxx, size=$xxxx, fill=yes, file=%O; # give the rest to bank1code
BANK2CODE: start=$xxxx, size=$xxxx, fill=yes;          # bank2 should be identical to BANK1CODE
...
}
SEGMENTS {
... # put segment definitions here
}

Code: Select all

# pseudo code for bank2.cfg
MEMORY {
...
CODE:      start=$8000, size=$xxxx, fill=yes, file=%O; # make this just big enough to fit the library/common code
BANK1CODE: start=$xxxx, size=$xxxx, fill=yes;          # don't output bank1 this time
BANK2CODE: start=$xxxx, size=$xxxx, file=yes, file=%O; # output bank2 instead
...
}
SEGMENTS {
... # put segment definitions here
}
Run the linker with each of these configs, passing the object files for all banks each time. It will assign memory locations to all of the banks You'll get separate files, say "bank1.bin" and "bank2.bin". Each one will be 32kb, containing first the library code as a virtual fixed bank, then the code for the bank. That way incsp1, pusha, etc. will all be in the virtual fixed bank. Run

Code: Select all

$ cat header.bin bank1.bin bank2.bin > game.nes
to get the result.

User avatar
gauauu
Posts: 657
Joined: Sat Jan 09, 2016 9:21 pm
Location: Central Illinois, USA
Contact:

Re: cc65 - 32KiB PRG banks and segments

Post by gauauu » Tue Oct 16, 2018 12:03 pm

rainwarrior wrote:If you want to run C code in multiple 32k banks I think the only practical way is to duplicate the runtime library in each bank that has code. As pointed out, this requires each bank to be linked separately.
Alternatively, you can reserve empty space in each other bank for the runtime library, link the whole thing once, then copy the binary runtime library chunk from the first bank into the space you reserved in each bank. That might be more painful than linking separately, though.

My preferred method is just to keep all my C code in the first bank, and do assembly-only in the other banks. Or, for the other banks, write my C code more carefully so that it doesn't make runtime library calls (but that's a pain to do).

User avatar
rainwarrior
Posts: 7677
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: cc65 - 32KiB PRG banks and segments

Post by rainwarrior » Tue Oct 16, 2018 12:19 pm

gauauu wrote:Alternatively, you can reserve empty space in each other bank for the runtime library, link the whole thing once, then copy the binary runtime library chunk from the first bank into the space you reserved in each bank. That might be more painful than linking separately, though.
I guess that can work... (you could even do the final patching with another ca65 pass and .incbin). The lazy linking thing seems like a problem, because the size of the runtime is going to grow as you write more code and happen to implicitly require more of the library...
gauauu wrote:...write my C code more carefully so that it doesn't make runtime library calls (but that's a pain to do).
That sounds like a nearly impossible task to me. :(


russellsprouts technique of having extra memory blocks that don't output works pretty well, I've used that in a lot of cases where I need addresses from different links to match up. Has the bonus that you only need to assemble the objects once, but get to use them in multiple links.

User avatar
dougeff
Posts: 2616
Joined: Fri May 08, 2015 7:17 pm
Location: DIGDUG
Contact:

Re: cc65 - 32KiB PRG banks and segments

Post by dougeff » Tue Oct 16, 2018 12:59 pm

No wait. I got it.

So, each bank will have, at a fixed location, an array of pointers to each shared function.

Also, each bank will have a copy, at a fixed location, of the bank changing code.

You just need to have the offset of the function in the array, and the and the bank it's in, and call the bank switching function.

That will switch the bank, then read the address of the pointer off the list of functions, then call that, then exiting that function should return to the bank switching code, where it will switch back.

But then you need to find a way back to the original calling bank, so the original bank needs to be saved for that.
nesdoug.com -- blog/tutorial on programming for the NES

User avatar
rainwarrior
Posts: 7677
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: cc65 - 32KiB PRG banks and segments

Post by rainwarrior » Tue Oct 16, 2018 1:20 pm

calima wrote a "trampoline" extension for cc65 to facilitate that:
https://forums.nesdev.com/viewtopic.php?f=2&t=15959

User avatar
NOOPr
Posts: 64
Joined: Tue Feb 27, 2018 10:41 am
Location: Brazil
Contact:

Re: cc65 - 32KiB PRG banks and segments

Post by NOOPr » Tue Oct 16, 2018 3:13 pm

russellsprouts 's technique is what I was looking for.
But it have a drawback: each bank will going to have data from segment "CODE" from every other bank, for example:

Suppose that:
code at BANK1CODE imports func0 and func1 from runtime.lib
and that:
code at BANK2CODE imports func2 and func1 from runtime.lib

So, the "virtual" bank at segment "CODE" will store func0, func1 and func2.
The cool thing about this is that you can store your trampoline and nmi/irq functions at segment "CODE", it'll be safe to call from any bank.

Thank you guys!

User avatar
NOOPr
Posts: 64
Joined: Tue Feb 27, 2018 10:41 am
Location: Brazil
Contact:

Re: cc65 - 32KiB PRG banks and segments

Post by NOOPr » Tue Oct 16, 2018 3:40 pm

Just one correction on the russellsprouts example: you have to omit the "fill=yes" and "file=%O" or else the linker will ignore and outputs the bank

correct:

Code: Select all

# pseudo code for bank2.cfg
MEMORY {
...
CODE:      start=$8000, size=$xxxx, fill=yes, file=%O; # make this just big enough to fit the library/common code
BANK1CODE: start=$xxxx, size=$xxxx;                    # don't output bank1 this time
BANK2CODE: start=$xxxx, size=$xxxx, file=yes, file=%O; # output bank2 instead
...
}
SEGMENTS {
... # put segment definitions here
}

Post Reply