Functional Modeling

One can broadly classify the models written in Verilog-A into two categories: device models and functional models. Device models generally try to capture the detailed behavior of some physical device. There are many examples of device models such as resistors, capacitors, diodes, transistors, motors, etc. Device models are generally combined into a circuit and simulated. Functional models are higher level models; models of the circuit themselves. For example, one might contain tens to hundreds of device models into a circuit that represents an amplifier. Then, to allow efficient simulation of systems that contain that amplifier, one might create a more abstract model that represents the amplifier. This model is a functional model. It represents the inputs and outputs in rather ideal ways and does not contain the internal structure constructed from device models.

The abstract nature of functional models brings new requirements on the model itself. There are certain things that the simulator needs to know in order to render the behavior of the model efficiently and accurately beyond the basic input-output relationship. In this case, the model must include hints that the simulator can use to adapt its behavior to the needs of the model. For example, functional models often exhibit discontinuous behavior, and the simulator needs the hints to identify the discontinuity and deal with it efficiently. Consider a digital inverter. In the simplest model of an inverter there is a threshold voltage at the input. When the input voltage is below the threshold the output is high, and when it is above the threshold the output is low. One could write that as follows:

Warning

This is an example of a bad model. It is discontinuous and does not accurately resolve the time of the threshold crossing. Do not use it.

module inverter(out, in):
output out;
input in;
electrical out, in;

analog begin
    if (V(in) > 0.5*V(vdd))
        V(out) = 0;
    else
        V(out) = V(vdd);
end
endmodule

This is a very simple description of an inverter model. It has, however, two problems related to the discontinuous nature of the model. The first is that the behavior of the model changes abruptly when the input voltage crosses a threshold, and the simulator is unaware of the threshold and so can do little to control the simulation time steps to accurately resolve the crossing. Second, the output voltage changes discontinuously, which can cause substantial convergence and accuracy problems.

Resolving the Threshold

The model as given changes its behavior abruptly as the input voltage crosses the threshold of 0.5*V(vdd), however the simulator is unaware of this threshold. As such, the simulator can make no attempt to resolve the time of the threshold crossing. Instead the output changes at the first time point after the threshold is crossed. When that occurs depends on the time step the simulator has chosen. This represents a problem. A significant aspect of the behavior is dependent on the simulator’s time step. Since time steps exist only in a simulation and not in the real world, the resulting behavior of the model is non-physical. In effect, it is as if a substantial and seemingly random amount of delay has been added to the behavior of the inverter.

The way to avoid this problem is to employ an event statement that contains a cross function:

Warning

This is an example of a bad model. It is discontinuous. Do not use it.

module inverter(out, in):
output out;
input in;
electrical out, in;

analog begin
    @(cross(V(in) - 0.5*V(vdd)))
        ;
    if (V(in) > 0.5*V(vdd))
        V(out) <+ 0;
    else
        V(out) <+ V(vdd);
end
endmodule

Notice that the statement associated with the event statement (the statement that is executed when the event occurs) is empty. In Verilog-A, event statements do two things. They evaluate an event expression that detects events and acts to control the simulator’s choice of time points; and they execute the statement that immediately follows them, but only when a event occur. In this case, the cross function forces the simulator to place a time point close to, but just after, the threshold crossing. This is the desired effect of the event statement; nothing else is needed so an empty statement follows the event statement.

It is tempting to include the if statement in the event statement:

Warning

This is an example of a bad model. It is discontinuous and its value can be wrong at the beginning of the simulation. Do not use it.

module inverter(out, in):
output out;
input in;
electrical out, in;
integer d_out;

analog begin
    @(cross(V(in) - 0.5*V(vdd)))
        if (V(in) > 0.5*V(vdd))
            d_out = 0;
        else
            d_out = 1;
    V(out) <+ d_out*V(vdd);
end
endmodule

Notice that the contribution was moved out of the event statement. This is because event statements are only evaluated at discrete points in time, and contributions must be made continuously. In fact, it generally considered poor style to have contributions in if statements unless it is essential to the behavior of the model. It avoids the possibility that a contribution shows up in one side of the side of the if statement and not the other, which might inadvertently create a switch branch, which can cause simulation problems.

This version of the model is problematic in that the value of d_out is simply left at its initial value until the first threshold crossing. In Verilog-A variables are initialized to 0, so the output of the inverter will be zero regardless of the input value until the first time the input crosses the threshold. Thus, this version of the model should be avoided.

Smoothing the Output

The second issue with our first attempt to model an inverter was that the output jumped discontinuously between low and high values. That issue is resolved with the use of a transition function:

module inverter(out, in):
output out;
input in;
electrical out, in;
integer d_out;

analog begin
    @(cross(V(in) - 0.5*V(vdd)))
        ;
    if (V(in) > 0.5*V(vdd))
        d_out = 0;
    else
        d_out = 1;

    V(out) <+ transition(d_out, 0, 10n)*V(vdd);
end
endmodule

The transition takes three or four arguments. The first is the signal to be smoothed. It is expected to be piecewise constant. This is an important restriction. If you were to pass value that changed continuously as the first argument, it conceptually generates a continuous stream of transitions, which would slow the simulator and generate a ratty looking smoothed output. Ideally, the simulator would prevent you from passing a continuous-varying signal as the first argument. You cannot count on that. This restriction is why V(vdd) is placed outside the transition function. This is important even if you believe that vdd is constant because to the simulator vdd is an electrical node and as such has a value that is computed to within the simulator’s tolerances. Thus even a ‘constant’ supply voltage may include microscopic variations that would be picked up by the transition function. See Modeling Multiplexers for another example of how continuous and discrete behavior can be combined in an analog block.

The second argument to the transition function is a time delay. Generally you want to keep this a zero unless modeling the delay is important. The reason being that specifying a non-zero delay will force an extra time step to resolve the beginning of the transition, and so would slow the simulator to some degree. Extremely short delay times are especially problematic.

If four arguments are passed, the last two are the rise and fall time of the transition. If only three arguments are passed, the last is the time of both rising and falling transitions. The transition time should always be non-zero and should be as large as you are comfortable making them.

Short delays and transition times require small time steps in order to render the delay or transition. This can be problematic as the simulator cannot recover immediately from a small time step; it must grow the time step slowly. Consider a 1 MHz clock that is rendered with a 1ps delay or transition time. If 1 MHz is the highest frequency in the circuit, then time steps needed by the simulation probably fall in the 10-100 ns range. However, when a clock transition occurs, the simulator needs a time step no greater than 1 ps. Then it will have to grow the step slowly back to the 10 ns range (1 ps, 2 ps, 4 ps, …). The result will be needlessly many extra time points, perhaps 10-20 needless time points per clock transition. Better to set the delay time to 0 and the transition time to 1 ns, or even better 10 ns. It may result in signals that are somewhat unrepresentative for your circuit, but if the those delays and transition times are not important to the behavior of your circuit, the zero delays and longer transition times will reward you with a faster simulation without degrading the accuracy of the result.

Simplified Model

The model can be shortened a bit by replacing the if statement with a simple comparison operation. Comparison operations return 1 if true and 0 if false:

module inverter(out, in):
output out;
input in;
electrical out, in;

analog begin
    @(cross(V(in) - 0.5*V(vdd)))
        ;

    V(out) <+ transition(V(in) < 0.5*V(vdd), 0, 10n)*V(vdd);
end
endmodule

Notice that the comparison operation was switched from > to < in order to implement the inversion. It is also possible to do it as follows:

module inverter(out, in):
output out;
input in;
electrical out, in;
integer d_in;

analog begin
    @(cross(V(in) - 0.5*V(vdd)))
        ;
    d_in = V(in) > 0.5*V(vdd);

    V(out) <+ transition(!d_in, 0, 10n)*V(vdd);
end
endmodule

The model is slightly preferred to the previous model as it is a bit more obvious what is intended.

However, there is a user trap to be avoided here. Consider the following version:

Warning

This is an example of a bad model. It generates extremely large output voltages. Do not use it.

module inverter(out, in):
output out;
input in;
electrical out, in;
integer d_in;

analog begin
    @(cross(V(in) - 0.5*V(vdd)))
        ;
    d_in = V(in) > 0.5*V(vdd);

    V(out) <+ transition(~d_in, 0, 10n)*V(vdd);
end
endmodule

In this version the logical inversion operator was replaced by the bitwise inversion operator. The value of the logical inversion operator is either 0 or 1, but the bitwise inversion operator returns the value of its argument with all bits inverted. Here the argument is a 32-bit integer. So in this case the value returned by the bitwise inversion operator is either ‘hFFFF_FFFE ‘hFFFF_FFFF. In decimal numbers, that is either 4,294,967,294 or 4,294,967,295. Thus, the output of this model is 4.3 GV × V(vdd) regardless of the value of d_in.

Best Model

Given these rules, as well as a desire to make the behavior of the model as obvious as possible, the following represents the best of the above models:

module inverter(out, in):
output out;
input in;
electrical out, in;
integer d_in;

analog begin
    @(cross(V(in) - 0.5*V(vdd)))
        ;
    d_in = V(in) > 0.5*V(vdd);

    V(out) <+ transition(!d_in, 0, 10n)*V(vdd);
end
endmodule

Digital Control of Analog Behavior

This is another situation that comes up in mixed-signal models that requires that the model provide hints to the simulator as before. Consider the following charge pump model:

module cp (out, u, d):
output out; electrical out;
input u, d; logic u, d;

analog begin
    @(u or d)
        ;
    I(out) <+ 10u*(transition(d, 0, 1n) - transition(u, 0, 1n));
end

The charge pump is expected to deliver charge to the output in proportion to to the amount of time either the u or d inputs are high, and the charge should be positive if the u input is high and negative if the d input is high.

There are two things to notice about this model. First is the presence of the event statement. It assures that the analog kernel places a time point at the instant when either input changes value. Second, transition functions are used to control the transition times of the output pulses. Both are very important for this model. Lack of time point control would result in high level of jitter in the output. Lack of transition functions would result in uncontrolled transition times, with the result being either much more or much less charge being delivered than is expected. This type of charge pump is used in phase-locked loops, and a significant amount of error in the charge delivered can dramatically alter the effective gain the loop, which can cause the loop to become unstable.

Unfortunately, the Cadence simulator places unreasonable restrictions on event expressions in the analog block. Specifically, any digital signals used in an analog event expression must be preceded by either the posedge or negedge qualifiers. Thus, the model must be modified when intended for the Cadence simulator:

module cp (out, u, d):
output out; electrical out;
input u, d; logic u, d;
reg sync = 0;

always @(u or d)
    sync <= !sync;

analog begin
    @(posedge sync or negedge sync)
        ;
    I(out) <+ 10u*(transition(d, 0, 1n) - transition(u, 0, 1n));
end

With only two values, u and d, it is not difficult to add the required posedge and negedge to the event expression. However, with more than two values it can become tedious. Also, posedge and negedge cannot be applied to buses or real values. Instead, one must follow this pattern is used where the event expression is implemented outside the analog block and a register variable is toggled to trigger the event statement within the analog block.

General Rules

This inverter and charge pump examples illustrates three important rules that must always be kept in mind when creating functional models in Verilog-A or Veriog-AMS:

  1. Thresholds require a event statement that contains a cross function so that the time of the threshold crossing is accurately resolved.

  2. Output with discrete levels require transition functions to provide smooth transitions between the levels.

  3. Digital control of analog behavior requires an analog event statement to synchronize the kernels when the controlling behavior changes.

These rules hold true for both Verilog-A and Verilog-AMS.