The short answer: Dataflow modelling describes combinational logic with continuous assignments: assign target = expression;. The target must be a net (a wire), and the assignment is always active: whenever any signal on the right changes, the target is updated. It’s the simplest way to write muxes, arithmetic and Boolean logic. Two assigns driving the same wire create two drivers and a conflict, so each net should have exactly one assign.
Continuous assignments are the closest Verilog gets to drawing a schematic with equations. They’re short, they can’t create latches, and they’re very readable for simple logic. This post combines my older posts on dataflow modelling and continuous assignments.
- What assign really means
- Rules
- Useful examples
- assign or always @(*)?
- Common mistakes
Table of Contents
Part 1: What assign really means

wire y;
assign y = (a & b) | c;
// or declare and assign in one line (net declaration assignment)
wire z = a ^ b;
The order of assign statements in a module doesn’t matter. They all describe hardware that exists at the same time.
Part 2: Rules
- The left-hand side must be a net (wire) or a concatenation of nets, never a reg.
- The right-hand side can use any signals: nets, regs, constants and function calls.
- Each net should be driven by one assign. Two drivers on the same wire resolve to x when they disagree.
- Delays like
assign #2 y = a & b;are for simulation only; synthesis ignores them.
Part 3: Useful examples
Full adder
assign sum = a ^ b ^ cin;
assign cout = (a & b) | (cin & (a ^ b));
N-bit adder with carry out
assign {cout, sum} = a + b + cin; // sum is N bits, cout is 1 bit
4:1 multiplexer
assign y = (sel == 2'd0) ? d0 :
(sel == 2'd1) ? d1 :
(sel == 2'd2) ? d2 : d3;
2-to-4 decoder
assign dec = en ? (4'b0001 << sel) : 4'b0000;
Comparator
assign eq = (a == b);
assign gt = (a > b);
assign lt = (a < b);
Tri-state output
assign pad = oe ? dout : 1'bz; // only at chip pads in modern designs
Part 4: assign or always @(*)?
| assign | always @(*) | |
|---|---|---|
| Target type | wire | reg |
| Can create a latch | No | Yes, if an output isn’t assigned on every path |
| Best for | Simple expressions, muxes, arithmetic | Complex logic with if/case, many outputs |
| Readability | Great for one-liners | Better for long decision logic |
Both synthesise to the same kind of logic. I use assign for anything that fits on one or two lines and always @(*) when the logic needs if or case. See writing combinational logic for more.
Part 5: Common mistakes
- Assigning a reg with assign. Compile error; declare it as a wire (or use logic in SystemVerilog).
- Two assigns to one wire. Usually a copy-paste bug that shows up as x in simulation.
- Long nested ?: chains. They work, but become a priority mux and are hard to read; switch to a case statement.
- Losing the carry. Make the left-hand side wide enough, as in the adder example.
Practise this on VLSI Forge
I built VLSI Forge so you can write RTL in your browser, run it on a real simulator and check every signal in the waveform. Free, nothing to install.
Previous in my Verilog series: Levels of modelling in Verilog. Next: initial and always blocks.
FAQ
What is a continuous assignment in Verilog?
A statement of the form assign net = expression; that keeps the net equal to the expression at all times, like a wire connected to logic gates.
Can I use assign with a reg?
No. The target of an assign must be a net. For a reg, use an always block. SystemVerilog’s logic type can be used with either.
Can assign create a latch?
No. A continuous assignment always describes combinational logic (or a combinational loop if it feeds back on itself).
Is the order of assign statements important?
No. They all run concurrently, so you can write them in any order.
