Skip to content

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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
wire [7:0] count;
assign count = count + 1;        // intent: a counter that increments

It 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 flow
What dataflow can and cannot modelCombinationallogicany function of current inputs✓ dataflow(assign)muxes, decoders, arithmetic, assemblySequential logicregisters, counters, FSMs — needs state✗ needsprocedural alwaysChapter 14
Dataflow models any combinational function (the top path) — but cannot model sequential logic, which requires state and a clock (the bottom path, Chapter 14). The dividing question: does the output depend on its own past value? If yes, it is sequential and outside dataflow.

3. 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:

onehot-mux.v
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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);
endmodule

Each 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:

parameterized.v
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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:

bus-assembly.v
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   // 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):

multi-operand.v
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   wire [9:0] total = a + b + c + d;     // four 8-bit operands → up to 10 bits

A 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):

generated.v
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   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
   endgenerate

generate 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

Combinational feedback loop in dataflowcount + 1expressionwire count= count + 1 (itself!)feedback with noregister→ unstable / illegal12
A continuous assignment that depends on its own output — assign count = count + 1 — creates a combinational feedback loop: count is wired to equal count + 1, a contradiction with no stable value. There is no register and no clock to break the loop, so it oscillates or resolves to x in simulation and is an illegal combinational loop in synthesis. State requires a clocked register, which is procedural always (Chapter 14).

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)
ModelsCombinational logic onlyCombinational and sequential
State / memoryNoneYes (registers, on a clock)
ClockNoYes (@(posedge clk))
Output depends onCurrent inputs onlyCurrent inputs and past values
Targetnet (wire)reg
Use forDatapath glue, muxes, arithmetic, assemblyRegisters, 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; if x must depend on its past, it needs a clocked register (Chapter 14).
  • Parameterize and use width-independent operators so dataflow blocks scale.
  • Reach for always the 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 flow
Dataflow or procedural? One questionDoes the output depend on its past?Does the outputdepend on its…must it remember?No → dataflow(assign)combinationalYes → proceduralalways (Ch 14)sequential, clocked
The single question that places logic on the right side of the boundary: does the output depend on its own past value? If not, it is combinational — dataflow. If so, it is sequential — procedural always (Chapter 14). This decision recurs in every module.

7. Worked Examples

7.1 Example 1 — a one-hot mux

onehot.v
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   // 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)

counter-correct.v
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   // 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

param-network.v
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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);
endmodule

A 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 + always is the whole of RTL. A module is combinational dataflow glue plus clocked always blocks for state — the two styles together cover synchronous design.

9. Common Mistakes

  1. Self-referential continuous assignmentassign x = f(x) is a combinational feedback loop, not state; use a clocked always (§1/§4, DebugLab 1).
  2. Trying to build a register/counter/FSM in dataflow — these need state and a clock; they are Chapter 14 (DebugLab 2).
  3. 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).
  4. Under-sizing a multi-operand sum — size the result to the maximum (§3.4).
  5. 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).

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
assign q = en ? d : q;     // intent: hold q when en is 0, load d when en is 1

13. 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$clog2 and 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 dataflowgenerate replicates 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) → procedural always (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 — always blocks, 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.