Sign in to follow this  
D@n

FPGA based PWM generation

Recommended Posts

 

@Piasa,

Back to this comment

On 9/16/2017 at 9:43 AM, Piasa said:

In this application, a signed value must be mapped into "number of cycles on per 2**N cycles".  This is an unsigned value independent of if the input is signed or unsigned.  The design assumes that a 1 output maps to +V and a 0 output maps to -V.  To output 0V, the design would need to output an equal number of 0's and 1's.  In this case, the number of cycles in the period is also related to the precision of the input.  That is not required in general.  However, for this case the operation to get from signed input sample to unsigned number of on-cycles is the same as the conversion from signed to offset binary.

I don't believe that testing of the algorithm on hardware quite agrees with the above description. The description might be correct but I don't believe that the implementation does exactly what it should be doing to agree with the description. Of course I've been wrong about other aspect of this topic.

Here is my test project. I do hope that anyone interested in this conversation will look it over and try it in simulation and on hardware if they can.

 

IPTEST_R1.zip

Share this post


Link to post
Share on other sites

Can you be more specific?

For example, differences in comparisons can affect the design.  IIRC the original blog post used "<=" and "<" in two different snippets.  The difference between comparisons is either a small offset or a phase inversion or both.

Share this post


Link to post
Share on other sites

@Piasa

Yes I can. My testbed had a logic error that stalled the waveform generation under certain conditions. Here is a better version of the project. It now includes the correct testbench and the control program has minor cosmetic improvements.

The general algorithm works with signed or unsigned inputs as you indicate. On the PmodAMP2 there is considerable distortion with tones above 5 KHz but this is not unexpected as the number of samples are low and the load impedance is fixed. For low tones under 1 KHz  the results are pretty reasonable (though not particularly for an audiophile's tastes).

IPTEST_R2.zip

Edited by zygot

Share this post


Link to post
Share on other sites

It should be noted for anyone wanting to replicate this algorithm as a general purpose application that the FPGA IO are not designed to drive highly reactive loads. The 200 ohm series resistor on the Nexys Video JA pins and the first 300 ohm resistor on the PmodAMP2 limit the current into and out of the IO pin. Still there is a considerable amount of capacitive loading. When using IO, and especially when using IO with a non-standard load,  you should do the analysis of power dissipation and voltages as seen by the IO pin.

I you are replicating the project on hardware you should be aware that my version of the IP uses only unsigned values and the msb should not be flipping. It does so as a default only to replicate D@n's code behaviour.

Share this post


Link to post
Share on other sites

@zygot,

Let's pause to straighten up our language for a moment, 'cause it looks like you and I have described the same thing with two different terms and arguing about nothing as a result.  If the input to a traditional PWM is unsigned, ranging from 0-65535, what should the duty cycle be for values 0x0000, 0x4000, 0x8000, 0xc000?  Shouldn't the duty cycle result be 0%, 25%, 50%, and 75% respectively?  This would then produce at a fictional audio port the most negative bias, a halfway negative bias, a zero bias, and a halfway to positive bias respectivvely.  Now, what's wrong with creating this duty cycle, and thus the bias, by simply comparing a counter to the input value, so that if the counter is less than the given value the number should be a one, but in all other respects a zero?  At least, that's what's being done in this beginners example on fpga4fun.com (top example on the page).

Of course, this ignores the off-by-one issue which has been brought up but which I wish to treat separately only after this part of the discussion is settled.

As for any RC response from the board's manufacture, wouldn't such a response from the board simply low-pass filter the FPGA pin output on the way to the analog amplifier?  Judging by the s transform given within the discussion on wikipedia, this certainly seems so.  And, if that's the case, wouldn't that just bring this digital waveform closer to being the audio waveform that it was intended to create?  You do remember your criticism from earlier that the audio chip was expecting an audio input, rather than a digital input, right?

Dan

Share this post


Link to post
Share on other sites

@D@n

 

3 hours ago, D@n said:

Shouldn't the duty cycle result be 0%, 25%, 50%, and 75% respectively?  This would then produce at a fictional audio port the most negative bias, a halfway negative bias, a zero bias, and a halfway to positive bias respectivvely.  Now, what's wrong with creating this duty cycle, and thus the bias, by simply comparing a counter to the input value, so that if the counter is less than the given value the number should be a one, but in all other respects a zero?

As to a PWM , as long as the counter over-flow interval is the same as the input data sample period I agree with your logic. You could also use an up-down counter and center your PWM around the mid-point of the sample period.

3 hours ago, D@n said:

You do remember your criticism from earlier that the audio chip was expecting an audio input, rather than a digital input, right?

Yes, I do remember that criticism. It turns out that I hadn't properly understood the reconstruction filter characteristics. I had always expected that there would be some sort of analog representation of a waveform... just not nearly as good as it is. The SSM2237 still needs an analog input ( preferably driven differentially ). It also expects the analog common-mode input to be between 1V and Vdd-1 V ( for the PmodAMP2 this is 1V-2.3V ). The  Sigma-Delta modulator in the SSM2237 is clocked at about 6 MHz which probably helps smooth the input. As to how well this all works you can download the last version IPTEST_R2 and see for yourself. Assessing the reconstructed analog input to the SSM2237 will be hard to do without a good scope. I may try using some well known PC audio tools to see if it's reasonable to make an assessment of the amplifier output. I suggest that you try really low tones < 200 Hz and really high tones above 5 KHz to see it in action. Really low tones should be quite accurate as there are a lot of samples per sample period. Lastly, you should change the sample scale factor to see low signal levels being reconstructed.

One way to look at the application is to think of a waveform putting energy into a an RC load and removing it proportionally to the duty cycle of the digital waveform. A 50% duty waveform should have no net exchange of energy.  It's a bit more complex than that and as you saw in your reference above the time constant for putting energy into and out of a reactive load is not symmetric. But, counter to my instincts it all works better than I thought it would. That's due to the design of the reconstruction filter. It's important to understand that the analog inputs on SSM2237 on the PmodAMP2 are AC-coupled.

The only argument that I see us as having is about binary representation of values. As I understand it the method that maps input values to pulse density waveforms effectively removes the sign bit. I still have some playing around to do on that regard.

I have not yet made a plan for analyzing the SSM2237 output quality.

 

 

Edited by zygot

Share this post


Link to post
Share on other sites

Oh I forgot that I wanted to mention to the casual observers that if nothing else the project source shows how to create waveforms in Python, upload them into an  FPGA block ram memory and have a waveform generator that runs at a particular sample rate. Python lists are not ideal containers for arrays of things but it does work on Windows and Linux and supports the serial port nicely. If I were using the DPTI interface I'd use C and be a happier camper ( there are a few things about Python that I don't like but it doesn't stop me from using it extensively ). Even if you don't care about PWMs, PDM, the PmodAMP2 or binary numbers there might be something worth slogging through all the excess verbage that Piasa finds irritating. Oh, and don't forget about simulation and verification...

Edited by zygot

Share this post


Link to post
Share on other sites

The system is AC coupled, so no dc output.  You can directly use a 16b unsigned value if you consider yourself to have a 16b unsigned input.  The DC component will be removed.  If you consider yourself to have a 16b signed 2's complement input, you would need to invert the msb.  Otherwise -1 would map to ~100% duty ratio, and -32k would map to 50%.  (0 would map to 0% and +32k would map to ~50%).

Inverting the msb has the effect of converting the 16b signed value into a 16b unsigned value that is equal to x + 32*1024.  -1 maps to ~50%, -32k maps to 0%, 0 maps to 50% and 32k maps to ~100%.

This design maximizes the number of transitions.  Each transition has a cost in terms of parasitics in the circuit.  The goal of maximizing transitions is to move more energy to higher frequencies that the RCRC filter can reject more easily -- the goal being to reduce ripple within a pwm period. 

There are other options. in terms of arranging the bits.  You can also get 2x rate PWM signals or 4x rate PWM for example.

Share this post


Link to post
Share on other sites
On 9/21/2017 at 11:51 PM, Piasa said:

Inverting the msb has the effect of converting the 16b signed value into a 16b unsigned value that is equal to x + 32*1024.  -1 maps to ~50%, -32k maps to 0%, 0 maps to 50% and 32k maps to ~100%.

@Piasa,

Let's create a signal: S=[-8,-3,2,7]

In 4-bit two's compliment binary this is: ST=[1000,1101,0010,0111]

In 4-bit offset binary this is: SO=[0000,0101,1010,1111]

If we change the msb in either binary format from 0 to 1 this has the effect of subtracting 8 (2^3). If we change the msb from 1 to 0 this has the effect of adding 8. Large negative values become small positive values and visa versa.

FST=[0000,0101,1010,1111] = [0,5,-6,-1]
FSO=[1000,1101,0010,0111] = [0,5,-6,-1]

We can't represent S in unsigned binary format unless we halve the magnitude and offset it by 8.


If we have a 4-bit unsigned counter clocked at 100 MHz the bit0 toggles at 50 MHz, bit1 toggles at 25 MHz and so on. The bit-reversed version of this counter has bit3 toggling at 50 MHz, bit2 toggling at 25 MHz and so on. The counter monotonically increases until it wraps from 15 to 0. The bit-reversed counter is: [0,8,4,12,2,10,6,14,1,9,5,13,3,11,7,15] which is now cyclic with varying magnitude. If we compare the bit-reversed counter to any of the 16 possible input values, regardless of how the data was encoded we can get a pulse density representation because of this cyclic nature as long as the input data changes once per counter period.

For D@n's code, the input data sample period of 44.1 KHz with a 100 MHz clock is about 1/30th the counter period. This means that the encoding into pulse density loses about 5 bits of precision. If we want to use this IP as an audio application we need to scale the data to achieve reasonable sound levels whether the amplifier gain is set to 6 dB or 12 dB. I encourage anyone interested to experiment with the test project that I uploaded earlier. 

 

 

Share this post


Link to post
Share on other sites

@zygot,

Let me try and explain (again).  I'll use your notation, it's convenient and should be sufficient.  We'll start with S, in a two's complement format.  You wrote this as ST above.  Now, let's sign extend these values to infinite precision.  I'll use (1*) to indicate an infinite number of 1's extending to the left, and (0*) to represent the same for 0's.  Hence, for infinite precision, you'd have:

IST = [(1*)1000,(1*)1101,(0*)0010,(0*)0111]

If we truncated this value at 8-bits, we'd have

8ST=[1111_1000,1111_1101,0000_0010,0000_0111]

Now, if we add 8 to all of these samples, we'd get ...

8p8ST=[0000_0000,0000_0101,0000_1010,0000_1111] = [0,5,10,15]

If we then truncate this value to 4 bits and interpret it as an unsigned value, we then get

4ST=[0000,0101,1010,1111] = [0,5,10,15]

This is equivalent to toggling the MSB, and then treating the results as though they were unsigned.

These values would then translate to being "ON" 0% of the time, 5/16ths, 10/16ths, and 15/16ths of the time.  This makes sense when the data interval matches the counter interval, and at that point it matches PWM exactly save only having a different permutatation of the output bits.

The next issue has to deal with the period.  You note that 5-bits are lost.  This is both true, and to be expected.  You'll only ever get about 2^11 outputs at 100MHz between 44.1kHz samples (100e6/44.1e3 ~= 2267).  A traditional PWM system would also achieve the same performance.  Since you can't get 2^16 outputs between samples, you won't get 16-bit quality no matter how you arrange the output bits.  I use 16-bit samples not because the device gets 16-bits of precision, but rather because it makes it easier to stuff 16-bit samples into 32-bit bus words.  You could just as easily take a 12-bit sample and shift it up by four so the MSB is in the same position.  (This is my practice as well ...)

One method of getting better resolution within a PWM system is to lower the sample rate.  This allows you to stuff more output intervals within each sample interval, and so get closer to an "exact" voltage.  The problem when applying this approach using traditional PWM signalling is that it lowers the frequency of the PWM waveform -- often into hearing range.  This reduces audio quality.  On the other hand, if you can shuffle the samples around you may get closer to the voltage you want but without the PWM transitions coming into the hearing range.

Dan

 

Share this post


Link to post
Share on other sites
3 hours ago, D@n said:

This is equivalent to toggling the MSB, and then treating the results as though they were unsigned.

Ah, now we're getting somewhere.

The notion of infinite precision numbers is not one that can be expressed in FPGA logic. In logic expressed as gates you cannot take a slice of the low 4 bits of an 8-bit signed signal ( you call it truncation ) and call it an unsigned 4-bit value without changing the meaning of the bits in the new signal.

3 hours ago, D@n said:

If we then truncate this value to 4 bits and interpret it as an unsigned value, we then get

 

And, we can treat any bit in any signal in any manner we want to in an HDL. What we can't do is change the original encoding. When you decide to "truncate and treat the msb as a sign ( polarity ) bit that's what you are doing. It works for this code because of the cyclic nature of the bit-reversed counter being compared to an input ( that is unsigned, two's compliment, offset binary msb flipped or msb not flipped).  This is an important concept because it has to do with HDL and FPGA interpretations. Let me offer an example.

signal a : std_logic_vector(3 downto 0) := "000"

  process(sysclk)
  begin
    if (rising_edge(sysclk)) then
      a <= a+1;
    end if;
  end process;

One might interpret the process above an adder. I don't know of any  synthesis tool that doesn't express this as: a=(a+1) mod 16. This is not truncation but wrapping from 15 to 0. This is expressed as counter logic.

On the other hand

signal a : std_logic_vector(3 downto 0);

signal b : std_logic_vector(3 downto 0);

signal c : std_logic_vector(3 downto 0);

  process(sysclk)
  begin
    if (rising_edge(sysclk)) then
      c <= a+b;
    end if;
  end process;

In an HDL you will get a synthesis error because the sum of two 4-bit signals requires a 5-bit signal. 14 ("1110") + 14 ("1110") = "11100".

So if you are clever you can take advantage of HDL rules and the properties of number representations but you had better know what you are doing.

A concept that works in a strictly mathematical world has to take into account the reality of digital logic, HDL syntax and synthesis interpretation of HDL statements.

Not only does your "better pwm" lose 5 bits of full scale precision at 44.1KHz sampling rate with a 100 MHz clock but a 48KHz or 96KHz or 128KHz sampling rate with the same clock rate it loses even more bits. As to whether or not your PDM is better than a PWM I really don't care as neither is particularly a high quality audio approach. I do care about whether the resulting audio is hurting my hearing which is why I have a scaler to set some sort of sound level. Any audio application requires controlling sound level as well as fidelity in replicating the sample inputs.

I'm assuming that neither you nor Piasa has bothered to play around with the project that I uploaded. You might find it interesting. I run your code along side my code that can have the msb flipped or not. Try it and see what happens. If there's an interest I may upload a newer version that has some instrumentation and allows for changing the sample rate.

If you can find a way to implement a very complicated concept in a very elegant and extremely simple way that doesn't come with negative consequences then you can put the following sign on your office door: "Do not disturb. Genius at Work". For the rest of us consigned to cubicles in a large room we will have to find those negative consequences for our clever solutions

My real complaint of course is not with the general idea of driving an amplifier with a digital signal instead of using an A/D converter. This is interesting if not particularly useful. My complaint is making claims without proper explanation, offering plots that aren't reproducible and obviously not real, and creating confusion in your intended audience.

 

 

 

 

 

Edited by zygot

Share this post


Link to post
Share on other sites

First, VHDL doesn't define "+" for std_logic_vector.  Second, VHDL (for numeric_std and std_logic_arith) defines size(A+B) = max(size(A), size(B)).  "c<=a+b" would not be a synthesis error.  This would be a logical error.  Verilog is similar, but includes the LHS in the size calculation as well.  Last, mod is defined differently for VHDL vs Verilog when negative numbers are used.  That point is just a note.

I haven't really looked at the FPGA implementation because it doesn't answer any interesting questions.  It does not include the reconstruction filter and does not provide any estimate of the output spectrum.  It also does not include the effects of finite rise/fall times or other driver imperfections.

Edited by Piasa

Share this post


Link to post
Share on other sites
9 hours ago, Piasa said:

haven't really looked at the FPGA implementation because it doesn't answer any interesting questions.  It does not include the reconstruction filter and does not provide any estimate of the output spectrum.  It also does not include the effects of finite rise/fall times or other driver imperfections.

Perhaps you'd like to provide your spice models that allowed you to verify all of this. I didn't use spice but I do have a reasonably good scope, which I've been using with my little project source to get a reasonably accurate sense of all of those things.

As to not being interested in what an actual implementation in hardware is doing to a theoretical concept I really don't understand that at all.

Share this post


Link to post
Share on other sites

In the current version of my project I've instrumented the design and allow the VHDL simulation to print out signal states.

testsnippet.txt is an annotated snippet from the testbench output file

RIGOL Print Screen9-28-2017 11_27_18 AM.614.png is a scope shot of a 16 level ramp waveform produced by TestIP.py ( all unsigned values ) with msb flipping R0=0x09

RIGOL Print Screen9-28-2017 11_31_20 AM.215.png is a scope shot of the same waveform without flipping the msb. R0=0x29

RIGOL Print Screen9-28-2017 12_18_24 PM.996.png has Dan's code with the same waveform driving JA pin 1 instead of my code driving it. R0=0x49

For all screen shots R5 is 0xFFFF.

Thu Sep 28 112345 2017.txt is the log file of the 16 level waveform produced by the python test script.

In the scope pictures channel 3 in purple is the reconstructed PDM at C3 on the side connected to the SSM2237 +Vin pin. Channel 1 is JA pin 1 and Channel 2 is JA pin 3 which is my pwm output and Dan's respectively.

At this point I'm bored with silly arguments so unless anyone who has taken the time to actually experiment with this asks any "interesting" questions I'm on to more useful things.

 

 

Thu Sep 28 112345 2017.txt

RIGOL Print Screen9-28-2017 11_27_18 AM.614.png

RIGOL Print Screen9-28-2017 11_31_20 AM.215.png

testsnippet.txt

RIGOL Print Screen9-28-2017 12_18_24 PM.996.png

Edited by zygot

Share this post


Link to post
Share on other sites

@zygot,

You realize that your choice of test has dictated the answer and hidden the MSB issue?  A ramp will look the same regardless of whether or not you swap the MSB.

You might wish to examine a sine wave instead.  A sinewave will look different depending on how you handle the MSB.

Dan

Share this post


Link to post
Share on other sites

@D@n,

Actually the ramp works out fine. The differences are subtle. In the middle picture where my code is driving the amplifier and not flipping bits the PDM has the fewest pulses at the reconstructed ramp minimum whereas your code has the fewest pulses in the middle of the ramp. Sine wave msb issues are more dramatic for sure.

The point of the pictures is this. My the waveform was encoded using unsigned 16-bit data. My VHDL code doesn't know about the existence of signed values and your code thinks that it's converting two's compliment values into unsigned values. And yet the result, for the same data samples, is similar, regardless of the msb ( except for phase ). Except for levels that produce phase reversals all the the sine waves look pretty much the same as well. The annotated snippet is provided to encourage those  who want to understand what's happening where to look. And it's all about that bit-reversed counter.. which is analogous to electrical commutation.. and that simple 4-bit table showing two's compliment and offset binary encoding.

I could show pictures of sine waves with phase reversals (inversions) but I don't think that this would resolve anything that we've discussed.

I could also show a 5512.5 Hz tone that has been scaled by 1/16 to make the resulting sound level reasonably tolerable ( now we've lost the bottom 5 bits as well as the top 4 and are left have 8  7-bit samples ) and show a pretty distorted and noisy sine wave but that's up to anyone with the interest to do for themselves. While I am sure that both you and Piasa are wrong about what is happening with this algorithm I don't feel a need to convince either of you. If anyone is interested in working this out for themselves, the project files at here.  Those with an inquisitive mind will find a lot to play with. Those who prefer assumptions over experimentation aren't my audience. I might be wrong about this ( I've been wrong about other things related to this thread...) but I don't think that there are many people following this thread.

PS. I'll need some encouragement to post more on this topic. Otherwise that PmodAMP2 is going into the a drawer to spend the rest of it's life ( unless I can think of a non-audio application.... hmmmm)

Edited by zygot

Share this post


Link to post
Share on other sites
6 hours ago, zygot said:

While I am sure that both you and Piasa are wrong about what is happening with this algorithm I don't feel a need to convince either of you.

I'm not entirely certain what your argument is.

 

9 hours ago, D@n said:

A ramp will look the same regardless of whether or not you swap the MSB.

The PDM waveforms can be different when the 15 lsb of the ramp's first half do not match the 15 lsb of the ramp's second half.  In that case, the values provided to the comparator will be different.  The analog waveform should look similar, except for any DC offset (which is not possible with the AC-coupled input) or any phase shift (which is not visible without an external reference).  

--Added:

I will answer the question I think is being discussed /wrt encoding.  At a minimum, an input value (A) that is considered higher in value than a second input (B) should result in a higher density of the PDM output.  sumOfOnesPerPeriod(A) > sumOfOnesPerPeriod(B).  This is true for all encoding -- unsigned, signed 2's complement, signed 1's complement, zig-zag, etc...  For unsigned, this property is automatic.  For signed 2's complement, this becomes a simple operation of inverting the msb.  For 1s complement this is a bit more difficult because 1's complement addition does have feedback from the msb to the lsb.  For zig-zag this would be (i >>> 1) ^ -(i & 1) ^ 0x8000.

Edited by Piasa

Share this post


Link to post
Share on other sites

@Piasa,

In implementation the idea works fairly well regardless of how the input data is encoded. This is not, in my experience, how things work in FPGA development but your algorithm is a special case. As a consequence you can get the same reconstructed waveform shape with a high pulse density or a low pulse density. I assume that the AC coupling helps with that. 

An unfortunate "feature" of this PDM encoding is that the msb's of the word being compared to the bit-reversed counter are really important. As the counter period is not related or synchronized to the sample period the effect of the bit-reversed counter low bits changing from sample period to sample  period have a greater prominence in the pulse density and reconstructed output. This is why scaling the input to achieve a tolerable sound level has such a negative effect on the reconstructed signal. At least that's how I understand it.  Of course there's more going on because the modulator in the SSM2377 is converting the reconstructed input, which is not necessarily within the common-mode input specification and creating a different PDM to drive the speaker. 

Another unfortunate "feature" is that higher input data sample rates relative to a given clock rate have a greater loss in precision. Usually, we want higher sampling rates to improve fidelity.

Lastly, the PDM accuracy doesn't scale well with increasing input data width as it would using traditional A/D converters.

 

Edited by zygot

Share this post


Link to post
Share on other sites
2 hours ago, zygot said:

In implementation the idea works fairly well regardless of how the input data is encoded. This is not, in my experience, how things work in FPGA development but your algorithm is a special case. As a consequence you can get the same reconstructed waveform shape with a high pulse density or a low pulse density. I assume that the AC coupling helps with that. 

This is not correct.  For the ramp case it mostly works.  The ramp is a test signal that minimizes the importance of the signed/unsigned input differentiation.  The choice to invert the msb or not is based on if the input is signed/unsigned.  A test signal that alternates between +1 and -1 would show this best.  After all, if the msb is not inverted this would be 0x0001 to 0xFFFF vs 0x8001 to 0x7FFF.  This is ~0% to ~100% vs ~50% to ~50%.

2 hours ago, zygot said:

Another unfortunate "feature" is that higher input data sample rates relative to a given clock rate have a greater loss in precision. Usually, we want higher sampling rates to improve fidelity.

Is this in comparison to a traditional PWM system or a traditional PDM system?

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
Sign in to follow this