Menu

Verilog Continuous Assignment: The assign Statement

How assign works - the always-true relationship it describes, what it can and can't drive, and the patterns it shines at compared to procedural code.

This page includes runnable editors - edit, run, and see output instantly.

assign Describes a Permanent Truth

A wire declaration creates a signal. An assign describes what drives it. The relationship is continuous: whatever's on the right-hand side, the left-hand side equals it, at every point in simulated time:

wire y;
assign y = a & b;

Two things implied by that pair of lines:

  • y exists as a wire in the circuit.
  • y is at all times the bitwise AND of a and b. Change either input and y follows.

There's no clock and no event triggering the update. The simulator sees a or b change, marks y dirty, and re-evaluates the expression. In hardware this maps to a couple of AND gates: combinational, stateless, instant.

The Implicit Form

You can combine the declaration and the assignment on one line:

wire y = a & b;

That's the same as the two lines above. Useful for inline wires that nobody outside this scope cares about. Many style guides actually prefer the implicit form because it puts the declaration right next to the equation.

What assign Can Drive

The target of assign must be a net type - in plain Verilog, that's wire (or one of the rarer cousins like tri, wand, wor). It cannot be a reg. If you accidentally declare your target as reg:

reg y;
assign y = a & b;   // ERROR: cannot drive reg with assign

The compiler will tell you. Either change the target to wire, or move the logic into an always @(*) block where reg is the legal target.

The right-hand side can be anything that evaluates to a value: literals, signals, parameters, operator expressions, function calls. It can mix multiple input widths; the standard widening rules apply.

When to Use assign vs always

Both can produce combinational logic. The choice is mostly about how the code reads:

  • assign is best when the relationship is a single expression. Adders, simple muxes built with ?:, masks, parity bits, anything you can write in one line.
  • always @(*) is best when you need procedural statements. Multi-way case statements, nested if/else if, anything that benefits from named intermediate regs. We cover this in Always Block.

Here's the same 4-to-1 mux written both ways:

Both modules synthesize to essentially the same multiplexer. The assign version is one line of code; the always version is six. For four cases it's close; for sixteen cases the case block is clearly easier to read.

Common Patterns

Plain Combinational Logic

assign sum   = a + b;
assign carry = a[7] & b[7];
assign equal = (data == 8'hFF);

One expression, one wire. The bread and butter of assign.

A 2-to-1 Mux

assign out = sel ? a : b;

Single conditional expression - the synthesizer turns it into a single 2-to-1 mux. The cleanest possible spelling of "select between a and b".

Bit Packing

assign status = {error, overflow, ready, busy, 4'b0};

Concatenation on the right of an assign is how you pack flags into a status byte. The result is computed and driven continuously.

Tri-State Output

assign data_pin = output_enable ? data_out : 1'bz;

When output_enable is high, drive the pin. When low, release to high impedance. This is the canonical pattern at chip pins where multiple drivers might share a wire.

Concurrency: Multiple Assigns Aren't Sequential

A reminder that won't stop being relevant: multiple assign statements in the same module all run in parallel. They are not a sequence:

assign y = a & b;     // exists at all times
assign z = a | b;     // also exists at all times, independently

The order in the file is irrelevant. Both equations are simultaneously true. The synthesizer might lay out the AND gate to the left of the OR gate or to the right; it doesn't matter, both gates fire continuously.

If you want sequential-looking behavior, you're reaching for an always block (and probably a clock). That's a different chapter.

Multiple Drivers: The Bus Pattern

A wire can have more than one assign targeting it, but you almost never want this except for tri-state buses. Two drivers fighting for a wire produces undefined behavior:

assign y = a;
assign y = b;   // BAD - two drivers, simulator picks one or x's it out

The legitimate pattern: each driver releases to z when inactive, and at most one is active at any time.

assign bus = device_a_active ? data_from_a : 1'bz;
assign bus = device_b_active ? data_from_b : 1'bz;

That works because at any given time, at most one of the two ternaries produces a non-z value. The wire's actual value is whichever driver isn't releasing.

On internal logic - anywhere that isn't a chip pin or a shared on-chip bus - one driver per wire. Multi-driver bugs are nasty to debug.

What assign Can't Do

Some things assign is the wrong tool for:

  • Storage. assign describes combinational relationships; it can't introduce a flip-flop. If you need a value to be remembered across clock cycles, that's an always @(posedge clk) block.
  • Multi-step procedural logic. You can't write if/else or case inside an assign. The closest you get is chained ?:, which gets ugly past three branches.
  • Driving registers from inside a procedural block. reg targets need procedural assignment, not assign.

Knowing the limits is how you decide when to switch to always.

What Comes Next

You've now seen the complete structural side of Verilog: declaring modules, instantiating them, and wiring up combinational logic with assign. The next chapter moves into procedural blocks - the initial and always constructs where time and ordering start to matter.

Frequently Asked Questions

What is a continuous assignment in Verilog?

assign target = expression; declares a permanent, continuous relationship: target always equals expression. Whenever any signal in the expression changes, the simulator re-evaluates the right-hand side and updates target. There's no clock, no event - the relationship is true at every point in time.

What can you target with an assign in Verilog?

assign can drive a wire, but never a reg. The target has to be a net type. If you need to assign to something inside an always block instead, declare it as reg. The compiler will reject assign x = ... if x is reg, and reject x = ... inside an always if x is wire.

When should I use assign vs an always block?

Use assign for simple combinational logic - one expression in, one signal out, no need for if/else. Use always @(*) when the logic needs procedural statements (a case, an if/else if chain, a for loop). Both produce combinational hardware; the choice is about readability.

Can you have multiple assigns to the same wire in Verilog?

Only if you're modeling a tri-state bus where each driver releases the wire to z when inactive. Two assigns trying to drive the wire to definite values at the same time produce contention - the simulator might pick one, might X-out the signal, depends on the tool. For ordinary combinational logic, one driver per wire.

Coddy programming languages illustration

Learn to code with Coddy

GET STARTED