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/deassign and force/release for advanced testbench control

Three Basic Assignment Forms

Verilog provides three fundamental assignment mechanisms:

  • Procedural - Used in always, initial, tasks, and functions
  • Continuous - Used with assign keyword for combinational logic
  • Procedural Continuous - Used with assign/deassign and force/release for 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 TypeLeft-Hand Side (LHS) Can BeExample
Procedural
  • Variables (vector/scalar reg, integer, time)
  • Bit-select or part-select of a vector variable
  • Memory word
  • Concatenation of any of the above
data = 8'hAB;
reg_file[3] = 0;
{carry, sum} = a + b;
Continuous
  • Net (vector/scalar wire)
  • Bit-select or part-select of a vector net
  • Concatenation of bit-selects and part-selects
assign out = in1 & in2;
assign bus[7:4] = data;
assign {cout, sum} = a + b;
Procedural Continuous
  • Net or variable (vector/scalar)
  • Bit-select or part-select of a vector net
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 ... deassign
  • force ... 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

AspectProceduralContinuousProcedural Continuous
Where Usedalways, initial, task, functionOutside procedural blocksInside procedural blocks (testbenches)
LHS TypeVariables (reg, integer, etc.)Nets (wire)Variables or nets
Syntaxvar = expr; or var <= expr;assign net = expr;assign/force + deassign/release
When EvaluatedWhen procedural block executesContinuously (any RHS change)When procedural block executes
DurationUntil next assignment to same variableEntire simulation (always active)Until deassign or release
SynthesisSynthesizable (RTL design)Synthesizable (RTL design)Not synthesizable (testbench only)
Primary UseSequential/combinational logic in RTLCombinational logic, wire connectionsDebugging, 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 with assign), and procedural continuous (for testbench debugging with force/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 initial block 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
Did you find what you were looking for?

Quiz

Test Your Knowledge

1 of 5 Score: 0 / 5