S-SMP

From SNESdev Wiki
Revision as of 10:37, 3 October 2022 by Rainwarrior (talk | contribs) (→‎IPL Boot ROM: speculating about meaning of IPL)
Jump to navigationJump to search

The SPC700 is the CPU portion of the S-SMP processor used to run the sound and music of the SNES. It is an 8-bit CPU with capabilities similar to the 6502.

Memory Layout

64 kilobytes of RAM are mapped across the 16-bit memory space of the SPC-700. Some regions of this space are overlaid with special hardware functions.

Range Note
$0000-00EF Zero Page RAM
$00F0-00FF Sound CPU Registers
$0100-01FF Stack Page RAM
$0200-FFBF RAM
$FFC0-FFFF IPL ROM or RAM

The region at $FFC0-FFFF will normally read from the 64-byte IPL ROM, but the underlying RAM can always be written to, and the high bit of the Control register $F1 can be cleared to unmap the IPL ROM and allow read access to this RAM.

Registers

Name Address Bits Type Notes
Test $F0 .I.E TRWH W8 Undocumented test register. Internal and external wait-states (I,E), timers enable (T), RAM disable (R), RAM writable (W), timers disable (H).
Control $F1 I.CC .210 W8 Enable IPL ROM (I), Clear data ports (C), timer enable (2,1,0).
Register Address $F2 .AAA AAAA RW8 Selects a DSP register address.
Register Data $F3 VVVV VVVV RW8 Reads or writes data to the selected DSP address.
Port 0 $F4 VVVV VVVV RW8 Reads or writes data to APUIO0.
Port 1 $F5 VVVV VVVV RW8 Reads or writes data to APUIO1.
Port 2 $F6 VVVV VVVV RW8 Reads or writes data to APUIO2.
Port 3 $F7 VVVV VVVV RW8 Reads or writes data to APUIO3.
--- $F8 .... .... RW8 Unused (normal RAM).
--- $F9 .... .... RW8 Unused (normal RAM).
Timer 0 $FA TTTT TTTT W8 8KHz timer 0 interval.
Timer 1 $FB TTTT TTTT W8 8KHz timer 1 interval.
Timer 2 $FC TTTT TTTT W8 64KHz timer 2 interval.
Counter 0 $FD .... CCCC R8 Timer 0 count-up.
Counter 1 $FE .... CCCC R8 Timer 1 count-up.
Counter 2 $FF .... CCCC R8 Timer 2 count-up.

Write-only registers will read back as $00.

$F0 Test

This undocumented register responds to writes only when the P flag is clear.

$F1 Control

This provides a way for the SPC-700 to reset the read ports ($F4-F7) without the SNES CPU having to write them externally. It also starts and stops the 3 timers.

  • Bit 0 enables timer 0 when set. A transition from clear to set resets the internal interval counter to 0.
  • Bit 1 enables timer 1.
  • Bit 2 enables timer 2.
  • Setting bit 4 will reset the value the SPC will read from ports 0 and 1 ($F4, $F5) to $00.
  • Setting bit 5 will reset ports 2 and 3 ($F6, $F7).
  • Bit 7 will enable the IPL ROM if set.

At reset this register is initialized as if $80 was written to it.

$F2-F3 DSP

Write $F2 to select a DSP register, then a value can be read or written to that DSP register via $F3.

  • Writing $F2 with the high bit set will select a DSP register according to the lower 7 bits, but it will be read-only.
  • The high bit of $F2 will always read back as 0.

$F4-F7 Port 0-3

These 4 ports allow communication with the SNES CPU. There are 8 stored values, each is a one-way communication written from one side, and readable only from the other side. Each port therefore has two separate one-way values, each seen from only either the SNES CPU or the SPC-700.

If a port is read on the same cycle it is written, an incorrect value will result. For this reason, common practice is to read a port in a loop until the value changes, and then read it once more to ensure the correct value is read. (A single port can be used this way to indicate that a message is ready, and the other 3 ports could be safely read only once with the assumption that the other CPU will not write to them once the ready indication was given.)

At reset these registers are initialized to $00.

$FA-FC Timer 0-2

When enabled via $F1, the 3 timers will internally count at a rate of 8 KHz (timers 0,1) or 64 KHz (timer 2), and when this interval value has been exceeded, they will increment their external counter result ($FD-FF) and begin again.

$FD-FF Counter 0-2

The 4-bit result of the three timers counts up every time the interval is reached.

Reading these registers resets each counter to 0 immediately after the read. The upper 4 bits will always read as 0.

DSP Registers

A DSP register can be selected with $F2, after which it can be read or written at $F3. Often it is useful to load the register address into A, and the value to send in Y, so that MOV $F2, YA can be used to do both in one 16-bit instruction.

The DSP register address space only has 7 bits. The high bit of $F2, if set, will make the selected register read-only via $F3.

Voices

There are 8 voices, numbered 0 to 7. Each voice X has 10 registers in the range $X0-$X9.

Name Address Bits Notes
VOL (L) $X0 SVVV VVVV Left channel volume, signed.
VOL (R) $X1 SVVV VVVV Right channel volume, signed.
P (L) $X2 LLLL LLLL Low 8 bits of sample pitch.
P (H) $X3 --HH HHHH High 6 bits of sample pitch.
SCRN $X4 SSSS SSSS Selects a sample source entry from the directory (see DIR below).
ADSR (1) $X5 EDDD AAAA ADSR enable (E), decay rate (D), attack rate (A).
ADSR (2) $X6 LLLR RRRR Sustain level (L), sustain rate (R).
GAIN $X7 0VVV VVVV
1MMV VVVV
Mode (M), value (V).
ENVX $X8 0VVV VVVV Reads current 7-bit value of ADSR/GAIN envelope.
OUTX $X9 SVVV VVVV Reads signed 8-bit value of current sample wave multiplied by ENVX, before applying VOL.

P

Sample pitch is a 14-bit value controlling the rate the BRR sound sample will be played back.

Rate: P x 32,000 Hz / $1000

A pitch of $1000 will play back the sample at the SNES native samplerate of 32,000 Hz.

The pitch can go as high as $3FFF, almost two octaves above $1000. Pitches above $1000 will be subject to some aliasing from samples that are skipped over.

The pitch can go all the way down to 0, where it is halted. Pitches below $1000 will be lacking in higher frequencies, and there is not very much precision as the pitch value approaches 0.

SCRN

This points to an entry in the sample source directory (DIR). Changing this will not immediately change the voice's sample without a key on (KON), but if a looping sample is playing it can be used to change the loop point without a key on.

ADSR

This controls an Attack-Decay-Sustain-Release envelope that automatically adjusts the sample's envelope volume (ENVX) over time.

  • E: 1 to enable ADSR envelope, otherwise 0 uses GAIN instead.
  • A: Attack speed. $F for instant.
  • D: Decay speed, time to decay from full volume to the sustain level after the initial attack.
  • L: Sustain level.
  • R: Sustain release, speed of decay to 0 after note off.

GAIN

This register has 5 modes:

  • 0VVV VVVV sets ENVX directly.
  • 110V VVVV Linear slide up to 100% volume with rate V.
  • 111V VVVV Bent-line (fast to 75%, then slower to 100%) slide up with rate V.
  • 100V VVVV Linear slide down to 0% volume with rate V.
  • 101V VVVV Exponential slide down to 0% volume with rate V.

Global

Other DSP registers apply globally, rather than to a specific voice.

Name Address Bits Notes
MVOL (L) $0C SVVV VVVV Left channel main volume, signed.
MVOL (R) $1C SVVV VVVV Right channel main volume, signed.
EVOL (L) $2C SVVV VVVV Left channel echo volume, signed.
EVOL (R) $3C SVVV VVVV Right channel main volume, signed.
KON $4C 7654 3210 Key on. Writing this with any bit set will start a new note for the corresponding voice.
KOF $5C 7654 3210 Key off. Writing this with any bit set will put the corresponding voice into its release state.
FLG $6C RMEN NNNN Flags: soft reset (R), mute all (M), echo disable (E), noise frequency (N).
ENDX $7C 7654 3210 Read for end of sample flag for each channel.
EFB $0D SVVV VVVV Echo feedback, signed.
- $1D ---- ---- Unused.
PMON $2D 7654 321- Enables pitch modulation for each channel, controlled by OUTX of the next lower channel.
NON $3D 7654 3210 For each channel, replaces the sample waveform with the noise generator output.
EON $4D 7654 3210 For each channel, sends to the echo unit.
DIR $5D DDDD DDDD Pointer to the sample source directory page at $DD00.
ESA $6D EEEE EEEE Pointer to the start of the echo memory region at $EE00.
EDL $7D ---- DDDD Echo delay time (D).
C0 $0F SVVV VVVV Echo filter coefficient.
C1 $1F SVVV VVVV Echo filter coefficient.
C2 $2F SVVV VVVV Echo filter coefficient.
C3 $3F SVVV VVVV Echo filter coefficient.
C4 $4F SVVV VVVV Echo filter coefficient.
C5 $5F SVVV VVVV Echo filter coefficient.
C6 $6F SVVV VVVV Echo filter coefficient.
C7 $7F SVVV VVVV Echo filter coefficient.

KON

This immediately starts the sample from its beginning, as designated by SCRN. If ADSR is enabled, its envelope will also start from the beginning.

FLG

  • R: soft reset prevents KON and mutes all voices.
  • M: mutes all voices.
  • E: set to 1 to disable echo, if 0 the echo unit will actively overwrite memory in the echo region (ESA). Do not write 0 to this bit unless ESA/EDL have been prepared.
  • N: sets the noise generator frequency.

ENDX

Each bit will read as set once a voice has finished playing a BRR sample block with the Source End flag set. Writing any value to this register will reset all the bits to 0, otherwise they remain set until the voice receives a new note on (KON).

Instruction Set

Official instruction names and syntax for SPC-700 instruction were provided by Sony. However, because of its architectural similarity to 6502, some prefer to rename and remap them using a 6502 style syntax. Both are provided here.

SPC-700 Instruction Set
Instruction 6502-Style Opcode Bytes Cycles Flags Notes
TODO

IPL Boot ROM

See: Booting the SPC700

The IPL Boot ROM is a small built-in program responsible for initializing the SPC-700, and making it ready to transfer a program from the SNES CPU, then execute it. It normally resides at $FFC0, with its code beginning at this address, but if desired it can be unmapped using the $F1 control register, and replaced with the underlying RAM.

When the SNES is reset, the SPC-700 will also reset and begin executing the IPL.

IPL might stand for "Initial Program Load".

The high level process of the IPL is described below:

  1. Reset: Stack pointer = $EF. Zero-page from $00-$EF is set to $00. (Note: this leaves the top 16-bytes of the stack page unused by default.)
  2. Signal ready: Port 0 = $AA. Port 1 = $BB.
  3. Wait for signal: Loop until $CC is read from port 0.
  4. Read address: Read a 2 byte address from port 2 (low) and 3 (high).
  5. Acknowledge: Read value from port 0 and write it to port 0, confirming the signal was received.
  6. Begin: Read value from port 1, if 0 begin executing code at address read in step 4, otherwise begin reading data (step 7).
  7. Begin Transfer: Loop until read port 0 reads 0. Set 8-bit counter to 0, then proceed to step 8.
  8. Transfer Loop:
    • Read a byte from port 1 and write to the destination address.
    • Write the value read from port 0 to port 0 to acknowledge receipt of the byte.
    • Increment the destination address, and increment the 8-bit counter.
    • Wait until port 0 reads equal to the new counter (SNES increments port 0 for next byte), and repeat step 8, otherwise...
    • If port 0 reads greater than the new counter (i.e. SNES increments 2 or more times before writing port 0): write back port 0 to acknowledge, transfer ends and return to step 4.

On the SNES side, to load your program into the SPC:

  1. Wait for the ready signal (IPL 2).
  2. Write the first destination address to ports 2+3, write any non-zero value to port 1, then write $CC to port 0 to begin (IPL 3).
  3. Wait for port 0 to read back $CC (IPL 5).
  4. Write the first byte to port 1, then write 0 to port 0 to initiate the data transfer (IPL 6). Set 8-bit counter to 0.
  5. When port 0 read the value of your 8-bit counter, write the next byte to port 1, then increment your counter and write it to port 0. Repeat for each byte of data. (IPL 8)
  6. After the last byte is written, write the next destination address to ports 2+3, write 0 to port 1, then increment your counter twice and write it to port 0. (IPL 4)
  7. The SPC-700 will begin executing code at the destination address now.

Most often only one data transfer is necessary, but by writing a non-zero value to port 1 in SNES step 6, we can instead initiate another transfer at the new address. When doing this, if your counter is 0 after incrementing twice, increment it a third time to be non-zero before writing it to port 0. This is because a value of 0 in port 0 will also signal the first byte of the transfer in the next step.

It is most common to place the data program at $0200, just above the stack page, and start execution from there, but this is not a requirement.

BRR Samples

TODO (See Links section below.)

See Also

Links