Skip to content

Verilog · Chapter 13.2 · Data-Flow Modeling

Dataflow Practical Examples in Verilog — Muxes, Decoders, Adders & Comparators

With the operators and the continuous assignment in hand, you can now build the circuits every datapath is made of. This is the dataflow cookbook: the canonical combinational building blocks, including multiplexers, decoders, priority encoders, adders, and comparators, each written as a clean continuous assignment of an operator expression. The point of this page is fluency, seeing that a mux is a conditional operator, a decoder is a shift, a priority encoder is a chain of ternaries, an adder is an addition with a concatenated carry, and a comparator is a relational operator, so that when you need one of these blocks you write it in one line of dataflow rather than wiring gates. Every example is a complete, synthesizable combinational module. By the end you can express any standard combinational function in dataflow on sight.

Foundation20 min readVerilogDataflowMultiplexerDecoderAdderComparatorRTL Design

Chapter 13 · Section 13.2 · Data-Flow Modeling

1. The Engineering Problem

Every datapath is built from a small set of combinational blocks — muxes to select, decoders to translate, encoders to prioritize, adders to compute, comparators to decide. You now know the operators that are these blocks and the assign that wires them. The remaining step is fluency:

How do you express each standard combinational building block — mux, decoder, encoder, adder, comparator — as a single, clean dataflow assignment?

The answer is a small cookbook, and it is mostly recognition: a multiplexer is the conditional operator (?:), a decoder is a shift (1 << sel), a priority encoder is a chain of ternaries, an adder is + with the carry captured by concatenation, and a comparator is a relational operator. Once you see each block as its operator expression, you write it in one line instead of assembling gates. This page is that cookbook — each building block as a complete dataflow module, applying everything from Chapters 10 and 13.1.

2. Mental Model — Every Combinational Block Is a Dataflow Expression

Visual A — the combinational building blocks

Building blocks → operator expressions

data flow
Building blocks → operator expressionsMultiplexer?: (conditional)Decoder1 << sel (shift)Priority encoder?: chain + reductionAdder+ with {carry, sum}Comparator== / < / >
Each canonical combinational block is an operator expression wired with a continuous assignment: a mux is a conditional, a decoder a shift, a priority encoder a ternary chain, an adder a + with carry capture, a comparator a relational operator. This page builds each one.

3. Multiplexers

A multiplexer selects one of several inputs. In dataflow it is the conditional operator (10.11):

mux.v
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 2:1 mux
module mux2 #(parameter integer W = 8)(
    input  [W-1:0] d0, d1,
    input          sel,
    output [W-1:0] y
);
    assign y = sel ? d1 : d0;
endmodule
 
// 4:1 mux — nested ternaries select on a 2-bit sel
module mux4 #(parameter integer W = 8)(
    input  [W-1:0] d0, d1, d2, d3,
    input  [1:0]   sel,
    output [W-1:0] y
);
    assign y = sel[1] ? (sel[1:0] == 2'b11 ? d3 : d2)
                      : (sel[0]            ? d1 : d0);
    // equivalently, a flat ternary chain:
    // assign y = (sel == 2'd0) ? d0 : (sel == 2'd1) ? d1 : (sel == 2'd2) ? d2 : d3;
endmodule

The 2:1 mux is one ternary; the 4:1 is nested or chained ternaries selecting on the 2-bit sel. Both are width-independent (the ?: selects the whole bus, 10.11). The flat chain reads as "if sel is 0 then d0, else if 1 then d1, …" — the priority-ladder form. Selection is the most common combinational operation, and ?: is its dataflow form.

4. Decoders

A decoder translates an N-bit code into a one-hot output (exactly one bit set). In dataflow it is elegantly a shift1 shifted left by the code (10.7):

decoder.v
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 2:4 decoder with enable
module decoder2to4 (
    input  [1:0] sel,
    input        en,
    output [3:0] onehot
);
    assign onehot = en ? (4'b0001 << sel) : 4'b0000;
endmodule

`4'b0001 << sel` shifts a single 1 to position sel, producing the one-hot output: sel = 0 gives 0001, sel = 1 gives 0010, sel = 2 gives 0100, sel = 3 gives 1000. The enable gates it to all-zeros when off (a ternary, 10.11). This one-line decoder is width-independent in spirit and far cleaner than enumerating each output bit. (A 3:8 decoder is the same with 8'b1 << sel.)

Visual B — the decoder as a shift

Decoder implemented as a shiftsel (2-bit)the code4'b0001 << selshift a 1 to position selonehot (4-bit)exactly one bit set12
A 2:4 decoder shifts a single 1 left by the select value: 4'b0001 << sel puts the 1 at position sel, producing a one-hot output (exactly one bit set). sel=0 → 0001, sel=1 → 0010, sel=2 → 0100, sel=3 → 1000. The enable gates the result to all-zeros when off. The decoder is a shift — one clean dataflow assignment.

5. Priority Encoders

A priority encoder is the inverse of a decoder: given several request bits, output the index of the highest-priority set bit (plus a valid flag). In dataflow it is a chain of ternaries (10.11) with a reduction (10.5) for valid:

priority-encoder.v
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 4-to-2 priority encoder: req[3] highest priority
module priority_enc (
    input  [3:0] req,
    output [1:0] code,
    output       valid
);
    assign valid = |req;                 // any request? (reduction-OR, 10.5)
    assign code  = req[3] ? 2'd3 :
                   req[2] ? 2'd2 :
                   req[1] ? 2'd1 :
                            2'd0;        // req[0] (or default when none)
endmodule

The ternary chain encodes priority directly: req[3] is checked first (highest priority), down to req[0]. The chain's top-to-bottom order is the priority order (10.11) — req[3] wins over req[2] if both are set. valid (an OR-reduction, 10.5) flags whether any request is present, so the consumer can distinguish "code 0, request on bit 0" from "no request." Priority encoders appear in arbiters, interrupt controllers, and find-first logic.

6. Adders

Addition is the + operator (10.2), with the carry captured by concatenation (10.10):

adders.v
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// half adder: a + b, 1-bit operands, 2-bit result
module half_adder (
    input  a, b,
    output sum, carry
);
    assign {carry, sum} = a + b;         // 1+1 can be 2 → needs 2 bits
endmodule
 
// full adder: a + b + cin
module full_adder (
    input  a, b, cin,
    output sum, cout
);
    assign {cout, sum} = a + b + cin;
endmodule
 
// N-bit adder: the + operator builds the whole adder
module adderN #(parameter integer W = 8)(
    input  [W-1:0] a, b,
    input          cin,
    output [W-1:0] sum,
    output         cout
);
    assign {cout, sum} = a + b + cin;    // (W+1)-bit result split: carry + sum
endmodule

The pattern is the same at every width: the + operator builds the adder, and `{carry, sum}` on the left captures the carry-out in its own bit (the 10.2 overflow fix via 10.10). The N-bit adderN is a single assignment — the synthesizer builds an optimized adder of the right width. You rarely wire full-adders structurally; the + operator and a sized result do it.

7. Comparators

Comparison is the relational and equality operators (10.6, 10.8):

comparators.v
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// equality comparator
module eq_cmp #(parameter integer W = 8)(
    input  [W-1:0] a, b,
    output         eq
);
    assign eq = (a == b);                // 10.8
endmodule
 
// magnitude comparator: greater / equal / less
module mag_cmp #(parameter integer W = 8)(
    input  [W-1:0] a, b,
    output         gt, eq, lt
);
    assign gt = (a > b);                 // 10.6
    assign eq = (a == b);
    assign lt = (a < b);
endmodule

Equality is == (10.8); magnitude is the relational operators <, > (10.6). The three outputs of the magnitude comparator are three concurrent continuous assignments. Remember the signedness rule (10.6): if the operands are signed, declare them signed so negatives compare correctly — a magnitude comparator on signed data versus unsigned builds different (and differently-correct) hardware.

Visual C — building blocks in a datapath

The blocks composing a small datapath

data flow
The blocks composing a small datapathdecoderselect an operandmuxroute dataaddercomputecomparatordecide
A datapath is these blocks composed: decoders and muxes route data, adders and other arithmetic compute, comparators decide. Each is a one-line dataflow assignment, and continuous assignments wire them together concurrently into the combinational logic of a design.

8. Worked Example — A Small ALU

The blocks compose into a small combinational ALU — a mux selecting among arithmetic and logic results, all in dataflow:

alu.v
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
module alu #(parameter integer W = 8)(
    input  [W-1:0] a, b,
    input  [1:0]   op,           // 00:add 01:sub 10:and 11:or
    output [W-1:0] result,
    output         carry,
    output         zero
);
    wire [W-1:0] sum  = a + b;           // adder
    wire [W-1:0] diff = a - b;           // subtractor
    wire [W:0]   add_full = a + b;       // capture carry for the add case
 
    assign result = (op == 2'b00) ? sum  :   // mux of results (?: chain)
                    (op == 2'b01) ? diff :
                    (op == 2'b10) ? (a & b) :
                                    (a | b);
    assign carry  = (op == 2'b00) ? add_full[W] : 1'b0;   // carry only for add
    assign zero   = ~|result;            // result == 0  (reduction-NOR, 10.5)
endmodule

The ALU is pure dataflow: continuous assignments computing each operation (adder, subtractor, bitwise), a ternary chain selecting the result by op (a mux), the carry extracted from the captured full sum, and a reduction-NOR for the zero flag. Every line is an operator expression from Chapter 10 wired with assign — the building blocks of this page, composed. This is how combinational datapaths are written.

9. Industry Perspective

  • Dataflow building blocks are everyday RTL. Muxes, decoders, encoders, adders, and comparators are written as continuous assignments of operator expressions — concise, readable, and directly synthesizable.
  • The operator is the block. Experienced engineers see ?: and read "mux," 1 << sel and read "decoder," + with a captured carry and read "adder" — fluency this page builds.
  • Parameterized blocks scale. Real building blocks are parameterized by width (#(parameter W = 8)) so one module serves every size — the width-independence of the operators (10.5, 10.9, 10.11) makes this natural.
  • Synthesis optimizes the operators. Writing + lets the synthesizer pick an optimal adder for the timing target; writing the block as an expression, not a gate structure, gives the tool the freedom to optimize.

10. Common Mistakes

  1. Dropping the adder carrysum = a + b into a same-width net loses the carry; capture it with {carry, sum} (10.2/10.10, DebugLab 2).
  2. Wrong decoder width or non-one-hot logic — the one-hot output must match the decode width; 1 << sel must be sized correctly (DebugLab 1).
  3. Priority-encoder order reversed — the ternary chain's order is the priority order; highest priority first (10.11).
  4. Comparator signedness — signed data compared unsigned (or vice versa) flips on negatives (10.6, DebugLab 3).
  5. Mux branch width mismatch?: branches should be the same width (10.11).

11. Debugging Lab

Three dataflow-building-block debug post-mortems

12. Interview Q&A

13. Exercises

Exercise 1 — Write the block

Write a one-line continuous assignment for each: (a) an 8:1 mux selecting d[0..7] on a 3-bit sel; (b) a 3:8 decoder with enable; (c) an equality comparator for two 16-bit values; (d) a zero flag for an 8-bit result.

Exercise 2 — Build a priority encoder

Write a dataflow 8-to-3 priority encoder: req[7] highest priority, output a 3-bit code and a valid flag. Show the ternary chain and the valid reduction.

Exercise 3 — Fix the building blocks

Each has a bug from §10. Identify and fix it.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
assign onehot = 4'b1 << sel;          // 3-bit sel, 8-output decoder
assign sum = a + b;                   // 8-bit a,b; need the carry too
assign code = req[1] ? 2'd1 : req[2] ? 2'd2 : 2'd0;  // req[2] should be higher priority

Exercise 4 — Compose a datapath

Sketch (in dataflow assignments) a block that: selects one of two 8-bit operands with sel, adds it to a third operand, and asserts overflow if the add carries out. Name the operators used for each step.

14. Summary

The dataflow cookbook: every canonical combinational building block is an operator expression wired with a continuous assignment.

The building-block-to-operator map:

  • Multiplexer → ?:assign y = sel ? d1 : d0;; N:1 is a ternary chain (priority order = chain order).
  • Decoder → 1 << selassign onehot = (1 << sel);, sized to the full one-hot width; gate with an enable ternary.
  • Priority encoder → ?: chain + reduction — ternary chain ordered by priority, |req for valid.
  • Adder → + with {carry, sum}assign {cout, sum} = a + b + cin; captures the carry at any width.
  • Comparator → == / < / >assign gt = (a > b);; mind signedness.

The discipline this page instils:

  • See the operator as the block?: is a mux, 1 << sel a decoder, + with carry capture an adder, < a comparator.
  • Carry the Chapter 10 rules — capture adder carries, size decoders to the one-hot width, match comparator signedness, align mux branch widths.
  • Parameterize by width so one block serves every size.

You can now build any standard combinational block in dataflow on sight. The final dataflow page goes further: Chapter 13.3 Advanced Techniques covers larger and parameterized dataflow patterns — generated structures, complex selection networks, and the point where dataflow gives way to procedural always modeling (Chapter 14) for sequential logic.