Netgen (legacy)

Table of Contents

Introduction
Hierarchical specification of circuits
Connecting up the circuit
The relationship between placement and interconnect
Scoping rules for identifiers and Implicit (global) connections
Naming conventions
Other Procedures
Generating Output
Other Useful Functions
Other Cell Manipulation commands
Alternative Input Formats
Testing graph isomorphism.
Command-line interface and X-windows interface.
Future work

Netgen: An embedded-language netlist specification

This document (presumably) written by Massimo Sivilotti, the original author of Netgen. The file can be found in the distribution source under the filename doc/netgen.doc. It is an ASCII text file. The version here has been formated in HTML.

Netgen consists of a set of C language subroutines intended to facilitate the specification of netlists of circuits that are ill-suited to conventional schematic-capture paradigms.

Some of the features of the netgen system:

  1. full hierarchical specification of the circuit in question.
  2. powerful list-based operators that permit rapid declaration and subsequent connection of semantic structures such as busses, signal vectors, port lists, etc.
  3. optional topology-driven front end, providing connectivity of cells according to a composition-by-abuttment paradigm.
  4. dynamic scoping rules governing visibility of ports and nodes.
  5. support for multiple output netlist file formats, including .NTK, .EXT (and consequently .SIM), .ACTEL, and .WOMBAT. In addition, hierarchical .NTK files can be read, providing an alternate mechanism to input cells.

Hierarchical specification of circuits.

The basic unit in defining a circuit is a CELL. A cell, in turn, can contain any number of other cells, called INSTANCES. A cell has some special nodes, called PORTS, that are the connections that can be made to the cell when it is instanced. When a cell is actually instanced, these ports are called PINS within the context of the cell being constructed. The ports (of the current cell) and pins (of previously defined cells) may connect together directly, or through optional NODES.

So much for the definitions. Now the good news: within a CELL, there are 3 kinds of elements (PORTS, NODES, and PINS); these are treated ABSOLUTELY IDENTICALLY from the user's perspective. Netgen has a uniform internal representation with a single operator: "connect". In defining a cell, the user "connects" ports, nodes, and pins as required to construct the desired circuit. In particular, there is no real notion of a "wire" within netgen; situations where named signal lines would be useful can be accomodated by using named internal nodes.

In it's simplest form, the user interface to netgen consists of four procedures:

void CellDef(char *name);
Declares cell "name", and appends all future commands to the definition of that cell.
void Port(char *name);
Declares a port node called "name" within the current cell.
void Node(char *name);
Declares an internal node called "name" within the current cell.
void Instance(char *cell, *instancename);
Incorporates an instance of cell "cell" into the current cell; the instance is called "instancename", which must be a unique identifier within the context of the current cell (the function Next(cell) is often useful in generating this unique identifier). The Instance() procedure generates a set of PINS corresponding to the ports of "cell"; these pins are named "instancename"/"portname".

As a (trivial) example, consider the following definition of the elements within a CMOS inverter:

CellDef("inv");
PortDef("in");
PortDef("out");
PortDef("vdd");
PortDef("gnd");
Instance("p","pullup");
Instance("n","pulldown");
Instance() also has some subtle side-effects. See the section below on dynamic scoping for a discussion of how these can be used to implicitly connect elements automatically.

Connecting up the circuit

(or, using wirelists effectively).

We must now specify how the above elements are to be interconnected. The most general form of this operator is:

void Connect(char *list1, *list2);
In its simplest form, list1 and list2 are the names of two elements to be connected together. In the above example,
Connect("pulldown/source", "gnd");
Connect("pullup/source", "vdd");
Connect("pulldown/gate", "in");
Connect("pullup/gate", "in");
Connect("pulldown/drain", "out");
Connect("pullup/drain", "out");
completes the specification of the inverter. However, the same result can be obtained more simply by the use of WILDCARDS in the list-constructor strings list1 and list2. Supported wildcards are the UNIX(TM) shell wildcards (*,?,{},[]) where:
*
matches 0 or more characters (unlike csh, "/" is treated identically to any other character).
?
matches any single character.
{}
delimits alternate choices. For example "c{a,o}t" matches "cat" and "cot".
[]
delimits ranges for matching a single character. For example, "cell[1-3]" matches "cell1", "cell2", and "cell3". Multiple ranges can be specified (e.g. [a-mp-x]), as can excluded ranges ([~a-m]).
Full regular expression pattern matching is also available. To use this form, set the global flag UnixWildcards to 0, and see regexp(3) for detailed syntax.

The result of Connecting two lists is the following:

  1. if the lists are the same length, corresponding pairs of elements are connected to each other,
  2. if one of the lists has a single element, all elements of the other list are connected to that element,
  3. otherwise, the lists are of unequal lengths and an error is reported.
Thus, the inverter can be specified simply by:
Connect("*drain", "out");
Connect("*gate", "in");
Connect("*up/source", "vdd");
Connect("*down/source", "gnd");
The ORDER of generated list elements is the same as the order in which the elements were added to the cell. In the above example, "*gate" gets expanded to {"pullup/gate","pulldown/gate"}. It is possible to take advantage of wildcards (and this bit of information) even further:
Connect("*drain", "out");
Connect("*gate", "in");
Connect("*source","*d");
The user should, however, try to minimize the abuse of wildcards at the expense of clarity. Wildcards are provided to expedite grouping of elements with associated semantics (see the section below on topological specification of interconnection), NOT to minimize the keystrokes required to specify a netlist.

The relationship between placement and interconnect.

Netgen provides a simple model for VLSI cell placement: cells are rectangular and have ports along the perimeter on 4 sides (N,S,E,W). Composition is by abuttment and can occur in either the horizontal or vertical direction. When two cells are placed so that they share an edge, the port lists along that edge of the respective cells are generated, and the Connect operator is invoked on them. Ports along the othogonal edges are propagated as ports of the cell being defined. The (implied) order of ports in cells is bottom-to-top on vertical edges, and left-to-right on horizontal edges, corresponding to the order of placement of subsequent cells.
void Place(char *name);
An instance of cell "name" is generated, and placed in a direction corresponding to the "Composition" flag (if Composition==HORIZONTAL, the cell is placed to the right of any previously placed cells; if Composition==VERTICAL, the cell is placed above; if Composition==NONE, a call to Place(name) is equivalent to Instance(name,Next(name))). A unique instance name is generated for the cell, by appending an integer (starting at 1) to "name". Ports along abutting edges are "sealed", and ports along orthogonal edges are made ports of the cell being defined. In the first call to Place within a cell, the leftmost (or bottom, if Composition==VERTICAL) ports of cell "name" are defined to be the corresponding ports of the cell being defined. In order to correctly generate the port list at the other end of the cell, the procedure:
void EndCell();
must be called to end the current cell definition. It is good form to always terminate cell definitions with this statement (even if you did not use the implicit composition mechanism) -- one side-effect of EndCell() is to arbitrarily connect all otherwise disconnected nodes if the global variable NoDisconnectedNodes is set to 1 (the default is 0).

In order to understand the relationship between topology and connectivity, the user must adhere to a particular NAMING CONVENTION. Ports must be declared in the correct order (bottom-to-top and left-to-right), and with the following syntax: the side of the port is prepended to the port name, separated by a ".". For example, if the input to the inverter is to be on the left (west) side, we would replace the previous definition of port "in" by:

Port("W.in");
Corresponding changes are required for the other ports (for example, "E.out", "N.vdd", "S.gnd"). Then, the specification for a chain of 4 inverters (perhaps an exponential horn for a pad driver) takes the form:
CellDef("exphorn"); Composition = HORIZONTAL; for (i=0; i<4; i++) Place("inv"); EndCell();
This code fragment specifies a cell "exphorn" that takes a single input on the left (west) side, generates a single output on the right side, and has 4 ports on the top (which should be connected to vdd in the cell that calls "exphorn"), and 4 on the bottom (gnd).

To simplify code fragments such as the above, the procedure

void Array(char *name, int count)
is provided, and is equivalent to "for (i=0;i < count;i++) Place(name)".

Scoping rules for identifiers; Implicit (global) connections.

It is often inconvenient to have to continually declare high connectivity nodes (e.g. power rails, bit/word lines, etc.) explicitly as parameters to each cell. Also, the number of ports of generated cells (see example above) can be kept managable.

The solution to this problem takes the form of scoping rules for element names; when a cell is instantiated within the context of a calling cell, particular elements within the CALLED cell are implicitly connected to elements in the PARENT cell. This binding process is exactly analogous to local/global variables within nested PASCAL procedure declarations, but the process is slightly different: instead of declaring LOCAL variables, and assuming all others to have GLOBAL visibility, particular ports (i.e. explicitly global objects) are declared to obey certain implicit connection rules by the following:

void Global(char *name);
Semantically, "name" is a PORT that connects itself automatically when the cell is instanced. In particular, a port obtained by a call to Global("xxx") in cell B will automatically connect to an element named "xxx" in cell A, whenever A instantiates B. If "xxx" does not exist in A, the instantiation of B will CAUSE a global port named "xxx" to be declared (by a call to Global("xxx") in A). The result of this action is that the unbound variable "xxx" is now propagated to the next level of the hierarchy. This dynamic scoping allows cell B (which is instantiated within cell A) to be declared BEFORE cell A.

An example is useful. Reconsider the "inv" cell above, but this time change the definitions of the power ports to :

Global("vdd");
Global("gnd");
The previous code fragment for "exphorn" would now result in a cell with 4 ports, named "vdd", "gnd", "W.inv1/W.in" and "E.inv4/E.out" (instead of the previous exphorn, which had 10 ports).

Naming conventions:

Within the context of the calling cell:
  1. Ports:
    SEPARATOR
  2. Ports - Oriented:
    { PORT_DELIMITER PORT_DELIMITER }* \ PORT_DELIMITER
  3. Ports - Global:
    -- name propagates unchanged, but is bound to locally-declared identifier of same name.
  4. Ports - Unique Global:
    INSTANCE_DELIMITER SEPARATOR
  5. Flattened cells:
    { SEPARATOR }* SEPARATOR
  6. Cells read from NTK:
    INSTANCE_DELIMITER SEPARATOR

Other Procedures

(i.e. what else do I need to know?)

In order to use the netgen package, the user's program must include the library, link the correct object module, and initialize the package.

The first is accomplished by the line:

#include "netgen.h"
at the top of every program. In practice, "netgen.h" is not likely to be found in the current directory, so ask a system guru where it is located. The same goes for the object file "netgen.a" that must be linked in (via a command-line parameter in UNIX; if you are running netgen on a PC (or maybe even someday on a chipmunk) talk to a system mangler).

The netgen package is written in ANSI-standard C, and requires an ANSI-C compiler. On many UNIX systems, the GNU C compiler is known to work; the incantation to compile a user's main program main.c is:

gcc -o main main.c netgen.a
The netgen package is initialized via a call to:
void Initialize();
This procedure sets up the internal data structures, and pre-defines two types of cells: n- and p-channel transistors. These cells are declared by the following code (which is actually inside Initialize()):
CellDef("p");
Primitive();
PortDef("gate");
PortDef("drain");
PortDef("source");
EndCell();

CellDef("n");
Primitive();
PortDef("gate");
PortDef("drain");
PortDef("source");
EndCell();
The procedure Primitive() in the above code fragment informs netgen that the element is a primitive, and not to be further decomposed when an output netlist file is written. This mechanism assumes that the system READING the netlist understands the meaning of the element.

A slightly more powerful initialization routine is:

void InitializeCommandLine(int argc, char *argv[]);
This routine parses any command-line arguements (interpreted as commands to the Query() routine), and processes them first. A single '-' in the command line indicates that interactive operation should follow these commands.

For the user's convenience, an alternative to the Instance/Connect mechanism is provided; when a cell is instantiated, explicit connections may be specified to each of its ports via the procedure:

void Cell(char *CellName, char *PortNameList, ...);
The strings passed in PortNameList, and any subsequent (assumed char *) are of one of two forms:
  1. they are a list of "port=element" strings. In this case, their order is unimportant; the ports of the cell are connected to the corresponding element. For example,
    Cell("n", "drain=n1", "gate=g1", "source=n2");
    does the obvious thing.
  2. arguements do not contain "=", but may contain wildcards, in which case they are expanded into lists, and this list is sealed with the port list obtained when CellName is instanced (and after any "=" ports are matched). For example, an n-channel device in parallel with another (named "tran1") can be obtained by:
    Cell("n", "tran1/*");
In both of these cases, the portnames of CellName are also added to the namespace of the current cell, so subsequent calls to Connect() operate correctly.

Along the lines of Cell(), two other procedures are defined:

void N(char *PortNameList, ...);
void P(char *PortNameList, ...);
These are merely calls to Cell(), with the CellName arguement hard-wired to "n" and "p", respectively.

A slight variant on Cell() is the Wire() procedure:

void Wire(char *instance_name, ...);
This procedure accepts the name of an instance, and a list of ports identical to Cell(). The only difference is that for Wire(), the instance name is explicitly required, while for Cell() it is totally suppressed (in fact, the user cannot even find out what it was). The "=" form of the parameter list can be used in Wire() as well.

Other Useful Functions

Because many element names are derived from others, netgen provides some useful functions to manipulate temporary strings:
char *Str(char *format, ...);
This function takes a format string and a variable arguement list in a manner identical to printf(), and returns a pointer to a temporary string. There are only a small number of these (statically allocated) strings available, so you must use the pointer quickly. Typical use is in declaring a "subscripted" cell: CellDef(Str("array%d", i));

Finally, as a convenience to the user, a procedure:

void Query(void);
puts netgen in an interactive mode, where the user is permitted to choose between output netlist formats, input files to read, and various other things. It is a convenient way to end a program.

If your version of netgen was compiled with X11 support, a more general command parser is available as:

void X_main_loop(int argc, char *argv[]);

Generating Output

The main output format currently supported is the Caltech NTK format. An output file is created by calling:
void Ntk(char *cellname, char *filename);
All nodes are named, with the following priority given to nodes with multiple elements connected to them. Highest priority goes to port names, then to internal node names, and finally to instance pin names. Certain shortcomings in the .NTK specification (such as the inability to connect two ports within a cell) are dealt with correctly. If filename is NULL, or an empty string, a file name is generated by appending .ntk to "cellname".

Late news flash: the MAGIC hierarchical extract format is now supported to a limited. To generate .EXT files, call

void Ext(char *cellname);
This procedure generates a .EXT file for EACH(!) cell class. The only thing .EXT is good for is to run EXT2SIM on, in order to get the other MAGIC netlist format (flattened list of transistors only.). SIM output can be generated directly by:
void Sim(char *cellname);
Netgen also supports the Actel(TM) cell libraries and netlist file format. To access these cells, call the procedure:
void ActelLib();
These cells are then regular netgen objects, whose elements are accessed by the names described in the Actel gate catalog. For example, the pins on a 2-input NAND gate are "thisgate/A", "thisgate/B", and "thisgate/Y", after a call to Instance("NAND2","thisgate").

Actel .adl files are written by calling the procedure:

void Actel(char *cellname, char *filename);
As before, if filename is NULL, or an empty string, a file name is generated by appending .adl to "cellname".

Netgen supports the netlist format required by the WOMBAT netlist comparison program. Such files are written by the procedure:

void Wombat(char *cellname, char *filename);
WOMBAT requires flat (non-hierarchical) netlists. See the Flatten() procedure below.

Netgen also supports two popular circuit simulator formats:

void SpiceCell(char *cellname, char *filename);
void EsacapCell(char *cellname, char *filename);
Of course, because netgen only maintains topological information and not device sizing information, writing circuit simulator input files generally requires some manual post-processing.

Netgen also supports an output format that is, in fact, the specification of the netlist using the netgen C-language embedded interface:

void Ccode(char *cellname, char *filename);
This procedure creates a file that is suitable for inclusion and compilation within a program that, when linked with the netgen libraries, will re-create the netlist.

Finally, netgen provides a machine-dependent (i.e., nonportable) but fast netlist format, based on a binary dump of its internal data structures:

void WriteNetgenFile(char *cellname, char *filename);

Other Cell Manipulation commands

Two routines are also provided to manipulate the cell dictionary (the list of defined cells):
void CellDelete(char *cellname);
This procedure deletes the definition of 'cellname' from the dictionary. It does NOT care whether instances of 'cellname' exist within other cells. This procedure is useful for remapping sub-cells within a design, by deleting the original cells, then subsequently loading in new definitions.
void CellRename(char *from, char *to);
This procedure deletes the definition of cell 'from', and creates an identical cell in 'to'. If cell 'to' already exists, that cell is overwritten.
void CellCopy(char *from, char *to);
Copy cell 'from', calling the new cell 'to'. If 'to' exists already, its definition is overwritten. Netgen also supports some hierarchy-manipulation functions:
void Flatten(char *cellname);
The Flatten() procedure actually flattens the internal data structure, and is consequently irreversible. Of course, flattened cells can also be written out in any of the other netlist formats.
void FlattenInstancesOf(char *classname);
This procedure flattens all instances of 'classname' within other cells. However, any hierarchy within 'classname' remains.

Alternative Input Formats

In addition to the embedded procedural interface for declaring/defining cells, netgen is able to read hierarchical .NTK files directly, and convert them into the internal element list representation. These cells can then be operated on identically to procedurally defined cells. This process facilitates using a (separately generated, perhaps by a graphical schematic capture system) cell library, then using netgen as a composition tool.

In fact, many different input netlist formats are supported:

char *ReadNtk(char *filename);
char *ReadExt(char *filename);
char *ReadSim(char *filename);
char *ReadSpice(char *filename);
char *ReadNetgenFile(char *filename);
All of these procedures return the name of the top-level cell that was read.

A general file-reading interface is provided by:

char *ReadNetlist(char *filename);
This procedure selects one of the above netlist formats, based on the file extension of the 'filename' arguement.

Testing graph isomorphism.

Netgen includes a fast, powerful probabilistic algorithm for verifying whether two netlists represent isomorphic graphs. The favored interface to this facility is the 'netcomp' program that is built along with the 'netgen' interface. However, advanced users may need the additional functionality provided by the C-language interface:
void NETCOMP(void);
This is a primitive command-line interpreter (similar to Query()) that gives the user access to all the internal capabilities of netcomp. A simpler interface is given by:
int Compare(char *cell1, char *cell2);
This procedure returns 1 if the two cells are isomorphic, and 0 otherwise. If you consider MOS transistor sources and drains to be equivalent (i.e., permutable), set the integer variable EquivalenceTransistors to 1. This is appropriate for extracted outputs of VLSI circuits, but (typically) not for asymmetrical circuit-simulator specifications.

Command-line interface and X-windows interface.

By default in UNIX, if the DISPLAY environment variable is set, netgen starts an X-window application. Otherwise, a command-line interface is executed, running the function Query() described above. In either case, any command-line arguements are passed through Query(); if the last command is a single dash ("-"), the regular command parser is started AFTER the command-line commands are executed. Otherwise, netgen terminates.

Future work:

  1. have ntk generate file of output
  2. capture wildcard query routine into library (returning a string, so user can incorporate it into interactive programs).
  3. add "internal ports" to implement scoping rules for variable names.
  4. find out what output format WOMBAT requires and generate it.
  5. add simple operators to act on element lists: move, delete, reverse
  6. don't forget to mention Initialize() procedure
  7. discuss alternatives to Instance() (e.g. Cell(), and N(), P())
  8. ability to flatten cells
  9. learn to write ACTEL format
  10. more streamlined syntax for Global ports
  11. don't forget to mention Primitive() procedure.

email:

Last updated: August 5, 2016 at 7:10pm