Tutorial 12 · Verilog Series

Verilog Gate-Level Modeling

Gate-level modeling is the layer where logic meets silicon — you connect named primitive gates with wires, just as a synthesis tool would after compiling RTL. Understanding it lets you read post-synthesis netlists, model propagation delays accurately, and appreciate what happens beneath the assign and always abstractions.

and / or / notnand / nor / xorbuf / bufif1 gate delayrise/fallnetlistUDP
VERILOG BUILT-IN GATE PRIMITIVES and out=A·B Y=1 iff all=1 or out=A+B Y=1 if any=1 not out=~A 1 input only nand out=~(A·B) universal gate nor out=~(A+B) universal gate xor out=A^B odd parity buf driver / fan-out 1 input, N outputs bufif1 / bufif0 tri-state: out/Z by en models inout/bus notif1 / notif0 inverting tri-state out=~in when enabled

1. Built-in Gate Primitives

Verilog provides 26 built-in primitives. The most common fall into three groups:

GroupPrimitives
Basic logicand, or, not, nand, nor, xor, xnor, buf
Tri-statebufif0, bufif1, notif0, notif1
MOS switchesnmos, pmos, cmos, rnmos, rpmos, rcmos, tran, tranif0, tranif1

MOS switch primitives model transistor-level behavior (charge sharing, bidirectional current) and are rarely used in RTL or verification. You will encounter the first two groups in gate-level netlists.

2. Pin Order Convention

Critical rule: The first argument is always the output. All subsequent arguments are inputs. This is the opposite of how functions are written mathematically.
// Syntax: gate_type [#(delay)] instance_name (output, input1, input2, ...);
and  g1(y, a, b);          // y = a & b
or   g2(y, a, b, c);      // y = a | b | c  (3-input)
not  g3(y, a);            // y = ~a
nand g4(y, a, b);        // y = ~(a & b)
xor  g5(y, a, b);        // y = a ^ b

// buf can drive multiple outputs from one input
buf  g6(y1, y2, y3, a);  // y1=y2=y3=a (fan-out)

// tri-state: bufif1(output, data, enable)
bufif1 g7(y, a, en);     // y = a when en=1, y = Z when en=0
bufif0 g8(y, a, en);     // y = a when en=0, y = Z when en=1

3. Basic Logic Gates

Truth tables for key primitives

abandornandnorxorxnor
00001101
01011010
10011010
11110001
X00X1XXX
1XX1X0XX

Gates understand all four logic values (0, 1, X, Z). Verilog applies pessimistic X-propagation: any ambiguous input produces X output unless one controlling input forces a known output (e.g., AND with a 0 input is always 0).

4. Tri-state Buffers

PrimitiveenableOutput
bufif11data
0Z (high impedance)
XX or Z (uncertain)
bufif00data
1Z (high impedance)
XX or Z (uncertain)
// Simple bidirectional bus with tri-state drivers
module bus_driver (
  output bus,
  input  d0, d1, d2,
  input  en0, en1, en2
);
  bufif1 b0(bus, d0, en0);   // driver 0
  bufif1 b1(bus, d1, en1);   // driver 1
  bufif1 b2(bus, d2, en2);   // driver 2
endmodule
Only one driver should be enabled at a time. If two enabled drivers force conflicting values (one drives 0, another drives 1), the bus value becomes X — a bus contention error.

5. Gate Delays: rise, fall, turn-off

// Single delay — applies to all transitions
and #5      g1(y, a, b);

// Two delays — (rise_delay, fall_delay)
and #(3,5)  g2(y, a, b);  // 3ns rise, 5ns fall

// Three delays — (rise, fall, turn-off-to-Z)
bufif1 #(2,3,4) b1(y, a, en);
// 2ns to drive data, 3ns to go low, 4ns to release to Z

// Min:typ:max delay — simulator picks one (default: typ)
and #(2:3:5, 3:4:6) g3(y, a, b);

Rise delay: time from output becoming 0 to 1. Fall delay: time from 1 to 0. Turn-off delay: time to transition to high-impedance Z. Delays are simulation-only — synthesis ignores them and uses timing from the standard cell library instead.

6. Building a Gate-Level Netlist

A netlist is just Verilog with only wire declarations and gate instantiations — no always, no assign. Synthesis tools produce netlists like this, referencing cells from a technology library instead of built-in primitives.

module xor_from_nand (output y, input a, b);
  wire n1, n2, n3;

  nand g1(n1, a, b);
  nand g2(n2, a, n1);
  nand g3(n3, b, n1);
  nand g4(y,  n2, n3);
endmodule

This implements XOR using only NAND gates — a classic NAND-only circuit. The internal wires n1, n2, n3 carry intermediate signals between stages.

7. Full Adder at Gate Level

module full_adder (
  output sum, cout,
  input  a, b, cin
);
  wire s1, c1, c2;

  xor g1(s1,  a,  b);     // half-adder sum
  and g2(c1,  a,  b);     // half-adder carry
  xor g3(sum, s1, cin);  // full-adder sum
  and g4(c2,  s1, cin);  // carry from second stage
  or  g5(cout, c1, c2);  // final carry out
endmodule

// 4-bit ripple-carry adder using gate-level full_adder
module rca4 (output [3:0] sum, output cout, input [3:0] a, b, input cin);
  wire c1, c2, c3;
  full_adder fa0(sum[0], c1, a[0], b[0], cin);
  full_adder fa1(sum[1], c2, a[1], b[1], c1);
  full_adder fa2(sum[2], c3, a[2], b[2], c2);
  full_adder fa3(sum[3], cout, a[3], b[3], c3);
endmodule

8. 2-to-1 Multiplexer at Gate Level

A 2-to-1 mux selects between inputs a and b based on select s. Boolean: y = (~s & a) | (s & b).

module mux2to1 (output y, input a, b, s);
  wire ns, t1, t2;

  not g1(ns, s);          // ~s
  and g2(t1, a, ns);      // a & ~s
  and g3(t2, b, s);       // b & s
  or  g4(y, t1, t2);     // y = t1 | t2
endmodule

9. User Defined Primitives (UDP)

UDPs let you define custom gates using a truth table — useful for modeling non-standard cells or for simulation performance when the built-in primitives aren't a perfect fit.

Combinational UDP example

primitive my_mux (y, a, b, s);
  output y;
  input  a, b, s;
  table
    // a  b  s :  y
       0  ?  0 :  0;  // s=0 → select a
       1  ?  0 :  1;
       ?  0  1 :  0;  // s=1 → select b
       ?  1  1 :  1;
       0  0  ? :  0;  // both same → output that value
       1  1  ? :  1;
  endtable
endprimitive

Sequential UDP — D Latch

primitive d_latch (q, d, en);
  output reg q;          // sequential: output is reg
  input d, en;
  initial q = 0;
  table
    // d  en :  q  q_next
       1  1  :  ?  :  1;  // en=1, d=1 → latch 1
       0  1  :  ?  :  0;  // en=1, d=0 → latch 0
       ?  0  :  ?  :  -;  // en=0 → hold (- means no change)
  endtable
endprimitive
In sequential UDP table entries: the format is inputs : current_state : next_state. A - in next_state means "no change — hold current value". A ? matches any logic value (0, 1, or X).

10. Post-Synthesis Netlist Flow

In a real VLSI flow you rarely write gate-level Verilog by hand. Instead:

  1. You write RTL (behavioral Verilog with always and assign).
  2. A synthesis tool (Synopsys Design Compiler, Yosys) maps RTL to a technology library of standard cells.
  3. The tool outputs a gate-level netlist — Verilog with module instantiations from the cell library.
  4. You simulate this netlist with timing from an SDF (Standard Delay Format) file to catch setup/hold violations.
// Simplified post-synthesis netlist fragment (real names vary by PDK)
module counter4_synth (clk, rst_n, q);
  input  clk, rst_n;
  output [3:0] q;
  wire n1, n2, n3, n4;

  // Standard cell instances (names from 45nm cell library)
  DFFRX1 q_reg0 (.D(n1), .CK(clk), .RN(rst_n), .Q(q[0]));
  DFFRX1 q_reg1 (.D(n2), .CK(clk), .RN(rst_n), .Q(q[1]));
  XOR2X1 xor0  (.A(q[0]), .B(q[1]), .Y(n2));
  INVX1  inv0  (.A(q[0]), .Y(n1));
  // ... more cells ...
endmodule

Gate-level simulation of this netlist with an SDF file checks that all flip-flops meet setup and hold times at the actual clock frequency — the final sign-off step before tapeout.