Skip to content

Latest commit

 

History

History
375 lines (313 loc) · 18.1 KB

File metadata and controls

375 lines (313 loc) · 18.1 KB

PRG32 ABI

PRG32 portable cartridges call framework functions through a stable, versioned ABI table supplied by the resident firmware. The table is generated from prg32/abi/prg32_abi.json; generated files contain the function indices, ABI hash, and firmware table population code.

The builder now produces only portable ABI-table cartridges. It rejects --firmware-elf, --runtime-url, and --legacy-absolute-imports for cartridge builds. Existing firmware-specific absolute-import packages remain loadable when they match the resident firmware, but developers must rebuild them as portable cartridges to distribute them independently of a firmware image.

The public declarations in components/prg32/include/prg32.h include the cartridge-facing contracts for coordinates, lifetimes, return values, and data layouts. Documentation-only changes to that header do not change ABI indices, hashes, structure layouts, constants, or generated call stubs. Run python3 -m prg32 abi check after editing it; previously built portable cartridges must continue to validate and load without recompilation.

Portable C builds use the medium-any code model and disable compiler-generated switch tables. This keeps normal code, literal references, and explicit switch dispatch position-relative when QEMU and hardware expose different executable buffer addresses. Cartridge sources should not store code or string addresses in initialized writable-data pointer tables; use explicit dispatch until the reserved relocation fields in the package format are activated.

Register Convention

PRG32 follows the standard RISC-V calling convention:

Register Purpose
a0-a7 arguments and return values
ra return address
sp 16-byte aligned stack
t0-t6 caller-saved temporaries
s0-s11 callee-saved values

Assembly examples save ra around C calls and keep stack alignment visible. For portable cartridges, a0 contains a pointer to prg32_abi_table_t when the runtime enters init, update, or draw. The cartridge-side stubs emitted by python3 -m prg32 --portable store that pointer in __prg32_abi and keep the familiar call prg32_gfx_clear style available to examples.

Stable ABI Table

The firmware exposes one prg32_abi_table with magic PABI, ABI major/minor, the generated ABI hash, feature bits, and an indexed function pointer array. Cartridges declare the ABI hash and required features in the v2 cartridge header.

Compatibility rules:

  • same ABI major and current hash, or a listed compatible historical hash: accepted when required feature bits are available
  • missing required feature bits: rejected
  • newer incompatible major: rejected
  • legacy absolute imports: existing matching packages remain loadable; new builds are rejected

Feature bits currently cover audio, Wi-Fi, multiplayer, metrics, audio-plus, keyboard, tilemap, platformer, and sprites.

Cartridge Package ABI

The executable cartridge ABI remains PRG2 major 1, minor 1. Header v2 extends the original header via header_size with abi_hash, required_features, optional_features, relocation placeholders, and import_model. import_model=abi-table marks a portable cartridge; import_model=legacy-absolute marks the older firmware-specific path.

ABI minor 1 adds prg32_sprite_draw_24x24 as an append-only sprite helper. ABI minor 3 appends prg32_sprite_draw_indexed and prg32_sprite_draw_bitplanes. The indexed sprite entries remain available, but cartridges built with older hashes must be rebuilt for this combined table.

ABI minor 4 appends indices 124 through 132 for the pluggable performance broker: prg32_perf_now_us, prg32_perf_begin, prg32_perf_case_begin, prg32_perf_record, prg32_perf_case_end, prg32_perf_end, prg32_perf_abort, prg32_perf_get_state, and prg32_perf_get_summary. The exact 1.4 hash is generated from prg32_abi.json.

ABI minor 5 appends indices 133 through 137 for the indexed framebuffer: prg32_palette_set, prg32_palette_get, prg32_gfx_pixel_indexed, prg32_gfx_rect_indexed, and prg32_gfx_clear_indexed. The five additions follow the existing graphics and metrics entries and retain RGB565 signatures. Palette changes affect existing indexed pixels at the next presentation; the ILI9341 wire format remains RGB565. The exact 1.5 hash is generated from prg32_abi.json. The merged ABI 1.5 table retains these five indexed entries after the PR #36 audio changes. The generated hash identifies that combined table. Earlier 1.3 and 1.4 hashes are rejected because the PR #36 audio changes reused early slots and are not append-only.

ABI minor 6 appends index 138, prg32_random_number(uint32_t min, uint32_t max). It returns a uniform unsigned value in the inclusive range; when max <= min, it returns min. The full 0 to UINT32_MAX range is supported. Previously built portable cartridges with hashes 0x006427c2 (133 entries) and 0x6be6e8d0 (138 entries) remain loadable because those tables are unchanged prefixes of this table. This applies to firmware loading, Store downloads, and upload/QEMU tooling. The earlier pre-audio-change hashes still require a rebuild because their table entries differ. Existing legacy cartridges with firmware-specific absolute imports are usable only when their addresses match the resident firmware; rebuild their source as portable cartridges before distributing them for other firmware images.

The performance broker lifecycle, descriptor layouts, failure semantics, and custom-cartridge tutorial are documented in the Performance Test Guide. The concise contract is also available in Pluggable Performance Cartridge ABI.

Compact Sprite ABI Calls

Symbol Asset layout
prg32_sprite_draw_indexed packed palette indices, most-significant pixel first in each byte
prg32_sprite_draw_bitplanes plane-major bitmaps, least-significant value plane first

Both calls receive x, y, a pointer to prg32_indexed_sprite_t, and a frame index. The descriptor points to pixel bytes and an RGB565 palette and records width, height, frame count, palette count, bits per pixel, and a signed transparent index. Supported depths are 1, 2, 4, and 8 bits per pixel. -1 makes every palette entry opaque; 8-bit assets may select any transparent index through 255. Each frame starts on its own byte boundary.

Four-bit packed assets contain two pixels per byte, high nibble first, and at most 16 shared palette entries. Eight-bit assets contain one pixel per byte and at most 256 shared entries. One descriptor palette is shared across every animation frame.

Packed frames are frame-major and each frame begins on a byte boundary. Bitplanes are also frame-major; within a frame the least-significant index plane comes first. Each plane contains rows in top-to-bottom order. Every row is independently byte-aligned, pixel 0 occupies bit 7, and unused low bits in the last byte are zero. This permits the renderer to load one byte from each plane and reconstruct up to eight adjacent pixels without arbitrary bit addressing.

Generated assets expose a pointer with bit zero tagged as compact (bit one selects planar layout). Because RGB565 arrays are naturally aligned, the tag is unambiguous and the signatures and layouts of prg32_anim_sprite_t and all existing sprite functions remain unchanged. Passing the generated tagged alias through prg32_sprite_draw_16x16, prg32_sprite_draw_24x24, prg32_sprite_draw_frame, or prg32_sprite_anim_init makes the existing draw and animation paths dispatch indexed data automatically. Untagged pointers use the original RGB565 path and its unchanged transparent-color comparison. For tagged calls through an existing RGB565 function, the reconstructed RGB565 palette value is compared with that function's transparent-color argument; the descriptor's transparent index is also honored. The additive direct compact calls use the descriptor index because their prototypes contain no color key.

These calls add asset encodings, not a new physical display ABI. On ESP32-C6 drawing targets the indexed game framebuffer and presentation expands it to RGB565; legacy RGB565 sprite signatures, screenshots, and the LCD protocol remain compatible.

The destination-row optimization is internal. ABI 1.5 appends palette APIs but does not change graphics structure layouts or indices. Cartridges built against the earlier indexed-only ABI 1.5 hash must be rebuilt for the combined table; RGB565 and indexed function signatures remain available. Firmware-specific legacy-absolute cartridges retain their existing limitation: they are compatible only with the firmware image whose exported addresses were used when they were linked.

Store-ready cartridges append a backward-compatible PRG32META trailer after the payload. The trailer gives host tools and setup-mode clients standard blocks for META, ICON, SCRN, SIGN, and COLO.

Please refer to the Colophon ABI for full documentation on cartridge metadata and colophon formats.

Audio ABI Calls

The audio ABI is the C API exposed to cartridges:

Symbol Purpose Return
prg32_audio_init initialize mono/stereo runtime bool
prg32_audio_shutdown stop audio runtime none
prg32_audio_get_mode return PRG32_AUDIO_MODE_MONO or STEREO mode
prg32_audio_play_sample play sample centered channel or negative
prg32_audio_play_sample_pan play sample with pan channel or negative
prg32_audio_stop_channel stop one voice none
prg32_audio_stop_all stop all voices none
prg32_audio_note play a note asynchronously on a channel none
prg32_audio_note_on start PCM or synth instrument note none
prg32_audio_note_on_pan start PCM or synth note with pan none
prg32_audio_note_off stop PCM or begin synth release none
prg32_audio_notes play a blocking sequence of notes on the I2S synth. If you need asynchronous audio, consider using tracks. none
prg32_audio_play_track start tracker stream none
prg32_audio_stop_track stop tracker stream none
prg32_audio_set_tempo set tracker BPM none
prg32_audio_set_master_volume set global volume none
prg32_audio_set_channel_volume set one voice volume none
prg32_audio_set_channel_pan set one voice pan none
prg32_audio_led_vu_enable allow audio helpers to drive the RGB LED VU meter none
prg32_audio_led_vu_enabled read the RGB LED VU meter flag int
prg32_audio_led_vu_level update the RGB LED VU level if enabled none

Pan uses signed values:

-64 full left, 0 center, +63 full right

Mono builds accept pan calls but mix to one output. Stereo-only programs should check prg32_audio_get_mode() before making a wiring assumption.

SID-like synthesis is ABI-neutral: bit 15 of the existing instrument sample_id selects a procedural instrument. No function index, prototype, descriptor layout, tracker event, or AUDIO block version changed. Portable cartridges built before synthesis therefore remain compatible, and ordinary sample IDs retain PCM semantics. The encoding is defined in the audio guide. The later synth ID decoder fix and delta-0 tracker timing fix change runtime behaviour only, not the ABI.

RGB LED ABI Calls

The onboard RGB LED API is optional because many classroom display harnesses use the same GPIO as the board LED. Check availability before depending on it.

Symbol Purpose Return
prg32_rgb_led_init initialize an addressable RGB LED on a GPIO 0 or negative
prg32_rgb_led_available report whether the LED is ready int
prg32_rgb_led_set set red, green, blue intensity none
prg32_rgb_led_off turn the LED off none
prg32_rgb_led_vu map a 0-255 level to spectrum color none

Error Values

Audio calls that return int use a non-negative channel number for success and a negative value for failure. Common failure causes:

  • audio runtime was not initialized
  • ordinary PCM sample id is missing
  • channel id is outside the configured voice table
  • AUDIO block is invalid

Assembly Example

    li a0, 0          /* sample id */
    li a1, 255        /* volume */
    li a2, 1024       /* natural pitch */
    call prg32_audio_play_sample

For stereo pan:

    li a0, 0
    li a1, 255
    li a2, 1024
    li a3, -64        /* left */
    call prg32_audio_play_sample_pan

Splash ABI Calls

Splash helpers are exported for cartridges and examples:

Symbol Purpose
prg32_splash_draw_game draw a 320x200 game title screen without delaying
prg32_splash_show_game draw a 320x200 game title screen, present, and wait
prg32_splash_draw draw a full 320x240 framework splash/title screen without delaying
prg32_splash_show draw a full 320x240 framework splash, present, and wait
prg32_splash_show_default show the built-in PRG32 startup splash
prg32_gfx_lock enter the recursive graphics critical section
prg32_gfx_unlock leave the recursive graphics critical section
prg32_gfx_set_fullscreen use 320x240 coordinates for framework/title screens
prg32_gfx_fullscreen_enabled return whether full-screen drawing is active
prg32_gfx_set_band_color set a custom top/bottom band color for games
prg32_gfx_use_background_bands make game bands follow prg32_gfx_clear again
prg32_gfx_snapshot_row_rgb565 copy a physical framebuffer row as RGB565
prg32_band_set_mode choose what status data a band renders
prg32_band_mode read the current mode for a band
prg32_band_set_text set custom band text
prg32_band_set_game_info set game status text
prg32_band_log set debug/status log text
prg32_band_set_colors set foreground/background colors for one band
prg32_band_use_default_colors make a band use the game background color again

prg32_splash_show_game and prg32_splash_show arguments:

Register Value
a0 title C string
a1 subtitle C string
a2 duration in milliseconds
a3 RGB565 background color
a4 RGB565 foreground color
a5 RGB565 accent color

Example:

    la a0, game_title
    la a1, game_subtitle
    li a2, 900
    li a3, 0x0000
    li a4, 0xffff
    li a5, 0x07ff
    call prg32_splash_show_game

Band identifiers:

Constant Value
PRG32_BAND_TOP 0
PRG32_BAND_BOTTOM 1

Band modes:

Constant Meaning
PRG32_BAND_MODE_NONE hide band text
PRG32_BAND_MODE_FPS show measured frame rate
PRG32_BAND_MODE_WIFI show current SSID and IP
PRG32_BAND_MODE_GAME show cartridge/game info
PRG32_BAND_MODE_DEBUG show the last debug message
PRG32_BAND_MODE_CUSTOM show text set with prg32_band_set_text

Input And Setup ABI Calls

Setup screens and cartridge programs use the same button bitmasks:

Symbol Purpose
prg32_input_read read the local player input bitmask
prg32_input_read_player read player 1 normalized to low bits; player 2 returns 0
prg32_input_read_menu read local joystick input for setup/menu navigation
prg32_input_wait_released wait until selected menu bits are released
prg32_wifi_current_mode return the active Wi-Fi mode enum
prg32_wifi_current_ip return the current IP display string
prg32_wifi_current_ssid return the current AP or infrastructure SSID
prg32_multiplayer_init initialize the multiplayer service
prg32_multiplayer_available return whether cartridge multiplayer can be used
prg32_multiplayer_join join peers with the same cartridge signature
prg32_multiplayer_leave leave the current multiplayer room
prg32_multiplayer_tick service periodic multiplayer sends and peer expiry
prg32_multiplayer_set_local_state publish local player position and sprite status
prg32_multiplayer_set_input publish local player input
prg32_multiplayer_get_peer_count return visible peer count
prg32_multiplayer_get_peer copy one peer snapshot
prg32_cart_default_slot return the saved default cartridge slot, or -1
prg32_cart_set_default_slot save a default cartridge slot, or clear with -1
prg32_cart_select_default load the saved default cartridge
prg32_score_player_get copy the current scoreboard player name
prg32_score_player_set set the current scoreboard player name
prg32_score_player_prompt show the on-screen player-name entry UI
prg32_score_submit_current_player submit a score for the current player
prg32_score_sync_remote retry pending local scores against the configured Cartridge Store
prg32_score_count count local scoreboard records, optionally by game
prg32_score_get copy one local scoreboard record
prg32_scoreboard_show show the built-in local scoreboard screen
prg32_perf_now_us return the monotonic microsecond timer used for benchmark intervals
prg32_perf_begin begin one cartridge-defined performance suite
prg32_perf_case_begin begin one named case and reserve its temporary observations
prg32_perf_record submit one update/draw/present timing observation
prg32_perf_case_end aggregate the active case and release its temporary array
prg32_perf_end finalize a suite and publish its compact result
prg32_perf_abort terminate a suite and release active temporary storage
prg32_perf_get_state copy lifecycle and heap checkpoints into a versioned structure
prg32_perf_get_summary copy the completed suite-wide aggregate
prg32_performance_test_run run the unattended multi-screen setup benchmark
prg32_performance_has_results return nonzero when onboard benchmark results are available
prg32_performance_summary copy the latest benchmark summary into a caller-provided struct

PRG32_BTN_SELECT is the classroom-facing name for the select button. PRG32_BTN_START remains an alias for existing code.