DIY DSP for Digital Room Correction

Status
This old topic is closed. If you want to reopen this topic, contact a moderator using the "Report Post" button.
If I create my own simple "filter" by entering coeffs manually in a text file I can actually get some sort of filtering happening now without tons of distortion / clipping. When I say a simple filter, I mean very simple like using taps of 1 2 3 4 5 repeating every 32 taps or something like that - I'm not using proper filter coeffs yet due to the problems below....

@Ozone
I have done my FIR core in VHDL and i can supply you with the code if you like.
A practical hint: When testing the FIR do a simulation with actual coefficients and start testing by supplying a '1' to the system, followed by zero data. When all goes well the coeffiecients come out again.

@Wingfeather
For 32-bit filter coefficients, I tend to use a 2.30 format, since filter coefficients often require a range between plus- and minus-two.
Please elaborate !
It is a question of scaling is it ?

best regards

Simon
 
Originally posted by blu_line
Please elaborate !

I don't know exactly what you're asking, but I'll expand a little on what I meant by "2.30 format".

Fractional binary could be described as 1.(n-1), in that there is one bit to the left of the decimal place and n-1 bits to the right. Since the value of this bit that's to the left of the decimal point is -1, this fractional number system is capable of representing numbers between -1 and 1 - (2 ^ -(n-1)). This is generally how it is thought of, although, as I said in my previous post, the existence of a decimal place is really just a nice imaginary detail that simplifies things.

The problem is that, in IIR filters at least, you often want coefficients that are greater than 1 in magnitude. They can be very large at times, depending on the filter you want, and that's a problem. But most of the time I've personally found a range of plus/minus-two to be fine. In order to do this you move the figurative decimal point one place to the right, to give you a 2.(n-2) format. This doesn't really change anything on it's own, since the binary pattern you get from the multiplier is the same. However, the weighting of the bits at the output is now different and it's important that this is remembered when doing subsequent operations.

In order to use the output of a filter based on the 2.30 format (or, to be clear, a filter based on the 1.31 format for the audio data and the 2.30 format for the coefficients) for a DAC, for example, you must remember to shift values left by one place before outputting them, or else you'll be introducing a gain of -6dB and also losing one bit of resolution. This shift means that it's now possible for things to clip at this stage, so it's important to be careful.
 
Originally posted by abzug
Uh, why not just use floating point? Much harder to mess up.

Implementing floating-point on an FPGA might be a bit of a pain (though there are presumably libraries for this...). Besides, it has its own problems, uses lots more logic, and can't be dithered. Floating point using lots of bits is okay, but the problems of logic usage and general fussyness get worse. I guess it could be worth a try eventually, but I think the OP is having trouble enough with fixed-point so far!


Originally posted by abzug
there's no amount of headroom that can guarantee they won't occur for every possible input

Not true - the maximum output that can occur is very easy to calculate for an FIR filter. The maximum instantaneous output you can get is when the audio samples are all full scale, with their signs matching that of their respective coefficients. In other words, it's the sum of the magnitudes of all of the coefficients.
 
Hi all,

OK, I really want to get back into this again big time. I'll be building my first chipamp very soon when the parts arrive (Mauro RevC), and I want to integrate an FPGA board with the amp (plus some DACs)....

I've been doing some more reading about DSPs etc. and I've been trying to get my head around this Q15 format thing for months now. I can get the general idea, but it still has me stumped as to where exactly the changes need to be made in the way things are calculated. (It's near impossible to find any code examples in Verilog or VHDL for this sort of thing.) The best way to explain this is to tell you what I understand so far....

One common use of the Q15 format is for when you want a way of constraining the result of a multiply operation when using a fixed-point DSP. This is mainly so the result cannot overflow, but it has other benefits / drawbacks depending on what you need....

An example of using Q15 would be if wanted to do a MAC between an audio input sample and a filter coefficient.... On a 16-bit fixed-point DSP, if your two numbers (sample and filter coef) are represented as integers, the result of a multiply is very likely to cause an overflow (since you're likely to want your output to also be 16 bits)....

The idea of the Q15 format is to represent your two numbers as fractions instead of integers. This would mean that when the two numbers are multiplied together, the result will always be less than 1 (will not overflow / clip your output).

ie. normally, I2S input samples are 16-bit signed (-32768 to +32767). The Q15 format represents the useable range as being between -1 and 1 (technically, the maximum is just below 1 due to the quantization of 15 bits?? AFAIUI)

In Q15, the MSB is the sign bit, but it's common in most DSP purposes for the sign bit to be ignored because you often want the inputs and results to stay "minus" anyway (less than 1).

The part I'm stuck on is whether you need to "convert" your input samples and / or coeffs into the Q15 format first, or if the idea is that the multilplier logic stays the same (as with integers), but you need to scale the results before and / or after the multiply???

In other words, if you assume the incoming I2S audio samples are 16-bit signed numbers, how would I convert these samples to the Q15 format, or do I even need to? Is it simply a case of finding the correct method of scaling the samples before / after calculation, or is a conversion necessary first?

I've seen that you can scale the binary representations back to the "real" fractions by multiplying or dividing by 32767, but is this only needed to check the "real" results? (I'm only interested in how the logic needs to operate on the fixed-point binary numbers inside the FPGA - the 32767 thing confuses things for me.)

I can understand that in the Q15 format, you "think" of the binary point (binary equivalent of the decimal point) as being to the left of bit 15, so does the use of Q15 require a change to the majority of the logic, or was the whole idea of the Q format to keep most logic the same, and only change the input / result scaling? ie. is it more of a binary "trick" than a format as such?

Ok, I also see that when you multiply two Q15 numbers together you end up with a Q30 number (32 bits = two sign bits + 30 "fraction" bits). To "scale" this back to 16 bits, you can simply shift left by one bit, then take the top 16 bits of the 32 bit number as your result. (those 16 bits can be easily routed directly to the next processing stage in an FPGA.)

It's been very frustrating trying to find any simplified info on the Web regarding DSPs and data formats etc. I can see why many people have recommended books before (sorry, what is this "book" thing exactly?!) ;)

Some of the few precious pages I found that explain things in layman's terms (well, almost). (pages 28 to 33)....

http://www.elin.ttu.ee/~olev/lect1.pdf

And page 8....

http://www.fulton.asu.edu/~karam/realdsp/Lectures/FixedPointArithmetics2.ppt#345,8,Q format Multiplication

So, how would I need to scale / convert the incoming audio samples (assuming it's 16 bit "two's" I2S for now.), and how would I go about converting my coefficients from 32 bit float or 16 bit PCM files to Q15 format?

(I realize there are such things as floating point DSP chips, but I need help to understand the fixed point stuff first.)

I know this is a big ask for one post, but any help would be more than appreciated. Thanks in advance for any suggestions.

OzOnE.
P.S. I also understand the need for scaling the coeffs to ensure that the maximum result of the whole FIR filter does not clip the output at any time. I've also read about truncation / rounding / dithering etc., but this could probably wait a while until the Q15 thing makes more sense.
 
Hi,

I did some more reading last night and I've started to make sense of some of this. I think the main confusion is that things are dependant on which "realm" they are in. ie. "real" or "binary / hex". The real numbers only exist on say the PC side and the filter coeffs need to be converted to Q15 first so a fixed-point DSP can process them....

I did a simple practical example of multiplying (using the Q15 format)... I took two random (arbitrary) "real" numbers of 0.23 and 0.38, and converted these to Q15 - I scaled them by first multiplying them by 32767 (the Q15 coeff?)...

0.23 x 32767 = 7536 = 1D70 in HEX
0.38 x 32767 = 12451 = 30A3 in HEX

These numbers have been rounded when stored as Q15 due to quantization (I think that's the general term).

I then took the hex Q15 numbers and multiplied... 1D70 x 30A3 = 597BE50 (Q30 hex?)...

During the initial multiply, the "Q15 coefficient" (32767) applied to the original "real" numbers gets multiplied too, so you need to scale this back down again to get the proper Q15 result....

597BE50 / 7FFF = B2F

This now gives us the Q15 result of the multiply. ie. B2F (hex) = 2863 (decimal)....

Convert to "real" again: 2863 / 32767 = 0.08737449 etc.

What I couldn't get to work is shifting the Q30 result bits left, then taking the top 16 bits as the Q15 result?

So, I can see now that the Q15 format is basically a way of representing the real data inside a fixed-point DSP. The input data has to be converted / scaled first, and some logic has to be changed or added to calculate the correct result(s).

Ok, so if my input audio data is 16-bit signed (-32768 to 32767), how would I normalize using binary operations to make the samples less than 1 and convert to Q15? Or, do I even need to convert the samples?

I hope this can help others too, but please don't take all of this as accurate, as I'm not 100% on anything yet.

OzOnE.
 
My impression from reading those slides is that you don't need to do anything to go between two's complement int and Q15. It's a matter of reinterpreting where the decimal point goes, and using different rules for multiplication. For example, we can multiply 3 by 4 to get 12. We can also multiply 0.3 and 0.4 to get 0.12, but we have to remember to "add the decimals" or whatever they called it back in grade school.

Looking around, people seem to use 32768 as the Q15 multiplier. This makes multiplies and divides clean 15-bit shifts. Going with your example again:

0.23 x 32768 = 7537 = 0x1d71
0.38 x 32768 = 12452 = 0x30a4

0x1d71 x 0x30a4 = 0x5980C64, or
0000 0101 1001 1000 0000 1100 0110 0100 (result in bold)

0000 1011 0011 0000 = 0xB30 = 2864
2864 / 32768 = 0.0874

Sanity check: 0.23 x 0.38 = 0.0874

If we had been working under the int paradigm, we'd have gotten
7537 x 12452 = 93850724
which overflows. But by reinterpreting ints as Q15, we can multiply without worrying about overflow.
 
Hi,

@mako - thanks for the sanity check. I've now found the glaring mistake in why I couldn't "take the top bits". You see, I was using Windoze Calculator to test the theory, and when it gives the answer in binary, it of course doesn't show all the leading zero's! (only 27 bits)...

So, when I took the new answer, the bits were shifted way to far to the right.....

0x5980C64 =
101 1001 1000 0000 1100 0110 0100

I should have noticed they weren't in groups of four bits. Anyway, that's good because I can now simply take the top bits directly as my result in the FPGA design (ignoring extended sign bit). This will mean simply routing the 16 result bits directly instead of doing a logic divide (probably gets routed the same way anyway if the divide is a power of two though.)

It does look like using 32768 is better as it rounds things closer to the real result. The question is, is taking the top bits of the result (apart from the first bit) the same as dividing by 32768 or 32767 ?

(btw, I've already changed most of the logic in the design to work with Q format coeffs.)

I'm now just looking for a simple CD player or something to test it out on. I was using a DVB-T digibox before, but I really need something with a repeatable audio stream. I suppose I could use an old sound card, but a lot of these have crappy integrated DACs (no I2S pins). The other thing is that I only have one FPGA board atm, and I'm using it on my LED projector design. I'm not sure which new FPGA I should buy for the DSP work until I know how much power / logic is really required, so I'll have to borrow the other board again for now.

I think I understand that the DAC data is also in the range of -32768 to 32767, so the Q15 coeffs should produce the correct result when multiplied / accumulated etc. I think what people were saying before (ie. wingfeather) was that without rounding the result before taking the top bits, the output samples would give a -0.5 LSB error, and hence a very small DC offset on the DAC - is this roughly what was meant?

Ok, so when the results of the FIR are accumulated, they can still overflow - to prevent this from happening, is the general idea just to scale the input samples down by a power of two first? (after deciding a suitable scale factor.) Or is it standard practive to just scale the coeffs first?

What worries me about this DSP stuff is that it seems quite difficult to find information on things without finding tons of white papers with complex algebra on. I don't know a thing about theta / delta / sigma / pi / cake! - why are there not many step-by-step examples about FIR?

Things like the following are great for playing with, but they don't show the underlying process of the taps etc....

http://www.htwm.de/vannacke/dfilter/index2.html

What I'm looking for is a simulator where you can add logic blocks in, pass some audio through and hear the results in realtime (especially good if it shows the Q format at work). This could also allow you to see the results one sample at a time. I'm sure software like matlab is great for this, but it's also quite expensive.

Once I got my head around FIR filtering (still a long way to go), all I really needed to know was something like....

1. Take 32-bit float coeffs (or whatever format you have)
2. "Normalize" coeffs to be between -1 and 1
3. Convert coeffs to Q15 (multiply by 32768 and store as 15 bits (+ sign.)
4. Create circular buffer of "x taps" and input one audio sample at a time into buffer (16 bit two's format)
5. Multiply every sample currently in the buffer by it's corresponding coeff.
6. Take the top bits of the multiply to convert result back to Q15 (divide down again).
7. Store Q15 multiply results in accumulator until all taps processed.
8. Output the result from the accumulator to the DAC (after possible scaling.)
9. Shift all audio samples through the taps one place and receive next new sample onto first tap (ie. update circular buffer pointer.)
10. Repeat 5

Again, not exactly a complete explanation, but it's taken me about a year to get this far - I can see the point of understanding the process first, but surely there's a better way for the information to be demonstrated?

I'm very greatful for all the help received on this forum, and I realize that I should help my own naivety and actually read a book for once, so with that in mind, I'll look out for some of the book suggestions I've had so far.

The main thing driving me along is that I'm convinced that long FIR filters can be produced in a far more cost effective way using an FPGA and that expensive DSP solutions aren't necessarily needed. The beauty of FPGA's is that you design the logic around your goal so you utilize things in the most efficient way.

I think I'll test this with 16 bit audio and Q15 filter coeffs first before worrying about 24 bit audio and 32 bit coeffs!

OzOnE.
P.S. Sorry the essay - again!
 
OzOnE_2k3 said:
It does look like using 32768 is better as it rounds things closer to the real result. The question is, is taking the top bits of the result (apart from the first bit) the same as dividing by 32768 or 32767 ?

The rounding off closer is probably just a fluke. What matters is that 32768 is a more convenient number for binary systems, as 2^15 = 32768. And yes, dividing by 32768 is equivalent to right-shifting by 15 bits, which is really easy on a processor or FPGA. Dividing by 32767 would be really nasty, so it's not done that way.

I'm not too well read in DSP so I can't answer your other questions. For learning, I'd suggest Octave, which is the open-source equivalent of Matlab. You should be able to run WAV files through a filter treatment and play them back without too much trouble.
 
Originally posted by OzOnE_2k3
These numbers have been rounded when stored as Q15 due to quantization (I think that's the general term).

It's only a little thing, but rounding and quantisation/truncation aren't quite the same. Rounding is where you round to nearest (e.g. 0.7 -> 1.0), and truncation is where you discard the fractional part (e.g. 0.7 -> 0.0). In terms of total error effects like distortion, the two behave the same. Truncation however ends up with a small negative DC offset. Rounding is what you want (and can be done by adding 0.5 prior to truncating).


Originally posted by mako1138
My impression from reading those slides is that you don't need to do anything to go between two's complement int and Q15

That's correct. The whole thing is conceptual, and all two's complement gives you is a fraction of whatever range you want to pretend you're using.


Originally posted by OzOnE_2k3
I can now simply take the top bits directly as my result in the FPGA design (ignoring extended sign bit). This will mean simply routing the 16 result bits directly instead of doing a logic divide (probably gets routed the same way anyway if the divide is a power of two though.)

The first part is right - simply taking the top bits is the way to do it. Be careful when comparing it with a divide, though; the result isn't always the same. For example, 1/2 will often come out as a 1 (depending on architecture - writing in C, for example, on the x86), when a right shift will clearly give you zero.


I think the thing to remember is that it's all conceptual. When multiplying two B-bit numbers in two's-complement format, you're going to end up with a result which is 2B bits long. All architectures calculate this double-length result internally, and the bit-pattern will be the same regardless of whether the inputs are integer or have some decimal point in them somewhere. Whether you get an integer or a fractional value overall just depends on whether you take the bottom-half or the top-half, respectively, of this long result as your output.
 
Also recall that q15 represents numbers between -1.0 and 0.999..., so that multiplier results must also represent correct numbers in that format.

For example, consider 1/2 in q15 = 0x4000. Multiply by 1/2, you get 0x10000000, and taking the top 16 bits as your q15 representation means the 0x1000 converts to 1/8 instead of 1/4!

What happened? Well, it turns out multiplying a 1.15 number by another 1.15 number generates a 2.30 number, which means the result is shifted to the right by 1 with respect to the proper result for a 1.31 number (which you would truncate to 1.15). So the proper thing to do after the multiplier is to shift the result left by 1 before truncation. Try it for yourself.
 
Hi, DSP_Geek,

I understand the need for the shift / taking the 16 bits directly (ignoring the MSB). I'm still a bit hazy at times because I get confused by Windoze calculator, but if I'm programming things in Verilog I know what I'm "asking" the software to do to the bits directly. (if that makes sense! ;) )

My main hurdle was understanding the concept of how Q15 numbers work and what Q format really is.... Many people / web sites just say something like: "The binary point is effectively moved during calculation", whereas I like to think of it as "Magnifying the original (real) value to bring it towards the binary point during calculation".

In reality, what you're doing is magnifying the original (real) values beforehand, then reducing them again after the result. This makes best use of the 16 bit resolution (well, 15 bits + sign) and also "allows" the maths work correctly using the usual integer binary multipliers etc.

(The only extra logic you really need to add for Q15 multiplies is for taking the top 16 bits of the Q30 result.)

So, as long as you take the top 16 bits and ignore the MSB of the multiplied result, it should all work. ie. the full 32-bit result of 0x4000 (0.5) multiplied by itself is....

0001 0000 0000 0000 0000 0000 0000 0000

Take the top bits (ignoring MSB), or shift to the left by 1 bit first....

= 001 0000 0000 0000 0

= 0x2000 (hex result)
= 0.25 (real number)

Again, this is somewhat confused by the fact that taking the top bits is not actually producing your "real-world" (original) value. The reason for taking the top bits is only to convert the Q30 result back to Q15 format again. You have to do this because when the multiply occurs with two Q15 values, the Q15 "scaling factor" (32768) also gets multiplied at the same time.

I hope this is mostly correct. Again, this is only my understanding of it so far, and I'm posting this from a definite learners point of view (I'm NO expert!)

I've actually got a nice DSP board on the way to me now (TMS320C6711 DSK). I'll try to get some long filters running on that first, then hopefully I can come back to the FPGA stuff with a better understanding. The FPGA design is actually producing results now though (with a very short FIR).


OzOnE.

P.S. I now have a nice Mauro RevC stereo kit built with a 300VA toroid and Twisted Pear PCBs !!! For anyone wondering about power output from these little LM3886 chips - it easily beats my Denon AVC-A11SR for sheer dynamics and output (before clipping). The Denon is rated at 125 Watts RMS into 8 ohms (20hz-20KHz), so the RevC amp must be doing some sort of voodoo, or just shows that it's much better designed?
 
(I tried to change the above post, but the evil 30 minute rule kicked in.)....

Corrections - "Scaling the original (real) value to best fit your 15 fixed bits."

+ "and also allows you to "keep" the same multiplier logic as you were using for integers."

New - OK, so some Q format "prerequisites" are:

1. Convert all your original coeffs to fractional values (confine to between -1 and 1 to help prevent overflow of final results)...
2. Convert these fractions to Q15 format by multiplying by 32768 (will loose a small amount of precision when fit into 15 bits)
3. When two Q15 values are multiplied, take the top 16 bits of the 32-bit result (ignore extended sign / MSB).

You don't necessarily have to "see" the real coeff values at all during the processing inside the DSP, you just keep them in Q15 right the way through. If you need to see or check the real values again, just divide the Q15 value by 32768.

(for an example; you would normally only see the real coeff values when you initially generate your filter taps on the PC.)

I now also get the point about the audio samples - because they are (usually) already confined to a 16-bit signed number (15-bits + sign), they can generally be thought of as already "being in" Q15 format. Because of this you can process them with Q15 methods (as long as it doesn't introduce things like large DC offsets or clipping at the output etc.)

OzOnE.
 
Status
This old topic is closed. If you want to reopen this topic, contact a moderator using the "Report Post" button.