4 bit adder in vhdl

Shawn picture Shawn · Sep 13, 2013 · Viewed 14.9k times · Source

im pretty new to the vhdl language so please bear with me. I just did the vhdl code for a 1 bit adder, but I am having trouble writing for the 4bit adder. This is what I got so far, if anybody could point me in the right direction of what to look up that would be awesome!

VHDL code:

LIBRARY IEEE; 
USE IEEE.STD_LOGIC_1164.ALL; 

ENTITY Adder4 IS
  GENERIC(CONSTANT N: INTEGER := 4);
  PORT(
    a, b: IN STD_LOGIC_VECTOR(N-1 DOWNTO 0);  -- Input SW[7..4]: a[3..0] inputs,
                                              -- SW[3..0]: b[3..0]
    sum: OUT STD_LOGIC_VECTOR(N-1 DOWNTO 0); -- Output LEDR[3..0]
    cOut: OUT STD_LOGIC -- Output LEDR[4]
  );
END Adder4;

ARCHITECTURE imp OF Adder4 IS
  COMPONENT Adder1 
  PORT(
    a, b, cIn : in STD_LOGIC;
    sum, cOut : out STD_LOGIC);
  END COMPONENT;
  SIGNAL carry_sig: std_logic_vector(N DOWNTO 0);
BEGIN
  -- What to write here?
END imp;

Answer

user1155120 picture user1155120 · Sep 14, 2013

In deference to sharth's fine answer on the chance you did intend to have an N number of Adder1s instantiated in Adder4:

ARCHITECTURE imp OF Adder4 IS
COMPONENT Adder1 
PORT(
a, b, cIn : in STD_LOGIC;
sum, cOut : out STD_LOGIC);
END COMPONENT;
SIGNAL carry_sig: std_logic_vector(N-1 DOWNTO 0);
signal carry_in:  std_logic_vector(N-1 DOWNTO 0);
BEGIN
-- What to write here?

    carry_in <= ((carry_sig(N-2 downto 0)) &'0');
Adders:
for i in 0 to N-1 generate
    begin
    ADD1:
        Adder1 port map (
            a => a(i), 
            b => b(i),
            cIn => carry_in(i),
            sum => sum(i),
            cOut => carry_sig(i)            
        );
    end generate;

Carry_Out:
    cOut <= carry_sig(N-1);

END imp;

ARCHITECTURE gen OF Adder4 IS
    COMPONENT Adder1 
    PORT(
        a, b, cIn : in STD_LOGIC;
        sum, cOut : out STD_LOGIC);
        END COMPONENT;

SIGNAL carry_sig: std_logic_vector(N-1 DOWNTO 0);

BEGIN
-- What to write here?

Adders:
    for i in 0 to N-1 generate
    ADD0:    
        if i = 0 generate
        Add1:
            Adder1 port map (
                a => a(i), 
                b => b(i),
                cIn => '0',
                sum => sum(i),
                cOut => carry_sig(i)            
            );
        end generate;
    ADDN:
        if i /= 0 generate
            Add1:
            Adder1 port map (
                a => a(i), 
                b => b(i),
                cIn => carry_sig(i-1),
                sum => sum(i),
                cOut => carry_sig(i)  
            );          
        end generate;

    end generate;

Carry_Out:
    cOut <= carry_sig(N-1);

END architecture;

I prefer the first architecture (imp) myself, requiring a second std_logic_vector for carry_in, but greatly simplifying any generate construct. There's a difference in hierarchy between the two and first is easier to read.


The first architecture (imp) also shows how to instantiate Adder1 four times manually, eliminating the generate construct and substituting all (i) range expressions for their respective Adder1 instance range expressions ((0),(1),(2),(3), respectively).

The manually instantiated adder1s would look something like:

-- Note in this case you'd likely declare all the std_logic_vector with 
-- ranges (3 downto 0)


    SIGNAL carry_sig: std_logic_vector(3 DOWNTO 0);
    signal carry_in:  std_logic_vector(3 downto 0);
BEGIN
-- What to write here?

    carry_in <= ((carry_sig(2 downto 0)) &'0'); 

    ADD0:
        Adder1 port map (
            a => a(0), 
            b => b(0),
            cIn => carry_in(0),
            sum => sum(0),
            cOut => carry_sig(0)            
        );

 ...

    ADD3:
        Adder1 port map (
            a => a(3), 
            b => b(3),
            cIn => carry_in(3),
            sum => sum(3),
            cOut => carry_sig(3)            
        );

cOut <= carry_sig(3); -- or connect directly to cOut in ADD3 above

The additional carry_in vector using carry_sig adjusted upward with a least significant carry_in of '0' makes it simple to write. It can also be easier to read implementing a carry look ahead method if the carry in and carry out signals are separately named.


A test bench can also accommodate a width N Adder4:

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

entity adder4_tb is
       constant N:  natural := 4; 
end entity;

architecture tb of adder4_tb is
   signal a,b,sum:  std_logic_vector (N-1 downto 0);
   signal carryout:    std_logic;

begin

DUT: entity work.Adder4
    generic map (N => N)  -- associates formal N with actual N (a constant)
    port map (
        a => a,
        b => b,
        sum => sum,
        cOut => carryout
    );

STIMULUS:
    process 
    variable i,j:   integer;
    begin
        for i in 0 to N*N-1 loop
            for j in 0 to N*N-1 loop
                a <= std_logic_vector(to_unsigned(i,N)); 
                b <= std_logic_vector(to_unsigned(j,N));
                wait for 10 ns;     -- so we can view waveform display
            end loop;
        end loop;
        wait;   -- end the simulation
    end process;   
end architecture;

All this without regard to carry tree delay time which can be affected by implementation or the use of fast carry circuits (e.g. carry look ahead).

And this gives us a simulation that looks like:

Adder4_tb Simulation Waveform

Or for a closer view:

Adder4_tb Simulation Waveform, closeup

When using a generate statement based architecture if you changed the declaration of N you'd have an adder that would synthesis and simulate in variable widths specified by N up until the ripple carry would no longer work for the input data rate (10 ns in the test bench currently).

Note the generic map association of he generic N formal with the actual N declared in the test bench means in this case that the N declared in the test bench sets the width N in Adder4 as well.