It is currently Wed Nov 14, 2018 7:08 pm

All times are UTC - 7 hours





Post new topic Reply to topic  [ 18 posts ]  Go to page 1, 2  Next
Author Message
PostPosted: Tue Oct 16, 2018 3:39 am 
Offline

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


Top
 Profile  
 
PostPosted: Tue Oct 16, 2018 4:52 am 
Offline
User avatar

Joined: Fri May 08, 2015 7:17 pm
Posts: 2331
Location: DIGDUG
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


Top
 Profile  
 
PostPosted: Tue Oct 16, 2018 6:04 am 
Offline

Joined: Tue Feb 27, 2018 10:41 am
Posts: 18
Location: Brazil
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) {
   ...
}


Top
 Profile  
 
PostPosted: Tue Oct 16, 2018 7:30 am 
Offline
User avatar

Joined: Fri May 08, 2015 7:17 pm
Posts: 2331
Location: DIGDUG
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


Top
 Profile  
 
PostPosted: Tue Oct 16, 2018 7:45 am 
Offline

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


Top
 Profile  
 
PostPosted: Tue Oct 16, 2018 7:56 am 
Offline
User avatar

Joined: Fri May 08, 2015 7:17 pm
Posts: 2331
Location: DIGDUG
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


Top
 Profile  
 
PostPosted: Tue Oct 16, 2018 10:55 am 
Offline

Joined: Tue Oct 06, 2015 10:16 am
Posts: 819
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...


Top
 Profile  
 
PostPosted: Tue Oct 16, 2018 11:29 am 
Offline
User avatar

Joined: Sun Jan 22, 2012 12:03 pm
Posts: 6949
Location: Canada
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.


Top
 Profile  
 
PostPosted: Tue Oct 16, 2018 11:50 am 
Offline

Joined: Sun Jan 31, 2016 9:55 pm
Posts: 48
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.


Top
 Profile  
 
PostPosted: Tue Oct 16, 2018 12:03 pm 
Offline
User avatar

Joined: Sat Jan 09, 2016 9:21 pm
Posts: 493
Location: Central Illinois, USA
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).

_________________
My games: http://www.bitethechili.com


Top
 Profile  
 
PostPosted: Tue Oct 16, 2018 12:19 pm 
Offline
User avatar

Joined: Sun Jan 22, 2012 12:03 pm
Posts: 6949
Location: Canada
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.


Top
 Profile  
 
PostPosted: Tue Oct 16, 2018 12:59 pm 
Offline
User avatar

Joined: Fri May 08, 2015 7:17 pm
Posts: 2331
Location: DIGDUG
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


Top
 Profile  
 
PostPosted: Tue Oct 16, 2018 1:20 pm 
Offline
User avatar

Joined: Sun Jan 22, 2012 12:03 pm
Posts: 6949
Location: Canada
calima wrote a "trampoline" extension for cc65 to facilitate that:
https://forums.nesdev.com/viewtopic.php?f=2&t=15959


Top
 Profile  
 
PostPosted: Tue Oct 16, 2018 3:13 pm 
Offline

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


Top
 Profile  
 
PostPosted: Tue Oct 16, 2018 3:40 pm 
Offline

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


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 18 posts ]  Go to page 1, 2  Next

All times are UTC - 7 hours


Who is online

Users browsing this forum: No registered users and 5 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
Powered by phpBB® Forum Software © phpBB Group