To build Post, type "make depend" and then "make". If you like what you see, then run "make install" with sufficient permission to write in "/usr/local/bin".

Post does it's graphing with gnuplot() which you will need to install first.

Run post with "post". You'll get a ":" prompt in your terminal window. There should be command line editing available. I think emacs is the default, I switch it to "vi" mode by having a .inputrc file in my home directory which contains:

# this is the gnu-readline configuration file set editing-mode vi set keymap vi

If you have written some nice waveform definitions for post you can put them in file and load them at start up time with "post mydefs -". Post will load all listed files into memory. The final "-" drops you into interactive mode after loading everything.

Of course, the most important feature is loading spice raw files. You can get a list of loadable spice raw files with "ls".

You can load a rawfile with

: ci "file"

The filename must be in quotes. You can list the loaded variables with "di".

Numbers can be real or complex. All numbers are cast to double precision. Engineering notation is supported.

Starting with A,a for "atto" or 10^-18, permitted suffixes are: F,f (10^-15), P,p (10-^12), N,n (10^-9), U,u (10^e-6), "%" (10e-2), k,K (10^3), MEG,meg (10^6), G,g (10^9) and T,t (10^12).

Some examples of valid numbers are:

.1 1.0 -1.3e-7 1f 10P 8meg

The variable name "i" and "I" are both equal to the imaginary number sqrt(-1). You can create a pure imaginary number with "3i", or "3*i". Mixed numbers are written as "1+i" or 1+3*i" or "1+3i".

Examples: :i i :3i 3i :3n*i 3e-09i :1p+i 1e-12+i

Piecewise linear waveforms can be defined by :a = {0,0; 1,4; 3,4; 4,3}

a list of independant value, dependant value pairs. It is required that the independant value be in strict monotonic rising order. For many engineering applications the independant variable will be either time or frequency, however it can also represent space, or any other parameter such as a resistor values in circuit.

As implemented, a PWL can be thought of as both a data-type and a function. if a is defined as above then a(1) will return 4. a(2) also returns 4 by interpolation. a(sqrt(4)) will return 4.0. This functional notation replaces the yvalue() function of HP's post.

Expressions are fairly obvious, read below for details. You can graph any number of expressions on the same graph with "gr".

: gr a,b,c

You can plot two groups of variables on separate axes with

: gr a,b,c ; d, e, f

You can limit the xaxis range with "xl

: gr a,b ; d,e xl .1,1.8

Finally, each graph can have its yaxis range set with "yl

: gr a,b yl -1,1; d,e yl -2,2 xl .1,1.8

The rule is that each expression or PWL name must be separated from any other PWL or expression by either a "," or a ";". A semicolon creates second plot.

Piecewise linear (PWL) variables can be treated like normal scalars. Generally post will do the obvious, most useful thing. Adding two PWLs with

: c=a+b

will add them point by point, cross-interpolating where necessary. The output PWL will always include a value at every independant variable point defined in either of the input PWLs.

The output PWL is defined only at the intersection of the of the span of each PWL's independant variable. For instance, you can define two PWL's with

: a = {0,0; 1,1} : b = {0.5,1; 1.5,2}

and the addition of a,b yields

: pr a+b { 0.5,1 1,1.5 }

Which is only defined over the overlap of a,b.

There is currently only one fundamental data type in post(). This is the DATUM which is currently defined as a doubly linked list:

typedef struct datum { double iv; /* independant variable, usually time or freq */ double re; /* real part */ double im; /* imaginary part */ struct datum *next; struct datum *prev; } DATUM

Simple scalars like "0.0", "1+2i", "-4i" are just DATUMS with *next pointing to NULL. A piece-wise linear (PWL) such as "a = {0,0; 1,1; 2,i}" is implemented as a doubly-linked list of datums defined in such a way that appending new items is fast. (this is done by keeping the *prev link of the first element pointing at the last element so we don't have to walk the list to add a new element to the end).

In the summaries below, a single scalar is notated as "d" and a PWL list is notated as "p".

In general, when a math operation is performed on two PWLs, the operation is done point by point with cross interpolation whenever the two PWLs differ in their independant variables. A math operation between a PWL and a scalar DATUM generally takes the DATUM value as applying over all time or frequency.

Operations between two scalar DATUMs is just the ordinary math that one would expect.

In cases where complex definitions are awkward, the real value is used. An example is the max(p1,p2) function. In most cases, it is expected that it is the maximum real value that is compared. Although only the real value is compared, the entire complex value of the maximum segment is copied to the output. This allows the max/min functions to be used as a multiplexer.

/* given two real signals siga, sigb, and select signal */ /* "mux" that is greater than 0 when we want to select siga, */ /* a multiplexor can be implemented as: */ a=re(siga)*i+re(mux) b=re(sigb)*i+0.0 output = i*max(a,b)

In this summary, names like "p, p1, p2,..." refer to piecewise linear waveforms. Names like "d, d1, d2..." refer to simple scalars. Individual PWL datapoints are referred to as "p(k)". Since both scalars and PWL's can be complex, the real and imaginary parts are referred to as p(k).re, or d.re and p(k).im or d.im. In most cases, math between a PWL and a scalar is handled as if the scalar was a constant valued PWL defined from plus to minus infinity. When an algorithm is handled point by point, it is described below as pn(k) = f(p1(k), p2(k), ...) where k is understood to be evaluated at the union of timepoints defined by the set of all independent PWLs. Finally, the output of any operation is truncated to the range of times that are spanned by all the input waveforms.

p3=avg(p1,p2) ;p3(k) = (p1(k)+p2(k))/2 d3=avg(d1,d2) ;d3 = (d1+d2)/2 p3=avg(d1,p1) ;p3(k) = (p1(k)+d1)/2 p3=avg(p1,d1) ;p3(k) = (p1(k)+d1)/2 d=avg(p) ;compute average value of a single PWL

p=db(p) ;return 20*log10(mag(p)) d=db(d) ;return 20*log10(mag(d))

p2=dt(p1) ;uses global variable DT, default = 1u;p2 = (warp(p1,-DT/2)-warp(p1,DT/2))/DT

p2=exp(p1) ;p2(k) = e^p1(k)

p2=integral(p1) ;return the running integral of p1*dt ;definite integral from a to b = p2(b)-p2(a)

p2=ln(p1) ;p2(k) = ln(p1(k)) p2=log10(p1) ;p2(k) = ln(p1(k))/ln(10) p2=log(p1) ;p2(k) = ln(p1(k))/ln(10)

p2=mag(p1) ;p2(k) = sqrt( (p1(k).re)^2 + (p1(k).im)^2 )

p3=max(p1,p2) ;p3 = (p1(k).re > p2(k).re)?p1(k):p2(k) d=max(p1) ;find k where p1(k).re is maximum, return p1(k) ;note: decision is made on real value, but ;complex value is returned.

min(p1,p2) ;p3 = (p1(k).re < p2(k).re)?p1(k):p2(k) d=min(p1) ;find k where p1(k).re is minimum, return p1(k) ;note: decision is made on real value, but ;complex value is returned.

pause() ;post will sleep(2) for expression.re seconds ;if an interrupt (usually ^C) is received, the ;pause will be aborted, returning to normal ;command flow. Pause is handy inside a script ;for putting between graph commands so the user ;can page through multiple graph results with ^C. ;pause() returns the number of seconds remaining ;to wait. If you don't want this number printed ;then assign it to a scratch variable eg: ;"tmp=pause(10000)"

p2=pha(p) ;p2(k) = atan2(p(k).im, p(k).re) d2=pha(d) ;d2 = atan2(d.im, d.re)

p3=pow(p1,p2) ;p3(k) = p1(k)^p2(k) p3=pow(p1,d2) ;p3(k) = p1(k)^d d=pow(d1,d2) ;d = d1^d2 p3=p1^p2 ;alternative ways of computing a^b d3=d1^d2 d3=p1^d1

pr;print the value of expression

gr;graph an expression on a new graph gs ;graph on the same graph (currently not implemented) gn ;graph on next y-graph (currently not implemented) ;for example you can graph a,b on top ;plot and d,c on bottom plot with ; "gr a; gs b; gn c; gs d"

p2=re(p1) ;p2(k) = p1(k).re d2=re(d) ;d2 = d.re p2=im(p1) ;p2(k) = imaginary part of p1(k) d2=im(d) ;d2 = d.im

ls ; list all raw files in the current directory ci "file.raw" ; load the raw file "file". Quotes are required di ; display the names of all loaded variables

p2=sqrt(p1) ; same as pow(p,0.5)

p3=warp(p1,p2) ; use one PWL to phase modulate another ; p3(k+p1(k).re) = p1(k) p3=warp(p1,d) ; timeshift a signal by delay d ; p3(k+d) = p1(k) p3=delay(p1,d) ; delay is a synonym for warp...

p3=xcross(p1,p2) p3=xcrossp(p1,p2) p3=xcrossn(p1,p2) ; let n=(int)p2.re, find nth zero crossing (xcross), ; nth negative-going zero crossing (xcrossn), or ; nth positive going zero crossing (xcrossp) of ; p1(xc).re. set p3.re=xc. If n==0, return all ; zero crossings as a PWL with iv set to n, .re ; set to crossing time. Can then extract nth ; crossing with p3(n). If n is negative, will return ; nth crossing counted from the end of the waveform p2=ui(p1) ; compute the value of the unit interval as a ; function of time: ; for every pair of positive zero crossings in p1, ; create a data point in p2 with iv set to the ; iv of the second zero crossing and the dv setl ; to the time between the two crossings. ; eg: for a VCO, frequency can be approximated by ; 1/ui(vout)

p2 = lpf(p1, tau) ; filters a (possibly unevenly sampled) PWL p1 ; with RC time constant tau. returns a new ; filtered PWL that is evenly sampled in time ; with spacing = tau/16.0. A high-pass coupling ; can be created with "1-lpf()".