0
\$\begingroup\$

Good evening all! I am facing an unexpected behaviour of the timing analysis of my bypass (or skip) carry adder. In particular, the implementation of the adder looks correct to me, the Modelsim simulation yields correct functional results with all the input combination, but the timing analysis doesn't sound correct. In particular, i am working with Quartus Prime Lite (Cyclone IV hardware platform) and the timing analyzer which specifies the maximum clock frequency appropriate for the correct functioning of the circuit. Basically, if imposing an arbitrary clock to the circuit of 1 GHz, the maximum frequency from the timing analyzer results about 180 MHz, while the same analysis on a Ripple Carry Adder yields a maximum frequency of about 200 MHz. How can the CBA perform worse than the RCA in terms of delay? What am i missing? The two adders basically differ only for the skip logic and the multiplexer used in the CBA (the full adders are identical, i only bring out the propagate signal for the CBA cell).

The overall circuit (first code below) is a 16 bit signed adder, with clocked input and output register, implemented with a 16 bit RCA or a 16 bit (four 4-bit cells) CBA.

I am pasting the code of the two circuits below:

Overall Adder with input and output registers:

LIBRARY ieee;
USE ieee.std_logic_1164.all;
USE ieee.numeric_Std.all;

-- This circuit implements a 16 bit adder with different solutions.
-- In particular, ripple carry adder, carry select adder and carry bypass adder
-- are compared.


entity Adder16Bit is
    port (
    
        -- Input ports
        A          : in signed(15 downto 0);
        B          : in signed(15 downto 0);
        Resetn   : in std_logic;
        Clk    : in std_logic;
        -- Output ports
        S       : out signed(15 downto 0);
        Overflow : out std_logic    
    
    );
end Adder16Bit;

Architecture Behavior of Adder16Bit is 

    signal Sint : signed(15 downto 0);
    signal A_reg: signed(15 downto 0);
    signal B_reg: signed(15 downto 0);
    signal S_reg: signed(15 downto 0);
    signal Overflow_int: std_logic;
    
-- Ripple Carry Adder

component RippleCarryAdderNBit is
    GENERIC ( N : integer:= 16);
    port (
    
        -- Input ports
        A          : in signed((N-1) downto 0);
        B          : in signed((N-1) downto 0);
        Cin     : in std_logic;
        -- Output ports
        S       : out signed((N-1) downto 0);
        Overflow : out std_logic    
    
    );
end component;  

-- Carry Bypass Adder

COMPONENT CarryBypassAdder16Bit is
    port (
    
        -- Input ports
        A          : in signed(15 downto 0);
        B          : in signed(15 downto 0);
        Cin: in std_logic;
        -- Output ports
        S       : out signed(15 downto 0);
        Overflow : out std_logic    
    
    );
end COMPONENT;  

-- Synchronization register
component regn 

GENERIC ( N : integer:=4);

PORT (R : IN SIGNED(N-1 DOWNTO 0);

    Clock, Resetn : IN STD_LOGIC;
    Q : OUT SIGNED(N-1 DOWNTO 0));
    
END component;
-- D type FF
component flipflop IS

    PORT (D, Clock, Resetn : IN STD_LOGIC;
        Q : OUT STD_LOGIC);
        
END component;



begin
    

-- Instance of FF
FF1:flipflop port map(Overflow_int,Clk,Resetn,Overflow);

-- Instances of register
Reg1:regn generic map(16)
             port map(A,Clk,Resetn,A_reg);
Reg2:regn generic map(16)
             port map(B,Clk,Resetn,B_reg);           
Reg3:regn generic map(16)
             port map(Sint,Clk,Resetn,S);   
             
-- Instances of Adders
--RcaInst: RippleCarryAdderNBit PORT MAP(A_reg,B_reg,'0',Sint,Overflow_int);    -- Ripple Carry Adder
CbaInst: CarryBypassAdder16Bit PORT MAP(A_reg,B_reg,'0',Sint,Overflow_int); -- Carry Bypass Adder


end Behavior;

Ripple Carry Adder

LIBRARY ieee;
USE ieee.std_logic_1164.all;
USE ieee.numeric_Std.all;

-- Ripple carry adder subcircuit

entity RippleCarryAdderNBit is
    GENERIC ( N : integer:= 16);
    port (
    
        -- Input ports
        A          : in signed((N-1) downto 0);
        B          : in signed((N-1) downto 0);
        Cin     : in std_logic;
        -- Output ports
        S       : out signed((N-1) downto 0);
        Overflow : out std_logic    
    
    );
end RippleCarryAdderNBit;   
    
Architecture Behavior of RippleCarryAdderNBit is 

signal Cint : std_logic_vector((N-1) downto 0);
signal Sint : std_logic_vector((N-1) downto 0);

 -- Full adder
 component FA 
      port (
            -- Input ports
            a : in std_logic;
            b : in std_logic;
            cin : in std_logic;
            -- Output ports
            s : out std_logic;
            cout : out std_logic
     );
 end component; 
    
begin
    
-- Overflow detection
Overflow <= (Cint(N-1) XOR Cint(N-2));

             
-- Instances of Adders using generate 
GENERATE_ADDER : FOR i IN 0 to (N-1) GENERATE

    -- At first stage, usually no carry in
    FirstStage : IF (i = 0) GENERATE
        AddInst0 : FA PORT MAP(std_logic(A(i)),std_logic(B(i)),Cin,Sint(i),Cint(i));
    END GENERATE FirstStage;
    
    -- Other stages take carry in from previous one
    OtherStages : IF (i > 0) GENERATE
        AddInstI : FA PORT MAP(std_logic(A(i)),std_logic(B(i)),Cint(i-1),Sint(i),Cint(i));
    END GENERATE OtherStages;   
    
END GENERATE GENERATE_ADDER;


-- Cast output sum
S <= signed(Sint);

end Behavior;

Carry Bypass Adder (4-bit sub cell first, complete adder with 4 cells instance below)

LIBRARY ieee;
USE ieee.std_logic_1164.all;
USE ieee.numeric_Std.all;

-- This circuit implements a single 4-bit cell
-- for a carry bypass adder

entity CBACell is

    port (
        -- Input ports
        A : in signed(3 downto 0);
        B : in signed(3 downto 0);
        Cin: in std_logic;
        -- Output ports
        C : out std_logic;
        S : buffer signed(3 downto 0);
        Cinternal: out std_logic_vector(3 downto 0)
    );

end CBACell;

Architecture Behavior of CBACell is 

    signal Cint : std_logic_vector(3 downto 0);
    signal Propagate : std_logic_vector(3 downto 0);
    signal CarryP: std_logic;
    
 -- Full adder
 component FA_prop 
     port (
            -- Input ports
            a : in std_logic;
            b : in std_logic;
            cin : in std_logic;
            -- Output ports
            s : out std_logic;
            cout : out std_logic;
            p : buffer std_logic
     );
 end component;

begin

    -- Instances of Adders
    FA0:FA_prop port map(std_logic(A(0)),std_logic(B(0)),Cin,S(0),Cint(0),Propagate(0));
    FA1:FA_prop port map(std_logic(A(1)),std_logic(B(1)),Cint(0),S(1),Cint(1),Propagate(1));
    FA2:FA_prop port map(std_logic(A(2)),std_logic(B(2)),Cint(1),S(2),Cint(2),Propagate(2));
    FA3:FA_prop port map(std_logic(A(3)),std_logic(B(3)),Cint(2),S(3),Cint(3),Propagate(3));
 
    -- Logic condition for carry propagation
    CarryP <=  (Propagate(0) AND Propagate(1)) AND (Propagate(2) AND Propagate(3));
    

    C <= (CarryP AND Cin) OR Cint(3);
    Cinternal <= Cint;    
              
end Behavior;



LIBRARY ieee;
USE ieee.std_logic_1164.all;
USE ieee.numeric_Std.all;

-- carry bypass adder subcircuit

entity CarryBypassAdder16Bit is
    port (
    
        -- Input ports
        A          : in signed(15 downto 0);
        B          : in signed(15 downto 0);
        cin     : in std_logic;
        -- Output ports
        S       : out signed(15 downto 0);
        Overflow : out std_logic    
    
    );
end CarryBypassAdder16Bit;  
    
Architecture Behavior of CarryBypassAdder16Bit is 


-- Declare the single carry bypass adder cell (4 bits)

component CBACell
port (
    -- Input ports
    A : in signed(3 downto 0);
    B : in signed(3 downto 0);
    Cin: in std_logic;
    -- Output ports
    C : out std_logic;
    S : buffer signed(3 downto 0);
    Cinternal : out std_logic_vector(3 downto 0)
);
end component;
    
signal CarryIntern: std_logic_vector(3 downto 0);
signal CarryOutSingleBlock: std_logic_vector(3 downto 0);

 -- Full adder
 component FA 
      port (
            -- Input ports
            a : in std_logic;
            b : in std_logic;
            cin : in std_logic;
            -- Output ports
            s : out std_logic;
            cout : out std_logic
     );
 end component; 
    
    
-- regular ripple carry adder for last block
    
component RippleCarryAdderNBit is
    GENERIC ( N : integer:= 16);
    port (
    
        -- Input ports
        A          : in signed((N-1) downto 0);
        B          : in signed((N-1) downto 0);
        Cin     : in std_logic;
        -- Output ports
        S       : out signed((N-1) downto 0);
        Overflow : out std_logic    
    
    );
end component;  
    
begin
    
-- Instantiate four cells to implement the full 16 bit adder

Cell1:CBACell port map(A(3 downto 0),B(3 downto 0),cin,CarryIntern(0),S(3 downto 0));
Cell2:CBACell port map(A(7 downto 4),B(7 downto 4),CarryIntern(0),CarryIntern(1),S(7 downto 4));
Cell3:CBACell port map(A(11 downto 8),B(11 downto 8),CarryIntern(1),CarryIntern(2),S(11 downto 8));
Cell4:CBACell port map(A(15 downto 12),B(15 downto 12),CarryIntern(2),CarryIntern(3),S(15 downto 12),CarryOutSingleBlock);
--cell4:RippleCarryAdderNBit generic map(4) port map(A(15 downto 12),B(15 downto 12),CarryIntern(2),S(15 downto 12),CarryIntern(3));

Overflow <= CarryOutSingleBlock(3) XOR CarryOutSingleBlock(2);

end Behavior;

Full Adder code:

LIBRARY ieee;
USE ieee.std_logic_1164.all;
USE ieee.numeric_Std.all;

-- This circuit implements the full adder,
-- which takes the carry from previous stages,
-- sums two bits and outputs the carry of the new sum

entity FA_prop is
 port (
        -- Input ports
        a : in std_logic;
        b : in std_logic;
        cin : in std_logic;
        -- Output ports
        s : out std_logic;
        cout : out std_logic;
        p : buffer std_logic
 );
 end FA_prop;
 
 Architecture Behavior of FA_prop is
 
    signal c1,s1,c2,s2 : std_logic;
    
 
 component MUX_2_TO_1 is
    port
    (
        -- Input ports
        Input1  : in  STD_LOGIC;
        Input2  : in  STD_LOGIC;
        Sel : in  STD_LOGIC;
        -- Output ports
        Output  : out STD_LOGIC
    );
end component;
 
 begin

    p <= (a XOR b);
    s <= p XOR cin;
    
    MuxInst:      MUX_2_TO_1 PORT MAP(b,Cin,p,cout);
    
 end Behavior;

Thanks in advance for any suggestion!

EDIT:

Attaching a screenshot of the carry bypass adder as implemented in the RTL viewer (what i want) and the post-mapping implementation (what the compiler does):

RTL viewer

Post-synthesis

\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

The answer to your question will be found in the utilization and timing reports from the FPGA tool (Quartus you said).

What you are missing might be the fact that FPGAs have hard adder resources. So it will be critical to look at the reports and see what your in theory ~'carry bypass adder' in HDL actually gets implemented as in LUTs/CARRY blocks, etc.

I often see ASIC folks coming to FPGAs looking to try out, for example, new adder architectures. Only to find the hard adders/CARRY resources in the FPGA are already you best/fastest option almost always.

\$\endgroup\$
2
  • \$\begingroup\$ Thanks for your answer! Where should i look in particular? I edited the top post to add a screenshot of the adder as implemented in RTL viewer and post-mapping. Does this give a clue? Is there a way to make the compiler implement the circuit only with basic logic element? Thanks! \$\endgroup\$
    – Revo
    Mar 11 at 19:00
  • \$\begingroup\$ Definitely look at the post-mapping (as opposed to RTL view in this case). But then also be looking at the timing reports. I am most familiar with Vivado but Im sure Quartus too provides detailed information what LUTs/adders your critical path goes through. Should be able to match that path to what you see in post map. \$\endgroup\$ Mar 12 at 19:12

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.