Dataflow Modeling
Dataflow modeling describes a circuit as one or more equations relating outputs to inputs, using the assign keyword instead of naming individual gates. It's the style already previewed informally in Operators and Expressions — this page covers what assign itself actually means, beyond just "evaluate this expression."
Continuous assignment
assign sum = a ^ b ^ cin;
assign cout = (a & b) | (b & cin) | (a & cin);
The keyword continuous is the operative idea: an assign statement isn't executed once — it's a standing description that holds true at all times. The moment any signal on the right-hand side changes, the left-hand side is immediately re-evaluated and updated, forever, for as long as the circuit exists. This is fundamentally different from a procedural assignment inside an always block (covered next page), which only runs when something in its sensitivity list triggers it. An assign target must be a wire (a net) — never a reg — because it's describing hardware driven continuously by combinational logic, not a value being set by a step of a procedure.
A wire can also be given its continuous-assignment expression right at the point it's declared, instead of as a separate assign line — this is called implicit continuous assignment:
wire w1 = a ^ b; // identical to: wire w1; assign w1 = a ^ b;
The two forms produce identical hardware; implicit continuous assignment is just a shorthand for declaring the net and writing its assign in one line instead of two. A net can only be given a value this way once, at its own declaration — it's not a substitute for assign everywhere, just a convenience for the common case of a wire whose value is fully described the moment it's introduced.
This one assign line is exactly equivalent, gate-for-gate, to the six-primitive gate-level circuit from Gate-Level Modeling — same XOR/AND/OR gates, same wires, just written as an equation instead of individually named and wired:
Three completely different-looking pieces of code — gate primitives, this assign, and a behavioral always block next page — all describe the identical physical circuit. That's the core thing to internalize about Verilog's modeling styles: they're different ways of expressing hardware, not different hardware.
The conditional operator as a multiplexer
The ternary operator ?: is dataflow modeling's standard way to describe a multiplexer:
assign out = sel ? in1 : in0; // sel=1 selects in1, sel=0 selects in0
This is the exact 2-to-1 MUX from Digital Design's Multiplexers, Decoders & Comparators page (Out = I0·S' + I1·S), just written with ?: instead of the SOP expression — both describe the same select-and-route hardware:
in0 ──┐
│ ┌───────┐
└─►│ │
│ MUX │──── out
┌─►│ │
in1 ──┘ └───┬───┘
│
sel
Ternary operators chain to build wider muxes without a case statement:
assign out = sel[1] ? (sel[0] ? in3 : in2)
: (sel[0] ? in1 : in0); // 4-to-1 MUX
A 2-to-1 or 4-to-1 mux reads fine as nested ?:. Anything wider, or with more than one level of nesting, is usually clearer as a case statement inside a behavioral block — covered in the next section, on conditional and case statements. Dataflow style is best kept to genuinely equation-shaped logic; reach for behavioral modeling once the logic is more naturally described as decisions than as a formula.
Delays in dataflow assignments
Like gate primitives, assign accepts a delay for simulation purposes:
assign #3 sum = a ^ b ^ cin; // sum updates 3 time units after any RHS input changes
Exactly as with gate delays, this is a simulation-only annotation — synthesis tools ignore it and derive real timing from the target technology after synthesis and place-and-route. It's useful for making waveforms in a simulator resemble real propagation delay, and meaningless as a way to control actual hardware timing.
What's next
Dataflow modeling is naturally suited to combinational, equation-shaped logic — but nothing about assign can express a state machine, a counter, or any circuit whose next state depends on more than the current inputs. The next page introduces behavioral modeling — initial and always blocks — which is how Verilog describes both procedural combinational logic and, crucially, the sequential logic that assign alone can't reach.