This is an old revision of the document!


Modelgen-Verilog

The term “modelgen” refers to the device model generator and language that has been part of the Gnucap project from early on. Modelgen reads device descriptions and emits C++ code to be compiled into plugins. Support for Verilog-AMS compact models will be implemented in a modelgen successor, “modelgen-verilog”, following the design patterns and device architecture. Major technical advantages of the latter are automatic differentiation and support for device specific numerical tolerances. Others will follow by adding subsequent Verilog-AMS features.

This work has been carried out with financial support from the NGI0 Entrust Fund, see verilogAMS.

Preprocessing

Verilog-AMS inherits a few “compiler directives” from IEEE Std 1364-2005 Verilog HDL. The important ones are '`define', '`include', '`if(n)def', '`else', '`endif'. These are dealt with in the input stage of the model compiler, where we also strip comments and whitespace.

The semantics are similar to C, relevant differences are - Verilog does not support arithmetic expression in macros, - In Verilog, '`include' only takes a '“quoted”' argument.

Like ordinary C preprocessors, gnucap-verilog accepts macro definitions from the command line using '-D', and include paths with '-I'. We currently process command line options from left to right, and the order of the arguments matters.

The preprocessor functionality is exposed to users through the '–pp' option, it displays the input stream as it will be parsed. The complementary '–dump' option prints the final state of the data base, i.e. after parsing.

Computing Partial Derivatives

In Verilog-A, analog components are essentially modelled as controlled sources. In this section, think of a current source controlled by voltage sources. For example, a linear admittance would boil down to a contribution statement like

I(p, n) <+ V(p, n) * g;

given nodes p, n, and a real parameter g. More generally, a current may depend on multiple node voltages, as in

I(p, n) <+ f(V(c1), V(c2), ... V(cn));

modelled by some real valued multivariate function f.

In a nutshell, to solve the circuit equations, we need to evaluate the partial derivatives of f wrt. to its arguments. Writing v=(v1..vn)=(V(c1) .. V(cn) This amounts to computing \del f(v) / \del v_i for all i.

In practice f is provided as a program involving assignments, loops and conditionals. For simplicity, think of something like

real v0;
real gain;
gain = 10;
v0 = V(c0);
v_in = v0 - V(c1)
I(p, n) <+ v_in * g;

forming an ordinary ccvs. The approach we use is referred to as “forward mode” in Chapter 6 “Implementation and Software” of “Evaluating Derivatives (2nd Ed.)” by Andreas Griewank and Andrea Walther. We implement it as described using operator overloading, with a data type that bundles each value with the derivatives wrt. to the input voltages. We hence reduce the problem to emitting ordinary C++ evaluation code for each rhs of an assignment or contribution statement using a datatype ddouble. ddouble is a struct with a double member variable and additional doubles for each of the 'v_i', arithmetic overloads and some helper functions.

This reduction is particularly handy, because Gnucap parses expressions into reverse polish representation. Remember that the rhs of an assignment like x = (a-b)*c is stored as a token sequence a b - c *. From there, all we need to do is scan the tokens from left to right, emitting code for each operand while keeping track of intermediates on a stack.

Here's how it works with the assignment above. It is transcribed as follows.

  1. open new scope {;
  2. (a). refers to a run time variable. Emit ddouble t0(a); and push 0 on the stack.
  3. (b). refers to a run time variable. Emit ddouble t1(b); and push 1 on the stack.
  4. (-). find and pop 1, find 0 on the top. Emit t0 -= t1;
  5. (c). find t1 unused, put 1 back on the stack and emit t1 = c;
  6. (*). same as 3. but *=.
  7. print x = t0; and close }.

Now, x holds the value of the expression, and partial derivative values.

NB: This is similar in principle to the ADMS approach, but a little less obfuscated. Of course, we also keep track of unused derivatives, but (at the time of writing), pass them to the C++ compiler as literal zeroes. Gcc is pretty good at optimising them out…

Branches and Contributions

In Verilog-AMS, analog behaviour is modelled in terms of controlled sources. Sources of either flow or potential nature are expressed implicitly as contribution statements to branches i.e. pairs of nodes. In Gnucap these controlled sources are represented by subdevices derived from ELEMENT.

We use variants of “d_poly_g”, the transconductance ELEMENT used in (legacy) modelgen. Unlike Verilog-AMS, modelgen only provides current sources with voltage control. One variant “d_vaflow” adds current controls, and the other “d_vapot” implements voltage output.

It is the model compilers responsibility to identify the branches that require sources to be instanciated, select the suitable one and connect the controls and their derivatives accordingly, following evaluation. In Gnucap, the model evaluation involves 5 phases on the component level. These are

  1. check if evaluation is required
  2. read probes
  3. evaluate analog expressions
  4. load (followed by solving the matrix in the simulator)
  5. check for convergence

The first and last step involve tolerances specified through disciplines. Ultimately, disciplines need to become part of the nodes, currently they are directly attached to the branches.

Filter operators

Verilog-AMS defines ddt and idt operators as a means to describe dynamic behaviour in terms of symbolic time derivatives and integrals respectively. The model generator turns these into subdevice elements, similar to source elements that represent analog contribution statements. The “ddt” implementation is derived from the traditional “fpoly_cap” storage element that serves a similar purpose. For this to work in the generality required by Verilog-AMS, we use an additional internal node for each filter. This way the expression evaluation and possible operator nesting remains manageable. Corner-case optimisations remain possible, and will be considered later on. The idt operator is a simple adaptation of the ddt operator.

gnucap/manual/tech/modelgen.1683216301.txt.gz · Last modified: 2023/05/04 11:05 by felixs
 
Recent changes RSS feed Donate Powered by PHP Valid XHTML 1.0 Valid CSS Run by Debian Driven by DokuWiki