Traditionally, a circuit simulator consists of a program that processes and analyses circuits, together with a collection of device model implementations. The various Verilog standards provide a language to describe circuits, which we refer to as the “structural subset” of Verilog. The “behavioural modelling” subset provides a flexible standard for device modelling.
Gnucap is organised as a simulator program, which implements parsing and interpretation of the “structural subset”, whereas the “behavioural subset” is covered by another tool “modelgen-verilog”. Using modelgen-verilog (and gcc) we turn behavioural models into device plugins for use in the simulator with implementation notes here. The following sections relate to the structural subset, and its implementation on the simulator side implementation on the simulator side.
A module in a Verilog netlist is a collection of components similar to a SPICE subcircuit. Like paramsets, modules are declared at top level only. A module is an object inherited from BASE_SUBCKT, a COMPONENT that permits subdevices. Type resolution and elaboration is different to SPICE. Verilog has no “subtypes”: each subdevice refers to a COMPONENT by type name and has parameters and ports. The type name is resolved after read-in, and held by a stub device. Prototypes matching the type, port and parameter names of the stub are collected as candidates in precalc_first.
Once a candidate COMPONENT has undergone precalc_first, “is_valid()” indicates whether the candidate is the correct one. During expand, a cloned instance of the stub turns into a singleton subcircuit holding the valid candidate.
This singleton subcircuit is then deflated into just the candidate instance by the parent module at the end of expand.
According the the Verilog standard, only paramset, a special COMPONENT type permits overloading. In Gnucap Verilog mode a type name used in a device instance refers to all the prototypes by that name, regardless of their origin. Possible origins are device plugins and module or paramset statements loaded or parsed at any time before circuit expansion takes place. In spice mode, .subckt and .model commands provide a similar mechanism to create prototypes.
A paramset is a COMPONENT with a named reference to another of an underlying type and a set of parameters, similar to a one-device subcircuit. The type name is resolved during precalc_first and is supposedly unique. It is currently assumed to be available when parsing the paramset. Port names are inherited from this prototype and available after precalc_first.
The prototype name in a paramset declaration (used in the simulator) resolves to the first component found in its scope. Next, the device_dispatcher is queried. This is where dynamically loaded plugins may provide models. Note that the dispatcher replaces existing entries when installing the same name again, hence the last one loaded wins.
When gnucap-modelgen reads in a paramset, a module definition must be available by the prototype name, i.e. further up within the compilation unit. The standard requires a unique name. If it is not unique, the outcome is undefined (under costruction).
paramset res resistor parameter R; .r(R) endparamset
Paramsets are declared at top level only, and ready before instances are elaborated when the containing modules are expanded.
The standard allows multiple paramset declarations with the same label (type name). In Gnucap, type resolution is implemented equally for all COMPONENTS.
During elaboration, paramset instances are flattened. An instance R1, as in
module main(); res #(.R(r0)) R1(a, b); endmodule
Is transformed into
resistor #(.r(r0)) R1(.p(a), .n(b));
after “paramset res” has been identified as the only valid, matching prototype. If there are multiple candidates, such as an additional
paramset res resistor parameter r; .r(r) endparamset
TODO: move to tech:modelgen
The motivation for compiled paramsets, as opposed to interpreted ones, or legacy spice .model instances is speed. Compiled paramsets can be an order of magnitude lighter due to the obvious constant pruning and structural collapse. According to the LRM, Section 6.4.2, paramset identifiers need not be unique, and offer a basis for overloading. We make use of this mechanism in slight deviation from the standard.
In Gnucap, a paramset provides a device prototype, and device prototypes need not be unique. Moreover device prototypes are not distinguishable by their origin. In particular, a paramset name may be identical to the prototype module name. This offers the possibility to bundle multiple optimised paramsets alongside a generic module into a single plugin.
When a compilation unit contains such a module and a set of (supposedly optimised, simplified) specialisations of the same one, modelgen-verilog may bundle them into a single plugin. In this situation the plugin installs a set of device prototypes, and the generic one is installed last. Subsequently declared interpreted paramsets refer to this last installed and generic prototype. This way, interpreted paramsets remain flexible, while compiled paramsets may coexist for speed.
TODO: move to tech:modelgen
Given a module declaration, the standard does not offer a way to fix a subset of its parameters, while transparently passing through the others. Paramset sort of does it, but not without changing the behaviour of the specialised device relative to the generic one. Typical use cases are devices specialised without noise, without initial conditions, without additional resistors, without temperature, or with their levels fixed.
One issue shows up when $param_given is used in a model. Consider a module/paramset combo as follows.
module M([...]); parameter real p=1; [..] pg = $param given(p) [..] endparamset paramset M M1 parameter real p=17; .p=p; .something_else=42; // specialise some parameters endparamset
In an instance of M1, $param given(p)
will unconditionally evaluate to ''true', which may break the behaviour otherwise inherited from M.
The possible solution is to introduce parameters without a default value, as shown in the following example
paramset M M2 parameter real p; .p=p; .something_else=42; // specialise some parameters endparamset
The intent is that the user takes control of $param_given(p) in instances of M2 as shown below.
M2 #() myexample1(..); // $param_given(p) == false M2 #(.p(3.14)) myexample2(..); // $param_given(p) == true
With this mechanism, it ought to be possible to wrap M into a paramset that exposes behaviour identical to M.
Another issue is the lack of a portlist in a paramset declaration, allowing for re-using a prototype with its ports renamed. A singleton subcircuit (possibly combined with a paramset) may serve as a workaround, and hence this is low priority.
TODO: move to tech:modelgen
The LRM description for the transition function is unclear. The situation where a new transition overlaps with another needs clarification. A transition filter holds a waveform, similar to the delay line. It is updated whenever transition is called.
In any case, the new waveform must be continuous in all arguments of the new_transition call. Here's how this may be achieved. Suppose, transition is called at time t0, with delay d, new start time ts=t0+d, rise or fall time rf and destination l.
If the waveform has a sample at t0, future samples are deleted.
Otherwise, the future samples are deleted, excluding the first one after t0, say td1. Now let s0 be the slope at t0. The case s0=0 is simple, otherwise assume s0>0 w.l.o.g.
The sample at td1 has a value v1. This gives rise to a slope ps of (ts, v1), (ts+rf, l). Also, let vs be the value at ts.
If ps>s0, we drop the sample at td1 and put in (ts,vs). Else if v0<l, we compute (ti, vi) the intersection of (ts, v1) – (ts+rf, l) with the rising edge, and cut it short by moving (ts,vs) to (ti, vi).
Finally, put in (ts+rf, l).
(The new LRM, VAMS-2023 adds more clarity. It seems to describe the behaviour of the algorithm above).