Topics

UTTORI ASM ยท IN DEVELOPMENT

SPC700 architecture

Use spc700 architecture in SNES projects, with syntax and behavior notes.

The SPC700 is the SNES audio CPU, living in its own address space and RAM. The wiki has a full SPC700 reference and a practical explanation of transferring data to the APU.

For a ROM-embedded Nintendo-style transfer block:

arch 65816
lorom
org $128000

spcblock $6000
Start:
  mov a,#$7F
  mov $20,a
  bra Start
endspcblock execute Start

spcblock destination [nspc] writes a 16-bit placeholder size and 16-bit destination to the ROM, switches to SPC700 assembly at the destination, and fills the size during output finalization. endspcblock returns to the previous architecture. Either endspcblock execute address or a preceding startpos address appends a zero-size execute record.

spcblock $5000 nspc
  nop
startpos $5000
endspcblock

Rules worth knowing:

  • Destinations and execute addresses are 16-bit.
  • Blocks cannot nest.
  • arch, org, namespace, mapper directives, and freespace directives are unavailable inside a block.
  • Missing endspcblock is an error at the end of a pass.
  • custom block mode is recognized but intentionally not implemented.

arch spc700-raw selects 1:1 norom addressing for a standalone SPC payload. arch spc700-inline makes a later org begin an implicit SPC block and automatically closes it at pass end, matching the supported Asar inline fixture.

The encoder accepts the canonical forms listed in the appendix plus numbered bit operations such as bbs0 dp,label, bbc7 dp,label, and carry/bit spellings exercised by arch-spc700.asm.

The disassembly API exports Spc700Decoder, spc700Specifications, buildSpc700Catalog(), and formatSpc700Instruction(). The fixed-width 256-opcode table is the shared authority for decoding and editor tooling. Decoding reports control flow, branch/call references, bit-address operands, direct-page-aware data references, and foundational PSW/stack effects. Formatting uses this project's native spellings, including (X+) for post-increment.

Standalone static analysis uses Spc700AramImage and analyzeSpc700Aram(). snes.spc700.aram is a distinct 16-bit project address space. It is never treated as rom.file or the 65816 snes.cpu bus. The analyzer follows branches, direct calls, returns, BRK, and TCALL vectors available in the represented image, preserves unresolved indirect flow for target overrides, tracks PSW.P through joins and explicit PUSH P / POP P, identifies volatile $00F0โ€“$00FF I/O, and emits project-shaped CFG, annotation, and evidence records. Standalone payloads require explicit or project-derived entry points because a payload is not assumed to be a reset-time 64 KiB memory snapshot.

Embedded Nintendo-style/NSPC streams are parsed with parseSpc700UploadStream(rom, startOffset). The start is always explicit. The parser does not scan for a guessed signature. It bounds every little-endian {size, destination, payload} record and requires the {0, execute} trailer. materializeSpc700UploadBlock() copies payloads into analyzable Spc700AramImage segments, splitting a 16-bit wrap at $FFFF while retaining source ROM offsets. spc700UploadProjectRecords() emits a nested ROM stream โ†’ block โ†’ payload hierarchy with cross-address-space ARAM projection children and hash-bound detector evidence, never ROM bytes.

renderSpc700AramSource() emits byte-exact native source for a standalone ARAM payload using arch spc700-raw. renderSpc700UploadSource() recreates bounded Nintendo-style upload streams with spcblock/endspcblock, including the execute trailer. Both renderers preserve undecodable tails as db rather than guessing instructions.