User Tools

Site Tools


wswan:guide:memory_management

Memory management

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

  • The unified memory architecture places display and audio data in shared RAM with the CPU, however its restrictions lead to harsh alignment requirements, and thus the need for non-contiguous memory allocation in RAM.
  • The combined segmentation and banking benefits from a ROM layouting scheme which takes it into account.

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:

  • On wswan/bootfriend, all of the program's code and data is stored in RAM.
  • On wwitch, the default data segment actually points to SRAM - on this platform, IRAM only contains the stack.

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

  • __wf_rom - points to read-only program code/data on every subtarget;
  • __wf_iram - points to IRAM on every subtarget.

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:

  • Code (.fartext section) and read-only data marked __wf_rom or __far (.farrodata section) - the last 768 KiB “linear” bank of ROM,
  • Data without such a modifier (.rodata, .data sections) - console IRAM.

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),

  • without zeroing the content on startup (x),
  • with constraints expected of 2BPP tile data - the memory area 0x2000 - 0x5FFF, aligned to a multiple of 16 bytes (2bpp),
  • at location 0x2000 in memory (2000).

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:

  • bank index - the index of a bank, padded to the top of ROM.
  • offset - the absolute offset within a bank or memory location.

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`.

wswan/guide/memory_management.txt · Last modified: 2024/02/18 19:22 by asie