Numeric_std issue.

This article describes an error that I found only recently in NUMERIC_STD.

Conclusion : do not multiply signed or unsigned vectors by an integer !

Description

The IEEE numeric_std library issued (eg) in Nov 1994, and which (as of March 2017) is still used in the latest versions of the Simulation and Synthesis tools, implements incorrectly the multiplications of signed/unsigned vectors by an integer.

Fixing these functions is not difficult but my attempt to have the VHDL working group adopt the fix has been so far unsuccessful. But even if I had quickly succeeded, the change would not have been available probably for many (many) years !

So it’s best that you understand the problem and learn how to avoid it.

Functions affected

Here are the original prototypes (in IEEE.numeric_std) :

   -- Id: A.17
 function "*" ( L: UNSIGNED; R: NATURAL) return UNSIGNED;
    -- Result subtype: UNSIGNED((L'length+L'length-1) downto 0).
    -- Result: Multiplies an UNSIGNED vector, L, with a non-negative
    --         INTEGER, R. R is converted to an UNSIGNED vector of
    --         SIZE L'length before multiplication.

    -- Id: A.18
 function "*" ( L: NATURAL; R: UNSIGNED) return UNSIGNED;
    -- Result subtype: UNSIGNED((R'length+R'length-1) downto 0).
    -- Result: Multiplies an UNSIGNED vector, R, with a non-negative
    --         INTEGER, L. L is converted to an UNSIGNED vector of
    --         SIZE R'length before multiplication.

    -- Id: A.19
 function "*" ( L: SIGNED; R: INTEGER) return SIGNED;
    -- Result subtype: SIGNED((L'length+L'length-1) downto 0)
    -- Result: Multiplies a SIGNED vector, L, with an INTEGER, R. R is
    --         converted to a SIGNED vector of SIZE L'length before
    --         multiplication.

    -- Id: A.20
 function "*" ( L: INTEGER; R: SIGNED) return SIGNED;
    -- Result subtype: SIGNED((R'length+R'length-1) downto 0)
    -- Result: Multiplies a SIGNED vector, R, with an INTEGER, L. L is
    --         converted to a SIGNED vector of SIZE R'length before
    --         multiplication.

Issue

As we can see above (in the comments), when multiplying a vector by an integer, the integer is converted into a vector of the same width as the other operand !!!

As a consequence, the result’s width is forced to two times the width of the signed/unsigned vector, just as if the vector was squared (multiplied by itself), which absolutely does NOT make sense.

The result is either too short or too large.

Consequences

  • Multiplying a vector by 1 (or a small integer) creates a vector twice as large.
    Quite inefficient, a bit ridiculous, but relatively harmless.
  • Multiplying a 128-bits vector by 7 (eg) creates a 256-bits results.
    Same remark as above.
  • The result of Multiplying an 8-bits unsigned vector by 256 is a 16-bits vector (okay by chance) but with a value of ZERO ! See the test case included.
    This is definitely VERY WRONG :-( and the multiplication result is not usable.
  • Multiplying a 8-bits signed vector by 1000 (decimal) produces an incorrect result (actually V * 232) and the result is limited to 16 bits anyway.
    This is also very wrong.

Note that simulators will typically issue truncation warnings during the simulation (run-time) in the most offending cases, or refuse to compile if the result width is not what the user believed (which is how I uncovered the issue).
But Synthesis tools will compile and generate hardware which can potentially produce incorrect results, and this is not acceptable.

Are these functions useful ?

Certainly. They are required by the principle of numeric_std which is to extend arithmetic operators to vectors that represent numbers (signed and unsigned).

Moreover, Synthesis tools are usually relatively smart when they see multiplications by constants, in which case they know how to replace the multiplication by adder(s).

However, their use has been limited (which explains why the incorrect implementation hasn’t been reported heavily before).
One can note that the older Synopsys library « std_logic_arith » did not provide the multiplication of signed/unsigned vector by integers, and therefore could not have the same problem.

Repairing NUMERIC_STD ?

Fixing the affected functions is not complicated : it suffices to convert the natural or integer into a 32-bits vector !
The resulting width at least starts making some sense (=Operand width + 32) and no truncation / incorrect result can occur.
If the result is still too large for you (like when you multiply by an integer range), you just have to resize the result. If you loose information in the resize (you resized into a too short vector), you would get a run time warning as usual.

BUT, the issue is that numeric_std will probably never be fixed !

So you have to take care of your code and make sure you are not affected by the library errors, as explained below.

Conclusion

In spite of the library clumsiness (shift operators, and this bug in particular), I still keep recommending using numeric_std instead of other non-IEEE libraries.

My VHDL Coding Style Guide is updated :

  • Do not multiply signed/unsigned vectors by Integers.
    • Use slices and adders if you multiply by an integer constant
    • Convert the integer in a properly sized signed or unsigned vector before multiplying.

and the older recommendation remains :

  • Avoid using shift/rotate operators from numeric_std (use slices & concatenation)

Documents à télécharger