Search code examples
overflowvhdladditionnumeric

VHDL numeric_std function ("+")


everyone!

Please, can you explain me a strange thing. The numeric_std library contains a function "+", addition. I inserted a description of the function.

-- Id: A.3 function "+" (L, R: UNSIGNED) return UNSIGNED; -- Result subtype: UNSIGNED(MAX(L'LENGTH, R'LENGTH)-1 downto 0). -- Result: Adds two UNSIGNED vectors that may be of different lengths.

For instance, we have:

    a      : in  unsigned(2 downto 0);
    b      : in  unsigned(1 downto 0);
    result : out unsigned(2 downto 0);

So, If a is "111" and b is "01" the result will be "000". The reason is an overflow. My question is why the library allows it, why it does not expand the results length? Why a compiler does not warn me about the overflow? Is it possible to add the values without the overflow? And how should I prevent or find to occur the overflow.

Thank you!


Solution

  • In hardware, we have incrementers and decrementers - which "roll over" as part of their normal operation, we have accumulators - which should never roll over, and we have math units which need to maintain full precision of the operation.

    In VHDL, when we do an operation, it must produce an exact sized result. Incrementers, decrementers, and accumulators all need to keep the same size result, where as math units that need to maintain full precision of the operation need to increase in size.

    Hence, the "+" can only meet one of these needs. The other part needs to be handled by code.

    For numeric_std, the rule for "+" is as you state, the size of the result is the size of the largest array operand. What is interesting is that in ieee.fixed_pkg, the size of the result is the 1 + size of the largest array operand.

    Lets take a look at how this impacts our math.

    signal Co : std_logic ; -- to throw away carry out
    
    signal Incrementer_unsigned : unsigned(3 downto 0) ; 
    signal Incrementer_ufixed   : ufixed(3 downto 0) ; 
    
    signal Accumulator_unsigned : unsigned(3 downto 0) ; 
    signal Next_unsigned        : unsigned(3 downto 0) ; 
    signal Accumulator_ufixed   : ufixed(3 downto 0) ;
    signal Next_ufixed          : ufixed(3 downto 0) ; 
    
    signal Sum_unsigned         : unsigned(8 downto 0) ; 
    signal A_unsigned           : unsigned(7 downto 0) ; 
    signal B_unsigned           : unsigned(7 downto 0) ; 
    signal Sum_ufixed           : ufixed(8 downto 0) ; 
    signal A_ufixed             : ufixed(7 downto 0) ; 
    signal B_ufixed             : ufixed(7 downto 0) ; 
    
    . . .
    
    process (Clk)
    begin
      if rising_edge(Clk) then 
        -- Incrementer / Decrementer with unsigned or ufixed.  Result same size
        Incrementer_unsigned     <= Incrementer_unsigned + 1 ; 
        (Co, Incrementer_ufixed) <= Incrementer_ufixed   + 1 ; 
    
        -- Accumulator with unsigned or ufixed.  Result same size
        Accumulate_unsigned      <= Accumulate_unsigned + Next_unsigned ; 
        (Co, Accumulate_ufixed)  <= Accumulate_unfixed  + Next_ufixed ; 
    
        -- math unit with full precision.  Result size increases
        Sum_unsigned  <= ('0' & A_unsigned) + ('0' & B_unsigned) ; 
        Sum_ufixed    <= A_ufixed           +  B_ufixed ; 
      end if ; 
    end process ; 
    

    So for numeric_std, if we threw a warning for the incrementer, we would get numerous FALSE positive warnings and eventually ignore all warnings - that would be a mistake.

    For numeric_std, the sizing rules work very well for the incrementer and accumulator. The size of results is what is expected. For the Sum_unsigned, we have to upsize one argument (if they are the same size), I chose to upsize both. There is a resize function that I chose not to use.

    For fixed_pkg, for the incrementer and accumulator, we had to downsize the result by using an aggregate on the left hand side to remove the carry out (this code is VHDL-2008). There is also a resize that can handle this. For the Sum_ufixed, the size of the result matched our application.

    One thing this demonstrates is that no matter which rule you pick, there is something the person writing the code must do.

    For Sum_ufixed, the results get much more exciting if we have:

    signal Sum_ufixed10         : ufixed(9 downto 0) ; 
    . . . 
    
    Sum_ufixed10 <= (A_ufixed + B_ufixed) + (C_ufixed + D_ufixed) ; 
    

    Because when you follow the rules, the size of the associativity identical expression ends up one bit bigger. While I have shown parentheses below, it is the same with them.

    signal Sum_ufixed11         : ufixed(10 downto 0) ; 
    . . . 
    
    Sum_ufixed11 <= ((A_ufixed + B_ufixed) + C_ufixed) + D_ufixed ; 
    

    Also note that both packages support "+"[unsigned/ufixed, natural] return unsigned/ufixed, however, the natural argument does not influence the sizing. My take on this is 0 and 1 are simple to use, but other values must be scrutinized. If the integer is truncated, there is a warning, but in my mind maybe it should have been a failure instead (which would be consistent with what happens with array values). This certainly is open for discussion in the IEEE working group (see http://www.eda-twiki.org/cgi-bin/view.cgi/P1076/WebHome).