The SNES (or Super Famicom) is widely considered as one of the greatest gaming consoles of all time. Even though its games had fantastic soundtracks, it's not too well known, that the console actually used a sort of wavetable synthesis chip for this - the SPC700! This chip is based on the technology of iconic synthesizers such as the Korg Wavestation. Together with the DSP by Sony the console can produce 8 channels of stereo wavetable synthesis, 1 noise channel, has ADSR envelope controls and even echo or filtering effects.
The goal of this project is to make use of this chip and to turn the console into a standalone synthesizer, but without modifying the console itself. A cartridge, that everyone with a console can use, is needed!
The potentiometer turns, the ARM co-processor thinks about this for a little while, tells the CPU on the SNES that things have changed since 2µs ago so we need to talk to the SPC-700, which is already sweating from looping the same sample over and over again.
Made some minor changes to the STM32 firmware today and was finally able to test some real-life code for the SNES. It's a small set of instructions that's supposed to color the screen completely green. This screen isn't green, I know that. :) But it's not too far off!
If you look at this output of bsnes, you can see what I was expecting to see:
But I did not write or invent this code, and if I load it in bsnes with accuracy mode, it looks like this:
And after a reset it looks like that:
So, I think it's safe to say that the pitch green image is a product of emulation errors because a more accurate emulation also shows a random image.
Going to test a sound sample next.
As I said, it's an interesting world where a picture like this can be considered success. :D
Today, I executed the first line of code on the SNES which was actually stored in an array in the firmware for the STM32! Felt like landing on the moon tbh. :D This means, a very important part of the firmware is done and working. I am now able to store 65816 instructions in the STM32 memory and have them executed on real hardware.
Coming up is firmware for the SNES to get some sounds from the SPC-700! Stay tuned!
So, as promised, we're going to write our first SNES program today. There are many tutorials out there for SNES code in assembler language, but we're going to do things a little differently today: We're going to write bare-metal machine code right away!
HxD: A Hex editor for Windows
bsnes+: A SNES emulator with debugging tools
We'll have to use bsnes+ because it's emulating hardware and it's not focused on playing SNES games. Emulators like 9snesx will not work because they ommit certain hardware "features" (and bugs). The bsnes+ emulator simply takes machine code from a binary file and feeds it to a virtual 65816 processor in a SNES machine. bsnes+ also emulates SPC-700 and other chips in the SNES. To write our first program we have to understand at least a little bit about memory mapping in the SNES. Unlike processors of today, the Ricoh 5A22 (65816 core) does not have built-in memory. It has a 24-bit address bus and an 8-bit data bus, and everything is connected to these busses. Even the internal system registers. So, the ROM, the RAM, the GPU... everything is connected to the address bus and seperated into address spaces called a "memory map". To talk to the ROM, we have to use the correct address space. As you can see in the Memory Map of the SNES (https://wiki.superfamicom.org/memory-mapping), ROM is located at Banks 0x00-0x3F on addresses from 0x8000 until 0xFFFF. So the ROM is located on 64 banks of 32768 bytes each. 64 times 32768 equals 2097152, which equals to 2MB. There are ways to address much more space on the SNES, but for now, we don't care about this.
A bit about the ROM model on the SNES: There are two typical types of ROM for the SNES called LoROM and HiROM. I will only focus on LoROM. LoROM causes ROM chunks in SNES to be max 32KB wide ($8000 bytes), and are typically fetched from the $8000-FFFF region. It is produced by connecting the address lines like this:
So for addresses from 0x000000 up to 0x007FFF everything works "normal". Since A15 is not connected, the adresses from 0x008000 up to 0x00FFFF refer to the same area. Same with the banks. Since A23 is ommited, the ROM can only distinguish between adresses in the banks from 0x00 up to 0x7F (128 banks) and the banks from 0x80 to 0xFF refer to the same area. So the ROM is repeated once in every bank and the upper banks repeat too. But if we take SNES signals A16 to A23 (the bank lines), we can see that A16 is actually connected to A15 on the ROM. So the SNES counts the addresses from 0x000000 up to 0x007FFF, everything is cool, then it counts from 0x008000 up to 0x00FFFF and actually sees the same data. This means the ROM is now at address 0x007FFF.
So, since one bit (A15) was left out, the ROM gets mapped twice on the SNES and has its own address space. This concept works for banks up to 0x1F, then things get a bit more complicated, but I wouldn't worry about it too much right now. One more important thing is that ROM is only visible from SNES addresses 0x008000 to 0x00FFFF, because the lower half is used for other things.
O.K., now back to our ROM file. The file needs a block of specific information which includes the internal name of the ROM, the interrupt vectors (addresses to machine code within the ROM in 16 bit format), version, etc. This portion of the ROM is also known as the SNES header. While the name of the ROM and certain region locks are not important, because they are only tested by software...
Let's start from the top here and work our way down... The CPU in the SNES is the Ricoh 5A22.
The 5A22 is a Microprocessor, so it has a CPU and peripherals. In addition to the 65C816 CPU core, the 5A22 includes:
Controller port interface circuits, including serial access to controller data
An 8-bit parallel I/O port, which is mostly unused in the SNES
Circuitry for generating non-maskable interrupts on V-blank
Circuitry for generating interrupts on calculated screen positions
A DMA unit, supporting two primary modes:
General DMA, for block transfers at a rate of 2.68 MB/s
H-blank DMA, for transferring small data sets at the end of each scanline outside of the active display period
Multiplication and division registers
Two separate address busses driving the 8-bit data bus: a 24-bit "Bus A" for general access, and an 8-bit "Bus B" mainly for APU and PPU registers
The 65816 core is an enhanced version of the 6502, which is an 8-bit core. So, 65 in the name means it's compatible to the 6502, and 816 means the possibility to switch between 8- and 16-bit register sizes.
So, in the beginning there was the 6502. It's used in personal computers and consoles such as the Apple IIe, the Commodore PET, the Atari 2600, the Commodore 64, the original NES and Bender from Futurama.
Then, there was the 65816, which was an enhancement with higher clock rates and bigger register sizes. It's used in the Apple IIG and the SNES. The 65816 is (almost completely) backwards compatible with the 6502 and even has an emulation mode that is automatically initialized when the processor is powered up.
So, to understand the 65816, we need to understand the 6502, and luckily, many many people have spent lots of time doing so. There's even this visually animated emulator for the 6502: http://www.visual6502.org/ It only exists because Michael Steil took the lid off of the 6502 and actually made microscopic photographs of this legendary device. He did a talk on this work and you can watch it here: (bottom)
When I learned that the SNES CPU is actually an enhanced 6502, it was even more reason for me to do this project because I often fantasized abpout programming a 6502 in real life. :) Programming doesn't get anymore "bare-bone" than 6502. ;) So, the 65816 takes opcodes which you can find in tables, but most people used to program this chip in assembler which is then "assembled" to opcodes that are directly readable by the processor. These machine codes are stored in external memory, because the 5A22 has no built in memory. This external memory is actually just the cardtridge! When the 5A22 is powered up, it loads the first adress on its 24-bit adressbus, this bus is hardwired to the ROM, which reacts and presents the first opcode for the processor. After processing the first command, the processor now wants another operand or increments the program counter to read the next command, which again is presented on the adress bus and the ROM reacts and puts data on the 8-bit databus. So, this is exactly the bahaviour that we will have to simulate for the processor! Doesn't sound too complicated, right? Read an address, look up the data on this address and present the data! Well... turns out it's actually really that easy! :D Yeah well almost, as you will see.
Someone has actually already done something similar with an ARM processor. You can find this project here: https://dhole.github.io/post/gameboy_cartridge_emu_1/ It's really interesting and you can already see some problems that we will encounter much later in this project.
If you want to read more about the 65816, such as the opcodes or other technical details, I can recomend this page on the superfamicom wiki: https://wiki.superfamicom.org/65816-reference They also link to datasheets by Western Design Center (WDC) for...
Some basic investigation whether a project is even possible to realize is obviously mandatory if it's something you've never done before. Most of the times I get stuck in this phase though, so this time I wanted to limit myself to essential things and not research too much (which turned out to be a good idea). In this first part I want to talk about how sounds on the SNES are created, but don't worry, this one is going to be a short one. :)
Most of the work is done by the synthesizer chip - the SPC-700. This is actually already a co-processor for the main CPU on the SNES. It runs at roughly 1MHz, has access to 64KB of memory and has 4 8-bit I/O ports to transfer data to/from the SNES. The sound is composed of 8 channels of compressed sample synthesis in 8bit resolution. This means, the SPC-700 laods a short sample of audio into one of the 8 channels and then it reads from its own program memory what it's going to do with this sample. At the most basic level, it's going to repeat this one sample in an infinite loop, so if you store a sine wave of 1kHz in a channel, you're going to hear a sine wave sound at 1kHz. So, to "play" this sound musically, the SPC is also able to "tune" and "pitch shift" this sample. Together with an envelope shaper with paramters such as attack and release, it's already possible to mimic simple instrument sounds like on old 80s keyboards.
If you want to know more about wavetable synthesis, I can highly reccomend this short video about the basics: (it's attached at the bottom)
So basically, the SPC-700 needs samples and instructions to generate music. Turns out, there's already a small scene of people out there who build little devices to play such programs which are extracted from SNES ROMs. It's awesome! Check it out right here: http://snesmusic.org/files/spc700.html
But the APU (audio processing unit) of the SNES not only consists of the SPC-700. There's also a little DSP (digital signal processing) chip involved. When I said earlier that the SPC-700 is doing the pitch shifting, I was actually lieing (for simplicity reasons), because the pitch shifting is actually done by this DSP. It also has some neat features like echo effects and a white noise generator which will come in handy much later if we want to produce some rhythmic sounds. The DSP is accessed through the SPC-700, which makes the whole thing a little bit more complicated. But since both chips are fairly straight forward (I hope), everything will be fine... ;) Well, the SPC-700 has two registers which are directly wired to the DSP. So one register holds adresses for the DSP and one holds instructions.
Accessing both the SPC-700 and the DSP, again, is done through the main CPU. So next post will cover one of the greatest CPUs of all time: The 65816! If you've never heard of it before, I can tell you it's not much more than a 6502, which was the chip in legendary computers such as the Apple II, the Commodore 64 and the original Nintendo Entertainment System! <3
In the last part of "Is it even possible?" we're even going to write a little ROM file that generates sound and works on an emulator, so stay tuned!! This was the first time that I wrote my own ROM file for a vintage gaming console and I can tell you it's quite fascinating! :)
Use the wavetable synthesizer in the SNES as a musical instrument
Develop a cartridge for this purpose that everyone who owns a console can use
Not to damage or harm any consoles involved
The SPC-700 and the DSP by Sony in the Super NES
The idea is to build a cartridge with some potentiometers and buttons that control the synthesizer chip in the SNES. To do so, I developed a co-processor cartridge (similar to a Star Fox cartridge) that reads the potentiometers and buttons, generates data and instructions and sends it to the main processor of the SNES (which is a 65816).
The wavetable synth will be fed with some wave samples at boot time and start oscillating. The potentiometers are used to control the frequency and volume of these sounds. This is very similar to a modern drone synthesizer!
JMT Synths NOSC-12 - a typical drone synthesizer
I got the idea for this project when I saw a video about how music on the SNES was made back then. I think it's really fascinating and since I am a big fan of synthesizer instruments in general, I wanted to be able to use this wavetable chip on the SNES without the need to write code for a cartridge or whatever. I want to use this chip exactly like an instrument... live!
Another inspiration is the video by ben7 called "reverse engineering the NES to give it super powers". Mr. Ben7 used a raspberry PI to push some better graphics through the NES, and in principle, I am doing the same here. I am using a co-processor to control the audio generation of the SNES.
The videos are attached at the bottom of this post...
Well... so far so good. The project is pretty far already but I will post progress updates everyday as if I am doing it at the moment, so it's more fun to follow the log here.
I honestly hope that I made my intentions clear and whoever reads this is almost as excited about this as I am!! :D I can't get my mind off of this project at the moment! :)
So here are the videos that inspired me, have fun!!