OpenSNES
Modern Open-Source SNES Development SDK
Loading...
Searching...
No Matches
superfx.h File Reference

SuperFX (GSU) coprocessor library — registers, config, and API. More...

#include <snes/types.h>

Go to the source code of this file.

Macros

#define CFGR_FAST_MUL   0x20
#define CFGR_IRQ_MASK   0x80
#define CLSR_10MHZ   0x00
#define CLSR_21MHZ   0x01
#define GSU_SRAM_BASE   0x700000
#define REG_BRAMR   (*(volatile u8*)0x3033)
#define REG_CBR   (*(volatile u16*)0x303E)
#define REG_CFGR   (*(volatile u8*)0x3037)
#define REG_CLSR   (*(volatile u8*)0x3039)
#define REG_GSU_R0   (*(volatile u16*)0x3000)
#define REG_GSU_R1   (*(volatile u16*)0x3002)
#define REG_GSU_R10   (*(volatile u16*)0x3014)
#define REG_GSU_R11   (*(volatile u16*)0x3016)
#define REG_GSU_R12   (*(volatile u16*)0x3018)
#define REG_GSU_R13   (*(volatile u16*)0x301A)
#define REG_GSU_R14   (*(volatile u16*)0x301C)
#define REG_GSU_R15   (*(volatile u16*)0x301E)
#define REG_GSU_R2   (*(volatile u16*)0x3004)
#define REG_GSU_R3   (*(volatile u16*)0x3006)
#define REG_GSU_R4   (*(volatile u16*)0x3008)
#define REG_GSU_R5   (*(volatile u16*)0x300A)
#define REG_GSU_R6   (*(volatile u16*)0x300C)
#define REG_GSU_R7   (*(volatile u16*)0x300E)
#define REG_GSU_R8   (*(volatile u16*)0x3010)
#define REG_GSU_R9   (*(volatile u16*)0x3012)
#define REG_PBR   (*(volatile u8*)0x3034)
#define REG_RAMBR   (*(volatile u8*)0x303C)
#define REG_ROMBR   (*(volatile u8*)0x3036)
#define REG_SCBR   (*(volatile u8*)0x3038)
#define REG_SCMR   (*(volatile u8*)0x303A)
#define REG_SFR   (*(volatile u16*)0x3030)
#define REG_SFR_L   (*(volatile u8*)0x3030)
#define REG_VCR   (*(volatile u8*)0x303B)
#define SCMR_2BPP   0x00
#define SCMR_4BPP   0x01
#define SCMR_8BPP   0x03
#define SCMR_H128   0x00
#define SCMR_H160   0x04
#define SCMR_H192   0x20
#define SCMR_RAN   0x08
#define SCMR_RON   0x10
#define SFR_GO   0x20

Functions

void gsuCall (u16 entry)
 Launch the program at one of its entry points and wait (WRAM-safe).
void gsuDmaFullFrame (void)
 One 16 KB DMA from Game Pak RAM to VRAM $0000, in the letterbox.
u8 gsuInit (void)
 Set the GSU default configuration; returns gsuIsPresent().
u8 gsuIsPresent (void)
 Is a GSU on this cartridge? (crt0 detects it; this reads the outcome).
void gsuLaunch (void)
 Launch GSU program and wait for completion (WRAM-safe).
void gsuSetProgram (const void *program)
 Tell the library where the GSU program lives.
void gsuSetupBitmapTilemap (u16 vramAddr)
 Setup column-major tilemap for SuperFX PLOT framebuffer.
void gsuSetupHdmaBlanking (u16 topBlank, u16 bottomBlank)
 Setup HDMA screen blanking for DMA bandwidth.
Running a GSU job while the game keeps running

gsuLaunch() parks the CPU in WRAM until the job ends, because a GSU that runs its program from ROM owns the Game Pak ROM (SCMR RON = 1) and the CPU cannot read it. A program that fits in the GSU's 512-byte code cache does not need the ROM: loaded there by the CPU and started from the cache, it keeps running with RON = 0, and the CPU goes on with its own code in ROM (Nintendo manual Book II 6.1.2 and 6.8.4; fullsnes, SCMR).

Constraints on such a program: at most 512 bytes; no CACHE and no LJMP (both empty the cache and refetch from ROM); no ROM data reads (GETB, ROM buffer). Game Pak RAM stays the GSU's while the job runs if gsu_scmr grants it (RAN): the CPU must not touch $70:xxxx until gsuWait().

gsuCacheLoad(gsu_program, gsu_program_end - gsu_program);
gsuStartCached(0); // returns at once
while (gsuBusy()) {
game_logic(); // the CPU runs from ROM meanwhile
}
gsuWait(); // gives Game Pak RAM back
const u8 gsu_program[]
void gsuWait(void)
Wait for the end of the job, then give the Game Pak back to the CPU.
u8 gsuBusy(void)
Is the GSU still running? (SFR GO bit).
void gsuStartCached(u16 pc)
Start the program loaded by gsuCacheLoad() and return at once.
void gsuCacheLoad(const void *code, u16 size)
Copy a GSU program into the GSU code cache ($3100-$32FF).

Or be told when it ends: clear CFGR_IRQ_MASK and watch gsu_stop_irqs.

gsu_cfgr = 0x00; // IRQ on STOP (irqEnable() done earlier)
u8 stops = gsu_stop_irqs;
while (gsu_stop_irqs == stops) {
game_logic();
}
unsigned char u8
8-bit unsigned integer (0 to 255)
Definition types.h:47
u8 gsu_cfgr
CFGR register value written by the launchers ($80 = IRQ mask). CFGR_FAST_MUL (bit 5,...
volatile u8 gsu_stop_irqs
GSU IRQs on STOP acknowledged so far (crt0, wraps at 256).
u8 gsuBusy (void)
 Is the GSU still running? (SFR GO bit).
void gsuCacheLoad (const void *code, u16 size)
 Copy a GSU program into the GSU code cache ($3100-$32FF).
void gsuStartCached (u16 pc)
 Start the program loaded by gsuCacheLoad() and return at once.
void gsuWait (void)
 Wait for the end of the job, then give the Game Pak back to the CPU.

Variables

u8 gsu_cfgr
 CFGR register value written by the launchers ($80 = IRQ mask). CFGR_FAST_MUL (bit 5, MS0) is cleared by gsuLaunch() and gsuStartCached() before the write: both run the GSU at 21 MHz, and "MS0 must be zero in 21MHz mode" (fullsnes, CFGR, 1adef8e33ff3c4e9; ares: products "may be invalid" with both set). A value of $A0 therefore reaches the register as $80 — this line said "$A0 = IRQ mask + fast multiply" until 2026-10-05. Code that starts the GSU at 10.7 MHz by itself may set MS0.
u8 gsu_dma_src_hi
 DMA source high byte ($00=buffer A at $70:0000, $40=buffer B at $70:4000).
u8 gsu_owns_cart
 1 while a GSU job owns the Game Pak ROM (crt0)
u16 gsu_prog_addr
 GSU program 16-bit address (set by gsuSetProgram).
u8 gsu_prog_bank
 GSU program bank byte (set by gsuSetProgram).
u8 gsu_scbr
 SCBR screen base ($00=buffer A, $10=buffer B).
u8 gsu_scmr
 SCMR register value ($18=RAN+RON, $19=4bpp+RAN+RON).
u8 gsu_scmr_live
 The value last written to SCMR (write-only), kept by the library.
volatile u8 gsu_stop_irqs
 GSU IRQs on STOP acknowledged so far (crt0, wraps at 256).
u8 superfx_status
 SuperFX status from crt0 init (VCR chip version, 0=not detected).

Presenting frames: double buffer, moved by the NMI

The GSU draws in one Game Pak RAM framebuffer while the NMI moves the previous one to VRAM, a piece per VBlank, and shows it only once all of it has landed. The NMI takes Game Pak RAM from the GSU for each piece (Nintendo manual Book II 5.3: the GSU waits on its next RAM access and resumes after); nothing polls, the CPU runs the game meanwhile.

How much lands per VBlank is the blank window: the VBlank alone moves about 4.7 KB (measured on luna), and a top letterbox from gsuSetupHdmaBlanking() (with a bottom band of at least one line) adds about 150 bytes per blanked line: a 16 KB frame lands in two VBlanks with a 40 + 40 letterbox, in one with 84 + 4. The NMI measures the window itself (V counter) at every frame.

gsu_scbr = 0x00; // buffer A at $70:0000
gsuPresentInit(0x0000, 0x2000, 0); // two 16 KB char blocks
while (1) {
game_logic();
gsuLaunch(); // draws in gsu_scbr's buffer
gsuPresent(); // queue it, draw the next in the other
}
#define SCMR_RON
Definition superfx.h:67
void gsuSetupBitmapTilemap(u16 vramAddr)
Setup column-major tilemap for SuperFX PLOT framebuffer.
void gsuPresent(void)
Queue the frame the last job drew; the next job draws in the other.
#define SCMR_RAN
Definition superfx.h:66
u8 gsuPresentInit(u16 vram_a, u16 vram_b, u8 flags)
Set up presentation for gsu_scmr's geometry.
u8 gsu_scbr
SCBR screen base ($00=buffer A, $10=buffer B).
#define SCMR_4BPP
Definition superfx.h:64
#define SCMR_H128
Definition superfx.h:68
u8 gsu_scmr
SCMR register value ($18=RAN+RON, $19=4bpp+RAN+RON).
void gsuLaunch(void)
Launch GSU program and wait for completion (WRAM-safe).

The NMI callback gets what is left of the VBlank after the transfer: it must not write VRAM while a frame is in flight.

#define GSU_PRESENT_ON_LAG_FRAMES   0x01
 gsuPresentInit flag: move frames on lag frames too
u16 gsuFrameBytes (void)
 Bytes of one framebuffer for gsu_scmr's height and depth.
void gsuPresent (void)
 Queue the frame the last job drew; the next job draws in the other.
u8 gsuPresentBusy (void)
 1 while a presented frame is still being moved to VRAM
u8 gsuPresentInit (u16 vram_a, u16 vram_b, u8 flags)
 Set up presentation for gsu_scmr's geometry.
void gsuPresentWait (void)
 Wait until the last presented frame is on screen.

Detailed Description

SuperFX (GSU) coprocessor library — registers, config, and API.

Provides a complete C API for SuperFX cartridges:

  • GSU detection and configuration
  • WRAM-safe GSU launch (mandatory — CPU can't read ROM during GSU execution)
  • DMA helpers for SRAM-to-VRAM framebuffer transfer
  • HDMA screen blanking (letterbox) that lengthens the VRAM transfer window
  • Double-buffered presentation moved by the NMI (gsuPresent)
  • Column-major tilemap setup for PLOT rendering

The GSU code itself is written in SuperFX assembly (.sfx files). The C API handles all SNES-side boilerplate.

See also
docs/tutorials/superfx.md

Macro Definition Documentation

◆ CFGR_FAST_MUL

#define CFGR_FAST_MUL   0x20

CFGR bit 5: high-speed multiplier

◆ CFGR_IRQ_MASK

#define CFGR_IRQ_MASK   0x80

CFGR bit 7: 1 = no IRQ on STOP (the gsuInit default)

◆ CLSR_10MHZ

#define CLSR_10MHZ   0x00

◆ CLSR_21MHZ

#define CLSR_21MHZ   0x01

◆ GSU_PRESENT_ON_LAG_FRAMES

#define GSU_PRESENT_ON_LAG_FRAMES   0x01

gsuPresentInit flag: move frames on lag frames too

By default the NMI moves a frame only while the main thread is parked — in WaitForVBlank, or waiting for a job — the rule the tilemap and OAM uploads follow, because a main thread in the middle of its own VRAM access would be corrupted. A game that never touches VRAM, VMAIN, VMADD, DMA channel 7 or the H/V counter latch outside the NMI while a frame is in flight can set this flag: the frame then moves at every VBlank, even one that falls in the game's logic.

◆ GSU_SRAM_BASE

#define GSU_SRAM_BASE   0x700000

◆ REG_BRAMR

#define REG_BRAMR   (*(volatile u8*)0x3033)

◆ REG_CBR

#define REG_CBR   (*(volatile u16*)0x303E)

◆ REG_CFGR

#define REG_CFGR   (*(volatile u8*)0x3037)

◆ REG_CLSR

#define REG_CLSR   (*(volatile u8*)0x3039)

◆ REG_GSU_R0

#define REG_GSU_R0   (*(volatile u16*)0x3000)

◆ REG_GSU_R1

#define REG_GSU_R1   (*(volatile u16*)0x3002)

◆ REG_GSU_R10

#define REG_GSU_R10   (*(volatile u16*)0x3014)

◆ REG_GSU_R11

#define REG_GSU_R11   (*(volatile u16*)0x3016)

◆ REG_GSU_R12

#define REG_GSU_R12   (*(volatile u16*)0x3018)

◆ REG_GSU_R13

#define REG_GSU_R13   (*(volatile u16*)0x301A)

◆ REG_GSU_R14

#define REG_GSU_R14   (*(volatile u16*)0x301C)

◆ REG_GSU_R15

#define REG_GSU_R15   (*(volatile u16*)0x301E)

◆ REG_GSU_R2

#define REG_GSU_R2   (*(volatile u16*)0x3004)

◆ REG_GSU_R3

#define REG_GSU_R3   (*(volatile u16*)0x3006)

◆ REG_GSU_R4

#define REG_GSU_R4   (*(volatile u16*)0x3008)

◆ REG_GSU_R5

#define REG_GSU_R5   (*(volatile u16*)0x300A)

◆ REG_GSU_R6

#define REG_GSU_R6   (*(volatile u16*)0x300C)

◆ REG_GSU_R7

#define REG_GSU_R7   (*(volatile u16*)0x300E)

◆ REG_GSU_R8

#define REG_GSU_R8   (*(volatile u16*)0x3010)

◆ REG_GSU_R9

#define REG_GSU_R9   (*(volatile u16*)0x3012)

◆ REG_PBR

#define REG_PBR   (*(volatile u8*)0x3034)

◆ REG_RAMBR

#define REG_RAMBR   (*(volatile u8*)0x303C)

◆ REG_ROMBR

#define REG_ROMBR   (*(volatile u8*)0x3036)

◆ REG_SCBR

#define REG_SCBR   (*(volatile u8*)0x3038)

◆ REG_SCMR

#define REG_SCMR   (*(volatile u8*)0x303A)

◆ REG_SFR

#define REG_SFR   (*(volatile u16*)0x3030)

◆ REG_SFR_L

#define REG_SFR_L   (*(volatile u8*)0x3030)

◆ REG_VCR

#define REG_VCR   (*(volatile u8*)0x303B)

◆ SCMR_2BPP

#define SCMR_2BPP   0x00

◆ SCMR_4BPP

#define SCMR_4BPP   0x01

◆ SCMR_8BPP

#define SCMR_8BPP   0x03

◆ SCMR_H128

#define SCMR_H128   0x00

◆ SCMR_H160

#define SCMR_H160   0x04

◆ SCMR_H192

#define SCMR_H192   0x20

◆ SCMR_RAN

#define SCMR_RAN   0x08

◆ SCMR_RON

#define SCMR_RON   0x10

◆ SFR_GO

#define SFR_GO   0x20

Function Documentation

◆ gsuBusy()

u8 gsuBusy ( void )

Is the GSU still running? (SFR GO bit).

Returns
1 while the job runs, 0 once it has executed STOP

◆ gsuCacheLoad()

void gsuCacheLoad ( const void * code,
u16 size )

Copy a GSU program into the GSU code cache ($3100-$32FF).

Parameters
codeThe assembled GSU binary (any ROM bank)
sizeIts size in bytes, 1 to 512

The GSU must be stopped. Stops it (GO = 0, which sets CBR to 0 and empties the cache), copies the code, and pads the last 16-byte cache line with NOP ($01) so every line it uses is written in full, as krom's cache injection test does. A size above 512 is cut to 512.

◆ gsuCall()

void gsuCall ( u16 entry)

Launch the program at one of its entry points and wait (WRAM-safe).

Parameters
entryOffset of the entry point in the program set by gsuSetProgram() — the build writes one per global label of the .sfx into <name>.sfx.h (gsu_job.sfx's mul_job becomes GSU_JOB_MUL_JOB)

gsuLaunch() starts at offset 0; this starts at entry, the rest is the same (interrupts, bus ownership, the wait from the RAM code window). Pass arguments in the GSU registers before the call — REG_GSU_R1 = x; — any of R0-R7 and R9-R13: the launcher itself writes R8 (buffer base, SCBR × 1024) and R15. The program must not cross a 32 KB ROM bank.

#include "gsu_job.sfx.h"
REG_GSU_R1 = 300;
gsuCall(GSU_JOB_MUL_JOB);
void gsuCall(u16 entry)
Launch the program at one of its entry points and wait (WRAM-safe).
#define REG_GSU_R2
Definition superfx.h:30
#define REG_GSU_R1
Definition superfx.h:29

◆ gsuDmaFullFrame()

void gsuDmaFullFrame ( void )
extern

One 16 KB DMA from Game Pak RAM to VRAM $0000, in the letterbox.

Waits for a line from which the whole frame lands before the display comes back — the bottom band's first line (225 - bottom) up to 152 + top — and starts the DMA there; called later than that window, it waits for the next frame's. Needs gsuSetupHdmaBlanking() bands with top + bottom >= 73 (40 + 40: lines 185-192). The CPU waits the whole time; gsuPresent() is the variant that does not.

With the screen off (setScreenOff()) there is nothing to wait for and the DMA starts at once, bands or not: that is the way to load a first frame before gsuSetupHdmaBlanking(). (Until 2026-10-08 such a call waited for line 225 exactly, the line the NMI starts on, and returned after a number of frames that depended on the caller's timing.)

Until 2026-09-29 it read OPVCT once per poll without STAT78, so every other call read the high byte (PPU2 open bus: the previous exit value) and started at once: in superfx_3d a third of the bytes landed on visible lines and were dropped (luna –dma-trace).

◆ gsuFrameBytes()

u16 gsuFrameBytes ( void )

Bytes of one framebuffer for gsu_scmr's height and depth.

Returns
32 columns x H/8 x 8*bpp bytes (16384 at 256x128x4bpp), 0 for OBJ mode or the reserved depth

◆ gsuInit()

u8 gsuInit ( void )
inline

Set the GSU default configuration; returns gsuIsPresent().

Returns
1 if GSU detected, 0 if not

Sets defaults: gsu_cfgr=$80, gsu_scmr=$19, gsu_scbr=$00, gsu_dma_src_hi=$00. Detection itself is crt0's (superfx_status); call this once at boot, and gsuIsPresent() when you only want the answer. Inlined.

◆ gsuIsPresent()

u8 gsuIsPresent ( void )
inline

Is a GSU on this cartridge? (crt0 detects it; this reads the outcome).

Returns
1 if crt0 read a non-zero chip version from the VCR, 0 if not

The presence test gsuInit() also returns, without the side effects — use it anywhere after the defaults are set. Inlined. (Added 2026-09-22; docs cited a superfxIsPresent() that never existed.)

◆ gsuLaunch()

void gsuLaunch ( void )
extern

Launch GSU program and wait for completion (WRAM-safe).

Call gsuSetProgram() first to set the GSU binary address. Reads gsu_cfgr, gsu_scmr, gsu_scbr for configuration. The CPU waits in WRAM while the GSU owns the Game Pak. Interrupts keep working (since 2026-09-25/26): the vectors point into WRAM, the NMI counts the frame and uploads OAM, an H/V-timer or GSU IRQ is acknowledged. The ROM-side work is not run during the job: your NMI callback, the pads and the dirty flags are picked up by the first ROM NMI after it (one call, not one per missed frame), and an H/V-timer IRQ raised during the job is acknowledged and DROPPED — your IRQ handler never sees it (crt0 gsu_irq_blob; until 2026-10-04 this line said it "waits").

◆ gsuPresent()

void gsuPresent ( void )

Queue the frame the last job drew; the next job draws in the other.

Waits (WaitForVBlank) while the previous frame is still being moved, then hands the buffer at gsu_scbr to the NMI and switches gsu_scbr to the other one. Call it after the job has stopped (gsuLaunch() returned, or gsuWait()).

◆ gsuPresentBusy()

u8 gsuPresentBusy ( void )

1 while a presented frame is still being moved to VRAM

◆ gsuPresentInit()

u8 gsuPresentInit ( u16 vram_a,
u16 vram_b,
u8 flags )

Set up presentation for gsu_scmr's geometry.

Parameters
vram_aVRAM word address of the char block shown first
vram_bVRAM word address of the other one
flags0, or GSU_PRESENT_ON_LAG_FRAMES
Returns
1, or 0 if a block is not a multiple of $1000 words, runs past VRAM, or two framebuffers from gsu_scbr on do not fit in the Game Pak RAM the header declares

The two Game Pak RAM framebuffers start at gsu_scbr and right after it. Sets BG1's char base to vram_a: call it with the screen off. The tilemap (gsuSetupBitmapTilemap) serves both blocks.

◆ gsuPresentWait()

void gsuPresentWait ( void )

Wait until the last presented frame is on screen.

◆ gsuSetProgram()

void gsuSetProgram ( const void * program)

Tell the library where the GSU program lives.

Parameters
programThe first byte of the assembled GSU binary (the .sfx.bin the build .incbins), in any ROM bank — the bank comes from the pointer

Sets gsu_prog_bank and gsu_prog_addr, which gsuLaunch() reads. Call it once before the first gsuLaunch(), and again only to switch programs. (Until 2026-09-26 this header asked for it but only the superfx_3d example defined it, in asm.)

extern const u8 gsu_program[]; // label before the .incbin in your .asm
void gsuSetProgram(const void *program)
Tell the library where the GSU program lives.

◆ gsuSetupBitmapTilemap()

void gsuSetupBitmapTilemap ( u16 vramAddr)
extern

Setup column-major tilemap for SuperFX PLOT framebuffer.

Parameters
vramAddrVRAM word address for tilemap (typically 0x4000)

32 columns of H/8 tiles, H from gsu_scmr's height (128, 160 or 192; since 2026-09-29, 16 rows whatever the height before); the rest of the 32x32 map points at tile 0. Set gsu_scmr first.

◆ gsuSetupHdmaBlanking()

void gsuSetupHdmaBlanking ( u16 topBlank,
u16 bottomBlank )
extern

Setup HDMA screen blanking for DMA bandwidth.

Parameters
topBlankScanlines of forced blank at top (e.g., 40)
bottomBlankScanlines of forced blank at bottom (e.g., 40)

Creates black bars like Star Fox, on HDMA channel 1 (INIDISP). HDMA starts a channel at line 0, so the bands appear from the next frame: the function returns only once they are on screen (it waits up to a frame) and then publishes them to gsuDmaFullFrame() and the gsuPresent() NMI, which use the blanked lines as extra transfer time. Leave the bands on while either is in use.

A band may be 0 (no band) to 224 lines; a band of 128 lines or more takes two HDMA entries. top + bottom above 224 leaves no visible lines. (Until 2026-10-03 a top band of 0 ended the table before it began, and the whole frame was then transferred on visible lines.) The visible lines are written with INIDISP = $0F, full brightness: setBrightness() and the fades do not apply while the bands are on.

◆ gsuStartCached()

void gsuStartCached ( u16 pc)

Start the program loaded by gsuCacheLoad() and return at once.

Parameters
pcEntry offset in the cache (0 for the first byte; the build's <name>.sfx.h names every entry point)

Writes gsu_cfgr, the 21 MHz clock, gsu_scbr, R8 = the buffer base (gsu_scbr x 1024, as gsuLaunch() does; since 2026-09-29), then gsu_scmr with RON forced to 0 (the ROM stays the CPU's), then R15, which starts the GSU.

◆ gsuWait()

void gsuWait ( void )

Wait for the end of the job, then give the Game Pak back to the CPU.

Returns at once if the GSU has already stopped. Clears SCMR, so the CPU can read the results the program left in Game Pak RAM.

Variable Documentation

◆ gsu_cfgr

u8 gsu_cfgr
extern

CFGR register value written by the launchers ($80 = IRQ mask). CFGR_FAST_MUL (bit 5, MS0) is cleared by gsuLaunch() and gsuStartCached() before the write: both run the GSU at 21 MHz, and "MS0 must be zero in 21MHz mode" (fullsnes, CFGR, 1adef8e33ff3c4e9; ares: products "may be invalid" with both set). A value of $A0 therefore reaches the register as $80 — this line said "$A0 = IRQ mask + fast multiply" until 2026-10-05. Code that starts the GSU at 10.7 MHz by itself may set MS0.

◆ gsu_dma_src_hi

u8 gsu_dma_src_hi
extern

DMA source high byte ($00=buffer A at $70:0000, $40=buffer B at $70:4000).

◆ gsu_owns_cart

u8 gsu_owns_cart
extern

1 while a GSU job owns the Game Pak ROM (crt0)

gsuLaunch() sets it around its job. Code of your own that starts a job with SCMR RON = 1 (from the RAM code window: RAM_CODE, RAM_CODE_SECTION) sets it before the start and clears it once SCMR is back to 0: while it is 1 the NMI and IRQ entries (crt0, in WRAM) do only the ROM-free part of their work — frame_count, OAM upload, IRQ acknowledge — and defer the rest.

◆ gsu_prog_addr

u16 gsu_prog_addr
extern

GSU program 16-bit address (set by gsuSetProgram).

◆ gsu_prog_bank

u8 gsu_prog_bank
extern

GSU program bank byte (set by gsuSetProgram).

◆ gsu_scbr

u8 gsu_scbr
extern

SCBR screen base ($00=buffer A, $10=buffer B).

◆ gsu_scmr

u8 gsu_scmr
extern

SCMR register value ($18=RAN+RON, $19=4bpp+RAN+RON).

◆ gsu_scmr_live

u8 gsu_scmr_live
extern

The value last written to SCMR (write-only), kept by the library.

gsuLaunch(), gsuCall(), gsuStartCached() and gsuWait() keep it. Code of your own that writes REG_SCMR while gsuPresent() is in use must keep it too — write it BEFORE giving the GSU the buses, clear it BEFORE taking them back: the presentation NMI takes Game Pak RAM from the GSU for its transfer and restores this value after.

◆ gsu_stop_irqs

volatile u8 gsu_stop_irqs
extern

GSU IRQs on STOP acknowledged so far (crt0, wraps at 256).

With gsu_cfgr's CFGR_IRQ_MASK clear, the GSU raises an IRQ when its program executes STOP. The Super FX IRQ entry (crt0, in WRAM) reads the GSU status to tell it from an H/V-timer IRQ, acknowledges it and adds one here; your irqSet() handler never sees it. Compare with a copy taken before the start: the count moving is the end of the job, without polling the GSU. The CPU's I flag must be clear (irqEnable() clears it) for the IRQ to be taken at all. (Since 2026-09-27: before, an unmasked STOP IRQ with the I flag clear locked the CPU in its IRQ entry.)

◆ superfx_status

u8 superfx_status
extern

SuperFX status from crt0 init (VCR chip version, 0=not detected).