In short: Gate-level (structural) Verilog describes a circuit as an explicit netlist of primitive gates (and, or, nand, xor…) wired together through named nets, mirroring a logic schematic one-for-one rather than describing behavior.
Structural modeling is where most VLSI engineers first meet Verilog, and it is also what a synthesis tool like Synopsys Design Compiler ultimately emits. When you hand-write gate-level code you are doing manually what synthesis does automatically: turning a Boolean intent into a connected graph of cells. Understanding it well is the difference between reading a post-synthesis netlist with confidence and staring at it blankly in an interview.
What "structural" really means
Verilog supports three modeling styles: gate-level (structural), dataflow (assign with operators), and behavioral (always blocks). Structural is the lowest level you can write by hand. Instead of saying what the output should be, you instantiate physical building blocks and declare the wires between them. The compiler never infers logic for you — the connectivity you type is exactly the connectivity that is elaborated.
Verilog ships with 12 built-in primitives. The ones you use daily are the logic gates and, nand, or, nor, xor, xnor plus the single-input buf and not. There are also the tri-state buffers bufif0/bufif1 and pull family. All multi-input gates take the form gate_type instance_name (output, in1, in2, …) — output always first.
Rule to memorize: for every Verilog primitive, the first terminal is the output and all remaining terminals are inputs. A 4-input AND is simply and g1(y, a, b, c, d); — no extra syntax needed for fan-in.
A worked example: a 1-bit full adder
A full adder computes sum = a ⊕ b ⊕ cin and cout = ab + cin(a⊕b). From the schematic it is two XORs, two ANDs and one OR, joined by two internal nets (t1, the half-sum, and t2/t3, the carry terms).
module full_adder (output sum, cout,
input a, b, cin);
wire t1, t2, t3; // internal nets
xor x1 (t1, a, b); // half-sum
xor x2 (sum, t1, cin); // full sum
and a1 (t2, a, b);
and a2 (t3, t1, cin);
or o1 (cout, t2, t3); // carry out
endmodule
Notice there is no always, no assign, no clock — just five gate instances and three wires. The order of the statements is irrelevant: structural Verilog is a netlist, not a program, so o1 could appear before x1 and the elaborated circuit is identical. This is a frequent interview trap.
Nets, not variables
Every signal driven by a gate output must be a net type — normally wire. This is not a stylistic choice: a primitive is a continuous driver, and only nets can hold a continuously driven value. Declaring reg t1; and connecting it to a gate output is a compile error in Simulators such as Cadence Xcelium or Siemens Questa. Ports default to wire unless re-declared, which is why the example above connects cleanly without extra declarations for sum and cout.
Nets also resolve contention. If two gates drive the same wire, the four-valued logic system (0, 1, x, z) produces x (unknown) on conflict — a behavior you will see flagged in gate-level simulation when a bus is multiply driven.
Building hierarchy: a 4-bit ripple-carry adder
Structural modeling scales by instantiating modules the same way you instantiate gates. Chaining four full adders, passing each stage's cout into the next stage's cin, gives a ripple-carry 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
This is positional port connection. In production code prefer named connection — full_adder fa0 (.sum(sum[0]), .cout(c1), .a(a[0]), …) — because it survives port-order changes and is self-documenting in reviews. The carry nets c1…c3 are the classic critical path: a 4-bit ripple adder has a worst-case delay of roughly 4 carry hops, which is exactly the timing a synthesis tool would try to flatten into carry-lookahead logic.
Modeling delays at the gate level
A unique strength of primitives is built-in delay annotation, used in post-synthesis and SDF-backed timing simulation. and #3 g1(y,a,b); gives a 3-unit propagation delay; the two-value form #(2,3) sets rise and fall separately, and #(2,3,4) adds turn-off (to z) time. During sign-off, tools back-annotate real cell delays from a Standard Delay Format file so that the same netlist reflects silicon timing.
| Style | Construct | Abstraction | Typical use |
|---|---|---|---|
| Gate-level | primitives + nets | Lowest (netlist) | Post-synthesis, SDF timing |
| Dataflow | assign | Medium | Combinational RTL |
| Behavioral | always | Highest | FSMs, sequential RTL |
For a deeper contrast with the abstraction you would actually write for synthesizable design, see our companion guide on behavioral modeling in Verilog, and for the synthesis step that converts behavior into the gate netlist described here, read the RTL-to-gates synthesis flow.
Interview angle: you almost never write structural RTL for new designs — it is verbose and error-prone. Its real value is reading what Design Compiler or Cadence Genus produces, and writing tiny verified cells for custom standard-cell or FPGA primitives. Teams that build silicon at firms like Avecas VLSI design services rely on exactly this skill when debugging gate-level mismatches.
Common pitfalls
- Wrong terminal order — placing an input first silently miswires the gate; the compiler does not warn because the arity is still valid.
- Using
regon a gate output — only nets may be continuously driven. - Forgetting internal wires — undeclared intermediate nets may be implicitly created as 1-bit wires, masking a wider-bus bug.
- Assuming statement order matters — it does not; the netlist is order-independent.
Want to go from reading netlists to taping out your own blocks? CourseTron's VLSI track walks you from gate-level primitives through RTL, synthesis and timing closure with real EDA tools. Explore the courses →
FAQ
Is gate-level Verilog synthesizable?
Yes — primitives map directly to cells, so a structural netlist is trivially synthesizable. In practice you rarely write it by hand; synthesis generates it from higher-level RTL.
Why must gate outputs be wires and not regs?
A primitive is a continuous driver. Only net types (wire, tri, etc.) can carry a continuously driven value; reg holds a procedurally assigned value and will cause a compile error if driven by a gate.
Does the order of gate instantiations change the circuit?
No. Structural Verilog is a netlist describing connectivity, so reordering instances produces an identical elaborated circuit — unlike procedural code, which executes sequentially.
How do I add timing to primitive gates?
Use delay syntax such as and #3 g1(y,a,b); or the rise/fall form #(2,3). For sign-off, real delays are back-annotated from an SDF file during gate-level simulation.