raaj
raaj

Reputation: 3291

Subtractor Module VHDL generating wrong values

I have a code as such below that is designed to do subtraction and addition. Basically, when Binv is set, it should subtract, and Binv is 0, it should add. Unfortunately, it seems to be adding when Binv is set sometimes, and subtracting when it isn't set sometimes. Here is a snapshot of the simulation:

entity ADD_SUB is
Port ( A : in  STD_LOGIC_VECTOR (31 downto 0);
       B : in  STD_LOGIC_VECTOR (31 downto 0);
     Binv : in  STD_LOGIC;
     C_in: in  STD_LOGIC;
       S : out  STD_LOGIC_VECTOR (31 downto 0);
     TEST : out  STD_LOGIC_VECTOR (31 downto 0);
       C_out : out  STD_LOGIC
       );
end ADD_SUB;

architecture ADD_SUB_ARCH of ADD_SUB is
signal S_wider : std_logic_vector(32 downto 0);
begin

process (A,B,C_in,Binv)
begin

if Binv = '0' then
    S_wider <= ( A(31) & A) + ( B(31) & B) + C_in;
elsif Binv = '1' then
    S_wider <= (A(31)& A) + ('1'& not B) + '1';
else
    S_wider <= std_logic_vector(to_signed(0,32));
end if;

S <= S_wider(31 downto 0);
C_out <= S_wider(32);

end process;

enter image description here

I am getting nonsensical results which make no sense. In the first case, you can see that I tried to do (50 - 30) (Binv is 1). I get 80 which is wrong. You can see however that it works on (30 - 50) where I get -20. Second problem is where I try to do (30 + (-50)), however it shows up as 20.

The results are completely off and I can't see where I am going wrong

Upvotes: 0

Views: 400

Answers (3)

user1155120
user1155120

Reputation:

Jim is absolutely correct.

There are a couple of points that may be worth making.

First, the + C_in or + not C_in implies two 32 bit adds, one of which gets optimized away during synthesis leaving just the carry in to the remaining add.

Second, you are really only manipulating B and C_in using Binv. Subtraction is the equivalent of adding the two's complement, for B the one's complement + X"00000001. Note that Jim inverts C_in with Binv which allows C_in to be used for daisy chain operations (e.g. a 64 bit add or subtract with a 32 bit ALU).

Both points are illustrated with the following code, which also only uses numeric_std.unsigned and and only needs the unsigned numeric_std."+":

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity add_sub is
    port ( 
        a:      in  std_logic_vector (31 downto 0);
        b:      in  std_logic_vector (31 downto 0);
        binv:   in  std_logic;
        c_in:   in  std_logic;
        s:      out std_logic_vector (31 downto 0);
        test:   out std_logic_vector (31 downto 0);
       c_out:   out std_logic
    );
end entity;

architecture foo of add_sub is

begin

UNLABELLED:
    process (a,b,c_in,binv)
        variable x,y,z: std_logic_vector (33 downto 0);
    begin
        x := a(31) & a & '1'; -- this '1' generates a true carry in to z(1)
                              -- z(0) is optimized away as unused it's carry
                              -- retained as carry in to the next MS bit.
        if binv = '0' then
            y := b(31) & b & c_in;
        elsif binv = '1' then
            y := not b(31) & not b & not c_in;
        else 
            y := (others => 'X');  -- 'X' on binv is propagated from b onto y
        end if;

        z := std_logic_vector( unsigned(x) + unsigned(y));  -- only one add

        c_out <= z(33);
        s <= z(32 downto 1);

    end process;
end architecture;

This above example connects C_in a bit more directly to the adder stage with the LS bits of A and B and gives:

tb_add_sub_nvc.png (The image is can be clicked to open)

(Synthesis software is generally smart enough to do all this with using Jim's form modified to either add or subtract based on Binv and A and B extended to 33 bits without any direct bit or bitfield manipulation, our synthesis tools have had more than 25 years to get it right.)

The waveform was produced with the following test bench:

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity tb_add_sub is
end entity;

architecture foo of tb_add_sub is
    signal a:       std_logic_vector (31 downto 0) := (others =>'0');
    signal b:       std_logic_vector (31 downto 0) := (others =>'0');
    signal binv:    std_logic := '0';
    signal c_in:    std_logic := '0';
    signal s:       std_logic_vector (31 downto 0);
    signal test:    std_logic_vector (31 downto 0);
    signal c_out:   std_logic;

begin

DUT:
    entity work.add_sub
        port map ( 
            a => a,
            b => b,
            binv => binv,
            c_in => c_in,
            s => s,
            test => test,
           c_out => c_out
        );

STIMULUS:
    process
    begin
        wait for 100 ns;
        a <= std_logic_vector(to_signed(50,a'length));
        b <= std_logic_vector(to_signed(30,b'length));
        wait for 100 ns;
        binv <= '1';
        wait for 100 ns;
        binv <= '0';
        a <= std_logic_vector(to_signed(30,a'length));
        b <= std_logic_vector(to_signed(-50,b'length));
        wait for 100 ns;
        binv <= '1';
        b <= std_logic_vector(to_signed(50,b'length));
        wait for 600 ns;
        wait;
    end process;
end architecture;

Upvotes: 1

Paebbels
Paebbels

Reputation: 16221

An AddSub module has only one control input lets call it \bar{add}/sub. This means, if add_sub is zero perform an add operation, if its one perform a subtraction.

There is a solid relation between C_in and Binv. If you want to add Binv and C_in are zero, if you want to subtract both are one.

The equation for an adder is simply S := A + B + 0 for a subtracter it can be retrieved by some transformations:

S := A - B                           -- transform into an add operation
S := A + (- B)                       -- transform negative number using 2's complement
S := A + ( 2's complement of B)      -- transform 2's complement into 1's complement
S := A + ((1's complement of B) + 1) -- transform 1's complement into bit wise not operation
S := A + ((bit wise not of B) + 1)

If you combine both equations you will get:

S := A + (B xor vector(add_sub)) + add_sub

So in VHDL this would be:

S_wider <= unsigned('0' & A) + unsigned('0' & (B xor (B'range => add_sub))) + unsigned((B'range => '0') & add_sub);
S       <= S_wider(S'range);
C_out   <= S_wider(S_width'high);

Synthesis is smart enough to find a 3:1 adder with a switchable constant input 3 to be an addsub-macro block. If you want to perform signed add/sub then exchange the conversion functions and sign-extension accordingly.

Upvotes: 0

Jim Lewis
Jim Lewis

Reputation: 3973

enter code hereYour equation for subtraction is not correct. Like @neodelphi suggested, it should be:

A - B = A + (not B) + 1

However, this does not account for carry in and what to do with it. If I remember right, the borrow is subtracted:

A - B - C_in = A + (not B) + 1 - C_in = A + (not B) + (1 - C_in)

Now note that:

(1 - C_in) = not C_in

Now, to convert it to VHDL. If I overlook the fact that you are doing signed math with the package, std_logic_unsigned (Ahem), you could write (similar to @neodelphi):

S_wider <= (A(31)& A) + (not B(31) & not B) + not C_in ;

Note in the package std_logic_unsigned as well as numeric_std with VHDL-2008, there are no issues with adding with a std_ulogic.

My suggestion about types and packages is very simple. If you are doing math, use a math type like, signed (matching your math here) or unsigned (for other cases). I consider these part of the documentation.

Furthermore, using the appropriate type is important as the math packages allow you to add two array values that are different sizes. If you use the appropriate type, they do the appropriate extension replicate sign bit for signed or '0' fill for unsigned.

Hence, had you used type signed, then you could have used the first argument (A) to size the result and been lazy about B and written:

S_wider <= (A(31)& A) + not B + not C_in ;

BTW, testing for both '0' and '1' does not help the hardware in any way. My recommendation is to either be lazy (and safe) and write:

if Binv = '0' then
    S_wider <= ( A(31) & A) + ( B(31) & B) + C_in;
else
    S_wider <= (A(31)& A) + (not B(31) & not B) + not C_in;
end if;

Alternately be paranoid and vigilant and make the output is 'X' when the control input is an 'X'. However be sure to double check your "elsif" expression - get this wrong when it is more complex and it may be challenging to find the bug (meaning you better have test cases that cover all possible input values of the controls):

if Binv = '0' then
    S_wider <= ( A(31) & A) + ( B(31) & B) + C_in;
elsif Binv = '1' then
    S_wider <= (A(31)& A) + (not B(31) & not B) + not C_in;
else
    S_wider <= (others => 'X') ;  -- X in propagates as X out can help debug
end if;

Upvotes: 0

Related Questions