13.4
How Logic Simulation Works

The most common type of digital simulator is an
event-driven simulator
. When a circuit node changes in value the time, the node, and the new value are collectively known as an
event
. The event is scheduled by putting it in an
event queue
or
event list
. When the specified time is reached, the logic value of the node is changed. The change affects logic cells that have this node as an input. All of the affected logic cells must be
evaluated
, which may add more events to the event list. The simulator keeps track of the current time, the current
time step
, and the event list that holds future events. For each circuit node the simulator keeps a record of the logic state and the strength of the source or sources driving the node. When a node changes logic state, whether as an input or as an output of a logic cell, this causes an event.

An
interpreted-code simulator
uses the HDL model as data, compiling an executable model as part of the simulator structure, and then executes the model. This type of simulator usually has a short compile time but a longer execution time compared to other types of simulator. An example is Verilog-XL. A
compiled-code simulator
converts the HDL model to an intermediate form (usually C) and then uses a separate compiler to create executable binary code (an executable). This results in a longer compile time but shorter execution time than an interpreted-code simulator. A
native-code simulator
converts the HDL directly to an executable and offers the shortest execution time.

The logic cells for each of these types of event-driven simulator are modeled using a primitive modeling language (primitive in the sense of “fundamental”). There are no standards for this primitive modeling language. For example, the following code is a primitive model of a two-input NAND logic cell:

model nd01d1 (a, b, zn)

function (a, b) !(a & b); function end

model end

The model has three ports:
a
,
b
, and
zn
. These ports are connected to nodes when a NAND gate is instantiated in an input structural netlist,

nand nd01d1(a2, b3, r7)

An event occurs when one of the circuit nodes
a2
or
b3
changes, and the function defined in the primitive model is called. For example, when
a2
changes, it affects the port
a
of the model. The function will be called to set
zn
to the logical NAND of
a
and
b
. The implementation of the primitive functions is unique to each simulator and carefully coded to reduce execution time.

The data associated with an event consists of the affected node, a new logic value for the node, a time for the change to take effect, and the node that caused the event. Written in C, the data structure for an event might look like the following:

struct Event {

event_ptr fwd_link, back_link; /* event list */

event_ptr node_link; /* list of node events */

node_ptr event_node; /* node for the event */

node_ptr cause; /* node causing event */

port_ptr port; /* port which caused this event */

long event_time; /* event time, in units of delta */

char new_value; /* new value: '1' '0' etc. */

};

The event list keeps track of logic cells whose outputs are changing and the new values for each output. The
evaluation list
keeps track of logic cells whose inputs have changed. Using separate event and evaluation lists avoids any dependence on the order in which events are processed, since the evaluations occur only after all nodes have been updated. The sequence of event-list processing followed by the evaluation-list processing is called a
simulation cycle
, or an
event–evaluation cycle
(or event–eval cycle for short).

Delays are tracked using a
time wheel
divided into ticks or slots, with each slot representing a unit of time. A software pointer marks the current time on the timing wheel. As simulation progresses, the pointer moves forward by one slot for each time step. The event list tracks the events pending and, as the pointer moves, the simulator processes the event list for the current time.

13.4.1
VHDL Simulation Cycle

We shall use VHDL as an example to illustrate the steps in a
simulation cycle
(which is precisely defined in the LRM). In VHDL, before simulation begins, the design hierarchy is first
elaborated
. This means all the pieces of the model code (entities, architectures, and configurations) are put together. Then the nets in the model are initialized just before simulation starts. The simulation cycle is then continuously repeated during which processes are executed and signals are updated. A VHDL simulation cycle consists of the following steps:

The current time,
t
c
is set equal to
t
n
.

Each active signal in the model is updated and events may occur as a result.

For each process P, if P is currently sensitive to a signal S, and an event has occurred on signal S in this simulation cycle, then process P resumes.

Each resumed process is executed until it suspends.

The time of the next simulation cycle,
t
n
, is set to the earliest of:

a. the next time at which a driver becomes active or

b. the next time at which a process resumes

If
t
n
=
t
c
, then the next simulation cycle is a
delta cycle
.

Simulation is complete when we run out of time (
t
n
=
TIME'HIGH
) and there are no active drivers or process resumptions at
t
n
(there are some slight modifications to these rules involving
postponed processes—which we rarely use in ASIC design).

Time in an event-driven simulator has two dimensions. A
delta cycle
takes
delta time
, which does not result in a change in real time. Each event that occurs at the same
time step
executes in delta time. Only when all events have been completed and signals updated does real time advance to the next time step.

13.4.2 Delay

In VHDL you may assign a
delay mechanism
to an assignment statement.
Transport delay
is characteristic of wires and transmission lines that exhibit nearly infinite frequency response and will transmit any pulse, no matter how short.
Inertial delay
more closely models the real behavior of logic cells. Typically, a logic cell will not transmit a pulse that is shorter than the switching time of the circuit, and this is the default
pulse-rejection limit
. If we explicitly specify a pulse-rejection limit, the assignment will not transmit a pulse shorter than the limit. As an example, the following three assignments are equivalent to each other:

Op <= Ip after 10 ns;

Op <= inertial Ip after 10 ns;

Op <= reject 10 ns inertial Ip after 10 ns;

Every assignment that uses transport delay can be written using inertial delay with a pulse-rejection limit, as the following examples illustrate.