xschem/xschem_library/analyses/lib_init.tcl

628 lines
20 KiB
Tcl

# Analyses library for visual circuit analysis setup.
# Copyright (C) 2025 Arpad Buermen
#
# 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.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
namespace eval ::analyses {
# Parenthesize string if not empty
proc parenthesize {str} {
if {[string length $str] > 0} {
return "( $str )"
} else {
return "$str"
}
}
# Indent a multiline string
proc indent {str indent} {
set lines [split $str \n]
set indented_lines [lmap line $lines {string cat $indent $line}]
return [join $indented_lines \n]
}
# Helper function for display
# props: flat list of name-type pairs
# type can be
# N .. normal, print value
# G .. given, print if parameter is given (not empty string)
# SG .. string given, if quoted, print it out, otherwise just print given
# NV .. normal, value only
proc format_props {symname props} {
set args {}
foreach {propname type} $props {
set val [ xschem getprop instance $symname $propname ]
set len [string len $val]
set quoted [expr {[string index $val 0] eq "\""}]
if {$len > 0} {
if { $type eq "N" } {
lappend args "$propname=$val"
} elseif { $type eq "NV" } {
lappend args "$val"
} elseif { $type eq "G" } {
lappend args "$propname given"
} elseif { $type eq "SG" } {
if {$quoted} {
lappend args "$propname=$val"
} else {
lappend args "$propname given"
}
}
}
}
return [join $args "\n"]
}
# Helper function for spectre/spice netlisting
# N .. normal
# NV .. normal, value only
# UNV .. unquote, normal, value only
# S .. string (dump quoted)
proc format_args {symname props} {
set str ""
set args {}
foreach {propname type} $props {
set val [ xschem getprop instance $symname $propname ]
set len [string len $val]
# Unquote value if requested
if { $type eq "UNV" } {
if {[string match "\"*\"" $val]} {
set val [string range $val 1 end-1]
}
}
# Add to formatted arguments list
if {$len > 0} {
set first false
if { $type eq "N" } {
lappend args "$propname=$val"
} elseif { $type eq "NV" } {
lappend args "$val"
} elseif { $type eq "UNV" } {
lappend args "$val"
} elseif { $type eq "S" } {
lappend args "$propname=\"$val\""
}
}
}
return [join $args " "]
}
# Display output order
proc display_order {symname} {
set names [list order NV]
set txt [format_props $symname $names]
if {[string length $txt] > 0} {
return "#$txt"
} else {
return "unordered (#0)"
}
}
# Display simulator name
proc display_simulator {symname} {
set names [list simulator NV]
return [format_props $symname $names]
}
# Display sweep
proc display_sweep {symname} {
set names [list sweep N instance N model N parameter N "option" N "variable" N from N to N step N mode N points N "values" N continuation N]
return [format_props $symname $names]
}
# Display verbatim block
proc display_verbatim {symname} {
return [format_props $symname [list verbatim NV]]
}
# Display OP analysis
proc display_op {symname} {
set names [list sweep N nodeset SG store N write N]
return [format_props $symname $names]
}
# Display 1D DC sweep analysis
proc display_dc1d {symname} {
set names [list sweep N instance N model N parameter N "option" N "variable" N from N to N step N mode N points N "values" N continuation N nodeset SG store N write N]
return [format_props $symname $names]
}
# Display DCINC analysis
proc display_dcinc {symname} {
set names [list sweep N nodeset SG store N write N writeop N]
return [format_props $symname $names]
}
# Display DCXF analysis
proc display_dcxf {symname} {
set names [list sweep N outp N outn N in N nodeset SG store N write N writeop N]
return [format_props $symname $names]
}
# Display AC analysis
proc display_ac {symname} {
set names [list sweep N from N to N step N mode N points N values N nodeset SG store N write N writeop N]
return [format_props $symname $names]
}
# Display XF analysis
proc display_acxf {symname} {
set names [list sweep N outp N outn N from N to N step N mode N points N values N nodeset SG store N write N writeop N]
return [format_props $symname $names]
}
# Display NOISE analysis
proc display_noise {symname} {
set names [list sweep N outp N outn N in N from N to N step N mode N points N values N ptssum N nodeset SG store N write N writeop N]
return [format_props $symname $names]
}
# Display TRAN analysis
proc display_tran {symname} {
set names [list sweep N step N stop N start N maxstep N icmode N nodeset SG ic SG store N write N]
return [format_props $symname $names]
}
# Display HB analysis
proc display_hb {symname} {
set names [list sweep N freq N nharm N immax N truncate N samplefac N nper N sample N harmonic N imorder N nodeset N store N write N]
return [format_props $symname $names]
}
# Display postprocessing
proc display_postprocess {symname} {
set names [list file N]
return [format_props $symname $names]
}
#
# Netlister
#
proc netlister {netlist_type} {
set cmds {}
set types [dict create]
set prefix "netlist_command_"
foreach {name symfile type} [xschem instance_list] {
# Do not netlist entry point symbol
if {[string match netlist_command_header $type]} {
continue
}
# Collect only symbols of type netlist_*
if {[string match netlist_* $type]} {
dict set types $name $type
}
if {[string match $prefix* $type]} {
# Get order
set order [xschem getprop instance $name order]
if {[string len $order] == 0} {
set order 0
}
# Append as sublist
lappend cmds [list $order $name $type]
}
}
# Sort
set cmds [lsort -integer -index 0 $cmds]
# Loop and format
set blocks {}
foreach tuple $cmds {
lassign $tuple order name type
set suffix [string range $type [string length $prefix] end]
# Construct formatter function name
set func [join [list "format_" "$suffix" "_" "$netlist_type"] ""]
try {
# Format analysis and post analysis script
set retval [$func $name]
# retval has 2 members:
# - command and
# - post-command (for writing results in ngpice, empty string for spectre)
lassign $retval cmd postcmd
# Format sweep and add it to analysis
set swcmd [format_sweep_chain_$netlist_type $name $cmd types]
# Wrap (swept) analysis and post analysis script in a block
if {[string length $swcmd] > 0} {
lappend blocks [wrap_analysis_$netlist_type $swcmd $postcmd]
}
} on error msg {
puts "Error formatting $name: $msg"
continue
}
}
return [wrap_control_$netlist_type $blocks]
}
#
# Netlister for VACASK
#
# Wrap analysis
proc wrap_analysis_spectre {cmds postcmd} {
return "$cmds"
}
# Wrap in control block
proc wrap_control_spectre {cmds} {
set control [indent [join $cmds "\n\n"] " "]
return [join [list "//// begin user architecture code" "control" "$control" "endc" "//// end user architecture code"] "\n"]
}
# Add sweep chain to analysis
proc format_sweep_chain_spectre {name anstr types} {
upvar 1 $types typesdict
set sweep [ xschem getprop instance $name sweep ]
set sweeplist {}
format_sweep_spectre $name sweeplist typesdict
# Do we have any sweeps
if {[llength $sweeplist] > 0} {
# Yes, reverse sweep chain
set sweeplist [lreverse $sweeplist]
# Join sweeps
set sweeps [join "$sweeplist" "\n"]
return "$sweeps\n $anstr"
} else {
return "$anstr"
}
}
# Sweep formatter, construct sweep chain, innermost first
proc format_sweep_spectre {parent sweeplist types} {
upvar 1 $sweeplist swl
upvar 1 $types typesdict
set sweep [ xschem getprop instance $parent sweep ]
if {[string length $sweep] > 0} {
# Parent has sweep property
set tag [ xschem getprop instance $sweep tag ]
set type [dict get $typesdict $sweep]
if {[string length $tag] > 0 && ($type eq "netlist_modifier_sweep")} {
# Sweep has tag property
try {
lappend swl [format_single_sweep_spectre $sweep]
} on error msg {
# Stop traversing chain on error
error "$sweep: $msg"
}
# Recursion into parent sweep
format_sweep_spectre $sweep swl typesdict
}
}
}
# Fomat a single sweep
proc format_single_sweep_spectre {sweep} {
set tag [ xschem getprop instance $sweep tag ]
return "sweep $tag ( [format_sweep_spectre_params $sweep] [format_sweep_spectre_range $sweep] )"
}
# Sweep formatter, what to sweep
proc format_sweep_spectre_params {name} {
return [format_args $name [list instance N model N parameter N "option" N "variable" N]]
}
# Sweep formatter, how to sweep
proc format_sweep_spectre_range {name} {
return [format_args $name [list from N to N step N mode N points N "values" N continuation N]]
}
# Analysis formatters
proc format_verbatim_spectre {name} {
set sim [ xschem getprop instance $name simulator ]
set dump false
if {[string length $sim]==0} {
set dump true
} else {
try {
if {[sim_is_$sim]} {
set dump true
}
} on error msg {}
}
if { !$dump } {
return ""
}
return [list [format_args $name [list verbatim NV]] ""]
}
proc format_analysis_op_spectre {name} {
set args [format_args $name [list nodeset N store N write N]]
return [list "analysis $name op [parenthesize $args]" ""]
}
proc format_analysis_dc1d_spectre {name} {
# OP formatting
set args [format_args $name [list nodeset N store N write N]]
set anstr "analysis $name op [parenthesize $args]"
# 1D sweep formatting
set swp [format_single_sweep_spectre $name]
return [list "$swp\n $anstr" ""]
}
proc format_analysis_dcinc_spectre {name} {
set args [format_args $name [list nodeset N store N write N writeop N]]
return [list "analysis $name dcinc [parenthesize $args]" ""]
}
proc format_signal_output_spectre {name} {
set outp [ xschem getprop instance $name outp ]
set outn [ xschem getprop instance $name outn ]
set vecstr "\[ $outp"
if {[string length $outn] > 0} {
append vecstr ", $outn"
}
append vecstr " \]"
return $vecstr
}
proc format_analysis_dcxf_spectre {name} {
set args "out=[format_signal_output_spectre $name] "
append args [format_args $name [list nodeset N store N write N writeop N]]
return [list "analysis $name dcxf [parenthesize $args]" ""]
}
proc format_analysis_ac_spectre {name} {
set args "[format_sweep_spectre_range $name] "
append args [format_args $name [list nodeset N store N write N writeop N]]
return [list "analysis $name ac [parenthesize $args]" ""]
}
proc format_analysis_acxf_spectre {name} {
set args "out=[format_signal_output_spectre $name] "
append args "[format_sweep_spectre_range $name] "
append args [format_args $name [list nodeset N store N write N writeop N]]
return [list "analysis $name acxf [parenthesize $args]" ""]
}
proc format_analysis_noise_spectre {name} {
set args "out=[format_signal_output_spectre $name] "
append args "[format_args $name [list in N]] "
append args "[format_sweep_spectre_range $name] "
append args [format_args $name [list nodeset N store N write N writeop N]]
return [list "analysis $name noise [parenthesize $args]" ""]
}
proc format_analysis_tran_spectre {name} {
set args [format_args $name [list step N stop N start N maxstep N icmode N nodeset N ic N store N write N]]
return [list "analysis $name tran [parenthesize $args]" ""]
}
proc format_analysis_hb_spectre {name} {
set args [format_args $name [list freq N nharm N immax N truncate N samplefac N nper N sample N harmonic N imorder N nodeset N store N write N]]
return [list "analysis $name hb [parenthesize $args]" ""]
}
proc format_postprocess_spectre {name} {
set tool [format_args $name [list tool NV]]
set file [format_args $name [list file NV]]
return [list "postprocess($tool, $file)" ""]
}
#
# Netlister for Ngspice
#
# Wrap analysis
proc wrap_analysis_spice {cmds postcmd} {
set txt ""
if {[string length $postcmd] > 0} {
append txt "destroy all\n"
}
append txt $cmds
if {[string length $postcmd] > 0} {
# Write only if analysis succeeded
# append txt "\nif $#plots gt 1\n$postcmd\nend"
append txt "\n$postcmd"
}
return $txt
}
# Wrap in control block
proc wrap_control_spice {cmds} {
set control [join $cmds "\n\n"]
return [join [list "**** begin user architecture code" ".control" "set filetype=binary" "" "$control" ".endc" "**** end user architecture code"] "\n"]
}
# Add sweep chain to analysis
proc format_sweep_chain_spice {name anstr types} {
upvar 1 $types typesdict
# Get analysis type (op/dc1d or ac/noise)
set antype [dict get $typesdict $name]
# Is it a dec/oct/lin sweep
if {$antype eq "netlist_command_analysis_ac"} {
set decoctlin true
} elseif {$antype eq "netlist_command_analysis_noise"} {
set decoctlin true
} elseif {$antype eq "netlist_command_analysis_op"} {
# For swept op rename analysis to dc
set decoctlin false
set anstr "dc"
} else {
set decoctlin false
}
set sweep [ xschem getprop instance $name sweep ]
set sweeplist {}
format_sweep_spice $decoctlin $name sweeplist typesdict
# Limit sweep dimension for analyses
if {[string match "netlist_command_analysis_*" $antype]} {
if {$antype eq "netlist_command_analysis_op"} {
if {[llength $sweeplist] > 2} {
error "sweep dimension can be at most 2"
}
} elseif {$antype eq "netlist_command_analysis_dc1d"} {
if {[llength $sweeplist] > 1} {
error "sweep dimension can be at most 2"
}
} else {
if {[llength $sweeplist] > 0} {
error "sweep is not suported"
}
}
}
# Do we have any sweeps
if {[llength $sweeplist] > 0} {
# Yes, join sweeps
set sweeps [join $sweeplist " "]
return "$anstr $sweeps"
} else {
return "$anstr"
}
}
# Sweep formatter, construct sweep chain, innermost first
proc format_sweep_spice {decoctlin parent sweeplist types} {
upvar 1 $sweeplist swl
upvar 1 $types typesdict
set sweep [ xschem getprop instance $parent sweep ]
if {[string length $sweep] > 0} {
# Parent has sweep property
set tag [ xschem getprop instance $sweep tag ]
set type [dict get $typesdict $sweep]
if {[string length $tag] > 0 && ($type eq "netlist_modifier_sweep")} {
# Sweep has tag property
try {
lappend swl [format_single_sweep_spice $decoctlin $sweep]
} on error msg {
# Stop traversing chain on error
error "$sweep: $msg"
}
# Recursion into parent sweep
format_sweep_spice $decoctlin $sweep swl typesdict
}
}
}
# Fomat a single sweep
proc format_single_sweep_spice {decoctlin sweep} {
return "[format_sweep_spice_params $sweep] [format_sweep_spice_range $decoctlin $sweep]"
}
# Sweep formatter, what to sweep
proc format_sweep_spice_params {name} {
return [format_args $name [list instance UNV]]
}
# Sweep formatter, how to sweep
proc format_sweep_spice_range {decoctlin name} {
if {$decoctlin} {
return [format_args $name [list mode UNV points NV from NV to NV]]
} else {
return [format_args $name [list from NV to NV step NV]]
}
}
# Each formatter returns a list with two elements
# - analysis
# - rawfile write script / post-command script
proc format_verbatim_spice {name} {
return [list [format_verbatim_spectre $name] ""]
}
proc format_analysis_op_spice {name} {
return [list "op" "write $name.raw"]
}
proc format_analysis_dc1d_spice {name} {
# 1D sweep formatting
set swp [format_single_sweep_spice false $name]
return [list "dc $swp" "write $name.raw"]
}
proc format_analysis_dcinc_spice {name} {
error "dcinc is not supported by Ngspice"
}
proc format_signal_output_spice {name} {
set outp [ xschem getprop instance $name outp ]
set outn [ xschem getprop instance $name outn ]
# If outp starts with v(, use it literally, ignore outn
if {[string match "v(*" $outp]} {
return $outp
}
# If outp starts with i(, use it literally, ignore outn
if {[string match "i(*" $outp]} {
return $outp
}
# Unquote outp, outn
if {[string match "\"*\"" $outp]} {
set outp [string range $outp 1 end-1]
}
if {[string match "\"*\"" $outn]} {
set outn [string range $outn 1 end-1]
}
# Is outn empty
if {[string length $outn] == 0} {
return "v($outp)"
} else {
return "v($outp,$outn)"
}
}
proc format_analysis_dcxf_spice {name} {
set output "[format_signal_output_spice $name]"
return [list "tf $output [format_args $name [list in UNV]]" "write $name.raw"]
}
proc format_analysis_ac_spice {name} {
set swp "[format_sweep_spice_range true $name] "
return [list "ac $swp" "write $name.raw"]
}
proc format_analysis_acxf_spice {name} {
error "acxf is not supported by Ngspice"
}
proc format_analysis_noise_spice {name} {
set output "[format_signal_output_spice $name]"
set swp "[format_sweep_spice_range true $name]"
set writer "write $name-integrated.raw\nsetplot previous\nwrite $name.raw"
return [list "noise $output [format_args $name [list in UNV]] $swp [format_args $name [list ptssum NV]]" "$writer"]
}
proc format_analysis_tran_spice {name} {
set args [format_args $name [list step NV stop NV start NV maxstep NV]]
set icmode [format_args $name [list icmode UNV]]
if {$icmode eq "uic"} {
append args " uic"
}
return [list "tran $args" "write $name.raw"]
}
proc format_analysis_hb_spice {name} {
error "hb is not supported by Ngspice"
}
proc format_postprocess_spice {name} {
global tcl_platform
set tool [format_args $name [list tool NV]]
if {![string match "\"*\"" $tool]} {
# Not quoted, check if it is PYTHON (VACASK variable for autodetected Python)
if {$tool eq "PYTHON"} {
if {$tcl_platform(platform) eq "windows"} {
set tool "python.exe"
} else {
set tool "python3"
}
}
}
# Keep quotes around tool and file
set file [format_args $name [list file NV]]
return [list "shell $tool $file" ""]
}
}