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