Gate Level Modelling in Verilog
Learn how to model digital circuits using Verilog’s built-in gate primitives — AND, OR, NAND, NOR, XOR, NOT, BUF, and tri-state gates — with truth tables, diagrams, and complete examples.
🔗 Introduction
Gate level modelling (also called structural modelling) describes a digital circuit as a netlist of logic gate primitives connected by wires — exactly like assembling gates on a breadboard or PCB. It is the most direct correspondence between Verilog code and physical hardware.
Verilog has a set of built-in primitive gates that do not need to be defined — you simply instantiate them by name, just as you would place an IC chip on a board.
and, or, nand are pre-defined keywords. Just instantiate them.Gate Primitives at a Glance
| Category | Primitives | Output rule |
|---|---|---|
| AND family | and, nand | AND of all inputs (inverted for NAND) |
| OR family | or, nor | OR of all inputs (inverted for NOR) |
| XOR family | xor, xnor | XOR of all inputs (inverted for XNOR) |
| Buffer / Inv | buf, not | Pass or invert a single input; multiple outputs allowed |
| Tri-state | bufif1, bufif0, notif1, notif0 | Drive or float based on enable signal |
| MOS switches | nmos, pmos, cmos, tran | Transistor-level switch primitives |
🔵 AND Gate Primitive
The AND gate is the simplest starting point for gate-level modelling. Its output is 1 only when all inputs are 1. In Verilog it is used with the keyword and.
| a | b | y = a & b |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 0 |
| 1 | 0 | 0 |
| 1 | 1 | 1 |
// 2-input AND and g1(y, a, b); // 3-input AND and g2(y, a, b, c); // 4-input AND and g3(y, a, b, c, d);
📐 Gate Instantiation Syntax
Every gate primitive follows the same instantiation pattern. Understanding this anatomy prevents common ordering mistakes:
// General pattern: // gate_type [instance_name] (output, input1, input2, ...); and g1 (y, a, b); // named instance and (y, a, b); // anonymous — legal but harder to debug // With propagation delay (optional) and #5 (y, a, b); // 5 time-unit delay on output and #(3,4) (y, a, b); // rise=3, fall=4 time units and #(2,3,1) (y, a, b); // rise, fall, turn-off delays
u_and1, you immediately know which gate failed.
🏗 Module Structure for Gate-Level Design
A gate-level module follows the same Verilog module template — it just uses gate primitives in the body instead of assign or always statements. Internal wires carry signals between gates.
module module_name ( input a, b, c, // primary inputs output y, z // primary outputs ); // ── Internal wires (connect gates to each other) ────────── wire w1, w2, w3; // ── Gate instances (structural body) ────────────────────── and g1 (w1, a, b); // w1 = a & b or g2 (w2, b, c); // w2 = b | c not g3 (w3, a); // w3 = ~a xor g4 (y, w1, w2); // y = w1 ^ w2 nand g5 (z, w3, c); // z = ~(w3 & c) endmodule
module half_adder ( input a, b, output sum, cout ); xor g_sum (sum, a, b); // sum = a ^ b and g_cout (cout, a, b); // cout = a & b endmodule
⚡ Other Gate Primitives
Verilog provides all standard logic gate families as built-in primitives. Each can be instantiated directly without any module definition.
and g1(y, a, b); and g2(y, a, b, c, d);
Output is 1 only when ALL inputs are 1. Supports N inputs.
nand g1(y, a, b); nand g2(y, a, b, c);
Inverted AND — output is 0 only when ALL inputs are 1.
or g1(y, a, b); or g2(y, a, b, c);
Output is 1 when ANY input is 1.
nor g1(y, a, b); nor g2(y, a, b, c);
Inverted OR — output is 1 only when ALL inputs are 0.
xor g1(y, a, b); xor g2(y, a, b, c);
Output is 1 when an odd number of inputs are 1. Parity function.
xnor g1(y, a, b); xnor g2(y, a, b, c);
Inverted XOR — output is 1 when an even number of inputs are 1. Equivalence gate.
not g1(y, a); // multiple outputs, one input: not g2(y1, y2, a);
Inverts a single input. Can drive multiple outputs from the same input.
buf g1(y, a); // multiple outputs, one input: buf g2(y1, y2, y3, a);
Passes input to output unchanged. Used for fan-out buffering and driving multiple loads.
buf and not, the last port is the single input and all preceding ports are outputs. This allows one input to drive multiple output wires simultaneously.
📊 Truth Tables for All Gate Primitives
Complete truth tables for 2-input versions of all gate primitives. x = unknown, z = high-impedance.
| a | b | and | nand |
|---|---|---|---|
| 0 | 0 | 0 | 1 |
| 0 | 1 | 0 | 1 |
| 1 | 0 | 0 | 1 |
| 1 | 1 | 1 | 0 |
| x | 0 | 0 | 1 |
| x | 1 | x | x |
| a | b | or | nor |
|---|---|---|---|
| 0 | 0 | 0 | 1 |
| 0 | 1 | 1 | 0 |
| 1 | 0 | 1 | 0 |
| 1 | 1 | 1 | 0 |
| x | 0 | x | x |
| x | 1 | 1 | 0 |
| a | b | xor | xnor |
|---|---|---|---|
| 0 | 0 | 0 | 1 |
| 0 | 1 | 1 | 0 |
| 1 | 0 | 1 | 0 |
| 1 | 1 | 0 | 1 |
| x | 0 | x | x |
| x | 1 | x | x |
| a | buf | not |
|---|---|---|
| 0 | 0 | 1 |
| 1 | 1 | 0 |
| x | x | x |
| z | x | x |
0 AND x = 0 (output is definitely 0 no matter what x is), but 1 AND x = x (output depends on the unknown x).
💡 Illustrative Examples
Fig 4 — Full Adder (Gate Level)
module full_adder ( input a, b, cin, output sum, cout ); wire w1, w2, w3; xor g1 (w1, a, b ); // w1 = a ^ b xor g2 (sum, w1, cin); // sum = (a ^ b) ^ cin and g3 (w2, a, b ); // w2 = a & b and g4 (w3, w1, cin); // w3 = (a ^ b) & cin or g5 (cout, w2, w3 ); // cout = (a&b) | ((a^b)&cin) endmodule
Fig 5 — 2-to-1 Multiplexer (Gate Level)
module mux_2to1 ( input a, b, sel, output y ); wire sel_n, w1, w2; not g1 (sel_n, sel ); // sel_n = ~sel and g2 (w1, a, sel ); // w1 = a & sel and g3 (w2, b, sel_n ); // w2 = b & ~sel or g4 (y, w1, w2 ); // y = w1 | w2 endmodule
Fig 6 — D Latch (Gate Level using NAND gates)
module d_latch ( input d, en, output q, qn ); wire s, r, dn; not g0 (dn, d ); // dn = ~d nand g1 (s, d, en ); // S = ~(d & en) nand g2 (r, dn, en ); // R = ~(~d & en) nand g3 (q, s, qn ); // Q = ~(S & Qn) — cross-coupled nand g4 (qn, r, q ); // Qn = ~(R & Q) — cross-coupled endmodule
Fig 7 — 4-bit Ripple Carry Adder (Hierarchical Gate Level)
module ripple_adder_4bit ( input [3:0] a, b, input cin, output [3:0] sum, output cout ); wire c1, c2, c3; // carry wires between stages full_adder fa0 (.a(a[0]), .b(b[0]), .cin(cin), .sum(sum[0]), .cout(c1)); full_adder fa1 (.a(a[1]), .b(b[1]), .cin(c1), .sum(sum[1]), .cout(c2)); full_adder fa2 (.a(a[2]), .b(b[2]), .cin(c2), .sum(sum[2]), .cout(c3)); full_adder fa3 (.a(a[3]), .b(b[3]), .cin(c3), .sum(sum[3]), .cout(cout)); endmodule
Fig 8 — NAND-Only Implementation (Universal Gate)
// NOT from NAND: ~a = nand(a, a) nand g_not (y, a, a); // AND from NAND: a & b = nand(nand(a,b), nand(a,b)) wire nab; nand g1 (nab, a, b); nand g2 (y, nab, nab); // OR from NAND: a | b = nand(nand(a,a), nand(b,b)) wire na, nb; nand g3 (na, a, a); nand g4 (nb, b, b); nand g5 (y, na, nb);
🔀 Tri-State Gates
Tri-state gates have three possible output states: logic 0, logic 1, or high impedance (z). They are essential for building shared buses — multiple tri-state outputs can be connected to the same wire because only one drives at a time while the rest float to z.
Verilog provides four tri-state primitives, controlled by an enable signal:
The Four Tri-State Primitives
bufif1 g1(out, in, en);
Drives in to out when en=1. Output floats to z when en=0.
bufif0 g1(out, in, en);
Drives in to out when en=0. Output floats to z when en=1.
notif1 g1(out, in, en);
Drives inverted in to out when en=1. Floats when en=0.
notif0 g1(out, in, en);
Drives inverted in to out when en=0. Floats when en=1.
Tri-State Truth Table (bufif1)
| in | en | out |
|---|---|---|
| 0 | 0 | z |
| 1 | 0 | z |
| 0 | 1 | 0 |
| 1 | 1 | 1 |
| x | 1 | x |
| 0 | x | x/z |
| in | en | out |
|---|---|---|
| 0 | 0 | 0 |
| 1 | 0 | 1 |
| 0 | 1 | z |
| 1 | 1 | z |
| x | 0 | x |
| 0 | x | x/z |
Fig 9 — Shared Bus Using Tri-State Buffers
module tristate_bus ( input [7:0] data_a, data_b, data_c, input en_a, en_b, en_c, // enable signals — at most one HIGH output [7:0] bus ); // Each device connects to bus through its own tri-state buffer // Only the enabled device drives; others output z → bus floats correctly bufif1 ba[7:0] (bus, data_a, {8{en_a}}); // device A drives when en_a=1 bufif1 bb[7:0] (bus, data_b, {8{en_b}}); // device B drives when en_b=1 bufif1 bc[7:0] (bus, data_c, {8{en_c}}); // device C drives when en_c=1 endmodule
bufif1 ba[7:0] instantiates 8 independent bufif1 gates in one line — one per bit of the bus. This is called a gate array and significantly reduces repetitive code.
x — unknown. Always design control logic to guarantee mutual exclusion of enable signals.
📋 Gate Primitives Quick Reference
| Primitive | Inputs | Outputs | Function | Use case |
|---|---|---|---|---|
| and | N ≥ 2 | 1 | All inputs AND-ed | Combinational AND logic |
| nand | N ≥ 2 | 1 | Inverted AND | Universal gate — NAND-only designs |
| or | N ≥ 2 | 1 | Any input OR-ed | Combinational OR logic |
| nor | N ≥ 2 | 1 | Inverted OR | Universal gate — NOR-only designs |
| xor | N ≥ 2 | 1 | Odd-parity of inputs | Adders, parity checkers |
| xnor | N ≥ 2 | 1 | Even-parity (equivalence) | Comparators, error detection |
| buf | 1 | N ≥ 1 | Pass input unchanged | Fan-out buffers, clock drivers |
| not | 1 | N ≥ 1 | Invert input | Inverters, complementary signals |
| bufif1 | 1 + enable | 1 (tri) | Drive or float (active-high enable) | Shared buses, bidirectional I/O |
| bufif0 | 1 + enable | 1 (tri) | Drive or float (active-low enable) | Shared buses, OE# style control |
| notif1 | 1 + enable | 1 (tri) | Invert or float (active-high enable) | Inverted tri-state output |
| notif0 | 1 + enable | 1 (tri) | Invert or float (active-low enable) | Inverted tri-state, OE# style |
and / nand / or / nor / xor / xnor — first port = output, rest = inputs. For buf / not — last port = input, all others = outputs. For tri-state gates — port order is (output, data_input, enable).
