A Verilog assignment is the relation that determines how a signal gets its value, either continuously or within a procedural block.
If in the previous article we covered data types (wire and reg), today we will see how they are assigned a value. In classical programming (C, Python), there is only one way to assign: variable = value.
In Verilog, since we are describing hardware, things get more complicated. We have two distinct philosophies:
- Permanently connecting wires (
assign). - Defining behaviors in response to events (
always).
And as if that were not enough, within behavioral blocks, we have two types of assignment: Blocking (=) and Non-blocking (<=).
Understanding this is the barrier that separates someone who “copies code” from someone who “designs hardware.” Let’s break down that barrier.
Practical assignment table
| Goal | Tool | Data Type | Operator |
|---|---|---|---|
| Simple connection | assign | wire | = |
| Complex Combinational Logic | always @(*) | reg | = |
| Sequential Logic (Memory) | always @(posedge clk) | reg | <= |
Continuous assignment (assign)
The assign statement is used to describe pure combinational logic.
Imagine you have a soldering iron and are physically connecting wires. Once you solder a wire between an AND gate and an output, that connection is permanent. Whenever the inputs change, the output will change instantly (well, after a tiny physical delay).
- It only works with
wiretypes. - It is written outside
alwaysblocks. - It is ideal for simple Boolean equations.
module gate_logic (
input wire a,
input wire b,
output wire y_and,
output wire y_mux
);
// Continuous assignment: This happens ALWAYS, in parallel
assign y_and = a & b;
// We can use the ternary operator to make multiplexers
assign y_mux = (a == 1'b1) ? b : 1'b0;
endmoduleThink of assign like an Excel formula. If you change cell A1, the result in C1 changes immediately. There are no “steps”; it’s a permanent relationship.
Procedural blocks (always)
always blocks allow us to describe the circuit in a way more similar to an algorithm (“If this happens, do that”). But be careful, it is still hardware.
An always block executes when an event occurs in its sensitivity list.
We have two main flavors of always, and it’s best not to mix them:
The combinational always block (always @*)
It is used to describe complex combinational logic (like a large multiplexer or a state machine) using convenient statements like if-else or case.
- Sensitivity list: We use
*(oralways_combin SystemVerilog) to tell the synthesizer: “Execute this if ANY of the signals I read inside changes”. - It generates logic gates, it does NOT generate memory.
- We must use Blocking Assignment (
=).
// Example: A simple decoder
reg output_signal; // Must be 'reg' because it is assigned inside an always block
always @(*) begin
if (input_signal == 1'b1) begin
output_signal = 1'b0;
end else begin
output_signal = 1'b1;
end
endThe sequential always block (always @(posedge clk))
This is the creator of Flip-Flops. It is used for circuits that have memory and advance at the pace of the clock.
- Sensitivity list: It only reacts to the clock edge.
- We must use Non-blocking Assignment (
<=).
Blocking (=) and non-blocking (<=) assignments
This difference causes many of the hard-to-find errors when starting with Verilog.
Inside an always block, we can assign values in two ways. The difference lies in when the value is updated.
Blocking assignment (=)
It works like in C++. The line executes, the value is updated immediately, and the next line already sees the new value.
- Usage: Only in combinational logic (
always @*).
Non-blocking assignment (<=)
It means “Calculate the value now, but don’t store it yet”. All calculations within the block are done in parallel, and at the end of the clock cycle, all variables are updated at once.
- Usage: Only in sequential logic (
always @(posedge clk)).
Practical example: swapping values
Let’s try to swap the values of two registers, A and B.
Attempt with Blocking (=) - ERROR
always @(posedge clk) begin
a = b; // 'a' takes the value of 'b' immediately
b = a; // 'b' takes the value of 'a'... but 'a' is already equal to 'b'!
end
// Result: Both end up with the value that 'b' had. We lost the 'a' data.Correct Way with Non-blocking (<=) - SUCCESS
always @(posedge clk) begin
a <= b; // "I schedule" that 'a' will be 'b'
b <= a; // "I schedule" that 'b' will be what 'a' is RIGHT NOW (the old value)
end
// At the end of the cycle, both are updated in a cross-coupled way. Perfect!= is like passing a note in class. I give it to you, you read it, and pass it to the next person. It is sequential.
<= is like the teacher telling everyone to copy what’s on the board into their notebooks. Everyone looks at the previous value and writes at the same time.
Unintentional generation of latches
There is a danger when using always for combinational logic. If we use if but forget the else, or use a case that doesn’t cover all options, we are saying: “If the condition is not met, keep the previous value”.
“Keeping the value” implies memory. But since we are not using a clock, the tool will try to create an asynchronous memory element called a Latch.
A latch is not incorrect by definition, but here it almost always appears unintentionally and complicates timing analysis.
// DANGEROUS CODE: Generates Latch
always @(*) begin
if (enable == 1'b1)
output_signal = 1'b1;
// The 'else' is missing! What happens if enable is 0?
// The FPGA will create a Latch to remember the state.
endSolution: Always put a default value at the beginning of the block or make sure to cover all else cases.
// SAFE CODE
always @(*) begin
output_signal = 1'b0; // Default value
if (enable == 1'b1)
output_signal = 1'b1;
end