[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
welcome.
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
(use-modules (ice-9 readline))
(activate-readline)
(define (devel output-filename)
(scm-style-repl)
)
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 (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)
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:
(+ 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
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:
(define format-line
(lambda (name value)
(display name)
(display value)
(newline)
)
)
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))
(display-stats)
)
)
;; 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)
(newline)
)
)
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.
http://www.noqsi.com/
jpd@xxxxxxxxx
_______________________________________________
geda-user mailing list
geda-user@xxxxxxxxxxxxxx
http://www.seul.org/cgi-bin/mailman/listinfo/geda-user