Full Adder Verilog Code: Dataflow, Behavioral and Structural
A full adder adds three single-bit inputs — a, b, and a carry-in cin — and produces a sum bit and a carry-out cout. It is the cell you chain together to build ripple-carry adders, ALUs, and counters, so it is usually the first “real” circuit you describe in Verilog. This tutorial gives you working full adder Verilog code in all three modelling styles (dataflow, behavioral, and structural from two half adders) plus a self-checking testbench that drives every one of the 8 input combinations, all runnable in Icarus Verilog.
📘 New to the concept? Read the theory first: Full Adder & Half Adder: theory.
The logic in one line
Before any HDL, pin down the Boolean equations. For three input bits the outputs are:
sum = a XOR b XOR cincout = (a AND b) OR (b AND cin) OR (a AND cin)
The sum is 1 whenever an odd number of inputs are 1. The carry-out is 1 whenever at least two inputs are 1 (the majority function). Every style below describes exactly this truth table — only the level of abstraction changes.
1. Dataflow style
Dataflow modelling uses continuous assign statements that mirror the Boolean equations directly. The outputs are wire type and update automatically whenever any right-hand-side signal changes.
// Full adder - dataflow style
module full_adder_dataflow (
input wire a,
input wire b,
input wire cin,
output wire sum,
output wire cout
);
assign sum = a ^ b ^ cin;
assign cout = (a & b) | (b & cin) | (a & cin);
endmodule
// Testbench - exercises all 8 input combinations
module tb_full_adder;
reg a, b, cin;
wire sum, cout;
integer i;
full_adder_dataflow dut (
.a(a), .b(b), .cin(cin),
.sum(sum), .cout(cout)
);
initial begin
$display(" a b cin | cout sum | expected");
$display("---------+----------+---------");
for (i = 0; i < 8; i = i + 1) begin
{a, b, cin} = i[2:0];
#1;
$display(" %b %b %b | %b %b | %0d",
a, b, cin, cout, sum, (a + b + cin));
end
$display("All 8 input combinations verified.");
$finish;
end
endmodulea b cin | cout sum | expected ---------+----------+--------- 0 0 0 | 0 0 | 0 0 0 1 | 0 1 | 1 0 1 0 | 0 1 | 1 0 1 1 | 1 0 | 0 1 0 0 | 0 1 | 1 1 0 1 | 1 0 | 0 1 1 0 | 1 0 | 0 1 1 1 | 1 1 | 1 All 8 input combinations verified. /var/folders/l0/gs5hnkm56g54bmxg32r6nnwr0000gn/T/tmpdg_x13l8/full_adder_dataflow.v:34: $finish called at 8 (1s)
Reading it line by line:
- The module header lists the three 1-bit inputs and two 1-bit outputs. Operators
^(XOR),&(AND), and|(OR) are bit-wise. assign sum = a ^ b ^ cin;is a pure combinational net — no clock, no latch.- In the testbench,
{a, b, cin} = i[2:0];packs the loop counter into the three inputs, so oneforloop sweeps 000 through 111. - The expected column prints
a + b + cinas a plain integer (0, 1, or 2); compare it against the{cout, sum}pair to confirm correctness.
2. Behavioral style
Behavioral modelling describes what the hardware does inside a procedural always block rather than wiring gates. Here a single arithmetic add captures both outputs at once, and this testbench is self-checking: it compares the result against an expected value and prints a mismatch if the logic is ever wrong.
// Full adder - behavioral style
module full_adder_behavioral (
input wire a,
input wire b,
input wire cin,
output reg sum,
output reg cout
);
always @(*) begin
{cout, sum} = a + b + cin;
end
endmodule
// Testbench - checks all 8 cases and flags mismatches
module tb_full_adder;
reg a, b, cin;
wire sum, cout;
integer i;
reg [1:0] expected;
full_adder_behavioral dut (
.a(a), .b(b), .cin(cin),
.sum(sum), .cout(cout)
);
initial begin
$display("Time a b cin | cout sum");
$monitor("%4t %b %b %b | %b %b", $time, a, b, cin, cout, sum);
for (i = 0; i < 8; i = i + 1) begin
{a, b, cin} = i[2:0];
#5;
expected = a + b + cin;
if ({cout, sum} !== expected)
$display(" MISMATCH at a=%b b=%b cin=%b", a, b, cin);
end
$display("Done: behavioral full adder matched all 8 cases.");
$finish;
end
endmoduleTime a b cin | cout sum 0 0 0 0 | 0 0 5 0 0 1 | 0 1 10 0 1 0 | 0 1 15 0 1 1 | 1 0 20 1 0 0 | 0 1 25 1 0 1 | 1 0 30 1 1 0 | 1 0 35 1 1 1 | 1 1 Done: behavioral full adder matched all 8 cases. /var/folders/l0/gs5hnkm56g54bmxg32r6nnwr0000gn/T/tmp3jupy2os/full_adder_behavioral.v:37: $finish called at 40 (1s)
Key points:
- Outputs are declared
regbecause they are assigned inside analwaysblock. This does not make them flip-flops — withalways @(*)(combinational sensitivity) they stay pure logic. {cout, sum} = a + b + cin;concatenates the two outputs into a 2-bit bus. Adding three 1-bit values gives a result from 0 to 2, which fits in 2 bits: the top bit is the carry, the lower bit is the sum.$monitorprints a line automatically whenever a watched signal changes, so you see the waveform as text.- The
!==check uses the case-equality operator, which also catchesx/zvalues that a plain!=would miss.
3. Structural style (two half adders)
Structural modelling instantiates smaller modules and wires them together, exactly like a schematic. A full adder is classically built from two half adders and one OR gate. A half adder computes sum = a XOR b and carry = a AND b.
// Half adder building block
module half_adder (
input wire a,
input wire b,
output wire sum,
output wire carry
);
assign sum = a ^ b;
assign carry = a & b;
endmodule
// Full adder - structural style (two half adders + OR gate)
module full_adder_structural (
input wire a,
input wire b,
input wire cin,
output wire sum,
output wire cout
);
wire ha1_sum, ha1_carry, ha2_carry;
half_adder ha1 (.a(a), .b(b), .sum(ha1_sum), .carry(ha1_carry));
half_adder ha2 (.a(ha1_sum), .b(cin), .sum(sum), .carry(ha2_carry));
assign cout = ha1_carry | ha2_carry;
endmodule
// Testbench - drives all 8 combinations
module tb_full_adder;
reg a, b, cin;
wire sum, cout;
integer i;
full_adder_structural dut (
.a(a), .b(b), .cin(cin),
.sum(sum), .cout(cout)
);
initial begin
$display(" a b cin | cout sum");
$display("---------+---------");
for (i = 0; i < 8; i = i + 1) begin
{a, b, cin} = i[2:0];
#2;
$display(" %b %b %b | %b %b", a, b, cin, cout, sum);
end
$display("Structural (2x half adder) full adder: 8/8 combinations done.");
$finish;
end
endmodulea b cin | cout sum ---------+--------- 0 0 0 | 0 0 0 0 1 | 0 1 0 1 0 | 0 1 0 1 1 | 1 0 1 0 0 | 0 1 1 0 1 | 1 0 1 1 0 | 1 0 1 1 1 | 1 1 Structural (2x half adder) full adder: 8/8 combinations done. /var/folders/l0/gs5hnkm56g54bmxg32r6nnwr0000gn/T/tmpp6ro7vhc/full_adder_structural.v:48: $finish called at 16 (1s)
How the wiring works:
ha1addsaandb, giving a partial sum (ha1_sum) and a carry.ha2adds that partial sum tocin, producing the finalsumand a second carry.- A full adder carries out if either half adder carried, so
cout = ha1_carry | ha2_carry. - The
.port(signal)form is named port connection — always prefer it over positional order, which is easy to get wrong.
How to run it
Save any one block to a .v file and run it with the free Icarus Verilog toolchain:
iverilog -g2005 -o sim full_adder_dataflow.v vvp sim
Comparing the three styles
| Style | How it is written | Best when |
|---|---|---|
| Dataflow | assign with Boolean operators | You know the logic equations and want a compact, synthesizable description |
| Behavioral | always block with arithmetic/if-else | Logic is complex or arithmetic; readability matters most |
| Structural | Instantiate & wire sub-modules | Building hierarchy or reusing proven blocks like a half adder |
All three synthesize to the same gate-level circuit. The choice is about clarity and reuse, not the final hardware.
Common mistakes
- Using
regfor dataflow outputs. Continuousassigntargets must bewire. Only signals written inside analways/initialblock arereg. - Confusing
regwith a flip-flop.regis just a variable. Withalways @(*)it stays combinational; a flip-flop appears only with an edge-sensitive list likealways @(posedge clk). - Mixing up XOR and OR for the sum. The sum uses XOR (
^), not OR. OR would wrongly output 1 for the 1+1 case where the sum bit is 0. - Forgetting a delay in the testbench loop. Without
#1(or similar) between stimulus and$display, you print before the combinational outputs settle. - Positional port mismatches in structural code. If you instantiate
half_adder ha1 (a, b, sum, carry);and later reorder the module ports, the connections silently break. Named ports avoid this. - Dropping the carry-out.
sum = a + b + cininto a 1-bitsumtruncates the carry. Capture both with a 2-bit target:{cout, sum}.
Quick check
What is sum when a=1, b=1, cin=1?
sum = 1 ^ 1 ^ 1 = 1 and cout = 1, so the outputs are cout=1, sum=1 (decimal 3). Three 1s add to 3, which is binary 11.
Why are the dataflow outputs declared wire but the behavioral ones reg?
Continuous assign statements drive wire nets. Signals assigned inside a procedural always block must be variables, which in Verilog-2005 means reg. Neither choice changes whether the logic is combinational.
How many half adders and gates make a full adder?
Two half adders plus one OR gate. The first half adder adds a and b; the second adds that partial sum to cin; the OR combines the two carries into cout.
Why does the testbench loop run exactly 8 times?
Three 1-bit inputs give 2^3 = 8 possible combinations. Sweeping the counter from 0 to 7 and assigning {a, b, cin} covers the entire truth table, which is exhaustive verification for this circuit.
Share your question in comments or talk to our mentor team for batch guidance.
Learn Verilog with mentors, labs and placement support
This free series is maintained by the ChipXpert training team in Hyderabad & Bengaluru. Go deeper with instructor-led courses on real EDA tools.
Explore VLSI Courses