Panel For Example Panel For Example Panel For Example
SystemVerilog: Clocking Blocks & Process Communication

SystemVerilog: Clocking Blocks & Process Communication

Author : Adrian September 03, 2025

1. Clocking Blocks

In SystemVerilog, a clocking block is declared and instantiated in a single step; a separate instantiation is not necessary. Clocking blocks cannot be nested and can only be declared within a module, interface, checker, or program. They have three primary uses:

  • Synchronous events
  • Input sampling
  • Synchronous drives

Signals specified with the input direction in a clocking block can only be read, not written to. Signals with the output direction can only be written to, not read from. Signals with the inout direction can be both read from and written to, as this direction essentially defines two signals—an input and an output—with the same name.

 

2. Clocking Skew

Clocking skew determines how far in time from a clock event a signal is sampled or driven. Input skew is implicitly negative, meaning it always occurs before the clock event, while output skew always occurs after the clock event. Different signals within the same clocking block can have different input/output skews, or a default skew can be applied to all signals in the block.

Unless specified otherwise, the default input skew is 1step and the default output skew is 0. For an input with a 1step skew, the value is sampled in the Postponed region of the previous time step.

If an input specifies a #0 skew, it should theoretically be sampled at the exact moment of the clock event. However, to avoid race conditions, SystemVerilog samples it in the Observed region. Similarly, an output with a #0 skew or no specified skew is driven in the Re-NBA region.

It is important to note that if a clock event is triggered by a statement within a program block, a race condition can occur between the clock event and the sampled value. This race condition is avoided if the clock is updated within a module.

 

3. Synchronous Drive in Clocking Blocks

Assigning a value to a signal within a clocking block must be done using the synchronous drive operator (<=). Using other assignment methods will result in an error. An assignment to a clocking block signal may be executed at a time other than a clock event. Such drive statements do not block execution; however, the driven value only takes effect at the next clock event. This means the right-hand side expression is evaluated immediately when the statement is executed, but the actual driving of the signal is delayed until the next clock event. For example:

default clocking cb @(posedge clk); // Assume clk has a period of #10 units output v; endclocking initial begin #3 cb.v <= expr1; // Matures in cycle 1; equivalent to ##1 cb.v <= expr1 end

A synchronous drive to an inout signal does not change the input value of that signal within the clocking block. This is because the input value has already been sampled and is not re-evaluated at the time of the drive. For example:

clocking cb @(posedge clk); inout a; output b; endclocking initial begin cb.a <= c; // The value of a will change in the Re-NBA region cb.b <= cb.a; // b is assigned the value of a before the change end

In the example above, signal a is driven in the Re-NBA region. The value of a on the right-hand side of the assignment to b was sampled in the previous time step and has not yet been updated in the current time step. Therefore, b is assigned the old value of a. The exact sampling time depends on the input skew: with #0 skew, it occurs in the Observed region; with a non-zero skew like #1step, it is sampled earlier, in the Postponed region of the previous time step.

 

4. Inter-Process Synchronization and Communication

SystemVerilog provides three mechanisms for inter-process synchronization and communication:

  • Named events (->,@)
  • Semaphores
  • Mailboxes

Semaphores and mailboxes are built-in class types in SystemVerilog and can be extended as base classes for higher-level subclasses. These built-in classes are part of the std package and can be used in any scope.

A semaphore acts like a lock. Only a process that has acquired the key can continue execution. It is commonly used for mutual exclusion (mutexes), controlling access to shared resources, and basic synchronization.

A mailbox is a communication mechanism for exchanging messages between processes. Mailboxes can be bounded or unbounded. For a bounded mailbox, the put() method will block if the mailbox is full. An unbounded mailbox never becomes full, so its put() method will never block.

Mailboxes can be generic or parameterized. A generic mailbox can hold data of any type, which requires a type check at runtime; an error will occur if the types used in put() and get() calls do not match. A parameterized mailbox can only hold data of a specific type, allowing the simulator to perform type checking at compile time.

Named events provide a handle to a synchronization object. When a process waits for an event to be triggered, it is placed in a queue maintained by the event object. A process can wait for a named event using the @ operator or the wait() method, which checks the triggered status. Additionally, wait_order can be used to detect the sequence in which several events occur. An event handle can be cleared by assigning null to it:

event E1 = null; @ E1; // undefined: might block forever or not at all wait( E1.triggered ); // undefined -> E1; // no effect

Event variable handles can be compared using logical equality (==,!=) and case equality (===,!==). The comparison returns 1 if they are the same and 0 otherwise. For example:

event E1, E2; if ( E1 ) E1 = E2; // same as if ( E1 != null ) if ( E1 == E2 ) $display("E1 and E2 are the same event");