Placing values onto nets and variables are called assignments. Understanding the different assignment types and when to use each is fundamental to writing correct Verilog code. Choosing the wrong assignment type can lead to synthesis errors, simulation mismatches, or incorrect hardware behavior.
8 min read | Beginner to Intermediate
What You'll Learn
- Master the three assignment forms: procedural, continuous, and procedural continuous
- Understand what can legally appear on the left-hand side (LHS) of each assignment type
- Apply blocking (
=) and non-blocking (<=) assignments correctly in procedural blocks - Recognize when to use
assign/deassignandforce/releasefor advanced testbench control
Three Basic Assignment Forms
Verilog provides three fundamental assignment mechanisms:
- Procedural - Used in
always,initial, tasks, and functions - Continuous - Used with
assignkeyword for combinational logic - Procedural Continuous - Used with
assign/deassignandforce/releasefor overriding assignments
Assignment Structure
An assignment has two parts - right-hand side (RHS) and left-hand side (LHS) with an equal symbol (=) or a less than-equal symbol (<=) in between:
LHS = RHS; // Blocking assignment
LHS <= RHS; // Non-blocking assignment
assign LHS = RHS; // Continuous assignment
The RHS can contain any expression that evaluates to a final value, while the LHS indicates a net or a variable to which the value in RHS is being assigned.
Legal LHS Values
What can appear on the left-hand side depends on the assignment type:
| Assignment Type | Left-Hand Side (LHS) Can Be | Example |
|---|---|---|
| Procedural |
| data = 8'hAB;reg_file[3] = 0;{carry, sum} = a + b; |
| Continuous |
| assign out = in1 & in2;assign bus[7:4] = data;assign {cout, sum} = a + b; |
| Procedural Continuous |
| assign q = d;force data = 8'h00; |
Assignment Example with LHS and RHS
module tb;
reg clk;
wire a, b, c, d, e, f;
reg z, y;
// clk is on the LHS and the not of clk forms RHS
always #10 clk = ~clk;
// y is the LHS and the constant 1 is RHS (ERROR: can't assign to wire in continuous assignment)
// This should be: assign y = 1; (but y should be wire, not reg)
// f is the LHS, and the expression of a,b,d,e forms the RHS
assign f = (a | b) ^ (d & e);
always @ (posedge clk) begin
// z is the LHS, and the expression of a,b,c,d forms the RHS
z <= a + b + c + d;
end
initial begin
// Variable names on the left form LHS while 0 is RHS
a <= 0; b <= 0; c <= 0; d <= 0; e <= 0; // ERROR: can't assign to wires in initial block
clk <= 0;
end
endmodule
Common Error: The example above contains errors--wires (a, b, etc.) cannot be assigned from procedural blocks like initial. They must be driven by continuous assignments or module outputs.
Procedural Assignment
Procedural assignments occur within procedures such as always, initial, task and functions and are used to place values onto variables (not nets). The variable will hold the value until the next assignment to the same variable.
The value will be placed onto the variable when the simulation executes this statement at some point during simulation time. This can be controlled and modified by the use of control flow statements such as if-else-if, case statement and looping mechanisms.
Syntax
// Blocking procedural assignment (immediate update)
variable = expression;
// Non-blocking procedural assignment (scheduled for end of time-step)
variable <= expression;
Example
reg [7:0] data; // 8-bit register variable
integer count; // Integer variable
real period; // Real number variable
initial begin
data = 8'h3e; // Blocking assignment to reg
period = 4.23; // Blocking assignment to real
count = 0; // Blocking assignment to integer
end
always @ (posedge clk)
count <= count + 1; // Non-blocking assignment (for sequential logic)
Variable Declaration Assignment
An initial value can be placed onto a variable at the time of its declaration. The assignment does not have a duration and holds the value until the next assignment to the same variable happens. Note that variable declaration assignments to an array are not allowed.
module my_block;
reg [31:0] data = 32'hdead_cafe; // Initialize at declaration
initial begin
#20 data = 32'h1234_5678; // data will have dead_cafe from time 0 to time 20
// At time 20, data will get 1234_5678
end
endmodule
Declaration assignment is equivalent to an initial block assignment:
reg [3:0] a = 4'b0100; // Declaration assignment
// is equivalent to
reg [3:0] a;
initial a = 4'b0100; // Initial block assignment
Race Condition Warning: If the variable is initialized during declaration and at time 0 in an initial block, the order of evaluation is not guaranteed, and the variable can have either value.
module my_block;
reg [7:0] addr = 8'h05; // Initialize to 0x05
initial
addr = 8'hee; // Also assigns at time 0 - RACE CONDITION!
// Final value could be 0x05 OR 0xEE (undefined)
endmodule
Declaration Assignment Rules
reg [3:0] array [3:0] = 0; // ILLEGAL - cannot initialize arrays
integer i = 0, j; // Declares two integers i, j; only i is assigned 0
real r2 = 4.5, r3 = 8.0; // Declares r2=4.5, r3=8.0
time startTime = 40; // Declares time variable with initial value 40
Continuous Assignment
This is used to assign values onto scalar and vector nets (not variables) and happens whenever there is a change in the RHS. It provides a way to model combinational logic without specifying an interconnection of gates and makes it easier to drive the net with logical expressions.
Syntax
// Continuous assignment to a net
assign net_name = expression;
// Net declaration assignment (combined)
wire net_name = expression;
Example
// Example model of an AND gate
wire a, b, c;
assign a = b & c; // Continuous assignment: a updates whenever b or c changes
Whenever b or c changes its value, then the whole expression in RHS will be evaluated and a will be updated with the new value immediately (in zero simulation time).
Net Declaration Assignment
This allows us to place a continuous assignment on the same statement that declares the net. Note that because a net can be declared only once, only one declaration assignment is possible for a net.
wire penable = 1; // Declare wire and continuously assign it to 1
// is equivalent to
wire penable;
assign penable = 1;
Continuous Assignment Use Case: Use continuous assignments to model combinational logic, data path connections, and simple logic gates. The assignment is active throughout simulation--any change to the RHS immediately propagates to the LHS.
Real-World Application: Combinational vs. Sequential Logic
In RTL design for ASICs and FPGAs, continuous assignments model combinational logic (AND, OR, XOR gates, multiplexers) where the output responds immediately to input changes. Procedural assignments in always @(posedge clk) blocks model sequential logic (flip-flops, registers) where outputs change only on clock edges. Using the wrong assignment type--continuous assignment for a register or procedural for combinational logic--will cause synthesis errors or incorrect hardware.
Procedural Continuous Assignment
These are procedural statements that allow expressions to be continuously assigned to nets or variables, overriding all other assignments. Note that the "nets or variables" flexibility applies to force/release only -- assign/deassign, despite the similar name, can only target variables (regs), never nets. They are primarily used in testbenches for debugging and forcing specific signal values. There are two types:
assign...deassignforce...release
assign / deassign
This will override all procedural assignments to a variable and is deactivated by using the same signal with deassign. The value of the variable will remain the same until the variable gets a new value through a procedural or procedural continuous assignment.
Restrictions: The LHS of an assign statement cannot be a bit-select, part-select or an array reference but can be a variable or a concatenation of variables.
Syntax
assign variable = expression; // Override all procedural assignments to variable
deassign variable; // Stop overriding (variable retains last assigned value)
Example
reg q;
initial begin
assign q = 0; // Force q to 0 (overrides any other assignment)
#10 deassign q; // After 10 time units, stop forcing
// q retains value 0 until next assignment
end
force / release
These are similar to the assign - deassign statements but can also be applied to nets (not just variables). The LHS can be a bit-select of a net, part-select of a net, variable or a net but cannot be the reference to an array and bit/part select of a variable.
The force statement will override all other assignments made to the variable or net until it is released using the release keyword.
Syntax
force net_or_variable = expression; // Override all assignments (works on nets and variables)
release net_or_variable; // Stop overriding
Example
reg o, a, b;
initial begin
force o = a & b; // Force o to be a&b (overrides any assignment to o)
#50; // Hold for 50 time units
release o; // Release the force (o resumes normal behavior)
end
Real-World Application: Testbench Debugging with force/release
In verification environments, force/release is invaluable for debugging. When a bug occurs deep in a design hierarchy, you can force internal signals to specific values to isolate the issue without modifying the RTL. For example, if a cache controller is misbehaving, you can force the cache hit signal to 1 to test downstream logic independently. After debugging, release restores normal operation. This technique saves hours of debug time in complex SoC verification.
Assignment Types Comparison
| Aspect | Procedural | Continuous | Procedural Continuous |
|---|---|---|---|
| Where Used | always, initial, task, function | Outside procedural blocks | Inside procedural blocks (testbenches) |
| LHS Type | Variables (reg, integer, etc.) | Nets (wire) | Variables or nets |
| Syntax | var = expr; or var <= expr; | assign net = expr; | assign/force + deassign/release |
| When Evaluated | When procedural block executes | Continuously (any RHS change) | When procedural block executes |
| Duration | Until next assignment to same variable | Entire simulation (always active) | Until deassign or release |
| Synthesis | Synthesizable (RTL design) | Synthesizable (RTL design) | Not synthesizable (testbench only) |
| Primary Use | Sequential/combinational logic in RTL | Combinational logic, wire connections | Debugging, forcing values in testbenches |
Common Beginner Mistakes
Mistake #1: Using continuous assignment on variables or procedural assignment on nets
// WRONG - mixing assignment types with wrong target types
reg data;
wire addr;
assign data = 8'hAB; // ERROR: Can't use assign on reg
always @(*) addr = input; // ERROR: Can't use procedural assignment on wire
Why it's problematic: Continuous assignments (assign) can only target nets (wire), while procedural assignments (in always/initial) can only target variables (reg, integer, etc.). Mixing these causes compilation errors because the simulator doesn't know how to handle a reg that's continuously driven or a wire updated procedurally.
Best Practice: Use assign for wire, procedural assignments for reg:
// CORRECT - proper assignment types
wire data;
reg addr;
assign data = 8'hAB; // OK: assign to wire
always @(*) addr = input; // OK: procedural assignment to reg
Mistake #2: Initializing variables in declarations without considering race conditions
// WRONG - race condition between declaration and initial block
module race_example;
reg [7:0] counter = 8'h00; // Initialized at time 0
initial begin
counter = 8'hFF; // Also executes at time 0 - RACE!
end
endmodule
Why it's problematic: Both the declaration assignment and the initial block execute at time 0, but the Verilog standard doesn't specify which executes first. Different simulators or even different runs may produce different results (counter could be 0x00 or 0xFF). This non-determinism makes debugging impossible.
Best Practice: Use only one initialization method--either declaration assignment or initial block, not both:
// CORRECT - single initialization source
module no_race;
reg [7:0] counter; // No declaration initialization
initial begin
counter = 8'hFF; // Only initialization, no race
end
endmodule
Mistake #3: Using assign/deassign or force/release in synthesizable RTL
// WRONG - using procedural continuous in RTL
module bad_design (input clk, output reg q);
always @(posedge clk) begin
if (error_condition)
assign q = 1'b0; // ERROR: assign/deassign not synthesizable!
else
deassign q;
end
endmodule
Why it's problematic: Procedural continuous assignments (assign/deassign, force/release) are simulation-only constructs with no hardware equivalent. Synthesis tools will either error out or ignore them, causing simulation/synthesis mismatch. They should only be used in testbenches for debugging.
Best Practice: Use regular procedural assignments in RTL:
// CORRECT - synthesizable RTL
module good_design (input clk, output reg q);
always @(posedge clk) begin
if (error_condition)
q <= 1'b0; // OK: normal procedural assignment
else
q <= normal_value;
end
endmodule
Mistake #4: Not understanding that continuous assignments are always active
// WRONG - misunderstanding continuous assignment behavior
wire enable;
wire output_signal;
assign enable = 1'b1; // Always drives enable to 1
assign output_signal = data & enable; // Always evaluates
// Later, trying to disable:
always @(posedge clk) begin
if (disable_condition)
enable = 1'b0; // ERROR: Can't assign to wire from procedural block!
end
Why it's problematic: Once a wire has a continuous assignment, it cannot be overridden from a procedural block. The assign statement is always active throughout simulation, continuously driving the net. You cannot "turn off" a continuous assignment conditionally.
Best Practice: If you need conditional control, include the condition in the continuous assignment or use a reg with procedural assignment:
// CORRECT - condition in continuous assignment
wire enable;
wire output_signal;
assign enable = !disable_condition; // Condition in continuous assignment
assign output_signal = data & enable;
// OR use reg with procedural assignment
reg enable;
always @(*) begin
if (disable_condition)
enable = 1'b0;
else
enable = 1'b1;
end
Summary
In this tutorial, you learned:
- Three assignment types serve different purposes: procedural (for variables in
always/initial), continuous (for nets withassign), and procedural continuous (for testbench debugging withforce/release) - LHS restrictions vary by type: procedural targets variables, continuous targets nets, and procedural continuous can target both but is simulation-only
- Blocking vs. non-blocking applies only to procedural assignments: use blocking (
=) for combinational logic and non-blocking (<=) for sequential (clocked) logic - Declaration assignments initialize variables/nets at declaration time but can create race conditions if combined with
initialblock assignments at time 0 - Procedural continuous assignments (
assign/deassign,force/release) are powerful testbench debugging tools but are not synthesizable and should never appear in RTL design code