diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | ob-spice.el | 283 | ||||
-rw-r--r-- | ob-spice.org | 671 | ||||
-rw-r--r-- | readme.org | 119 |
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 |