2nd Order Sigma-Delta Digital to Analogue Converter (SD-DAC) in Verilog - Page 1
Forum section: electronics / general

author: SMB784
author_rank: Frequent Contributor
timestamp: June 17, 2020, 05:36:17 pm
content: Howdy everyone!I have a hobby FPGA project that I need some help with. I am familiar with how to implement a 1st SD-DAC, and have successfully done so (I have included code for this below), but now I would like to convert this to a 2nd SD-DAC.I understand that I need to chain two 1st order DACs together by feeding the output of one 1st order SD-DAC + the next input bit into the input of the the other 1st order SD-DAC, but I am having trouble actually getting this to work, and I would really appreciate some pointers on how to get this going.I am implementing this on an Altera Cyclone V (model number 5CSEBA6U23I7) on a Terasic DE10 Nano boardHere is the code for my working 1st order SD-DAC in Verilog:Code:module delta_sigma_DAC#(parameter OW, // output word sizeparameter OS=4 // oversampling ratio, 2^7 = 128)(input wire i_clk, // input clockinput wire i_res, // input reset lineinput wire [OW-1:0] i_func, // input functionoutput wire o_DAC // output DAC bit);reg [((OW-1)+OS):0] DAC_acc; // DAC accumulatoralways @(posedge i_clk or negedge i_res)beginif (i_res==0)beginDAC_acc<={((OW-1)+OS){1'd0}}; // zeroes output on resetendelseDAC_acc<=DAC_acc[((OW-1)+OS)-1:0]+i_func; /* adds value ofi_func to DACaccumulatorregister */endassign o_DAC=DAC_acc[((OW-1)+OS)]; // outputs top bit of DAC accumulatorendmoduleHere is the code for my 2nd order SD-DAC that compiles and runs, but gives me garbled output:Code:module delta_sigma_DAC_2nd_order#(parameter OW,parameter [(OW+2):0] max,parameter [(OW+2):0] min,parameter [(OW+2):0] mid)(input wire i_clk,input wire i_res,input wire [OW-1:0] i_func,output reg o_DAC);reg [(OW+2):0] e2,e1,e0;always @(posedge i_clk or negedge i_res)beginif (i_res==0)begine0<={(OW+2){1'd0}};e1<={(OW+2){1'd0}};e2<={(OW+2){1'd0}};o_DAC<=0;endelse if(i_clk)begine1<=i_func+e1-e0;e2<=e2+e1[(OW+2)]>>1-e0;if ((i_func+(e1+e2-e0-e0))>mid)begine0<=(max<<(OW));o_DAC<=1'b1;endelsebegine0<=(min<<(OW));o_DAC<=1'b0;endendendendmoduleAny idea what I am doing wrong here?

author: SMB784
author_rank: Frequent Contributor
timestamp: June 28, 2020, 11:56:43 pm
content: So I have made a little progress, but the output still comes out very noisy and not really the way I want it.The code that I am using is presented below, and I have added screenshots of the signal output at (freq=1000 Hz, signal out = LP_out) and the RTL view of the synthesized logic:Code:module delta_sigma_DAC_2nd_order#(parameter OW,parameter OS=4)(input wire i_clk,input wire i_res,input wire [OW-1:0] i_func,output wire o_DAC);reg [((OW-1)+OS):0] DAC_acc_1st;reg [((OW-1)+OS+1):0] DAC_acc_2nd;always @(posedge i_clk or negedge i_res)beginif (i_res==0)beginDAC_acc_1st<={((OW-1)+OS){1'd0}};DAC_acc_2nd<={((OW-1)+OS){1'd0}};endelsebeginDAC_acc_1st<=DAC_acc_2nd[((OW-1)+OS)-2:0]+i_func;DAC_acc_2nd<=DAC_acc_2nd[((OW-1)+OS)-1:0]+DAC_acc_1st;endendassign o_DAC=DAC_acc_2nd[((OW-1)+OS)];endmodule

author: hamster_nz
author_rank: Super Contributor
timestamp: June 30, 2020, 12:45:21 am
content: Hi SMB784I've been wanting to implement a Delta Sigma 2nd order DAC for a while, but never really understood what is required.I had a quick look athttps://www.beis.de/Elektronik/DeltaSigma/SigmaDelta.html, esp figure 6.Assuming that the ADC is doesn't actually exist physically, but just converts the '1' or '0' to the positive and negative limits, I can't see how your code maps onto it - you seem to be missing the second difference stage (the output of integrator 1 less the current output value);Oh, another thing is that logically the values in the accumulator of a "just keep adding and use the overflow as the output" DAC seem to be slightly different to what is in diagrams.Stripping the overflow is doing half of the first difference - e.g. for an 8-bit accumulator, stripping the overflow equivilent to subtracting the output value of 256 in the first difference stage.It's not HDL, but here is some C code that seems to implement the 2nd order DAC and give a sensible bitstream for the value (in the range of +/-127) given on the command line.Code:#include <stdio.h>#include <stdlib.h>int main(int argc, char *argv[]) {#include <stdio.h>#include <stdlib.h>int main(int argc, char *argv[]) {int i;int output;int accum=0, accum2=0;int input_val;if(argc != 2) {printf("Must supply input value\n");return 0;}input_val = atoi(argv);for(i = 0; i < 1000; i++) {int output_val = (output==1) ? 128 : -128;accum += input_val - output_val;accum2 += accum - output_val;output = (accum2 > 0) ? 1 : 0;printf("%c", '0'+output);if(i%80 == 79||i==999) putchar('\n');}return 0;}Am I on the same page as you?

author: hamster_nz
author_rank: Super Contributor
timestamp: June 30, 2020, 09:50:26 am
content: I've had a hack at a 1st Order and 2nd Order 12-bit Sigma-Delta DACs, both running from the same ~2kHz Sine wave samples (12-bit samples at 50kHz).Hooked them up to my Analog Discovery 2, via a RC low pass filter to get the spectrum.Sadly the code is in VHDL not Verilog, but below is the entire design. It's not suppose to be the most effective or stylish code, or use minimal resources (eg the accumulators are oversized), but it does work and should provide a workable template for your Verilog implementation. Note that it uses VHDL variables in the DAC processes, which have the "update now, not on the clock edge" semantics.Views / opinions?Code:library IEEE;use IEEE.STD_LOGIC_1164.ALL;use IEEE.NUMERIC_STD.ALL;entity top_level isPort ( clk : in STD_LOGIC;dac1 : out STD_LOGIC;dac2 : out STD_LOGIC);end top_level;architecture Behavioral of top_level issignal sample_count : unsigned(5 downto 0) := (others => '0');type a_samples is array (0 to 47) of signed(11 downto 0);signal samples : a_samples := (to_signed( 66,12), to_signed( 199,12), to_signed( 329,12),to_signed( 452,12), to_signed( 568,12), to_signed( 675,12),to_signed( 769,12), to_signed( 851,12), to_signed( 918,12),to_signed( 969,12), to_signed( 1004,12), to_signed( 1021,12),to_signed( 1021,12), to_signed( 1004,12), to_signed( 969,12),to_signed( 918,12), to_signed( 851,12), to_signed( 769,12),to_signed( 675,12), to_signed( 568,12), to_signed( 452,12),to_signed( 329,12), to_signed( 199,12), to_signed( 66,12),to_signed( -67,12), to_signed( -200,12), to_signed( -330,12),to_signed( -453,12), to_signed( -569,12), to_signed( -676,12),to_signed( -770,12), to_signed( -852,12), to_signed( -919,12),to_signed( -970,12), to_signed(-1005,12), to_signed(-1022,12),to_signed(-1022,12), to_signed(-1005,12), to_signed( -970,12),to_signed( -919,12), to_signed( -852,12), to_signed( -770,12),to_signed( -676,12), to_signed( -569,12), to_signed( -453,12),to_signed( -330,12), to_signed( -200,12), to_signed( -67,12));signal count : unsigned(9 downto 0) := (others => '0');signal sample : signed(11 downto 0) := (others => '0');signal dac1_out : std_logic := '0';signal dac1_accum : signed(15 downto 0) := (others => '0');signal dac2_out : std_logic := '0';signal dac2_accum1 : signed(15 downto 0) := (others => '0');signal dac2_accum2 : signed(15 downto 0) := (others => '0');begindac1 <= dac1_out;dac2 <= dac2_out;dac1_proc: process(clk)variable new_val : signed(15 downto 0);beginif rising_edge(clk) thenif dac1_out = '1' thennew_val := dac1_accum + sample - 2048;elsenew_val := dac1_accum + sample + 2048;end if;if new_val > 0 thendac1_out <= '1';elsedac1_out <= '0';end if;dac1_accum <= new_val;end if;end process;dac2_proc: process(clk)variable new_val1 : signed(15 downto 0);variable new_val2 : signed(15 downto 0);beginif rising_edge(clk) thenif dac2_out = '1' thennew_val1 := dac2_accum1 + sample - 2048;elsenew_val1 := dac2_accum1 + sample + 2048;end if;if dac2_out = '1' thennew_val2 := dac2_accum2 + new_val1 - 2048;elsenew_val2 := dac2_accum2 + new_val1 + 2048;end if;if new_val2 > 0 thendac2_out <= '1';elsedac2_out <= '0';end if;dac2_accum1 <= new_val1;dac2_accum2 <= new_val2;end if;end process;gen_samples: process(clk)beginif rising_edge(clk) thensample <= samples(to_integer(sample_count));if count = 999 thenif sample_count = 47 thensample_count <= (others => '0');elsesample_count <= sample_count +1;end if;count <= (others => '0');elsecount <= count + 1;end if;end if;end process;end Behavioral;

author: rstofer
author_rank: Super Contributor
timestamp: June 30, 2020, 02:58:49 pm
content: Nice use of the AD2, my favorite gadget. In particular, it does a terrific job on FFTs.This paper by Analog Devices will probably help me understand what is happening. The block diagrams, especially figure 6.8, seem useful.https://www.analog.com/media/en/technical-documentation/application-notes/292524291525717245054923680458171AN283.pdf

author: SMB784
author_rank: Frequent Contributor
timestamp: June 30, 2020, 06:08:42 pm
content: Quote from: hamster_nz on June 30, 2020, 12:45:21 amI think we are on the same page, but I have some questions about your implementation.The first question is how do you implement your oversampling bits? How many oversampling bits do you employ? Second question is what is the significance of the number 2048? Is that the max absolute size of your input signal?I'm not really sure why my first order DAC works without an explicit subtraction, but it does seem to work that way and I have verified the output (albeit noisy).I will try implementing your design in verilog and see where I get.

author: SMB784
author_rank: Frequent Contributor
timestamp: June 30, 2020, 08:16:19 pm
content: Quote from: hamster_nz on June 30, 2020, 09:50:26 amSo I couldn't get my verilog translation to produce an output, but I did add a small change to my original verilog code. Before I describe it, I will give a bit of background: my DAC outputs between 0 and 216, so instead of subtracting or adding 215, instead I add 0 or 216. The change I have implemented (seen in the code below) involves adding only the top bit of DAC_acc_2nd (which is what gets output to o_DAC) to DAC_acc_1st & DAC_acc_2nd, which should act as adding 0 or adding 216. This should take care of the DAC step in the diagram described in your reference (I have included an RTL diagram, as before).Code:module delta_sigma_DAC_2nd_order#(parameter OW,parameter OSR=4 // 2^OSR = oversampling ratio)(input wire i_clk,input wire i_res,input wire [OW-1:0] i_func,output wire o_DAC);reg [((OW-1)+OSR):0] DAC_acc_1st;reg [((OW-1)+OSR):0] DAC_acc_2nd;always @(posedge i_clk or negedge i_res)beginif (i_res==0)beginDAC_acc_1st<={((OW-1)+OSR){1'd0}};DAC_acc_2nd<={((OW-1)+OSR){1'd0}};endelsebeginDAC_acc_1st<=DAC_acc_1st+i_func+DAC_acc_2nd[((OW-1)+OSR)];DAC_acc_2nd<=DAC_acc_1st+DAC_acc_2nd[((OW-1)+OSR)];endendassign o_DAC=DAC_acc_2nd[((OW-1)+OSR)];endmoduleAt this point I have gotten quite close to the correct output. The output is periodic, and the square wave even looks like a good square wave (though with half the p2p amplitude and offset from 0 by half the DNR). The sine wave is antisymmetric around the oscillation node, the square and triangle waves are symmetric around the oscillation node, and the saw is asymmetric as expected. Now I just need to figure out what is causing this strange suboscillation behavior.Thanks again for your help so far, hopefully in one or two more iterations I will have this working. Once it is working, I will put it up on github so that the whole world has access to a working 2nd order SD-DAC in verilog.

author: hamster_nz
author_rank: Super Contributor
timestamp: June 30, 2020, 09:58:00 pm
content: Quote from: SMB784 on June 30, 2020, 06:08:42 pmI don't implement oversampling. As far as I know (and I may be very wrong), in this scheme oversampling would be to take the the stream of incoming samples, buffer them, and then run a sensible interpolation filter over them to generate extra "in between" samples, that would be feed into the Sigma Delta DAC.i guess the more formal way would be this process:- Take the stream of samples (a,b,c, d)- Insert three zeros between each sample (a,0,0,0,b,0,0,0,c,0,0,0,d,0,0,0....)- Run that through a low pass filter with gain of 4x to generate new samples(a0, a1, a2, a3, b0, b1, b2, b3, c0, c1, c2, c3, d0.....)Feed that into the DAC at 4x the original sample rate.QuoteI'll try to implement it in Verilog too later tonight.The +2048 and -2048 constant are the value represented by a '1' on the output and a '0' on the output. The input samples short be within that range (i.e. it is a 12 bit input value)

author: hamster_nz
author_rank: Super Contributor
timestamp: July 01, 2020, 01:15:30 am
content: It was raining at lunchtime, so I gave EDA Playground a crack. This seems to simulate OK. You will need to parameterize the widths of the inputs and registers.Code:module dac(input wire i_clk,input wire i_res,input wire [11:0] i_func, // A signed sample - ADJUST WIDTH HEREoutput wire o_DAC);reg this_bit;reg [15:0] DAC_acc_1st;reg [15:0] DAC_acc_2nd;assign o_DAC = this_bit;always @(posedge i_clk or negedge i_res)beginif (i_res==0)beginDAC_acc_1st<=16'd0;DAC_acc_2nd<=16'd0;this_bit = 1'b0;endelsebeginif(this_bit == 1'b1)beginDAC_acc_1st = DAC_acc_1st + i_func - 2048; << ADJUST CONSTANT TO 2^(n-1) of input widthDAC_acc_2nd = DAC_acc_2nd + DAC_acc_1st - 2048; << ADJUST CONSTANT TO 2^(n-1) of input widthendelsebeginDAC_acc_1st = DAC_acc_1st + i_func + 2048; << ADJUST CONSTANT TO 2^(n-1) of input widthDAC_acc_2nd = DAC_acc_2nd + DAC_acc_1st + 2048; << ADJUST CONSTANT TO 2^(n-1) of input widthendend// When the high bit is set (a negative value) we need to output a 0 and when it is clear we need to output a 1.this_bit = ~DAC_acc_2nd; << ADJUST TO HIGHEST BIT OF DAC_acc_2nd.endendmoduleThe widths of the accumulators do not have any thinking behind them, however a few bits longer than the input should be fine.

author: SiliconWizard
author_rank: Super Contributor
timestamp: July 01, 2020, 04:53:58 pm
content: Quote from: hamster_nz on June 30, 2020, 09:58:00 pmYes that's about it!I have used just linear interpolation for this in a past project, and got pretty decent results. Linear interpolation is relatively cheap. Of course, more advanced filtering will yield better results but I've found that linear interpolation was often adequate if you're not after ultra-low distortion.Note that "oversampling" makes sense when you implement a DAC that must be fed with an arbitrary stream of samples at a given (and limited) sample rate.In case you're implementing some kind of DDS meant to generate fixed periodic signals, such as in your example, as long as you have enough room for a larger memory buffer for the period, you can just store samples at a higher sample rate than required and directly feed the modulator without adding any "oversampling" capability per se (filters can be expensive.)As a quick example: assuming you want to generate a 1kHz sine wave with a "rated" 48kHz sample rate and a "virtual" oversampling ratio of 256. You'd just need to store a whole period consisting of 48*256 = 12KBytes (for 8-bit samples, double that for 16-bit samples, etc), and clock your modulator @12.288MHz. Even small FPGAs often have more than enough block RAM for this, and you'd save many LUTs not implementing the oversampling. Likewise, this clock frequency is pretty modest even for low-end FPGAs as well, and with such a "high" oversampling ratio, the required analog low-pass filter can be as basic as a first-order RC filter.

author: SMB784
author_rank: Frequent Contributor
timestamp: July 01, 2020, 09:42:54 pm
content: Quote from: hamster_nz on July 01, 2020, 01:15:30 amOk, below is my parametrized implementation of your example. I had to move my version of this_bit (known as ADC_out) to inside the outer else block because the compiler didn't like it outside.Code:module delta_sigma_DAC_2nd_order#(parameter OW,parameter OSR=4 // 2^OSR = oversampling ratio)(input wire i_clk,input wire i_res,input wire [OW-1:0] i_func,output wire o_DAC);reg [((OW-1)+OSR):0] DAC_acc_1st;reg [((OW-1)+OSR):0] DAC_acc_2nd;reg ADC_out;always @(posedge i_clk or negedge i_res)beginif (i_res==0)beginDAC_acc_1st<={((OW-1)+OSR){1'd0}};DAC_acc_2nd<={((OW-1)+OSR){1'd0}};ADC_out=1'b0;endelsebeginif(ADC_out==1'b1)beginDAC_acc_1st=DAC_acc_1st+i_func-(2**(OW-1)-1);DAC_acc_2nd=DAC_acc_2nd+DAC_acc_1st-(2**(OW-1)-1);endelsebeginDAC_acc_1st=DAC_acc_1st+i_func+(2**(OW-1)-1);DAC_acc_2nd=DAC_acc_2nd+DAC_acc_1st+(2**(OW-1)-1);endADC_out=DAC_acc_2nd[((OW-1)+OSR)];endendassign o_DAC=ADC_out;endmoduleI have attached the simulated output, where values of sel=0,1,2,&3 correspond to 1 kHz sine, square, triangle, and sawtooth waveforms. These waveforms are garbled for half the cycle. I believe this is because the highest value is overflowing the integer register and causing problems, but I'm not entirely sure.Were you able to get reasonable output with this implementation?

author: hamster_nz
author_rank: Super Contributor
timestamp: July 01, 2020, 09:55:25 pm
content: Quote from: SMB784 on July 01, 2020, 09:42:54 pmThe only check I did on that Verilog code was that for a single (positive) input value both the VHDL and Verilog version produced the same bitstream.The accumulators should be big enough - you have the OSR bits longer than the input.I think the issue is that you input sample is defaulting to "unsigned"input wire [OW-1:0] i_funcCan you try something likeinput signed wire [OW-1:0] i_func?As I said, my Verilog skills are very limited... but it seems that this also be a pattern used to sign extend (in this case adding 8 copies of the 7th bit of 'extend'):extended[15:0] <= { {8{extend}}, extend[7:0] };

author: SMB784
author_rank: Frequent Contributor
timestamp: July 01, 2020, 11:11:26 pm
content: Getting closer (see attachment)! Still some overflow, and the sine lookup is clearly inverted, but its getting there. I made DAC_acc_1st, DAC_acc_2nd, and i_func signed wires to account for positive and negative values. I would like, however, for them to be unsigned because my DAC is implemented with a GPIO pin that as far as I know is only valid for values between 0V and 5V.

author: hamster_nz
author_rank: Super Contributor
timestamp: July 02, 2020, 12:25:38 am
content: Quote from: SMB784 on July 01, 2020, 11:11:26 pmAlrighty! Getting somewhere...That looks like the samples coming in are unsigned so there are two options, first and easy wrong one: Flip the high bit in in the incoming samples to convert them to signed valuesThe better one would beWhen you update the accumulators, subtract off of the half scale sample, as that is keeping with the thinking that the accumulators hold a signed value representing the error between the current and desired output. This is then consistent with the DAC is going "too high? output a 0, too low? output a 1".You will then be able to simplify the math by merging the constants, but that sort of hides what is going on.

author: SMB784
author_rank: Frequent Contributor
timestamp: July 02, 2020, 04:38:28 am
content: Just a question before I work on it again, why are you using blocking assignments rather than non-blocking assignments for updating your accumulators (= rather than <=)? It turns out that if you use non-blocking assignments, the output is garbage, but blocking assignments make it work. Was that a conscious choice on your part, or just serendipity that it worked out as it was supposed to?

author: hamster_nz
author_rank: Super Contributor
timestamp: July 02, 2020, 04:56:08 am
content: Quote from: SMB784 on July 02, 2020, 04:38:28 amIf it was non-blocking, the 2nd accumulator will be updated using the original value of the 1st accumulator, not the updated one.You can work around this and use non-blocking assignments, but it starts to look ugly.

author: SMB784
author_rank: Frequent Contributor
timestamp: July 02, 2020, 05:16:56 am
content: Quote from: hamster_nz on July 02, 2020, 04:56:08 amFair enough, that makes sense. Thanks for the explanation!

author: hamster_nz
author_rank: Super Contributor
timestamp: July 02, 2020, 11:29:15 am
content: So I coded up a few things and tested.This Verilog version gives identical results to the VHDL version - set up to use 16-bit signed samples.Code:`timescale 1ns / 1psmodule second_order_dac(input wire i_clk,input wire i_res,input wire [15:0] i_func,output wire o_DAC);reg this_bit;reg [19:0] DAC_acc_1st;reg [19:0] DAC_acc_2nd;reg [19:0] i_func_extended;assign o_DAC = this_bit;always @(*)i_func_extended = {i_func,i_func,i_func,i_func,i_func};always @(posedge i_clk or negedge i_res)beginif (i_res==0)beginDAC_acc_1st<=16'd0;DAC_acc_2nd<=16'd0;this_bit = 1'b0;endelsebeginif(this_bit == 1'b1)beginDAC_acc_1st = DAC_acc_1st + i_func_extended - (2**15);DAC_acc_2nd = DAC_acc_2nd + DAC_acc_1st - (2**15);endelsebeginDAC_acc_1st = DAC_acc_1st + i_func_extended + (2**15);DAC_acc_2nd = DAC_acc_2nd + DAC_acc_1st + (2**15);endthis_bit = ~DAC_acc_2nd;endendendmoduleI also worked out where the "average" option rather than the "decimate" option was on the Spectrum Analyzer's settings, and got better looking spectrums:

author: BrianHG
author_rank: Super Contributor
timestamp: July 02, 2020, 11:53:25 am
content: Quote from: SMB784 on July 01, 2020, 09:42:54 pmIf you are using System Verilog, you can use the keyword 'signed' when declaring regs and integers, eg:(snip from code I working with on another thread)Code://************************************************// geometry counters//************************************************reg signed [11:0] geo_x;reg signed [11:0] geo_y;reg signed [11:0] geo_xdir;reg signed [11:0] geo_ydir;It made all the rest of the coding easy rather than having to worry about 2's compliment for thing like adding/subtracting positive & negative numbers. Especially with IF(x<0) and While(x<0) where I wanted to check for boundaries where a triangle may go off the negative edge of the screen. Also, using the analog display waveform in the simulator wouldn't pop the line to the top. (In my simulator, I had to set the register waveform view as 'signed' register as well...)

author: SMB784
author_rank: Frequent Contributor
timestamp: July 02, 2020, 03:04:27 pm
content: Quote from: hamster_nz on July 02, 2020, 11:29:15 amSo I have parametrized everything you have created, and this gets pretty close to what i want (i'm still working with unsigned integers because my output only goes from 0 to 5V), although my sine function still has an issue with the -Pi input and +Pi input yielding inverted results:Code:module delta_sigma_DAC_2nd_order#(parameter OW,parameter OSR=4 // 2^OSR = oversampling ratio)(input wire i_clk,input wire i_res,input wire [OW-1:0] i_func,output wire o_DAC);reg [((OW-1)+OSR):0] DAC_acc_1st;reg [((OW-1)+OSR):0] DAC_acc_2nd;reg [((OW-1)+OSR):0] i_func_ext;reg ADC_out;always @*begini_func_ext={{OSR{i_func[OW-1]}},i_func};endalways @(posedge i_clk or negedge i_res)beginif (i_res==0)beginDAC_acc_1st<={((OW-1)+OSR){1'd0}};DAC_acc_2nd<={((OW-1)+OSR){1'd0}};ADC_out<=1'b0;endelsebeginif(ADC_out==1'b1)beginDAC_acc_1st=DAC_acc_1st+i_func_ext+{((OW-1)){1'd1}};DAC_acc_2nd=DAC_acc_2nd+DAC_acc_1st+{((OW-1)){1'd1}};endelsebeginDAC_acc_1st=DAC_acc_1st+i_func_ext-{(OW-1){1'd1}};DAC_acc_2nd=DAC_acc_2nd+DAC_acc_1st-{(OW-1){1'd1}};endADC_out<=DAC_acc_2nd[((OW-1)+OSR)];endendassign o_DAC=ADC_out;endmoduleHowever, there is still some overflow/underflow issues at the waveform maxima/minima, as you can see in the attachment. For some reason I cant seem to get this to go away. I am pretty stumped as to what is happening here.

author: hamster_nz
author_rank: Super Contributor
timestamp: July 02, 2020, 09:20:44 pm
content: Quote from: SMB784 on July 02, 2020, 03:04:27 pmJust change your code to this and it will most likely solve both issues.Code:if(ADC_out==1'b1)beginDAC_acc_1st=DAC_acc_1st+i_func_ext;DAC_acc_2nd=DAC_acc_2nd+DAC_acc_1st;endelsebeginDAC_acc_1st=DAC_acc_1st+i_func_ext-{OW{1'd1}};DAC_acc_2nd=DAC_acc_2nd+DAC_acc_1st-{OW{1'd1}};endI've just subtracted {(OW-1){1'd1}} from all equations, and then simplified.

author: SMB784
author_rank: Frequent Contributor
timestamp: July 03, 2020, 04:07:33 am
content: Quote from: hamster_nz on July 02, 2020, 09:20:44 pmSo I have implemented this, and it seems to be worse than the last iteration. I know this must be frustrating, and thanks for all your help so far, it has been really very useful. I wonder if maybe we are seeing different results because we are using different simulators (i'm using modelsim packaged with Quartus Prime Lite).

author: Bassman59
author_rank: Super Contributor
timestamp: July 03, 2020, 04:46:39 am
content: Quote from: SMB784 on July 02, 2020, 04:38:28 amIt's common to use variables in VHDL code to break up a long combinatorial statement (the right-hand-side of an assignment) into two or more intermediate assignments simply for readability. Those variables are evaluated in the order in which they appear in the code.

author: SMB784
author_rank: Frequent Contributor
timestamp: July 03, 2020, 06:15:15 pm
content: Ok, so I almost have it completely correct. The first problem with the sine wave was that in the sinewave routine I was already shifting its output so that it would have an average of half the dynamic range (2.5V), and this shifting routine clashed with the existing addition/subtraction in the 2nd order DAC, causing the +Pi input to be shifted out of phase with the -Pi input. Removing that shift routine fixed the sine wave.Second thing to say is that the accumulators need to be tuned by adding or subtracting a specific amount so that the waveforms take up the whole output dynamic range but dont overflow. you can see the value of my shift parameter in the code below under the register "mid_DNR":Code:module delta_sigma_DAC_2nd_order#(parameter OW,parameter OSR=8 // 2^OSR = oversampling ratio)(input wire i_clk,input wire i_res,input wire [OW-1:0] i_func,output wire o_DAC);reg [((OW-1)+OSR):0] DAC_acc_1st;reg [((OW-1)+OSR):0] DAC_acc_2nd;reg [((OW-1)+OSR):0] i_func_ext;reg [OW-1:0] mid_DNR={(OW-1){1'd1}}+{(OW-6){1'd1}};reg ADC_out;always @*begini_func_ext={{OSR{i_func[OW-1]}},i_func};endalways @(posedge i_clk or negedge i_res)beginif (i_res==0)beginDAC_acc_1st<={((OW-1)+OSR){1'd0}};DAC_acc_2nd<={((OW-1)+OSR){1'd0}};ADC_out<=1'b0;endelsebeginif(ADC_out==1'b1)beginDAC_acc_1st=DAC_acc_1st+i_func_ext+mid_DNR;DAC_acc_2nd=DAC_acc_2nd+DAC_acc_1st+mid_DNR;endelsebeginDAC_acc_1st=DAC_acc_1st+i_func_ext-mid_DNR;DAC_acc_2nd=DAC_acc_2nd+DAC_acc_1st-mid_DNR;endADC_out<=DAC_acc_2nd[((OW-1)+OSR)];endendassign o_DAC=ADC_out;endmoduleUnfortunately this appears to kill the square wave (see attached waveform output) for some reason that I am still trying to figure out. However, this implementation is almost totally correct at this point! Just gotta fix the square wave and then we are good. Thanks again everyone for all your help on this.

author: SiliconWizard
author_rank: Super Contributor
timestamp: July 04, 2020, 05:57:11 pm
content: It's hard to tell what exactly is wrong as we don't see your whole code (and one hidden part was already one source of your problems!)But from the look of what you get, I guess it might still be overflowing.Have you tried with a wider accumulator width?

author: SMB784
author_rank: Frequent Contributor
timestamp: July 04, 2020, 09:47:30 pm
content: Quote from: SiliconWizard on July 04, 2020, 05:57:11 pmFair enough, I have included the entire DDS NCO below.Top Module:Code:module DE10_Nano_Function_Generator#(parameter PW=26,parameter OW=16)(input wire clk,input wire res,/////////SW "3.3-V LVTTL"//////////input wire [1:0] SW,/////////LED "3.3-V LVTTL"/////////output wire [3:0] LED,///////////function out//////////////output wire func1_out,output wire func_out//output wire [OW-1:0] func_out);wire [1:0] func_select;LED_display display(.sel(func_select),.LED(LED));dds_nco_synthesizer dds(.DAC_clk(clk),.DAC_res(res),.func_select(func_select),//.LP_out(func_out).DAC_out(func_out));defparam dds.OW=OW;defparam dds.PW=PW;assign func_select = SW[1:0];endmoduleLED Display:Code:module LED_display(input wire [1:0] sel,output reg [3:0] LED);always @(sel)begincase(sel)2'b00: LED<=4'b0001;2'b01: LED<=4'b0010;2'b10: LED<=4'b0100;2'b11: LED<=4'b1000;endcaseendendmoduleDDS NCO module:Code:module dds_nco_synthesizer#(parameter PW,OW)(input wire DAC_clk,input wire DAC_res,input wire [PW-2:0] freq_tune, // need 2 samples per waveform at leastinput wire [1:0] func_select,//output wire [OW-1:0] LP_outoutput wire DAC_out);reg [(PW-1):0] phase;wire [(OW-1):0] sin_out;wire [(OW-1):0] sqr_out;wire [(OW-1):0] tri_out;wire [(OW-1):0] sw_out;wire [(OW-1):0] func;always @(posedge DAC_clk or negedge DAC_res)beginif (DAC_res==0)begin/* define PW-bit number "phase" with PW-bitdecimal 0 created using repetition operator */phase<={(PW){1'd0}};endelse/* add PW-bit number with decimal value 1 to "phase"by concatenating 1 bit decimal value 1 to end of(PW-1)-bit decimal 0 created using repetitionoperator *///phase<=phase+{{(PW-1){1'd0}},1'd1};phase<=phase+'d200000;//phase<=phase+freq_tune;endsine sine_out(.i_phase(phase[(PW-1):((PW-1)-(OW-1))]),.o_sine(sin_out));defparam sine_out.OW=OW;square square_out(.i_phase(phase[(PW-1):((PW-1)-(OW-1))]),.o_square(sqr_out));defparam square_out.OW=OW;triangle triang_out(.i_phase(phase[(PW-1):((PW-1)-(OW-1))]),.o_triang(tri_out));defparam triang_out.OW=OW;saw saw_out(.i_phase(phase[(PW-1):((PW-1)-(OW-1))]),.o_saw(sw_out));defparam saw_out.OW=OW;function_MUX MUX(.sel(func_select),.si(sin_out),.sq(sqr_out),.tr(tri_out),.sw(sw_out),.o_func(func));defparam MUX.OW=OW;delta_sigma_DAC_2nd_order DAC2(.i_clk(DAC_clk),.i_res(DAC_res),.i_func(func),.o_DAC(DAC_out));defparam DAC2.OW=OW;//CIC_LP_filter LPF//(//.i_clk(DAC_clk),//.i_res(DAC_res),//.i_filter(DAC_out),//.o_filter(LP_out)//);//defparam LPF.OW=OW;endmoduleQuarter wave sine synthesizer:Code:module sine#(parameter OW)(inputwire [(OW-1):0] i_phase,output wire [(OW-1):0] o_sine);/* (1<<(OW-2)) means 2^(OW-2) */reg[(OW-1):0]quartertable[0:((1<<(OW-2))-1)];initial$readmemh("16bit_QuarterSineLookup.hex", quartertable);wire[1:0]negate;wire[((OW-1)-2):0]index; // OW-1:0 bit register reduced in size by 4quarters (2^2)wire[(OW-1):0]tblvalue;// Clock #1assign negate = i_phase[(OW-1)];assign index=i_phase[(OW-2)]?~i_phase[(OW-3):0]:i_phase[(OW-3):0];// Clock #2assign tblvalue=quartertable;assign negate=negate;// Output Clockassign o_sine=negate?-tblvalue:tblvalue;// negate?-tblvalue-{(OW-1){1'b1}}:tblvalue+{(OW-1){1'b1}};// above works for 1st order DACendmoduleSquare wave synthesizer:Code:module square#(parameter OW)(inputwire [(OW-1):0] i_phase,output wire [(OW-1):0] o_square);assign o_square = {OW{i_phase[OW-1]}};endmoduleTriangle wave synthesizer:Code:module triangle#(parameter OW)(inputwire [(OW-1):0] i_phase,output wire [(OW-1):0] o_triang);assign o_triang = i_phase[OW-1]?~i_phase[(OW-1):0]:i_phase[(OW-1):0];endmoduleSaw wave synthesizer:Code:module saw#(parameter OW)(inputwire [(OW-1):0] i_phase,output wire [(OW-1):0] o_saw);assign o_saw = i_phase[(OW-1):0];endmoduleWaveform selection MUX:Code:module function_MUX#(parameter OW)(input wire [1:0] sel,input wire [(OW-1):0] si,input wire [(OW-1):0] sq,input wire [(OW-1):0] tr,input wire [(OW-1):0] sw,output reg [(OW-1):0] o_func);always @(sel,si,sq,tr,sw)begincase(sel)2'b00:o_func<=si;2'b01:o_func<=sq;2'b10:o_func<=tr;2'b11:o_func<=sw;default:o_func<=si;endcaseendendmodule1st Order Sigma-Delta Digital-to-Analog Converter (not used now that 2nd order is working):Code:module delta_sigma_DAC#(parameter OW, // output word sizeparameter OS=4 // oversampling ratio, 2^7 = 128)(input wire i_clk, // input clockinput wire i_res, // input reset lineinput wire [OW-1:0] i_func, // input functionoutput wire o_DAC // output DAC bit);reg [((OW-1)+OS):0] DAC_acc; // DAC accumulatoralways @(posedge i_clk or negedge i_res)beginif (i_res==0)beginDAC_acc<={((OW-1)+OS){1'd0}}; // zeroes output on resetendelseDAC_acc<=DAC_acc[((OW-1)+OS)-1:0]+i_func; /* adds value ofi_func to DACaccumulatorregister */endassign o_DAC=DAC_acc[((OW-1)+OS)]; // outputs top bit of DAC accumulatorendmodule2nd Order Sigma-Delta Digital-to-Analog Converter:Code:module delta_sigma_DAC_2nd_order#(parameter OW,parameter OSR=8 // 2^OSR = oversampling ratio)(input wire i_clk,input wire i_res,input wire [OW-1:0] i_func,output wire o_DAC);reg [((OW-1)+OSR):0] DAC_acc_1st;reg [((OW-1)+OSR):0] DAC_acc_2nd;reg [((OW-1)+OSR):0] i_func_ext;reg [OW-1:0] mid_DNR={(OW-1){1'd1}}+{(OW-6){1'd1}};reg ADC_out;always @*begini_func_ext={{OSR{i_func[OW-1]}},i_func};endalways @(posedge i_clk or negedge i_res)beginif (i_res==0)beginDAC_acc_1st<={((OW-1)+OSR){1'd0}};DAC_acc_2nd<={((OW-1)+OSR){1'd0}};ADC_out<=1'b0;endelsebeginif(ADC_out==1'b1)beginDAC_acc_1st=DAC_acc_1st+i_func_ext+mid_DNR;DAC_acc_2nd=DAC_acc_2nd+DAC_acc_1st+mid_DNR;endelsebeginDAC_acc_1st=DAC_acc_1st+i_func_ext-mid_DNR;DAC_acc_2nd=DAC_acc_2nd+DAC_acc_1st-mid_DNR;endADC_out<=DAC_acc_2nd[((OW-1)+OSR)];endendassign o_DAC=ADC_out;endmoduleCascaded integrator comb rolling average low pass filter (not used except for simulations):Code:module CIC_LP_filter#(parameter OW,parameter FB=7 // number of sample bits for rolling average filter)(input wire i_clk,input wire i_res,input wire i_filter,output reg [OW-1:0] o_filter);reg roll_data [0:2**FB-1];reg [OW+FB-1:0] store;wire [OW+FB-1:0] integrate=i_filter+store-roll_data[2**FB-1];genvar i;generatefor (i = 0; i < 2**FB-1 ; i = i + 1)begin: gdalways @(posedge i_clk or negedge i_res)beginif(i_res==0)beginroll_data[i+1]<=0;endelsebeginroll_data[i+1] <= roll_data;endendendendgeneratealways @(posedge i_clk or negedge i_res)beginif(i_res==0)beginroll_data<=0;endelsebeginroll_data <= i_filter;endendalways@(posedge i_clk or negedge i_res)beginif (i_res==0)beginstore<=0;o_filter<=0;endelsebeginstore<=integrate;o_filter<=integrate[OW-1:0];endendendmodule*** EDIT ***By popular demand, I have added the test bench file, and also a copy of the sine wave hex file (attached at the end of this message). If you want an output waveform for the test bench, you will have to uncomment the CIC LP filter in the DDS NCO module and feed the DAC output into the input of the CIC LP filter module, and assign the output of the DDS NCO module to the output of the CIC LP filter moduleTestbench Module:Code:`timescale 100ns/10nsmodule dds_nco_synthesizer_tb#(parameter PW=26,parameter OW=16)(output wire [OW-1:0] LP_out);reg clk;reg res;reg [1:0] sel;reg [PW-2:0] freq;dds_nco_synthesizer dds(.DAC_clk(clk),.DAC_res(res),.func_select(sel),.freq_tune(freq),.LP_out(LP_out));defparam dds.OW=OW;defparam dds.PW=PW;initialbeginclk=0; res=0; freq=25'd1; #5; res=1;sel=0;$display("1 kHz Sine"); freq=25'd1000; #250000;sel=1;$display("1 kHz Square"); freq=25'd1000; #250000;sel=2;$display("1 kHz Triangle"); freq=25'd1000; #250000;sel=3;$display("1 kHz Saw"); freq=25'd1000; #250000;//$display("10 kHz"); freq=25'd10000; #100000;//$display("100 kHz"); freq=25'd100000; #100000;//$display("200 kHz"); freq=25'd200000; #100000;//$display("400 kHz"); freq=25'd400000; #100000;$stop;endalways #10 clk=~clk;endmoduleI have also added the timing constraints file for the board I am using (DE10 Nano from Terasic) for the Quartus Prime software suite (save it as .sdc rather than its existing .txt)

author: SMB784
author_rank: Frequent Contributor
timestamp: July 04, 2020, 09:59:53 pm
content: So now that the 2nd order SD-DAC is working, I had a little time to test and compare the noise of the 1st order to the 2nd order DAC at 150 kHz frequency, near the top end that the device can output at this sample rate. Both DAC outputs were filtered through a 50 Ohm impedance 2nd order low pass filter with a filter knee at approximately 100 kHz. I have attached the files to this message. As can be seen, there is less phase noise with the 2nd order, and also less ripple from the SD pulses. I will quantify this more when I hook it up to my spectrum analyzer. Overall this was a fun project, though I still need to fix the square wave. I welcome your thoughts!

author: hamster_nz
author_rank: Super Contributor
timestamp: July 05, 2020, 05:24:33 am
content: QuoteDoes ii bother you that the 1st Order output is about 1/8th that of the 2nd Order? ( ~-18dB )If so, it's because what you are doing with "OS". It isn't acting as an Oversampling factor, but a gain of 1/(2^*(OS-1)).If you were to fix that up it would give a more apples to apples comparison.

author: ali_asadzadeh
author_rank: Super Contributor
timestamp: July 05, 2020, 11:53:55 am
content: Nice topic

author: SMB784
author_rank: Frequent Contributor
timestamp: July 05, 2020, 06:18:14 pm
content: Quote from: hamster_nz on July 05, 2020, 05:24:33 amSo my understanding is that what I have done is known as Zero-Stuffing, i.e. putting in tons of zeroes (2^OSR to be exact) in between each data bit from i_func. It is supposed to act as an interpolating filter. This process is described here:https://pdfs.semanticscholar.org/602e/7d426297a62fe89a5748183c011fbc3c129e.pdfIf I am cramming a bunch of zeroes in between my samples, then my average output will go down in amplitude, which I believe is what I am seeing with the 1st order DAC. Not sure about all of this though, I am definitely not an electrical engineer, still pretty new to this whole game. Does this sound right to you?

author: SMB784
author_rank: Frequent Contributor
timestamp: July 08, 2020, 06:56:22 pm
content: Updated with test bench module and Quartus Prime timing constraints file for board I'm using.

author: langwadt
author_rank: Super Contributor
timestamp: July 08, 2020, 07:14:32 pm
content: Quote from: SMB784 on July 05, 2020, 06:18:14 pmafter the zero stuffing you run the data through an interpolating(low pass) filter before the DACif you can generate your waveform at the full rate there is not reason to zero stuff and interpolate

author: SiliconWizard
author_rank: Super Contributor
timestamp: July 09, 2020, 03:58:53 pm
content: Quote from: SMB784 on July 04, 2020, 09:59:53 pmSorry to chime in again - did you actually fix anything (and if so what?), because your post just before this one showed that there was still a problem (that you elicited with square waves?).I took a look at my 2nd-order implementation I wrote in VHDL some time ago, and what I can see is that the accumulator for the 2nd stage is added with a weight of 3/4 (0.75). Unfortunately, I don't completely remember the rationale (I would have to dig up back in the papers I read back then), but I'm pretty sure if it jsut had a weight of 1, it would overflow at some point. Just a thought.

author: SMB784
author_rank: Frequent Contributor
timestamp: July 10, 2020, 03:45:41 pm
content: Quote from: SiliconWizard on July 09, 2020, 03:58:53 pmI did fix things on my end by adding a factor of ~0.75 (that's in the most recent message I posted with all the verilog code in it. This helped avoid the overflow that was causing all of my signals to give a garbled output. However, I think my square wave is still overflowing, evidenced by the lack of a waveform produced in the output.

author: langwadt
author_rank: Super Contributor
timestamp: July 10, 2020, 07:30:21 pm
content: Quote from: SMB784 on July 10, 2020, 03:45:41 pmsecond and higher order DSMs are potentially unstable, staying below say ~80% of full scale input should be reasonably safe but it is not guranteed