Scripting a gnetlist backend in Scheme

by John Doty

(this was originally posted to the gEDA-user mailing list in July 2009)

Don't Panic!

If you've never written a program in Lisp, it looks daunting. But it's a lot easier than it looks. Wrap a little bit of “syntactic sugar” around Lisp, and it becomes Logo, which elementary school children can learn.

And, just to make it clear what some of the funny words mean, Lisp is a computer language, Scheme is a dialect of Lisp, and Guile is an implementation of Scheme. Guile is used for “scripting” gEDA. In particular, the gnetlist front end, written in C, extracts topology and attribute information from schematics, and then presents that data to “back end” Guile scripts for processing and output.

This tutorial is specifically about programming gnetlist back ends in Scheme. If you don't already know Scheme, you should probably look at other material too, such as:

http://www.ccs.neu.edu/home/dorai/t-y-scheme/t-y-scheme.html

Or look up “Scheme tutorial” with your favorite engine: there are many.

The reference document at:

http://www.gnu.org/software/guile/manual/html_node/index.html

may also be useful.

OK, let's get started. Here's a particularly simple back end:

;; gnetlist development playground
 

To use this, put it in a file called gnet-devel.scm. Copy this file to wherever gnetlist Scheme files are kept on your system. On the machine I'm using today, the command is:

$ sudo cp gnet-devel.scm /sw/share/gEDA/scheme/

The ”/sw/” will be ”/usr/” for most Linux package installations, maybe ”/usr/local” or ”~/mygeda/” for a tarball installation. You'll have to figure that out. If the target location is writable by you without superuser privileges, you won't need the “sudo”.

Now, translating ”/sw/” as needed, type:

$ gnetlist -g devel /sw/share/gEDA/examples/lightning_detector/lightning.sch

You should see the usual gnetlist boiler plate, followed by:

guile>

Try:

guile> packages

You should see:

"Q3""R5""Q2""R4""Q1""C6""R3""L2""A1""bat(+3v)""lamp(1)""R2""C5""L1""R1""C4""lamp(2)""C3""C2""C1""D1""bat(0v)""R7""Q4""R6"

“packages” is a handy variable, containing a list of all unique “refdes=” attribute values. By typing it, you fed it to the “REPL”, the Read, Evaluate, Print Loop. So, the REPL read it, evaluated it (getting a list), and printed it.

Now try:

guile> (length packages)
25

What happened here? Here, the REPL evaluated the list

 

In most programming languages, you'd write this expression in more traditional functional notation as “length( packages )”. “length” is a function, which tells you the length of a list.

Use the same notation to do arithmetic. For example, calculate “2+3” as:

guile> (+ 2 3)
5

Note that the procedure ”+” can be used to add any number of quantities, including none at all:

guile> (+)
0
guile> (+ 1 2 3)
6

We'll make use of this later on.

The readline stuff in our gnet-devel.scm back end allows you to use the cursor keys on your keyboard to move around through the history and edit input. Very handy when interacting. Try it.

Another useful variable gnetlist defines is “all-unique-nets” (type it). Just as ”(length packages)” tells you how many packages you have, ”(length all-unique-nets)” will tell you how many nets you have.

Then there's all-pins:

guile> all-pins
(("1" "2" "3") ("2" "3" "1") ("2" "1") ("1" "2") ("1" "2") ("1" "2") ("1" "2") ("1" "2") ("1" "2") ("2" "1") ("2" "1") ("2" "1") ("1" "2") ("2" "1") ("1") ("1") ("2" "1") ("2" "3" "1") ("2" "3" "1") ("1") ("2" "1") ("2" "3" "1") ("1" "2") ("1") ("1"))

Note that this is a little more complicated than the previous examples: it's a list of lists, not just a list of strings. Each of the lists corresponds to the pins on one package. One thing we might want to extract from this is a count of the number of pins. We can't just take the length of all-pins to get this: that gives us the number of lists it contains, which is the number of packages:

guile> (length all-pins)
25

To get the pin count, first get the individual pin counts for each package:

guile> (map length all-pins)
(3 3 2 2 2 2 2 2 2 2 2 2 2 2 1 1 2 3 3 1 2 3 2 1 1)

This is one of the easy ways to do a “loop” in Scheme; (map p x) yields a list of the results of calling procedure p individually for each element of x. Then we can add them up with a slightly different kind of “loop”:

guile> (apply + (map length all-pins))
50

(apply p x) calls procedure p once, with all of the elements of x as arguments. So the expression above winds up evaluating:

 

Thus far we've been using predefined variables and procedures. We'll want to be able to define our own. It's easy:

guile> (define the-answer 42)
guile> the-answer
42

This defines a variable, the-answer, and gives it the value 42.

We can also define procedures:

guile> (define add1 (lambda (x) (+ x 1)))
guile> (add1 100)
101

When you see “lambda” think “procedure”. The first thing (the technical term is “form”) following “lambda” is a list of the arguments of the procedure, in this case ”(x)”. When the the procedure is called, Guile evaluates the remaining forms, in this case just one, ”(+ x 1)”, with actual arguments substituted. The result of the procedure is the result of evaluating the last form. So, ”(add1 100)” becomes ”(+ 100 1)”, which evaluates to 101.

Now we can put our statistics collection together into a back end. First, define a procedure to write a line of output:

 

We're using two new builtin procedures here, “display” and “newline”, which should be self-explanatory. Now:

; no arguments
"pins:     ""packages: ""nets:     "
guile> (display-stats)
pins:     50
packages: 25
nets:     13

To finish off a back end, we need a “main program”. By convention, that has the name of the back end. It has the responsibility of opening the output file, too. So, for a “stats” back end for collecting the stats, the entire file looks like:

;; gnetlist back end for extracting design statistics
;;
;; Legal boilerplate here as needed
;; Collect and output the statistics
; no arguments
"pins:     ""packages: ""nets:     ";; Simple output format
 

Put this in a file named gnet-stats.scm, copy it to where it belongs with something like

$ sudo cp gnet-stats.scm /sw/share/gEDA/scheme/

and then “gnetlist -g stats” followed by the usual other arguments and schematic pathnames will put your design's statistics in the output file (default output.net).

Pretty easy, huh? Useful, too. Lately I've been designing systems that consist of stacks of boards: statistics like these are helpful in figuring out which subsystems I should combine on each board.