From Hamsterworks Wiki!
Please make sure that you have completed the earlier modules of FPGA course.
Having problems with your code? Do I need to change something? Want a reference solution to the project? Email me at "firstname.lastname@example.org", after removing the "nospam-" bit. I'll try to get back to you in a day or so.
Aims of module
- Learn how a simple one bit DAC works
- Build add in VHDL
- Use it to play audio waveforms from block RAM.
This module is largely based on http://www.xilinx.com/support/documentation/application_notes/xapp154.pdf
One bit (Delta Sigma) DAC
You are most probably familiar with Pulse Width Modulation (PWM) - It is when a signal of a constant frequency has its duty cycle modulated to generate different power levels. If a PWM signal is passed through a low pass filter you end up with an analogue voltage that is proportional to the duty cycle. PWM is used in power supplies, light dimmers and motor controllers and such.
Delta Sigma modulation is a lot like that, but without the constant frequency of PWM. It has a output that 'hunts' for the desired output value. A one bit DAC has only two output values (1 or 0), and it generates the value which when included in a running average brings it closest to the desired level:
- To generate a level of 0.5 the output will be "10101010101..."
- To generate 0.25 the output will be "000100010001...."
- To generate 0.66 the output will be "110110110110110".
All of these signals average out to the desired value but have different frequencies.
Um, that looks really hard to do
It's not that hard at all. For this example work in decimal to make it clearer, but implementation in binary is just the same.
To make a Delta Sigma DAC with 100 output levels you need an accumulator with two decimal digits, and you use the "carry to the hundreds" as the output. Just keep adding the desired output level to the two digits and the "carry to the hundreds" will be a stream of ones and zeros that averages to the desired level.
Here's a two decimal digit DAC generating the output of 33:
Of course there are a few little tricks:
- Do it quick enough so that at the highest frequency you have enough '1's and '0' for the resolution you need
- Careful design of the output filter is required for best performance
- Do not use all the DAC's range, as the spectrum of noise at either end is problematic
Rough back-of-the-envelope bandwidth and effective resolution calculation
If you need to produce signals at 22kHz have to use at least a 44kHz playback frequency. If the one-bit DAC runs at 25MHz there is a little of a five hundred output values (ones and zeros) to average over in 1/44000th of a second - giving you at best 9 bit resolution at that frequency.
Doing it in VHDL
Here is the code for an 8 bit DAC - It is pretty much a "count by 'n'" counter:
library IEEE; use IEEE.STD_LOGIC_1164.ALL; use IEEE.STD_LOGIC_ARITH.ALL; use IEEE.STD_LOGIC_UNSIGNED.ALL; entity dac8 is Port ( Clk : in STD_LOGIC; Data : in STD_LOGIC_VECTOR (7 downto 0); PulseStream : out STD_LOGIC); end dac8; architecture Behavioral of dac8 is signal sum : STD_LOGIC_VECTOR (8 downto 0); begin PulseStream <= sum(8); process (clk, sum) begin if rising_edge(Clk) then sum <= ("0" & sum(7 downto 0)) + ("0" &data); end if; end process;
Using a PMOD port
The PMODs on the Basys2 board have four signal wires from the FPGA, a ground and a 3.3V power connection.
For the JA header on the BASYS2 board the constraints are:
NET "JA<0>" LOC = "B2"; NET "JA<1>" LOC = "A3"; NET "JA<2>" LOC = "J3"; NET "JA<3>" LOC = "B5";
Firstly, make sure that you don't short the power pins. Shorting out ground and power upsets your USB port!
For this project connect a set of stereo earphones between pins 0 and pins 1 and the ground. To do this I used a header strip, 3.5mm socket and a length of wire:
If you pull the unused pins out of the header strip you might just be able to hold the 3.5mm jack in place at the correct time...
The inductive nature of the headphones/earphones proves to be a pretty good low pass filter for the high frequency signals so no additional components are needed - but if you want to you can include a suitable capacitor in series to prevent average DC voltage running through them.
NOTE TO PEOPLE NOT USING A PMOD PORT - The Digilent boards have a 200 ohm resister in series with the FPGA output pin. This makes the PMOD connectors somewhat protected against ESD, overvoltage and shorts. For this project it it also acts as a voltage divider reducing the DC bias and the peak to peak voltages that go through the headphones/earphones.
Project 13.1 - Wave file generation
In project 12.1 we hooked a block RAM to the LEDs, and used it to flash them. We can do the same to generate an audio waveform.
- Make a COE file containing the samples for a sine wave (something like "f(n) = int((sine(n*PI()/1024)+1)*100)+128" will give you values between 28 and 228 that you can use).
- Load it into project 12.1 and check that the lights look OK.
- To generate an audible tone we need to cycle through this somewhere around 400 times per second - so we need to use counter(15 downto 6) to address the ROM component. This should generate a tone of one cycle every 65536 clocks = 381.4Hz
- Add a 8 bit DAC to your project and connect the output to JA1 and JA2. Remember to add the appropriate constraints to your project!
- Build and download the design. If you connect your headphones you should have a tone!
- At the moment we can only generate one frequency. Design and try out ways to make different frequencies.
- The Spartan 3E-250 has 24K of on-chip memory. That's enough for 2 seconds of telephone quality 11kHz/8 bit audio....
- If you connect the two high address bits on RAM to switches you can have four different waveforms, each with 256 samples per cycle. e.g Square, Saw, Ramp, Sine.
- By right-shifting the samples you can control the volume - and with a 'wider' DAC you can keep the least significant bits. Remember to 'sign-extend' the sample when you shift it (e.g. y(8 downto 0) = x(7) & x(7 downto 0)).
- The design is quite lo-fi - very 8 bit!, Make the DAC into 16 bits, and changing the ROM to have a data width of 16 (you will also need a new '.coe' file with samples expanded out to match the range of the 16 bit values).
Ready to carry on?
Click here to carry on to the next module.