1. Simulation Time Control
In SystemVerilog, simulation time can be advanced using one of three methods:
- Delay control:
#
- Event control:
@
- The
wait
statement, which combines aspects of event control and a while loop.
Delay control can be applied in two ways: before the statement or within the statement (intra-assignment delay).
// Delay occurs before the assignment statement is executed
#10 rega = regb; // Wait 10 time units, then execute rega = regb;
#((d+e)/2) rega = regb; // Wait (d+e)/2 time units, then execute the assignment.
// Intra-assignment delay
a = #5 b; // Equivalent to: temp = b; #5; a = temp;
a = @(posedge clk) b; // Equivalent to: temp = b; @(posedge clk); a = temp;
a = repeat(3) @(posedge clk) b; // Equivalent to: temp = b; @(posedge clk); @(posedge clk); @(posedge clk); a = temp;
When the delay operator #
precedes the statement, the simulator waits for the specified delay, then evaluates and executes the entire statement. For intra-assignment delays, the right-hand side expression is evaluated immediately, and the assignment to the left-hand variable is postponed by the specified delay or event.
2. Controlling `fork` Threads
After initiating parallel threads withfork...join
,fork...join_any
, or fork...join_none
, you can control them using wait fork
, disable
, or disable fork
.
wait fork: This statement blocks the parent process until all of its immediate child processes have completed. It does not wait for grandchildren or other descendant processes.
disable: This statement terminates all activity within a named block or task, regardless of the parent-child process hierarchy. A child process can use disable
to terminate its parent, and any process can be terminated by an unrelated process.
disable fork: This statement also terminates processes, but it respects the parent-child hierarchy. It stops all active child processes spawned by the current process, including all of their descendants.
3. Fine-Grained Process Control
SystemVerilog includes a built-in process
class that allows other processes to access and control a process after it has started. The definition of this class is as follows:
class process;
typedef enum { FINISHED, RUNNING, WAITING, SUSPENDED, KILLED } state;
static function process self();
function state status();
function void kill();
task await();
function void suspend();
function void resume();
function void srandom( int seed );
function string get_randstate();
function void set_randstate( string state );
endclass
When a process is spawned, an object of type process
is created internally. Users cannot manually create a process
object with the new()
constructor, and attempting to do so will result in a compiler error. The process
class cannot be extended through inheritance.
To manipulate a process, you can declare a variable of type process
and assign it a handle to an existing process. The static method self()
returns the handle of the current process.
process job;
fork
begin: new_blk
job = process::self();
// ...
end
join_none
With the handle job
, you can now control the process running in the
new_blk
block.
The built-in methods of the process
class include:
-
status(): Returns the current state of the process object. The possible states are:
- FINISHED: The process terminated normally.
- RUNNING: The process is currently executing and not blocked.
- WAITING: The process is blocked, waiting for a condition to be met.
- SUSPENDED: The process has been suspended and is waiting to be resumed.
- KILLED: The process was forcibly terminated by
kill()
ordisable
.
- kill(): Terminates the specified process and all its descendant processes created with
fork
. - await(): A task that allows one process to wait for another process to complete. A process cannot call its own
await()
method. - suspend(): Allows a process to suspend its own execution or that of another process.
- resume(): Resumes a previously suspended process.
4. Equality Operators
SystemVerilog provides three types of equality operators:
- Logical equality (
==
,!=
): If either operand contains an X or Z bit, the result of the comparison is X (unknown). The result is 1 (True) or 0 (False) only if both operands are free of X and Z bits. - Case equality (
===
,!==
): This operator performs a bit-for-bit comparison that includes X and Z values. The result is always 1 (True) or 0 (False). - Wildcard equality (
==?
,!=?
): For this operator, the right-hand side is treated as a wildcard pattern. Any X or Z bits in the right-hand operand act as wildcards, matching any value (0, 1, X, or Z) in the corresponding bit of the left-hand operand. However, X or Z bits in the left-hand operand are treated as literal values. The result can be 0, 1, or X. If an X or Z on the left is not matched by a wildcard on the right, the result is X.
5. Short-Circuit Evaluation
The logical operators &&
, ||
, `->`, and the conditional operator ?:
support short-circuit evaluation.
The first operand is always evaluated. For &&
, if the first operand is false, subsequent operands are not evaluated. For ||
, if the first operand is true, subsequent operands are not evaluated.
For the conditional operator (cond_predicate ? expr1 : expr2
), if cond_predicate
is true, only expr1
is evaluated. If it is false, only expr2
is evaluated. If cond_predicate
evaluates to X or Z, the result is determined by merging the results of both expressions, as shown in this example:
wire [15:0] busa = drive_busa ? data : 16'bz;
If drive_busa
is 1, busa
is assigned the value of data
. If drive_busa
is an unknown state (X or Z), the result for `busa` will also be unknown. Because expr2
is high-impedance (Z), the output for any bit will resolve to X when `drive_busa` is unknown, regardless of the corresponding bit value in `data`.
6. Set Membership Operator (`inside`)
The inside
operator checks if an expression is a member of a set of values or ranges. Its syntax is:
inside_expression ::= expression inside { open_range_list }
The expression to the left of inside
must be a singular expression. The set on the right is a comma-separated list of expressions or ranges. If the left-hand expression is an unpacked array, it is automatically expanded to its singular elements.
For non-integral expressions, inside
uses logical equality (==
) for comparison. For integral expressions, it uses wildcard equality (==?
). This means that X or Z bits in the range list expressions are treated as wildcards, but X or Z bits in the left-hand expression are treated as literal values.
If no exact match is found, but some comparisons result in X, the entire operation returns 1'bx
.
Ranges are specified as [low_bound : high_bound]
. The $
symbol can be used to represent the minimum or maximum value of the left-hand expression's type. If the left value in a range is greater than the right value, the range is considered empty.
Examples:
// Array expansion
int array[$] = '{3,4,5};
if ( ex inside {1, 2, array} ) ... // Same as { 1, 2, 3, 4, 5 }
// Wildcard comparison and unknown result
assign r = 3'bz11 inside {3'b1?1, 3'b011}; // r = 1'bx