cc65 - 32KiB PRG banks and segments

This is an archive of a topic from NESdev BBS, taken in mid-October 2019 before a server upgrade.
View original topic
cc65 - 32KiB PRG banks and segments
by on (#227575)
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:
#pragma code-name(push, "MY32KBANK");
char func(unsigned char x){
    return x;
}
#pragma code-name(pop);


It's compiled to
Code:
.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:
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
Re: cc65 - 32KiB PRG banks and segments
by on (#227576)
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.
Re: cc65 - 32KiB PRG banks and segments
by on (#227577)
Greentings dougeff, thank you for your reply!

Quote:
Then concatenate them together at the end

Like this?
Code:
$ 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:
void func1(void);
void func2(void);


bank1.c
Code:
#include "funcs.h"

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

void func1(void) {
   ...
}


bank2.c
Code:
#include "funcs.h"

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

void func2(void) {
   ...
}
Re: cc65 - 32KiB PRG banks and segments
by on (#227579)
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.
Re: cc65 - 32KiB PRG banks and segments
by on (#227581)
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?
Re: cc65 - 32KiB PRG banks and segments
by on (#227584)
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.
Re: cc65 - 32KiB PRG banks and segments
by on (#227596)
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...
Re: cc65 - 32KiB PRG banks and segments
by on (#227599)
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.
Re: cc65 - 32KiB PRG banks and segments
by on (#227601)
Quote:
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:
# 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:
# 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:
$ cat header.bin bank1.bin bank2.bin > game.nes
to get the result.
Re: cc65 - 32KiB PRG banks and segments
by on (#227603)
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).
Re: cc65 - 32KiB PRG banks and segments
by on (#227605)
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.
Re: cc65 - 32KiB PRG banks and segments
by on (#227606)
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.
Re: cc65 - 32KiB PRG banks and segments
by on (#227607)
calima wrote a "trampoline" extension for cc65 to facilitate that:
https://forums.nesdev.com/viewtopic.php?f=2&t=15959
Re: cc65 - 32KiB PRG banks and segments
by on (#227611)
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!
Re: cc65 - 32KiB PRG banks and segments
by on (#227614)
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:
# 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
}
Re: cc65 - 32KiB PRG banks and segments
by on (#227621)
NOOPr wrote:
But it have a drawback: each bank will going to have data from segment "CODE" from every other bank

Use #pragma code-name, data-name, bss-name, and you can decide what segment each goes in.
Re: cc65 - 32KiB PRG banks and segments
by on (#227651)
rainwarrior wrote:
NOOPr wrote:
But it have a drawback: each bank will going to have data from segment "CODE" from every other bank

Use #pragma code-name, data-name, bss-name, and you can decide what segment each goes in.

You cannot do this for data imported from runtime.lib
Re: cc65 - 32KiB PRG banks and segments
by on (#227661)
That's true but it is the only thing that has to use those segments, so you can place the runtime where it is needed in your .cfg and for all other code send it to other segments.