3
\$\begingroup\$

I am getting my head around the process of multiplying two Qm.n numbers, and producing an answer of the same width.

As an example, I will pretend I have an 8 bit Q2.5 number. I understand that this format can represent a number in the range -4 to 3.96875.

I understand that multiplying two 8 bit SLVs results in a 16 bit SLV, I'm just not sure what each bit means in this 16 bit result.

Here is my algorithm:

  1. multiply the two 8 bit SLVs, to get one 16 bit result
  2. shift the result left by the number of fractional bits, 5 in this case
  3. assign the lower 8 bits as the result

Is there anything more to it than this? Why do the number of integer bits not come into it? Here is a function I've written to try do this:

function qmult(a : signed; b : signed; n : integer)
return signed is
    variable tmp0 : signed(a'length * 2 - 1 downto 0);
    variable tmp1 : signed(a'length * 2 - 1 downto 0);
    variable ret_val : signed(a'length - 1 downto 0);

begin
    tmp0 := a * b;
    tmp1 := tmp0 srl n;
    ret_val := tmp1(a'length - 1 downto 0);
    return ret_val;
end qmult;

Two main questions to conclude:

  1. Why is there no such function in numeric_std if it is this simple?

  2. From my understanding, if you are unsure of the Q format of two numbers, then is is impossible to get a proper multiplication result (you don't know the fractional bits, so step [2] of the above algorithm won't work) or is there a hole in my understanding?

\$\endgroup\$
0
\$\begingroup\$

One reason such a function doesn't belong in numeric_std is that, in practice, you may need more control of the details...

Addition is quite straightforward and most tools and technologies implement it well.

But multiplication is difficult enough that FPGA manufacturers devote chunks of FPGA area to providing 18-bit signed multipliers with associated logic. Synthesis tools will use these, but perhaps not optimally. If you need a 32-bit multiply, you might get a badly pipelined (slow!) multiplication that you can improve on by splitting the multiply into 4 and summing partial products yourself. (synth tools are improving, so this may no longer be true).

Or you may need to round, or dither, instead of truncating the product.

Or one input is a constant, so that KCM (constant coefficient multipliers) unrolled in hardware yields a more efficient solution.

So multiplication is still not a one-size-fits-all operation, and it certainly wasn't when numeric_std was created. As Martin Thompson says, look at the newer fixed-point library for what is possible now.

As for performing your own fixed point scaling and truncation; I find it easier to reason starting at the MSB and working down...

Given your 8-bit Q2.5 format (signed!) numbers,

s_mm.nnnnn * s_mm.nnnnn = ss_mmmm.nn_nnnn_nnnn

just remember that multiplying the sign bits effectively gives you 2 identical sign bits EXCEPT for the case -4.0*-4.0 (more generally, both inputs -2**m). If you can guarantee this doesn't happen (e.g. you control the filter coefficients) you can simplify handling this case...

\$\endgroup\$
2
\$\begingroup\$

1) Possibly because as you say it's easy enough to implement yourself;

2) Correct, you have to know where the "binary point" is.

I find the concept of the binary point (like decimal point, but for binary) to be very useful when dealing with fixed point.

     aaa.aaaaa
x    bbb.bbbbb
= nnnnnn.nnnnnnnnnn

The other way of thinking of it is that everything is implicitly multiplied by (1 << number of fractional bits):

A*(1<<5) * B*(1<<5) = (A*B) * (1<<10)
\$\endgroup\$
0
\$\begingroup\$

fixed_pkg has all the things you need: http://www.eda.org/fphdl/

The sizing rules for the operations in fixed_pkg are thoroughly in the user guide available from the website above, but the package contain a convenient function that does the resizing for you without affecting the scale of the signal, and also handles truncation and saturation according to your configuration:

architecture RTL of mult is
    signal a, b : sfixed(2 downto -5);
    signal y : sfixed(2 downto - 5);
begin

y <= resize( a * b, y);
end;
\$\endgroup\$
  • \$\begingroup\$ This is what Martin Thompson suggested in the first comment under the question. \$\endgroup\$ – Joe Hass Nov 21 '13 at 11:52
  • \$\begingroup\$ I know, but a comment is not an answer, and Martin Thompson knows that, just check his impressive reputation. Answering question as comments goes against the StackExchange model: electronics.stackexchange.com/help/behavior \$\endgroup\$ – trondd Nov 21 '13 at 11:56

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.