Search code examples
vhdl

How to divide a vector by two in VHDL using 2 complement?


I have a vector and I want to divide it by two in 2 complement. I did the following but I am not sure at all if this is good or not. Any help if this is correct or not?

Data_in  :in  std_logic_vector(15 downto 0) ;

Data_out :out  std_logic_vector(15 downto 0) ;

-- these vectors in 16 it

variable tmp : integer ;

tmp := conv_integer(Data_in  ) / 2 ;

Data_out <= conv_std_logic_vector (tmp,8);
-------- i will add these lines so the question would be posted 
-- come on :((((((((((((((((((((

thanks in advance !


Solution

  • Any help if this is correct or not?

    It's not correct. The reason is due to a semantic error that would show up during simulation (not shown here, the question lacking a Minimal, Complete and Verifiable example).

    Data_out <= conv_std_logic_vector (tmp,8);
    

    The length of the expression on the right hand side is determined by the second parameter to conv_std_logic_vector (the 8). There is a requirement when simulating that there is an element in the effective value for each element of the target signal. (IEEE Std 1076-2008 14.7.3.4 Signal update, para 2, b)). You'd get a simulation error.

    Correcting that in your snippet would be passing the correct length:

    Data_out <= conv_std_logic_vector (tmp, 16);
    

    Without a Minimal, Complete and Verifiable example it's not possible to determine if you've made another error.

    Type INTEGER has a minimum range (the actual range is implementation dependent, see 5.2.3.2 Predefined integer types where the minimum range is –2147483647 to +2147483647. No VHDL synthesis tools support wider range integer values. For binary values falling within that range there's nothing wrong with using conversion to integer and dividing by 2.

    Historically you could use shifts for powers of two division.

    Juergen's comment suggests shifting right one bit and preserving the sign would look like:

    Data_out <= Data_in(15) & Data_in(15 downto 1);
    

    That works fine for numbers 0 or greater. For numbers less it rounds down odd numbers due to two's complement representation. That can be overcome by adjusting the shift input, adding 1 to odd negative dividend values:

        if Data_in(15) = '1' and Data_in(0) = '1' then -- odd negative numbers 
            Data_out <= SHIFT_RIGHT (Data_in + 1, 1);
        else 
            Data_out <= Data_in(15) & Data_in(15 downto 1); -- SHIFT_RIGHT(Data_In, 1)
        end if;
    

    (Data_in and Data_out are type signed, along with function SHIFT_RIGHT from IEEE package numeric_std.)

    VHDL synthesis tools generally can divide by powers of two. For example using IEEE package numeric_std:

    Data_out <= std_logic_vector(signed(Data_in) / 2);
    

    Where the std_logic_vector value is treated as a two's complement value (type signed). The synthesized logic implements this without an actual divider.

    Xilinx Vivado (see UG901 Synthesis) supports VHDL division by powers of two for static right operands as here. See Table 5-10 VHDL Constructs and Support Status in the user guide).

    Note: Synopsys packages are referred to as legacy packages in the Vivado user guide. Synthesis tools have evolved to deal with division since Synopsys released their std_logic_arith, std_logic_unsigned and std_logic_signed packages. The IEEE provides standard numeric packages that include division operators for std_logic_1164 based array types.

    Code examples:

    library ieee;
    use ieee.std_logic_1164.all;
    use ieee.numeric_std.all;
    
    entity signed_div_2 is
    end entity;
    
    architecture foo of signed_div_2 is
        signal Data_in:  signed (15 downto 0) := (1 => '0', others => '1');
        signal Data_out: signed (15 downto 0);
    
        function to_string (inp: signed) return string is
                variable image_str: string (1 to inp'length);
                alias input_str:  signed (1 to inp'length) is inp;
            begin
                for i in input_str'range loop
                    image_str(i) := 
                         character'VALUE(std_ulogic'IMAGE(input_str(i)));
                end loop;
                return image_str;
            end function;
    begin
        process
        begin
            report LF & HT & "Data_in = " & to_string (Data_in) & 
                   " (" & integer'image(to_integer(Data_in)) & ")";
            Data_out <= Data_in(15) & Data_in(15 downto 1);
            wait for 0 ns; -- so Data_out is updated
            report LF & "By right shift (wrong)" & LF & HT &
                    "Data_out = " & to_string (Data_out) & 
                    " (" & integer'image(to_integer(Data_out)) & ")";
            Data_out <= Data_in / 2;
            wait for 0 ns; -- wait for update
            report LF & "Signed divide by 2" & LF & HT &
                    "Data_out = " & to_string (Data_out) & 
                    " (" & integer'image(to_integer(Data_out)) & ")";
            if Data_in(15) = '1' and Data_in(0) = '1' then -- odd negative numbers 
                Data_out <= SHIFT_RIGHT (Data_in + 1, 1);
            else 
                Data_out <= Data_in(15) & Data_in(15 downto 1);
            end if;
            wait for 0 ns; -- wait for update
            report LF & "By adjusted right shift" & LF & HT &
                    "Data_out = " & to_string (Data_out) & 
                    " (" & integer'image(to_integer(Data_out)) & ")";
            wait;  -- once only
        end process;
    end architecture;
    

    The types of Data_in and Data_out have been changed to signed to avoid numerous type conversions. Simulating the model gives:

    ghdl -r signed_div_2
    signed_div_2.vhdl:25:9:@0ms:(report note):
      Data_in = 1111111111111101 (-3)
    signed_div_2.vhdl:29:9:@0ms:(report note):
    By right shift (wrong)
      Data_out = 1111111111111110 (-2)
    signed_div_2.vhdl:34:9:@0ms:(report note):
    Signed divide by 2
      Data_out = 1111111111111111 (-1)
    signed_div_2.vhdl:43:9:@0ms:(report note):
    By adjusted right shift
      Data_out = 1111111111111111 (-1)
    

    The first Data_out report for shifting without adjustment shows that the result is wrong.

    The second Data_out report is uses signed divide by 2 and is correct.

    The third Data_out report uses the adjustment for odd negative values to give the correct result. The adjustment implements a carry tree in synthesis, all it's right operand bits are static and carry trees are optimally implemented in FPGAs.

    For legacy packages or applications not being able to use the division operator and requiring adjusted shifting the SHIFT_RIGHT function from numeric_std can be replaced by the SHR function from package std_logic_arith, the to_integer function would be similarly replaced by CONV_INTEGER and the to_signed function replaced by CONV_SIGNED.

    The to_string function is predefined in -2008 compliant VHDL tools. Included here to support older simulators.