summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorfpi2018-04-14 18:39:36 +0200
committerfpi2018-04-14 18:44:35 +0200
commitce56a224a87969649d60b8d006c5be9c160fd4ee (patch)
tree33ef212ed07a60ba92ac250ffb47c5fba90cbe5a
parentAdded example showing off external parameters and advanced plotting (diff)
Switched from .org to git of .el & readme
-rw-r--r--.gitignore1
-rw-r--r--ob-spice.el283
-rw-r--r--ob-spice.org671
-rw-r--r--readme.org119
4 files changed, 403 insertions, 671 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..f6acbf1
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+ob-spice.org
diff --git a/ob-spice.el b/ob-spice.el
new file mode 100644
index 0000000..f3dc9c0
--- /dev/null
+++ b/ob-spice.el
@@ -0,0 +1,283 @@
+;;; ob-spice.el --- Babel Functions for spice
+;;; -*- coding: utf-8 -*-
+
+;; License: GPL v3, or any later version
+;;
+;; This file is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 3, or (at your option)
+;; any later version.
+;;
+;; This file is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+;;
+;; You should have received a copy of the GNU General Public License
+;; along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; Org-Babel support for evaluating spice.
+
+;;; Requirements:
+
+;; - ngspice :: http://ngspice.sourceforge.net/
+
+;;; Code:
+
+(require 'ob)
+
+(make-comint "spice" "ngspice")
+
+(defvar org-babel-spice-eoe-indicator ":org_babel_spice_eoe"
+ "String to indicate that evaluation has completed.")
+
+(defvar org-babel-spice-command "ngspice"
+ "Name of command to use for executing ngspice.")
+(defun org-babel-spice-initiate-session (&optional session dir _params)
+ "Initiate a ngspice session.
+Create comint buffer SESSION running ngspice starting in
+default-directory or DIR if specified."
+ (let* ((sessionname (if (or (not session) (string= session "none"))
+ "spice" session))
+ (session (make-comint sessionname org-babel-spice-command)))
+ (if (and dir (file-name-absolute-p dir))
+ ;; absolute dir
+ (comint-simple-send session (format "cd %s" dir))
+ ;; relative dir
+ (comint-simple-send session (format "cd %s" default-directory))
+ session
+ )))
+(defun org-babel-prep-session:spice (session params)
+ "Prepare SESSION according to header arguments in PARAMS."
+ (let ((session (org-babel-spice-initiate-session session))
+ (var-lines (org-babel-variable-assignments:spice params)))
+ (org-babel-comint-in-buffer session
+ (sit-for .5) (goto-char (point-max))
+ (mapc (lambda (var)
+ (insert var) (comint-send-input nil t)
+ (org-babel-comint-wait-for-output session)
+ (sit-for .1) (goto-char (point-max))) var-lines))
+ session))
+(defun org-babel-load-session:spice (session body params)
+ "Load BODY into SESSION."
+ (save-window-excursion
+ (let ((buffer (org-babel-prep-session:spice session params)))
+ (with-current-buffer buffer
+ (goto-char (process-mark (get-buffer-process (current-buffer))))
+ (insert (org-babel-chomp body)))
+ buffer)))
+
+;; helper
+
+(defun org-babel-variable-assignments:spice (params)
+ "Return a list of spice statements to set the variables in PARAMS."
+ (mapcar
+ (lambda (pair)
+ (format "set %s=%s"
+ (car pair)
+ (org-babel-spice-var-to-spice (cdr pair))))
+ (org-babel--get-vars params)))
+(defun org-babel-spice-var-to-spice (var)
+ "Convert VAR into a spice variable."
+ (if (listp var)
+ (concat "( " (mapconcat #'org-babel-spice-var-to-spice var " ") " )")
+ (format "%S" var)))
+
+;; (lambda (text) (setq body (concat text "\n" body)))
+(defun org-babel-spice-vector-search (body vars)
+ "Replace first instance in BODY for all VARS."
+ (mapc (lambda (pair)
+ (if (string-match (format
+ "\\$%s\\[\\([0-9]\\)\\]"
+ (car pair))
+ body)
+ (let ((replacement (nth
+ (string-to-number (match-string 1 body))
+ pair)))
+ (setq body(format "%s%s%s"
+ (substring body 0 (match-beginning
+ 0))
+ replacement
+ (substring body (match-end 0)))))))
+ vars)
+ body
+ )
+
+(defun org-babel-spice-replace-vars (body vars)
+ "Expand BODY according to VARS."
+ (let ((old-body ""))
+ ;; replace vector variables preceded by '$' and followed by the
+ ;; index in square brackets starting at 0. Matches without
+ ;; preceding or succeeding spaces.
+ (while (not (string= old-body body))
+ (setq old-body body)
+ (setq body (org-babel-spice-vector-search body vars))
+ )
+ ;; replace any variable names preceded by '$' with the actual
+ ;; value of the variable. Matches only with succeeding space or
+ ;; end of line to prevent namespace limitations.
+ (mapc (lambda (pair)
+ (setq body (replace-regexp-in-string
+ (format "\\$%s\\( \\)\\|\\$%s$" (car pair)
+ (car pair))
+ (format "%s\1" (cdr pair))
+ body)))
+ vars)
+ body))
+(defun org-babel-expand-body:spice (body params)
+ "Expand BODY according to PARAMS, return the expanded body."
+ (let ((vars (org-babel--get-vars params))
+ (prologue (cdr (assq :prologue params)))
+ (epilogue (cdr (assq :epilogue params)))
+ (file (cdr (assq :file params))))
+ (setq body (org-babel-spice-replace-vars body vars))
+ ;; TODO :file stuff ....
+
+ ;; add prologue/epilogue
+ (when prologue (setq body (concat prologue "\n" body)))
+ (when epilogue (setq body (concat body "\n" epilogue)))
+ body))
+
+(defun org-babel-spice-trim-body (body)
+ "Prepare BODY to be used in interactive ngspice session."
+ ;; random control codes after $var inserts
+ (replace-regexp-in-string
+ "" " "
+ ;; .control .endc lines
+ (replace-regexp-in-string
+ "^ *\\.\\(control\\|endc\\) *$" ""
+ ;; comment lines
+ (replace-regexp-in-string
+ "^ *\\*.*$" "" body))))
+(defun org-babel-execute:spice (body params)
+ "Execute a block of Spice code with Babel.
+This function is called by `org-babel-execute-src-block'."
+ (let* (;(body (org-babel-expand-body:spice body params))
+ (gnuplot (cdr (assq :gnuplot params)))
+ (result-params (cdr (assq :result-params params)))
+ (result-type (cdr (assq :result-type params)))
+ (session (org-babel-spice-initiate-session
+ (cdr (assq :session params))
+ (cdr (assq :dir params))))
+ (vars (org-babel--get-vars params))
+ (no-source (cdr (assq :no-source params)))
+ (break-index (if (string-match "^ *\.end *$" body)
+ (match-end 0) 0))
+ ;;vars need to be replaced as they don't work when using source
+ (circuit-body (org-babel-expand-body:spice
+ (substring body 0 break-index)
+ (assq-delete-all :epilogue (copy-alist params))))
+ ;; todo: replace vars. :-( → set vars break when doing something like $file.txt
+ (control-body (org-babel-spice-trim-body (substring body break-index)))
+ (full-control-body (if (not (string= control-body ""))
+ (org-babel-expand-body:generic
+ control-body
+ (assq-delete-all :prologue (copy-alist params))
+ (org-babel-variable-assignments:spice params)) ""))
+ (circuit-file (if circuit-body (org-babel-temp-file "spice-body-" ".cir")))
+ (result))
+
+
+ (message (concat "circuit:\n" circuit-body))
+ (message (concat "\n-----\ncontrol:\n" control-body))
+
+ ;; Source circuit-body
+ (with-temp-file circuit-file (insert circuit-body))
+ ;; Evaluate
+ (setq result (org-babel-spice-evaluate session full-control-body
+ result-type circuit-file result-params))
+
+ ;; TODO deal with temporary files
+
+ ;;(org-babel-eval "ngspice -b " body)
+ ;; Write body to temp file & execute with ngspice comint buffer and ~source file~
+
+
+ ;; TODO read outputs from files
+
+ ;; TODO gnuplot options
+ (if (string= "yes" gnuplot)
+ nil ;return content(!) of gnuplot.plt for :post processing with gnuplot block?
+ nil ;return normal spice output
+ )
+ result
+ ))
+(defun org-babel-spice-source (buffer file)
+ "Source FILE in ngspice process running in BUFFER and return results."
+ (let ((body (concat "source " file)))
+ (org-babel-spice-evaluate buffer body 'value)))
+(defun org-babel-spice-evaluate (buffer body result-type &optional file result-params)
+ "Use BUFFER running ngspice process to eval BODY and return results.
+If RESULT-TYPE equals `output' return all outputs, if it equals
+`value' return only value of last statement. FILE can refer to a
+spice input file that is sourced before BODY execution is started."
+ (let ((eoe-string (format "echo \"%s\"" org-babel-spice-eoe-indicator))
+ (eval-body (if file (concat "source " file "\n" body) "")))
+ (pcase result-type
+ (`output
+ ;; Force session to be ready
+ ;;(org-babel-comint-with-output
+ ;; (buffer org-babel-spice-eoe-indicator t eoe-string)
+ ;; (insert eoe-string) (comint-send-input nil t))
+ ;; Eval body
+ (org-babel-chomp
+ (replace-regexp-in-string
+ "^Current directory: .*\n" ""
+ (replace-regexp-in-string
+ "^\\(ngspice [0-9]+ -> *\n*\\)*" ""
+ (mapconcat
+ #'identity
+ (butlast
+ (cdr
+ (split-string
+ (mapconcat
+ #'org-trim
+ (org-babel-comint-with-output (buffer org-babel-spice-eoe-indicator t eval-body)
+ (mapcar (lambda (line)
+ (insert (org-babel-chomp line)) (comint-send-input nil t))
+ (list eval-body
+ eoe-string
+ "\n")))
+ "\n") "[\r\n]")) 2) "\n"))))
+ )
+ (`value
+ (let ((tmp-file (org-babel-temp-file "spice-")))
+ (org-babel-comint-with-output
+ (buffer org-babel-spice-eoe-indicator t eval-body)
+ (mapcar
+ (lambda (line)
+ (insert (org-babel-chomp line)) (comint-send-input nil t))
+ (append (list eval-body)
+ (list (format "echo !! > %s" tmp-file)
+ (format "echo \"%s\"" org-babel-spice-eoe-indicator)
+ )))
+ (comint-send-input nil t))
+ ;; split result to output multiple comma separated vars as table
+ (let ((result (org-babel-spice-cleanup-result
+ (org-babel-chomp
+ (org-babel-eval-read-file tmp-file)))))
+ (if (or (not (listp result)) (cdr result))
+ result
+ (car result))
+ )))
+ ;;todo: add "smart" result type to display measurements (or echos?) & plot filenames
+ )))
+(defun org-babel-spice-cleanup-result (result)
+ "Cleanup value to return instead of RESULT.
+Commands that write to files return the filename."
+ (let* ((index (if (string-match "^ *[^ ]*" result)
+ (match-end 0) 0))
+ (type (substring result 0 index))
+ (arg (replace-regexp-in-string "^ *[^ ]* \\([^ ]*\\).*" "\\1" result)))
+ (message type)
+ (message arg)
+ (pcase type
+ ((or "wrdata" "write") arg)
+ ("gnuplot" (format "%s.png" arg))
+ ("echo" (split-string (substring result (+ index 1)) ","))
+ (_ result))))
+
+ (provide 'ob-spice)
+;;; ob-spice.el ends here
diff --git a/ob-spice.org b/ob-spice.org
deleted file mode 100644
index e162894..0000000
--- a/ob-spice.org
+++ /dev/null
@@ -1,671 +0,0 @@
-#+PROPERTY: header-args:emacs-lisp :tangle ob-spice.el :results silent
-#+PROPERTY: header-args:org :tangle readme.org :results silent
-* License
-#+BEGIN_SRC emacs-lisp
- ;;; ob-spice.el --- Babel Functions for spice
- ;;; -*- coding: utf-8 -*-
-
- ;; License: GPL v3, or any later version
- ;;
- ;; This file is free software; you can redistribute it and/or modify
- ;; it under the terms of the GNU General Public License as published by
- ;; the Free Software Foundation; either version 3, or (at your option)
- ;; any later version.
- ;;
- ;; This file is distributed in the hope that it will be useful,
- ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
- ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- ;; GNU General Public License for more details.
- ;;
- ;; You should have received a copy of the GNU General Public License
- ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
-
- ;;; Commentary:
-
- ;; Org-Babel support for evaluating spice.
-
- ;;; Requirements:
-
- ;; - ngspice :: http://ngspice.sourceforge.net/
-
- ;;; Code:
-
- (require 'ob)
-#+END_SRC
-* Auto-tangle
-#+BEGIN_SRC emacs-lisp :tangle no
-(defun my/tangle-ob-spice ()
- "If the current file is '~/.emacs.d/lisp/ob-spice/ob-spice.org', the code blocks are tangled"
- (when (equal buffer-file-name (concat (getenv "HOME") "/.emacs.d/lisp/ob-spice/ob-spice.org"))
- (org-babel-tangle)
- (message "%s tangled" buffer-file-name)))
-
-(add-hook 'after-save-hook #'my/tangle-ob-spice)
-#+END_SRC
-* Functionality
-
-- Temporary files in working directory are accepted (as they are
- common in ngspice)
-- With no :file specified try to direct all outputs to a /tmp directory
-- option to return content of generated gnuplot .plt for easy use with
- :post {generic gnuplot block}
- - Full .plt or just data points?
-** Use cases
-*** DONE just load some circuit
-#+BEGIN_SRC spice :results output
-,* RC
-r1 1 0 10k
-c1 1 0 1p
-.IC V(1)=1
-.tran 1n 0.1u
-.end
-#+END_SRC
-
-#+RESULTS:
-: Circuit: * rc
-
-*** DONE execute some stuff
-#+BEGIN_SRC spice
-echo "Hello world"
-#+END_SRC
-#+RESULTS:
-: Hello world
-
-#+BEGIN_SRC spice
-echo "0,1,2,3"
-#+END_SRC
-#+RESULTS:
-| 0 | 1 | 2 | 3 |
-
-
-*** DONE plot some voltages and return png
-#+BEGIN_SRC spice :var file="/tmp/xzy" :results file
-,*RC circuit
-r1 1 0 10k
-c1 1 0 1p
-
-.IC V(1)=1
-.tran 1n 0.1u
-.end
-
-.control
-run
-set gnuplot_terminal=png
-gnuplot $file v(1)
-.endc
-#+END_SRC
-#+RESULTS:
-[[file:/tmp/xzy.png]]
-
-*** DONE do measurements and return results
-#+BEGIN_SRC spice :session spicetest :results value
-,*Time Constant Measurement
-r1 1 0 10k
-c1 1 0 1p
-
-.IC V(1)=1
-.tran 1n 0.1u
-.print tran v(1)
-.end
-
-.control
-run
-meas tran value_at_tau find V(1) at=1e-8
-meas tran value_at_five_tau find V(1) at=5e-8
-echo $&value_at_tau ,$&value_at_five_tau
-.endc
-#+END_SRC
-#+RESULTS:
-| 0.36798 | 0.00671732 |
-
-*** DONE write simulation data to file and return file name(s?)
-#+BEGIN_SRC spice :var file="/tmp/xyz" :results file :post plot_stuff(data=*this*)
-,*RC circuit
-r1 1 0 10k
-c1 1 0 1p
-.IC V(1)=1
-.tran 1n 0.1u
-.end
-.control
-run
-wrdata $file v(1)
-.endc
-#+END_SRC
-#+RESULTS:
-[[file:/tmp/xyz_plot.png]]
-
-#+NAME: plot_stuff
-#+BEGIN_SRC gnuplot :var data="x" :file "/tmp/xyz_plot.png" :results silent
-load '~/.gnuplot_settings.plt'
-plot data u 1:2 w l ls 1
-#+END_SRC
-
-** [1/6] Flags
-*** NEXT [#C] :netlist / :circuit
-Name of src block to include for netlist
-*** DONE [#B] :dir
-working directory to start ngspice in
-*** NEXT [#B] :file?
-default filename to use for
-*** NEXT :gnuplot
-name of gnuplot block or .plt file: handle plotting instead of gnuplot
-lines
-*** NEXT :output
-**** `smart
-display measurements (or echos?) & plot filenames
-*** NEXT :batch / :no-interactive
-use batch mode
-** DONE Variable replacement in spice body
-** INPROGRESS Execution using ngspice
-*** NEXT Steps to using interactive mode
-- Output to a rawfile dumps all node data: ~ngspice -b -r x.raw
- y.cir~. Can be loaded in interactive mode with ~load filename~.
-- Run file in interactive mode syncronously ~source input-file~ or
- asyncronously ~aspice input-file~
-**** Comint mode to run ngspice in a buffer
-#+BEGIN_SRC emacs-lisp
- (make-comint "spice" "ngspice")
-#+END_SRC
-** PLANNING (Auto-)Plotting
-* Readme.org
-#+BEGIN_SRC org
-,* Overview
-
-Extends org-babel capabilities to support spice simulations using
-ngspice. Simulations are executed using an interactive ngspice process
-running in emacs. The running ngspice process can be used to
-manipulate the simulation results directly or spread a simulation into
-multiple src blocks.
-
-Spice source blocks are interpreted as circuit descriptions until an
-'.end' line is encountered. After that a control part may follow
-(surrounded by '.control' and '.endc' lines). *If there is no '.end'*
-,*line all of the src block is interpreted as a control block!*
-
-,* Use cases
-,*** Just load some circuit for later simulation:
-,#+BEGIN_SRC spice :results output
-,,* RC
-r1 1 0 10k
-c1 1 0 1p
-.IC V(1)=1
-.tran 1n 0.1u
-.end
-,#+END_SRC
-,#+RESULTS:
-: Circuit: * rc
-
-,*** Execute some stuff
-,#+BEGIN_SRC spice
-echo "Hello world"
-,#+END_SRC
-,#+RESULTS:
-: Hello world
-
-,#+BEGIN_SRC spice
-echo "0,1,2,3"
-,#+END_SRC
-,#+RESULTS:
-| 0 | 1 | 2 | 3 |
-
-,*** Plot some voltages and return png
-,#+BEGIN_SRC spice :var file="/tmp/xzy" :results file
-,,*RC circuit
-r1 1 0 10k
-c1 1 0 1p
-
-.IC V(1)=1
-.tran 1n 0.1u
-.end
-
-.control
-run
-set gnuplot_terminal=png
-gnuplot $file v(1)
-.endc
-,#+END_SRC
-
-,#+RESULTS:
-[[file:/tmp/xzy.png]]
-
-,*** Do measurements and return results
-,#+BEGIN_SRC spice :session spicetest :results value
-,,*Time Constant Measurement
-r1 1 0 10k
-c1 1 0 1p
-
-.IC V(1)=1
-.tran 1n 0.1u
-.print tran v(1)
-.end
-
-.control
-run
-meas tran value_at_tau find V(1) at=1e-8
-meas tran value_at_five_tau find V(1) at=5e-8
-echo $&value_at_tau ,$&value_at_five_tau
-.endc
-,#+END_SRC
-
-,#+RESULTS:
-| 0.36798 | 0.00671732 |
-
-,*** write simulation data to file and return file name
-,#+BEGIN_SRC spice :var file="/tmp/xyz" :post plot_stuff(data=*this*) :results file
-,,*RC circuit
-r1 1 0 10k
-c1 1 0 1p
-.IC V(1)=1
-.tran 1n 0.1u
-.end
-.control
-run
-wrdata $file v(1)
-.endc
-,#+END_SRC
-
-,#+RESULTS:
-[[file:/tmp/xyz_plot.png]]
-
-,#+NAME: plot_stuff
-,#+BEGIN_SRC gnuplot :var data="x" :file "/tmp/xyz_plot.png" :results silent
-plot data u 1:2 w l ls 1
-,#+END_SRC
-
-,* Flags
-,** :netlist / :circuit
-Name of a src block to include for netlist/circuit descriptions.
-,** :dir
-Working directory to run the src block in. Default is the value of `default-directory'.
-,** :file?
-
-,** :gnuplot
-Name of gnuplot block or .plt file: handle plotting instead of gnuplot
-lines
-,** :results
-Available result options are `value' which returns the output of the
- last expression, `output' which returns all output and `smart' which
- tries to only display echos and plot filenames.
-,** :batch / :no-interactive
-use batch mode
-#+END_SRC
-* Code
-
-#+BEGIN_SRC emacs-lisp :tangle no
-(add-to-list 'load-path "~/.emacs.d/lisp/ob-spice")
-#+END_SRC
-** vars
-#+BEGIN_SRC emacs-lisp
-(defvar org-babel-spice-eoe-indicator ":org_babel_spice_eoe"
- "String to indicate that evaluation has completed.")
-#+END_SRC
-** Session handling
-#+BEGIN_SRC emacs-lisp
-(defvar org-babel-spice-command "ngspice"
- "Name of command to use for executing ngspice.")
-(defun org-babel-spice-initiate-session (&optional session dir _params)
- "Initiate a ngspice session.
-Create comint buffer SESSION running ngspice starting in
-default-directory or DIR if specified."
- (let* ((sessionname (if (or (not session) (string= session "none"))
- "spice" session))
- (session (make-comint sessionname org-babel-spice-command)))
- (if (and dir (file-name-absolute-p dir))
- ;; absolute dir
- (comint-simple-send session (format "cd %s" dir))
- ;; relative dir
- (comint-simple-send session (format "cd %s" default-directory))
- session
- )))
-(defun org-babel-prep-session:spice (session params)
- "Prepare SESSION according to header arguments in PARAMS."
- (let ((session (org-babel-spice-initiate-session session))
- (var-lines (org-babel-variable-assignments:spice params)))
- (org-babel-comint-in-buffer session
- (sit-for .5) (goto-char (point-max))
- (mapc (lambda (var)
- (insert var) (comint-send-input nil t)
- (org-babel-comint-wait-for-output session)
- (sit-for .1) (goto-char (point-max))) var-lines))
- session))
-(defun org-babel-load-session:spice (session body params)
- "Load BODY into SESSION."
- (save-window-excursion
- (let ((buffer (org-babel-prep-session:spice session params)))
- (with-current-buffer buffer
- (goto-char (process-mark (get-buffer-process (current-buffer))))
- (insert (org-babel-chomp body)))
- buffer)))
-
-;; helper
-
-(defun org-babel-variable-assignments:spice (params)
- "Return a list of spice statements to set the variables in PARAMS."
- (mapcar
- (lambda (pair)
- (format "set %s=%s"
- (car pair)
- (org-babel-spice-var-to-spice (cdr pair))))
- (org-babel--get-vars params)))
-(defun org-babel-spice-var-to-spice (var)
- "Convert VAR into a spice variable."
- (if (listp var)
- (concat "( " (mapconcat #'org-babel-spice-var-to-spice var " ") " )")
- (format "%S" var)))
-#+END_SRC
-** NEXT Variable handling & expand body
-Don't replace variable calls in body. Instead set them in the spice
-session: ~set x=4~\\
-~set~ only sets lowercase variants of words!
-
-To set lists/arrays: ~set x=( 1 2 3 4 )~. Whitespace is important!
-Access with ~$x[0]~ or ~$x[2-len]~.
-#+BEGIN_SRC emacs-lisp
-;; (lambda (text) (setq body (concat text "\n" body)))
-(defun org-babel-spice-vector-search (body vars)
- "Replace first instance in BODY for all VARS."
- (mapc (lambda (pair)
- (if (string-match (format
- "\\$%s\\[\\([0-9]\\)\\]"
- (car pair))
- body)
- (let ((replacement (nth
- (string-to-number (match-string 1 body))
- pair)))
- (setq body(format "%s%s%s"
- (substring body 0 (match-beginning
- 0))
- replacement
- (substring body (match-end 0)))))))
- vars)
- body
- )
-#+END_SRC
-#+BEGIN_SRC emacs-lisp
-(defun org-babel-spice-replace-vars (body vars)
- "Expand BODY according to VARS."
- (let ((old-body ""))
- ;; replace vector variables preceded by '$' and followed by the
- ;; index in square brackets starting at 0. Matches without
- ;; preceding or succeeding spaces.
- (while (not (string= old-body body))
- (setq old-body body)
- (setq body (org-babel-spice-vector-search body vars))
- )
- ;; replace any variable names preceded by '$' with the actual
- ;; value of the variable. Matches only with succeeding space or
- ;; end of line to prevent namespace limitations.
- (mapc (lambda (pair)
- (setq body (replace-regexp-in-string
- (format "\\$%s\\( \\)\\|\\$%s$" (car pair)
- (car pair))
- (format "%s\1" (cdr pair))
- body)))
- vars)
- body))
-(defun org-babel-expand-body:spice (body params)
- "Expand BODY according to PARAMS, return the expanded body."
- (let ((vars (org-babel--get-vars params))
- (prologue (cdr (assq :prologue params)))
- (epilogue (cdr (assq :epilogue params)))
- (file (cdr (assq :file params))))
- (setq body (org-babel-spice-replace-vars body vars))
- ;; TODO :file stuff ....
-
- ;; add prologue/epilogue
- (when prologue (setq body (concat prologue "\n" body)))
- (when epilogue (setq body (concat body "\n" epilogue)))
- body))
-#+END_SRC
-** ob-execute
-#+BEGIN_SRC emacs-lisp
-(defun org-babel-spice-trim-body (body)
- "Prepare BODY to be used in interactive ngspice session."
- ;; random control codes after $var inserts
- (replace-regexp-in-string
- "" " "
- ;; .control .endc lines
- (replace-regexp-in-string
- "^ *\\.\\(control\\|endc\\) *$" ""
- ;; comment lines
- (replace-regexp-in-string
- "^ *\\*.*$" "" body))))
-(defun org-babel-execute:spice (body params)
- "Execute a block of Spice code with Babel.
-This function is called by `org-babel-execute-src-block'."
- (let* (;(body (org-babel-expand-body:spice body params))
- (gnuplot (cdr (assq :gnuplot params)))
- (result-params (cdr (assq :result-params params)))
- (result-type (cdr (assq :result-type params)))
- (session (org-babel-spice-initiate-session
- (cdr (assq :session params))
- (cdr (assq :dir params))))
- (vars (org-babel--get-vars params))
- (no-source (cdr (assq :no-source params)))
- (break-index (if (string-match "^ *\.end *$" body)
- (match-end 0) 0))
- ;;vars need to be replaced as they don't work when using source
- (circuit-body (org-babel-expand-body:spice
- (substring body 0 break-index)
- (assq-delete-all :epilogue (copy-alist params))))
- ;; todo: replace vars. :-( → set vars break when doing something like $file.txt
- (control-body (org-babel-spice-trim-body (substring body break-index)))
- (full-control-body (if (not (string= control-body ""))
- (org-babel-expand-body:generic
- control-body
- (assq-delete-all :prologue (copy-alist params))
- (org-babel-variable-assignments:spice params)) ""))
- (circuit-file (if circuit-body (org-babel-temp-file "spice-body-" ".cir")))
- (result))
-
-
- (message (concat "circuit:\n" circuit-body))
- (message (concat "\n-----\ncontrol:\n" control-body))
-
- ;; Source circuit-body
- (with-temp-file circuit-file (insert circuit-body))
- ;; Evaluate
- (setq result (org-babel-spice-evaluate session full-control-body
- result-type circuit-file result-params))
-
- ;; TODO deal with temporary files
-
- ;;(org-babel-eval "ngspice -b " body)
- ;; Write body to temp file & execute with ngspice comint buffer and ~source file~
-
-
- ;; TODO read outputs from files
-
- ;; TODO gnuplot options
- (if (string= "yes" gnuplot)
- nil ;return content(!) of gnuplot.plt for :post processing with gnuplot block?
- nil ;return normal spice output
- )
- result
- ))
-(defun org-babel-spice-source (buffer file)
- "Source FILE in ngspice process running in BUFFER and return results."
- (let ((body (concat "source " file)))
- (org-babel-spice-evaluate buffer body 'value)))
-(defun org-babel-spice-evaluate (buffer body result-type &optional file result-params)
- "Use BUFFER running ngspice process to eval BODY and return results.
-If RESULT-TYPE equals `output' return all outputs, if it equals
-`value' return only value of last statement. FILE can refer to a
-spice input file that is sourced before BODY execution is started."
- (let ((eoe-string (format "echo \"%s\"" org-babel-spice-eoe-indicator))
- (eval-body (if file (concat "source " file "\n" body) "")))
- (pcase result-type
- (`output
- ;; Force session to be ready
- ;;(org-babel-comint-with-output
- ;; (buffer org-babel-spice-eoe-indicator t eoe-string)
- ;; (insert eoe-string) (comint-send-input nil t))
- ;; Eval body
- (org-babel-chomp
- (replace-regexp-in-string
- "^Current directory: .*\n" ""
- (replace-regexp-in-string
- "^\\(ngspice [0-9]+ -> *\n*\\)*" ""
- (mapconcat
- #'identity
- (butlast
- (cdr
- (split-string
- (mapconcat
- #'org-trim
- (org-babel-comint-with-output (buffer org-babel-spice-eoe-indicator t eval-body)
- (mapcar (lambda (line)
- (insert (org-babel-chomp line)) (comint-send-input nil t))
- (list eval-body
- eoe-string
- "\n")))
- "\n") "[\r\n]")) 2) "\n"))))
- )
- (`value
- (let ((tmp-file (org-babel-temp-file "spice-")))
- (org-babel-comint-with-output
- (buffer org-babel-spice-eoe-indicator t eval-body)
- (mapcar
- (lambda (line)
- (insert (org-babel-chomp line)) (comint-send-input nil t))
- (append (list eval-body)
- (list (format "echo !! > %s" tmp-file)
- (format "echo \"%s\"" org-babel-spice-eoe-indicator)
- )))
- (comint-send-input nil t))
- ;; split result to output multiple comma separated vars as table
- (let ((result (org-babel-spice-cleanup-result
- (org-babel-chomp
- (org-babel-eval-read-file tmp-file)))))
- (if (or (not (listp result)) (cdr result))
- result
- (car result))
- )))
- ;;todo: add "smart" result type to display measurements (or echos?) & plot filenames
- )))
-(defun org-babel-spice-cleanup-result (result)
- "Cleanup value to return instead of RESULT.
-Commands that write to files return the filename."
- (let* ((index (if (string-match "^ *[^ ]*" result)
- (match-end 0) 0))
- (type (substring result 0 index))
- (arg (replace-regexp-in-string "^ *[^ ]* \\([^ ]*\\).*" "\\1" result)))
- (message type)
- (message arg)
- (pcase type
- ((or "wrdata" "write") arg)
- ("gnuplot" (format "%s.png" arg))
- ("echo" (split-string (substring result (+ index 1)) ","))
- (_ result))))
-
- (provide 'ob-spice)
-;;; ob-spice.el ends here
-#+END_SRC
-
-* Tests
-#+BEGIN_SRC spice :var x="4" :session spicetest :no-source yes
-echo "Hello World"
-#+END_SRC
-
-#+RESULTS:
-: Hello World
-
-
-#+BEGIN_SRC spice :var file="/tmp/spice_test" :session spicetest :results value
-,*Time Constant Measurement
-r1 1 0 10k
-c1 1 0 1p
-
-.IC V(1)=1
-.tran 1n 0.1u
-.print tran v(1)
-.end
-
-.control
-run
-set gnuplot_terminal=png
-,*gnuplot $file v(1)
-meas tran value_at_tau find V(1) at=1e-8
-meas tran value_at_five_tau find V(1) at=5e-8
-echo value_at_tau = "$&value_at_tau" > $file.txt
-,* Any better way to write one value of vector to a file??
-echo $&value_at_tau ,$&value_at_five_tau
-.endc
-#+END_SRC
-
-#+RESULTS:
-| 0.36798 | 0.00671732 |
-
-#+BEGIN_SRC spice :var file="/tmp/spice_test2" :session spicetest :results file
-,*Virtual Ground Test: opamp gain = 1000
-vin in 0 dc 0V sin(0 .1 100Hz)
-r1 in inn 10k
-r2 inn out 10k
-EOpamp out 0 0 inn 1000
-.tran 0.1ms 0.05s
-.print tran v(in)
-.meas tran vtest find v(in) at=0.04e-3
-.end
-.control
-run
-set gnuplot_terminal=png
-gnuplot $file v(in) v(out) v(inn)
-.endc
-#+END_SRC
-
-#+RESULTS:
-[[file:/tmp/spice_test2]]
-
-** advanced example
-#+NAME: dim1_params
-#+BEGIN_SRC ruby :exports none
-[7.3260073260073255e-06,1.3215597400496679e-05,5.411787135503391e-06,2.344322344322345e-05,2.4e-06, 100.0]
-#+END_SRC
-#+RESULTS: dim1_params
-| 7.3260073260073255e-06 | 1.3215597400496679e-05 | 5.411787135503391e-06 | 2.344322344322345e-05 | 2.4e-06 | 100.0 |
-
-#+NAME: dim1_plot
-#+BEGIN_SRC gnuplot :var data="x" :file /tmp/ignored.png :results silent :exports none
- set terminal pngcairo size 640,300 enhanced
- set format y "%.0s%cV"
- set format x "%.0s%cs"
- set xrange [190e-9:260e-9]
- set ytics 5e-3
- set xlabel "t"
- plot data using 1:2 w l ls 2 t "V_{out}"
-#+END_SRC
-
-#+BEGIN_SRC spice :session test :var dim=dim1_params :results output
-,* DAC.asc
-.model NMOS NMOS
-.model PMOS PMOS
-.lib /home/fred/spice/library/ngspice/CM5/CM5-N.phy CM5
-M1 0 VDD N005 N005 MP7 l= $dim[5] w= $dim[4]
-M2 VOUT VCTRL N005 N005 MP7 l= $dim[5] w= $dim[4]
-M4 N003 N002 VDD VDD MP7 l= $dim[3] w= $dim[2]
-M3 N002 N002 VDD VDD MP7 l= $dim[3] w= $dim[2]
-M5 N005 N004 N003 N003 MP7 l= $dim[3] w= $dim[2]
-M6 N004 N004 N002 N002 MP7 l= $dim[3] w= $dim[2]
-RL VOUT 0 $dim[6]
-CL VOUT 0 10p
-VDD VDD 0 5
-IREF N004 0 7.3u
-V1 VBIAS 0 2.5
-V2 VCTRL 0 PULSE(0 5 0 1n 1n 200n 420n)
-VM VDD N001 0
-.tran 1n 300n 190n
-.end
-#+END_SRC
-
-#+RESULTS:
-: Circuit: * dac.asc
-
-#+NAME: dim1
-#+BEGIN_SRC spice :session test :var file="/tmp/dim1" dim=dim1_params :post dim1_plot[:file /tmp/dim1.png](data=*this*) :results file
-save all
-run
-wrdata $file v(vout)
-#+END_SRC
-
-#+RESULTS: dim1
-[[file:/tmp/dim1.png]]
diff --git a/readme.org b/readme.org
new file mode 100644
index 0000000..b193b88
--- /dev/null
+++ b/readme.org
@@ -0,0 +1,119 @@
+* Overview
+
+Extends org-babel capabilities to support spice simulations using
+ngspice. Simulations are executed using an interactive ngspice process
+running in emacs. The running ngspice process can be used to
+manipulate the simulation results directly or spread a simulation into
+multiple src blocks.
+
+Spice source blocks are interpreted as circuit descriptions until an
+'.end' line is encountered. After that a control part may follow
+(surrounded by '.control' and '.endc' lines). *If there is no '.end'*
+*line all of the src block is interpreted as a control block!*
+
+* Use cases
+*** Just load some circuit for later simulation:
+#+BEGIN_SRC spice :results output
+,* RC
+r1 1 0 10k
+c1 1 0 1p
+.IC V(1)=1
+.tran 1n 0.1u
+.end
+#+END_SRC
+#+RESULTS:
+: Circuit: * rc
+
+*** Execute some stuff
+#+BEGIN_SRC spice
+echo "Hello world"
+#+END_SRC
+#+RESULTS:
+: Hello world
+
+#+BEGIN_SRC spice
+echo "0,1,2,3"
+#+END_SRC
+#+RESULTS:
+| 0 | 1 | 2 | 3 |
+
+*** Plot some voltages and return png
+#+BEGIN_SRC spice :var file="/tmp/xzy" :results file
+,*RC circuit
+r1 1 0 10k
+c1 1 0 1p
+
+.IC V(1)=1
+.tran 1n 0.1u
+.end
+
+.control
+run
+set gnuplot_terminal=png
+gnuplot $file v(1)
+.endc
+#+END_SRC
+
+#+RESULTS:
+[[file:/tmp/xzy.png]]
+
+*** Do measurements and return results
+#+BEGIN_SRC spice :session spicetest :results value
+,*Time Constant Measurement
+r1 1 0 10k
+c1 1 0 1p
+
+.IC V(1)=1
+.tran 1n 0.1u
+.print tran v(1)
+.end
+
+.control
+run
+meas tran value_at_tau find V(1) at=1e-8
+meas tran value_at_five_tau find V(1) at=5e-8
+echo $&value_at_tau ,$&value_at_five_tau
+.endc
+#+END_SRC
+
+#+RESULTS:
+| 0.36798 | 0.00671732 |
+
+*** write simulation data to file and return file name
+#+BEGIN_SRC spice :var file="/tmp/xyz" :post plot_stuff(data=*this*) :results file
+,*RC circuit
+r1 1 0 10k
+c1 1 0 1p
+.IC V(1)=1
+.tran 1n 0.1u
+.end
+.control
+run
+wrdata $file v(1)
+.endc
+#+END_SRC
+
+#+RESULTS:
+[[file:/tmp/xyz_plot.png]]
+
+#+NAME: plot_stuff
+#+BEGIN_SRC gnuplot :var data="x" :file "/tmp/xyz_plot.png" :results silent
+plot data u 1:2 w l ls 1
+#+END_SRC
+
+* Flags
+** :netlist / :circuit
+Name of a src block to include for netlist/circuit descriptions.
+** :dir
+Working directory to run the src block in. Default is the value of `default-directory'.
+** :file?
+
+** :gnuplot
+Name of gnuplot block or .plt file: handle plotting instead of gnuplot
+lines
+** :results
+Available result options are `value' which returns the output of the
+ last expression, `output' which returns all output and `smart' which
+ tries to only display echos and plot filenames.
+** :batch / :no-interactive
+use batch mode