Verilog · Chapter 13.1 · Data-Flow Modeling
Continuous Assignments in Verilog — assign and the Net-Driving Rule
The continuous assignment is the engine of dataflow modeling. The assign statement wires an expression to a net so the net continuously reflects whatever the expression evaluates to, turning Verilog operators into always-on combinational hardware. This page drills its mechanics: the explicit form and the implicit form that assigns at the net's declaration, the rule that the left-hand side must be a net such as a wire, the concurrency that makes a list of assignments order-independent parallel hardware, the one-driver-per-net discipline, how the net width relates to the expression width, and the simulation-only delay form. Above all it teaches the boundary that organizes the whole RTL core: a continuous assignment drives a net, while a procedural assignment inside an always block drives a reg. Get that boundary right and dataflow modeling is straightforward; cross it and the tool rejects your code immediately.
Foundation18 min readVerilogContinuous AssignmentassignDataflowNetsRTL Design
Chapter 13 · Section 13.1 · Data-Flow Modeling
1. The Engineering Problem
An engineer writes a combinational output and, out of habit, declares it reg:
reg out; // declared as reg
assign out = a & b; // continuous assignment to itIt fails to compile — out cannot be the target of a continuous assignment. The error feels arbitrary until the rule behind it is clear: a continuous assignment (assign) drives a net, not a reg. A reg is a procedural variable, assigned inside always/initial blocks; a continuous assignment can only drive a net (a wire). Declare out as a wire and it compiles:
wire out; // a net
assign out = a & b; // continuous assignment drives the net ✓The mirror-image mistake is just as common: declaring a signal wire and then trying to assign it inside an always block (which needs a reg). Both errors come from the same boundary, and it is the single most important rule for organizing RTL:
Continuous assignment (
assign) drives a net (wire). Procedural assignment (insidealways/initial) drives areg. The kind of assignment determines the kind of signal — and crossing the line is an immediate compile error.
This page drills the continuous assignment in full — its forms, the net-driving rule, the concurrency that makes a dataflow design parallel hardware, and the one-driver discipline — so the assign statement, the workhorse of combinational RTL, is second nature.
2. Mental Model — A Continuous Assignment Is a Permanent Connection to a Net
Visual A — a continuous assignment drives a net
3. The Hardware View — Always-On Combinational Logic
A continuous assignment synthesizes to the combinational hardware that computes its expression, with the output permanently wired to the net. `assign sum = a + b;` builds an adder whose output is sum; `assign y = sel ? a : b;` builds a mux whose output is y. The net is not a storage element — it holds no state — it is simply the output wire of the combinational logic, continuously carrying the expression's current value. Change an input and the output settles to the new value after the logic's propagation delay, exactly as a real gate network behaves. This is why dataflow models combinational logic: the net is a pure function of its current inputs, with no memory.
4. The Two Forms
A continuous assignment can be written explicitly, or folded into a net's declaration:
// EXPLICIT — declare the net, then assign it:
wire y;
assign y = a & b;
// IMPLICIT — assign at the net's declaration (declaration assignment):
wire z = a & b; // equivalent to: wire z; assign z = a & b;
// both are continuous assignments — z is identical to y in behaviour- Explicit
`assign net = expr;`is the general form — declare the net anywhere, assign it anywhere. - Implicit
`wire net = expr;`combines the declaration and the assignment in one line — convenient for simple combinational nets, and common for short glue logic.
Both create the same continuous-assignment hardware; the implicit form is just shorthand. (Note: the implicit declaration assignment is different from an implicit net, the accidental 1-bit wire created by a typo — §10.)
5. The Net-Driving Rule — assign Drives Wires, always Drives Regs
The §1 rule, stated fully, is the foundation of the RTL core:
// CONTINUOUS — drives a net (wire):
wire y;
assign y = a & b; // ✓
// PROCEDURAL — drives a reg (Chapter 14):
reg z;
always @(*) z = a & b; // ✓ (reg, assigned procedurally)
// ERRORS — crossing the boundary:
reg bad1; assign bad1 = a & b; // ✗ continuous assign to a reg
wire bad2; always @(*) bad2 = a; // ✗ procedural assign to a wireThe pairing is strict and the tool enforces it at compile time:
- A net (
wire) is driven by a continuous assignment (assign) or by a module-instance output. It cannot be assigned inside analwaysblock. - A
regis driven by a procedural assignment inside analways/initialblock. It cannot be the target of anassign.
The names mislead: a reg is not necessarily a register, and a wire is not only for structural connections — the distinction is purely how the signal is assigned (the Chapter 5 "reg is not a register" point). For dataflow modeling, the rule reduces to: the things you assign are wires. When you reach procedural always blocks (Chapter 14), their targets are regs.
Visual B — the assign/always boundary
The assignment-kind / signal-kind pairing
data flow6. Concurrency — Order Does Not Matter
Continuous assignments are parallel hardware, not sequential code. Every assign in a module is active simultaneously, and the textual order is irrelevant:
// these three build the identical circuit in ANY order:
assign r = q ^ d; // listed first, but...
assign q = p | c; // ...depends on p, which is...
assign p = a & b; // ...assigned "last" — order doesn't matter
// p, q, r are three concurrent combinational blocks; a change in 'a'
// ripples through p → q → r combinationally, regardless of listing orderBecause the assignments are connections, not steps, you can write them in any sequence and the synthesized circuit is identical. When an input changes, the change propagates through the connected expressions combinationally — p updates, which updates q, which updates r — just as signals ripple through a gate network. This concurrency is the defining property of dataflow (and the opposite of procedural always statements, which do execute in order). Reading a list of assigns as parallel hardware, not sequential code, is the key habit.
7. One Driver Per Net
A net is normally driven by exactly one continuous assignment. Two assignments to the same net create two drivers that contend:
wire y;
assign y = a & b; // driver 1
assign y = c | d; // driver 2 — CONTENTION: two drivers fight on y
// For an ordinary 'wire', conflicting drivers resolve to x where they
// disagree (the net-resolution rules of Chapter 5). Almost always a bug.For an ordinary wire, when two drivers disagree the net resolves to x (and even when they agree, the dual drive is unintended). This is the multi-driver contention of Chapter 5, now appearing as accidental double assigns — usually a copy-paste or naming bug. The discipline: one continuous assignment per net. (Deliberate multi-driver buses use special net types like wand/wor or tri-state tri, also Chapter 5 — but ordinary combinational logic uses a single driver per net.)
8. Width Matching — Net vs Expression
The net's width and the expression's width should match; a mismatch silently truncates or extends, exactly as in the operator expressions of Chapter 10:
wire [7:0] sum8;
assign sum8 = a + b; // a,b 8-bit → 8-bit context; carry dropped (10.2 overflow)
wire [8:0] sum9;
assign sum9 = a + b; // 9-bit net → 9-bit context → carry preservedThe expression is evaluated in the context width that includes the left-hand net (the Chapter 10 rule), so the net's width matters: too narrow and the high bits of the result are dropped (the addition overflow of 10.2), too wide and the result is zero/sign-extended. Size the net to hold the expression's result, or truncate deliberately. Width-mismatch warnings on continuous assignments are worth treating as errors.
9. Delays in Continuous Assignments (Simulation-Only)
A continuous assignment can carry a delay, which models propagation time in simulation:
assign #5 y = a & b; // y updates 5 time units after a or b changesThe #5 is an inertial delay — y reflects the new value 5 time units after an input change, and input pulses narrower than 5 are filtered out (as a real gate filters glitches). Two cautions: this is a simulation-only timing model — synthesis builds the AND gate but ignores the delay (real gate delays come from the technology library, not the RTL); and delays in RTL are generally avoided except in specific modeling situations. For synthesizable combinational logic, write `assign y = a & b;` without a delay. (Delays belong to the delay-modeling and gate-level reference material, not everyday RTL.)
10. Worked Examples
10.1 Example 1 — combinational logic with assign
module alu_slice (
input [7:0] a, b,
input sub,
output [7:0] result,
output carry
);
assign {carry, result} = sub ? (a - b) : (a + b); // add or subtract
endmoduleA one-line dataflow ALU slice: the conditional operator (10.11) selects add or subtract, the concatenation (10.10) captures the carry, and the assign wires it all to the output nets — always-on combinational hardware. Every output here is a wire (the module's output ports default to net type), driven by the continuous assignment.
10.2 Example 2 — explicit vs implicit form
// explicit:
wire [3:0] masked;
assign masked = data & 4'hF;
// implicit (declaration assignment) — identical hardware:
wire [3:0] masked2 = data & 4'hF;Both forms create the same continuous-assignment hardware. The implicit form is convenient for simple combinational nets defined where they are declared; the explicit form separates declaration from assignment, useful when the net is declared in one place and driven in another.
10.3 Example 3 — a network of concurrent assigns
module parity_tree (
input [3:0] d,
output parity
);
wire x0, x1;
assign x0 = d[0] ^ d[1]; // these three assigns are concurrent...
assign x1 = d[2] ^ d[3]; // ...order-independent...
assign parity = x0 ^ x1; // ...a change in any d bit ripples through
endmoduleThree concurrent continuous assignments form a small XOR tree (the parity of d). They run in parallel; a change in any d bit propagates combinationally through x0/x1 to parity. Written in any order, the circuit is identical — this is dataflow as a network of always-on expressions. (A single `assign parity = ^d;` with the reduction operator, 10.5, does the same in one line — both are valid dataflow.)
11. Industry Perspective
assignis the workhorse of combinational RTL. Muxes, decoders, encoders, adders, comparators, bit-field logic, and glue are written as continuous assignments — the concise, intent-revealing dataflow style.- The net-vs-reg rule is foundational and lint-enforced. Tools immediately flag a continuous assign to a
regor a procedural assign to awire; engineers internalize the pairing early, and`default_nettype none`is used to catch the implicit-net typo class. - One driver per net is a checked discipline. Multiple continuous drivers on an ordinary net are flagged by lint as multi-driver contention; deliberate multi-driver buses use explicit
wand/wor/trinet types. - Delays are kept out of synthesizable RTL. Continuous-assignment delays are a simulation-only modeling tool; synthesizable combinational logic is written without them, with real timing coming from the cell library.
12. Common Mistakes
- Continuous-assigning a
reg—assigndrives nets; aregis procedural (§1/§5, DebugLab 1). - Procedurally assigning a
wire— the mirror error;alwaystargets must bereg. - Implicit-net typo — a misspelled net name creates a 1-bit implicit wire, silently breaking a connection (§10 → DebugLab 2).
- Two
assigns to one net — multi-driver contention; one driver per net (§7, DebugLab 3). - Width mismatch — a too-narrow net drops the expression's high bits (§8).
13. Debugging Lab
Three continuous-assignment debug post-mortems
14. Interview Q&A
15. Exercises
Exercise 1 — Net or reg?
For each, state whether the signal should be a wire or a reg, and why: (a) the target of assign out = a | b;; (b) a signal assigned inside always @(*); (c) a module output driven by a continuous assignment; (d) a signal assigned inside always @(posedge clk).
Exercise 2 — Write the assignments
Write a continuous assignment for each: (a) y is the AND of a and b; (b) q is a 2:1 mux of m and n on sel; (c) sum (9-bit) is a + b with the carry preserved; (d) p is the parity of an 8-bit data.
Exercise 3 — Fix the dataflow bugs
Each snippet has a continuous-assignment bug. Identify and fix it.
reg y; assign y = a ^ b; // combinational output
assign z = a & b; assign z = c | d; // intent: z is a & b only
assign out = inpt; // 'inpt' is a typo for 'input_signal'Exercise 4 — Reason about concurrency
(a) Do these two orderings build the same circuit? assign q = p & c; assign p = a | b; vs the reverse. Why? (b) Why can't assign drive a reg? (c) What does default_nettype none prevent, and why does it matter for continuous assignments?
16. Summary
The continuous assignment (assign) drives a net to continuously reflect an expression — the engine of dataflow modeling and the construct that turns the Chapter 10 operators into always-on combinational hardware.
The core ideas:
assign net = expression;is a permanent connection — the net always equals the expression, updating whenever any operand changes. Two forms: explicit (assign y = …) and implicit (wire y = …).- The LHS must be a net (
wire).assigndrives nets; aregis driven procedurally inalways(Chapter 14). The pairing —assign/net,always/reg — is strict and compiler-enforced. - Continuous assignments are concurrent — order-independent parallel hardware; a change ripples through combinationally.
- One driver per net — two
assigns to one net contend (resolve tox); combine alternatives with a mux. - Width and delays: size the net to the expression (or it truncates); delays are simulation-only and kept out of synthesizable RTL.
The discipline this page instils:
assign→wire,always→reg— match the assignment kind to the signal kind.- One continuous assignment per net — use a mux (
?:) to select, not multiple drivers. - Enable
`default_nettype none— turn implicit-net typos into compile errors.
You now command the continuous assignment. The next page puts it to work: Chapter 13.2 Practical Examples builds real combinational circuits in dataflow — multiplexers, decoders, encoders, adders, and comparators — using continuous assignments and the full operator vocabulary of Chapter 10.
Related Tutorials
- Data-Flow Modeling — Chapter 13 overview; the dataflow model this continuous assignment realizes.
- wire and tri Nets — Chapter 5.1.1; the nets a continuous assignment drives, and their resolution rules.
- reg — Chapter 5.2.1; the procedural variable a continuous assignment cannot drive.
- Conditional Operator — Chapter 10.11; the mux expression continuous assignments most often carry.