Describing a circuit
A chip holds millions of gates, far too many to draw by hand. So engineers write a description in a hardware description language (HDL). The Verilog card on the left describes the full adder circuit on the right.
Verilog builds a design from modules: boxes with named pins called ports. Module full_adder has three inputs, a, b and cin, and two outputs, s and cout. Each port is a wire crossing the box's edge.
Combinational logic: assign
assign ties a wire to an expression for good: s is always a ^ b ^ cin, where ^ means XOR. It is not a one-time sum. It becomes two XOR gates, and s changes the moment an input changes.
In the cout line, & means AND and | means OR, so it becomes two AND gates and an OR. Its a^b is the same signal as in the s line, so one XOR gate serves both. Two assigns make a full adder.
Software runs one line, then the next. Hardware does not take turns: each assign is its own circuit, working all the time. Swap the two assigns and nothing changes. When an input flips, s and cout update together.
Sequential logic: always
To remember a value, Verilog uses always @(posedge clk): do this at every rising clock edge, when clk goes from 0 to 1. A value saved there becomes flip-flops, here four of them holding a 4-bit count.
At each edge, count <= count+1 saves the old value plus one. It becomes an adder whose answer waits at the flip-flops' inputs until the next edge. Four bits hold only 16 values, so 15 + 1 wraps to 0.
In simulation, a reg starts as x, unknown, so the counter needs a known start. Line 5 gives it one: if rst is 1 at a clock edge, count becomes 0 instead of count+1.
From text to gates
A synthesis tool, such as the open-source Yosys, reads the Verilog and turns each line into gates and flip-flops, a list called a netlist. That netlist can then configure an FPGA or become a real chip.
Code and circuit
;; step 1/9 · describe
;; about: full_adder code, XOR a^b, XOR ^cin, AND a&b, AND cin&p, OR, Inputs, Output s, Output cout
Verilog: IEEE 1364, now part of SystemVerilog (IEEE 1800)
module full_adder(
input a, b, cin,
output s, cout);
assign s = a ^ b ^ cin;
assign cout = (a&b) |
(cin&(a^b));
endmodule
The card is text. A tool reads it and builds
the gates beside it.
assign is a continuous assignment: its left side is driven by the expression all the time, like a wire soldered to a gate's output. Because every assign is working at once, their order in the file does not matter (Harris and Harris, chapter 4). Inside always @(posedge clk), write <= (nonblocking). Every right-hand side is read first, with the values from just before the edge, and all the flip-flops update together. Mixing up = and <= is a classic bug (Cummings, SNUG 2000).