Making The 6502 Sing With A SN76489A PSG
Computers are, inherently, silent. Only the very earliest electromechanical computers made much noise. Most of the time this is a good thing. I don’t think we’d put up with our modern smartphones if they made constant clacking noises. When it comes to the human-machine interface though, it removes one of our core senses from the experience.
Anyone who works with mechanical systems knows that the sound of a machine is a good proxy for how well it’s running. This seems to be the idea behind the first computer sound output. By connecting a speaker to various lines inside the computer, you could hear the program flow. Programs made distinctive noises which could be used to tell what they were doing, in absence of a terminal or front panel. Even today we know the sounds of the POST beep, the error alert, and the sudden silence that indicates the latest shoddily released “AAA” game has crashed again.
It didn’t take long for bored programmers to start using these simple diagnostic tools as musical instruments. This revealed some serious limitations. Diagnostic sounds only needed to be simple monophonic beeps. Under that situation it’s totally okay to just connect a speaker to a generic I/O line, directly controlled by the CPU. Making complex music with this kind of system is… challenging to say the least. Particularly if you want to do something else with the CPU. Like run a game. As computers shifted towards multimedia entertainment machines, programmable sound generators (PSGs) were invented to relieve the CPU. They are the predecessors of modern sound hardware.
One of the few PSGs to survive en mass to the present day is the SN76489. You can get them dirt cheap off eBay and the like. Unless you want to try building your own PSG (not that difficult, just tedious), the SN76489 is probably the easiest way to experiment with sound on a small computer. There’s a couple of caveats to this fussy old chip, but it’s pleasingly straightforward in all other respects.
SN76489A Programmable Sound Generator
Or the “SN76489 Digital Complex Sound Generator” according to Texas Instruments. I prefer calling the 76489 a PSG because that better differentiates it from other sound synthesis ICs from Texas Instruments like the SN76477 which is completely different and better suited as a stand-alone sound effect generator than a computer driven synthesizer. Despite being completely different, the SN76477 is also explicitly called a “complex sound generator” by TI. Don’t you just love it when that happens?
The good news is that the 76489 is a very straightforward IC to use. Problems exist, yes, but they’re pretty manageable on the whole. Parallel I/O ICs (e.g. the 65C22 VIA) help a lot.
76489 Internal Architecture
Inside the 76489 datasheet are some very detailed diagrams. This is a pretty good visual explanation of how the chip works. It’s also a pretty good basis for designing your own PSG.
Inside, the 76489 is little more than a set of programmable counters.
Following the diagram, the clock is first divided down by 16. Note that there are two variations of the 76489 (76489 vs 76489A) that differ only in this stage. The A parts have the 16x divider, while the standard parts only have a 2x divider. A parts have a maximum input frequency of 4MHz, while the standard ones top out at 0.5MHz.
After prescaling, the clock is distributed to the four channels. Tone channels are a simple down counter that loads their assigned value and produces a carry out when the count hits zero. Carry out is squared off by a 2x divider. Tone is produced indefinitely; the counter only needs to be loaded once. Noise is produced using a 15 bit LFSR, which uses the same squaring divider on the output. There are unique divider options for the noise channel, including the output from tone channel three.
All four channels have their own attenuator which is controlled by a four bit value. Any combination is valid; ‘1111’ is used to shut a channel off entirely. Higher values means quieter output. These attenuated outputs are then summed in a standard op-amp mixer. Output current tops out at ~10mA which is enough to drive a small speaker. An external amplifier may be required.
You’ll notice that this architecture is incredibly simple. At one point I was considering building my own PSG based on the 76489, but decided against it on practicality grounds. Since everything but the final attenuation/mixer stage is made using simple digital techniques, a medium sized PLD could easily implement something similar. 64 macrocells ought to be more than enough. Mixing is done using a basic op-amp summer; attenuation can be a multiplying DAC or digital potentiometer.
76489 External Connections
The only big catch with the external interface is that TI decided to declare D0 the MSB, a vile affront to basic common sense that still occasionally surfaces today. Under NO circumstances should the least significant digit be given the most significant index! This means to connect to almost every common computer, you need to reverse the order of the bus connections. Failing to do this will not ruin the system, but your software will become significantly more complex. Be careful though, some schematics for the 76489 show the pins in little-endian (correct) order. Double check those pin numbers!
A minor catch is that the IC pinout is badly arranged, with poor grouping. I’d bet good money on a lot of early PCB designers being driven mad by the unwillingness of IC designers to consider their needs. In this case, the 76489 is simple enough it doesn’t cause too much trouble.
This isn’t the worst IC layout I’ve ever seen, but it’s up there. As for the numbering of the bits, hardware is almost universally little-endian. Big-endian hardware is a surefire way to screw people up.
From a bus-design point of view, the 76489 is very slow and uses an asynchronous interlock. Every operation requires a 32 cycle delay where the data presented to the PSG must remain stable. This is in PSG cycles, not CPU cycles, so running the 76489 slower will slow the interface speed proportionally. Conversely, speeding the CPU up will actually make the interface seem even slower because you’re wasting more CPU cycles. Delays of a few tens of cycles are reasonable- hundreds to thousands of cycles not so much.
Rather than try to connect directly to a high speed bus, it makes more sense to use a dedicated I/O port. That way, the slow 76489 can take all the time it wants, while the CPU can do something else in the interim. Microcontrollers always have at least one I/O port built in; microcomputers must use a parallel I/O chip like the 65C22 VIA or discrete logic equivalent.
Power is strictly +5V, drawing up to 50mA. Inputs are TTL-like, but with far less current involved. Any 5V logic gate ought to be able to drive the inputs.
Connecting To The 6502
Since it’s very slow with an asynchronous interlock, the 76489 is an ideal match for the 65C22 VIA. Both VIA ports can be programmed to automatically generate delayed write strobes and interrupt when the PSG is ready again.
Connecting the PSG is a straightforward affair. For systems with a clock faster than 4MHz, add some external dividers or a dedicated PSG clock. The 65C22 can output square waves up to half the system clock frequency using PB7, which may be a viable option. A similar trick can be played with the serial port if those pins are available. For a simple 6502 computer (like my own development board) it’s as easy as it gets:
Connecting the 76489 physically was way faster than drawing the schematics. Mind you, the physical wiring falls apart in ways the schematic never will. One good bump is enough to ruin everything.
This is probably the easiest way to connect a 6502 to the 76489 PSG. Direct connection via the system bus is simpler, yes, but the slowdown inherent in the process is intolerable from a programming perspective. Sadly the VIA doesn’t have a FIFO buffer that would allow easy multi-byte updates. You still have to wait 32 cycles for every operation. Using the VIA just makes programming a bit easier since the CPU doesn’t have to stand still for those 32 cycles.
Initial Tests
No discrete 76489 has any reset hardware. That means when you turn it on, something comes out of the speaker. While an annoyance during normal operation, it’s a valuable test signal. If the output is sounding, the input is working. All you need for this test is a small speaker/amplifier and an input clock.
I am mentioning this because I seem to have been shipped some dud chips. Out of five ICs, only two seem to be working. The other three don’t work at all. They don’t even draw supply current! I have no explanation. They’re just DOA. Buyer beware, I guess. Were it not almost a year since I ordered them, I’d ask for some kind of concession.
Once the hardware failure was solved, I could move directly on to testing. Controlling the 76489 is straightforward, assuming you corrected the order of the data lines. It took a while to get the 65C22 VIA set up properly. Once I figured out the magic settings (Hint: PCR = $08), everything Just Worked. All you need to do is write the byte to PORTA and the VIA handles the rest.
These charts show all the controls bits you need to know. Keep in mind that ‘0’ responds to the most significant bit, because TI was having a funny day back in 1979.
Things of note I discovered while experimenting:
- Writing to the noise control channel silences the output. Writing to the noise attenuator channel restarts the output. This seems to be a “feature”, presumably to stop the LFSR from locking up.
- Setting channel frequency to $000 does not stop the tone, as some sources say. Presumably this is a difference in implementation between 76489 variants.
- Setting the noise generator to “periodic” mode simply turns it into a divide-by-8 stage. Useful for generating low frequency tones, or just as a fourth square wave “voice”.
- Modular Monitor/the SBDS are still quite buggy. Occasionally everything locks up or fails to read/write the correct value. That isn’t relevant to the 76489, but it is relevant to testing.
Test Drivers
Making the PSG produce noises is easy. Making it produce meaningful noises is hard. The good news is that save for highly experimental avant-garde music, pretty much every composition is strongly regular in structure. Computers deal with this regularity very well. The less good news is that the PSG requires lots of data to drive. Each channel needs of four bytes of data, which may or may not need to be updated in full or in part: frequency low, frequency high, attenuator, and duration.
Despite TI’s big-endian sin, the actual data for each channel is loaded little-endian. Yeah, I don’t know either. This isn’t a win for 6502s, or other little-endian computers, because TI aligned the data big-endian. In order to make this work, the least significant nibble is stored in it’s own byte, aligned to the right (little endian). Channel selection is done by adding the appropriate bits to the upper nibble, completing the byte. No special treatment is required for the upper six bits; just align to the right and pad them out with zeroes.
Attenuation is treated similarly- channel select in the upper nibble, value in the lower. I’m encoding the selector bits directly into the data bytes, so I can re-use the code for the frequency bytes with no modification. Duration is a special case but a simple one. Songs are just a long string of bytes, which makes things a little easier. Working within the 6502’s limitations, this code can handle 64 notes (including rests and effects). Longer songs require 16 bit pointers.
Only one channel is used- the others are silenced. Notes are selected by a pointer into a fixed length array. All other prep work was done prior to starting the program loop. That means this little program only has to deal with loading one channel, and counting a duration value. I added some complexity here and there, but I kept it reasonably simple.
EMBARRASSING EDIT: Somehow I managed to forget to add the source code! That’s been fixed now.
;MUSIC demo. Uses 76489 PSG with 65C02 using 65C22 VIA for control
;Prior to this program running, all other tone channels were silenced
;Written for W65C02; uses WAI to synchronize notes
START:
LDA #$09
STA $D00C ;Enable PORTA write shake
LDA #$C0
STA $D00E ;Enable timer 1 interrupts
STA $D00B ;Enable continuous output, continuous interrupts
LDA #$7A ;Load timer 1 for 128 Hz
STA $D005
LDA #$12
STA $D006 ;Latch high write starts timer
WAI ;Sync to prevent timing troubles
REPEAT:
LDY #$00
LOOP:
LDA #$9F ;Mute the note first
STA $D001
WAI
DELAY0:
LDA $D00D ;Load the low nibble
BIT #$02
BNE DELAY0
LDA NOTES, Y
STA $D001
INY
DELAY1:
LDA $D00D ;Load the high nibble
BIT #$02
BNE DELAY1
LDA NOTES, Y
STA $D001
INY
DELAY2:
LDA $D00D ;Load the attentuator
BIT #$02
BNE DELAY2
LDA NOTES, Y
STA $D001
INY
LDA NOTES, Y
DUR:
WAI ;Wait for the timer to expire
BIT $D004 ;Reset timer. BIT does not clobber A!
DEC A
BNE DUR
INY
CPY #124 ;Note count * 4
BEQ REPEAT
BRA LOOP
Note the use of the WAI instruction. Right now I don’t have interrupts working. Instead I’m exploiting a quirk with the W65C02- WAI just sets a latch that holds RDY low. IRQ resets said latch. This happens even if interrupts are disabled. Ergo you can synchronize to an external event, using IRQ, without actually having to go through all that interrupt stuff. Very handy! Only downside is that WAI only exists on 65C02 variants, and not all of them.
Test Results
It’s December as I write this, so I figured I’d use a Christmas song for testing. “We Wish You A Merry Christmas” is a classic song with a very simple melody. Handily it also consists of exactly 31 notes, making pointer calculations much easier. I added along rest at the end to pad out to 32 notes.
I couldn’t quite get this done before Christmas, but it is still December, so it still counts. That’s my excuse and I am sticking to it.
Getting this demo to run was hard. Most of the work went into organizing the 65C22 properly. There’s a lot going on in there, and WDC’s datasheets aren’t particularly well written. I am considering making a custom 65C22 “cheat sheet” because wow is it hard to keep track of everything.
The other problem was dusting off my musical knowledge after 20 years of disuse. I’m still pretty good at it, but “playing” the SN76489 is very different to an actual instrument. Mathematically speaking the song is correct. Artistically speaking some of the notes sound flat. Flat, as in, not quite right. I tweaked a few notes to sound better, but I ran out of time to make it sound as good as I want.
MUSIC worked perfectly the first time I ran it. Once again my software works fine; setting up the hardware properly is what causes problems.
Note Calculator
Tucked away in the 76489 datasheet is this little equation:
With a list of note frequencies, you can laboriously calculate all the divider constants for up to 5 or so octaves. You will then likely have to convert them into hexadecimal to actually use them. I imagine this was very tedious in 1979, when even basic calculators were still pretty hard to get hold of.
You could do that, or you could use this handy little spreadsheet I whipped up as part of this project. It produces six octaves tuned to A440. Custom frequencies can be calculated from count values, as well as the reverse. Basic guards are included to prevent invalid calculations. I also added a comparison between the desired frequency and the best the 76489 can do.
Finishing Up
Sound in computer systems is one of those things we just kind of take for granted these days. Any modern computer is capable of very realistic sound reproduction. We don’t need things like the SN76489 anymore. We don’t even need dedicated sound hardware anymore! Even tiny microcontrollers can pull it off. Being pushed back to the limitations of the 76489 is a harsh reminder of how difficult even simple tasks can be.
Composing music for the SN76489 is quite a bit harder than for most instruments. You have four channels, all of which are managed independently, with most musical effects (e.g. tremolo) being emulated in software. Specialized software known as a “tracker” is used to coordinate everything. I cannot find any tracker for the 76489 that works on a modern PC, much less the 6502. Once again it will be necessary to build my own tools.
Something the 76489 lacks is the ability to play samples. Supposedly there’s a way to do it by setting a channel to ‘$000’ to freeze the counter then messing with the attenuation. Unfortunately I discovered that setting a channel to ‘$000’ does not stop the counter. Some variations of the 76489 have a mixing input on pin 9. Mine don’t. That means adding an analog mixing stage to the output is the only viable option.
Wrestling the 65C22 VIA into compliance proved- somewhat surprisingly- the biggest sticking point. Like I said, there’s a lot going on inside. WDC’s datasheet frustratingly lacks a quick reference guide. Compare that to the TL16C550, which has a single page that tells you what every bit in every register does at a glance. This project was a valuable tutorial for VIA handshakes, which would have been much easier to get my head around with a competently written datasheet.
Because I am absolutely terrified of making unnecessary changes to Modular Monitor, I did not burn the test program into ROM. Instead, I chose to write a very simple upload program that simply takes raw serial input and shoves it into memory. It works, and I will probably use it for all my “complex” 6502 programming from now on. Well, until I get a proper file system going at least.
2023 comes to a close- a rough year for many, including me. 2024 is shaping up to be even worse, somehow. I can’t fix everything (anything?) that’s wrong with the world today, but I can at least share the joy of building things.
This will be the last article until late January. I have lots I want to get done in early 2024, including some big 6502 projects.
Have a question? Comment? Insight? Post below!