“wswan” target (WonderSwan/WonderWitch) #

Installation #

wf-pacman -S target-wswan

Examples and project templates are available in the wf-wswan-examples repository.

Documentation #

  • The Swan Book - work-in-progress guide to developing homebrew for the WonderSwan with Wonderful

Library references #

  • libws - hardware access library
  • libwsx - additional utilities library
  • libww - FreyaBIOS/FreyaOS wrapper library for WonderWitch

Wonderful-maintained tools #

  • BootFriend - custom console firmware allowing fastboot and running code from WSC/SC internal RAM without a flash cartridge,
  • CartFriend - custom flash cartridge firmware with a homebrew-centric design,
  • ExtFriend - DIY serial/headphone capture USB device, based on the RP2040/Raspberry Pi Pico.

Toolchain overview #

Most of the text below is in the process of being migrated to The Swan Book.

Memory models #

8086-class CPUs feature segmentation - a practice of using segment registers (CS - Code Segment, DS - Data Segment, SS - Stack Segment, ES) to extend the address space from 16 bits to 20 bits. This means that we can distinguish two class of pointers:

  • near pointers - containing only a 16-bit offset; these allow accessing up to 64KB of data;
  • far pointers - containing a 16-bit segment and a 16-bit offset; these allow accessing the full 1MB of address space.

The Wonderful toolchain currently offers two memory models:

  • small memory model - one segment (up to 64KB) of code (near code pointers),
  • medium memory model - many segments (more than 64KB) of code (far code pointers).

In both cases, data is stored using near pointers by default, and thus points to RAM. This means that data stored in ROM must be explicitly declared as far - see Caveats for an explanation on how to achieve this.

Caveats #

C compiler #

While gcc-ia16 offers competitive code generation and optimization for the architecture, it is a little hackier than dedicated 8086 C compiler solutions.

  • In the “small” and “medium” memory models, data is passed around using near pointers, which are limited to RAM. This means that near read-only data will end up being copied from ROM to RAM, using up limited heap memory, unless they are declared as far.

Note, though, that far read-only data is not supported on the wwitch target. As such, Wonderful provides a __wf_rom define that ensures correct behaviour on both targets.

Here’s an example:

const int8_t neighbor_delta_x[4] = {0, 0, -1, 1}; // stored in RAM
const int8_t __wf_rom neighbor_delta_y[4] = {-1, 1, 0, 0}; // stored in ROM
// __wf_rom is defined to __far on wswan, and nothing on wwitch

// pointer address space modifiers affect functions as well
void write_text(uint8_t x, uint8_t y, const char *text); // only accepts near data pointers (IRAM on wswan, SRAM on wwitch)
void write_text(uint8_t x, uint8_t y, const char __seg_ss *text); // wwitch only: accepts near stack pointers (IRAM)
void write_text(uint8_t x, uint8_t y, const char __wf_iram *text); // only accepts near IRAM pointers
void write_text(uint8_t x, uint8_t y, const char __far *text); // accepts all pointers (ROM, IRAM, SRAM)
  • In some cases, when calling pointers from arrays of far function pointers in optimization modes >= -O1, the code will be miscompiled. This is a known issue, with no ETA for a fix. One can work around this by annotating the affected function to be compiled without optimizations:
__attribute__((optimize("-O0"))) //
static void call_from_my_function_table(uint8_t index) {
  • Counter-intuitively, using -fno-function-sections in the “medium” memory model can generate smaller and faster code:
    • In -ffunction-sections mode, each function is put in its own segment (16-byte alignment), necessiating the use of far calls (10 CPU cycles). In -fno-function-sections mode, each compilation unit is put in its own segment, allowing for more tightly packed code. While all functions are still using “far” calls, an optimization can be made reducing the cost of calls within the same compilation unit to 7 CPU cycles.
  • ELF debug symbols are currently not supported, but will be eventually - see gcc-ia16/#127.

Assembler #

WonderWitch #

The WonderWitch is an official homebrew development environment provided by Qute Corporation. Because of the awkward licensing terms and legacy nature of the original software, a decision has been made to try and implement the target from scratch, using gcc-ia16’s modern compiler like in the WonderSwan target. Unfortunately, this poses some challenges:

  • While most of the WonderWitch’s programming interface is abstracted away via FreyaBIOS (WonderSwan-side system software) interrupt calls, some (such as WonderSwan Color routines, dynamic libraries, and file access) are not.
  • Under the WonderWitch environment, the data segment points to SRAM (segment 0x1000), while the stack segment points to console RAM (segment 0x0000). DS != SS is a somewhat less common thorn in 16-bit 8086 C development and, as such, has limited support. Importantly, unlike many of the compilers that came before it, gcc-ia16 is capable of emitting errors when a near stack-originating pointer is mistakenly used as a near data-originating pointer, and vice versa.

An important advantage of libww (Wonderful’s reimplementation of the WonderWitch libraries) is that it is capable of inlining ASM calls to such FreyaBIOS interrupts, saving the cost of call and ret instructions on them as a result, as well as allowing the compiler to allocate and reorder register usage accordingly.

The current status of the WonderWitch target is experimental. It is capable of compiling some non-trivial programs (such as WWTerm), but not all components of the original libraries are appropriately supported, and miscompilations/compiler ICEs may occasionally happen.