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 reg — reg 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 insidealwaysandinitialblocks. - Nets (
wireand 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
wirecannot appear on the left of=insidealways— there is no storage to update. - Why
regcannot appear on the left ofassign— there is no driver model;regis 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
zwhile variables default tox.
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:
| Question | Net answer | Variable answer |
|---|---|---|
| Has memory? | No — value follows the driver | Yes — value held between assignments |
| Multiple sources? | Yes — resolved by the net's type rule | No — last procedural write wins |
| What if no source drives? | Z (high-impedance) or pulled per type | Unchanged — keeps last value |
| Where assigned? | assign, gate primitive, module-output port | Inside 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 flowThe 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.
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-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:
signedchanges 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 shorthand —
wire enable = req & gnt;is identical to declaringwire enable;then writingassign enable = req & gnt;on a separate line.
// 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 keyword | Resolution rule | Typical use |
|---|---|---|
wire | Driver value (last-write-wins on a single driver) | Default point-to-point net |
tri | Same as wire | Style alias — signals "this net has tri-state drivers" |
wand, triand | Wired-AND (any 0 driver wins) | Open-drain emulation |
wor, trior | Wired-OR (any 1 driver wins) | Wired-OR bus (legacy) |
tri0 | Pulls to 0 when no driver active | Pull-down resistor model |
tri1 | Pulls to 1 when no driver active | Pull-up resistor model |
trireg | Holds last driven value when no driver | Capacitive charge storage (sim-only) |
supply0 | Constant 0 | Power-rail GND tie |
supply1 | Constant 1 | Power-rail VDD tie |
Sibling sub-pages will go deeper into each:
- 5.1.1
wireandtriNets — the workhorse pair. - 5.1.2 Signal Strengths — the eight-level strength lattice that drives resolution.
- 5.1.3 Wired Nets —
wand/wor/triand/trior. - 5.1.4
triregNets — charge-storage modelling. - 5.1.5
tri0andtri1Nets — pull-resistor models. - 5.1.6
supply0andsupply1— 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
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);
endmoduledata 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
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;
endmoduleThree 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.
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;
endmoduleWhen 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:
| Net | Two drivers 0 + 1 | One Z + one 0 (or 1) | Two 0s | Two 1s |
|---|---|---|---|---|
wire / tri | X | Non-Z driver wins | 0 | 1 |
wand / triand | 0 (AND of 0,1) | Non-Z driver wins | 0 | 1 |
wor / trior | 1 (OR of 0,1) | Non-Z driver wins | 0 | 1 |
tri0 (no driver active) | — pulled to 0 | Non-Z driver wins | 0 | 1 |
tri1 (no driver active) | — pulled to 1 | Non-Z driver wins | 0 | 1 |
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 type | Default at t = 0 |
|---|---|
wire, tri, wand, wor, triand, trior | z |
tri0 | 0 |
tri1 | 1 |
trireg | x (until first driven) |
supply0 | 0 (held permanently) |
supply1 | 1 (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 cyclesThree regions on the trace tell the whole story:
- Cycles 2–3 — single driver active.
oe_a=1,oe_b=0. The bus carriesA1. - Cycles 4–5 — no driver active. Both
oe_*=0. On a plainwire/tri,busfloats toZ. On atri0, it would resolve to0; on atri1, to1. - Cycles 8–9 — both drivers active with different values.
busresolves toX. 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:
wiresynthesises to a piece of routing — a wire on the chip. No logic added.trialso synthesises to a wire, but with the additional note that multiple drivers may exist. The drivers must be tri-state buffers (theoe ? data : 'bzpattern); 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/tri1map 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/worare rarely synthesisable on modern processes. The tool either rewrites them to NAND/NOR trees or rejects them.triregis not synthesisable — it models capacitive charge retention, a simulation-only construct.supply0/supply1synthesise to direct ties to GND / VDD.
The day-to-day synthesis discipline:
- Use
wirefor everything internal. It's the most boring, most predictable choice. - Reach for
tri/tri0/tri1only at the top-level pad layer. Internal tri-states are a code smell; you almost always want a mux. - Treat
triregas a flag for "this is a testbench artefact" — it should never appear in synthesisable RTL. - Use
supply0/supply1for explicit tie-offs. Some lint suites flagassign foo = 1'b0;ties as warning-worthy; asupply0 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
wiredriven by two separateassignstatements 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.
- Locate the first cycle where
bus=x. Trace backward to find which two drivers were both not-Zon that cycle. - Inspect the driver-enable signals on the cycle BEFORE the X appears — overlapping enables are the usual culprit.
- Add an SVA-style assertion if available:
assert property (@(posedge clk) $countones({oe_a, oe_b, oe_c}) <= 1); - If two drivers are intentionally writing the same value (e.g. mirrored peripherals), use
wandand 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
inputport 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 outputsZbecause all enables are deasserted.
Debug recipe.
- Confirm the net is floating, not driven-X — look for
z, notx. - Search the source for
assign\s+<name>andoutput\s+.*<name>— every plainwireshould have exactly one driver. - 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 totri0/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.
- Read the synthesis report — look for "tri-state buffer rewritten to mux" warnings.
- Run gate-level simulation; compare against RTL sim at the exact cycle of divergence.
- 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.
- 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 explicitwiredeclarations 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. JTAGTMS, strap pins read at boot, open-drain interrupt lines all use thetri0/tri1pattern.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:
- No
tri/tri0/tri1inside 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. - No
wand/worwithout 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. - No
triregoutside of testbench / behavioural-model files. Atriregin synthesisable RTL is rejected on sight. - Every internal
wirehas exactly one driver. Multi-driverwire(nottri) is a contract bug; the reviewer requires either single-driver refactor or a type change totri/wand/worwith a comment explaining the resolution. - 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. - Width and signed-ness explicit. A reviewer wants
wire [DATA_W-1:0] bus;notwire bus;whenbusis actually a vector. Lint should catch this; review catches it when lint doesn't. - 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. supply0/supply1for explicit ties. A reviewer preferssupply0 vss; assign tie_lo = vss;overassign 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").
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;
endmoduleWhat 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 vectortri1? — 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:
| Cycle | oe_a | oe_b | oe_c | data_a | data_b | data_c |
|---|---|---|---|---|---|---|
| 0 | 0 | 0 | 0 | xx | xx | xx |
| 1 | 1 | 0 | 0 | 8'h12 | xx | xx |
| 2 | 1 | 0 | 0 | 8'h34 | xx | xx |
| 3 | 0 | 0 | 0 | xx | xx | xx |
| 4 | 0 | 1 | 0 | xx | 8'h56 | xx |
| 5 | 1 | 1 | 0 | 8'h78 | 8'h56 | xx |
| 6 | 0 | 1 | 0 | xx | 8'h9A | xx |
| 7 | 0 | 0 | 1 | xx | xx | 8'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:
wirefor everything internal. Single-driver, point-to-point — the most boring choice is the right one.tri/tri0/tri1only 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/supply1for explicit ties. Cleaner than1'b0/1'b1literals and recognised by power-aware verification flows.- Treat any multi-driver
wire(nottri) as a contract bug. Lint will catch it; review should reject it. triregbelongs 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.
Related Tutorials
- 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/zthrough 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.