Skip to main content

Gate-Level Modeling

Gate-level modeling describes a circuit as an explicit netlist of primitive logic gates wired together — the closest Verilog gets to drawing a schematic in text. Verilog has a small set of built-in gate primitives that need no declaration, just instantiation, the same way or (cout, ha1_cout, ha2_cout); was used in Modules and Ports.

Built-in gate primitives​

PrimitiveFunctionInstantiation form
and, nandAND, NANDand (out, in1, in2, ...); — 2+ inputs allowed
or, norOR, NORor (out, in1, in2, ...);
xor, xnorXOR, XNORxor (out, in1, in2, ...);
notInverternot (out, in); — exactly one input
bufNon-inverting bufferbuf (out, in);

Every primitive's first port is its output, followed by one or more inputs — the reverse of the port order Verilog uses for module ports, and worth double-checking the first few times you write one. An optional instance name is allowed but not required (and a1 (w, x, y); or just and (w, x, y);), unlike user-defined modules, which effectively always want one for waveform/debug purposes.

and/or/xor (and their inverted forms) take exactly one output followed by two or more inputs. buf and not flip that: they take exactly one input, but can drive multiple outputs from it — buf (out1, out2, in); and not (out1, out2, in); are both legal, each output independently following (or inverting) the single input.

The full adder, purely at the gate level​

The full adder's minimized equations —

Sum = A ⊕ B ⊕ Cin
Cout = AB + BCin + ACin

— map onto primitive gates with no module hierarchy at all, one primitive per term:

module full_adder_gates (
input a,
input b,
input cin,
output sum,
output cout
);

wire w1; // a ^ b, an intermediate term shared by both outputs
wire p1, p2, p3; // the three AND terms of Cout

xor (w1, a, b);
xor (sum, w1, cin);

and (p1, a, b);
and (p2, b, cin);
and (p3, a, cin);
or (cout, p1, p2, p3);

endmodule

This is the literal gate-level circuit the equations describe:

a ──┬────┐
│ ┌─┴─┐
│ │XOR│── w1 ──┐
b ──┼──┤ │ ┌─┴─┐
│ └───┘ │XOR│──── sum
│ cin ──┤ │
│ └───┘
a ──┼────┬─────────────┐
│ ┌─┴─┐ │
b ──┴──│AND│── p1 ──┐ │
└───┘ │ │
b ────┬──────┐ │ │
┌─┴─┐ │ ┌─┴───┴─┐
cin ─┤AND│── p2 ───│ OR │──── cout
└───┘ │ (3-in)│
a ────┬──────┐ └───────┘
┌─┴─┐ │
cin ─┤AND│── p3 ──────┘
└───┘

Every wire in this module (w1, p1, p2, p3) is a physical net connecting exactly two gate terminals — there's no abstraction left to strip away; this is the transistor-adjacent circuit, modulo the actual gate-to-transistor mapping a standard-cell library performs.

Gate delays​

Primitives accept an optional delay, modeling real propagation delay for simulation:

and #(2) a1 (p1, a, b); // p1 settles 2 time units after a or b changes

Delays on primitives are a simulation-only concept for visualizing timing behavior — synthesis tools ignore any delay you write and instead compute real delay from the target technology library after place-and-route. Never rely on a hand-written delay value to make synthesized hardware correct; that's what static timing analysis and constraints are for, not source-code delay annotations.

Why gate-level modeling matters despite being rarely hand-written​

When you actually see gate-level Verilog

Almost no one hand-writes RTL at the gate-primitive level in modern chip design — it's exactly what a synthesis tool outputs, not what an engineer types. You'll encounter it reading a post-synthesis or post-place-and-route netlist, or in small structural glue logic (clock-gating cells, scan-chain stitching) where explicit gate-level control matters. Knowing the primitives is what lets you actually read that netlist instead of treating it as opaque tool output.

What's next​

The full adder above is fully specified structurally — every gate named, every wire connected by hand. The next page covers dataflow modeling, which describes the same circuit as an equation instead of a gate list, using assign — the style already previewed informally in Operators and Expressions.