We're using cookies to ensure you get the best experience on our website. More info
Understood
@guyperfectRegistered December 4, 2012Active 5 months, 3 weeks ago
375 Replies made

Except Arial is sans-serif. (-:

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.

Custom rendering hardware in the cartridge would absolutely make it possible. The affine window can be replaced entirely by an ordinary character map, provided the characters were prepared in the cartridge ahead of time. It’d require the Y-match VIP interrupt to work (which Mednafen doesn’t implement), but it would work.

SirGuntz wrote:

How about only the track is rendered, while everything else is black?

It’d be hard to see where you’re going. Drawing *anything* over the affine window spikes the rendering time.

SirGuntz wrote:

Simpler textures?

That’s not how the affine thing works. (-:

SirGuntz wrote:

Is the affine window the only easy way to get the timer/boost indicator/minimap/etc to work?

The affine window is responsible for displaying just the track.

SirGuntz wrote:

Does the game take on even more slowdown the more you add stuff to the affine window?

Presumably. I haven’t tried it because of the slowdown that already occurred.

SirGuntz wrote:

That said, I wouldn’t complain too much about a 25fps F-Zero.

Unfortunately, that doesn’t meet the project requirements. I’m not willing to do a 25fps F-Zero.

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.

DogP wrote:
I’ve also done the same thing with COMCNT, where you leave it high until someone presses start, and then pull it low to signal game start and who’s master.

Could you describe how to do this without using the phrase “source code”? (-:

Benjamin Stevens wrote:
Thus, it makes sense for a person who is willing to part with this special CIB game to let it go to someone who is willing to pay more than just about anyone else at the moment, which is about the only fair way to handle it.

I find it more likely that they don’t care about the game and are just turning around for a quick profit.

Could just manufacture our own. Just sayin’. It’s a piece of plastic with some holes in it and wires coming out of it.

Snatcher was a good title; I’m glad you decided to remake it. And to have it formally in English would be a big bonus. (-:

jrronimo wrote:
That said, I wouldn’t mind playing without a minimap… 🙂

I hope you also wouldn’t mind playing without a boost indicator, timer, lap number, rank indicator, or any presence of your machine or other racers! (-:

thunderstruck wrote:

libgccvb and the tech scroll didn’t help me allot with this. I couldn’t find any demo (except for the affine tilt one) showing how this is supposed to work. I understand the basic principle of how the affine mode is supposed to work. I’m just having trouble getting around the math part.

Though the affine window can be used for a perspective effect, the specifics on how to use the affine window is a different matter entirely and depends wholly on how you code up your program. Scaling is the simplest transformation, though rotation isn’t too hard to grasp either. What you’re looking for is a form of projection, and you can’t just toss a couple of parameters to a function and make that work.

It doesn’t help that there are multiple ways to approach the problem. The way I did the perspective is just one way; it may not be the most flexible nor the fastest, but it’s flexible and fast enough for what I needed it to do.

I’m sure there are ways to make it work, but one of the goals of the project was to showcase libvue as a devkitV810 project. To get F-Zero the way I want it to be, it’ll have a lot of assembly optimizations and CPU rendering, which doesn’t make for a very good sample project since it’s going to be A) over the heads of most people who look at the source and B) fairly inflexible since it’s made for F-Zero and only F-Zero.

Though if anything, I’d rather offload the perspective viewport to the CPU for rendering and do all the 2D stuff with the VIP. VB’s affine feature is a joke not just in its poor processing capability, but the parallax setting is severely limited. If it can be done on the CPU, I’ll get the control I need to make it look the way I want.

thunderstruck wrote:
Would you mind sharing the affine code/settings for the racing track?

That’s akin to asking to see “the 3D code” for Faceball. (-: It’s not like it’s five lines of text that I can just paste into a forum post and call it good. There’s quite a bit of concept behind it, and the implementation isn’t trivial.

It’s something that’s better off for an article about perspective effects using affine windows.

Lester Knight wrote:
It is awesome that you are going to stick with it and try to get everything working.

Wut? No. This is the exact opposite of what is going to happen. Sorry. (-:

It’s a classic case of Too Broke to Fix. The Virtual Boy’s affine functionality is just far too wimpy to pull off a game that relies on it like this.

Does that mean the system won’t explode if you remove the cartridge while it’s running (provided it’s not trying to execute from ROM space)? What happens if you attempt to read a ROM address while no cartridge is inserted?

On a mobile device you’re really gonna need some kind of recompiler if you want emulated games to run full-speed. It does by definition come at a cost to accuracy (for instance, the emulator won’t slow down like the real system would when there’s a lot of stuff going on), but it’s typically the preferred way to do things.

DogP wrote:
IIRC, DanB (maybe?) had tried copying memory with bitstring functions, but I don’t think it was any faster.

dasi tried them as well with a memmove() implementation and found them to actually be slower than the optimized LD.W/ST.W loop (presumably with the instruction cache enabled). It’s a novel idea, but the bit string instructions are both hard to use and not efficient, so I don’t see a point in using them.

DogP wrote:
Just enabling [the instruction cache] at boot and leaving it on isn’t likely to be very beneficial (cache contents are saved across enable/disable, so just enabling it for loops where performance matters is ideal).

You can also manually clear the cache. I recommend doing that whenever switching to a different block of code.

*Enable cache
*Loop 1
*Disable cache

(other code)

*Clear cache
*Enable cache
*Loop 2
*Disable cache

DogP wrote:
[…] 32-bit transfers are less beneficial, since it still causes two bus transfers (though it’s handled automatically, so it’s two bus transfers, but only one 32-bit transfer instruction, rather than two 16-bit transfer instructions).

I’m not clear on the details of how it works, frankly. I know the VIP registers *must* be accessed with 16-bit read/write instructions, but VIP memory can be accessed with 8- or 32-bit instructions. Similarly, the VSU and hardware control registers *must* be accessed with 8-bit instructions.

I’m not sure why the distinction matters, but to me it seems like a 32-bit write is semantically different from two 16-bit writes. And even in the event they’re the same, it still saves execution on a per-instruction basis, so I really don’t see any down-sides to using them.

You’re absolutely right about that one, HorvatM. I thought about the instruction cache after I opened this thread, but I had to go to work so didn’t get to experiment with it. I’m home now, though, and this gives me an opportunity to add some cache control functions to libvue. (-:

And my results? Holy. Freaking. Crap.

The V810 has 1KB of instruction cache. The instruction cache is designed specifically for the purpose of executing small chunks of code very quickly, such as those used by loading routines. The “big kahuna” I mentioned, where the loading routine is itself loaded into RAM first? Well, the CPU instruction cache is a sort of “bigger kahuna”, because it bypasses the RAM part and stores the loading routine right there on the processor chip.

After enabling the instruction cache around my level-loading code, I was able to achieve some remarkable scrolling speeds, in the neighborhood of 10-12 KB/s transferred from ROM to VIP memory before it started to slow down. This of course is in conjunction with the one-wait wait controller. Leaving the wait controller at its default of two waits, it was expectedly a little less than that, maybe in the 6-8 KB/s range. Don’t take these numbers as cold hard facts, though. We’d need a proper profiling test before drawing any conclusions about transfer limits.

So yeah, HorvatM hit the nail on the head. The instruction cache is, hands down, the best way to accelerate your code when you need to load a lot of stuff from ROM.

Don’t stop there; do your own homebrew too!