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

DSP-1 coprocessor (NEC µPD77C25) — fixed-point 3D math from C. More...

#include <snes/types.h>

Go to the source code of this file.

Classes

struct  Dsp1Camera
 The camera of the projection: the seven inputs of DSP-1 command $02, "Parameter", under their names in the manual. More...

Macros

Bridges to the SDK fixed-point types

The SDK's fixed (snes/math.h) is signed 8.8 and its angles are 8-bit (256 = full turn); the DSP-1 uses 1.15 fractions (T) and 16-bit angles (A). These macros convert. Note the T range is −1..+1: converting a fixed outside −1.0..+0.999 overflows — clamp first if your value can exceed it.

#define DSP1_A_FROM_FIX8(a)
 SDK 8-bit angle (256 = full turn) → DSP-1 A (2^16 = full turn).
#define DSP1_FIX_FROM_T(t)
 DSP-1 T (1.15) → SDK 8.8 fixed: an arithmetic shift, so it rounds toward minus infinity (floor), not toward zero (the word "truncating" stood here until 2026-10-04).
#define DSP1_T_FROM_FIX(f)
 SDK 8.8 fixed → DSP-1 T (1.15). Valid for −1.0 ≤ f < 1.0.

Functions

void dsp1Attitude (u16 scale, u16 az, u16 ay, u16 ax)
 Build a 3D rotation matrix into slot A (DSP-1 command $01, "Attitude").
u16 dsp1Distance (s16 x, s16 y, s16 z)
 Euclidean length of a 3D vector (DSP-1 command $28, "Distance").
void dsp1Init (void)
 Resynchronise the DSP-1 to a known command-wait state.
u8 dsp1IsPresent (void)
 Is a DSP-1 responding? Known-answer self-test.
s16 dsp1Multiply (s16 a, s16 b)
 Signed 1.15 × 1.15 multiply (DSP-1 command $00).
void dsp1Objective (s16 x, s16 y, s16 z)
 Transform a point by matrix A: local → world (DSP-1 command $0D, "Objective").
void dsp1Project (s16 x, s16 y, s16 z)
 Project a world point to the screen (DSP-1 command $06, "Project").
s16 dsp1Range (s16 x, s16 y, s16 z, u16 r)
 Sphere test (DSP-1 command $18, "Range").
void dsp1Raster (u8 FAR *ab, u8 FAR *cd, s16 vs, u16 count)
 Stream per-scanline Mode 7 matrices into two HDMA payloads (DSP-1 command $0A, "Raster").
void dsp1Rotate (u16 angle, s16 x, s16 y)
 Rotate a 2D point about the origin (DSP-1 command $0C, "Rotate").
void dsp1SetCamera (const Dsp1Camera *cam)
 Set up the projection plane (DSP-1 command $02, "Parameter").
void dsp1Target (s16 h, s16 v)
 Screen point → ground plane (DSP-1 command $0E, "Target").
void dsp1Triangle (u16 angle, s16 radius)
 Scaled sine and cosine of an angle (DSP-1 command $04, "Triangle").

Variables

volatile s16 dsp1_o0
 Multi-word DSP-1 result registers (written by multi-output commands).
volatile s16 dsp1_o1
volatile s16 dsp1_o2
volatile s16 dsp1_o3

Detailed Description

DSP-1 coprocessor (NEC µPD77C25) — fixed-point 3D math from C.

The DSP-1 is a fixed-function math coprocessor found in Pilotwings, Super Mario Kart and other pseudo-3D games. It runs Sony's fixed firmware; you do not program it — you invoke its ~30 built-in commands (matrix, vector, projection, trig) over a two-register port. This module wraps that command interface as ordinary C calls.

Cartridge requirement
Build with USE_DSP1 := 1 (sets the ROM header cartridge type to $03, or $05 with USE_SRAM := 1, and maps the DSP registers). The DSP-1 registers live at $30:8000 (data) / $30:C000 (status) on the LoROM board this SDK targets.
Fixed-point
The DSP-1 types its operands per slot:
  • T — signed 1.15 fraction (−1.0 … +0.99997); 0x7FFF≈+1, 0x4000=0.5.
  • I — signed 16-bit integer (coordinates).
  • A — signed 16-bit angle; a full turn is 2^16, so 0x4000=+90°, 0x8000=±180°, and angle arithmetic wraps for free.
Multi-word results
Commands that return more than one word write their outputs to the module's result globals (dsp1_o0, dsp1_o1, dsp1_o2); read them after the call. Single-word commands return their value directly.
Warning
luna emulates the DSP-1 at low level and needs Sony's dsp1b.rom firmware installed (~/.config/luna/firmware/ or via luna state --dsp1-rom <path> <rom> once — the setting persists). It is copyrighted and not shipped; DSP-1 examples are firmware-gated in CI.
DSP-1 calls are NOT safe inside nmiSet() callbacks. A command is a multi-byte DR transaction; if NMI interrupts one mid-sequence and the callback issues its own DSP-1 command, both transactions are corrupted (same class as the fixMul() restriction in KNOWN_LIMITATIONS.md). Keep all DSP-1 work in the main loop.
// rotate the point (100, 0) by 90 degrees -> (0, -99): clockwise with Y
// down, and the chip's sine of 90 degrees is 0x7FFF, not 1.0
dsp1Rotate(0x4000, 100, 0);
s16 x = dsp1_o0; // 0
s16 y = dsp1_o1; // -99
volatile s16 dsp1_o1
volatile s16 dsp1_o0
Multi-word DSP-1 result registers (written by multi-output commands).
void dsp1Rotate(u16 angle, s16 x, s16 y)
Rotate a 2D point about the origin (DSP-1 command $0C, "Rotate").
signed short s16
16-bit signed integer (-32768 to 32767)
Definition types.h:50
See also
.claude/notes/tech/dsp1_reference.md — the full command reference.

Macro Definition Documentation

◆ DSP1_A_FROM_FIX8

#define DSP1_A_FROM_FIX8 ( a)
Value:
((u16)((u16)(u8)(a) << 8))
static u16 a
Definition main.c:156
unsigned short u16
16-bit unsigned integer (0 to 65535)
Definition types.h:53
unsigned char u8
8-bit unsigned integer (0 to 255)
Definition types.h:47

SDK 8-bit angle (256 = full turn) → DSP-1 A (2^16 = full turn).

◆ DSP1_FIX_FROM_T

#define DSP1_FIX_FROM_T ( t)
Value:
((s16)((s16)(t) >> 7))

DSP-1 T (1.15) → SDK 8.8 fixed: an arithmetic shift, so it rounds toward minus infinity (floor), not toward zero (the word "truncating" stood here until 2026-10-04).

◆ DSP1_T_FROM_FIX

#define DSP1_T_FROM_FIX ( f)
Value:
((s16)((s16)(f) << 7))

SDK 8.8 fixed → DSP-1 T (1.15). Valid for −1.0 ≤ f < 1.0.

Function Documentation

◆ dsp1Attitude()

void dsp1Attitude ( u16 scale,
u16 az,
u16 ay,
u16 ax )

Build a 3D rotation matrix into slot A (DSP-1 command $01, "Attitude").

Parameters
scalematrix scale (T, signed 1.15; 0x7FFF ≈ 1.0)
azrotation about Z (A)
ayrotation about Y (A)
axrotation about X (A)

Produces no output — it loads the matrix the transform commands consume. Call once per frame (or whenever the orientation changes), then transform your points with dsp1Objective.

◆ dsp1Distance()

u16 dsp1Distance ( s16 x,
s16 y,
s16 z )

Euclidean length of a 3D vector (DSP-1 command $28, "Distance").

Parameters
xvector X (I)
yvector Y (I)
zvector Z (I)
Returns
sqrt(x²+y²+z²) as a u16 — not rounded: measured on luna (which runs the real DSP-1B firmware), every exact length reads ONE LOW: (3,4,12) -> 12, (300,400,0) -> 499, (0,0,10000) -> 9999. Treat the result as exact to within 1, and compare with >= / <, never ==. Measured on the DSP-1B firmware; the official manual (Book II §5.2.3, code 28H) states no rounding. The truncation is the algorithm (bsnes dsp1emu.cpp: Distance >>= (E >> 1)), not the DSP1/DSP1A bug that fullsnes names for this command (fixed in DSP1B, chunk 919f1a3794274d16): on the 1/1A revisions the interpolation table is read inverted at odd positions (bsnes dsp1emu.cpp l. 418-420, read by the corpus fiche 647e4ef6ebffdf00; sneslab names the bug without describing it). Command $2F returns the ROM version ($0100 = DSP1/1A, $0101 = DSP1B) if a game needs to know which one it runs on.

Hardware square root — handy for homing missiles, audio attenuation, anything that needs a true distance rather than a compare.

◆ dsp1Init()

void dsp1Init ( void )

Resynchronise the DSP-1 to a known command-wait state.

Issues the $80 Sync/Reset byte repeatedly (128×, matching the stock-game boot handshake). $80 flushes any pending command, so this recovers the chip even if a previous command was interrupted mid-parameter. Call once at startup before your first DSP-1 command; harmless to call again to recover.

◆ dsp1IsPresent()

u8 dsp1IsPresent ( void )

Is a DSP-1 responding? Known-answer self-test.

Returns
1 if the chip answered dsp1Multiply(0x4000, 0x4000) == 0x2000, 0 if the status port never raised RQM or the product was wrong.

Unlike the other calls this one cannot hang on a missing chip (bounded poll). Use it at boot the way sa1IsReady() / gsuIsPresent() are used — e.g. to fall back to software math when running on the wrong board or on an emulator without the firmware. (Named dsp1Present() until 2026-09-22; that name was removed on 2026-10-05.)

◆ dsp1Multiply()

s16 dsp1Multiply ( s16 a,
s16 b )

Signed 1.15 × 1.15 multiply (DSP-1 command $00).

Parameters
afirst factor (T, signed 1.15)
bsecond factor (T, signed 1.15)
Returns
the product in signed 1.15 (rounded to 15 bits)

Typed s16 like every other signed DSP-1 quantity (and like DSP1_T_FROM_FIX(), which produces one). Until 2026-09-21 the three were u16: dsp1Multiply(x, y) < 0 was always false and >> 7 on the result was a logical shift. Same stack slots, same bits.

◆ dsp1Objective()

void dsp1Objective ( s16 x,
s16 y,
s16 z )

Transform a point by matrix A: local → world (DSP-1 command $0D, "Objective").

Parameters
xlocal X (I)
ylocal Y (I)
zlocal Z (I)

Writes dsp1_o0 = x', dsp1_o1 = y', dsp1_o2 = z' (world space). Run dsp1Attitude first to set the matrix.

◆ dsp1Project()

void dsp1Project ( s16 x,
s16 y,
s16 z )

Project a world point to the screen (DSP-1 command $06, "Project").

Parameters
xworld X (I)
yworld Y (I)
zworld Z (I)

Writes dsp1_o0 = H (screen X), dsp1_o1 = V (screen Y), dsp1_o2 = M (scale/depth — use it to size sprites with distance). Requires a prior dsp1SetCamera. Takes ~627 DSP cycles (~83 µs); the driver polls, so timing is handled for you.

◆ dsp1Range()

s16 dsp1Range ( s16 x,
s16 y,
s16 z,
u16 r )

Sphere test (DSP-1 command $18, "Range").

Parameters
xoffset X from the sphere centre (I)
yoffset Y (I)
zoffset Z (I)
rsphere radius (I)
Returns
((x²+y²+z²) − r²) >> 15, arithmetic shift — NOT the raw difference (it would not fit 16 bits). Measured on luna's DSP-1B: (3000,4000,0, r 1000) -> 732 = 24 000 000 / 32768; (0,0,0, r 1000) -> -31. Negative means inside; but anything within 32768 squared-units outside the surface reads 0, and (30,40,0, r 5) — ten radii away in small units — reads 0 too. Use world units large enough for the shell you care about, and test < 0 for "clearly inside". The squared difference is what the official manual states (Book II §5.2.2, code 18H: "subtracts the square of the specified range from the square of the vector size", output type H2); the >> 15 is the H2 scaling as measured on the firmware. (Until 2026-09-22 this note claimed no reference gave it — the manual, in the corpus, did.)

One call replaces three multiplies and two adds — cheap 3D proximity / LOD tests without ever leaving 16-bit C.

◆ dsp1Raster()

void dsp1Raster ( u8 FAR * ab,
u8 FAR * cd,
s16 vs,
u16 count )

Stream per-scanline Mode 7 matrices into two HDMA payloads (DSP-1 command $0A, "Raster").

Parameters
abdestination for A,B: 4 bytes per raster (A lo, A hi, B lo, B hi)
cddestination for C,D: 4 bytes per raster (C lo, C hi, D lo, D hi)
vsfirst raster number (relative to the imaginary centre; use Vva + 2 from dsp1SetCamera for the first ground line)
countnumber of rasters to stream (at most 127 per HDMA repeat block)

This is the Super Mario Kart / Pilotwings ground: the chip computes, for each raster, the Mode 7 matrix that projects the ground plane under the camera set by dsp1SetCamera — perspective, and rotation when aas is non-zero. The two buffers are laid out as the payload of an HDMA_MODE_2REG_2X repeat block (M7A/M7B on one channel, M7C/M7D on the other); point the block's header at them and put the block on screen line 112 + Vof + vs. Set M7VOFS so the Mode 7 pivot (M7X/M7Y = Cx/Cy) sits on the imaginary centre line: the D values are secant slopes relative to that line, not derivatives.

The stream is closed the way the manual specifies ($8000 written in place of a D read), so the chip is back in command mode on return. Cost is CPU-bound: about 80 µs per raster, roughly 100 rasters in a frame's budget — refresh the ground at that size, or every other frame.

Warning
Not NMI-safe (like every DSP-1 call), and the tables must not be the ones HDMA is reading this frame: double-buffer and swap with hdmaSetTable during VBlank.

◆ dsp1Rotate()

void dsp1Rotate ( u16 angle,
s16 x,
s16 y )

Rotate a 2D point about the origin (DSP-1 command $0C, "Rotate").

Parameters
angle16-bit rotation angle (A)
xpoint X (I)
ypoint Y (I)

Writes dsp1_o0 = x' and dsp1_o1 = y'. Rotation is clockwise in the SNES screen convention (Y down): (100,0) at 90° gives (0,-99) — the chip's sine of 90° is 0x7FFF, not 1.0, so a full-scale result reads one short.

◆ dsp1SetCamera()

void dsp1SetCamera ( const Dsp1Camera * cam)

Set up the projection plane (DSP-1 command $02, "Parameter").

Parameters
camthe seven inputs (see Dsp1Camera); read during the call, not kept

Writes four words (order per the official manual §5.4.1, verified on luna):

  • dsp1_o0 = Vof, the raster number of the "imaginary centre";
  • dsp1_o1 = Vva, the horizon raster, relative to Vof — the horizon is on screen line 112 + Vof + Vva, and the ground below it streams from raster Vva + 2 (see dsp1Raster);
  • dsp1_o2 = Cx, dsp1_o3 = Cy: ground coordinates of the point under the imaginary centre — write them to M7X/M7Y (mode7SetCenter) when driving Mode 7 from dsp1Raster.

Raster numbers are down-positive and relative to the screen centre; note that dsp1Project's V is the opposite (up-positive). Call once (and again whenever the camera moves), then dsp1Project points or dsp1Raster the ground. lfe/les are best tuned empirically — see the values used by examples/chips/dsp1_cube; les is also the vertical focal length.

◆ dsp1Target()

void dsp1Target ( s16 h,
s16 v )

Screen point → ground plane (DSP-1 command $0E, "Target").

Parameters
hscreen X relative to the centre (I; mirrored like dsp1Project's H)
vscreen raster relative to the imaginary centre (I; down-positive)

The inverse of dsp1Project for the ground plane: which ground (x, y) is under a cursor, a crosshair, a missile scope. Writes dsp1_o0 = ground X and dsp1_o1 = ground Y, in the same convention as Cx/Cy — dsp1Target(0, 0) returns exactly the Cx/Cy pair of the last dsp1SetCamera. Requires a prior dsp1SetCamera.

◆ dsp1Triangle()

void dsp1Triangle ( u16 angle,
s16 radius )

Scaled sine and cosine of an angle (DSP-1 command $04, "Triangle").

Parameters
angle16-bit angle (A; full turn = 2^16)
radiusinteger magnitude (I) to scale the result by

Writes dsp1_o0 = radius·sin(angle) and dsp1_o1 = radius·cos(angle).

Variable Documentation

◆ dsp1_o0

volatile s16 dsp1_o0
extern

Multi-word DSP-1 result registers (written by multi-output commands).

Meaning is per command: e.g. after dsp1Triangle, dsp1_o0 = radius·sin and dsp1_o1 = radius·cos; after dsp1Rotate, dsp1_o0/dsp1_o1 = x'/y'.

◆ dsp1_o1

volatile s16 dsp1_o1
extern
See also
dsp1_o0

◆ dsp1_o2

volatile s16 dsp1_o2
extern
See also
dsp1_o0

◆ dsp1_o3

volatile s16 dsp1_o3
extern
See also
dsp1_o0 (4th word — dsp1SetCamera only)