This Verilog implementation is a replica of the classical SN76489 programmable sound generator. With roughly a 1400 logic gates this design fits on a single tile of the TinyTapeout.
The main goals of this project are:
- closely replicate the behavior and eventually the complete design of the original SN76489
- provide a readable and well documented code for educational and hardware preservation purposes
- leverage the modern fabrication process
A significant effort was put into a thorough Cocotb powered test suite test.py for regression testing and validation against the original chip behavior.
The module is parameterized and can match variants from the SN76489 family. The following parametrization options are provided:
- noise tapped bits
- tone counter and noise LFSR shift register size
- variable clock divider
The next step is to incorporate analog elements into the design to match the original SN76489 - DAC for each channel and an analog OpAmp for channel summation.
The ASIC for the SN76489 replica is built using the open source Skywater 130nm PDK and is part of multi project TinyTapeout.
Using 130nm process this SN76489 fits in 160x100 um. The design consist of roughly 1400 digital logic gates and of which 173 are data flip-flops storing 1 bit of data each. Logic takes 75% of the chip area. Total wire length is 46 mm!
Examine the layout of this chip in 3D!
- 3 square wave tone generators
- 1 noise generator
- 2 types of noise: white and periodic
- Capable to produce a range of waves typically from 122 Hz to 125 kHz, defined by 10-bit registers.
- 16 different volume levels
For a more detailed information on the inner workings of the chip, visit:
The SN76489 family of programmable sound generators was introduced by Texas Instruments in 1980. Variants of the SN76489 were used in a number of home computers, game consoles and arcade boards:
- home computers: TI-99/4, BBC Micro, IBM PCjr, Sega SC-3000, Tandy 1000
- game consoles: ColecoVision, Sega SG-1000, Sega Master System, Game Gear, Neo Geo Pocket and Sega Genesis
- arcade machines by Sega & Konami and would usually include 2 or 4 SN76489 chips
The SN76489 had many very similar variants, amongst them are SN76496, SN76494, SN94624 and TMS9919.
The SN76489 chip family competed with the similar General Instrument AY-3-8910.
This implementation is based on the results from reverse engineering efforts:
- Annotations and analysis of a decapped SN76489A chip.
- Reverse engineered schematics based on a decapped VDP chip from Sega Mega Drive which included a SN76496 variant.
- https://siliconpr0n.org/map/ti/sn76489an/
- https://github.com/emu-russia/SEGAChips/blob/main/VDP/PSG/Docs/PSG_Other.jpg
Simulated Verilog design can be fed with the stream of register values captured on a 8-bit computer in Video Game Music (VGM) format and output converted to .wav file. This process is recommended for both testing and enjoying 8-bit era music!
First, install the needed libraries
sudo apt install iverilog
pip3 install cocotb pytest
To record VGM file:
make MODULE=record VGM=../music/CrazeeRider-title.bbc50hz.vgm
cd ../output
For a reference you can find music recorded from the chip simulation:
- https://www.youtube.com/watch?v=ghBGasckpSY - Crazee Rider BBC Micro game
- https://www.youtube.com/watch?v=HXLAdA02I-w - MISSION76496 tune for Sega Master System
- Music from BBC Micro games
- Music from Sega Master System games
- Music from Tandy 100 games
- Sound effects from Sega SG1000 games
Note that currently only VGM file version 1.50 or higher are supported. You can convert older VGM files to 1.50 with https://github.com/simondotm/vgm-converter
Follow the instructions from Tiny Tapeout's Testing Your Design Guide and install required packages, first.
sudo apt install iverilog verilator
pip3 install cocotb pytest
First of all, run the test suite: make
from the \src
folder. make
will compile the Verilog source and launch cocotb
test suite.
cd src
make
There are a number of useful functions in test.py that simplify communication with the sound generator.
The following example sets up 440Hz (A4) note at the full volume on the 1st channel and white noise at the half volume:
await reset(dut)
await set_volume(dut, channel='1', 15) # Set `Channel 1` to maximum volume
await set_tone(dut, channel='1', frequency=440) # Play 440 Hz note on `Channel 1`
await set_volume(dut, channel='4', 8) # Set `Channel 4` (noise channel) to half volume
await set_noise(dut, white=True, divider=512): # Use on the 3 hardcoded divider values for noise generator
await set_noise_via_tone3(dut, white=True) # results in approximately 1 kHz white noise when chip is clocked at 4 MHz
There are several ways to connect this chip to the microcontroller and speaker.
One option is to connect off the shelf data parallel Digital to Analog Converter (DAC) for example Digilent R2R Pmod to the output pins and route the resulting analog audio to piezo speaker or amplifier.
Another option is to use the Pulse Width Modulated (PWM) AUDIO OUT pin with OpAmp+capacitor based integrator or capacitor based low-pass filter and a speaker:
uController SN76489
,---------. ,---._.---.
| | 4 Mhz ->|CLK SEL0|<-- 0
| GPIOx|----------->|D0 SEL1|<-- 0
| GPIOx|----------->|D1 |
| GPIOx|----------->|D2 |
| GPIOx|----------->|D3 | C1
| GPIOx|----------->|D4 | ,----||----.
| GPIOx|----------->|D5 | | |
| GPIOx|----------->|D6 | | OpAmp | Speaker
| GPIOx|----------->|D7 AUDIO| | |\ | /|
| GPIOx|----------->|/WE OUT |-----.---|-\ | C2 .--/ |
`---------' `---------' | }---.---||---| |
,--|+/ `--\ |
| |/ | \|
| |
--- ---
GND GND
Once playback schematics of the SN76489 are established, the controller program has to send data to the chip. SN76489 is programmed by updating its internal registers via data bus.
Below is a short summary of the communication protocol of SN76489. Please consult SN76489 Technical Manual for more information.
Command | Description | Parameters |
---|---|---|
1cc0ffff | Set tone fine frequency | f - 4 low bits, c - channel # |
00ffffff | Follow up with coarse freq | f - 6 high bits |
11100bff | Set noise and frequency | b - white/periodic, f - frequency control |
1cc1aaaa | Set channel attenuation | a - 4 bit attenuation, c - channel # |
NF1 | NF0 | Noise frequency control |
---|---|---|
0 | 0 | Clock divided by 512 |
0 | 1 | Clock divided by 1024 |
1 | 0 | Clock divided by 2048 |
1 | 1 | Use channel #2 tone frequency |
Use the following formula to calculate the 10-bit value for a particular note frequency:
n = clock_frequency / (32 * note_frequency)
For example 10-bit value that plays 440 Hz note on a chip clocked at 4 MHz would be:
n = 4000000 Hz / (32 * 400 Hz)
n = 284 = h11C
Hold /WE LOW while writing to the data bus.
10001100 - Set channel #0 tone 4 low bits to hC
00010001 - ---//--- tone 6 high bits to hC
10010000 - ---//--- volume to 100% (attenuation = 0)
11100100 - Set channel #3, noise type to white and frequency to 512 (NF1/NF0 bits = 0)
11111000 - ---//--- volume to 50% (attenuation = 8)
CLK ____ ____ ____ ____ ____ ____
__/ \____/ \____/ \____/ \____/ \____/ \___ ...
| | | | | |
| | | | | |
/WE_(inverted write enable) __ __ _______
\_____/ \______/ \______/ \______/ \______/ *
^
D0..D7_______ ________ ________ ________ ________ |
_/10001100\/00010001\/10010000\/11100100\/11111000\_|______
chan#0 chan#0 chan#0 chan#3 chan#3 |
tone=h??C =h11C atten=0 div=16 atten=8 |
h011C = 440 Hz /16 = ~1 Khz |
white noise |
|
noise restarts
after /WE goes high and
there was a write to noise register
This Verilog implementation is a completely digital and synchronous design that differs from the original SN76489 design which incorporated analog parts.
While the original chip had integrated OpAmp to sum generated channels in analog fashion, this implementation does digital signal summation and digital output. The module provides two alternative outputs for the generated audio signal:
- digital 8-bit audio output suitable for external Digital to Analog Converter (DAC)
- pseudo analog output with Pulse Width Modulation (PWM)
Outputs of all 4 channels are exposed along with the master output. This allows us to validate and mix signals externally. In contrast the original chip was limited to a single audio output pin due to the PDIP-16 package.
This implementation produces unsigned output waveforms without DC offset.
/CE, chip enable control pin is omitted in this design. The behavior is the same as if /CE is tied low and the chip is considered always enabled.
Unlike the original SN76489 which took 32 cycles to update registers, this implementation handles register writes in a single cycle and chip behaves as always READY.
The original design employed 2 phases of the clock for the operation of the registers. The original chip had no reset pin and would wake up to a random state.
To make it easier to synthesize and test on FPGAs this implementation uses single clock phase and synchronous reset for registers.
A configurable clock divider was introduced in this implementation. Clock divider can be controlled through SEL0 and SEL1 control pins and allows to select between 3 chip variants:
- the original SN76489 with the master clock internally divided by 16. This classical chip was intended for PAL and NTSC frequencies. However in BBC Micro 4 MHz clock was employed.
- SN94624/SN76494 variants without internal clock divider. These chips were intended for use with 250 to 500 KHz clocks.
- high frequency clock configuration for TinyTapeout, suitable for a range between 25 MHz and 50 Mhz. In this configuration the master clock is internally divided by 128.
SEL1 | SEL0 | Description | Clock frequency |
---|---|---|---|
0 | 0 | SN76489 mode, clock divided by 16 | 3.5 .. 4.2 MHz |
1 | 1 | -----//----- | 3.5 .. 4.2 MHz |
0 | 1 | SN76494 mode, no clock divider | 250 .. 500 kHZ |
1 | 0 | New mode for TT05, clock div. 128 | 25 .. 50 MHz |
- https://github.com/OpenVGS/OPSG - Verilog
- https://github.com/dnotq/sn76489_audio - Verilog
- https://github.com/jotego/jt89 - VHDL
- https://github.com/mamedev/mame/blob/master/src/devices/sound/sn76496.cpp - C++ MAME
- https://github.com/digital-sound-antiques/emu76489 - C
- https://www.eevblog.com/forum/projects/sound-synthesiser-for-retro-computing - using only discreet 74-series logic ICs!
TinyTapeout is an educational project that aims to make it easier and cheaper than ever to get your digital designs manufactured on a real chip.
To learn more and get started, visit https://tinytapeout.com.