With most of the FPGA side of XulaBee now sorted, I thought I'd take a closer look at the audio side of things before attempting to fix the PCB design.
The Microbee supports a few different forms of audio output:
- Single bit output connected to a speaker.
- Parallel port DAC - 8 bit audio driven by a resistor ladder connected to the parallel port.
- SN76489 Sound Generator Chip
- Beethoven Sound Generator - based on the AY-3-8910 chip connected through the parallell port.
While there are a number of audio DAC Pmod's available, I figured something simpler would suffice for the humble 8-bit audio requirements of the Microbee. A Sigma-Delta DAC uses a single digital output to generate an analog output. It does this by outputting 1's and 0's in proportion to the level of the desired analog output, which is then filtered with a low-pass filter to produce the final analog output.
For FPGABee's implementation I used the same VHDL component and sample circuit as described on fpgaarcade.com and connected it to a set of iPhone headphones.
Once the DAC and RC filter were setup I first connected the regular speaker output through the DAC and while I was at it, added a register to control the volume:
-- Convert speaker out bit to audio signal level speaker_aout <= volume when pio_port_b(6)='1' else to_unsigned(0, 8);
Parallel Port DAC
The parallel port is almost as simple and just required connecting a register to port 0:
if z80_addr(7 downto 0)=x"00" and port_wr = '1' then pardac <= unsigned(z80_dout); end if;
Microbee's that had the optional sound chip had it connected to ports 0x10, 0x11, 0x12 and 0x13 (all 4 ports behave the same way). I was going to write my own implementation of the sound chip but found this excellent implementation.
Hooking it up was pretty simple:
sn76489 : entity work.sn76489_top GENERIC MAP ( clock_div_16_g => 1 ) PORT MAP ( clock_i => clktb_3_375, clock_en_i => clken_3_375, res_n_i => mbee_reset_n, ce_n_i => psg_ce_n, we_n_i => psg_we_n, ready_o => psg_ready, d_i => z80_dout, aout_o => psg_aout ); -- Write to ports 0x10, 0x11, 0x12, 0x13 psg_we_n <= '0' when port_wr='1' else '1'; psg_ce_n <= '0' when z80_addr(7 downto 2)="000100" else '1'; psg_wait <= '1' when psg_we_n='0' and psg_ce_n='0' and psg_ready='0' else '0'; -- PSG ready signal must be mapped to Z80 wait z80_wait_n <= not (ram_wait or psg_wait);
Bringing it all Together
Normally when combining audio signals you need to make sure that the combination of all the signals won't overload the output. In this case however I think it's unlikely that the speaker, parallel port DAC and sound generator will all be used at once, so I've just added all the audio signals together:
-- Send audio to DAC dac_i <= std_logic_vector(speaker_aout + pardac + unsigned(psg_aout + 128));
Adding a Preamp
As mentioned above, the output worked pretty well on the headphones but I wanted a way to route it to the audio inputs on my PC for recording and/or forwarding to my PC speakers. I happened to have an OPA-2314 audio op-amp spare from another project. I don't really know what I'm doing here but I hooked it up like this:
and managed to get a reasonably clean signal into my PC. It only works on the Microphone input at the moment - I'd like to figure out how to get a line-level signal, but haven't looked into it yet.
How's it Sound?
So the big question is how does it sound. Not too bad:
Eventually I'd like to do an implementation of the Beethoven sound module, but for now I have enough to sort out the circuitry for XulaBee's PCB before getting it made.