Original Post

I got a question on variables. I’m certain the answer to my question lies the compiler settings, and likely a bit of the processor, which are two aspects I still know little about, but when you just create a variable… where does it decide to place it?

Say for instance I create a u16 variable called gamePadState as a global variable. Does it keep this in WRAM or Cart RAM? What address does it decide to start at, the beginning of one of those two ranges or somewhere haphazardly? And what about if you specify specific addresses for other variables, as I am doing with tables of structs, does the compiler know enough to avoid the range when determining placement? And is there a way you can specify ranges of memory these variables can be made in?

Also related, where do temporary variables, such as ones made during a chain assignment, get placed as well?

By extension, this is also very tied closely to the heap I imagine so any information regarding it as well would be appreciated!

I’ve been laying out a memory map to use for my game and I just want to know what I should be looking out for regarding variables like those.

Sorry for asking a relatively complex and broad question. It’s more or less my last major hurdle, outside of eventually peering into sound and possibly the cable. Thanks in advance for any info! πŸ™‚

13 Replies

Say for instance I create a u16 variable called gamePadState as a global variable. Does it keep this in WRAM or Cart RAM?

Global and static variables go into the WRAM. The cartridge SRAM is intended for saving game data. It is only accessed when you explicitly follow a pointer to it (the range 0x06xxxxxx).

What address does it decide to start at, the beginning of one of those two ranges or somewhere haphazardly?

I think it starts right at 0x05000000, but that’s an implementation detail and you shouldn’t even need to know about it, much less rely on it.

And what about if you specify specific addresses for other variables, as I am doing with tables of structs, does the compiler know enough to avoid the range when determining placement?

Please give an example. If you’re doing something like

struct MYSTRUCT* x = (struct MYSTRUCT*)0x0500ABCD;

then no, because you might be using that pointer to point to an array and access x[1000].

And is there a way you can specify ranges of memory these variables can be made in?

None that I know of. The vb.ld file maybe?

Also related, where do temporary variables, such as ones made during a chain assignment, get placed as well?

Temporary values go into the registers. In the case of running out of them (which is very unlikely on the V810), they would probably go on the stack, which is normally at the end of WRAM and grows towards lower addresses. The stack is also where local variables reside, unless you explicitly declare them with the register keyword (to my knowledge, gccVB will never automatically assign registers to them).

By extension, this is also very tied closely to the heap I imagine so any information regarding it as well would be appreciated!

If you’re writing a dynamic memory manager, probably the safest and simplest way to go is to just declare a large array (say, 32K) and manage that.

Thanks for answering all my questions so quickly!

Mmm, gotcha so basically it just goes systematically down from WRAM with variable creation, and avoiding any specifically addressed variables, yes? I might just create a few global variables for loop counters and the like so I can layout the memory without worry of it. And yes, that’s how I was doing my arrays. I realized there would be no way to be able to tell after I posted haha.. whoops!

I’m not exactly writing a memory management system per say, more just chunking off memory saying this will be used for this in a typical older game fashion. I won’t be changing much and figured I could save the processing and like involved doing typical memory managing.

This question isn’t exactly related, but I figured I’d ask this too real quick. Is it good practice to use SRAM for just typical RAM use even if there is no need to save such data? I saw the profiling done by Guy Perfect and noticed there isn’t any speed penalty using it and potentially much bigger addressable space. I was planning to use WRAM for space to do decompressions at, and storing sprites/instruments at and using the SRAM as the main RAM. Would that be a good idea?

The linker script is the right place for that.
You will have to know how your compiler names these variables, then you can simply put them where you want them using the linker script.

You could also use gcc-specific extension to determine the section your variables end up in, instead of the usual data, bss etc.

my_file.c:
int my_variable __attribute__((section(".bss_special.my_variable")));

vb.ld:
MEMORY
{
    rom : org = 0x07000000, len = 16M
    sram : org = 0x06000000, len = 8M /* lower 16M */
    sram_wram : org = 0x06800000, len = 8M /* upper 16M */
    wram : org = 0x05000000, len = 64k
}

...

.bss_special :
{
	. = ALIGN(4);
	PROVIDE(_bss_special_start = .);
	*(.bss_special)
	. = ALIGN(4);
	*(.bss_special.*)
	. = ALIGN(4);
	PROVIDE(_bss_special_end = .);
} > sram_wram

Then you can abuse the upper 8MB of SRAM as WRAM. Ideally, you would have a cartridge that actually has 16MB SRAM, and where the upper 8MB may or may not be battery-backed.

Edit the startup *.S files to initialize this segment to zero if you really want to using _bss_special_start, _bss_special_end.

cYa,

Tauwasser

HollowedEmpire wrote:
This question isn’t exactly related, but I figured I’d ask this too real quick. Is it good practice to use SRAM for just typical RAM use even if there is no need to save such data? I saw the profiling done by Guy Perfect and noticed there isn’t any speed penalty using it and potentially much bigger addressable space. I was planning to use WRAM for space to do decompressions at, and storing sprites/instruments at and using the SRAM as the main RAM. Would that be a good idea?

Sure, as long as you’re fine with your game not running on SRAM-less cartridges. I don’t think 8K of extra RAM is worth it. If you really need it, sacrifice one of the BGMaps, although VIP memory is slower to access. I think you should make it work first and then worry about performance if it’s not fast enough, because it probably will be.

@Tauwasser

Oooo, thanks for that! I’ll have to definitely look into playing the linker scripts. Very interesting!


@HorvatM

Oh, I wasn’t aware there were carts without SRAM chips! I thought they just didn’t included batteries, not missing the entire chip itself. I apparently don’t know a lot about the hardware! Maybe that extra 8K wouldn’t be worth it then… but I do intend for a saving feature, so a battery cartridge would be a good thing to have for this game nonetheless. But could be played without too in one sitting I imagine for those without one.

From the linker’s point of view, there are three distinct sections in the program:

.rodata and .text are both for ROM data. They’re stored in the program code itself, and are read-only. .rodata is for general data, where .text is for the actual program code.

.bss is for static data. This is located in system RAM (WRAM) and may be initialized at program startup. Most linkers will include a routine for initializing this data if necessary. Certain operating systems may just map executable data directly to these addresses, so all the initialization is present byte-for-byte in the executable.

From a memory usage standpoint, things like lookup tables or string literals should be stored in .text/.rodata, whereas global variables and static variables will wind up in .bss. You can control where things end up by using keywords to give hints to the compiler about how data will be used:

Any variable declared outside the scope of functions will be treated as static global. Static global variables are accessible in other source files by using the “extern” keyword. For example:

main.c

int some_global = 5;

other.c

extern int some_global;

If some function in other.c accesses some_global, it will by default hold the value of 5, as declared in main.c.

Confusingly, using the “static” keyword on static global variables will prevent the variable from being accessed from other source files:

main.c

static int some_global = 5;

If you try to access some_global in other.c this way, you’ll get an “undefined reference” error at link-time.

The static keyword can be used on functions to prevent other source files from using them.

When used on local variables inside of functions, the static keyword places the variable in static memory, aka the .bss section. The implication is that the value of a static variable will be retained between invocations of the function. For example:

int someFunc(int modifier) {
    static int some_var = 5;
    some_var += modifier;
    return some_var;
}

When static memory is initialized, some_var will be set to 5. If you call the function with an argument of 1, the first time it will return 6, the second time will return 7 and so-on.

And that brings us to the biggie… Where are local variables stored if they’re not static? The answer is that they’re stored on the “stack”. The stack is a region of memory (WRAM again), typically starting at the very end. When you “push” a value to the stack, it gets stored at the location given by the “stack pointer”, and then the stack pointer is subtracted. Some systems may subtract before writing. Some other systems yet, like Virtual Boy, don’t actually have a formal stack and the software has to implement its own. This is handled by gcc for V850/V810, so you don’t have to worry about it. “Pulling” from the stack reverses the process.

When you call a function, the program first pushes the “return address” to the stack, which is the location in ROM where to return to after the function completes. It then transfers execution to the ROM address of the function. The first thing that happens in any function is that the stack is further manipulated to make room for local variables. Local variables are accessed “stack relative”, meaning relative to the stack pointer. After all, the stack pointer can be different each time you call a function, so local variables are always relative to that. When the function completes, it puts the stack pointer back where it was when the function started, then transfers execution to the return address, which itself was on the stack before the function was called.

So you can see that system memory is limited not only by the static data/variables you’re using, but also by the stack and by extension the local variables every time you call a function. Virtual Boy has 64 KB of RAM, which is quite a bit, but it’s not unlimited. Whenever it’s possible, lookup tables and other read-only data should not be stored in RAM.

Here’s an example of what to not do:

int someFunc(int arg) {
    int some_lookup[] = {
        1, 2, 3, 4, 5, 6, 7, 8, 9, 10
    };

    return some_lookup[arg];
}

If you do that, some_lookup, and all of its data, will wind up on the stack every time you call the function. This wastes memory on the one hand, and execution time on the other because it has to initialize the data every time the function runs.

You can free up the execution time by initializing only once, and using static memory. This is done with the static keyword:

int someFunc(int arg) {
    static int some_lookup[] = {
        1, 2, 3, 4, 5, 6, 7, 8, 9, 10
    };

    return some_lookup[arg];
}

While this is more efficient speed-wise, it still uses memory that doesn’t need to be used. After all, the very point of a lookup table is that it doesn’t need to change, so why store it in RAM at all? We can put it in ROM instead, which frees up memory AND saves execution time by bypassing the need to initialize it. This is done with the “const” keyword:

int someFunc(int arg) {
    static const int some_lookup[] = {
        1, 2, 3, 4, 5, 6, 7, 8, 9, 10
    };

    return some_lookup[arg];
}

Keep in mind, though, that ROM accesses are slower than RAM accesses. If you have a small lookup table that is used frequently (like in a custom sine function), you may very well want to ditch the const keyword and treat it as a static variable in RAM. But if it’s a large lookup table, then you’ll generally want to use const because again, that 64 KB of RAM can only go so far.

Virtual Boy devkits initialize the stack pointer to 0x0500FFFC and count backwards for each push. If you don’t use any global variables, then you effectively have most of the 0x05000000 range to use however you wish through pointers (depending on how many functions/local variables you use). I highly recommend against doing this, but it’s your call.

Thanks Guy Perfect! That was a very helpful and covering post! You should see about putting it or parts of it into the wiki, especially aspects related to the linker fo sure. Quite a few things in there I didn’t know, such as static actually lowering the ‘scope’ of it!

I had a feeling the stack started around there, but I wasn’t sure and I’ve been having a little trouble getting accustomed to Mednafen’s memory viewer to try and peak around for it…

I noticed too you also stayed away from the SRAM. The manual mentions it can go up to 16 MBytes so I’ve been highly tempted to use it because of that. I doubt I would need all 16, but I am just a tiny bit worried about using only 32-48KB to save room for the stack. Though I might be worried for nothing, still my first real game on the VB so still getting a feel for things. Currently am using roughly 2,500 KB for all useable “objects” (not the built in type, max 64 as of now), and “object” management system so it’s made me slightly nervous. Still need a management system for the actual built-in objects too so that’ll be another major one.

PS
Thanks for that speed test share! πŸ˜›

It has to be noted that cartridge RAM accesses are as fast as WRAM accesses: 1 wait state.
ROM and EXT areas can be configured to 1 wait state from their default 2 wait state configuration.

So ROM accesses needn’t be slower than RAM accesses at all.

Of course, for a physical cartridge, you would have to have fast memory chips as well πŸ˜‰

cYa,

Tauwasser

HollowedEmpire wrote:
I noticed too you also stayed away from the SRAM. The manual mentions it can go up to 16 MBytes so I’ve been highly tempted to use it because of that.

When it says it can go up to 16MB, it just means that there’s 16MB of addressable space, not that the RAM is actually there. If you write to the space and the RAM isn’t there, nothing will happen… and if you try to read it, you’ll just read garbage. The only carts that have any RAM on them are the ones with 8KB of battery backed RAM for save data.

If you really need more RAM, there’s an extra 12KB scattered across the framebuffers in VRAM. The screen is only 56 bytes per column (224 pixels), but the memory is set up that each column is 64 bytes… so each column in each framebuffer has 8 unused bytes. That gives you 384 columns x 8 bytes x 4 framebuffers = 12KB. It’s certainly not very convenient, and VRAM is a bit slower, but it’s free.

DogP

@Tauwasser
Oh yes! I saw that from the speed test. That comparable speed coupled with the potential addressable space address is what turned me on to it in the first place. But now that I have a better understanding of the SRAM area, I’ll likely be simply using it for save data like a normal VB programmer. πŸ˜›


@DogP

Mmm, gotcha. I wasn’t sure if there were larger carts then that or not. Very interesting they opened up that possibility for future expansion.

Thanks for reminding me of the extra space in the VRAM. noticed that before too but forgot about it. I actually might be just fine with using the WRAM as is, but I’ll certainly keep it I mind if I need to store something extra that wouldn’t matter having a bit slower access times.

Thanks for all the information everyone! Definitely helped me out a lot! I’ll keep plugging away at my game in the background and eventually make a post about it once I have a little more playability involved. Armed with this new info, it shouldn’t be much longer till then… though might have to miss this coding competition sadly. πŸ™

HollowedEmpire wrote:

I had a feeling the stack started around there, but I wasn’t sure and I’ve been having a little trouble getting accustomed to Mednafen’s memory viewer to try and peak around for it…

VUCC and gcc both use register r3 as the stack pointer. That’s a formally-defined use in the V810 architecture. r1 is for immediate data composition, r2 is the exception handler stack pointer (not used in VB), r3 is the program stack pointer, r4 is the data pointer (set to 0x05008000 on VB so the entire 64 KB are accessible with a single read/write instruction), and r5 is the text pointer (not used on VB).

HollowedEmpire wrote:

I noticed too you also stayed away from the SRAM. The manual mentions it can go up to 16 MBytes so I’ve been highly tempted to use it because of that.

The cartridge bus supports addresses 24 bits in size, meaning cartridge RAM (SRAM) and ROM can both be up to 16 MB in size. The largest SRAM chip used in commercial games was 8 KB, which is also the size of the chip in the FlashBoy Plus. Likewise, the largest ROM chip in commercial games and on FlashBoys is 2 MB. When designing your software, aim for those target sizes to ensure people will be able to run your programs on the hardware.

DogP wrote:

When it says it can go up to 16MB, it just means that there’s 16MB of addressable space, not that the RAM is actually there. If you write to the space and the RAM isn’t there, nothing will happen… and if you try to read it, you’ll just read garbage.

Close. I had dasi test this for me before I had a FlashBoy of my own, though it was on a FlashBoy and not a commercial cartridge. What happened was the upper bits of the address were simply masked out, causing the 8 KB of SRAM to be mirrored across the entire address range.

Guy Perfect wrote:

DogP wrote:

When it says it can go up to 16MB, it just means that there’s 16MB of addressable space, not that the RAM is actually there. If you write to the space and the RAM isn’t there, nothing will happen… and if you try to read it, you’ll just read garbage.

Close. I had dasi test this for me before I had a FlashBoy of my own, though it was on a FlashBoy and not a commercial cartridge. What happened was the upper bits of the address were simply masked out, causing the 8 KB of SRAM to be mirrored across the entire address range.

Right, of course… on a cart with RAM (because there’s nothing decoding the address/chip select… just like the ROM). But on a cart with no RAM, you’ll write nothing and read garbage.

DogP

All this stuff is over my head πŸ™‚ BUT

don’t limit the size of your game based off of the flashboy memory. Richard has been working on a new board for reproductions that will hold larger games. I think we wanted to be prepared πŸ™‚

-Eric

 

Write a reply

You must be logged in to reply to this topic.