This shows you the differences between two versions of the page.
gnucap:manual:tech:modelgen [2023/05/05 03:57] felixs illustrate ddt |
gnucap:manual:tech:modelgen [2025/05/03 09:37] (current) felixs drop content now in modelgen-verilog |
||
---|---|---|---|
Line 1: | Line 1: | ||
- | ====== Modelgen-Verilog ====== | + | ====== Modelgen ====== |
The term "modelgen" refers to the device model generator and language that has | 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 | been part of the Gnucap project from early on. Modelgen reads device | ||
descriptions and emits C++ code to be compiled into plugins. Support for | descriptions and emits C++ code to be compiled into plugins. Support for | ||
- | Verilog-AMS compact models will be implemented in a modelgen successor, | + | Verilog-AMS compact models has been implemented in a modelgen successor, |
- | "modelgen-verilog", following the design patterns and device architecture. | + | "[[modelgen-verilog]]", following the design patterns and device architecture. |
Major technical advantages of the latter are automatic differentiation and | Major technical advantages of the latter are automatic differentiation and | ||
support for device specific numerical tolerances. Others will follow by adding | support for device specific numerical tolerances. Others will follow by adding | ||
subsequent Verilog-AMS features. | subsequent Verilog-AMS features. | ||
- | This work has been carried out with financial support from the NGI0 Entrust | ||
- | Fund, see [[gnucap:projects:nlnet: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. | ||
- | |||
- | - open new scope ''{''; | ||
- | - (''a''). refers to a run time variable. Emit ''ddouble t0(a);'' and push ''0'' on the stack. | ||
- | - (''b''). refers to a run time variable. Emit ''ddouble t1(b);'' and push ''1'' on the stack. | ||
- | - (''-''). find and pop ''1'', find ''0'' on the top. Emit ''t0 -= t1;'' | ||
- | - (''c''). find t1 unused, put ''1'' back on the stack and emit ''t1 = c;'' | ||
- | - (''*''). same as 3. but ''*=''. | ||
- | - 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 | ||
- | |||
- | - check if evaluation is required | ||
- | - read probes | ||
- | - evaluate analog expressions | ||
- | - load (followed by solving the matrix in the simulator) | ||
- | - 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. | ||
- | |||
- | To illustrate the implementation of a ddt filter, consider the contribution | ||
- | statement ''I(p,n) <+ f2(ddt(f1(V(p,n)))''. It splits into | ||
- | a voltage probe, a filter and a controlled source as follows | ||
- | <code> | ||
- | real t0; | ||
- | t0 = V(p,n); | ||
- | t0 = f1(t0); | ||
- | t0 = ddt(t0); // (*) | ||
- | t0 = f2(0); | ||
- | I(p,n) <+ t0; | ||
- | </code> | ||
- | |||
- | and happens to model a capacitor, if ''f1(x)==f2(x)==x''. All we need for the general case is ''ddt(t0)''. | ||
- | The following subcircuit model implements a capacitor corresponding to the simplified contribution statement. | ||
- | |||
- | <code> | ||
- | module cap(a, b) | ||
- | parameter c | ||
- | tcap #(c) store(i 0 a b); | ||
- | resistor #(.r(1)) shunt(0 i); | ||
- | vccs #(.gm(1)) branch_i(b a i 0); | ||
- | endmodule | ||
- | </code> | ||
- | |||
- | It contains a trans-capacitance device named "store". This device outputs a | ||
- | current proportional to the time derivative of the voltage across ''(a,b)''. In | ||
- | combination with the shunt resistor and the internal node ''i'' it represents a | ||
- | ''ddt'' filter as required in (*), where the rhs implicitly acts as a voltage probe ''V(i)''. | ||
- | |||
- | In terms of implementation, the ''tcap'' device is a version of the Modelgen | ||
- | ''fpoly_cap'' limited to 4 external nodes and without self-capacitance. | ||
- | The ''va_ddt'' filter in Modelgen-Verilog retains the arbitrary number of nodes and adds the shunt resistance. |