HansDR

Sigma-delta DAC audio quality (with 8-bit data)

Recommended Posts

While going through hamster's Intro to FPGA & VHDL book (great resource BTW), I had a go at outputting 8-bit audio at 11 KHz through a sigma-delta DAC. It worked, but the audio quality was pretty poor. Yes, 8-bit @ 11 KHz audio is low-fi, but the same audio data is much less noisy when played through a computer's sound-card. The DAC itself was clocked at 32 MHz, which I think should be more than enough for decent quality output.

 

I set myself the goal of improving quality to be similar to a sound-card (without increasing the number of bits or the input sample-rate). So far, I have managed to improve the quality by using a second-order sigma-delta DAC, but it still has crackling noise audible that shouldn't be there.

 

Has anybody else experimented with sigma-delta DACs and 8-bit audio? If so, what kind quality did you get? I'm wondering if there's something wrong with the DAC, or if I should be doing something like digitally low-pass filtering the 8-bit @ 11 KHz audio data before passing it on to the DAC.

 

Hans

Share this post


Link to post
Share on other sites

Hi Hans,

 

IIRC Alex had some problems when driving two channels at the same time (e.g. in stereo). It seem to be some strange ground-loopy sort of thing, or maybe the the output bank was current limiting and upsetting the other channel. Are you driving the headphones directly? Also driving a class-d amp (e.g. cheap computer speakers) without decent filtering on the output gives lots of crackles.

 

I never got to the bottom of it - I would need a scope :-)

 

A sigma-delta DAC shouldn't need filtering, as long as you don't make the accumulator too long or use too much of the range - the worst case for an 8 bit DAC is when outputting 0x01 - the DAC will generate a 31.25ns pulse every 1/125000th of a second, which should filter out fine. If you go for a 16 bit DAC the worst case is a pulse every 488th of a second - it will most likely have some audible component. If you avoid the top and bottom 10% of the range then you might get much better results.

 

If you want really high quaility audio output then I2S is really easy to use - a PMODi2S is an easy win here - http://hamsterworks.co.nz/mediawiki/index.php/Pmodi2s

Mike

Share this post


Link to post
Share on other sites

IIRC Alex had some problems when driving two channels at the same time (e.g. in stereo). It seem to be some strange ground-loopy sort of thing, or maybe the the output bank was current limiting and upsetting the other channel. Are you driving the headphones directly? Also driving a class-d amp (e.g. cheap computer speakers) without decent filtering on the output gives lots of crackles.

 

I'm using the Arcade MegaWing, and I've connected both (passive) headphones and my monitor's speakers. The crackling isn't present with other designs (e.g., PacMan), so I highly doubt that the analog side is the problem.

 

 

A sigma-delta DAC shouldn't need filtering, as long as you don't make the accumulator too long or use too much of the range - the worst case for an 8 bit DAC is when outputting 0x01 - the DAC will generate a 31.25ns pulse every 1/125000th of a second, which should filter out fine. If you go for a 16 bit DAC the worst case is a pulse every 488th of a second - it will most likely have some audible component. If you avoid the top and bottom 10% of the range then you might get much better results.

 

I think that pre-filtering might help because I'm not doing proper upsampling from 11 KHz to 32 MHz; it's just holding each value until the next one comes along (i.e., zero-order-hold). That means that the signal can have rather jagged edges at 11 KHz, which should add noise in the audible 5.5 KHz+ range. I'm not sure if that's the cause of the crackle, but it certainly doesn't help.

 

From what I've read, commercial audio sigma-delta DACs are typically 5th order, which implies that there is some sort of filtering going on.

 

If you want really high quaility audio output then I2S is really easy to use - a PMODi2S is an easy win here - http://hamsterworks.co.nz/mediawiki/index.php/Pmodi2s

Mike

Thanks. Right now, I'm more interested to see if I can improve the sigma-delta DAC output. The Super Audio CD format (that never took off) delivers hifi audio with a 1-bit DSD datastream at 2.8224 MHz, so I should be able to get much better sound than I am.

 

Hans

Share this post


Link to post
Share on other sites

I think you should try Hamsters suggestion and just use a mono channel and see if the noise is still there.

 

I just tried it, and crackling is still there, but different. Cracking and distortion is worst with just the left channel in use. With the right channel only, the the crackling is more muffled, but the sound is more distorted. Audio quality is actually best if both channels play the same thing. The DAC's output bitstream is the same in each case, so that's a bit weird.

 

I also tried bypassing the board's RC filters by removing the arcade wing, and connecting the speaker input directly to the Papilio Pro board's pins, and this odd behaviour persisted. In fact, shifting the audio output to a different pin, changes the noise/distortion.

 

The above suggests that what I'm hearing could well be noise from within the chip. I've been thinking of the digital output as a clean stream of 1's and 0's, but there can be a heap of analog noise on top of it. That noise would make it straight through the RC filters, and into my speakers. Alas, I don't have an oscilloscope to check it out.

 

Maybe running the DAC at 32 MHz is particularly bad for added noise.

 

 

HansDR, what version number of the Arcade MegaWing do you have?

 

Jack.

 

Version 1.2.

 

Hans

Share this post


Link to post
Share on other sites

Are you sure that the output is being driven directly from a register, and not the output of combinatorial logic?

That would explain the different behaviour during different builds.

Share this post


Link to post
Share on other sites

Are you sure that the output is being driven directly from a register, and not the output of combinatorial logic?

 

I just checked the code, and:

	-- The output latch	DACOut_latch : process(clk)	begin		if (rising_edge(CLK)) then			DACOut <= dataOut;		end if;	end process;

So yes, the output is direct from a register.

Share this post


Link to post
Share on other sites

Well how about you zip up the project and post it here. I'll run it up on a couple of my boards and see what I can hear. Please do a "Project/Cleanup Project files" from the menu before zipping it up.

Share this post


Link to post
Share on other sites

Well how about you zip up the project and post it here. I'll run it up on a couple of my boards and see what I can hear. Please do a "Project/Cleanup Project files" from the menu before zipping it up.

 

Project attached. The test audio data is the first few seconds of a tune that came with my phone.

Audio.zip

Share this post


Link to post
Share on other sites

Hi HansDR,

 

I tried the project and initally got the same results. Clicks/pops.

 

I put in a simple 1st order DAC - same result.

 

entity SigmaDeltaDAC is
  Generic ( DataWidth : INTEGER );
  Port ( CLK : in STD_LOGIC;
  Data : in STD_LOGIC_VECTOR (DataWidth - 1 downto 0);
  DACOut : out STD_LOGIC );
end SigmaDeltaDAC;
 
architecture Behavioral of SigmaDeltaDAC is
  signal total : STD_LOGIC_VECTOR (DataWidth-1  downto 0) := (others => '0');
  signal sum : STD_LOGIC_VECTOR  (DataWidth downto 0) := (others => '0');
begin
 
  sum <= ('0' & total) + ('0' & data);
 
sum1_p : process(clk)
  begin
    if (rising_edge(CLK)) then
      DACOut <= sum(sum'high);
      total <= sum(sum'high-1  downto 0);
    end if;
  end process;
end Behavioral;
 

 

It's something wrong with the data getting into the ROM - I converted the data to a '.coe' file and used the IP core generator and the clicks are gone.

 

I put back your original DAC, and still sounds OK.

 

The file with the samples in was a bit longer than it should be - had to trim off 30 lines or so.

 

Attached is the resulting bitstream - I used the IP generator the project is now rather large, and doesn't really help you much to debug the code.

 

Mike

audio.bit

Share this post


Link to post
Share on other sites

Yeah I wouldn't load the ROM like that, that code looks like it's for simulation, not really for synthesis. Get yourself a copy of romgen then use it to convert any binary file to VHDL instantiation files. Here's an example of a small ROM file produced by romgen, you could use this and adapt it for your larger initialization data manually, but I do recommend using romgen to save yourself a lot of manual labour. The repository at the previous link has a precompiled .exe if you're on windows.

 

romgen by MikeJ v3.03INFO : creating entity : ROMINFO : rtl model, rom arrayINFO : combinatorial output-- generated with romgen v3.03 by MikeJlibrary ieee;    use ieee.std_logic_1164.all;    use ieee.std_logic_unsigned.all;    use ieee.numeric_std.all;library UNISIM;    use UNISIM.Vcomponents.all;entity ROM isport (        ADDR : in  std_logic_vector(4 downto 0);        DATA : out std_logic_vector(7 downto 0)        );end;architecture RTL of ROM is        type ROM_ARRAY is array(0 to 31) of std_logic_vector(7 downto 0);        constant ROM : ROM_ARRAY := (                x"00",x"00",x"00",x"00",x"00",x"00",x"00",x"00", -- 0x0000                x"00",x"00",x"00",x"00",x"00",x"00",x"00",x"00", -- 0x0008                x"00",x"00",x"00",x"00",x"00",x"00",x"00",x"00", -- 0x0010                x"00",x"00",x"00",x"00",x"00",x"00",x"00",x"00"  -- 0x0018        );begin        p_rom : process(ADDR)        begin            DATA <= ROM(to_integer(unsigned(ADDR)));        end process;end RTL;

Share this post


Link to post
Share on other sites

@Hamster

 

Your comment about the bad ROM pointed me in the right direction. The problem was that the address counter was being updated on the clock's rising edge, while data was clocked out of the ROM on the falling edge, and being read by the DAC on the rising edge again. This was causing timing problems that resulted in invalid data being sent to the DAC. I guess that the address counter's output wasn't stable yet when the ROM was being read. This would also explain why outputting to just the left or right channel changed the crackling; it also changed the layout in the FPGA and, therefore, the timing.

 

With everything being clocked on the rising edge, the crackling is gone.

 

 

@alex

 

While file IO is mostly used in simulation, this is a perfectly valid way of loading data into a ROM. I personally prefer keeping the ROM data in a separate file when it gets this big. It's cleaner and more compact than sticking it in a dirty big array in a VHDL file. I had hoped to directly read in binary data directly, but it appears that VHDL's IO functions aren't flexible enough to do that (or, I'm missing something).

 

The biggest downside to loading ROM data from an external file, is that Xilinx ISE is unaware of this dependency, and won't recompile the appropriate files if the ROM's contents change.

 

 

@all

 

Thanks for your help.

 

Hans

Share this post


Link to post
Share on other sites

That is really odd - I was thinking about this today and assumed that you didn't have a timing constraint on, and it was failing to run at 32MHz - otherwise the tools should give you a timing error.

 

I've just had look and you do have one, so it should be have warned/errors if timing failed.

 

Hummmm

Share this post


Link to post
Share on other sites

That is really odd - I was thinking about this today and assumed that you didn't have a timing constraint on, and it was failing to run at 32MHz - otherwise the tools should give you a timing error.

 

I've just had look and you do have one, so it should be have warned/errors if timing failed.

 

Yes, I'm not sure what to make of that either. Based on the timing report, it looks like it's treating each bit in the address register as independent with 0 levels of logic. Considering that it's connected to an adder, that's wrong.

 

EDIT: Okay, I just noticed that the ROM is using a different clock from the address counter:

	-- The memory containing the audio data	ROM : entity work.ROM		generic map ( DataWidth => DataWidth,						  AddressWidth => AddressWidth,						  ROMFileName => ROMFileName)		port map ( CLK => CLK_AudioData,					  Address => AudioAddress,					  Data => AudioData);	-- Read the audio data from memory	AudioPlay_p : process(CLK_IN, CLK_AudioData)	begin		if (rising_edge(CLK_IN) AND CLK_AudioData = '1') then			AudioAddress <= STD_LOGIC_VECTOR(UNSIGNED(AudioAddress) + 1);		end if;	end process;

Notice how the ROM is clocked with CLK_AudioData, whereas the address counter is clocked by CLK_IN, and uses CLK_AudioData as an enable signal. That's asking for trouble, isn't it? The timing analyzer may not be smart enough to work out what's going on in such a case.

 

EDIT2: After changing the ROM's clock to CLK_IN, the timing report makes a lot more sense, and it's correctly checking whether the data path delay fits within the rising to falling clock edge period.

 

Hans

Share this post


Link to post
Share on other sites

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 account

Sign in

Already have an account? Sign in here.

Sign In Now