Block | Inputs | sum | carry out |
|---|---|---|---|
Half adder | a, b | a ⊕ b | a · b |
Full adder | a, b, cin | a ⊕ b ⊕ cin | a·b + cin·(a ⊕ b) |
A full adder adds three bits and reports a two-bit result (cout, sum). Chaining N of them, each cout feeding the next cin, gives an N-bit ripple-carry adder:
module full_adder (input wire a, b, cin, output wire sum, cout);
assign sum = a ^ b ^ cin;
assign cout = (a & b) | (cin & (a ^ b));
endmodule
module ripple_adder #(parameter N = 8) (
input wire [N-1:0] a, b,
input wire cin,
output wire [N-1:0] sum,
output wire cout
);
wire [N:0] c;
assign c[0] = cin;
genvar i;
generate
for (i = 0; i < N; i = i + 1) begin : stage
full_adder fa (.a(a[i]), .b(b[i]), .cin(c[i]), .sum(sum[i]), .cout(c[i+1]));
end
endgenerate
assign cout = c[N];
endmoduleBecause −b = ~b + 1, an adder becomes a subtractor if you invert b and set the carry-in to 1. One control bit sub turns the same hardware into an adder or subtractor: y = a + (b ^ {N{sub}}) + sub. The flags follow from the earlier overflow lesson:
Carry out (cout) for addition means unsigned overflow. For subtraction it means no borrow (a ≥ b unsigned).
Signed overflow uses the effective b operand (after the XOR): operands of the same sign producing a result of the other sign.
Saturating arithmetic clamps on overflow instead of wrapping. On overflow the adder picks the max or min value, using the overflow flag plus an operand sign; DSP blocks use it often, at the cost of one mux after the adder.
Watch y, cout and ovf as sub switches the unit from add to subtract. The diagram shows hex values, so the bullets give the decimal value in brackets.
Slot 1: 3 + 5 = 8, so cout = 0 and ovf = 0.
Slot 2: 60 + 7 = 67 needs seven bits, so y = 3 with cout = 1 and ovf = 0.
Slot 3: 20 + 20 = 40 (shown as 28) leaves signed range, so ovf = 1 while cout = 0.
Slot 4: 20 − 7 = 13 (shown as d) needs no borrow, so cout = 1 and ovf = 0.
Slot 5: 5 − 9 borrows, so y wraps to 3c (60) with cout = 0 and ovf = 0.
Likely question: “How is a full adder built, and how do you reuse it for subtraction?”
Full adder is sum = a⊕b⊕cin with cout = majority(a, b, cin).
Subtract by inverting b and setting carry-in to 1.
Flags fall out: unsigned carry means no borrow on subtract; signed overflow compares effective operand signs.
sum = 0, cout = 1. Two ones make two (binary 10): the sum bit is 0 with a carry of 1.
+ sub as the carry-in give two's complement subtraction?Inverting b produces ~b = −b − 1. Adding the carry-in of 1 supplies the missing +1, so the sum becomes a + (−b), which is a − b.
cout = 0. A borrow was needed, and for subtraction cout means no borrow, so it reads 0.
One add/subtract unit proved against references for all 8192 combinations of a, b and sub.
module addsub #(parameter int N = 6) (
input logic [N-1:0] a, b,
input logic sub,
output logic [N-1:0] y,
output logic cout, ovf
);
logic [N-1:0] b_eff;
logic [N:0] sum;
always_comb begin
b_eff = b ^ {N{sub}}; // invert b when subtracting
sum = {1'b0, a} + {1'b0, b_eff} + {{N{1'b0}}, sub};
y = sum[N-1:0];
cout = sum[N];
ovf = (a[N-1] == b_eff[N-1]) && (y[N-1] != a[N-1]);
end
endmodule
The functional testbenches here use timeunit 1ns and timeprecision 1ps. Each #1 lets zero-delay combinational RTL settle before comparison; it is not a measured 1 ns hardware delay. A mismatch reports the inputs, expected value and actual value; accumulated errors end with $fatal(1, ...), and PASS is printed only on success.