Verilog · Chapter 13.3 · Data-Flow Modeling
Dataflow Advanced Techniques in Verilog — Patterns and the Limits of Dataflow
This page closes the dataflow chapter with two complementary lessons. First, the advanced techniques that push continuous assignments further: the one-hot AND-OR multiplexer as an alternative to ternary chains for wide selection, parameterized and computed-width logic, complex bus assembly with concatenation and replication, and multi-operand arithmetic networks. Second, and more important, the limits of dataflow. A continuous assignment models combinational logic only; it has no clock and no state, so it cannot hold a value, count, or remember anything. Try to build a counter by assigning count to count plus one and you get a combinational feedback loop, not a counter. Recognizing that boundary is the whole point of this page: it is where dataflow ends and procedural always modeling, the home of clocked, stateful, sequential logic, begins.
Foundation18 min readVerilogDataflowOne-Hot MuxCombinationalSequential LogicRTL Design
Chapter 13 · Section 13.3 · Data-Flow Modeling
1. The Engineering Problem
Having built combinational blocks in dataflow (13.2), an engineer tries to build a counter the same way:
wire [7:0] count;
assign count = count + 1; // intent: a counter that incrementsIt does not count. It is not even a counter — it is a combinational feedback loop. A continuous assignment is instantaneous and always-on: count is wired to continuously equal count + 1, which is a contradiction with no stable solution. There is no clock to say "increment once per cycle," no register to hold the value between updates — so in simulation it oscillates or resolves to x, and in synthesis it is an illegal combinational loop. The design has no concept of "next cycle" because dataflow has no state and no clock.
Dataflow modeling describes combinational logic only — output as a function of current inputs, with no memory and no clock. It cannot hold a value, count, or remember; anything sequential (a register, a counter, an FSM) is outside dataflow's reach. A counter needs a clocked register, which is procedural
always(Chapter 14).
This page has two halves. The first pushes dataflow's combinational expressiveness further — one-hot muxes, parameterized networks, complex assembly. The second draws the limit — the boundary where dataflow ends and the next chapter, behavioural modeling, begins. Knowing both is what makes you choose the right tool: dataflow for combinational, always for sequential.
2. Mental Model — Powerful for Combinational, Hard-Limited at State
Visual A — dataflow's reach and its wall
What dataflow can and cannot model
data flow3. Advanced Combinational Patterns
Within combinational logic, dataflow scales to sophisticated structures. A few patterns worth knowing.
3.1 The one-hot AND-OR multiplexer
For a one-hot select (exactly one bit set), a mux can be built as AND-OR rather than a ternary chain — useful for wide selection and sometimes better timing:
module onehot_mux #(parameter integer W = 8)(
input [W-1:0] d0, d1, d2, d3,
input [3:0] sel, // one-hot: exactly one bit set
output [W-1:0] y
);
assign y = ({W{sel[0]}} & d0)
| ({W{sel[1]}} & d1)
| ({W{sel[2]}} & d2)
| ({W{sel[3]}} & d3);
endmoduleEach data input is ANDed with its select bit replicated to the data width (`{W{sel[i]}}`, replication from 10.9) — so a selected input passes and an unselected one becomes zero — and the results are ORed (since only one is nonzero). This AND-OR mux is flat (no nested priority), maps well to wide selection, and is the dataflow form behind many synthesizer mux implementations. It assumes a true one-hot select; if more than one bit is set, the ORed result is the OR of multiple inputs.
3.2 Parameterized and computed-width logic
Dataflow logic parameterizes cleanly, including computed widths via $clog2:
module addr_decode #(
parameter integer DEPTH = 16,
parameter integer AW = $clog2(DEPTH) // computed address width
)(
input [AW-1:0] addr,
output [DEPTH-1:0] onehot
);
assign onehot = {{(DEPTH-1){1'b0}}, 1'b1} << addr; // one-hot decode, any DEPTH
endmodule$clog2(DEPTH) computes the address width at elaboration, and the one-hot decode (1 << addr, sized to DEPTH) scales with the parameter. Computed parameters and width-independent operators (10.5, 10.9) let one dataflow module serve any size — the basis of reusable combinational IP.
3.3 Complex bus assembly
Concatenation (10.10) and replication (10.9) assemble structured buses in dataflow:
// assemble a 32-bit word: 4-bit tag, sign-extended 20-bit payload, 8-bit crc
assign word = {tag, {12{payload[19]}}, payload, crc[7:0]};
// (tag=4, sign-ext=12, payload=20... adjust widths to sum to 32)
// reverse a byte order (endianness)
assign swapped = {data[7:0], data[15:8], data[23:16], data[31:24]};Heavy concatenation/replication expressions build packed words, sign-extended fields, and reordered buses — all combinational, all one continuous assignment.
3.4 Multi-operand arithmetic
The + operator chains for multi-operand sums (size the result to hold the maximum):
wire [9:0] total = a + b + c + d; // four 8-bit operands → up to 10 bitsA sum of several operands is a single expression; the synthesizer builds an optimized adder tree. Size the result wide enough to hold the maximum sum (4 × 8-bit values can need 10 bits) so no carry is dropped (10.2).
3.5 Generated dataflow (preview)
A generate loop can replicate continuous assignments to build a regular structure (the mechanics are Chapter 14.7):
genvar i;
generate
for (i = 0; i < W; i = i + 1) begin : bit_and
assign y[i] = a[i] & b[i]; // a row of per-bit assigns
end
endgenerategenerate replicates the continuous assignment across the width at elaboration — useful for regular bit-sliced structures. It is still dataflow (continuous assignments), just programmatically replicated; the generate construct itself is drilled in Chapter 14.7.
4. The Limits of Dataflow
Now the hard boundary. Dataflow cannot model:
- State / memory — there is no construct in dataflow to hold a value between updates. A register, a flip-flop, an accumulator — all require storage dataflow does not have.
- Clocked behavior — dataflow has no clock and no notion of "on the clock edge." Anything edge-triggered (the basis of synchronous design) is outside it.
- Sequential control — counters, FSMs, pipelines, shift registers — anything whose output depends on its past values needs state, and so needs procedural
always.
The root cause is single: a continuous assignment is instantaneous and stateless — the net equals its expression now, with no memory of before and no clock to mark cycles. The §1 counter fails for exactly this reason: "increment each cycle" presupposes a previous value and a cycle boundary, neither of which dataflow provides. Attempting state in dataflow produces a combinational feedback loop (an output wired to depend on itself with no register in between) — unstable in simulation, illegal in synthesis.
Visual B — the combinational feedback trap
5. The Boundary — Dataflow vs Procedural always
The dividing line that the rest of the RTL core is built on:
Dataflow (assign, this chapter) | Procedural (always, Chapter 14) | |
|---|---|---|
| Models | Combinational logic only | Combinational and sequential |
| State / memory | None | Yes (registers, on a clock) |
| Clock | No | Yes (@(posedge clk)) |
| Output depends on | Current inputs only | Current inputs and past values |
| Target | net (wire) | reg |
| Use for | Datapath glue, muxes, arithmetic, assembly | Registers, counters, FSMs, pipelines |
The decision rule: ask whether the output must remember its past. If no — it is a pure function of current inputs — use dataflow (assign). If yes — it counts, holds, sequences, or is clocked — use a procedural always block (Chapter 14). The conditional operator ?: is the dataflow mux; its sequential sibling is the clocked always that registers a value. Real designs combine both: dataflow for the combinational datapath, always for the state.
6. Best Practices
- Keep dataflow combinational. Use continuous assignments for combinational logic — muxes, decoders, arithmetic, bus assembly — and never try to force state into them.
- Never write self-referential continuous assignments.
assign x = f(x)is a combinational feedback loop; ifxmust depend on its past, it needs a clocked register (Chapter 14). - Parameterize and use width-independent operators so dataflow blocks scale.
- Reach for
alwaysthe moment you need to remember. Recognizing the combinational/sequential boundary early is what keeps a design clean — combinational glue in dataflow, state in procedural blocks.
Visual C — the decision
Dataflow or procedural? One question
data flow7. Worked Examples
7.1 Example 1 — a one-hot mux
// 4:1 mux from a one-hot select, AND-OR form
assign y = ({W{oh[0]}} & a)
| ({W{oh[1]}} & b)
| ({W{oh[2]}} & c)
| ({W{oh[3]}} & d);Each input is gated by its replicated one-hot select bit and the gated values are ORed — a flat, wide-friendly mux (§3.1). With a true one-hot oh, exactly one input passes; the others are zeroed. This is a genuinely advanced dataflow pattern, common in wide datapaths.
7.2 Example 2 — the counter, done correctly (a preview of Chapter 14)
// WRONG (dataflow) — combinational feedback, not a counter:
// assign count = count + 1;
// RIGHT (procedural, Chapter 14) — a clocked register holds the state:
reg [7:0] count;
always @(posedge clk or negedge rst_n)
if (!rst_n) count <= 8'd0;
else count <= count + 1'b1;The counter cannot be dataflow because it must remember its value between clock edges — it needs a register. The correct form is a procedural always @(posedge clk) block driving a reg, where the clock provides the cycle boundary and the register provides the state. This is exactly what Chapter 14 builds; shown here to mark the boundary concretely. The reg count and the clocked always are the things dataflow does not have.
7.3 Example 3 — a parameterized comparator network
module is_max #(parameter integer W = 8)(
input [W-1:0] a, b, c,
output a_is_max
);
// a is the max iff a >= b AND a >= c — pure combinational dataflow
assign a_is_max = (a >= b) && (a >= c);
endmoduleA combinational network of comparisons (10.6) joined with a logical AND (10.3), parameterized by width — entirely dataflow, because the output is a pure function of the current inputs with no memory. Networks like this (range checks, max/min selection, validity logic) are dataflow's natural domain.
8. Industry Perspective
- Dataflow owns the combinational datapath. Wide muxes (often one-hot AND-OR for timing), arithmetic networks, bus packing, and decode/select logic are written as continuous assignments — the concise, optimizable form.
- The combinational/sequential split is fundamental. Every RTL engineer instinctively separates combinational logic (dataflow) from state (procedural
always); mixing them — especially accidental feedback or latches — is a classic bug class that lint and review target. - Combinational feedback loops are flagged. Synthesis and lint detect self-referential combinational logic (an output depending on itself with no register) and reject it; it is almost always an attempt to express state in dataflow.
- Dataflow +
alwaysis the whole of RTL. A module is combinational dataflow glue plus clockedalwaysblocks for state — the two styles together cover synchronous design.
9. Common Mistakes
- Self-referential continuous assignment —
assign x = f(x)is a combinational feedback loop, not state; use a clockedalways(§1/§4, DebugLab 1). - Trying to build a register/counter/FSM in dataflow — these need state and a clock; they are Chapter 14 (DebugLab 2).
- One-hot mux with a non-one-hot select — if multiple select bits are set, the AND-OR mux ORs multiple inputs (§3.1, DebugLab 3).
- Under-sizing a multi-operand sum — size the result to the maximum (§3.4).
- Overly deep single-expression logic — readability/timing suffers; sometimes a procedural
always(or pipelining) is clearer.
10. Debugging Lab
Three advanced-dataflow / boundary debug post-mortems
11. Interview Q&A
12. Exercises
Exercise 1 — Dataflow or always?
For each, state whether it can be built in dataflow (assign) or needs procedural always, and why: (a) an 8-bit comparator; (b) a free-running counter; (c) a 16:1 mux; (d) an accumulator; (e) a priority encoder; (f) a shift register.
Exercise 2 — Spot the feedback loop
Which of these are illegal combinational feedback loops, and why? (a) assign y = a & b; (b) assign y = y | a; (c) assign p = q + 1; assign q = p - 1; (d) assign sum = a + b;
Exercise 3 — Build a one-hot mux
Write a 4:1 one-hot AND-OR mux for parameter width W, with a one-hot sel[3:0]. Then state what goes wrong if sel is 4'b0011.
Exercise 4 — Cross the boundary
The snippet below tries to build a register in dataflow and fails. Explain why, then rewrite it as a correct clocked register (procedural always, previewing Chapter 14).
assign q = en ? d : q; // intent: hold q when en is 0, load d when en is 113. Summary
This page closes dataflow with its advanced patterns and its hard limit.
The advanced combinational patterns:
- One-hot AND-OR mux —
({W{sel[i]}} & di)ORed; flat, wide-friendly selection for a guaranteed one-hot select. - Parameterized / computed-width logic —
$clog2and width-independent operators scale a block to any size. - Complex bus assembly — concatenation and replication build packed, extended, reordered buses.
- Multi-operand arithmetic —
+chains into adder trees; size the result. - Generated dataflow —
generatereplicates continuous assignments (mechanics in 14.7).
The hard limit — the most important takeaway:
- Dataflow models combinational logic only — no state, no clock, no memory. It cannot hold a value, count, or remember.
- A self-referential continuous assignment is a combinational feedback loop, not a register — unstable and unsynthesizable.
- The boundary: combinational (output = f(current inputs)) → dataflow (
assign); sequential (depends on past, clocked) → proceduralalways(Chapter 14). - The deciding question: does the output depend on its own past value? If yes, it is sequential — leave dataflow.
Chapter 13 complete
With this page, Chapter 13 — Data-Flow Modeling — is complete. You can describe any combinational circuit by continuous assignments of operator expressions — the overview's model, the assign mechanics (13.1), the building-block cookbook (13.2), and now advanced patterns and the limits (13.3). Dataflow is the natural home of the Chapter 10 operators and the combinational datapath.
But every real design needs state — registers, counters, FSMs, pipelines — and that is exactly where dataflow stops. The RTL core now crosses its most important boundary into the constructs that model sequential logic:
Chapter 14 Behavioural Modeling —
alwaysblocks,initial, blocking vs non-blocking assignment, combinational vs sequential inference,if/case, loops, and the clocked logic that holds state.
Behavioural modeling is where the counter you could not build here becomes real — a procedural always @(posedge clk) block driving a reg. It completes the RTL core: combinational dataflow plus sequential procedural logic together describe any synchronous digital design.
Related Tutorials
- Dataflow Practical Examples — Chapter 13.2; the combinational building blocks these advanced patterns extend.
- Continuous Assignments — Chapter 13.1; the
assignmechanics and the net-vs-reg boundary. - Replication Operator — Chapter 10.9; the
{W{sel}}replication behind the one-hot mux. - RTL Designing — Chapter 3; the register-transfer model whose sequential half awaits Chapter 14.