Adders

Addition is the first place gate-level thinking meets arithmetic. A full adder adds three bits (a, b, carry-in) into a sum and carry-out:

sum  = a ^ b ^ cin
cout = (a & b) | (cin & (a ^ b))

Chain four of them, carry-out to carry-in, and you have a 4-bit ripple-carry adder: the design below builds it with a generate loop, worth reading as your first taste of structural repetition in Verilog.

The design also computes the same sum a second way: assign {c5, s5} = a + b + cin;. In the waveform the two results are always identical, that's the point. Synthesis tools map + onto the FPGA's dedicated fast carry chains, which are quicker than anything you can describe by hand with gates. Write +, not adder structures. The value of knowing the full adder isn't to type it; it's to understand what the tools build, why a wide adder's delay grows with its width, and what a "carry chain" is when the timing report mentions one.

One habit to take away: the sum of two N-bit numbers needs N+1 bits. The concatenation trick {cout, sum} = a + b captures the carry cleanly and appears everywhere real arithmetic is written.

Experiment: make an add/subtract unit, b ^ {4{sub}} plus sub as carry-in is two's-complement subtraction for one extra XOR per bit. (This exact trick reappears in the ALU capstone.)

Schematic

abcinsumabcincout

The design

Verilog, design.v
// A structural ripple-carry adder next to the `+` you should use.
module adders (
    input  wire [3:0] a, b,
    input  wire       cin,
    output wire [3:0] sum_ripple,
    output wire       cout_ripple,
    output wire [3:0] sum_plus,
    output wire       cout_plus
);
    // --- structural: 4 chained full adders ---
    wire [4:0] c;
    assign c[0] = cin;

    genvar i;
    generate
        for (i = 0; i < 4; i = i + 1) begin : fa
            assign sum_ripple[i] = a[i] ^ b[i] ^ c[i];
            assign c[i+1]        = (a[i] & b[i]) | (c[i] & (a[i] ^ b[i]));
        end
    endgenerate
    assign cout_ripple = c[4];

    // --- behavioral: what you should actually write ---
    assign {cout_plus, sum_plus} = a + b + cin;
endmodule
Show the VHDL version
VHDL, design.vhd
-- Ripple-carry adder next to the '+' you should use.
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity adders is
    port (
        a, b        : in  std_logic_vector(3 downto 0);
        cin         : in  std_logic;
        sum_ripple  : out std_logic_vector(3 downto 0);
        cout_ripple : out std_logic;
        sum_plus    : out std_logic_vector(3 downto 0);
        cout_plus   : out std_logic
    );
end entity;

architecture rtl of adders is
    signal c : std_logic_vector(4 downto 0);
    signal r : unsigned(4 downto 0);
begin
    -- structural ripple carry
    c(0) <= cin;
    gen_fa : for i in 0 to 3 generate
        sum_ripple(i) <= a(i) xor b(i) xor c(i);
        c(i+1) <= (a(i) and b(i)) or (c(i) and (a(i) xor b(i)));
    end generate;
    cout_ripple <= c(4);

    -- behavioral
    r <= resize(unsigned(a), 5) + resize(unsigned(b), 5)
         + ("0000" & cin);
    sum_plus  <= std_logic_vector(r(3 downto 0));
    cout_plus <= r(4);
end architecture;
Show the MyHDL (Python) version
MyHDL, design.py
from myhdl import block, Signal, intbv, always_comb, instance, delay, StopSimulation

@block
def adders(a, b, cin, sum_ripple, cout_ripple, sum_plus, cout_plus):
    @always_comb
    def logic():
        # structural ripple carry: chain of full adders
        sr = intbv(0)[4:]
        carry = int(cin)
        for i in range(4):
            sr[i] = a[i] ^ b[i] ^ carry
            carry = (a[i] & b[i]) | (carry & (a[i] ^ b[i]))
        sum_ripple.next = sr
        cout_ripple.next = carry
        # behavioral: the '+' you should actually write
        s = intbv(a + b + cin)[5:]
        sum_plus.next = s[4:0]
        cout_plus.next = s[4]
    return logic

@block
def tb():
    a, b = Signal(intbv(0)[4:]), Signal(intbv(0)[4:])
    cin = Signal(bool(0))
    sum_ripple, sum_plus = Signal(intbv(0)[4:]), Signal(intbv(0)[4:])
    cout_ripple, cout_plus = Signal(bool(0)), Signal(bool(0))
    dut = adders(a, b, cin, sum_ripple, cout_ripple, sum_plus, cout_plus)

    @instance
    def stim():
        yield delay(10); a.next, b.next = 3, 4
        yield delay(10); a.next, b.next = 9, 8
        yield delay(10); a.next, b.next = 15, 1
        yield delay(10); cin.next = 1
        yield delay(10); a.next, b.next, cin.next = 5, 5, 0
        yield delay(10); raise StopSimulation
    return dut, stim

inst = tb()
inst.config_sim(trace=True)
inst.run_sim()

The testbench

Verilog, tb.v
`timescale 1ns/1ns
module tb;
    reg  [3:0] a = 0, b = 0;
    reg        cin = 0;
    wire [3:0] sum_ripple, sum_plus;
    wire       cout_ripple, cout_plus;

    adders dut (.a(a), .b(b), .cin(cin),
                .sum_ripple(sum_ripple), .cout_ripple(cout_ripple),
                .sum_plus(sum_plus), .cout_plus(cout_plus));

    initial begin
        $dumpfile("wave.vcd"); $dumpvars(0, tb);
        #10 begin a = 4'd3;  b = 4'd4;  end   // 7, no carry
        #10 begin a = 4'd9;  b = 4'd8;  end   // 17: carry out
        #10 begin a = 4'hF;  b = 4'h1;  end   // wrap: 0 + carry
        #10 cin = 1;                          // F + 1 + 1
        #10 begin a = 4'd5;  b = 4'd5; cin = 0; end
        #10 $finish;
    end
endmodule
Show the VHDL testbench
VHDL, tb.vhd
library ieee;
use ieee.std_logic_1164.all;
entity tb is end entity;
architecture sim of tb is
  signal a, b : std_logic_vector(3 downto 0) := "0000";
  signal cin : std_logic := '0';
  signal sum_ripple, sum_plus : std_logic_vector(3 downto 0);
  signal cout_ripple, cout_plus : std_logic;
begin
  dut : entity work.adders port map (a=>a, b=>b, cin=>cin,
        sum_ripple=>sum_ripple, cout_ripple=>cout_ripple,
        sum_plus=>sum_plus, cout_plus=>cout_plus);
  process begin
    wait for 10 ns; a <= "0011"; b <= "0100";        -- 7, no carry
    wait for 10 ns; a <= "1001"; b <= "1000";        -- 17: carry out
    wait for 10 ns; a <= "1111"; b <= "0001";        -- wrap
    wait for 10 ns; cin <= '1';                      -- F + 1 + 1
    wait for 10 ns; a <= "0101"; b <= "0101"; cin <= '0';
    wait for 10 ns; std.env.stop;
  end process;
end architecture;

The MyHDL version keeps the design and its testbench in one design.py.

Simulated waveform

This trace was produced by actually simulating the code above with Icarus Verilog.

6 12 18 24 30 36 42 48 54 t (ns) sum_ripple[3:0] 0 7 1 0 1 A sum_plus[3:0] 0 7 1 0 1 A cout_ripple cout_plus a[3:0] 0 3 9 F 5 b[3:0] 0 4 8 1 5 cin c[4:0] 0 10 1E 1F A

Try it live

Open this lesson in a playground, edit the code, and re-run it.

Verilog → VHDL → MyHDL →

Put it to work

Tools that apply what this lesson covers.