top of page

MicroZed Chronicles: Delta Sigma DAC Part One

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



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

0 comments

Comments


bottom of page