Verilog Series · Module 07

Gate Level Modelling in Verilog — VLSI Trainers
Verilog Series · Module 07

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.

🧱
Structural Modelling
Describes how a circuit is built — which gates are connected to which wires. You must know the full circuit topology.
📦
Built-in Primitives
No module definition needed — gates like and, or, nand are pre-defined keywords. Just instantiate them.
🔁
Multiple Inputs
All multi-input gates (AND, OR, NAND, NOR, XOR, XNOR) can accept any number of inputs — not just two.
Concurrent Evaluation
All gate instances evaluate simultaneously — when any input changes, the output updates immediately (with optional propagation delay).
Where you see gate-level code: Post-synthesis netlists produced by tools like Synopsys Design Compiler or Cadence Genus are written in gate-level Verilog — a flat list of primitive instantiations and wires.

Gate Primitives at a Glance

CategoryPrimitivesOutput 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 / Invbuf, not Pass or invert a single input; multiple outputs allowed
Tri-state bufif1, bufif0, notif1, notif0 Drive or float based on enable signal
MOS switchesnmos, 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.

Fig 1 — 2-input AND gate: symbol, truth table, and Verilog
Gate Symbol
a b y AND
Truth Table
aby = a & b
000
010
100
111
Verilog
// 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);
Output is always listed first. In all gate primitives, the first port is the output and all remaining ports are inputs. This is the opposite of most module port conventions where inputs come first.

📐 Gate Instantiation Syntax

Every gate primitive follows the same instantiation pattern. Understanding this anatomy prevents common ordering mistakes:

and
Primitive
gate keyword
g1
Instance Name
optional but recommended
(
open port list
y
Output
ALWAYS first port
a, b
Inputs
all remaining ports
);
close + semicolon
// 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
Instance names are optional but strongly recommended. Without them, error messages from simulators reference line numbers only — with instance names like 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.

Fig 2 — Gate-level module template (annotated)
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
Fig 3 — Half Adder: gate-level module with netlist diagram
a b XOR sum AND cout
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 AND Gate
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 NAND Gate
nand g1(y, a, b);
nand g2(y, a, b, c);

Inverted AND — output is 0 only when ALL inputs are 1.

or OR Gate
or g1(y, a, b);
or g2(y, a, b, c);

Output is 1 when ANY input is 1.

nor NOR Gate
nor g1(y, a, b);
nor g2(y, a, b, c);

Inverted OR — output is 1 only when ALL inputs are 0.

xor XOR Gate
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 XNOR Gate
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 Inverter
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 Buffer
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 port order is reversed: For 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.

AND / NAND
abandnand
0001
0101
1001
1110
x001
x1xx
OR / NOR
abornor
0001
0110
1010
1110
x0xx
x110
XOR / XNOR
abxorxnor
0001
0110
1010
1101
x0xx
x1xx
BUF / NOT
abufnot
001
110
xxx
zxx
x propagation rule: When an input is x (unknown), the output is x unless the known inputs already determine the output regardless of the unknown. Example: 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)

Full adder: sum = a^b^cin, cout = (a&b)|(b&cin)|(a&cin)
a b cin XOR1 w1 AND1 XOR2 sum AND2 OR cout
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)

MUX: y = (sel & a) | (~sel & b)
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)

SR-NAND latch forming a D latch: Q, Qn are complementary
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)

Chain four full_adder instances — carry propagates stage to stage
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)

Any logic function can be built using only NAND gates
// 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:

enable = 1 (bufif1)
0 or 1
Output drives data — gate is transparent
enable = 0 (bufif1)
z
Output floats — gate disconnected from bus
enable = x
x or z
Enable unknown — output indeterminate

The Four Tri-State Primitives

bufif1 Active-High Enable Buffer
bufif1 g1(out, in, en);

Drives in to out when en=1. Output floats to z when en=0.

bufif0 Active-Low Enable Buffer
bufif0 g1(out, in, en);

Drives in to out when en=0. Output floats to z when en=1.

notif1 Active-High Enable Inverter
notif1 g1(out, in, en);

Drives inverted in to out when en=1. Floats when en=0.

notif0 Active-Low Enable Inverter
notif0 g1(out, in, en);

Drives inverted in to out when en=0. Floats when en=1.

Tri-State Truth Table (bufif1)

bufif1 — out = in when en=1, else z
inenout
00z
10z
010
111
x1x
0xx/z
bufif0 — out = in when en=0, else z
inenout
000
101
01z
11z
x0x
0xx/z

Fig 9 — Shared Bus Using Tri-State Buffers

Only one device drives the bus at any time — others float to z
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
Array of instances: Writing 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.
Bus contention: If two or more tri-state outputs are enabled simultaneously on the same wire with different values, the result is x — unknown. Always design control logic to guarantee mutual exclusion of enable signals.

📋 Gate Primitives Quick Reference

PrimitiveInputsOutputsFunctionUse case
and N ≥ 21All inputs AND-edCombinational AND logic
nand N ≥ 21Inverted ANDUniversal gate — NAND-only designs
or N ≥ 21Any input OR-edCombinational OR logic
nor N ≥ 21Inverted ORUniversal gate — NOR-only designs
xor N ≥ 21Odd-parity of inputsAdders, parity checkers
xnor N ≥ 21Even-parity (equivalence)Comparators, error detection
buf 1N ≥ 1Pass input unchangedFan-out buffers, clock drivers
not 1N ≥ 1Invert inputInverters, complementary signals
bufif1 1 + enable1 (tri)Drive or float (active-high enable)Shared buses, bidirectional I/O
bufif0 1 + enable1 (tri)Drive or float (active-low enable)Shared buses, OE# style control
notif1 1 + enable1 (tri)Invert or float (active-high enable)Inverted tri-state output
notif0 1 + enable1 (tri)Invert or float (active-low enable)Inverted tri-state, OE# style
Key rule to remember: For and / nand / or / nor / xor / xnorfirst port = output, rest = inputs. For buf / notlast port = input, all others = outputs. For tri-state gates — port order is (output, data_input, enable).

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top