;;-------------------------------------------------------------------------------------------------- 
;;                                       gnet-spice-msw.scm
;;                                      --------------------
;; Description : gEDA               - GPL Electronic Design Automation
;;               gnetlist           - gEDA Netlist
;;               gnet-spice-msw.scm - gnetlist SPICE backend
;; Last Update : Refer to version string declaration
;; Authors     : Copyright (C) 1998-2010 Ales Hvezda
;;               Copyright (C) 1998-2010 gEDA Contributors (see ChangeLog for details)
;;               SPICE netlist backend written by S. Gieltjes.
;;               Modified by W. Kazubski to use scaling parameters for devices other than MOSFETS.
;;               Hacked by SDB (Stuart Brorson) to support advanced SPICE netlist generation.
;;               Refactoring and debugging by Mike Waters (2016).
;;--------------------------------------------------------------------------------------------------
;; This program 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 2 of the
;; License, or (at your option) any later version.
;;
;; This program 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.
;;--------------------------------------------------------------------------------------------------
;; New Features :
;;
;;   1. Program is now case insensitive.
;;   2. Refactored code to make it more readable eg. matched indenting of matching parenthesis.
;;   3. In verbose mode the debug spew has been tidied up.
;;   4. Documentation of procedure argument list and return values where appropriate.
;;   5. Documentation of program call options.
;;   6. Fixed a bug where JFETs could be identified as unknown components.
;;   7. Removed unnecessary procedures eg. "write-footer" and "empty-string?".
;;   8. Inserted the contents of spice-common.scm in this file. Easier to debug and maintain.
;;   9. Extend nomunge_mode to include all components eg. resistor, capacitors, etc.
;;  10. Add support for current controlled switches.
;;  11. Procedure "get-file-type" now only stops before EOF if it encounters a .SUBCKT or .MODEL line.
;;--------------------------------------------------------------------------------------------------
;; Usage :
;;
;;   1. Invoke this guile backend from gnetlist :
;;        gnetlist -g spice-msw -o circuit.ckt circuit.sch
;;   2. Send command line arguments to the guile backend :
;;        gnetlist -O <OPTION1> -O <OPTION2> -g spice-msw circuit.sch
;;   2. Send the gnetlist output to standard out :
;;        gnetlist -g spice-msw -o - circuit.sch
;;
;; Options :
;;
;;   include_mode - Enable .INCLUDE directives instead of inserting file contents eg. for model files
;;   nomunge_mode - Enable testing of package prefix and prepending the correct prefix if required
;;   sort_mode    - Enable sorting of packages alphabetically according to refdes's
;;   embedd_mode  - Enable inserting of file contents when a .INCLUDE directive is encountered
;;   no_end_mode  - Disable appending of .END or .ENDS directives at the end of the netlist file
;;--------------------------------------------------------------------------------------------------
;; Organization :
;;
;;  - The program entry point is at the top of the file.
;;  - High-level functions which control program flow.
;;  - Functions for program housekeeping, handling of calling flags, file manipulation.
;;  - Functions for handling nets & devices and creating SPICE cards.
;;--------------------------------------------------------------------------------------------------
;; Things To Do :
;;
;; 2016-10-24 The Josephson Junction procedure "write-joseph-jn" uses the prefix B for the refdes
;;            but in SPICE this is the prefix for a non-linear dependent source.
;; 2016-09-08 Search for "???" to find locations needing attention
;;--------------------------------------------------------------------------------------------------
;; Change Log :
;;
;; 2016-11-05 Removed unecessary space characters from component definition lines in netlist
;; 2016-09-08 Procedure "get-file-type" now overlooks SPICE .DIRECTIVE commands (eg. .PARAM) and
;;            only stop before EOF if it encounters a .SUBCKT or .MODEL line.
;; 2016-10-24 Extend procedure "write-def-cmpnt" to include as many components as possible.
;; 2016-10-24 Removed procedure "get-cmpnt-value", it didn't do enough to justify it's existence.
;; 2016-10-23 Drop support for Guile 1.8.x (gnetlist has dropped support for it).
;; 2016-10-22 Removed procedure "write-footer", unnecessary obfuscation.
;; 2016-10-21 Dropped case sensitivity by using string-ci? instead of string? when testing strings.
;; 2016-10-20 Document procedure argument lists and return values
;; 2016-10-20 Moved to a case insensitive model. This backend should now be less finicky.
;; 2016-10-16 Move the script entry point to the top of the file (seems more logical to me).
;; 2016-09-17 Refactor code : choose more readable procedure names where necessary. 
;; 2016-09-17 Removed procedure "component-model" as it wasn't used.
;; 2016-09-17 Removed procedure "component-optional-value" as it wasn't used.
;; 2016-09-16 Insert contents of spice-common.scm in this file. Easier to debug.
;; 2016-09-12 Removed procedure "empty-string?" and replaced it with call to builtin "string-null?" 
;; 2016-09-10 Refactor code : use short hand procedure definitions (remove lambda lines), 
;; 2016-09-10 Refactor code : match indenting of matched parenthesis.
;; 2016-09-10 Refactor code : general tidy up of file formatting. 
;;--------------------------------------------------------------------------------------------------

;; Define modules needed by this program
(use-modules (ice-9 rdelim))
;;(use-modules (srfi srfi-1))  ;; This is needed to make guile 1.8.x happy

;;--------------------------------------------------------------------------------------------------
;; Get the current version of this Guile script.
;;
;; Note : This procedure definition is to get the version string at the top of the file
;;
;; Return Values :
;;   The current version string.

(define version "2016-11-06")

;;**************************************************************************************************
;;*                                                                                                *
;;*                                      Program Entry Point                                       *
;;*                                                                                                *
;;**************************************************************************************************

;;--------------------------------------------------------------------------------------------------
;; Spice netlist generation entry point.
;;
;; The algorithm is as follows :
;;
;;   1. Determine if there's a .SUBCKT block in the schematic or if it is just a normal schematic.
;;      If there's a .SUBCKT block :
;;       - Write out subcircuit header (a comment identifying the netlister).
;;       - Find all spice-IO pins. Get a list of the packages.
;;       - Put them in order (ordered by package refdes)
;;       - Get the list of nets attached to the spice-IO pins.
;;       - Write out .SUBCKT line
;;      If a normal schematic :
;;       - Write out top header (a comment identifying the netlister).
;;   2. Loop through all components looking for components with a "file" attribute. Every time a
;;      "file" attribute is found :
;;       - Open the file and find out what kind of file it is (.SUBCKT or .MODEL).
;;       - Determine if the file has previously been processed. If not stick the following info.
;;         into the file-info list : (model-name file-name file-type), otherwise just continue.
;;   3. Loop through all components again, and write out a SPICE card for each.
;;   4. Afterwards, for each item in the file-info list, open the file and write it's contents into
;;      the netlist.
;;   5. If the schematic type is .SUBCKT :  write out .ENDS otherwise write out .END
;;   6. Close the SPICE netlist file and return.
;;
;; Argument List :
;;   netlist-filename - The name of the SPICE netlist file

(define (spice-msw netlist-filename)

  ;; Send the script output to the SPICE netlist file
  (set-current-output-port (gnetlist:output-port netlist-filename))

  (let*
    ( (schem-type (spice-msw:get-schem-type packages))
      (model-name (substring schem-type 8 (string-length schem-type)))
      (file-list  (list)) )

    (message "Running the SPICE backend to gnetlist :\n\n")
    
    ;; First decide if this is a .SUBCKT lower level, or if it is a regular schematic
    (if (string=? schem-type "normal schematic")
      ;; This is a regular schematic
      (begin
        (debug-spew "Found a normal type schematic\n\n")
        (display (string-append "* " (gnetlist:get-command-line) "\n"))
        (spice-msw:write-netlist-header)
      )
      ;; This is a .SUBCKT type schematic
      (let*
        ( (io-pin-packages         (spice-msw:get-spice-io-pins  packages (list)))
          (io-pin-packages-ordered (spice-msw:sort-spice-io-pins io-pin-packages))
          (io-nets-list            (spice-msw:get-io-nets        io-pin-packages-ordered (list))) )

        ;; The procedure "debug-spew" is defined in : gnetlist.scm
        (debug-spew "Found a .SUBCKT type schematic\n\n")
        ;; Write out a .SUBCKT header and .SUBCKT line
        (spice-msw:write-subcircuit-header)
        (let
          ( (io-nets-string (list-2-string io-nets-list)) )
          
          (display (string-append schem-type " " io-nets-string "\n"))
        )
      )
    )

    ;; Search all the devices and compile a file list from all the "file" attributes
    (debug-spew "Make first pass through design and create list of all model files referenced :\n")
    (set! file-list (spice-msw:create-file-list packages file-list))
    (debug-spew "\nDone creating file-list\n\n")

    ;; Loop through the file list and write the file contents or a reference to it to the netlist
    (debug-spew "Now process the items in the model file list :\n")
    (spice-msw:process-files file-list)
    (debug-spew "Done processing items in the model file list\n\n")

    ;; Write components to the netlist file (sorting components if required)
    (debug-spew "Make second pass through design and write out a SPICE card for each component found :\n")
    (if (calling-flag? "sort_mode" (gnetlist:get-calling-flags))
      (spice-msw:write-netlist file-list (sort packages spice-msw:package-sort))  ;; Sort on refdes
      (spice-msw:write-netlist file-list packages)                                ;; Don't sort
    )
    (debug-spew "\n")

    ;; Write .END(S) of netlist, depending upon whether schematic is a "normal schematic" or .SUBCKT
    (if (not (string=? schem-type "normal schematic"))
      (display (string-append ".ENDS " model-name "\n"))
      (if (not (calling-flag? "no_end_mode" (gnetlist:get-calling-flags)))
        (display ".END\n")
      )
    )
  )

  (debug-spew "\nDone writing SPICE cards\n\n")

  ;;  Finally, close the netlist file
  (close-output-port (current-output-port))
)

;;**************************************************************************************************
;;*                                                                                                *
;;*                           High-level functions for program control                             *
;;*                                                                                                *
;;**************************************************************************************************

;;--------------------------------------------------------------------------------------------------
;; Determine the schematic type ie. a normal schematic or a .SUBCKT lower level.
;;
;; Search for a "spice-subcircuit-LL" device instantiated somewhere in the schematic. If it is a
;; .SUBCKT return ".SUBCKT model-name" else return "normal schematic".
;;
;; Argument List :
;;   package-list - A list of package (component) labels (refdes's)

(define (spice-msw:get-schem-type package-list)
    
  (if (not (null? package-list))
    (let*
      ( (package (car package-list))
        (device  (gnetlist:get-package-attribute package "device")) )

      (if (string-ci=? device "SPICE-SUBCIRCUIT-LL")      ;; Look for a subcircuit label
        (string-append ".SUBCKT " (gnetlist:get-package-attribute package "model-name"))
        (spice-msw:get-schem-type (cdr package-list))  ;; Iterate to the next package
      )
    )
    "normal schematic"
  )
)

;;--------------------------------------------------------------------------------------------------
;; This function takes as argument the list of packages (refdes's). It runs through the package
;; list, and for each gets the attributes. If there is a "FILE" attribute, it gets the file info &
;; uses it to build the file-list. When done, it returns the file-list.
;;
;; Argument List :
;;   package-list - A list of package (component) labels (refdes's)
;;   file-list    - A list of files referred to in the schematic file

(define (spice-msw:create-file-list package-list file-list)
    
  (if (null? package-list)
    file-list                            ;; Return the file-list
    (let*
      ( (package    (car package-list))  ;; Get the next package (ie. refdes)
        (device     (string))
        (model      (string))
        (value      (string))
        (model-file (string)) )

      (set! device     (gnetlist:get-package-attribute package "device") )
      (set! model      (gnetlist:get-package-attribute package "model-name") )
      (set! value      (gnetlist:get-package-attribute package "value") )
      (set! model-file (gnetlist:get-package-attribute package "file") )

      ;; Now run a series of checks to see if we should stick this file into the file-list
      ;; Check to see if "file" attribute is non-empty
      (if (not (string-ci=? model-file "unknown"))
        (begin
          (debug-spew "\n  spice-msw:create-file-list : ")
          (debug-spew (string-append "Found file attribute for package " package " = " model-file "\n"))

          ;; Now check if the file is already in the file-list
          (if (not (spice-msw:in-file-list? model-file file-list))

            ;; File is new, open it and find out what type it is
            (let
              ( (file-type (spice-msw:get-file-type model-file)) )

              (debug-spew "  spice-msw:create-file-list : ")
              (debug-spew (string-append "File is not in the list and has type " file-type "\n"))

              ;; Check to see if file-type is known.
              (if (not (string-ci=? file-type "OTHER"))
                (begin
                  (debug-spew "  spice-msw:create-file-list : Adding it to the list of model files\n")

                  (set! file-list (append (list (list model model-file file-type)) file-list) )
                )
                (debug-spew "  spice-msw:create-file-list : File type is OTHER so ignore it\n")
              )
            )

            ;; File is already in list. Print debug spew if desired.
            (debug-spew "  spice-msw:create-file-list : File is already in the model file list.\n")
          )
        )
      )

      ;; having done checking and processing of this package, iterate to the next one.
      (spice-msw:create-file-list (cdr package-list) file-list)
    )
  )
)

;;--------------------------------------------------------------------------------------------------
;; This is a helper function which returns #t if a file is already in file-list, otherwise #f.
;;
;; Note : It is assumed that file-list is of the form :
;;   ( (model1 file-name1 file-type1)  (model2 file-name2 file-type2) . . . . )
;;
;; Argument List :
;;   file-name - The file name to search for
;;   file-list - The list of file info. to search
;;
;; Return Values :
;;   Success - #t (the file was    found in the list)
;;   Failure - #f (the file wasn't found in the list)

(define (spice-msw:in-file-list? file-name file-list)

  (if (null? file-list)
    #f                                             ;; file-list is empty
    (let
      ( (list-element (car file-list)) )           ;; Get a list element and process it
      
      (if (null? list-element)
        #f                                         ;; list-element is empty (should never get here)
        (let
          ( (list-file-name (cadr list-element)) )
          
          (if (string=? list-file-name file-name)
            #t                                     ;; file-name was found in file-list
            (spice-msw:in-file-list? file-name (cdr file-list))  ;; Iterate . . .
          )
        )
      )
    )
  )
)

;;--------------------------------------------------------------------------------------------------
;; Search a file and determine it's type based on it's contents.
;;
;; Note : The function opens input-file and closes it when it is done.
;;
;; Argument List :
;;   file-name - The file to test
;;
;; Return Values :
;;   ".MODEL"  - The file contains a model
;;   ".SUBCKT" - The file contains a sub-circuit
;;   "OTHER"   - The file is neither of the above

(define (spice-msw:get-file-type file-name)

  (if (file-exists? file-name)
    (let
      ( (in-file (open-input-file file-name)) )
      
      (let while ( (file-line (read-line in-file)) )
        
        (cond
         
          ((eof-object? file-line)        ;; Arrived at end of line without finding .MODEL or .SUBCKT
            "OTHER"
          )
          
          ((string-null? file-line)       ;; Found empty line, iterate before doing anything else
            (while (read-line in-file))
          )

          ((string=? (string (string-ref file-line 0)) "*")  ;; Found comment, iterate
            (while (read-line in-file))
          )

          ((string=? (string (string-ref file-line 0)) ".")  ;; The first char is a '.'
            (begin
              (cond
                ((string-ci=? (safe-string-head file-line 7) ".SUBCKT")  ;; Found .SUBCKT line
                  ".SUBCKT"
                )
                ((string-ci=? (safe-string-head file-line 6) ".MODEL")   ;; Found .MODEL  line
                  ".MODEL"
                )
                (else
                  (while (read-line in-file))
                )
              )
            )
          )
          
          (else
            (while (read-line in-file))
          )
        )
      )
    )
    (begin
      (message (string-append "ERROR: File '" file-name "' not found.\n"))
      (primitive-exit 1)
    )
  )
)

;;--------------------------------------------------------------------------------------------------
;; Take a list and turns it into a string.
;;
;; The difference between this and list->string is that this function can handle lists made up of
;; multi-char strings.
;;
;; Argument List :
;;   str-list - A list of strings

(define (list-2-string str-list)
    
  (let while ( (st (string)) (local-list str-list) )
    (if (null? local-list)
      st                                                   ;; end iteration & return string if list is empty.
      (begin                                               ;; otherwise turn next element of list into string. . .
        (set! st (string-append (car local-list) " " st))  ;; stuff next element onto st
        (while st (cdr local-list))                        ;; iterate with remainder of ls
      )
    )
  )
)

;;--------------------------------------------------------------------------------------------------
;; Write out spice netlist header.

(define (spice-msw:write-netlist-header)

  (display                "**************************************************\n")
  (display                "*        SPICE file generated by gnetlist        *\n")
  (display (string-append "*      using spice-msw (" version ") backend      *\n"))
  (display                "**************************************************\n\n")
)

;;--------------------------------------------------------------------------------------------------
;; Write out .SUBCKT netlist header.

(define (spice-msw:write-subckt-header)

  (display "*************************************\n")
  (display "*        Begin .SUBCKT model        *\n")
  (display "*************************************\n")
)

;;**************************************************************************************************
;;*                                                                                                *
;;*                Program housekeeping, handling calling flags, file manipulation                 *
;;*                                                                                                *
;;**************************************************************************************************

;;--------------------------------------------------------------------------------------------------
;; Loop through the model-file list looking for a triplet corresponding to a particular model-name.
;;
;; Argument List :
;;   model-name - The model name being sort
;;   file-list  - A list of files referred to in the schematic file
;;
;; Return Values :
;;   Success - The file list item associated with the model name
;;   Failure - #f

(define (spice-msw:get-file-list-item model-name file-list)

  (if (null? file-list)
    (quote #f)
    (let*
      ( (list-item       (car   file-list))
        (item-model-name (car   list-item))
        (item-file-name  (cadr  list-item))
        (item-file-type  (caddr list-item)) )
        
      (if (string-ci=? item-model-name model-name)
         list-item                                                 ;; Found model-name
         (spice-msw:get-file-list-item model-name (cdr file-list)) ;; Keep looking
      )
    )
  )
)

;;--------------------------------------------------------------------------------------------------
;; Loop through the model file list and for each file invoke handle-spice-file.
;;
;; Argument List :
;;   file-list - A list of files referred to in the schematic file

(define (spice-msw:process-files file-list)

  (if (not (null? file-list))
    (let*
      ( (list-element (car  file-list))    (model-name (car   list-element))
        (file-name    (cadr list-element)) (file-type  (caddr list-element)) )

      (spice-msw:process-spice-file file-name)
      (spice-msw:process-files (cdr file-list))
    )
    (display "\n")
  )
)

;;--------------------------------------------------------------------------------------------------
;; This wraps insert-text-file.
;;
;; If "include_mode" has been enabled just write an .INCLUDE card with the file name. If not it
;; call insert-text-file to insert the file's contents into the SPICE netlist.
;;
;; Argument List :
;;   file-name - The file name to be processed

(define (spice-msw:process-spice-file file-name)

  (debug-spew (string-append "\n  spice-msw:process-spice-file : " file-name "\n"))

  (if (calling-flag? "include_mode" (gnetlist:get-calling-flags))
    (display (string-append ".INCLUDE " file-name "\n"))  ;; Use .INCLUDE card
    (spice-msw:insert-text-file file-name)                ;; Insert file contents
  )
)

;;--------------------------------------------------------------------------------------------------
;; Open a file, get the contents and insert it into the SPICE file.
;;
;; This function is usually used to include SPICE models contained in files into the netlist.
;;
;; Argument List :
;;   file-name - The name of the file

(define (spice-msw:insert-text-file file-name)
    
  (if (file-exists? file-name)
    (let
      ( (in-file (open-input-file file-name)) )
      
      (display (string-append "* Include SPICE model file : " file-name "\n") )
      
      (let while ((line (read-line in-file)))
        (if (not (eof-object? line))
          (begin
            (display (string-append line "\n"))
            (while (read-line in-file))
          )
        )
      )
      
      (close-port in-file)
      (newline)
    )
    (begin
      (message (string-append "ERROR: File '" file-name "' not found.\n"))
      (primitive-exit 1)
    )
  )
)

;;--------------------------------------------------------------------------------------------------
;; Iterate through the schematic and compile a list of all SPICE-IO pins found. This is used when
;; writing out a .SUBCKT lower level netlist.
;;
;; Argument List :
;;   package-list - A list of package (component) labels (refdes's)
;;
;; Return Values :
;;   spice-io-pin-list - A list of the SPICE-IO pins

(define (spice-msw:get-spice-io-pins package-list spice-io-pin-list)
    
  (if (null? package-list)
    spice-io-pin-list
    (let*
      ( (package (car package-list))
        (device  (gnetlist:get-package-attribute package "device")) )
      
      (if (string-ci=? device "SPICE-IO")  ;; Look for subcircuit label
         ;; Found a spice-IO pin.
         (spice-msw:get-spice-io-pins (cdr ls) (cons package spice-io-pin-list))
         ;; No spice-IO pin found. Iterate . . . .
         (spice-msw:get-spice-io-pins (cdr ls) spice-io-pin-list)
      )
    )
  )
)

;;--------------------------------------------------------------------------------------------------
;; This takes the list of io-pin-packages and sorts it in order of refdes.
;;
;; Argument List :
;;   package-list - A list of package (component) labels (refdes's)

(define (spice-msw:sort-spice-io-pins package-list)
    
  ;; Yes, this isn't good Scheme form. Tough! Writing this out in a functional programming form
  ;; would be totally confusing! Note that this function requires that each spice-IO pin have the
  ;; same, single character prefix (i.e. 'P')
  (let*
    ( (char-prefixes              (map car (map string->list package-list)))  ;; Pull off first char (prefix)
      (prefixes                   (map string char-prefixes))                 ;; Make list of strings from prefixes
      (split-numbers-list         (map cdr (map string->list package-list)))  ;; Pull off refdes numbers as list elements
      (string-numbers-list        (map list->string split-numbers-list))      ;; Recombine split up (multidigit) number strings
      (numbers-list               (map string->number string-numbers-list))   ;; Convert strings to numbers for sorting
      (sorted-numbers-list        (sort numbers-list <))                      ;; Sort refdes numbers as numbers
      (sorted-string-numbers-list (map number->string sorted-numbers-list)) ) ;; Create sorted list of refdes strings.

    (map-in-order string-append  prefixes sorted-string-numbers-list)  ;; Laminate prefixes back onto refdes numbers & return.
  )
)

;;--------------------------------------------------------------------------------------------------
;; Given a list of spice-IO packages (refdes's), return the list of nets attached to the IOs.
;;
;; Argument List :
;;   package-list - A list of package (component) labels (refdes's)
;;
;; Return Values :
;;   net-list - A list of nets

(define (spice-msw:get-io-nets package-list net-list)
    
  (if (null? package-list)
    net-list        ;; End iteration & return net-list if ls is empty.
    (let*
      ( (package (car package-list))                       ;; otherwise process package. . .
        (net     (car (gnetlist:get-nets package "1"))) )  ;; get the net attached to pin 1

      ;; Now iterate
      (spice-msw:get-io-nets (cdr package-list) (cons net net-list))
    )
  )
)

;;--------------------------------------------------------------------------------------------------
;; Write netnames connected to pin-a and pin-b.
;;
;; Currently used by the controlled sources eg. f and h.
;;
;; Argument List :
;;   package - The refdes of a component eg. R1
;;   pin-a   - The name of pin-a
;;   pin-b   - The name of pin-b

(define (spice-msw:write-two-pin-names package pin-a pin-b)
  
  (display (string-append
    (car (spice-msw:get-net package (gnetlist:get-attribute-by-pinseq package pin-a "pinnumber"))) " "))
  (display (string-append
    (car (spice-msw:get-net package (gnetlist:get-attribute-by-pinseq package pin-b "pinnumber"))) " "))
)

;;--------------------------------------------------------------------------------------------------
;; Write all listed and available attributes in the form of <variable>=<value>.
;;
;; Argument List :
;;   package     - The refdes of a component eg. R1
;;   attrib-list - A list of attributes

(define (spice-msw:write-attrib-list package attrib-list)
  
  (if (not (null? attrib-list))
    (let
      ( (attrib (gnetlist:get-package-attribute package (car attrib-list))) )
      
      (if (not (string-ci=? attrib "unknown"))
        (display (string-append " " (car attrib-list) "=" attrib))
      )
      (spice-msw:write-attrib-list package (cdr attrib-list))
    )
  )
)

;;--------------------------------------------------------------------------------------------------
;; A replacement of the procedure gnetlist:get-nets. A net labeled "GND" becomes 0.
;;
;; Argument List :
;;   package  - The refdes of a component eg. R1
;;   pin-name - The component pin name connected to the net

(define (spice-msw:get-net package pin-name)
  
  (let
    ( (net-name (gnetlist:get-nets package pin-name)) )

    (cond ((string-ci=? (car net-name) "GND") (cons "0" #t))
      (else                                   (cons (car net-name) #t))
    )
  )
)

;;--------------------------------------------------------------------------------------------------
;; Sort procedure to order refdes's alphabetically but keep A? packages at the end of the list so
;; SPICE simulation directives operate correctly.
;;
;; This function was written by Ken Healy to facilitate SPICE netlisting for GNU-CAP, which wants A?
;; refdes cards (ie. SPICE directives) to appear last in the SPICE netlist.
;;
;; Argument List :
;;   x - First  item to compare
;;   y - Second item to compare

(define (spice-msw:package-sort x y)
    
  (define (string-tail string start) (substring string start (string-length string)))

  (let
    ( (xdes (string-ref  x 0))
      (ydes (string-ref  y 0))
      (xnum (string-tail x 1))
      (ynum (string-tail y 1)) )
      
    (if (char-ci=? xdes ydes)
      (if (string-ci<? xnum ynum) #t #f)
      (if (char-ci=? xdes #\A) #f
        (if (char-ci=? ydes #\A) #t
          (if (char-ci<? xdes ydes) #t #f)
        )
      )
    )
  )
)

;;**************************************************************************************************
;;*                                                                                                *
;;*                          Dealing with nets, devices, & SPICE cards                             *
;;*                                                                                                *
;;**************************************************************************************************

;;--------------------------------------------------------------------------------------------------
;; This function is passed a list of refdes's (ls). It uses each refdes to get the corresponding
;; "device" attribute.  Depending upon the device, it then invokes one or another of the spice line
;; output fcns to output a line of the spice netlist.
;;
;; Write the refdes, to the pin# connected net and component value and optional extra attributes
;; check if the component is a special spice component.
;;
;; Argument List :
;;   file-list    - A list of files referred to in the schematic file
;;   package-list - A list of package (component) labels (refdes's)

(define (spice-msw:write-netlist file-list package-list)

  (if (not (null? package-list))
    (let*
      ( (package (car package-list))                                   ;; assign package
        (device  (gnetlist:get-package-attribute package "device")) )  ;; assign device.

      ;; Super debug stuff -- outputs line describing device being processed.
      (debug-spew "\n  spice-msw:write-netlist     : ")
      (debug-spew (string-append "Check package refdes = " package ", device = " device "\n"))

      (cond
        ((string-ci=? device "NONE"))                 ;; Do nothing for graphical symbols
        ((string-ci=? device "SPICE-SUBCIRCUIT-LL"))  ;; Do nothing for subcircuit declaration
        ((string-ci=? device "SPICE-IO"))             ;; Do nothing for SPICE IO pins
        ((string-ci=? device "SPICE-CCVS"         ) (spice-msw:write-ccvs        package))
        ((string-ci=? device "SPICE-CCCS"         ) (spice-msw:write-cccs        package))
        ((string-ci=? device "SPICE-VCVS"         ) (spice-msw:write-vcvs        package))
        ((string-ci=? device "SPICE-VCCS"         ) (spice-msw:write-vccs        package))
        ((string-ci=? device "VOLTAGE_SOURCE"     ) (spice-msw:write-ivs         package)) ;; change someday ???
        ((string-ci=? device "CURRENT_SOURCE"     ) (spice-msw:write-ics         package)) ;; change someday ???
        ((string-ci=? device "SPICE-NULLOR"       ) (spice-msw:write-nullor      package))
        ((string-ci=? device "DIODE"              ) (spice-msw:write-diode       package))
        ((string-ci=? device "PMOS_TRANSISTOR"    ) (spice-msw:write-pmos        package))
        ((string-ci=? device "NMOS_TRANSISTOR"    ) (spice-msw:write-nmos        package))
        ((string-ci=? device "SUBCKT_PMOS"        ) (spice-msw:write-pmos-subckt package))
        ((string-ci=? device "SUBCKT_NMOS"        ) (spice-msw:write-nmos-subckt package))
        ((string-ci=? device "PNP_TRANSISTOR"     ) (spice-msw:write-pnp         package))
        ((string-ci=? device "SPICE-PNP"          ) (spice-msw:write-pnp         package))
        ((string-ci=? device "NPN_TRANSISTOR"     ) (spice-msw:write-npn         package))
        ((string-ci=? device "SPICE-NPN"          ) (spice-msw:write-npn         package))
        ((string-ci=? device "PFET_TRANSISTOR"    ) (spice-msw:write-pfet        package))
        ((string-ci=? device "NFET_TRANSISTOR"    ) (spice-msw:write-nfet        package))
        ((string-ci=? device "MESFET_TRANSISTOR"  ) (spice-msw:write-mesfet      package))
        ((string-ci=? device "SPICE-VC-SWITCH"    ) (spice-msw:write-vc-switch   package))
        ((string-ci=? device "SPICE-CC-SWITCH"    ) (spice-msw:write-cc-switch   package))
        ((string-ci=? device "AOP-STANDARD"       ) (spice-msw:write-def-cmpnt   package device file-list))
        ((string-ci=? device "RESISTOR"           ) (spice-msw:write-resistor    package))
        ((string-ci=? device "CAPACITOR"          ) (spice-msw:write-capacitor   package))
        ((string-ci=? device "POLARIZED_CAPACITOR") (spice-msw:write-capacitor   package)) ;; change someday ???
        ((string-ci=? device "INDUCTOR"           ) (spice-msw:write-inductor    package))
        ((string-ci=? device "COIL"               ) (spice-msw:write-inductor    package)) ;; Added to enable netlisting of coil-*.sym
        ((string-ci=? device "JOSEPHSON_JUNCTION" ) (spice-msw:write-joseph-jn   package))
        ((string-ci=? device "K"                  ) (spice-msw:write-coupled-ind package))
        ((string-ci=? device "MODEL"              ) (spice-msw:write-model       package))
        ((string-ci=? device "OPTIONS"            ) (spice-msw:write-options     package))
        ((string-ci=? device "DIRECTIVE"          ) (spice-msw:write-directive   package))
        ((string-ci=? device "INCLUDE"            ) (spice-msw:write-include     package))
        ((string-ci=? device "TESTPOINT"          ) (spice-msw:write-probe       package))
        (else
          ;; The package couldn't be identified using it's device attribute so attempt to write the
          ;;  package using it's refdes prefix to identify it
          (spice-msw:write-def-cmpnt package device file-list)
        )
      )
      
      (spice-msw:write-netlist file-list (cdr package-list))
    )
  )
)

;;--------------------------------------------------------------------------------------------------
;; Write out the default component based on the first character of it's refdes (eg. a prefix 'Q' is
;; assumed to be a bipolar transistor).
;;
;; This function does the following :
;;
;;   1. Gets the refdes (package).
;;   2. Checks the refdes against a short list of possible values.
;;      Depending upon the refdes, it does the following thing :
;;        A? -- Invoke write-ic. This provides the opportunity for a code model which may include a
;;              .MODEL line.
;;        D? -- Invoke write-diode.
;;        Q? -- Invoke write-component. (The "type" attribute is <unknown> in this case so that the
;;              SPICE simulator will barf if the user has been careless).
;;        M? -- Same as Q.
;;        U? -- Invoke write-ic. This provides the opportunity for a component model to be
;;              instantiated.
;;        X? -- Invoke write-ic. This provides the opportunity for a component subcircuit to be
;;              instantiated.
;;        V? -- Invoke write-ivs.
;;        I? -- Invoke write-ics.
;;   3. Otherwise, just output the refdes, the attached nets and the "value" attribute.
;;
;; Argument List :
;;   package   - The refdes of a component eg. R1
;;   device    - The device attribute value for the package
;;   file-list - A list of files referred to in the schematic file

(define (spice-msw:write-def-cmpnt package device file-list)

  (let
    ( (first-char (string (string-ref package 0))) )  ;; Extract first char of refdes

    (cond
      ((string-ci=? first-char "A") (spice-msw:write-ic          package file-list))
      ((string-ci=? first-char "C") (spice-msw:write-capacitor   package))
      ((string-ci=? first-char "D") (spice-msw:write-diode       package))
      ((string-ci=? first-char "E") (spice-msw:write-vcvs        package))
      ((string-ci=? first-char "F") (spice-msw:write-cccs        package))
      ((string-ci=? first-char "G") (spice-msw:write-vccs        package))
      ((string-ci=? first-char "H") (spice-msw:write-ccvs        package))
      ((string-ci=? first-char "I") (spice-msw:write-ics         package))
      ((string-ci=? first-char "J") (spice-msw:write-component   package #f "<unknown>" (list)))
      ((string-ci=? first-char "K") (spice-msw:write-coupled-ind package))
      ((string-ci=? first-char "L") (spice-msw:write-inductor    package))
      ((string-ci=? first-char "M") (spice-msw:write-component   package #f "<unknown>" (list)))
      ((string-ci=? first-char "Q") (spice-msw:write-component   package #f "<unknown>" (list)))
      ((string-ci=? first-char "R") (spice-msw:write-resistor    package))
      ((string-ci=? first-char "S") (spice-msw:write-vc-switch   package))
      ((string-ci=? first-char "M") (spice-msw:write-component   package #f "<unknown>" (list)))
      ((string-ci=? first-char "U") (spice-msw:write-ic          package file-list))
      ((string-ci=? first-char "V") (spice-msw:write-ivs         package))
      ((string-ci=? first-char "W") (spice-msw:write-cc-switch   package))
      ((string-ci=? first-char "X") (spice-msw:write-ic          package file-list))
      ((string-ci=? first-char "Z") (spice-msw:write-mesfet      package))
      (else
        (debug-spew "  spice-msw:write-def-cmpnt   : ")
        (debug-spew (string-append "Found unknown device " device "\n"))
    
        (spice-msw:write-refdes-nets package)
        (display (gnetlist:get-package-attribute package "value"))
        (newline)
      )
    )
  )
)

;;--------------------------------------------------------------------------------------------------
;; Write out component followed by model or model file associated with the component.
;;
;; This function does the following :
;;   1.  Writes out the correct refdes prefix (if specified and necessary).
;;   2.  Writes out the refdes and nets
;;   3.  Looks for "model-name" attribute. Writes it out if it exists.
;;   4.  If there is no "model-name" attribute, it writes out the "value" attribute. If there is no
;;       "value" attribute, it writes out "unknown" and returns, causing the spice simulator to puke
;;       when the netlist is run. This is important because the spice simulator needs to have some
;;       indication of what model to look for.
;;   5.  Outputs optional attributes attached to device, if any.
;;   6.  Outputs a new line
;;   7.  Looks for a "model" attribute. If it exists, it writes a .MODEL line like :
;;         .MODEL model-name type ( model )
;;
;; Argument List :
;;   package     - The refdes of a component eg. Q1
;;   prefix      - The correct refdes prefix for this package type
;;   type        - The SPICE model type eg. NPN or JFT
;;   attrib-list - A list of attributes for the package

(define (spice-msw:write-component package prefix type attrib-list)

  (let
    ( (model-name (gnetlist:get-package-attribute package "model-name"))
      (model      (gnetlist:get-package-attribute package "model"     ))
      (value      (gnetlist:get-package-attribute package "value"     ))
      (area       (gnetlist:get-package-attribute package "area"      ))
      (off        (gnetlist:get-package-attribute package "off"       ))
      (model-file (gnetlist:get-package-attribute package "file"      )) )

    ;; Write out the refdes prefix, if required
    (if prefix (spice-msw:write-prefix package prefix) )

    ;; Next we write out the refdes and nets.
    (spice-msw:write-refdes-nets package)

    ;; Look for "model-name" attribute. Write it out if it exists otherwise look for "value" attribute
    (if (not (string-ci=? model-name "unknown"))
      (display model-name)  ;; Display the model-name
      (display value)       ;; Otherwise display the value
    )
    
    ;; Next write out attributes if they exist
    ;; First attribute is area. It is written as a simple string
    (if (not (string-ci=? area "unknown"))
      (display (string-append " " area))
    )

    ;; Next attribute is off. It is written as a simple string
    (if (not (string-ci=? off "unknown"))
      (display (string-append " " off))
    )

    ;; Write out remaining attributes
    (spice-msw:write-attrib-list package attrib-list)

    ;; Now write out newline in preparation for writing out model.
    (newline)

    ;; Now write out any model which is pointed to by the part.
    (cond

      ;; One line model and model name exist
      ( (not (or (string-ci=? model "unknown") (string-ci=? model-name "unknown")))
        (begin
          (debug-spew "  spice-msw:write-component   : ")
          (debug-spew (string-append "Found model and model-name for " package "\n"))
        )
        (display (string-append ".MODEL " model-name " " type " (" model ")\n"))
      )

      ;; One line model and component value exist
      ( (not (or (string-ci=? model "unknown") (string-ci=? value "unknown")))
        (begin
          (debug-spew "  spice-msw:write-component   : ")
          (debug-spew (string-append "Found model and value for " package "\n"))
        )
        (display (string-append ".MODEL " model-name " " type " (" value ")\n"))
      )

      ;; Model file and model name exist
      ( (not (or (string-ci=? model-file "unknown") (string-ci=? model-name "unknown")))
        (begin
          (debug-spew "  spice-msw:write-component   : ")
          (debug-spew (string-append "Found file and model-name for " package "\n"))
        )
      )

      ;; Model file and component value exist
      ( (not (or (string-ci=? model-file "unknown") (string-ci=? value "unknown")))
        (begin
          (debug-spew "  spice-msw:write-component   : ")
          (debug-spew (string-append "Found file and value for " package "\n"))
        )
      )
    )
  )
)

;;--------------------------------------------------------------------------------------------------
;; Write a diode.
;;
;; Write out a valid diode refdes & then call the function which writes the rest of the line.
;;
;; Argument List :
;;   package - The refdes of a component eg. D1

(define (spice-msw:write-diode package)
    
  (debug-spew "  spice-msw:write-diode       : Found a diode\n")
  
  (let
    ( (attrib-list (list "IC" "TEMP")) )
    
    (spice-msw:write-component package "D" "D" attrib-list)
  )
)

;;--------------------------------------------------------------------------------------------------
;; Write out a valid IC or sub-circuit line.
;;
;; The algorithm is as follows :
;;  1. Figure out what type of model goes with this part from file-list. If it isn't listed
;;     look for a MODEL attribute. If a MODEL attribute is attached write out SPICE card and then
;;     write out .MODEL on next line. If no MODEL attribute is attached just write out what little
;;     we know then return.
;;  2. If the model-name is in the file-list, get the associated file-type. Compare it against
;;     the component's refdes. If model-type is .MODEL or .SUBCKT and refdes doesn't begin with a U
;;     or X respectively, prepend the correct prefix to the refdes.
;;  3. Print out the rest of the line.
;;
;; Argument List :
;;   package   - The refdes of a component eg. U1
;;   file-list - A list of files referred to in the schematic file

(define (spice-msw:write-ic package file-list)

  ;; First do local assignments
  (let
    ( (first-char (string (string-ref package 0)))  ;; extract first char of refdes
      (model-name (gnetlist:get-package-attribute package "model-name"))
      (model      (gnetlist:get-package-attribute package "model"))
      (value      (gnetlist:get-package-attribute package "value"))
      (type       (gnetlist:get-package-attribute package "type"))
      (model-file (gnetlist:get-package-attribute package "file"))
      (list-item  (list)) )

    (cond
      ( (string-ci=? first-char "U")
        (debug-spew "  spice-msw:write-ic          : Found a Integrated Circuit\n")
      )
      ( (string-ci=? first-char "X")
        (debug-spew "  spice-msw:write-ic          : Found a Sub-Circuit\n")
      )
    )

    ;; First, if model-name is empty, we use value attribute instead.
    ;; We do this by sticking the contents of "value" into "model-name".
    (if (string-ci=? model-name "unknown") (set! model-name value) )

    ;; Now get item from file-list using model-name as key
    (set! list-item (spice-msw:get-file-list-item model-name file-list) )

    ;; Check if list-item is null
    (if (or (null? list-item) (eq? list-item #f))

      ;; list-item is null.  Evidently, we didn't discover any files holding this model.
      ;; Instead we look for model attribute
      (if (not (string-ci=? model "unknown"))
        (begin                                     ;; Model attribute exists, write card and model
          (debug-spew "  spice-msw:write-ic          : ")
          (debug-spew "Model info not found in model file list, but model attribute exists, write out spice card and .MODEL line\n")
          (spice-msw:write-refdes-nets package)
          (display (string-append model-name "\n" ))
          (display (string-append ".MODEL " model-name " "))
          (if (not (string-ci=? type "unknown")) (display (string-append type " ")) )  ;; If no type then just skip it.
          (display (string-append "(" model ")\n"))
        )
        (begin                                     ;; No model attribute either, just write out card
          (debug-spew "  spice-msw:write-ic          : ")
          (debug-spew "Model info not found in model file list, no model attribute either\n")
          (spice-msw:write-refdes-nets package)
          (display (string-append model-name "\n" ))
        )
      )

      ;; list-item is not null. Therefore we process line depending upon contents of list-item
      (let
        ( (file-type (caddr list-item)) )

        (cond
          ;; File contains a model
          ((string-ci=? file-type ".MODEL")
            (begin
              (debug-spew "  spice-msw:write-ic          : ")
              (debug-spew (string-append "Found .MODEL with model-file and model-name for " package "\n"))
              (spice-msw:write-prefix package "U")  ;; Write out the refdes prefix, if required
              (spice-msw:write-refdes-nets package)
              (display (string-append model-name "\n" ))
            )
          )
          ;; File contains a subcircuit
          ((string-ci=? file-type ".SUBCKT")
            (begin
             (debug-spew "  spice-msw:write-ic          : ")
              (debug-spew (string-append "Found .SUBCKT with model-file and model-name for " package "\n"))
              (spice-msw:write-prefix package "X")  ;; Write out the refdes prefix, if required
              (spice-msw:write-refdes-nets package)
              (display (string-append model-name "\n" ))
            )
          )
        )
      )
    )
  )
)

;;--------------------------------------------------------------------------------------------------
;; Write an NPN bipolar transistor.
;;
;; Write a valid transistor refdes & then call the function which writes the rest of the line.
;;
;; Argument List :
;;   package - The refdes of a component eg. Q1

(define (spice-msw:write-npn package)
  
  (debug-spew "  spice-msw:write-npn         : Found a NPN Bipolar Transistor\n")

  (let
    ( (attrib-list (list "IC" "TEMP")) )

    (spice-msw:write-component package "Q" "NPN" attrib-list)
  )
)

;;--------------------------------------------------------------------------------------------------
;; Write a PNP bipolar transistor.
;;
;; Argument List :
;;   package - The refdes of a component eg. Q1

(define (spice-msw:write-pnp package)
  
  (debug-spew "  spice-msw:write-pnp         : Found a PNP Bipolar Transistor\n")

  (let
    ( (attrib-list (list "IC" "TEMP")) )
    
    (spice-msw:write-component package "Q" "PNP" attrib-list)
  )
)

;;--------------------------------------------------------------------------------------------------
;; Write an N-channel JFET transistor.
;;
;; Argument List :
;;   package - The refdes of a component eg. J1

(define (spice-msw:write-nfet package)
  
  (debug-spew "  spice-msw:write-nfet        : Found a N-channel JFET\n")

  (let
    ( (attrib-list (list "IC" "TEMP") ))

    (spice-msw:write-component package "J" "NJF" attrib-list)
  )
)

;;--------------------------------------------------------------------------------------------------
;; Write a P-channel JFET transistor.
;;
;; Argument List :
;;   package - The refdes of a component eg. J1

(define (spice-msw:write-pfet package)
  
  (debug-spew "  spice-msw:write-pfet        : Found a P-channel JFET\n")

  (let
    ( (attrib-list (list "IC" "TEMP")) )
    
    (spice-msw:write-component package "J" "PJF" attrib-list)
  )
)

;;--------------------------------------------------------------------------------------------------
;; Write a PMOS transistor.
;;
;; Argument List :
;;   package - The refdes of a component eg. M1

(define (spice-msw:write-pmos package)
  
  (debug-spew "  spice-msw:write-pmos        : Found a PMOS Transistor\n")

  (let
    ( (attrib-list (list "L" "W" "AS" "AD" "PD" "PS" "NRD" "NRS" "TEMP" "IC" "M")) )
    
    (spice-msw:write-component package "M" "PMOS" attrib-list)
  )
)

;;--------------------------------------------------------------------------------------------------
;; Write a NMOS transistor.
;;
;; Argument List :
;;   package - The refdes of a component eg. M1

(define (spice-msw:write-nmos package)

  (debug-spew "  spice-msw:write-nmos        : Found a NMOS Transistor\n")

  (let
    ( (attrib-list (list "L" "W" "AS" "AD" "PD" "PS" "NRD" "NRS" "TEMP" "IC" "M")) )

    (spice-msw:write-component package "M" "NMOS" attrib-list)
  )
)

;;--------------------------------------------------------------------------------------------------
;; Write a subckt PMOS transistor.
;;
;; Argument List :
;;   package - The refdes of a component eg. X1

(define (spice-msw:write-pmos-subckt package)
  
  (debug-spew "  spice-msw:write-pmos-subckt : Found a PMOS Transistor sub-circuit\n")

  (let
    ( (attrib-list (list "L" "W" "AS" "AD" "PD" "PS" "NRD" "NRS" "TEMP" "IC" "M")) )
    
    (spice-msw:write-component package "X" "PMOS" attrib-list)
  )
)

;;--------------------------------------------------------------------------------------------------
;; Write a subckt NMOS transistor.
;;
;; Argument List :
;;   package - The refdes of a component eg. X1

(define (spice-msw:write-nmos-subckt package)

  (debug-spew "  spice-msw:write-nmos-subckt : Found a NMOS Transistor sub-circuit\n")

  (let
    ( (attrib-list (list "L" "W" "AS" "AD" "PD" "PS" "NRD" "NRS" "TEMP" "IC" "M")) )
    
    (spice-msw:write-component package "X" "NMOS" attrib-list)
  )
)

;;--------------------------------------------------------------------------------------------------
;; Write a MESFET transistor.
;;
;; Argument List :
;;   package - The refdes of a component eg. Z1

(define (spice-msw:write-mesfet package)
  
  (debug-spew "  spice-msw:write-mesfet      : Found a MESFET Transistor\n")

  (let
    ( (attrib-list (list "IC")) )

    (spice-msw:write-component package "Z" "MESFET" attrib-list)
  )
)

;;--------------------------------------------------------------------------------------------------
;; Write a voltage controled switch.
;;
;; Argument List :
;;   package - The refdes of a component eg. S1

(define (spice-msw:write-vc-switch package)

  (debug-spew "  spice-msw:write-vc-switch   : Found a Voltage Controlled Switch\n")

  (let
    ( (attrib-list (list " ")) )
    
    (spice-msw:write-component package "S" "SW" attrib-list)
  )
)

;;--------------------------------------------------------------------------------------------------
;; Write a current controled switch.
;;
;; Argument List :
;;   package - The refdes of a component eg. W1

(define (spice-msw:write-cc-switch package)

  (debug-spew "  spice-msw:write-cc-switch   : Found a Voltage Controlled Switch\n")

  (let
    ( (attrib-list (list " ")) )
    
    (spice-msw:write-component package "W" "CSW" attrib-list)
  )
)

;;--------------------------------------------------------------------------------------------------
;; Write a resistor.
;;
;; Argument List :
;;   package - The refdes of a component eg. R1

(define (spice-msw:write-resistor package)

  (debug-spew "  spice-msw:write-resistor    : Found a Resistor\n")

  ;; Write out the refdes prefix, if required
  (spice-msw:write-prefix package "R")

  ;; First write out refdes and attached nets
  (spice-msw:write-refdes-nets package)

  ;; Next write mandatory resistor value if it exists.
  (let
    ( (value (gnetlist:get-package-attribute package "value")) )
    
    (if (not (string-ci=? value "unknown"))
      (display value)
    )
  )

  ;; Next write our model name if it exists
  (let
    ( (model-name (gnetlist:get-package-attribute package "model-name")))
    
    (if (not (string-ci=? model-name "unknown"))
      (display (string-append " " model-name))
    )
  )

  ;; next create list of attributes which can be attached to a resistor.
  ;; I include non-standard "area" attrib here per popular demand.
  (let
    ((attrib-list (list "AREA" "L" "W" "TEMP")))
    
    (spice-msw:write-attrib-list package attrib-list)  ;; write the attributes (if any) separately
  )
  
  ;; finally output a new line
  (newline)
)

;;--------------------------------------------------------------------------------------------------
;; Write a capacitor.
;;
;; Argument List :
;;   package - The refdes of a component eg. C1

(define (spice-msw:write-capacitor package)

  (debug-spew "  spice-msw:write-capacitor   : Found a Capacitor\n")

  ;; Write out the refdes prefix, if required
  (spice-msw:write-prefix package "C")

  ;; first write out refdes and attached nets
  (spice-msw:write-refdes-nets package)

  ;; next write capacitor value, if any.  Note that if the
  ;; component value is not assigned nothing will be written out.
  (let
    ( (value (gnetlist:get-package-attribute package "value")) )
    
    (if (not (string-ci=? value "unknown"))
      (display value)
    )
  )

  ;; next write capacitor model name, if any.  This is applicable to
  ;; semiconductor caps used in chip design.
  (let
    ( (model-name (gnetlist:get-package-attribute package "model-name")) )

    (if (not (string-ci=? model-name "unknown"))
      (display (string-append " " model-name))
    )
  )

  ;; Next write out attributes if they exist.  Use
  ;; a list of attributes which can be attached to a capacitor.
  ;; I include non-standard "area" attrib here per request of Peter Kaiser.
  (let
    ( (attrib-list (list "AREA" "L" "W" "IC")) )
    
    (spice-msw:write-attrib-list package attrib-list)
  )
    
  (newline)
)

;;--------------------------------------------------------------------------------------------------
;; Write an inductor.
;;
;; Argument List :
;;   package - The refdes of a component eg. L1

(define (spice-msw:write-inductor package)

  (debug-spew "  spice-msw:write-inductor    : Found a Inductor\n")

  ;; Write out the refdes prefix, if required
  (spice-msw:write-prefix package "L")

  ;; first write out refdes and attached nets
  (spice-msw:write-refdes-nets package)

  ;; next write inductor value, if any.  Note that if the
  ;; component value is not assigned, then it will write "unknown"
  (let
    ( (value (gnetlist:get-package-attribute package "value")) )

    (display value)
  )

  ;; create list of attributes which can be attached to a inductor
  (let
    ( (attrib-list (list "L" "W" "IC")) )
    
    (spice-msw:write-attrib-list package attrib-list)
  )
  
  (newline)
)

;;--------------------------------------------------------------------------------------------------
;; Write an independent voltage source.
;;
;; The behavior of the voltage source is held in the "value" attribute.
;;
;; Argument List :
;;   package - The refdes of a component eg. V1

(define (spice-msw:write-ivs package)

  (debug-spew "  spice-msw:write-ivs         : Found a Independent Voltage Source\n")

  ;; Write out the refdes prefix, if required
  (spice-msw:write-prefix package "V")

  ;; First write out refdes and attached nets
  (spice-msw:write-refdes-nets package)

  ;; Next write voltage value, if any. Note that if the voltage value is not assigned, then it will
  ;; write "unknown".
  (let
    ( (value (gnetlist:get-package-attribute package "value")) )
    
    (display value)
  )

  (newline)
)

;;--------------------------------------------------------------------------------------------------
;; Write an independent current source.
;;
;; The behavior of the current source is held in the "value" attribute
;;
;; Argument List :
;;   package - The refdes of a component eg. I1

(define (spice-msw:write-ics package)

  (debug-spew "  write-ics                   : Found a Independent Current Source\n")

  ;; Write out the refdes prefix, if required
  (spice-msw:write-prefix package "I")

  ;; First write out refdes and attached nets
  (spice-msw:write-refdes-nets package)

  ;; Next write current value, if any. Note that if the current value is not assigned, then it will
  ;; write "unknown".
  (let
    ( (value (gnetlist:get-package-attribute package "value")) )
    
    (display value)
  )

  (newline)
)

;;--------------------------------------------------------------------------------------------------
;; Write a current controlled voltage source and implement the necessary current measuring voltage
;; source.
;;
;; Argument List :
;;   package - The refdes of a component eg. H1

(define (spice-msw:write-ccvs package)
  
  (debug-spew "  spice-msw:write-ccvs        : Found a Current Controlled Voltage Source\n")

  ;; Write out the refdes prefix, if required
  (spice-msw:write-prefix package "H")

  ;; Implement the controlled current source
  (display (string-append package " "))
  (spice-msw:write-two-pin-names package "1" "2")
  (display (string-append "Vsense_" package  " " (spice-msw:component-value package) "\n" ))
    
  ;; Implement the current measuring voltage source
  (display (string-append "Vsense_" package " "))
  (spice-msw:write-two-pin-names package "3" "4")
  (display "DC 0\n")
    
  ;; It's possible to leave the output voltage source unconnected, SPICE won't complain about
  ;; unconnected nodes
  (display (string-append "IOut_" package " "))
  (spice-msw:write-two-pin-names package "1" "2")
  (display "DC 0\n")
)

;;--------------------------------------------------------------------------------------------------
;; Write a current controlled current source and implement the necessary current measuring voltage
;; source.
;;
;; Argument List :
;;   package - The refdes of a component eg. F1

(define (spice-msw:write-cccs package)
  
  (debug-spew "  spice-msw:write-cccs        : Found a Current Controlled Current Source\n")

  ;; Write out the refdes prefix, if required
  (spice-msw:write-prefix package "F")

  ;; Implement the controlled current source
  (display (string-append package " "))
  (spice-msw:write-two-pin-names package "1" "2")
  (display (string-append "Vsense_" package " " (gnetlist:get-package-attribute package "value") "\n"))
    
  ;; Implement the current measuring voltage source
  (display (string-append "Vsense_" package " "))
  (spice-msw:write-two-pin-names package "3" "4")
  (display "DC 0\n")
)

;;--------------------------------------------------------------------------------------------------
;; Write a voltage controlled current source and implement the necessary voltage measuring current
;; source.
;;
;; Argument List :
;;   package - The refdes of a component eg. G1

(define (spice-msw:write-vccs package)
  
  (debug-spew "  spice-msw:write-vccs        : Found a Voltage Controlled Current Source\n")

  ;; Write out the refdes prefix, if required
  (spice-msw:write-prefix package "G")
    
  ;; Implement the controlled current source
  (display (string-append package " "))
  (spice-msw:write-net-names package)
  (display (string-append (spice-msw:component-value package) "\n"))
    
  ;; Implement the voltage measuring current source
  ;; Imagine copying the voltage of a voltage source with an internal impedance, SPICE complains
  ;; about unconnected nets if this current source is not here.
  (display (string-append "IMeasure_" package " "))
  (spice-msw:write-two-pin-names package "3" "4")
  (display "DC 0\n")
)

;;--------------------------------------------------------------------------------------------------
;; Write a voltage controlled voltage source and implement the necessary voltage measuring current
;; source.
;;
;; Argument List :
;;   package - The refdes of a component eg. E1

(define (spice-msw:write-vcvs package)

  (debug-spew "  spice-msw:write-vcvs        : Found a Voltage Controlled Voltage Source\n")

  ;; Write out the refdes prefix, if required
  (spice-msw:write-prefix package "E")

  ;; Implement the controlled voltage source
  (display (string-append package " "))
  (spice-msw:write-net-names package)
  (display (string-append (gnetlist:get-package-attribute package "value") "\n" ))
    
  ;; Implement the voltage measuring current source
  ;; Imagine copying the voltage of a voltage source with an internal impedance, SPICE complains
  ;; about unconnected nets if this current source is not here.
  (display (string-append "Isense_" package " "))
  (spice-msw:write-two-pin-names package "3" "4")
  (display "DC 0\n")

  ;; With an output current source it is possible to leave the output voltage source unconnected,
  ;; SPICE won't complain about unconnected nodes
  (display (string-append "IOut_" package " "))
  (spice-msw:write-two-pin-names package "1" "2")
  (display "DC 0\n")
)

;;--------------------------------------------------------------------------------------------------
;; Create a nullor, make sure it consists of a voltage controlled source.
;;
;; Argument List :
;;   package - The refdes of a component eg. E1

(define (spice-msw:write-nullor package)
                                
  (debug-spew "  spice-msw:write-nullor      : Found a Nullor\n")

  (let
    ( (value (gnetlist:get-package-attribute package "value")) )
    
    ;; Write out the refdes prefix, if required
    (spice-msw:write-prefix package "E")

    ;; Implement the controlled voltage source
    (display (string-append package " "))
    (spice-msw:write-net-names package)
    (display (string-append (if (string-ci=? value "unknown") "1000Meg" value) "\n"))

    ;; Implement the voltage measuring current source.
    ;; Imagine yourself copying the voltage of a voltage source with an internal impedance, SPICE
    ;; complains about unconnected nets if this current source is not here.
    (display (string-append "IMeasure_" package " "))
    (spice-msw:write-two-pin-names package "3" "4")
    (display "DC 0\n")

    ;; With an output current source it is possible to leave the output voltage source unconnected,
    ;; SPICE won't complain about unconnected nodes
    (display (string-append "IOut_" package " "))
    (spice-msw:write-two-pin-names package "1" "2")
    (display "DC 0\n")
  )
)

;;--------------------------------------------------------------------------------------------------
;; Write a Josephson junction.
;;
;; Argument List :
;;   package - The refdes of a component eg. B1

(define (spice-msw:write-joseph-jn package)

  (debug-spew "  spice-msw:write-joseph-jn   : Found a Josephson junction\n")

    ;; Write out the refdes prefix, if required
    (spice-msw:write-prefix package "B")

   ;; First write out refdes and attached nets
  (spice-msw:write-refdes-nets package)

  ;; Next, add a dummy node for JJ phase. Unlike in Xic netlister, give it
  ;; a reasonable name, not a number, eg. refdes
  (display (string-append package " "))

  ;; Next write JJ model name, if any.
  (let
    ((model-name (gnetlist:get-package-attribute package "model-name")))
    (if (not (string-ci=? model-name "unknown")) (display (string-append model-name " " )))
  )

  ;; Next write out attributes if they exist. Use a list of attributes which can be attached to a
  ;; junction.
  (spice-msw:write-attrib-list package (list "AREA"))
    
  (newline)
)

;;--------------------------------------------------------------------------------------------------
;; Write a coupled (mutual) inductor.
;;
;; Argument List :
;;   package - The refdes of a component eg. K1

(define (spice-msw:write-coupled-ind package)

  (debug-spew "  spice-msw:write-coupled-ind : Found a coupled (mutual) inductor\n")

  ;; Write out the refdes prefix, if required
  (spice-msw:write-prefix package "K")

  ;; First write out refdes and attached nets (none)
  (spice-msw:write-refdes-nets package)

  ;; Next two inductor names and value
  (let
    ( (inductors (gnetlist:get-package-attribute package "inductors"))
      (value     (gnetlist:get-package-attribute package "value")) )
    
    (if (not (string-ci=? inductors "unknown")) (display (string-append inductors " " )))
    (if (not (string-ci=? value "unknown"))     (display (string-append value " " )))
  )

  (newline)
)

;;--------------------------------------------------------------------------------------------------
;; Write a voltage probe.
;;
;; Argument List :
;;   package - The refdes of a component eg. R1

(define (spice-msw:write-probe package)
  
  (debug-spew "  spice-msw:write-probe       : Found a probe\n")

  (let
    ((value (gnetlist:get-package-attribute package "value")) )

    (if (string-ci=? value "unknown") (set! value "TRAN") )

    (display (string-append "* Probe device " package " on nets "))
    (spice-msw:write-net-names package)
    (newline)
    (display (string-append ".print " value " +"))
    (spice-msw:write-net-names package
    (string-join (map (lambda (x) "V(~a)") (gnetlist:get-pins package)) " " 'infix) ) ;; make format string
    (newline)
  )
)

;;--------------------------------------------------------------------------------------------------
;; Write a prefix if first char of refdes is improper.
;;
;; Eg. if MOSFET is named T1 then it becomes MT1 in the SPICE file.
;;
;; Argument List :
;;   package - The refdes of a component eg. R1
;;   prefix  - The desired prefix character

(define (spice-msw:write-prefix package prefix)

  (let
    ( (different-prefix (not (string-ci=? (substring package 0 1) prefix)))
      (nomunge-mode     (calling-flag? "nomunge_mode" (gnetlist:get-calling-flags))) )
      
    (debug-spew "  spice-msw:write-prefix      : Check refdes ")
    (debug-spew (string-append "prefix = " (substring package 0 1)))
    (debug-spew (string-append " (correct prefix = " prefix ")\n"))

    (if (and different-prefix (not nomunge-mode)) (display prefix))
  )
)

;;--------------------------------------------------------------------------------------------------
;; Given a refdes, and optionally a format string, write out the nets attached to the component's
;; pins.
;;
;; If it's not called with a format string it looks for one in the net-format attribute, otherwise
;; it writes out the pins unformatted. This is used to write out non-slotted parts.
;;
;; Argument List :
;;   ???

(define (spice-msw:write-net-names refdes . format)

  ;; get-net-name - helper function. Called with pinseq, returns net name, unless net name is
  ;;                "ERROR_INVALID_PIN" then it returns false.
  (define (get-net-name pin)
    (set! pin (number->string pin))

    ;; -------  Super debug stuff  --------
    (if #f
      (begin
        (debug-spew "  spice-msw:write-net-names :\n")
        (debug-spew (string-append "     pin-name = " pin "\n"))
        (debug-spew (string-append "     pinnumber = "
          (gnetlist:get-attribute-by-pinseq refdes pin "pinnumber") "\n"))
        (debug-spew (string-append "     pinseq = "
          (gnetlist:get-attribute-by-pinseq refdes pin "pinseq")))
        (if (not (string-ci=? pin (gnetlist:get-attribute-by-pinseq refdes pin "pinseq")))
          (debug-spew " <== INCONSISTENT!\n")
          (debug-spew "\n")
        )
        (debug-spew (string-append "     netname = "
          (car (spice-msw:get-net refdes (gnetlist:get-attribute-by-pinseq refdes pin "pinnumber"))) "\n"))
      )
    )
    ;; -------------------------------------

    (set! pin (car (spice-msw:get-net refdes (gnetlist:get-attribute-by-pinseq refdes pin "pinnumber"))))
    (if (string-ci=? pin "ERROR_INVALID_PIN")
      (begin
        (debug-spew (string-append "For " refdes ", found pin with no pinseq attribute. Ignoring. . . .\n"))
        #f
      )
      pin
    )
  )

  ;; First do local assignments
  (let
    ( (netnames (filter-map get-net-name (range 1 (length (gnetlist:get-pins refdes))))) )
    
    (if (null? format) ;; Format agument take priority, otherwise use attribute
      (set! format (gnetlist:get-package-attribute refdes "net-format"))
      (set! format (car format))
    )
    (if (string-ci=? format "unknown")
      (display (string-join netnames " " 'suffix))             ;; Write out nets.
      (apply simple-format (cons #t (cons format netnames)))   ;; Write out nets with format string
    )
  )
)

;;--------------------------------------------------------------------------------------------------
;; Return a list of all the integers from start to stop, with the optional step size.
;;
;; It is similar to perl's range operator '..'
;;
;; Argument List :
;;   ???

(define (range start stop . step)
  
  (if (null? step)
    (iota (+ (- stop start) 1) start)
    (begin
      (set! step (car step))
      (iota (+ (ceiling (/ (- stop start) step)) 1) start step)
    )
  )
)

;;--------------------------------------------------------------------------------------------------
;; Write the refdes and the net names connected to pins on this component.
;;
;; No component value is written or extra attributes. Those are handled later.
;;
;; Argument List :
;;   package - The refdes of a component eg. R1

(define (spice-msw:write-refdes-nets package)
  
  (display (string-append package " "))  ;; Write component refdes
  (spice-msw:write-net-names package)    ;; Write the net names
)

;;--------------------------------------------------------------------------------------------------
;; Include SPICE statements from a directive block.
;;
;; Argument List :
;;   package - The refdes of a component eg. ???

(define (spice-msw:write-directive package)

  (debug-spew "  spice-msw:write-directive   : Found a SPICE directive\n")

  ;; Collect variables used in creating spice code
  (let
    ( (value (gnetlist:get-package-attribute package "value"))
      (file  (gnetlist:get-package-attribute package "file")) )

    (cond
      ;; First look to see if there is a value.
      ((not (string-ci=? value "unknown"))
        (begin
          (display (string-append value "\n"))
          (debug-spew (string-append "Appending value = \"" value "\" to output file.\n"))
        )
      )

      ;; Since there is no value, look for file.
      ((not (string-ci=? file "unknown"))
        (begin
          (spice-msw:insert-text-file file)  ;; Note that we don't wait until the end here.  Is that OK?
          (debug-spew (string-append "Inserting contents of file = " file " into output file.\n"))
        )
      )
    )
  )
)

;;--------------------------------------------------------------------------------------------------
;; Include a file using an .INCLUDE directive.
;;
;; Argument List :
;;   package - The refdes of a component eg. .INCLUDE

(define (spice-msw:write-include package)
    
  (debug-spew "  spice-msw:write-include     : Found a .INCLUDE directive\n")

  (let
    ( (file (gnetlist:get-package-attribute package "file")) )

    (if (not (string-ci=? file "unknown"))
      (if (calling-flag? "embedd_mode" (gnetlist:get-calling-flags))
        (begin
          (spice-msw:insert-text-file file)
          (debug-spew (string-append "embedding contents of file " file " into netlist.\n"))
        )
        (begin
          (display (string-append ".INCLUDE " file "\n"))
          (debug-spew "placing .include directive string into netlist.\n")
        )
      )
      (debug-spew "silently skip \"unknown\" file.\n")
    )
  )
)

;;--------------------------------------------------------------------------------------------------
;; Include an option using an .OPTIONS directive.
;;
;; Argument List :
;;   package - The refdes of a component eg. .OPTIONS

(define (spice-msw:write-options package)
  
  (debug-spew "  spice-msw:write-options     : Found a .OPTIONS directive\n")

  (display (string-append ".OPTIONS " (gnetlist:get-package-attribute package "value")  "\n"))
)

;;--------------------------------------------------------------------------------------------------
;; Include a spice model (instantiated as a model box on the schematic).
;;
;; Two types of model can be included:
;;   1. An embedded model, which is a one- or multi-line string held in the attribute "model".
;;      In this case, the following attributes are mandatory :
;;       - model (i.e. list of parameter=value strings)
;;       - model-name
;;       - type
;;      In this case, the function creates and formats the correct spice model line(s).
;;   2. A model held in a file whose name is held in the attribute "file"
;;      In this case, the following attribute are mandatory :
;;       - file (i.e. list of parameter=value strings)
;;      In this case, the function just opens the file and dumps the contents into the netlist.
;;
;; Argument List :
;;   package - The refdes of a component eg. .MODEL

(define (spice-msw:write-model package)
    
  (debug-spew "  spice-msw:write-model       : Found a .MODEL directive")

  ;; Collect variables used in creating spice code
  (let
    ( (model-name (gnetlist:get-package-attribute package "model-name"))
      (model-file (gnetlist:get-package-attribute package "file"))
      (model      (gnetlist:get-package-attribute package "model"))
      (type       (gnetlist:get-package-attribute package "type")) )

    ;; Now, depending upon what combination of model, model-file, and model-name
    ;; exist (as described above) write out lines into spice netlist.
    (cond
     
      ;; one model and model name exist
      ((not (or (string-ci=? model "unknown") (string-ci=? model-name "unknown")))
        (debug-spew (string-append " and model and model-name for " package "\n"))
        (display (string-append ".MODEL " model-name " " type " (" model ")\n"))
      )

      ;; model file exists
      ((not (or (string-ci=? model-file "unknown") ))
        (debug-spew (string-append " and model-file for " package "\n"))
      )
      
      (else
        (debug-spew " but no model, model-name or model-file\n")
      )
    )
  )
)

;;**************************************************************************************************
