Electronic – Qm.n multiplication in VHDL

vhdl

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?

Best Answer

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...