Author Topic: 2nd Order Sigma-Delta Digital to Analogue Converter (SD-DAC) in Verilog  (Read 9033 times)

0 Members and 1 Guest are viewing this topic.

Offline SMB784Topic starter

  • Frequent Contributor
  • **
  • Posts: 421
  • Country: us
    • Tequity Surplus
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?

Fair enough, I have included the entire DDS NCO below.

Top Module:
Code: [Select]
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];

endmodule

LED Display:
Code: [Select]
module LED_display
(
input wire [1:0] sel,
output reg [3:0] LED
);

always @(sel)
begin
case(sel)
2'b00: LED<=4'b0001;
2'b01: LED<=4'b0010;
2'b10: LED<=4'b0100;
2'b11: LED<=4'b1000;
endcase
end

endmodule

DDS NCO  module:
Code: [Select]
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 least
input wire [1:0] func_select,

// output wire [OW-1:0] LP_out
output 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)
begin
if (DAC_res==0)
begin
/* define PW-bit number "phase" with PW-bit
decimal 0 created using repetition operator */
phase<={(PW){1'd0}};
end
else
/* 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 repetition
operator */
// phase<=phase+{{(PW-1){1'd0}},1'd1};
phase<=phase+'d200000;
// phase<=phase+freq_tune;
end

sine 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;

endmodule

Quarter wave sine synthesizer:
Code: [Select]
module sine
#(
parameter OW
)
(
input wire [(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 #1
assign negate[0] = i_phase[(OW-1)];
assign index=i_phase[(OW-2)]?~i_phase[(OW-3):0]:i_phase[(OW-3):0];

// Clock #2
assign tblvalue=quartertable[index];
assign negate[1]=negate[0];

// Output Clock
assign o_sine=negate[1]?-tblvalue:tblvalue;
  // negate[1]?-tblvalue-{(OW-1){1'b1}}:tblvalue+{(OW-1){1'b1}};
  // above works for 1st order DAC
endmodule

Square wave synthesizer:
Code: [Select]
module square
#(
parameter OW
)
(
input wire [(OW-1):0] i_phase,
output wire [(OW-1):0] o_square
);

assign o_square = {OW{i_phase[OW-1]}};

endmodule

Triangle wave synthesizer:
Code: [Select]
module triangle
#(
parameter OW
)
(
input wire [(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];

endmodule

Saw wave synthesizer:
Code: [Select]
module saw
#(
parameter OW
)
(
input wire [(OW-1):0] i_phase,
output wire [(OW-1):0] o_saw
);

assign o_saw = i_phase[(OW-1):0];

endmodule

Waveform selection MUX:
Code: [Select]
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)
begin
case(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;
endcase
end

endmodule

1st Order Sigma-Delta Digital-to-Analog Converter (not used now that 2nd order is working):
Code: [Select]
module delta_sigma_DAC
#(
parameter OW,  // output word size
parameter OS=4 // oversampling ratio, 2^7 = 128
)
(
input wire i_clk, // input clock
input wire i_res, // input reset line
input wire [OW-1:0] i_func, // input function
output wire o_DAC // output DAC bit
);

reg [((OW-1)+OS):0] DAC_acc; // DAC accumulator

always @(posedge i_clk or negedge i_res)
begin
if (i_res==0)
begin
DAC_acc<={((OW-1)+OS){1'd0}}; // zeroes output on reset
end
else
DAC_acc<=DAC_acc[((OW-1)+OS)-1:0]+i_func; /* adds value of
i_func to DAC
accumulator
register */
end

assign o_DAC=DAC_acc[((OW-1)+OS)]; // outputs top bit of DAC accumulator

endmodule

2nd Order Sigma-Delta Digital-to-Analog Converter:
Code: [Select]
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 @*
begin
i_func_ext={{OSR{i_func[OW-1]}},i_func};
end

always @(posedge i_clk or negedge i_res)
begin
if (i_res==0)
begin
DAC_acc_1st<={((OW-1)+OSR){1'd0}};
DAC_acc_2nd<={((OW-1)+OSR){1'd0}};
ADC_out<=1'b0;
end

else
begin
if(ADC_out==1'b1)
begin
DAC_acc_1st=DAC_acc_1st+i_func_ext+mid_DNR;
DAC_acc_2nd=DAC_acc_2nd+DAC_acc_1st+mid_DNR;
end
else
begin
DAC_acc_1st=DAC_acc_1st+i_func_ext-mid_DNR;
DAC_acc_2nd=DAC_acc_2nd+DAC_acc_1st-mid_DNR;
end
ADC_out<=DAC_acc_2nd[((OW-1)+OSR)];
end
end

assign o_DAC=ADC_out;

endmodule

Cascaded integrator comb rolling average low pass filter (not used except for simulations):
Code: [Select]
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;
generate
for (i = 0; i < 2**FB-1 ; i = i + 1)
begin: gd
always @(posedge i_clk or negedge i_res)
begin
if(i_res==0)
begin
roll_data[i+1]<=0;
end
else
begin
roll_data[i+1] <= roll_data[i];
end
end
end
endgenerate


always @(posedge i_clk or negedge i_res)
begin
if(i_res==0)
begin
roll_data[0]<=0;
end
else
begin
roll_data[0] <= i_filter;
end
end

always@(posedge i_clk or negedge i_res)
begin
if (i_res==0)
begin
store<=0;
o_filter<=0;
end
else
begin
store<=integrate;
o_filter<=integrate[OW-1:0];
end
end

endmodule

*** 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 module

Testbench Module:
Code: [Select]
`timescale 100ns/10ns
module 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;

initial
begin
clk=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;
end

always #10 clk=~clk;

endmodule

I 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)
« Last Edit: July 08, 2020, 06:55:47 pm by SMB784 »
 

Offline SMB784Topic starter

  • Frequent Contributor
  • **
  • Posts: 421
  • Country: us
    • Tequity Surplus
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!

Offline hamster_nz

  • Super Contributor
  • ***
  • Posts: 2812
  • Country: nz
Quote
1st Order Sigma-Delta Digital-to-Analog Converter (not used now that 2nd order is working):

Does 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.
« Last Edit: July 05, 2020, 05:26:58 am by hamster_nz »
Gaze not into the abyss, lest you become recognized as an abyss domain expert, and they expect you keep gazing into the damn thing.
 

Offline ali_asadzadeh

  • Super Contributor
  • ***
  • Posts: 1930
  • Country: ca
Nice topic :) >:D
ASiDesigner, Stands for Application specific intelligent devices
I'm a Digital Expert from 8-bits to 64-bits
 

Offline SMB784Topic starter

  • Frequent Contributor
  • **
  • Posts: 421
  • Country: us
    • Tequity Surplus
Quote
1st Order Sigma-Delta Digital-to-Analog Converter (not used now that 2nd order is working):

Does 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.

So 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.pdf

If 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?

Offline SMB784Topic starter

  • Frequent Contributor
  • **
  • Posts: 421
  • Country: us
    • Tequity Surplus
Updated with test bench module and Quartus Prime timing constraints file for board I'm using.

Offline langwadt

  • Super Contributor
  • ***
  • Posts: 4718
  • Country: dk
Quote
1st Order Sigma-Delta Digital-to-Analog Converter (not used now that 2nd order is working):

Does 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.

So 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.pdf

If 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?

after the zero stuffing you run the data through an interpolating(low pass) filter before the DAC

if you can generate your waveform at the full rate there is not reason to zero stuff and interpolate

 

Online SiliconWizard

  • Super Contributor
  • ***
  • Posts: 15323
  • Country: fr
So now that the 2nd order SD-DAC is working

Sorry 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.
 

Offline SMB784Topic starter

  • Frequent Contributor
  • **
  • Posts: 421
  • Country: us
    • Tequity Surplus
So now that the 2nd order SD-DAC is working

Sorry 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.

I 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.

Offline langwadt

  • Super Contributor
  • ***
  • Posts: 4718
  • Country: dk
So now that the 2nd order SD-DAC is working

Sorry 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.

I 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.

second and higher order DSMs are potentially unstable, staying below say ~80% of full scale input should be reasonably safe but it is not guranteed
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf