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.
Layouting
By default, the following types of code and data will be placed into the following locations:
- Code (
.fartext
section) and read-only data marked__wf_rom
(.farrodata
section) - the last 768 KiB "linear" bank of ROM, - Data not marked
__wf_rom
(.rodata
,.data
section) - console RAM.
To change this, one can use the C section
attribute combined with Wonderful's extensions to section naming. For example:
This 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) |
.iramc[x] |
offset | Mono/Color internal RAM (0x0000 - 0xFFFF) |
.iramC[x] |
offset | Color internal RAM (0x4000 - 0xFFFF) |
.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.
It is good practice to ensure every section name is unique; one can use .
after the prefix to specify further naming information.
However, variables in a section with the same name are guaranteed to end up in the same bank, which can be useful.
Layouting 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 grants sections unique names by default.
Working with 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, a pseudo-symbol is defined:
__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"
To access data in a separate bank, the following method is recommended:
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);
}
Note
ws_bank_rom0_*
can accept both numeric values and const void*
pointers; in the latter case, the address of the pointer will be used.
Tip
When using wf_process
, a gfx_font_bank
define is added for convenience - it equals &__bank_gfx_font
.
The save/restore pattern ensures that if you're using ROM bank 0
in a parent function, or outside of an interrupt handler, the value is not changed:
bool load_graphics(void) {
ws_bank_rom0_set(__bank_gfx_title_screen);
load_font();
// If load_font() did not restore the previous bank value,
// the code below would copy unexpected data.
memcpy(screen_1, gfx_title_screen, sizeof(gfx_title_screen));
// Likewise, if the caller of load_graphics() expects the bank value
// to be preserved, they will be in for a moderately annoying surprise.
return true;
}