hamster Posted July 1, 2020 Posted July 1, 2020 I played around with a 1st and 2nd order 12-bit Sigma Delta DAC implemented on the FPGA. I found the results quite interesting, as the change is pretty simple to implement and the change to the noise on the output spectrum is quite significant, with lowered 2nd harmonic and a much smother noise floor. VHDL code is on GitHub at https://github.com/hamsternz/second_order_sigma_delta
D@n Posted July 1, 2020 Posted July 1, 2020 @hamster, Impressive! Would you mind sharing the hardware you chose to use? The clock speed of the delta-sigma converter? It also looks like you biased your sine wave negative by a half a sample. Out of curiosity, was this on purpose or just the result of the way you truncated floats to integers when generating your table? I'm a bit surprised by the second harmonic you show in your pictures. I had rather thought this technique would've done better than that. Do you have any suggestions as to what might have caused that harmonic? Either way, good fun, and thanks for sharing! Dan
hamster Posted July 2, 2020 Author Posted July 2, 2020 BASYS3 + PMOD Breadboard + Analog Discovery 2. It was just a hack, so the table was a quick formula in a spreadsheet, yes, I assume the + and - sides are both rounding towards zero causing some asymmetry, but with 11 significant bits that should be somewherere about -60dB at a guess. Most of the noise is just the shoddy physical implementation. Flying wires on a breadboard, on PMODs, just the shielded wires on the AD2 and so on. If I leave a wire hanging around it will pick up most the noise too, maybe 6dB lower than on the channel that is under measurement. This was just a quick experiment, just using the 100MHz clock rate. If I use a slower clock (e.g. update only every 8 cycles so 12.5MHz) the noise floor actually drops a lot.. Also using the AD2 on a different laptop to the one programming/powering the FPGA removes a lot of noise too. I assume that this is due to voltage drops and noise on the USB cables. Plenty of room for experimentation and improvement.
hamster Posted July 2, 2020 Author Posted July 2, 2020 @Dan, I managed to find the 'average' option rather than 'decimate' which has stopped high frequency noise showing up in the spectrums, giving a more reasonable noise floor.
zygot Posted July 2, 2020 Posted July 2, 2020 (edited) Measurement technique is certainly critical to successful analysis of how your hardware is performing. So is the selection of test equipment. I see that your most recent plots show a high DC component. As Sigma-Delta Modulators aren't DC accurate is appears that your are trying to do measurements using direct coupling. An AC coupled load would seem to me to be more appropriate. This raises the topic of source and load impedance. Edited July 2, 2020 by zygot
zygot Posted July 2, 2020 Posted July 2, 2020 I glanced at your code. The lines like this one bother me: new_val := dac1_accum + sample - 2048; I wonder how Vivado implemented this. Usually, in my experience a better way to do this is to pipeline so that each '+' operation is performed on a separate pipeline version of the signal. From a coding viewpoint it appears to be straightforward. From an implementation viewpoint it looks like you have in implied latch, at least. Regardless, you are trying to perform 2 add or one add and 1 subtract in a clock cycle. Even for a quick prototype exercise this isn't a good idea. Sometimes our HDL code gets written by the parts of our brain wired for C. It's dangerous.
hamster Posted July 2, 2020 Author Posted July 2, 2020 7 hours ago, zygot said: I glanced at your code. The lines like this one bother me: new_val := dac1_accum + sample - 2048; I wonder how Vivado implemented this. Usually, in my experience a better way to do this is to pipeline so that each '+' operation is performed on a separate pipeline version of the signal. From a coding viewpoint it appears to be straightforward. From an implementation viewpoint it looks like you have in implied latch, at least. Regardless, you are trying to perform 2 add or one add and 1 subtract in a clock cycle. Even for a quick prototype exercise this isn't a good idea. Sometimes our HDL code gets written by the parts of our brain wired for C. It's dangerous. I can't see the latching issues, but agree that if the +/-2048 was a separate signal, then the code could be simplified quite a bit... however, the optimizer should be doing that at the moment. "Premature optimization is the root of all evil" and so on. One other finer point. As currently written +full scale value will generate a stream of all ones output but a -full scale won't generate all zero outputs, but a zero value gives a perfect 50:50 mix of ones and zeros. Others might need it that -full scale gives all zeros, and +full scale gives all ones, but a zero value will give slightly more zeros than ones.
zygot Posted July 2, 2020 Posted July 2, 2020 50 minutes ago, hamster said: but a zero value gives a perfect 50:50 mix of ones and zeros. Mull over your sine LUT. BTW, why did you replicate 2 cycles instead of just using 1 in your LUT?
hamster Posted July 3, 2020 Author Posted July 3, 2020 26 minutes ago, zygot said: Mull over your sine LUT. BTW, why did you replicate 2 cycles instead of just using 1 in your LUT? Because I am quite happy to use a whole block of RAM rather than debugging indexing and sign-flipping code... It's actually one full cycle (half positive, half negative). It is jsut a column from a Google docs spreadsheet https://docs.google.com/spreadsheets/d/13srKHRNCD2dfbMglMvvCUESHR23kHWlzAJ24MJ_erzc/edit?usp=sharing I could have also got away with just one quadrant, but then it would be more 'active' code on what I'm not interested in playing with
xc6lx45 Posted July 3, 2020 Posted July 3, 2020 (edited) Hi, for a DA converter hack, you could also have a look at the one I used here: There was quite a bit of discussion about reconstruction filtering . The shown measurements are taken using the on-board XADC, which acts as lowpass filter. I think the first two plot labels are wrong (plot 1, 2 show a sine wave. Plot 3, 4 show an 8-tone test signal) It's not sigma-delta but open-loop PWM with creative dithering. The idea is - I generate a PWM signal simply by comparing the output value MSBs against a periodic ramp (e.g. 4 bit) - for the lower bits, use pseudorandom numbers It modulates most of the quantization error up to higher frequencies, where it can be filtered by analog means. Sigma-Delta is more sophisticated (this one might be 8..10 bit equivalent for an audio signal). The main advantage of the PWM scheme is that it works rail-to-rail, where sigma-delta does not. Quote // pseudorandom generation with sufficiently long period module pn24(i_clk, o_out); input wire i_clk; output reg [23:0] o_out = 24'h1; always @(posedge i_clk) begin o_out <= o_out >> 1; o_out[23] <= o_out[0]; o_out[22] <= o_out[23] ^ o_out[0]; o_out[21] <= o_out[22] ^ o_out[0]; o_out[16] <= o_out[17] ^ o_out[0]; end endmodule // construct PWM reference with // - large periodic component (quantization error is modulated up to the repetition frequency and harmonics) // - small random component (dithering to break up systematic quantization error) wire [23:0] dither2; pn24 iDither(.i_clk(CLKDAC), .o_out(dither2)); reg [23:0] dither = 24'd0; reg [4:0] dacCount = 5'd0; reg [4:0] dacCountA = 5'd0; reg [4:0] dacCountB = 5'd0; reg [17:0] pwm = 18'd0; always @(posedge CLKDAC) begin dither <= dither2; dacCount <= dacCount + 5'd1; dacCountA <= dacCount; dacCountB <= dacCount ^ 5'b01010; // break the linear ramp pwm <= {dacCountA[3:0], dither[13:0]}; end Missing above: "outputBitReg <= myDacVal >= pwm; Note that it's feedforward-only so it should be clocked fairly high e.g. 250 MHz on Artix. The code allows also a 5-bit ramp but I got best results using 4 bits (if in doubt, experiment - depends on the lowpass). Edited July 3, 2020 by xc6lx45
SmashedTransistors Posted January 4, 2022 Posted January 4, 2022 (edited) Hello, I've been experimenting with one bit DAC recently to get a very easy way to output sound from the Basys3. So, here it is. It uses a 100MHz clock (even if the output one bit modulation is 12.5MHz). The input is 20 bit. The output bit is meant to be sent directly to a physical port. Before going Verilog I experimented it in a Java benchmark to check stability - the first integrator u is leaky ( hence the - (u>>>3) ) to limit chaotic behavior for low level sounds. This is not an intent to replace a genuine converter. But I think it can be handy if you don't have one at hand and want to experiment with sound generation. `timescale 1ns / 1ps ////////////////////////////////////////////////////////////////////////////////// // Engineer: Thierry Rochebois // // Create Date: 22.12.2021 10:31:45 // Module Name: DAC // Description: A second order one bit audio DAC. // inputs: clk 100 MHz clock // in 20 bit signed input // (can be refreshed at a rate up to 200kHz) // output: out 1 bit out modulated at 12.5MHz // // Dependencies: NONE // Revision 0.01 - File Created // Additional Comments: // ////////////////////////////////////////////////////////////////////////////////// module DAC( input clk, // 100MHz clock input signed [19:0] in, // input (update rate up to 200kHz) output reg out // one bit out modulated at 12.5MHz ); reg signed [2:-21] x = 24'b0; // input cast to q3.21 reg signed [2:-21] u = 24'b0; // first integrator reg signed [2:-21] v = 24'b0; // second integrator wire signed [2:0] y; // output for feedback q3.0 -1 or +1 always @(posedge clk) begin x <= {{4{in[19]}}, in}; // 20 bit input -> q3.21 [-0.5 0.5[ end // 1/8 counter -> 12.5MHz reg [2:0] cpt8; always @(posedge clk) begin cpt8 <= cpt8 - 1; end // for feedback -1 q3.0 +1 q3.0 assign y = (v > 0) ? 3'b111 : 3'b001; wire signed [2:-20] s1; wire signed [2:-21] s1h; assign s1 = {x[2:0] + y, x[-1:-20]}; assign s1h = {s1[2], s1[2:-20]}; wire signed [2:-20] s2; wire signed [2:-21] s2h; assign s2 = {u[2:0] + y, u[-1:-20]}; assign s2h = {s2[2], s2[2:-20]}; // 100MHz / 8 = 12.5MHz for the 1 bit DAC always @(posedge clk && (cpt8 == 0)) begin u <= u + s1h - (u>>>3); // first integrator with feedback v <= v + s2h; // second integrator with feedback out <= v[2]; // output end endmodule Here is an example with some triangle waves : Edited January 4, 2022 by SmashedTransistors epsilon 1
engi Posted January 20, 2022 Posted January 20, 2022 You can try to add and alter substract noise (or use a noisy DSC) in order to flatten the noise spectrum. Focussing on audio one can reach up to almost a 16 bit quality for music signals following pink noise spectrum. Calibration of the input behaviour has to be done too (Schmitt Trigger issue).
epsilon Posted May 29, 2023 Posted May 29, 2023 Quote // for feedback -1 q3.0 +1 q3.0 assign y = (v > 0) ? 3'b111 : 3'b001; I think there might be a small error here. y should track v[2], the DAC's output, i.e. if v[2]==1 y should be 1, if v[2]==0 y should be -1. In the code above, if v==0, v[2] == 0, but y will be 1. Testing against v[2]==0 or (v>=0) would be more accurate. I tested with a square wave. I recorded the bitstream, sent it through a low-pass filter, and plotted the magnitude spectrum. In the 'fixed' version (sqr_fft_after), the noise floor is cleaner than before (sqr_fft_before). I'm not a signal-processing expert by any means. I was just wondering why the output of the 2nd order DAC above was noisier than that of another implementation I tested against (https://github.com/briansune/Delta-Sigma-DAC-Verilog), and it turned out to be the (v>0) test. Best regards, Ruben.
Recommended Posts
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now