{"id":1578,"date":"2021-04-03T19:18:30","date_gmt":"2021-04-03T19:18:30","guid":{"rendered":"http:\/\/retroramblings.net\/?p=1578"},"modified":"2021-04-05T11:16:22","modified_gmt":"2021-04-05T11:16:22","slug":"solving-the-puzzle","status":"publish","type":"post","link":"https:\/\/retroramblings.net\/?p=1578","title":{"rendered":"Solving the puzzle&#8230;"},"content":{"rendered":"\n<p><strong>Exploring audio DACs in simulation &#8211; Part 3 &#8211; 2021-04-03<\/strong><\/p>\n\n\n\n<p>Having set up a simplistic testbench in Verilator, I want to be able to use it to evaluate several different DACs.  The ones I will be testing are:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>The simple 1st-order Sigma Delta I included in my last post.<\/li><li>A 2nd order DAC found on Github: <a href=\"https:\/\/github.com\/hamsternz\/second_order_sigma_delta_DAC\/blob\/master\/second_order_dac.v\">https:\/\/github.com\/hamsternz\/second_order_sigma_delta_DAC\/blob\/master\/second_order_dac.v<\/a>  &#8211;  however, I&#8217;ve adapted this to halve the input volume by discarding the lowest bit, since second- (and higher) order 1-bit sigma delta DACs are unstable at the extremities of their input range &#8211; they require some headroom.  Since the Amiga application only requires 15-bit input this is not a problem.<\/li><li>A 3rd-order DAC found in <a href=\"http:\/\/www.64kib.com\/atarixlfpga_svn\/trunk\/atari_800xl\/common\/components\/hq_dac.v\">Mark Watson&#8217;s Atari 800 repository<\/a>.  I&#8217;m not sure where it originally came from or what its copyright status is, since it&#8217;s not listed in Mark&#8217;s otherwise comprehensive list of which license applies to which files.  The 3rd order DAC already attenuates the signal to maintain stability so I didn&#8217;t need to modify this one, except as detailed below.<\/li><li>Finally, the <a href=\"https:\/\/github.com\/robinsonb5\/ZPUDemos\/blob\/master\/RTL\/Sound\/hybrid_pwm_sd.v\">simplest form of my hybrid DAC<\/a>.<\/li><\/ul>\n\n\n\n<!--more-->\n\n\n\n<p>I also found a DAC written in <a href=\"https:\/\/opencores.org\/projects\/sigma_delta_dac_dual_loop\">VHDL at OpenCores,<\/a> which has two variants &#8211; the description of these gave me the clue I finally needed to solve this puzzle!<\/p>\n\n\n\n<p>In each case I edited the DACs so that they have a standard interface &#8211; &#8220;clk&#8221; and &#8220;reset_n&#8221;, a 16-bit wide input port, &#8220;d&#8221;, and a 1-bit output port, &#8220;q&#8221;.<\/p>\n\n\n\n<p>I then make use of a blend of Makefiles and shell scripting to build the testbench against each DAC in turn, run the testbench and capture its output to a raw audio file, which I can then load in Audacity.<\/p>\n\n\n\n<p>The <a href=\"https:\/\/github.com\/robinsonb5\/DACTests\/blob\/main\/Makefile\">Makefile<\/a> uses the following construct to achieve this:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">DACS = sigma_delta_dac_1storder hybrid_pwm_sd sigma_delta_dac_2ndorder sigma_delta_dac_3rdorder<br>TESTS = sine<br>all:<br>  for DAC in $(DACS); do \\ <br>    for TEST in $(TESTS); do \\<br>      make $${TEST}_$${DAC}.raw TEST=$$TEST DAC=$$DAC; \\<br>    done; \\<br>  done<\/pre>\n\n\n\n<p>Note the backslashes, which mean that as far as the Makefile&#8217;s concerned everything after the first &#8220;for&#8221; is a single shell command.  Without these, each line would be launched as a separate command, and the variables defined in the &#8220;for&#8221; constructs would be immediately lost.<\/p>\n\n\n\n<p>So the makefile calls itself recursively with &#8220;sine_sigma_delta_dac_1storder.raw&#8221; as a target, then similarly for each of the other DACs in turn &#8211; and we can add new tests later, too.<\/p>\n\n\n\n<p>So let&#8217;s load up the output files into Audacity and take a look.  Each one in turn is imported as raw audio, with the format 16-bit little-endian, 1-channel. <\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"819\" src=\"http:\/\/retroramblings.net\/wp-content\/uploads\/2021\/04\/SpectrumPlot1-1024x819.png\" alt=\"\" class=\"wp-image-1579\" srcset=\"https:\/\/retroramblings.net\/wp-content\/uploads\/2021\/04\/SpectrumPlot1-1024x819.png 1024w, https:\/\/retroramblings.net\/wp-content\/uploads\/2021\/04\/SpectrumPlot1-300x240.png 300w, https:\/\/retroramblings.net\/wp-content\/uploads\/2021\/04\/SpectrumPlot1-768x614.png 768w, https:\/\/retroramblings.net\/wp-content\/uploads\/2021\/04\/SpectrumPlot1-375x300.png 375w, https:\/\/retroramblings.net\/wp-content\/uploads\/2021\/04\/SpectrumPlot1.png 1280w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>Frequency plots of the three standard DACs&#8217; rendition of a sinewave are very similar to each other &#8211; while the hybrid DAC&#8217;s output is a little different:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"819\" src=\"http:\/\/retroramblings.net\/wp-content\/uploads\/2021\/04\/SpectrumPlot2-1024x819.png\" alt=\"\" class=\"wp-image-1580\" srcset=\"https:\/\/retroramblings.net\/wp-content\/uploads\/2021\/04\/SpectrumPlot2-1024x819.png 1024w, https:\/\/retroramblings.net\/wp-content\/uploads\/2021\/04\/SpectrumPlot2-300x240.png 300w, https:\/\/retroramblings.net\/wp-content\/uploads\/2021\/04\/SpectrumPlot2-768x614.png 768w, https:\/\/retroramblings.net\/wp-content\/uploads\/2021\/04\/SpectrumPlot2-375x300.png 375w, https:\/\/retroramblings.net\/wp-content\/uploads\/2021\/04\/SpectrumPlot2.png 1280w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>We have a slight DC offset, and also a bit of a second harmonic.  So the hybrid DAC&#8217;s performing significantly worse than the others in this test &#8211; and I see similar results if I change the frequency of the sinewave.<\/p>\n\n\n\n<p>However, we know the other DACs are struggling most when volume is low, so let&#8217;s create another test.<\/p>\n\n\n\n<p>I copy the sine.cpp testbench to fadeout.cpp and make some changes to the code so that it ramps a sinewave&#8217;s volume down to zero.  I add fadeout to the TESTS variable in the makefile and let it run.  Again, the DACs&#8217; results look very similar.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"819\" src=\"http:\/\/retroramblings.net\/wp-content\/uploads\/2021\/04\/FreqPlot3-1024x819.png\" alt=\"\" class=\"wp-image-1581\" srcset=\"https:\/\/retroramblings.net\/wp-content\/uploads\/2021\/04\/FreqPlot3-1024x819.png 1024w, https:\/\/retroramblings.net\/wp-content\/uploads\/2021\/04\/FreqPlot3-300x240.png 300w, https:\/\/retroramblings.net\/wp-content\/uploads\/2021\/04\/FreqPlot3-768x614.png 768w, https:\/\/retroramblings.net\/wp-content\/uploads\/2021\/04\/FreqPlot3-375x300.png 375w, https:\/\/retroramblings.net\/wp-content\/uploads\/2021\/04\/FreqPlot3.png 1280w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>We have some low frequency noise, which I believe comes from the fact that the volume isn&#8217;t constant for each cycle of the sinewave, but there&#8217;s no real sign of misbehaviour here.<\/p>\n\n\n\n<p>I can create a testbench which (rather slowly) reads a raw audio file, runs it through the DAC and writes it out again, and then listen to the results &#8211; and again I don&#8217;t see (or hear) any real sign of misbehaviour &#8211; so what gives?  Did I dream the problems we were seeing a few years back?<\/p>\n\n\n\n<p>So I built the MiST Minimig core using each of the DACs in turn and repeated some of the tests from back then.  These recordings were made with the MiST connected to my PC&#8217;s line in, input gain set to maximum.  Stardust Memories played using ProTracker 3.15 with the volume slider set one pixel above minimum &#8211; at normal comfortable listening levels this is just on the edge of being audible at 1\/64th of its usual volume.<\/p>\n\n\n\n<figure class=\"wp-block-embed-youtube wp-block-embed is-type-video is-provider-youtube wp-embed-aspect-4-3 wp-has-aspect-ratio\"><div class=\"wp-block-embed__wrapper\">\n<iframe loading=\"lazy\" title=\"Amiga Stardust Memories at minimum volume through 4 DACs\" width=\"584\" height=\"438\" src=\"https:\/\/www.youtube.com\/embed\/n3DxYie6AQo?feature=oembed\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe>\n<\/div><\/figure>\n\n\n\n<p>And the last few seconds of the fadeout at the end of the Gauntlet III title music.  Again, this is recorded with input gain set to maximum, so this part of the tune is usually barely audible:<\/p>\n\n\n\n<figure class=\"wp-block-embed-youtube wp-block-embed is-type-video is-provider-youtube wp-embed-aspect-4-3 wp-has-aspect-ratio\"><div class=\"wp-block-embed__wrapper\">\n<iframe loading=\"lazy\" title=\"Amiga Gauntlet III music through 4 DACs, volume boosted.\" width=\"584\" height=\"438\" src=\"https:\/\/www.youtube.com\/embed\/podXBwp_Q9A?feature=oembed\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe>\n<\/div><\/figure>\n\n\n\n<p>So&#8230; I didn&#8217;t dream it.<\/p>\n\n\n\n<p>So how come the &#8220;regular&#8221; DACs are performing so much worse in real hardware than in simulation?  I found the clue I needed in the description of the VHDL DAC on OpenCores that I mentioned earlier.<\/p>\n\n\n\n<p>The simulation is happening in physics-experiment land, where there&#8217;s no friction, every variable starts out at a nice neat round number and edges are perfectly square.<\/p>\n\n\n\n<p>The DACs are all built upon the same basic assumption, which is that thanks to the low-pass filtering of the reconstruction filter, the pulse trains represented by &#8220;00001111&#8221;, &#8220;00110011&#8221; and &#8220;01010101&#8221; will ultimately produce the same output.  Is that actually true in practice, though?  What happens if the amount of energy delivered to the filter on a rising edge differs from the amount removed on a falling edge?<\/p>\n\n\n\n<p>Time to add another testbench &#8211; a copy of fadeout.cpp to asymmetric.cpp.  We&#8217;ll change the output so that instead of simply multiplying the DAC&#8217;s q output by 65535, we&#8217;ll use a different value depending on whether it&#8217;s a rising or falling edge:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"><code>  \/\/ Simulate asymmetric rising and falling edges...<\/code><br><code>  if(tb-&gt;q &amp;&amp; s&lt;32768) \/\/ Rising edge?<\/code><br><code>    s=0xdfff;<\/code><br><code>  else if(!tb-&gt;q &amp;&amp; s&gt;=32768) \/\/ Falling edge?<\/code><br><code>    s=0x5000;<\/code><br><code>  else<\/code><br><code>    s=(0xffff*tb-&gt;q);<\/code><\/pre>\n\n\n\n<p>These numbers are basically plucked out of thin air &#8211; they&#8217;re not based on any kind of measurement, and they&#8217;re probably quite extreme &#8211; but they&#8217;ll be enough to get an idea of how each DAC copes with asymmetric rising and falling edges.<\/p>\n\n\n\n<p>So let&#8217;s see how the 1st order DAC copes:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"819\" src=\"http:\/\/retroramblings.net\/wp-content\/uploads\/2021\/04\/FreqPlot4-1024x819.png\" alt=\"\" class=\"wp-image-1582\" srcset=\"https:\/\/retroramblings.net\/wp-content\/uploads\/2021\/04\/FreqPlot4-1024x819.png 1024w, https:\/\/retroramblings.net\/wp-content\/uploads\/2021\/04\/FreqPlot4-300x240.png 300w, https:\/\/retroramblings.net\/wp-content\/uploads\/2021\/04\/FreqPlot4-768x614.png 768w, https:\/\/retroramblings.net\/wp-content\/uploads\/2021\/04\/FreqPlot4-375x300.png 375w, https:\/\/retroramblings.net\/wp-content\/uploads\/2021\/04\/FreqPlot4.png 1280w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>Yeah, that&#8217;s not great is it?  And the 2nd and 3rd order DACs don&#8217;t fare too well either:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"819\" src=\"http:\/\/retroramblings.net\/wp-content\/uploads\/2021\/04\/FreqPlot5-1024x819.png\" alt=\"\" class=\"wp-image-1583\" srcset=\"https:\/\/retroramblings.net\/wp-content\/uploads\/2021\/04\/FreqPlot5-1024x819.png 1024w, https:\/\/retroramblings.net\/wp-content\/uploads\/2021\/04\/FreqPlot5-300x240.png 300w, https:\/\/retroramblings.net\/wp-content\/uploads\/2021\/04\/FreqPlot5-768x614.png 768w, https:\/\/retroramblings.net\/wp-content\/uploads\/2021\/04\/FreqPlot5-375x300.png 375w, https:\/\/retroramblings.net\/wp-content\/uploads\/2021\/04\/FreqPlot5.png 1280w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>And the hybrid DAC?<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"819\" src=\"http:\/\/retroramblings.net\/wp-content\/uploads\/2021\/04\/FreqPlot6-1024x819.png\" alt=\"\" class=\"wp-image-1584\" srcset=\"https:\/\/retroramblings.net\/wp-content\/uploads\/2021\/04\/FreqPlot6-1024x819.png 1024w, https:\/\/retroramblings.net\/wp-content\/uploads\/2021\/04\/FreqPlot6-300x240.png 300w, https:\/\/retroramblings.net\/wp-content\/uploads\/2021\/04\/FreqPlot6-768x614.png 768w, https:\/\/retroramblings.net\/wp-content\/uploads\/2021\/04\/FreqPlot6-375x300.png 375w, https:\/\/retroramblings.net\/wp-content\/uploads\/2021\/04\/FreqPlot6.png 1280w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>Pretty much unaffected.<\/p>\n\n\n\n<p>So while this isn&#8217;t absolute proof, it&#8217;s pretty strong evidence that the reason the hybrid DAC performs so much better on real hardware is that the rising and falling edges aren&#8217;t symmetrical.  Unless it&#8217;s saturated, the PWM cycle contains exactly one rising and one falling edge per cycle, whereas in the 1st-order DAC the number of rising and falling edges in a given time period varies in direct proportion to the input value.  That&#8217;s why the noise takes the form of extra harmonics.  With the 2nd- and 3rd- order DACs the number of edges varies pseudo-randomly within a given time-period, hence we see more broad-spectrum noise.<\/p>\n\n\n\n<p>So this experiment has been an interesting exercise in simulation, but also served as a lesson that I shouldn&#8217;t rely too much on it!<\/p>\n\n\n\n<p>The testbenches, Makefile and DACs used for this series can be found at <a href=\"https:\/\/github.com\/robinsonb5\/DACTests\">https:\/\/github.com\/robinsonb5\/DACTests<\/a><br><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Exploring audio DACs in simulation &#8211; Part 3 &#8211; 2021-04-03 Having set up a simplistic testbench in Verilator, I want to be able to use it to evaluate several different DACs. The ones I will be testing are: The simple &hellip; <a href=\"https:\/\/retroramblings.net\/?p=1578\">Continue reading <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[4,8],"tags":[],"class_list":["post-1578","post","type-post","status-publish","format-standard","hentry","category-fpga","category-hardware"],"_links":{"self":[{"href":"https:\/\/retroramblings.net\/index.php?rest_route=\/wp\/v2\/posts\/1578","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/retroramblings.net\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/retroramblings.net\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/retroramblings.net\/index.php?rest_route=\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/retroramblings.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=1578"}],"version-history":[{"count":5,"href":"https:\/\/retroramblings.net\/index.php?rest_route=\/wp\/v2\/posts\/1578\/revisions"}],"predecessor-version":[{"id":1590,"href":"https:\/\/retroramblings.net\/index.php?rest_route=\/wp\/v2\/posts\/1578\/revisions\/1590"}],"wp:attachment":[{"href":"https:\/\/retroramblings.net\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1578"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/retroramblings.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=1578"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/retroramblings.net\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=1578"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}