One of the things I love about all AMD FPGA and SoC devices is the inclusion of the XADC / Sysmon ADC. This allows us to monitor not only the FPGA / SoC power and temperatures, which can be very useful for board bring-up, safety, and security applications, but also several analog signals on the board. This can save on BOM costs and enables tighter integration.
Sometimes, along with being able to monitor the analog signals, we may want to generate analog signals as well. In this case, we can use either an external DAC component or leverage the logic capabilities of the FPGA to generate an analog output using the FPGA I/O and a simple RC filter externally.
To do this, we use an approach called delta-sigma modulation (sometimes called sigma-delta). This technique enables us to create a one-bit ADC or DAC. In this blog, we are going to examine how we can create a simple one-bit DAC using the FPGA I/O.
The delta-sigma is a one-bit DAC that requires only a simple RC filter on the output to recover the signal. To achieve this, the sampling rate of the signal is much higher than the input signal. The delta-sigma works by ensuring the average output level represents the average input level, generating a pulse proportional modulation (PPM) output signal.
A first-order delta-sigma converter has the following design:
We can implement this very simply in VHDL using the fixed-point package. All we need to do is implement an accumulator, strip off the MSB as the output, and then determine what the feedback value is to be fed back into the accumulator along with the next input signal.
For this example, I am going to create a 16-bit DAC and an associated test bench. The test bench will use the output from a Python script to determine the waveform we wish to sample.
First, let's look at the VHDL 2008 RTL code. I have used a standard logic vector input for the sample data along with a valid signal. This could be easily connected to an AXIS peripheral further up in the processing chain; just tie Tready high.
The use of standard logic on the ports enables me not to worry about type conversions in the architectural VHDL for larger projects. The value passed across in the port is treated as a signed 16-bit value internally, with a value between +/- 0.99999.
Inside the RTL, I have created two signals, both 20 bits long, and used the fixed-point package signed type. One of these acts as the accumulator and the other as the feedback signal from the one-bit DAC looking at the output. As the input will be between +/- 0.99999, I have leveraged the fractional element of the signal definition in the fixed-point package.
signal s_accumulator : sfixed(3 downto -16) := (others =>'0');
signal s_feedback : sfixed(3 downto -16) := (others =>'0');
The process itself is simple. If the sample valid input is high, the accumulator adds in the sample along with the feedback value. As the feedback value is meant to be subtracted, I inverted the values for the feedback to make it just an addition.
As we are using the fixed-point types, I needed to ensure the accumulator was sized correctly and used the resize option. This is because the size of the output needs to match the size of one of the inputs. I set the overflow style to wrap around should the accumulator saturate. To do this, I needed to add in the fixed-point type package.
v_accum_temp := resize (arg => (s_accumulator + to_sfixed(
i_sample_data,-1,-16)- s_feedback),
size_res => s_accumulator,
overflow_style => fixed_wrap);
The output is generated as a 0 when the value of the accumulator is less than 0 and 1 in other cases. The final determination is the feedback signal, which is -1 when the output is high and 1 when the output is low to account for the difference subtraction.
s_feedback <= to_sfixed(-1,3,-16) when o_dac_op = '1' else to_sfixed( 1,3,-16);
With the code written, the next stage is to create a test bench. This test bench will have 100 samples of a sine wave stored in an array. The array contents are generated by the Python script below.
import math
samples = 100
max_int = 65535 # Maximum value for 16-bit unsigned integer
# Compute scaled sine values
sine_values = [int((math.sin(2 * math.pi * i / samples) * 0.5 + 0.5) * max_int) for i in range(samples)]
# Generate VHDL formatted sine values for std_logic_vector type
slv_values = ',\n '.join(f'"{value:016b}"' for value in sine_values)
# Print the values in VHDL array initialization format
vhdl_output = (
"constant analog_wave : sine_array := (\n"
" " + slv_values + "\n"
");"
)
print(vhdl_output)
The test bench then just cycles through the contents of the array, applying the signal to the UUT and enabling us to look at the output. The complete code can be found here
Putting this together in Vivado and targeting a simple FPGA board such as the Arty S7 or Leonidas enables a simulation to be performed, which shows the Delta Sigma DAC performing as we would expect.
The next steps are to implement the design in hardware and create the reconstitution RC filters on some Veroboard, etc., and look at the frequency response. We will explore this next time!
Workshops and Webinars
If you enjoyed the blog why not take a look at the free webinars, workshops and training courses we have created over the years. Highlights include
Professional PYNQ Learn how to use PYNQ in your developments
Introduction to Vivado learn how to use AMD Vivado
Ultra96, MiniZed & ZU1 three day course looking at HW, SW and PetaLinux
Arty Z7-20 Class looking at HW, SW and PetaLinux
Mastering MicroBlaze learn how to create MicroBlaze solutions
HLS Hero Workshop learn how to create High Level Synthesis based solutions
Perfecting Petalinux learn how to create and work with PetaLinux OS
Embedded System Book
Do you want to know more about designing embedded systems from scratch? Check out our book on creating embedded systems. This book will walk you through all the stages of requirements, architecture, component selection, schematics, layout, and FPGA / software design. We designed and manufactured the board at the heart of the book! The schematics and layout are available in Altium here Learn more about the board (see previous blogs on Bring up, DDR validation, USB, Sensors) and view the schematics here.
Sponsored by AMD
Hi Adam,
I am working on an electronic Hammond organ (a pretty basic one though - just for fun) and was looking to use a SigmaDelta DAC to drive the output and was led to this page.
I have a few questions/remarks.
1) you chose the accumulator to be (3,-16) but (1,-16) suffices
2) the simulus_pkg.vhd (did you forget the 't'?) holds a unipolar sinus pattern, but the to_sfixed( data, -1, -16) interprets this as a bipolar signal.
To debug this I first added a 'simple' analog output to the test-bench (in fact a crude integrator adding 1 when the output is '1' else subtracting 1 when the output is '0'. This doesn't produce the expected sine wave. I then…