how to manage code per bank when in C

This is an archive of a topic from NESdev BBS, taken in mid-October 2019 before a server upgrade.
View original topic
how to manage code per bank when in C
by on (#224153)
If I'm using a mapper that uses multiples banks and I write the code in assembler, I know how to define the segments and put them at the right place in the cfg file. My issue is if I write the code in C or define some data in C structure, how can I say that this code or this data should be in a specific bank?

For now I didn't find much yet. There seems to be something new (2017) about trampoline or something but I cannot visualize how to organize my code around that. Then I saw an old post (2016) by dougeff that seems to compile the code separately and put the vector in each file but that seems to be for a very specific mapper.

I guess I should the mapper that I would like to target, and that would be the mmc3. So what approach can be used with that mapper? Since I'm getting to like to write some part in C that would be a shame to have to go back to asm and rewrite the code.
Re: how to manage code per bank when in C
by on (#224154)
Check out #pragma code-name and wrapped-call:
https://cc65.github.io/doc/cc65.html
Re: how to manage code per bank when in C
by on (#224155)
That particular extension was written by calima. There's some more info here:
https://forums.nesdev.com/viewtopic.php?f=2&t=15959
Re: how to manage code per bank when in C
by on (#224175)
@slembcke

This is the first thing I looked at. It is very ambiguous unless you already know this coding pattern. This is why I searched more on the subject and ended back on nesdev :lol:

@rainwarrior
I saw that post. This is actually the first one I found when searching on the subject (actually there was a post from 2016 when it was not possible yet but that post is not relevant anymore).

Maybe it's because it was 2h in the morning when I checked the content but I cannot visualize how I would use it in a C project to put C code in specific segment/bank. This example seems to explain the "how" to call the code in that bank. My current problem is I don't even know how to put the code in a specific bank in the first place. There is ".segment" in assembler but no such thing in C. I think it just ends up in the "code" segment by default. If I have to compile file per file and put in specific bank then this requires a complete rethinking of my makeFile that just compile everything and let the linker do the job.

Is there an example that shows how C code is put in a specific bank then called later? If I can see with my eyes and experiment with it then I will be able to figure out how to reproduce in my own code.
Re: how to manage code per bank when in C
by on (#224178)
CC65's C has pragmas that can be used to set segments. Use #pragma code-name to put code into a specific segment, and #pragma rodata-name to put read-only (const) data in a specific segment.
Re: how to manage code per bank when in C
by on (#224182)
I knew about those pragma but I completely misunderstood their usage! I was sure it was to change the name of the current "code/data" segment to something else inside my code instead of through the command line, not to add extra segments. Must be an English as a second language misunderstanding kind of thing.

Thank you for pointing that out. Now I need to migrate back my project to my target mapper and see how it goes.
Re: how to manage code per bank when in C
by on (#224284)
You can also check my UNROM-ready template using neslib and famitone.

viewtopic.php?p=169438#p169438
Re: how to manage code per bank when in C
by on (#224301)
I will start to experiment soon but there is one thing that I would like to confirm before trying it.

What is the use of the trampoline code? I'm not sure yet. I'm my current project, what I expect to do is to switch the bank first by calling a method made in ASM then call the code expected in that bank. I would think with that way that it would work right away. So if this work then what is the use of the trampoline code? My guess would be go to a function by changing the bank before then calling the method then going back to previous bank?
Re: how to manage code per bank when in C
by on (#224304)
The answer relates to what bank is active immediately after a function returns (using the rts instruction in assembly, or return or reaching the end of a void function in C). To help us understand what you expect to do, please answer these questions with respect to that:

To which bank does the subroutine made in assembly language to change the bank return?
To which bank does the function in the new bank return?
Re: how to manage code per bank when in C
by on (#224306)
I think it's a good idea, for starters, to begin with a project that has all relevant code (i.e. main loop) in the fixed bank, which calls to subroutines (i.e. functions) in different pages. That's the simplest scenario. You just bank switch to make the relevant bank visible, then call the function. Once you get the hang of it you can start complicating things.

My game Cheril the Goddess does exactly that. All the main stuff is in the fixed bank, and subroutines in different banks.
Re: how to manage code per bank when in C
by on (#224307)
@Tepples

What I expect to do is like na_th_an said. In my fixed bank I will have code that contains the main flow of the game. When I change a phase (for example title screen, menu, game play), the fixed bank prepare the state, change the bank then call the code inside that bank.

Inside that code, it will loop until that state is over. Once over, it return to the fixed bank code to decide what to do next. So I think in that kind of scenario, the "trampoline" code ins not necessary unless I'm mistaken?
Re: how to manage code per bank when in C
by on (#224310)
The "multicart full of 16K games" organization might work until a single phase needs to access more than 16K of total code and data.
Re: how to manage code per bank when in C
by on (#224314)
You indirectly answered my question. What you means is, yes, it will be fine except if you data/code becomes more than 16k you will have issues.

So I have to be careful regarding size of data and code.
Re: how to manage code per bank when in C
by on (#224335)
I think you should try to keep all of the code in the fixed bank and only use the several banks for data. This way, the control of the program is always in the fixed bank. Otherwise you might run into an issue when your code from a variable bank needs data from yet another bank.

For example, in my game I cannot put the functions to build a new screen into a variable bank. Because I will probably need more than one bank for all the screen data anyway, so the code has to be in the fixed bank, so that it's able to switch between all the banks to read all those constant arrays.

I have one exception:
The code for the text screens (title screen, credits etc.) is in a separate bank.
Text screen logic is separated enough from the actual main game logic, so that there's no interaction between the text screens and any read-only data from the game logic.
And one bank is more than enough to store all the text screen code and its display data, so in this particular case I can be sure that there won't be any conflicts.

But stuff like the music library or rendering the sprites: That always goes into the fixed bank while the songs themselves and the meta sprite definitions are in their own banks.

(If you use a mapper with extended cartridge RAM, you can use this RAM for yet some more KB of always available code: Just store some code into a variable bank. And at the start of the program, you copy this code from the bank into RAM.
cc65 config files have a feature that let you store data into one place, but execute it as if it was located in another place. You can define a segment for your code and tell the compiler that it's stored in one memory location, but executed in another.)
Re: how to manage code per bank when in C
by on (#224346)
More generally: If one code module needs to refer to no more than about 12K of data, it and its data go in a bank. Otherwise, it can go in the fixed bank and refer to multiple banks of data.

Putting all code in the fixed bank may limit how many distinct player, enemy, and projectile behaviors you can add. It did in The Curse of Possum Hollow, requiring several refactors over the course of its development to move things out as the fixed bank at $C000-$FFFF filled up.
Re: how to manage code per bank when in C
by on (#224373)
DRW wrote:
I think you should try to keep all of the code in the fixed bank and only use the several banks for data. This way, the control of the program is always in the fixed bank. Otherwise you might run into an issue when your code from a variable bank needs data from yet another bank.

This is not a problem with mappers like FME-7 though, where you have 5 bank slots available (only one of which is fixed).
Re: how to manage code per bank when in C
by on (#224424)
I think from everyone comments I have a good idea what to test. The state management code should stay in the main bank, code like for title screen, main menu should be in the same bank as their assets, and the rest I will experiment based on space.

Got plenty of ideas to test. Thanks everyone!
Re: how to manage code per bank when in C
by on (#224430)
Also, make sure that code in non-fixed banks really only uses stuff from its own bank and from the fixed bank, but not from another bank.

For example, if you have a pause screen that uses its own color palettes, then unpausing the game needs to set the palettes back to the original level values.

If you saved the original colors into an array in RAM before, then everything is alright.

But if you re-read the level colors from the corresponding ROM location, you better do this after the pause screen functions have ended.
Remember: If your pause screen functions are in the variable bank and your colors are stored in another variable bank, then switching to the color bank while the pause screen functions are running will pull the code away and your game will crash.
Re: how to manage code per bank when in C
by on (#224433)
Yes, this is something I will have to be careful about. At first I selected the wrong bank and since it was only the data the impact was a very "interesting" map but with code, that won't be as forgiving :lol:

I will keep that in mind while testing. thanks.
Re: how to manage code per bank when in C
by on (#224455)
Another, non-technical hint:

I created a naming scheme for my constants and functions, so that I always know which banks I need to set.

First, I gave every bank a short name, like "Lvl" for level data and so on.

Every constant that is located in this bank gets a postfix:
const byte Screen1_bLvl[]

This way, I always know that I have to change the bank before using this constant.

The same goes for pointer variables that access those arrays:
const byte *CurrentScreen_bLvl
This variable is not tied to a bank since it's a variable in RAM. But it will point to something in a bank, so it gets the postfix as well.

Functions get the naming scheme as well:
void __fastcall__ LoadCurrentScreen_bLvl(void)
This postfix can mean one of two things:
Either the code of this function is stored in a bank itself.
Or the code accesses data from another bank, but doesn't set the bank itself.
In both cases, I must be sure that the corresponding bank is set before I call the function.

If a function changes banks itself, they get this:
void __fastcall__ SomeFunction_setsB(void)
This way you know that calling this function will change the bank, so if you rely on a bank that you set before calling this function, you need to set it again.


Remember that those names need to be done recursively:
If you put an array in a variable bank, then all functions who call this array need their name changed. And all functions who call those functions need their names to be changed as well and so on. Unless one function sets the corresponsing bank. In this case, this function only needs the "_setsB" postfix:
Code:
Screen1_bLvl
--> referenced by CurrentScreen_bLvl
    --> used in LoadCurrentScreen_bLvl()
        --> called in LoadAllLevelData_bLvl()
            --> called in ProcessGameLogic_setsB() after SwitchBank() was called