In short: Structural modeling describes a Verilog design as an explicit interconnection of primitive gates and instantiated sub-modules — a literal netlist in HDL form — whereas dataflow (assign) and behavioral (always) styles describe what the logic computes and let the synthesizer decide the gates.
When engineers first learn Verilog they usually write assign y = a & b; and move on. But the oldest and most literal way to describe hardware is structural modeling: you name the actual gates, wire them together, and the HDL becomes a one-to-one map of the circuit schematic. Understanding this style is not academic nostalgia — it is how IP blocks are assembled at the top level, how post-synthesis netlists are represented, and a reliably-asked interview topic. This article walks through gate-level primitives, module instantiation, and how all three abstraction levels coexist in a real project.
What "structural" actually means
A structural description is a hierarchy of components and the nets that join them. There is no behavioral intent expressed — no +, no if, no priority. You place instances and connect their ports. The elaborated design is identical whether you call it a schematic, a netlist, or structural Verilog. This is precisely why the output of Synopsys Design Compiler or Cadence Genus — the synthesized .v netlist — is pure structural Verilog made of standard-cell instances like AND2X1 or DFFQX2. If you can read structural HDL, you can read a netlist.
A 2-million-instance SoC netlist produced by synthesis is, syntactically, nothing more than millions of structural module instantiations wired through named nets. Gate-level simulation (GLS) in Siemens Questa or Synopsys VCS runs directly on this structural Verilog.
Gate-level primitives
Verilog ships with built-in primitive gates so you never define common logic yourself: and, or, nand, nor, xor, xnor, plus the single-input buf and not, and the tri-state drivers bufif1, bufif0, notif1, notif0. The syntax places the output first, followed by the inputs:
module mux2 (output y, input a, b, sel);
wire nsel, a_and, b_and;
not u_inv (nsel, sel); // nsel = ~sel
and u_a (a_and, a, nsel); // a when sel=0
and u_b (b_and, b, sel); // b when sel=1
or u_out (y, a_and, b_and); // combine
endmodule
Each line is a gate instance with an optional instance name (u_inv) and a port list. A multi-input gate simply lists more inputs: and g (out, i0, i1, i2, i3); is a 4-input AND. These primitives also accept drive-strength and delay specifications — e.g. and #(2,3) g (y,a,b); gives 2 ns rise and 3 ns fall — which matters for the SDF-annotated timing in gate-level sim but is ignored by synthesis.
mux2 module above.Module instantiation and hierarchy
Real designs are built by instantiating sub-modules, not just primitives. Once mux2 exists, a 4:1 mux reuses it three times:
module mux4 (output y, input [3:0] d, input [1:0] s);
wire lo, hi;
mux2 u_lo (.y(lo), .a(d[0]), .b(d[1]), .sel(s[0]));
mux2 u_hi (.y(hi), .a(d[2]), .b(d[3]), .sel(s[0]));
mux2 u_top(.y(y), .a(lo), .b(hi), .sel(s[1]));
endmodule
Two connection styles exist. Named association — .y(lo) — binds by port name and is the only style you should use in production: it is order-independent and immune to someone reordering ports later. Positional association — mux2 u (lo, d[0], d[1], s[0]); — binds by position and is a classic bug source. Hierarchy also enables generate loops for regular structures:
genvar i;
generate
for (i=0; i<8; i=i+1) begin : bit
full_adder fa (.s(sum[i]), .co(c[i+1]),
.a(a[i]), .b(b[i]), .ci(c[i]));
end
endgenerate
Named port connection is non-negotiable at scale. On a block with 300+ ports, positional binding is effectively unreviewable — and a single transposed signal can pass lint yet fail only in gate-level sim weeks later.
mux4 module above: u_lo and u_hi each pick within a pair using s[0], then u_top picks between lo and hi using s[1].Structural vs. dataflow vs. behavioral
The three styles are abstraction levels, not rival languages — you mix them freely in one module. The table shows the same 1-bit function each way.
| Aspect | Structural | Dataflow | Behavioral |
|---|---|---|---|
| Construct | gate/module instances | assign | always, if, case |
| XOR example | xor g(y,a,b); | assign y=a^b; | always @* y=a^b; |
| Describes | exact topology | logic equations | algorithm / intent |
| Abstraction | lowest | medium | highest |
| Typical use | top-level wiring, netlists | combinational glue | FSMs, RTL, datapaths |
In practice an engineer writes behavioral/RTL for the logic (an FSM, an ALU), uses dataflow for small combinational expressions, and reserves structural for the integration layer — the top module that stitches the CPU, memory controller, and PHY together, each a black-box instance. For a deeper side-by-side of when to pick each, see our companion piece on Verilog modeling styles, and for the sequential-logic angle our guide to blocking vs non-blocking assignments.
Why it still matters on FPGAs and ASICs
You rarely hand-write large structural blocks, yet the style is everywhere you look in a flow. Xilinx/AMD Vivado and Intel Quartus both emit a post-implementation structural netlist of LUTs, flip-flops, and carry chains that you can simulate for timing. When you instantiate a vendor primitive — a Xilinx BUFG clock buffer, a MMCME4 PLL, or an IBUFDS differential input — you are doing structural modeling, because those cannot be inferred from behavior and must be placed explicitly. The same is true for ASIC clock-gating cells and I/O pads. Teams building secure or high-reliability silicon, such as the HRoT work done at Avecas design services, lean on explicit structural instantiation to keep critical cells from being optimized away.
Common pitfalls
- Output-first ordering: primitives always list the output first; swapping it is the most common beginner error.
- Implicit nets: an undeclared identifier used as a wire silently becomes a 1-bit net, hiding width-mismatch bugs. Compile with
`default_nettype none. - Positional drift: positional port maps break the instant a module's port order changes.
- Delays in synthesis:
#delays on primitives are simulation-only and are ignored by Design Compiler/Genus — never rely on them for real timing.
Want to go from "I can write assign" to confidently architecting module hierarchies and reading synthesis netlists? Our CourseTron Verilog & FPGA track drills structural, dataflow, and behavioral modeling with lab-graded netlist exercises.
Frequently asked questions
Is structural modeling synthesizable?
Yes. Gate primitives and module instantiations are fully synthesizable (except the # delays, which tools ignore). In fact synthesis output is structural Verilog, so it is as synthesizable as HDL gets.
When should I prefer structural over behavioral code?
Use structural when the exact topology matters — top-level integration, instantiating vendor hard macros or clock buffers, or building regular arrays with generate. Use behavioral for everything where you want the tool to choose the implementation.
What is the difference between positional and named port connection?
Positional binds ports by their order in the child module; named (.port(signal)) binds by name. Named is safer, self-documenting, and order-independent — always prefer it in real projects.
Can I mix all three styles in one module?
Absolutely. A single module can contain gate instances, assign statements, and always blocks simultaneously. Mixed-style RTL is the norm, not the exception.