Section
15
Clocking
Domains Blocks
SystemVerilog adds the clocking
construct block that identifies
clock signals, and captures the timing and synchronization requirements of the
blocks being modeled. A clocking domain block assembles signals that are synchronous to a
particular clock, and makes their timing explicit. The clocking domain block is a
key element in a cycle-based methodology, which enables users to write
testbenches at a higher level of abstraction. Rather than focusing on signals
and transitions in time, the test can be defined in terms of cycles and
transactions. Depending on the environment, a testbench can contain one or more
clocking domains
blocks, each containing its own clock plus an arbitrary number of
signals.
The clocking domain block
separates the timing and synchronization details from the structural,
functional, and procedural elements of a testbench. Thus, the timing for
sampling and driving clocking domain block signals is implicit and relative to the
clocking domain
block’s clock. This enables a set of key operations to be written very
succinctly, without explicitly using clocks or specifying timing. These
operations are:
15.2
Clocking domain block
declaration
The
syntax for the clocking construct block is:
The clocking_identifier specifies the name of the
clocking domain block
being declared.
The signal_identfier identifies a signal in the
scope enclosing the clocking domain block declaration, and declares
the name of a signal in the clocking domain block. Unless a hierarchical_expression is used, both
the signal and the clocking_item names shall be the same.
The clocking_event designates a particular event
to act as the clock for the clocking domain block. Typically, this expression is either the
posedge or negedge of a
clocking signal. The timing of all the other signals specified in a given
clocking domain block
are is governed by the clocking event. All input or inout signals specified in the clocking domain block are sampled when the corresponding clock event occurs.
Likewise, all output or inout signals in the clocking domain block are driven when the corresponding clock event occurs.
Bidirectional signals (inout)
are sampled as well as driven.
The clocking_skew determines how many time units
away from the clock event a signal is to be sampled or driven. Input skews are
implicitly negative, that is, they always refer to a time before the clock,
whereas output skews always refer to a time after the clock (see Section 15.3).
When the clocking event specifies a simple edge, instead of a number, the skew
can be specified as the opposite specific edge of
the signal. A single skew can be specified for the entire domain block by using a default clocking
item.
clocking
ck1 (posedge clk);
default input #1step output negedge; //
legal
// outputs driven on the negedge clk
input ... ;
output
... ;
endclocking
clocking
ck2 (clk); // no edge specified!
default input #1step output negedge; //
legal
input ... ;
output
... ;
endclocking
The hierarchical_identifier specifies that,
instead of a local port, the signal to be associated with the clocking domain block is specified by its hierarchical name (cross-module
reference).
In the above example, the
first line declares a clocking domain block called bus that is to be clocked on the positive edge of the
signal clock1. The second line
specifies that by default all signals in the domain shall use a 10ns input skew and a 2ns output skew. The next line adds three input signals to
the domain: data, ready, and enable;
the last signal refers to the hierarchical signal top.mem1.enable. The fourth line adds the signal ack to the
domain, and overrides the default output skew so that ack is driven
on the negative edge of the clock. The last line adds the signal addr and
overrides the default input skew so that addr is sampled one step before the positive edge of the
clock.
Unless otherwise specified,
the default input skew
is 1step and the default output
skew is 0 (in the NBA region). A step is a special time unit whose value is defined in Section 18.9. A 1step
input skew allows input signals to
sample their steady-state values in the time step immediately before the clock
event (i.e., in the preceding Postponed region). Unlike other time units, which
represent physical units, a step cannot be used to set or modify either the
precision or the timeunit.
An input skew
of #0 forces
a skew of zero. Inputs with zero explicit #0
skew are sampled at the same time as their corresponding clocking event, but to
avoid races, they are sampled in the Observed region. Likewise, clocking block outputs with zero no skew (or explicit #0 skew) are
driven at the same time as their specified clocking event, as nonblocking
assignments (in the NBA region).
Skews are declarative
constructs, thus, they are semantically very different from the syntactically
similar procedural delay statement. In particular, an
explicit #0 skew, does not suspend any process
nor does it execute or sample values in the Inactive region.
Any signal in a clocking domain block can
be associated with an arbitrary hierarchical expression. As described in
Section 15.2, a hierarchical expression is introduced by appending an equal
sign (=) followed by the
hierarchical expression:
15.5
Signals in multiple clocking domains blocks
The same signals—clock,
inputs, inouts, or outputs—can appear in more than
one domain block.
Clocking domains
blocks that use the same clock (or clocking expression) shall share the
same synchronization event, in the same manner as several latches can be
controlled by the same clock. Input semantics are described in Section 15.12,
and output semantics are described in Section 15.14.
A clocking
construct block
is both a declaration and an instance of that declaration. A separate
instantiation step is not necessary. Instead, one copy is created for each
instance of the block containing the declaration (like an always block). Once
declared, the clocking signals are available via the clocking-domain block name and the dot (.) operator:
dom.sig //
signal sig in clocking dom
Clocking domains blocks
cannot be nested. They cannot be declared inside functions, tasks, packages, or outside
all declarations in a compilation unit. Clocking domains blocks can only be declared inside a module, interface or program
(see Section 16).
Clocking domains blocks have
static lifetime and scope local to their enclosing module, interface or
program.
15.7
Multiple clocking domains blocks example
In this example, a simple
test program includes two clocking domains blocks. The program construct used in this example
is discussed in Section 16.
15.8
Interfaces and clocking domains blocks
A clocking
encapsulates a set of signals that
share a common clock, therefore, specifying a clocking
domain block
using a SystemVerilog interface can
significantly reduce the amount of code needed to connect the testbench.
Furthermore, since the signal directions in the clocking domain block
within the testbench are with respect to the testbench, and not the design
under test, a modport declaration can appropriately describe either
direction. A testbench program can be contained within a program and its
ports can be interfaces that correspond to the signals declared in each clocking
domain block.
The interface’s wires shall have the same direction as specified in the
clocking domain
block when viewed from the testbench side (i.e., modport test), and reversed when viewed from the device under test
(i.e., modport dut).
Alternatively, in the
program test above, the clocking domain block can be written using both interfaces and
hierarchical expressions as:
15.9
Clocking domain block
events
The clocking event of a
clocking domain
block is available directly by using the clocking domain block name,
regardless of the actual clocking event used to declare the clocking domain block.
cycle_delay_range ::= //
from Annex A.2.10
## constant_expression
| ## [ cycle_delay_const_range_expression ]
cycle_delay_const_range_expression ::=
constant_expression : constant_expression
| constant_expression : $
procedural_timing_control_statement ::= // from Annex A.6.5
procedural_timing_control statement_or_null
procedural_timing_control ::=
…
| cycle_delay
cycle_delay ::= ## expression // from Annex A.6.11
The constant_expression expression can be any
SystemVerilog expression that evaluates to a positive integer value.
The clocking_identifier
must be the name of a clocking domain block.
All clocking domain block
inputs (input or inout) are sampled at the
corresponding clocking event. If the input skew is non-zero not an explicit #0, then the value sampled corresponds to the signal value at the
Postponed region of the time step skew time-units prior to the clocking event
(see Figure 15-1 in Section 15.3). If the input skew is zero an explicit #0, then the value sampled corresponds to
the signal value in the Observed region.
Samples happen
immediately (the calling process does not block). When a signal appears in an
expression, it is replaced by the signal’s sampled value, that is, the value
that was sampled at the last sampling point.
When the same
signal is an input to multiple clocking domain blocks, the semantics are straightforward; each
clocking domain block samples the corresponding signal with its own clocking event.
The expression used with
the event control can denote clocking-domain
block input (input or inout), or a slice thereof. Slices can include dynamic
indices, which are evaluated once, when the @ expression executes.
Clocking domain block outputs (output or inout) are used to drive values onto their corresponding
signals, but at a specified time. That is, the corresponding signal changes
value at the indicated clocking event as modified by the output skew.
The clockvar_expression
is either a bit-select, slice, or the entire
clocking domain
block output whose corresponding signal
is to be driven (concatenation is not allowed):
The event_count
is an integral expression that optionally specifies the number of clocking
events (i.e. cycles) that must pass before the statement executes. Specifying a
non-zero event_count blocks the current process until the specified number
of clocking events have elapsed, otherwise the statement executes at the
current time. The event_count uses syntax similar to the cycle-delay operator (see
Section 15.10), however, the synchronous drive uses
the clocking domain block of the signal
being driven and not the default clocking.
bus.data[3:0] <= 4’h5; // drive data in the NBA region of the current cycle
A key feature of inout clocking
domain block variables and synchronous drives is that a drive does
not change the clock domain input. This is because reading the input always
yields the last sampled value, and not the driven value.
When more than one
synchronous drive is applied to the same clocking domain block output (or inout) at
the same simulation time, the driven values are checked for conflicts. When
conflicting drives are detected a runtime error is issued, and each conflicting
bit is driven to X (or 0 for a 2-state port).
When the same variable is
an output from multiple clocking domain blocks, the last drive determines the value of the
variable. This allows a single module to model multi-rate devices, such as a
DDR memory, using a different clocking domain block to model each active edge. For example:
The variable j is an output to two clocking domain blocks
using different clocking events (posedge vs. negedge). When driven, the variable j shall take on the value most recently assigned by
either clocking domain block.
Clocking- domain block outputs driving a net (i.e. through different
ports) cause the net to be driven to its resolved signal value. When a Clocking- domain block output
corresponds to a wire, a driver for that wire is created that is updated as if
by a continuous assignment from a register inside the Clocking- domain block that is updated as a nonblocking assignment.