Half Adder Verilog Code: Dataflow and Gate-Level
A half adder is the smallest building block of binary arithmetic: it takes two single-bit inputs, a and b, and produces a sum and a carry. This tutorial gives you working half adder Verilog code in two styles you will actually be asked to write in labs and interviews — dataflow (continuous assignments) and gate-level (built-in primitives) — each with a self-checking testbench that sweeps all four input combinations. Everything below was compiled and simulated with Icarus Verilog, so you can copy, run, and see the exact output.
📘 New to the concept? Read the theory first: Half Adder & Full Adder: theory.
The logic behind a half adder
Adding two bits has only four possibilities. Write them out and two familiar gates fall straight out of the table:
| a | b | sum | carry |
|---|---|---|---|
| 0 | 0 | 0 | 0 |
| 0 | 1 | 1 | 0 |
| 1 | 0 | 1 | 0 |
| 1 | 1 | 0 | 1 |
The sum column is exactly a XOR of the inputs, and the carry column is exactly an AND. So the whole device collapses to two equations: sum = a ^ b and carry = a & b. The only thing that changes between the versions below is how you tell Verilog to build those two gates.
Half adder Verilog code — dataflow style
Dataflow modelling uses the assign keyword to describe the circuit as Boolean expressions on the wires. It is the most compact and readable way to capture a half adder.
// half_adder_dataflow.v
// Half adder described with continuous (dataflow) assignments.
module half_adder_dataflow (
input wire a, // first input bit
input wire b, // second input bit
output wire sum, // sum output = a XOR b
output wire carry // carry output = a AND b
);
assign sum = a ^ b; // XOR gives the sum bit
assign carry = a & b; // AND gives the carry bit
endmodule
// Testbench: drives all 4 input combinations and prints a truth table.
module half_adder_dataflow_tb;
reg a, b; // stimulus (reg: assigned in procedural block)
wire sum, carry; // observed outputs (wire: driven by the DUT)
integer i; // loop counter
// Instantiate the Device Under Test (DUT) by named port connection.
half_adder_dataflow dut (
.a(a), .b(b), .sum(sum), .carry(carry)
);
initial begin
$display("A B | SUM CARRY");
$display("----+----------");
for (i = 0; i < 4; i = i + 1) begin
{a, b} = i[1:0]; // unpack the 2 input bits
#10; // let combinational logic settle
$display("%b %b | %b %b", a, b, sum, carry);
end
$display("Done. SUM=A^B, CARRY=A&B verified for all 4 cases.");
$finish;
end
endmoduleA B | SUM CARRY ----+---------- 0 0 | 0 0 0 1 | 1 0 1 0 | 1 0 1 1 | 0 1 Done. SUM=A^B, CARRY=A&B verified for all 4 cases. /var/folders/l0/gs5hnkm56g54bmxg32r6nnwr0000gn/T/tmpj7u5klbo/half_adder_dataflow.v:33: $finish called at 40 (1s)
How the dataflow code works
module half_adder_dataflow (...)declares the design and its ports. Inputs and outputs arewirebecause nothing procedural drives them — they are pure combinational nets.assign sum = a ^ b;is a continuous assignment. The right-hand side is re-evaluated wheneveraorbchanges, sosumalways tracks the XOR instantly.^is the bitwise-XOR operator.assign carry = a & b;does the same for the AND carry using the&operator.- In the testbench,
aandbareregbecause they are assigned inside aninitialblock, whilesumandcarrystaywirebecause the DUT drives them. {a, b} = i[1:0];uses concatenation to load both input bits from the loop counter, soi= 0,1,2,3 walks through every row of the truth table.#10;waits 10 time units before sampling, giving the combinational output a moment to settle before$displayprints the result.
Half adder Verilog code — gate-level style
Gate-level (structural) modelling wires up Verilog’s built-in primitives by hand. It maps one-to-one onto a schematic, which makes it the clearest way to see the two gates in the design.
// half_adder_gate.v
// Half adder built from Verilog built-in gate primitives (structural / gate-level).
module half_adder_gate (
input wire a, // first input bit
input wire b, // second input bit
output wire sum, // sum output
output wire carry // carry output
);
// gate_primitive instance_name (output, input1, input2);
xor g1 (sum, a, b); // sum = a XOR b
and g2 (carry, a, b); // carry = a AND b
endmodule
// Testbench: $monitor logs every change across the 4 input combinations.
module half_adder_gate_tb;
reg a, b;
wire sum, carry;
half_adder_gate dut (.a(a), .b(b), .sum(sum), .carry(carry));
initial begin
// $monitor prints one line whenever any listed signal changes.
$monitor("t=%0t | a=%b b=%b -> sum=%b carry=%b",
$time, a, b, sum, carry);
a = 0; b = 0; #10;
a = 0; b = 1; #10;
a = 1; b = 0; #10;
a = 1; b = 1; #10;
$display("All 4 combinations applied to the gate-level half adder.");
$finish;
end
endmodulet=0 | a=0 b=0 -> sum=0 carry=0 t=10 | a=0 b=1 -> sum=1 carry=0 t=20 | a=1 b=0 -> sum=1 carry=0 t=30 | a=1 b=1 -> sum=0 carry=1 All 4 combinations applied to the gate-level half adder. /var/folders/l0/gs5hnkm56g54bmxg32r6nnwr0000gn/T/tmpjdls8i71/half_adder_gate.v:30: $finish called at 40 (1s)
How the gate-level code works
xor g1 (sum, a, b);instantiates a built-in XOR gate. The syntax is fixed: the first port in the list is the output, every port after it is an input.g1is just the instance name.and g2 (carry, a, b);instantiates the AND gate that forms the carry, following the same output-first convention.- Because primitives are inherently combinational, the outputs stay
wireand noassignis needed — the gates themselves drive the nets. - The testbench swaps the
forloop for four explicit stimulus lines and uses$monitor, which automatically reprints whenever any watched signal changes, giving you a time-stamped trace instead of a static table. $timewith%0tprints the current simulation time so you can see each input change land at t = 0, 10, 20 and 30.
Both modules are logically identical; the dataflow version is what you would normally write for synthesis, while the gate-level version documents the exact structure.
Common mistakes to avoid
- Declaring testbench inputs as
wire. Signals you assign insideinitial/alwaysmust bereg. Leavingaandbaswireis one of the most frequent compile errors beginners hit. - Confusing bitwise and logical operators. Use
^and&(bitwise), not&&(logical AND). For single bits&&may look right, but it returns a 1-bit truth value, not a gate — a habit that breaks the moment your signals become vectors. - Getting primitive port order wrong. In
xor g1 (sum, a, b);the output is always listed first. Writingxor g1 (a, b, sum);silently builds the wrong circuit. - Sampling outputs with no delay. If you print immediately after changing
aorbwithout a#delay, you read stale values. Always let combinational logic settle before$display. - Forgetting
$finish. A testbench that uses$monitorbut never calls$finishcan hang; always terminate the run. - Treating the half adder as a full adder. A half adder has no carry-in. If a problem gives you three inputs, you need a full adder instead.
Quick check
Which two gates fully describe a half adder?
An XOR gate produces the sum (a ^ b) and an AND gate produces the carry (a & b). No other gates are required.
Why are a and b declared as reg in the testbench but wire in the module?
Inside the testbench they are assigned procedurally in an initial block, which requires reg. Inside the DUT they are inputs driven from outside, so they are nets (wire).
In xor g1 (sum, a, b);, which terminal is the output?
The first one, sum. For all Verilog gate primitives the output port is listed first, followed by the inputs.
What is the key difference between a half adder and a full adder?
A half adder adds only two bits and has no carry-in. A full adder adds three bits (a, b, and a carry-in), so it needs extra logic to combine the two partial carries.
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