NUMERIC_STD Issues.

This article describes two problems I found in NUMERIC_STD.
Conclusions : do not multiply signed or unsigned vectors by an integer, and be careful with with the resize function !

Description

The IEEE numeric_std library issued (eg) in Nov 1994, and which (as of 2022) is still used in the latest versions of the Simulation and Synthesis tools, implements incorrectly the multiplications of signed/unsigned vectors by an integer. Moreover, the resize function it includes can produce incorrect results without warning.

Fixing these functions is not difficult but my attempts to have the VHDL working group fix this library have been unsuccessful. As a consequence, you should understand and take into account these “features”. This is the purpose of this page.

Multiplication issue : 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.

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 lose information in the resize (you resized into a too short vector), YOU WILL NOT GET A WARNING.

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, resize issue, 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.
    • Keep in mind that if resize creates an incorrect value due to truncation, you will NOT be warned !

and the older recommendation remains :

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

And finally : kudos to the brilliant Technical Support at Mentor / Model Technology !

In the same section…