[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]

gEDA-user: gnetlist/Scheme tutorial

Here, as promised, is the text of the first installment of my  
tutorial. Text only, I still gotta figure out the wiki. Comments are  

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:


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

The reference document at:


may also be useful.

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

;; gnetlist development playground

(use-modules (ice-9 readline))

(define (devel output-filename)

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/ 

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



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)

What happened here? Here, the REPL evaluated the list (length  
packages). 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)

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

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

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)

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

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))

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

(+ 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)

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

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)

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:

(define format-line
     (lambda (name value)
         (display name)
         (display value)

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

(define display-stats
     (lambda ()                  ; no arguments
         (format-line "pins:     " (apply + (map length all-pins)))
         (format-line "packages: " (length packages))
         (format-line "nets:     " (length all-unique-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

(define stats
     (lambda (filename)
         (set-current-output-port (open-output-file filename))

;; Collect and output the statistics

(define display-stats
     (lambda ()                  ; no arguments
         (format-line "pins:     " (apply + (map length all-pins)))
         (format-line "packages: " (length packages))
         (format-line "nets:     " (length all-unique-nets))

;; Simple output format

(define format-line
     (lambda (name value)
         (display name)
         (display value)

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.

John Doty              Noqsi Aerospace, Ltd.

geda-user mailing list