Table of Contents

Memory management

The WonderSwan poses unique challenges for implementing a robust memory management system:

Addressing RAM and ROM

In a C environment, by default, near pointers are utilized - they're smaller and faster to use, but they can only point to the program's default data segment - for all wswan targets, this is the console's internal RAM (IRAM).

To point to data in ROM, a far pointer is required. These are explicitly declared by using the __far modifier:

// The variable below will be stored in IRAM, even if it's declared const.
static const uint16_t table_iram[] = {0, 1, 2, 3};

// The variable below will now be stored in ROM instead of IRAM.
static const uint16_t __far table[] = {0, 1, 2, 3};

void process_table_iram(uint16_t *tbl); // This function can accept pointers to IRAM only.
void process_table(uint16_t __far *tbl); // This function can accept pointers to both IRAM and ROM.

However, certain subtargets model things differently:

For this reason, Wonderful provides helper modifiers which abstract away the differences; these are useful for developing libraries usable across all subtargets:

Placing in specific memory locations

By default, as discussed above, the following types of code and data will be placed into the following locations by the linker:

However, on the WonderSwan, especially the Color model, there are many restrictions as to where or with what alignment certain types of data (screen tables, tiles, audio wavetables) may be placed. For this reason, Wonderful's linking scheme has been extended to allow a special section name format. For example:

__attribute__((section(".iramx_2bpp_2000.tiles")))
ws_tile_t tile_2bpp[512];

The above block of code will allocate 512 tiles worth of memory in:

* internal console RAM (.iram),

The following prefixes exist:

Prefix Optional arguments Description
.iram[x] offset Mono internal RAM (0x0000 - 0x3FFF) [zeroed on startup?]
.iramc[x] offset Mono/Color internal RAM (0x0000 - 0xFFFF) [zeroed on startup?]
.iramC[x] offset Color internal RAM (0x4000 - 0xFFFF) [zeroed on startup?]
.iram[…]_screen offset Screen data (aligned to 0x800 bytes)
.iram[…]_sprite offset Sprite table (aligned to 0x200 bytes)
.iram[…]_2bpp offset 2bpp tile data (aligned to one tile)
.iram[…]_4bpp offset 4bpp tile data (aligned to one tile)
.iram[…]_wave offset Audio wavetable data (aligned to 0x40 bytes)
.iram[…]_palette offset Color palette data (aligned to 0x20 bytes)
.rom0 bank index, offset ROM bank 0
.rom1 bank index, offset ROM bank 1
.romL bank index, offset ROM linear bank

with the following optional arguments:

Note that the linker requires distinct section names to place things separately: in other banks, memory locations, or for garbage collection. One can use . after the prefix to specify an unique name.

Controlling placement in wf-process

wf-process supports an easy syntax for moving data to a specific ROM bank, index, or offset:

process.emit_symbol("gfx_title_screen", tilemap_color, {
    ["bank"] = 0, -- optional; or 1, or "L"
    ["bank_index"] = "F", -- optional; using a string is preferred over a number
    ["offset"] = 0x1000 -- optional.
})

This approach also ensures that sections are granted unique names by default.

Accessing data across ROM banks

A given 20-bit linear address to memory contains the bank type information (highest 4 bits) and the offset (lowest 16 bits), but it does not contain the bank index itself. For this purpose, the Wonderful linker was extended with support for additional symbols;

__attribute__((section(".rom0.gfx_font")))
const uint8_t __wf_rom gfx_font[1024] = { ... }; // array "gfx_font"

extern const void *__bank_gfx_font; // address = bank for "gfx_font"
</code C>

To access data in a separate bank, the following method is recommended:

<code C>
void load_font(void) {
    // Change bank, saving the previous bank value.
    // Assumes "gfx_font" exists at ROM bank 0
    ws_bank_t previous_bank = ws_bank_rom0_save(&__bank_gfx_font);

    // Load font data from ROM to IRAM.
    memcpy(MEM_TILE(0), gfx_font, sizeof(gfx_font));

    // Restore the previous bank value.
    ws_bank_rom0_restore(previous_bank);
}

Observe the save/restore pattern. As the parent function may already be using ROM bank 0 for something else, it's a good habit to ensure at function exit that the ROM bank remains unchanged from what it was at function entry:

bool load_graphics(void) {
    ws_bank_rom0_set(__bank_gfx_title_screen);

    // This function changes ROM bank 0!
    load_font();

    // If load_font() did not restore the previous bank index.
    // the code below will copy unexpected data.
    memcpy(screen_1, gfx_title_screen, sizeof(gfx_title_screen));

    // load_graphics() did not restore the previous bank index either.
    // If the function calling load_graphics() relies on said index,
    // it too will be handling unexpected data.
    return true;
}

Note that ws_bank_rom0_* can accept both numeric values and const void* pointers; in the latter case, the address of the pointer will be used.

In addition, when using wf_process, a `gfx_font_bank` define is provided for convenience - it equals `&__bank_gfx_font`.