Skip to content

Verilog · Chapter 5.1 · Data & Variables

Physical Data Types in Verilog — the Net Family

Verilog splits every signal into two families: nets for physical interconnect and variables for procedural storage. This lesson is the deep drill into the net half, a group of eleven keywords that share one binding rule: a net carries whatever its drivers put on it at any instant and has no memory of its own. We walk the family from the basic wire outward, define the resolution function that combines multiple drivers, and sort the synthesisable subset from the simulator-only subset. You will see how to model a shared bus with several drivers, what happens when nothing drives a net, and how a pull-up or pull-down default is expressed. We finish with the design-review and debug habits that keep real RTL out of trouble when many signals run through a single block.

Foundation22 min readVerilogData TypesNetsWiresTri-State

Chapter 5 · Section 5.1 · Data & Variables

1. The Engineering Problem

You are writing the RTL for a memory-mapped peripheral. The system bus is shared by three masters; each master can drive the bus when its grant line is asserted. When none is granted, the bus floats — and the system designer expects a pull-up resistor on the PCB to default it to all-ones.

How do you write that in Verilog? You cannot write it as a regreg is procedural storage, and there is no "store" happening on the bus; whoever is driving determines the value. You also cannot write it as plain combinational logic, because you actually need three independent drivers sharing a single name. And the pull-up is a property of the wire itself, not of any one driver.

This is the gap that physical data types fill. Verilog's net family is the language's vocabulary for wires on the chip — what they carry, who drives them, what happens when nothing does, and what happens when drivers disagree. Every IP block you ship has hundreds of these signals running through it, and the type you choose for each one shapes how the simulator, the synthesis tool, and (eventually) the silicon behave.

2. Why "Physical Data Types" Exists

Verilog draws a hard line down the middle of its type system:

  • Variables (reg, integer, real, time, realtime) model procedural storage — something that holds a value between assignments. They live inside always and initial blocks.
  • Nets (wire and ten siblings) model physical interconnect — a piece of metal driven by something else, with no storage of its own. They live in continuous-assignment context (assign, gate primitives, module-output ports).

The split exists because real silicon has the same split. A flip-flop has storage; a wire between two gates does not. Verilog's job is to let you write code that maps cleanly to either side of that line. The simulator's job is to model both correctly — and the rules that govern each side flow from this single distinction.

A reg lives in your code's memory. A wire is a promise that something somewhere will be driving this signal, and the simulator (and synthesis) will resolve who and how.

Once you internalise this, every typing rule downstream falls into place:

  • Why wire cannot appear on the left of = inside always — there is no storage to update.
  • Why reg cannot appear on the left of assign — there is no driver model; reg is a held value, not a connection.
  • Why nets can have multiple drivers (and need a resolution function) while variables cannot (the language picks the last procedural write).
  • Why nets default to z while variables default to x.

The "physical" in "physical data types" is the whole story.

3. Mental Model — A Net Is a Wire on the PCB

The behavioural consequences fall straight out of the picture:

QuestionNet answerVariable answer
Has memory?No — value follows the driverYes — value held between assignments
Multiple sources?Yes — resolved by the net's type ruleNo — last procedural write wins
What if no source drives?Z (high-impedance) or pulled per typeUnchanged — keeps last value
Where assigned?assign, gate primitive, module-output portInside always / initial
Synthesises to?A piece of metal (and possibly a tri-state buffer / pull resistor)A flop, latch, or combinational logic

4. Visual Explanation

The simplest net is a single-driver point-to-point wire — the workhorse wire keyword. One module drives, another module reads, and the net is the metal between them.

Single-driver net — the wire workhorse

data flow
Single-driver net — the wire workhorsedata_qdata_indriver moduleoutputs data_qwireinterconnect — no storageconsumer modulereads data
The net is the connection — not a storage element. Whatever the driver puts on it propagates instantly to every load.

The interesting cases arrive when the topology gets non-trivial — multiple potential drivers sharing one net (tri-state buses), wired-logic resolution (open-drain), or pull resistors that tie the net to a known level when no driver is active.

Tri-state bus topology — three drivers converging on one netmaster Adrives when oe_a=1master Bdrives when oe_b=1master Cdrives when oe_c=1tri netshared busslavereads bus12
At any instant, at most one master should drive the bus (oe_*=1). When all three release, the net floats — a tri1 pulls it high; tri0 pulls it low; tri leaves it at Z.

The net family exists to give RTL a vocabulary for exactly these topologies — point-to-point wires, tri-state buses, wired-AND/OR, and pull-tied rails.

5. Verilog Syntax — Declaring a Net

Net declarations follow the same shape regardless of which net keyword you choose:

net-syntax.v — declaration anatomy
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// <net-type> [signed] [strength] [range] name [= continuous-assign];
 
wire                       enable;            // scalar, default unsigned
wire        [7:0]          data_bus;          // 8-bit packed vector
wire signed [15:0]         s_acc_w;           // signed 16-bit net
wand                       open_drain_bus;    // wired-AND, scalar
tri         [31:0]         tristate_bus;      // 32-bit tri-state bus
tri1                       irq_n;             // pulled HIGH when nothing drives
tri0        [3:0]          mode_strap_n;      // pulled LOW when nothing drives
supply0                    gnd;               // permanent 0
supply1                    vdd;               // permanent 1
trireg      (small) [7:0]  retain_data;       // charge-storage (sim-only)

The optional pieces:

  • signed changes the operator interpretation for <, >, >>>, and width-extension rules — see Number Representation §Width-Extension Rules.
  • Strength specifier (e.g. (strong1, pull0)) overrides the default (strong1, strong0) for the net's drivers — almost never used in RTL; see the sibling 5.1.2 Signal Strengths page when it lands.
  • Range ([MSB:LSB]) makes the net a vector instead of a scalar. Conventionally [N-1:0].
  • Continuous-assignment shorthandwire enable = req & gnt; is identical to declaring wire enable; then writing assign enable = req & gnt; on a separate line.
continuous-assign-shorthand.v — two equivalent forms
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Form A — declaration + separate assign
wire        ack;
assign      ack = req & gnt;
 
// Form B — declaration with embedded continuous assignment
wire        ack = req & gnt;

Both produce identical netlists. The shorthand is convenient for small nets; the explicit form is easier to read in dense RTL where the declaration and the logic live in different sections of the file.

5.1 The full net catalogue

The parent chapter (Variables & Data Types §Net Types) lists all eleven net types in a single table. The short version: wire covers 95% of real RTL; the rest are specialised:

Net keywordResolution ruleTypical use
wireDriver value (last-write-wins on a single driver)Default point-to-point net
triSame as wireStyle alias — signals "this net has tri-state drivers"
wand, triandWired-AND (any 0 driver wins)Open-drain emulation
wor, triorWired-OR (any 1 driver wins)Wired-OR bus (legacy)
tri0Pulls to 0 when no driver activePull-down resistor model
tri1Pulls to 1 when no driver activePull-up resistor model
triregHolds last driven value when no driverCapacitive charge storage (sim-only)
supply0Constant 0Power-rail GND tie
supply1Constant 1Power-rail VDD tie

Sibling sub-pages will go deeper into each:

  • 5.1.1 wire and tri Nets — the workhorse pair.
  • 5.1.2 Signal Strengths — the eight-level strength lattice that drives resolution.
  • 5.1.3 Wired Netswand / wor / triand / trior.
  • 5.1.4 trireg Nets — charge-storage modelling.
  • 5.1.5 tri0 and tri1 Nets — pull-resistor models.
  • 5.1.6 supply0 and supply1 — power-rail tie-offs.

6. Executable Examples

Three concrete examples — minimal, practical, and industry — that exercise the net family.

6.1 Minimal — a single-driver net between two modules

rtl/wire_basic.v — point-to-point wire
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
module wire_basic;
 
    wire        clk;          // clock distributed from the top-level
    wire [7:0]  data;         // 8-bit data bus
 
    // continuous assignment — the driver of `data`
    assign data = 8'hA5;
 
    // a load — reads but never drives `data`
    initial $monitor("t=%0t  data=%h", $time, data);
 
endmodule

data is an 8-bit net. It has one driver (the assign) and one observer (the $monitor). At t = 0 the simulator resolves data to 8'hA5; it stays that way for the whole run. No procedural code touches it because no procedural code can — data is a net.

6.2 Practical — a tri-state bus with three masters

rtl/tristate_bus.v — three drivers, one shared net
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
module tristate_bus (
    input  wire        oe_a, oe_b, oe_c,
    input  wire [7:0]  data_a, data_b, data_c,
    output wire [7:0]  bus
);
 
    // Each master drives the bus through a tri-state buffer.
    // When oe_* is 0, the driver outputs Z (high-impedance) and contributes
    // nothing to the resolved bus value.
    assign bus = oe_a ? data_a : 8'bz;
    assign bus = oe_b ? data_b : 8'bz;
    assign bus = oe_c ? data_c : 8'bz;
 
endmodule

Three assign statements drive the same net. Whichever master has oe_* asserted drives the bus; the other two contribute Z (high-impedance), which does not participate in resolution. The system contract is "at most one oe_* asserted at a time" — if two are asserted with different values, the resolved value goes to X and the simulator (and any lint tool) flags the bug.

6.3 Industry — open-drain interrupt line with pull-up

The classic system topology: every peripheral can pull an IRQ line low to request service; when none does, the line should sit high. On real silicon this is a wired-AND with an external pull-up resistor. In Verilog it's a tri1 net.

rtl/irq_open_drain.v — open-drain IRQ with implicit pull-up
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
module irq_open_drain (
    input  wire        peripheral_a_req,
    input  wire        peripheral_b_req,
    input  wire        peripheral_c_req,
    output tri1        irq_n              // pulled HIGH when no peripheral asserts
);
 
    // Each peripheral pulls irq_n LOW to request, releases (Z) otherwise.
    // tri1 means "if nothing's driving, treat as 1" — the implicit pull-up.
    assign irq_n = peripheral_a_req ? 1'b0 : 1'bz;
    assign irq_n = peripheral_b_req ? 1'b0 : 1'bz;
    assign irq_n = peripheral_c_req ? 1'b0 : 1'bz;
 
endmodule

When all three peripherals are quiet, every driver outputs Z and the tri1 resolves to 1. As soon as any peripheral asserts, that driver wins (it's the only one that's not Z) and irq_n goes low. If two peripherals try to release simultaneously and a third is still pulling low, the low value still wins — exactly matching open-drain behaviour on silicon.

7. Simulation Behavior — Resolution and Defaults

The interesting Verilog semantics live in how the simulator handles multiple drivers on a single net and what the simulator returns for a net with no driver at all.

7.1 Resolution when multiple drivers exist

The resolution rule depends on the net type:

NetTwo drivers 0 + 1One Z + one 0 (or 1)Two 0sTwo 1s
wire / triXNon-Z driver wins01
wand / triand0 (AND of 0,1)Non-Z driver wins01
wor / trior1 (OR of 0,1)Non-Z driver wins01
tri0 (no driver active)— pulled to 0Non-Z driver wins01
tri1 (no driver active)— pulled to 1Non-Z driver wins01

The single most common surprise: a plain wire with conflicting drivers resolves to X, not to the most-recent assignment. Verilog has no concept of "this assign overrides the other" between continuous assignments on the same net; both are live at all times.

7.2 Default values at t = 0

Net typeDefault at t = 0
wire, tri, wand, wor, triand, triorz
tri00
tri11
triregx (until first driven)
supply00 (held permanently)
supply11 (held permanently)

The defaults matter for both simulation correctness (uninitialised nets propagating Z through gate logic) and for synthesis modeling (the pull-net types map to pad-cell pull resistors on the chip).

8. Waveform Analysis

The single most useful waveform for understanding nets is the tri-state-bus trace — it shows the driver enables and the resolved net value side by side, and you can read every interesting case off it: driven, floating, contention.

Tri-state bus — driver A, driver B, and the resolved net

12 cycles
Tri-state bus — driver A, driver B, and the resolved netA drives — bus follows data_aA drives — bus follows…no driver — bus = Z (float)no driver — bus = Z (f…B drives — bus follows data_bB drives — bus follows…both drive opposite values → Xboth drive opposite va…clkoe_adata_aXXA1A1XXXXA2A2XXoe_bdata_bXXXXXXB1B1B2B2XXbusXXt0t1t2t3t4t5t6t7t8t9t10t11
Read the bus row top-down: Z when no oe is asserted; the driven data value when exactly one oe is asserted; X when two drivers contend. The X annotation is the simulator's warning that the system contract has been violated.

Three regions on the trace tell the whole story:

  1. Cycles 2–3 — single driver active. oe_a=1, oe_b=0. The bus carries A1.
  2. Cycles 4–5 — no driver active. Both oe_*=0. On a plain wire / tri, bus floats to Z. On a tri0, it would resolve to 0; on a tri1, to 1.
  3. Cycles 8–9 — both drivers active with different values. bus resolves to X. The simulator shows the conflict; the chip would show a destructive short — both drivers source-vs-sink current through each other.

Open the same trace under lint with tri versus wire and you'll see another distinction the language makes for the reader: declaring the bus as tri (rather than plain wire) signals to anyone reading the code that the multi-driver pattern is intentional and the system contract handles the mutual-exclusion. Both keywords behave identically at the simulator — the difference is documentation.

9. Synthesis Insights

The synthesis tool reads net declarations as instructions for how to wire up real metal:

  • wire synthesises to a piece of routing — a wire on the chip. No logic added.
  • tri also synthesises to a wire, but with the additional note that multiple drivers may exist. The drivers must be tri-state buffers (the oe ? data : 'bz pattern); the tool will produce a tri-state-enable net for each one. Inside a synthesised digital block, tri-state buffers are typically rewritten as a multiplexer tree at the synthesis stage (because most ASIC technologies do not allow internal tri-states); the tri-state shape is preserved only at chip-pad cells.
  • tri0 / tri1 map to pad cells with built-in pull-down / pull-up resistors — almost always on top-level I/O pins. Inside a block they are usually rewritten to a constant tie-off or rejected with a synthesis warning.
  • wand / wor are rarely synthesisable on modern processes. The tool either rewrites them to NAND/NOR trees or rejects them.
  • trireg is not synthesisable — it models capacitive charge retention, a simulation-only construct.
  • supply0 / supply1 synthesise to direct ties to GND / VDD.

The day-to-day synthesis discipline:

  • Use wire for everything internal. It's the most boring, most predictable choice.
  • Reach for tri / tri0 / tri1 only at the top-level pad layer. Internal tri-states are a code smell; you almost always want a mux.
  • Treat trireg as a flag for "this is a testbench artefact" — it should never appear in synthesisable RTL.
  • Use supply0 / supply1 for explicit tie-offs. Some lint suites flag assign foo = 1'b0; ties as warning-worthy; a supply0 foo; declaration makes the intent unambiguous.

10. Debugging Guide

Three failure modes account for the vast majority of net-related debug time.

10.1 Bus resolves to X — multiple drivers, no mutual exclusion

Symptom. A wire you expected to carry valid data shows x in the waveform after some specific event.

Root cause. Two or more drivers became active simultaneously with different values. Common scenarios:

  • A tri-state bus where two oe_* enables overlap on a cycle boundary (one master starts driving before the previous master releases).
  • A wire driven by two separate assign statements that the author thought were mutually exclusive but actually aren't (overlapping conditions in the ternary expressions).
  • A module port left unconnected on one instance, picking up the default Z, then a second instance accidentally drives the same wire.

Debug recipe.

  1. Locate the first cycle where bus = x. Trace backward to find which two drivers were both not-Z on that cycle.
  2. Inspect the driver-enable signals on the cycle BEFORE the X appears — overlapping enables are the usual culprit.
  3. Add an SVA-style assertion if available: assert property (@(posedge clk) $countones({oe_a, oe_b, oe_c}) <= 1);
  4. If two drivers are intentionally writing the same value (e.g. mirrored peripherals), use wand and accept the resolution, or refactor the upstream logic to drive a single point.

10.2 Net floats to Z — no driver, no pull

Symptom. A signal that downstream logic depends on shows z and propagates x into the consuming flop.

Root cause. A wire declared but never driven. Common scenarios:

  • A module port forgotten in the parent's instantiation list (an input port left unconnected — though most simulators flag this).
  • A bus declared at module scope but every driver guarded by a condition that's always false on this revision.
  • A tri-typed bus where every driver outputs Z because all enables are deasserted.

Debug recipe.

  1. Confirm the net is floating, not driven-X — look for z, not x.
  2. Search the source for assign\s+<name> and output\s+.*<name> — every plain wire should have exactly one driver.
  3. If the net is intentionally a tri-state bus, decide whether the floating-no-driver case is legal. If not, add a default driver (assign bus = enable ? data : pull_default_value;) or change the type to tri0 / tri1.

10.3 Net unexpectedly synthesises differently from simulation

Symptom. Sim passes; gate-level sim or netlist post-synthesis behaves differently around a tri-state pattern.

Root cause. The synthesis tool rewrote your tri-state as a mux. The Z value is no longer in the netlist — the simulator was free to propagate Z but the gates are forced to drive either 0 or 1. Often the mux's default case takes a value you didn't expect.

Debug recipe.

  1. Read the synthesis report — look for "tri-state buffer rewritten to mux" warnings.
  2. Run gate-level simulation; compare against RTL sim at the exact cycle of divergence.
  3. If you actually wanted a tri-state, you cannot have one inside an internal block — route the signal out to a pad-cell or restructure.
  4. If you wanted a mux all along, explicitly write the mux in RTL (assign bus = oe_a ? data_a : oe_b ? data_b : 8'h00;) so the synthesis intent is clear and the default value is yours.

11. Industry Usage

Where you actually see each net type in working RTL:

  • wire — every internal interconnect in every block. The default. Lint suites typically require explicit wire declarations for every net to catch typos and accidental implicit declarations.
  • tri — at top-level pads of FPGA designs and at the chip boundary of legacy ASICs that exposed tri-state I/O. Inside synthesisable blocks of modern ASIC RTL it appears only as a style hint and is rewritten by the synthesis tool.
  • tri0 / tri1 — pad-cell I/Os that have on-die pull resistors. JTAG TMS, strap pins read at boot, open-drain interrupt lines all use the tri0 / tri1 pattern.
  • wand / wor — rare on modern processes. Show up in legacy designs ported from older technologies and in some testbench helpers.
  • trireg — testbench-only. Used to model the capacitive retention of a precharge node in a memory or sense-amp simulation; never in synthesisable RTL.
  • supply0 / supply1 — explicit GND / VDD ties at the chip-boundary level and inside isolation-cell models for power-aware verification (UPF / CPF flows).

At any working ASIC house the day-to-day reality is that ~95% of net declarations are plain wire, ~3% are supply0 / supply1 tie-offs at the chip boundary, ~1.5% are tri / tri0 / tri1 at the pad layer, and the remaining 0.5% are legacy or testbench-only. Reaching for an exotic net type without a clear pad-layer or legacy justification is itself a code smell.

12. Design Review Notes

What a senior RTL reviewer flags on a net declaration:

  1. No tri / tri0 / tri1 inside an internal block. If a synthesisable module declares a tri-state net that is not exposed at a top-level output port, the reviewer asks for the rewrite to a mux. The synthesis tool will do this rewrite anyway; doing it explicitly in RTL keeps the gate-level behaviour predictable.
  2. No wand / wor without explicit justification. A wired-AND/OR in modern RTL is almost always a porting accident or a habit from a legacy design. Reviewer asks for the equivalent NAND/NOR tree or a multi-input gate.
  3. No trireg outside of testbench / behavioural-model files. A trireg in synthesisable RTL is rejected on sight.
  4. Every internal wire has exactly one driver. Multi-driver wire (not tri) is a contract bug; the reviewer requires either single-driver refactor or a type change to tri/wand/wor with a comment explaining the resolution.
  5. Pull-net types (tri0 / tri1) only at chip-boundary I/Os. Inside the block they're either rejected or rewritten to a constant tie. The reviewer asks the author to move the pull to the pad-cell.
  6. Width and signed-ness explicit. A reviewer wants wire [DATA_W-1:0] bus; not wire bus; when bus is actually a vector. Lint should catch this; review catches it when lint doesn't.
  7. Continuous-assign shorthand only for one-line definitions. wire ack = req & gnt; is fine. wire result = (a & b) | (c & d) | ((e ^ f) & ~g); is hard to read; split it out.
  8. supply0 / supply1 for explicit ties. A reviewer prefers supply0 vss; assign tie_lo = vss; over assign tie_lo = 1'b0; because the intent is unambiguous and lint never flags it.

13. Interview Insights

14. Exercises

Three exercises that turn the page's mental model into reflex.

Exercise 1 — Spot the bug

The following module compiles and simulates without warnings but produces an unexpected X on mux_out whenever both sel_a and sel_b are asserted. Identify the bug and propose two fixes that preserve the intent ("when both are asserted, take data_a").

exercise-1.v
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
module mux_unsafe (
    input  wire        sel_a,
    input  wire        sel_b,
    input  wire [7:0]  data_a,
    input  wire [7:0]  data_b,
    output wire [7:0]  mux_out
);
 
    assign mux_out = sel_a ? data_a : 8'bz;
    assign mux_out = sel_b ? data_b : 8'bz;
 
endmodule

What to produce. A one-paragraph explanation, plus the rewritten RTL for fix A (prioritise data_a explicitly) and fix B (declare the net as tri with a comment documenting the contract).

Exercise 2 — Build it

Write a synthesisable Verilog module pulled_open_drain_bus that models a 4-bit open-drain bus with an implicit pull-up:

  • Inputs: wire [3:0] drv_en (one bit per driver), wire [3:0] drv_val (the value each driver would put on the bus when its enable is asserted).
  • Output: tri1 [3:0] (wait — a vector tri1? — yes, perfectly legal).

The semantic: when any driver's enable is asserted, that driver wins by pulling the bus to its drv_val; when all enables are deasserted, the bus floats and the tri1 pulls it to 4'hF.

Hints. You'll need four assign statements, one per driver — each gated by the driver's enable.

Exercise 3 — Predict the waveform

Hand-sketch the waveform of bus over 8 cycles given the following stimulus, assuming the tri declaration in §6.2 Practical:

Cycleoe_aoe_boe_cdata_adata_bdata_c
0000xxxxxx
11008'h12xxxx
21008'h34xxxx
3000xxxxxx
4010xx8'h56xx
51108'h788'h56xx
6010xx8'h9Axx
7001xxxx8'hBC

Predict the bus value at every cycle. (Hint: cycle 5 is the interesting one.)

15. Summary

Verilog's net family is the language's vocabulary for physical interconnect — the half of the type system that models wires on the chip, distinct from variables (reg, integer, …) that model procedural storage. The defining property of every net type is no storage — a net carries whatever its driver(s) put on it at every instant, and floats to Z (or to 0 / 1 for tri0 / tri1) when nothing is driving. Eleven keywords cover the whole family; wire carries 95% of real RTL, with tri / tri0 / tri1 / supply0 / supply1 reserved for the pad-cell boundary and wand / wor / trireg reserved for legacy / testbench-only use.

The day-to-day discipline:

  • wire for everything internal. Single-driver, point-to-point — the most boring choice is the right one.
  • tri / tri0 / tri1 only at the chip boundary. Internal tri-states are rewritten to muxes by the synthesis tool; if you actually need tri-state behaviour on silicon you need to be at a pad cell.
  • supply0 / supply1 for explicit ties. Cleaner than 1'b0 / 1'b1 literals and recognised by power-aware verification flows.
  • Treat any multi-driver wire (not tri) as a contract bug. Lint will catch it; review should reject it.
  • trireg belongs in testbench files only. No synthesis tool accepts it.

Internalise the family distinction (net vs variable) and the resolution rule of each net type and you've covered the entire physical-data-types layer. The sibling sub-pages (5.1.1–5.1.6, listed in §5) drill deeper into each net keyword; Chapter 5.2 picks up with the variable family (reg, integer, real, …) on the other side of the line.

  • Variables & Data Types — Chapter 5; the parent overview of both nets and variables.
  • RTL Designing — Chapter 3; how net-family discipline plays out across a full module.
  • Operator Usage — Chapter 4.3; the operators that consume these typed signals (and propagate x / z through them).
  • Number Representation — Chapter 4.4; the literal syntax (8'bz, 1'b1) that drives these nets.
  • Identifier Declaration — Chapter 4.6; the naming discipline for the nets declared here.