====== 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 [[https://ws.nesdev.org/wiki/Memory_map#Internal|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"
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);
}
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`.