Files
magic/tcltk/toolkit.tcl
T
Ahmed Nematallah 900f6d7b0f Ensure all cells are loaded into memory in netlist_to_spice to avoid file corruption during save
Added logic to ensure all cells are loaded into memory to prevent corruption when saving files. This is a fix for a minor edge case when generating "netlist_to_spice" for a file that already has a netlist, and which contains cells containing other cells. Followed by "writeall force". This causes unloaded files (that are still referenced) to be loaded during save with "ignoreTech" being true, and thus disregarding the scale, corrupting all coordinates if a scale is used.

This was caused by skipping regenerating cells that already had a .mag file
2026-05-06 11:37:52 -04:00

1856 lines
63 KiB
Tcl

#-----------------------------------------------------
# Magic/TCL general-purpose toolkit procedures
#-----------------------------------------------------
# Tim Edwards
# February 11, 2007
# Revision 0
# December 15, 2016
# Revision 1
# October 29, 2020
# Revision 2 (names are hashed from properties)
# March 9, 2021
# Added spice-to-layout procedure
# March 4, 2026
# Changed to make use of new "units" command
# March 26, 2026
# Added behavior to handle ideal devices (resistor, capacitor,
# inductor)
# April 2, 2026
# Changed the hash to MurmurHash3, as the existing hash
# is prone to creating name collisions (rare, but not rare
# enough).
#--------------------------------------------------------------
# Sets up the environment for a toolkit. The toolkit must
# supply a namespace that is the "library name". For each
# parameter-defined device ("gencell") type, the toolkit must
# supply five procedures:
#
# 1. ${library}::${gencell_type}_defaults {}
# 2. ${library}::${gencell_type}_convert {parameters}
# 3. ${library}::${gencell_type}_dialog {parameters}
# 4. ${library}::${gencell_type}_check {parameters}
# 5. ${library}::${gencell_type}_draw {parameters}
#
# The first defines the parameters used by the gencell, and
# declares default parameters to use when first generating
# the window that prompts for the device parameters prior to
# creating the device. The second converts between parameters
# in a SPICE netlist and parameters used by the dialog,
# performing units conversion and parameter name conversion as
# needed. The third builds the dialog window for entering
# device parameters. The fourth checks the parameters for
# legal values. The fifth draws the device.
#
# If "library" is not specified then it defaults to "toolkit".
# Otherwise, where specified, the name "gencell_fullname"
# is equivalent to "${library}::${gencell_type}"
#
# Each gencell is defined by cell properties as created by
# the "cellname property" command. Specific properties used
# by the toolkit are:
#
# library --- name of library (see above, default "toolkit")
# gencell --- base name of gencell (gencell_type, above)
# parameters --- list of gencell parameter-value pairs
#--------------------------------------------------------------
# Initialize toolkit menus to the wrapper window
global Opts
#----------------------------------------------------------------
# Add a menu button to the Magic wrapper window for the toolkit
#----------------------------------------------------------------
proc magic::add_toolkit_menu {framename button_text {library toolkit}} {
menubutton ${framename}.titlebar.mbuttons.${library} \
-text $button_text \
-relief raised \
-menu ${framename}.titlebar.mbuttons.${library}.toolmenu \
-borderwidth 2
menu ${framename}.titlebar.mbuttons.${library}.toolmenu -tearoff 0
pack ${framename}.titlebar.mbuttons.${library} -side left
}
#-----------------------------------------------------------------
# Add a menu item to the toolkit menu calling the default function
#-----------------------------------------------------------------
proc magic::add_toolkit_button {framename button_text gencell_type \
{library toolkit} args} {
set m ${framename}.titlebar.mbuttons.${library}.toolmenu
$m add command -label "$button_text" -command \
"magic::gencell $library::$gencell_type {} $args"
}
#----------------------------------------------------------------
# Add a menu item to the toolkit menu that calls the provided
# function
#----------------------------------------------------------------
proc magic::add_toolkit_command {framename button_text \
command {library toolkit} args} {
set m ${framename}.titlebar.mbuttons.${library}.toolmenu
$m add command -label "$button_text" -command "$command $args"
}
#----------------------------------------------------------------
# Add a separator to the toolkit menu
#----------------------------------------------------------------
proc magic::add_toolkit_separator {framename {library toolkit}} {
set m ${framename}.titlebar.mbuttons.${library}.toolmenu
$m add separator
}
#-----------------------------------------------------
# Add "Ctrl-P" key callback for device selection
#-----------------------------------------------------
magic::macro ^P "magic::gencell {} ; raise .params"
#-------------------------------------------------------------
# Add tag callback to select to update the gencell window
#-------------------------------------------------------------
magic::tag add select "magic::gencell_update %1"
#--------------------------------------------------------------
# Supporting procedures for netlist_to_layout procedure
#--------------------------------------------------------------
# move_forward_by_width --
#
# Given an instance name, find the instance and position the
# cursor box at the right side of the instance.
proc magic::move_forward_by_width {instname} {
select cell $instname
set curunits [units]
units internal
set anum [lindex [array -list count] 1]
set xpitch [lindex [array -list pitch] 0]
set bbox [box values]
set posx [lindex $bbox 0]
set posy [lindex $bbox 1]
set width [expr [lindex $bbox 2] - $posx]
set posx [expr $posx + $width + $xpitch * $anum]
box position ${posx} ${posy}
units {*}$curunits
return [lindex $bbox 3]
}
# get_and_move_inst --
#
# Given a cell name, creat an instance of the cell named "instname"
# at the current cursor box position. If option "anum" is given
# and > 1, then array the cell.
proc magic::get_and_move_inst {cellname instname {anum 1}} {
set newinst [getcell $cellname]
select cell $newinst
if {$newinst == ""} {return}
identify $instname
if {$anum > 1} {array 1 $anum}
set curunits [units]
units internal
set bbox [box values]
set posx [lindex $bbox 2]
set posy [lindex $bbox 1]
box position ${posx} ${posy}
units {*}$curunits
return [lindex $bbox 3]
}
# create_new_pin --
#
# Create a new pin of size 1um x 1um at the current cursor box
# location. If "layer" is given, then create the pin on the
# given layer. Otherwise, the pin is created on the m1 layer.
proc magic::create_new_pin {pinname portnum {layer m1}} {
set curunits [units]
units microns
box size 1 1
paint $layer
label $pinname FreeSans 1 0 0 0 c $layer
port make $portnum
box move s 2
units {*}$curunits
}
# generate_layout_add --
#
# Add a new subcircuit to a layout and seed it with components
# as found in the list "complist", and add pins according to the
# pin names in "subpins". Each entry in "complist" is a single
# device line from a SPICE file.
proc magic::generate_layout_add {subname subpins complist library} {
global PDKNAMESPACE
set curunits [units]
units internal
# Create a new subcircuit.
load $subname -quiet
# In the case where subcells of circuit "subname" do not exist,
# delete the placeholders so that they can be regenerated.
set children [cellname list children $subname]
foreach child $children {
set flags [cellname flags $child]
foreach flag $flags {
if {$flag == "not-found"} {
set insts [cellname list instances $child]
foreach inst $insts {
select cell $inst
delete
}
cellname delete $child -noprompt
}
}
}
box 0 0 0 0
# Generate pins. This routine can be made re-entrant; if a
# cell exists, then its contents are compared to the incoming
# netlist contents. Pins that exist in the netlist but not
# the layout are added. Pins that exist in the layout but not
# in the netlist are removed.
if {[llength $subpins] > 0} {
set pinlist [split $subpins]
# Determine if this cell already has pins.
set existpins []
for {set i [port first]} {$i > -1} {set i [port $i next]} {
lappend existpins [port $i name]
}
set addpins []
foreach pin $pinlist {
if {[lsearch $existpins $pin] == -1} {
lappend addpins $pin
}
}
set rmpins []
foreach pin $existpins {
if {[lsearch $pinlist $pin] == -1} {
lappend rmpins $pin
}
}
set i 0
foreach pin $addpins {
# Escape [ and ] in pin name
set pin_esc [string map {\[ \\\[ \] \\\]} $pin]
magic::create_new_pin $pin_esc $i
incr i
}
foreach pin $rmpins {
set llayer [goto $pin]
select area $llayer
erase label
}
}
# Set initial position for importing cells
box size 0 0
set posx 0
set posy [expr {round(3 / [cif scale out])}]
box position ${posx} ${posy}
# Find all instances in the circuit
select top cell
set compexist [instance list children]
set compcells []
# Find the matching cell defs
foreach comp $compexist {
lappend compcells [instance list celldef $comp]
}
# Diagnostic
puts stdout "Cells already in the layout of ${subname}:"
for {set i 0} {$i < [llength $compexist]} {incr i} {
set comp [lindex $compexist $i]
set ccell [lindex $compcells $i]
puts stdout "${comp} (${ccell})"
}
flush stdout
# Seed layout with components
foreach comp $complist {
set pinlist {}
set paramlist {}
# NOTE: This routine deals with subcircuit calls and devices
# with models. There are two exceptions, for toolkits which
# wish to implement a way to generate unmodeled capacitors,
# resistors, or inductors based on value; for example, metal
# interdigitated capacitors. For those exceptions, the device
# value is recast as a parameter called "value", and the device
# is given a model "capacitor", "resistor", or "inductor",
# respectively.
# Parse SPICE line into pins, device name, and parameters. Make
# sure parameters incorporate quoted expressions as {} or ''.
set rest $comp
while {$rest != ""} {
if {[regexp -nocase {^[ \t]*[^= \t]+=[^=]+} $rest]} {
break
} elseif {[regexp -nocase {^[ \t]*([^ \t]+)[ \t]*(.*)$} $rest \
valid token rest]} {
lappend pinlist $token
} else {
set rest ""
}
}
while {$rest != ""} {
if {[regexp -nocase {^([^= \t]+)=\'([^\']+)\'[ \t]*(.*)} $rest \
valid pname value rest]} {
lappend paramlist [list $pname "{$value}"]
} elseif {[regexp -nocase {^([^= \t]+)=\{([^\}]+)\}[ \t]*(.*)} $rest \
valid pname value rest]} {
lappend paramlist [list $pname "{$value}"]
} elseif {[regexp -nocase {^([^= \t]+)=([^= \t]+)[ \t]*(.*)} $rest \
valid pname value rest]} {
lappend paramlist [list $pname $value]
} else {
puts stderr "Error parsing line \"$comp\""
puts stderr "at: \"$rest\""
set rest ""
}
}
if {[llength $pinlist] < 2} {
puts stderr "Error: No device type found in line \"$comp\""
puts stderr "Tokens found are: \"$pinlist\""
continue
}
set instname [lindex $pinlist 0]
set devtype [lindex $pinlist end]
set pinlist [lrange $pinlist 0 end-1]
# Ideal device check: "devtype" will start with a digit.
# The instname will begin with "c", "r", or "l".
if {[regexp {^([0-9\.]+.*)} $devtype pval]} {
set comptype [string tolower [string range $instname 0 0]]
if {$comptype == "c"} {
lappend paramlist [list value $pval]
set devtype capacitor
} elseif {$comptype == "r"} {
lappend paramlist [list value $pval]
set devtype resistor
} elseif {$comptype == "l"} {
lappend paramlist [list value $pval]
set devtype inductor
}
}
set mult 1
foreach param $paramlist {
set parmname [lindex $param 0]
set parmval [lindex $param 1]
if {[string toupper $parmname] == "M"} {
if {[catch {set mult [expr {int($parmval)}]}]} {
set mult [expr [string trim $parmval "'"]]
}
}
}
# Check if devtype has routines by looking for ${devtype}_defaults.
# If not found, do a case-insensitive check against all devices
# before deciding that devtype is a subcircuit and not a device.
# If found by case-insensitive check, then change the device name
# to the one used in the library.
if {$library != ""} {
set alldevices [namespace eval ::${library} {info procs}]
} else {
set alldevices [namespace eval ::${PDKNAMESPACE} {info procs}]
}
set devdefault [lsearch $alldevices ${devtype}_defaults]
if {$devdefault == -1} {
set devdefault [lsearch -nocase $alldevices ${devtype}_defaults]
if {$devdefault != -1} {
set devprocname [lindex $alldevices $devdefault]
set devproclist [split $devprocname "_"]
set devtype [lindex $devproclist 0]
}
}
# devtype is assumed to be in library. If not, it will attempt to
# use 'getcell' on devtype. Note that this code depends on the
# PDK setting varible PDKNAMESPACE.
if {$library != ""} {
set libdev ${library}::${devtype}
} else {
set libdev ${PDKNAMESPACE}::${devtype}
}
set outparts {}
# Escape any brackets in the instance name, since it gets passed to "eval"
set minstname [string map {[ \\\[ ] \\\]} $instname]
lappend outparts "magic::gencell $libdev $minstname"
# Output all parameters. Parameters not used by the toolkit are
# ignored by the toolkit.
lappend outparts "-spice"
foreach param $paramlist {
lappend outparts [string tolower [lindex $param 0]]
lappend outparts [lindex $param 1]
}
set inst_exists false
set existidx [lsearch $compexist $instname]
if {$existidx > -1} {
set existcell [lindex $compcells $existidx]
if {$devtype == $existcell} {
set inst_exists true
} else {
# Instance name exists but is the wrong device type.
# To do: Attempt to maintain the same position
pushbox
select cell $instname
delete
popbox
}
}
if {$inst_exists == false} {
if {[catch {eval [join $outparts]}]} {
# Assume this is not a gencell, and get an instance.
magic::get_and_move_inst $devtype $instname $mult
} else {
# Move forward for next gencell
magic::move_forward_by_width $instname
}
}
}
save $subname
units {*}$curunits
}
#--------------------------------------------------------------
# Wrapper for generating an initial layout from a SPICE netlist
# using the defined PDK toolkit procedures
#
# "netfile" is the name of a SPICE netlist
# "library" is the name of the PDK library namespace
#--------------------------------------------------------------
proc magic::netlist_to_layout {netfile library} {
if {![file exists $netfile]} {
puts stderr "No such file $netfile"
return
}
# Read data from file. Remove comment lines and concatenate
# continuation lines.
set topname [file rootname [file tail $netfile]]
puts stdout "Creating layout from [file tail $netfile]"
if {[file ext $netfile] == ".cdl"} {
set is_cdl true
} else {
set is_cdl false
}
if [catch {open $netfile r} fnet] {
puts stderr "Error: Cannot open file \"$netfile\" for reading."
return
}
set fdata {}
set lastline ""
while {[gets $fnet line] >= 0} {
# Handle CDL format *.PININFO (convert to .PININFO ...)
if {$is_cdl && ([string range $line 0 1] == "*.")} {
if {[string tolower [string range $line 2 8]] == "pininfo"} {
set line [string range $line 1 end]
}
}
if {[string index $line 0] != "*"} {
if {[string index $line 0] == "+"} {
if {[string range $line end end] != " "} {
append lastline " "
}
append lastline [string range $line 1 end]
} else {
lappend fdata $lastline
set lastline $line
}
}
}
lappend fdata $lastline
close $fnet
set insub false
set incmd false
set subname ""
set subpins ""
set complist {}
set toplist {}
# suspendall
set ignorekeys {.global .ic .option .end}
# Parse the file once and find all subcircuits being defined, to
# catch any issues with subcircuits being used before they are
# defined.
set allsubs {}
foreach line $fdata {
set ftokens [split $line]
set keyword [string tolower [lindex $ftokens 0]]
if {$keyword == ".subckt"} {
set subname [lindex $ftokens 1]
lappend allsubs $subname
}
}
# Generate list of pre-existing top level cells
set existing [cellname list top]
# Pre-generate placeholders for all subcircuits.
set curtop [cellname list self]
array set existing_cells {}
foreach subckt $allsubs {
if {$subckt == $curtop || $subckt == $topname} {
set existing_cells($subckt) "false"
load $subckt -silent
continue
}
if {[catch {load $subckt -fail -silent}] == 0} {
puts stdout "Subcircuit $subckt successfully loaded."
set existing_cells($subckt) "true"
# Make sure to load all cells into memory to avoid
# corruption when saving all files (files get loaded
# without taking the scale into account)
select top cell
expand
} else {
puts stdout "Subcircuit $subckt not found. Will generate."
set existing_cells($subckt) "false"
# Now we load it normally to create the placeholder for the generator
load $subckt -silent
}
}
load $curtop
# Parse the file and process all lines
foreach line $fdata {
if {$incmd} {
if {[regexp -nocase {^[ \t]*\.endc} $line]} {
set incmd false
}
} elseif {! $insub} {
set ftokens [split $line]
set keyword [string tolower [lindex $ftokens 0]]
if {[lsearch $ignorekeys $keyword] != -1} {
continue
} elseif {$keyword == ".command"} {
set incmd true
} elseif {$keyword == ".subckt"} {
set subname [lindex $ftokens 1]
set subpins [lrange $ftokens 2 end]
set insub true
} elseif {[regexp -nocase {^[xmcrldq]([^ \t]+)[ \t](.*)$} $line \
valid instname rest]} {
lappend toplist $line
} elseif {[regexp -nocase {^[ivbe]([^ \t]+)[ \t](.*)$} $line \
valid instname rest]} {
# These are testbench devices and should be ignored
continue
}
} else {
if {[regexp -nocase {^[ \t]*\.ends} $line]} {
set insub false
if {[info exists existing_cells($subname)] && $existing_cells($subname) == "false"} {
puts stdout "Cell $subname is empty. Generating initial layout..."
magic::generate_layout_add $subname $subpins $complist $library
} else {
puts stdout "Cell $subname already contains layout. Skipping generation."
}
set subname ""
set subpins ""
set complist {}
} elseif {[regexp -nocase {^[xmcrldq]([^ \t]+)[ \t](.*)$} $line \
valid instname rest]} {
lappend complist $line
} elseif {[regexp -nocase {^[ivbe]([^ \t]+)[ \t](.*)$} $line \
valid instname rest]} {
# These are testbench devices and should be ignored
continue
}
}
}
# Add in any top-level components (not in subcircuits)
if {[llength $toplist] > 0} {
# Make sure the top level cell is loaded before adding top-level
# components.
load $curtop
magic::generate_layout_add $topname "" $toplist $library
} else {
# There was no top level, so load the first new top cell that
# was generated by the import.
set allcells [cellname list top]
foreach cell $allcells {
if {[lsearch $existing $cell] < 0} {
load $cell
break
}
}
}
# resumeall
}
#-------------------------------------------------------------
# gencell
#
# Main routine to call a cell from either a menu button or
# from a script or command line. The name of the device
# is required, followed by the name of the instance, followed
# by an optional list of parameters. Handling depends on
# instname and args:
#
# gencell_name is either the name of an instance or the name
# of the gencell in the form <library>::<device>.
#
# name args action
#-----------------------------------------------------------------
# none empty interactive, new device w/defaults
# none specified interactive, new device w/parameters
# instname empty interactive, edit device
# instname specified non-interactive, change device
# device empty non-interactive, new device w/defaults
# device specified non-interactive, new device w/parameters
#
#-------------------------------------------------------------
# Also, if instname is empty and gencell_name is not specified,
# and if a device is selected in the layout, then gencell
# behaves like line 3 above (instname exists, args is empty).
# Note that macro Ctrl-P calls gencell this way. If gencell_name
# is not specified and nothing is selected, then gencell{}
# does nothing.
#
# "args" must be a list of the cell parameters in key:value pairs,
# and an odd number is not legal; the exception is that if the
# first argument is "-spice", then the list of parameters is
# expected to be in the format used in a SPICE netlist, and the
# parameter names and values will be treated accordingly.
#-------------------------------------------------------------
proc magic::gencell {gencell_name {instname {}} args} {
# Pull "-spice" out of args, if it is the first argument
if {[lindex $args 0] == "-spice"} {
set spicemode 1
set args [lrange $args 1 end]
} else {
set spicemode 0
}
set argpar [dict create {*}$args]
if {$gencell_name == {}} {
# Find selected item (to-do: handle multiple selections)
set wlist [what -list]
set clist [lindex $wlist 2]
set ccell [lindex $clist 0]
set ginst [lindex $ccell 0]
set gname [lindex $ccell 1]
set library [cellname list property $gname library]
if {$library == {}} {
set library toolkit
}
set gencell_type [cellname list property $gname gencell]
if {$gencell_type == {}} {
if {![regexp {^(.*)_[0-9]*$} $gname valid gencell_type]} {
# Error message
error "No gencell device is selected!"
}
}
# need to incorporate argpar?
set parameters [cellname list property $gname parameters]
set parameters [magic::gencell_defaults $gencell_type $library $parameters]
magic::gencell_dialog $ginst $gencell_type $library $parameters
} else {
# Parse out library name from gencell_name, otherwise default
# library is assumed to be "toolkit".
if {[regexp {^([^:]+)::([^:]+)$} $gencell_name valid library gencell_type] \
== 0} {
set library "toolkit"
set gencell_type $gencell_name
}
# Check that the device exists as a gencell, or else return an error
if {[namespace eval ::${library} info commands ${gencell_type}_convert] == ""} {
error "No import routine for ${library} library cell ${gencell_type}!"
}
if {$instname == {}} {
# Case: Interactive, new device with parameters in args (if any)
if {$spicemode == 1} {
# Legal not to have a *_convert routine
if {[info commands ${library}::${gencell_type}_convert] != ""} {
set argpar [${library}::${gencell_type}_convert $argpar]
}
}
set parameters [magic::gencell_defaults $gencell_type $library $argpar]
magic::gencell_dialog {} $gencell_type $library $parameters
} else {
# Check if instance exists or not in the cell
set cellname [instance list celldef $instname]
if {$cellname != ""} {
# Case: Change existing instance, parameters in args (if any)
select cell $instname
set devparms [cellname list property $cellname parameters]
set parameters [magic::gencell_defaults $gencell_type $library $devparms]
if {[dict exists $parameters nocell]} {
set arcount [array -list count]
set arpitch [array -list pitch]
dict set parameters nx [lindex $arcount 1]
dict set parameters ny [lindex $arcount 3]
dict set parameters pitchx $delx
dict set parameters pitchy $dely
}
if {[dict size $argpar] == 0} {
# No changes entered on the command line, so start dialog
magic::gencell_dialog $instname $gencell_type $library $parameters
} else {
# Apply specified changes without invoking the dialog
if {$spicemode == 1} {
set argpar [${library}::${gencell_type}_convert $argpar]
}
set parameters [dict merge $parameters $argpar]
magic::gencell_change $instname $gencell_type $library $parameters
}
} else {
# Case: Non-interactive, create new device with parameters
# in args (if any)
if {$spicemode == 1} {
set argpar [${library}::${gencell_type}_convert $argpar]
}
set parameters [magic::gencell_defaults $gencell_type $library $argpar]
set inst_defaultname [magic::gencell_create \
$gencell_type $library $parameters]
select cell $inst_defaultname
identify $instname
}
}
}
return 0
}
#-------------------------------------------------------------
# gencell_makecell
#
# This is a variation of magic::gencell and is used to generate
# a cell and return the cell name without creating or placing
# an instance.
#-------------------------------------------------------------
proc magic::gencell_makecell {gencell_fullname args} {
set argpar [dict create {*}$args]
set gencell_basename [namespace tail $gencell_fullname]
set library [namespace qualifiers $gencell_fullname]
set parameters [magic::gencell_defaults $gencell_basename $library $argpar]
set gsuffix [magic::get_gencell_hash ${parameters}]
set gname ${gencell_basename}_${gsuffix}
suspendall
cellname create $gname
pushstack $gname
if {[catch {${library}::${gencell_basename}_draw $parameters} drawerr]} {
puts stderr $drawerr
}
property library $library
property gencell $gencell_basename
property parameters $parameters
popstack
resumeall
return $gname
}
#-------------------------------------------------------------
# gencell_getparams
#
# Go through the parameter window and collect all of the
# named parameters and their values. Return the result as
# a dictionary.
#-------------------------------------------------------------
proc magic::gencell_getparams {} {
set parameters [dict create]
set slist [grid slaves .params.body.area.edits]
foreach s $slist {
if {[regexp {^\.params\.body\.area\.edits\.(.*)_ent$} $s valid pname] != 0} {
set value [subst \$magic::${pname}_val]
} elseif {[regexp {^\.params\.body\.area\.edits\.(.*)_chk$} $s valid pname] != 0} {
set value [subst \$magic::${pname}_val]
} elseif {[regexp {^\.params\.body\.area\.edits\.(.*)_sel$} $s valid pname] != 0} {
set value [subst \$magic::${pname}_val]
}
dict set parameters $pname $value
}
return $parameters
}
#-------------------------------------------------------------
# gencell_setparams
#
# Fill in values in the dialog from a set of parameters
#-------------------------------------------------------------
proc magic::gencell_setparams {parameters} {
if {[catch {set state [wm state .params]}]} {return}
set slist [grid slaves .params.body.area.edits]
foreach s $slist {
# ignore .params.body.area.edits.gencell_sel, as that does not exist in the
# parameters dictionary
if {$s == ".params.body.area.edits.gencell_sel"} {continue}
if {[regexp {^.params.body.area.edits.(.*)_ent$} $s valid pname] != 0} {
set value [dict get $parameters $pname]
set magic::${pname}_val $value
} elseif {[regexp {^.params.body.area.edits.(.*)_chk$} $s valid pname] != 0} {
set value [dict get $parameters $pname]
set magic::${pname}_val $value
} elseif {[regexp {^.params.body.area.edits.(.*)_sel$} $s valid pname] != 0} {
set value [dict get $parameters $pname]
set magic::${pname}_val $value
.params.body.area.edits.${pname}_sel configure -text $value
} elseif {[regexp {^.params.body.area.edits.(.*)_txt$} $s valid pname] != 0} {
if {[dict exists $parameters $pname]} {
set value [dict get $parameters $pname]
.params.body.area.edits.${pname}_txt configure -text $value
}
}
}
}
#-------------------------------------------------------------
# gencell_change
#
# Recreate a gencell with new parameters. Note that because
# each cellname is uniquely identified by the (hashed) set
# of parameters, changing parameters effectively means
# creating a new cell. If the original cell has parents
# other than the parent of the instance being changed, then
# it is retained; otherwise, it is deleted. The instance
# being edited gets replaced by an instance of the new cell.
# If the instance name was the cellname + suffix, then the
# instance name is regenerated. Otherwise, the instance
# name is retained.
#-------------------------------------------------------------
proc magic::gencell_change {instname gencell_type library parameters} {
global Opts
suspendall
set newinstname $instname
if {$parameters == {}} {
# Get device defaults
set pdefaults [${library}::${gencell_type}_defaults]
# Pull user-entered values from dialog
set parameters [dict merge $pdefaults [magic::gencell_getparams]]
}
# Attempt to set the new instance name as specified in the dialog.
# If the entry is "(default)" or if there is a name collision,
# revert the name to the original name.
set newinstname [.params.title.ient get]
if {$newinstname == "(default)"} {set newinstname $instname}
if {[instance list exists $newinstname] != ""} {set newinstname $instname}
if {[dict exists $parameters gencell]} {
# Setting special parameter "gencell" forces the gencell to change type
set gencell_type [dict get $parameters gencell]
}
if {[catch {set parameters [${library}::${gencell_type}_check $parameters]} \
checkerr]} {
puts stderr $checkerr
}
magic::gencell_setparams $parameters
if {[dict exists $parameters gencell]} {
set parameters [dict remove $parameters gencell]
}
set old_gname [instance list celldef $instname]
set gsuffix [magic::get_gencell_hash ${parameters}]
set gname ${gencell_type}_${gsuffix}
# Handle instance name changing first. If no parameters changed, then
# we're done.
if {$newinstname != $instname} {
identify $newinstname
# The buttons "Apply" and "Okay" need to be changed for the new
# instance name
catch {.params.buttons.apply config -command \
"magic::gencell_change $newinstname $gencell_type $library {}"}
catch {.params.buttons.okay config -command \
"magic::gencell_change $newinstname $gencell_type $library {} ;\
destroy .params"}
set instname $newinstname
}
# Guard against instance having been deleted. Also, if parameters have not
# changed as evidenced by the cell suffix not changing, then nothing further
# needs to be done.
if {$gname == "" || $gname == $old_gname} {
resumeall
return
}
set curunits [units]
units internal
set savebox [box values]
catch {setpoint 0 0 $Opts(focus)}
if [dict exists $parameters nocell] {
select cell $instname
set abox [instance list abutment]
delete
if {$abox != ""} {box values {*}$abox}
if {[catch {set newinst [${library}::${gencell_type}_draw $parameters]} \
drawerr]} {
puts stderr $drawerr
}
select cell $newinst
} elseif {[cellname list exists $gname] != 0} {
# If there is already a cell of this type then it is only required to
# remove the instance and replace it with an instance of the cell
select cell $instname
# check rotate/flip before replacing and replace with same
set orient [instance list orientation]
set abox [instance list abutment]
delete
if {$abox != ""} {box values {*}$abox}
set newinstname [getcell $gname $orient]
select cell $newinstname
set origname $newinstname
expand
# If the old instance name was not formed from the old cell name,
# then keep the old instance name.
if {[string first $old_gname $instname] != 0} {
set newinstname $instname
} else {
# The buttons "Apply" and "Okay" need to be changed for the new
# instance name
catch {.params.buttons.apply config -command \
"magic::gencell_change $newinstname $gencell_type $library {}"}
catch {.params.buttons.okay config -command \
"magic::gencell_change $newinstname $gencell_type $library {} ;\
destroy .params"}
}
if {[cellname list parents $old_gname] == []} {
# If the original cell has no intances left, delete it. It can
# be regenerated if and when necessary.
cellname delete $old_gname -noprompt
select cell $origname
}
} else {
select cell $instname
set orient [instance list orientation]
set abox [instance list abutment]
delete
# There is no cell of this name, so generate one and instantiate it.
if {$abox != ""} {box values {*}$abox}
set newinstname [magic::gencell_create $gencell_type $library $parameters $orient]
select cell $newinstname
set origname $newinstname
# If the old instance name was not formed from the old cell name,
# then keep the old instance name.
if {[string first $old_gname $instname] != 0} {
set newinstname $instname
} else {
# The buttons "Apply" and "Okay" need to be changed for the new
# instance name
catch {.params.buttons.apply config -command \
"magic::gencell_change $newinstname $gencell_type $library {}"}
catch {.params.buttons.okay config -command \
"magic::gencell_change $newinstname $gencell_type $library {} ;\
destroy .params"}
}
# If the old cell is not used anywhere, delete it
if {[cellname list parents $old_gname] == []} {
# If the original cell has no intances left, delete it. It can
# be regenerated if and when necessary.
cellname delete $old_gname -noprompt
select cell $origname
}
}
identify $newinstname
eval "box values $savebox"
units {*}$curunits
# Update window
if {$gname != $old_gname} {
catch {.params.title.glab configure -text "$gname"}
}
if {$instname != $newinstname} {
catch {.params.title.ient delete 0 end}
catch {.params.title.ient insert 0 "$newinstname"}
}
resumeall
drc check ;# force a DRC update if DRC is on.
redraw
}
#-------------------------------------------------------------
# gencell_change_orig
#
# Original version: Redraw a gencell with new parameters,
# without changing the cell itself.
#-------------------------------------------------------------
proc magic::gencell_change_orig {instname gencell_type library parameters} {
global Opts
suspendall
set newinstname $instname
if {$parameters == {}} {
# Get device defaults
set pdefaults [${library}::${gencell_type}_defaults]
# Pull user-entered values from dialog
set parameters [dict merge $pdefaults [magic::gencell_getparams]]
set newinstname [.params.title.ient get]
if {$newinstname == "(default)"} {set newinstname $instname}
if {$newinstname == $instname} {set newinstname $instname}
if {[instance list exists $newinstname] != ""} {set newinstname $instname}
}
if {[dict exists $parameters gencell]} {
# Setting special parameter "gencell" forces the gencell to change type
set gencell_type [dict get $parameters gencell]
}
if {[catch {set parameters [${library}::${gencell_type}_check $parameters]} \
checkerr]} {
puts stderr $checkerr
}
magic::gencell_setparams $parameters
if {[dict exists $parameters gencell]} {
set parameters [dict remove $parameters gencell]
}
set gname [instance list celldef $instname]
# Guard against instance having been deleted
if {$gname == ""} {
resumeall
return
}
set curunits [units]
units internal
set savebox [box values]
catch {setpoint 0 0 $Opts(focus)}
if [dict exists $parameters nocell] {
select cell $instname
delete
if {[catch {set newinst [${library}::${gencell_type}_draw $parameters]} \
drawerr]} {
puts stderr $drawerr
}
select cell $newinst
} else {
pushstack $gname
select cell
tech unlock *
erase *
if {[catch {${library}::${gencell_type}_draw $parameters} drawerr]} {
puts stderr $drawerr
}
property parameters $parameters
property gencell ${gencell_type}
tech revert
popstack
select cell $instname
}
identify $newinstname
eval "box values $savebox"
units {*}$curunits
resumeall
redraw
}
#-------------------------------------------------------------
# Assign a unique name for a gencell
#
# Note: This depends on the unlikelihood of the name
# existing in a cell on disk. Only cells in memory are
# checked for name collisions. Since the names will go
# into SPICE netlists, names must be unique when compared
# in a case-insensitive manner. Using base-36 (alphabet and
# numbers), each gencell name with 6 randomized characters
# has a 1 in 4.6E-10 chance of reappearing.
#-------------------------------------------------------------
proc magic::get_gencell_name {gencell_type} {
while {true} {
set postfix ""
for {set i 0} {$i < 6} {incr i} {
set pint [expr 48 + int(rand() * 36)]
if {$pint > 57} {set pint [expr $pint + 39]}
append postfix [format %c $pint]
}
if {[cellname list exists ${gencell_type}_$postfix] == 0} {break}
}
return ${gencell_type}_$postfix
}
#----------------------------------------------------------------
# get_gencell_hash
#
# A better approach to the above. Take the parameter
# dictionary, and run all the values through a hash function
# to generate a 30-bit value, then convert to base32. This
# gives a result that is repeatable for the same set of
# parameter values with a very low probability of a collision.
#
# The hash function is murmur3 but reduced from 32 to 30 bits
# so that the result can form a 6-character value in base36
# with all characters being valid for a SPICE subcell name
# (e.g., alphanumeric only and case-insensitive). This
# reduces the space from ~4 billion unique suffixes to ~1
# billion; however, even a complex mixed-signal chip design
# is unlikely to have more than a few hundred unique parameter
# sets for any given device.
#
# Code courtesy of ChatGPT, derived from my implementation.
#----------------------------------------------------------------
proc magic::get_gencell_hash {parameters} {
# Canonicalize: sort by key
set keys [lsort [dict keys $parameters]]
# Build input string (values only, but delimited)
set input ""
foreach k $keys {
set value [magic::normalize_value [dict get $parameters $k]]
append input "${value};"
}
# Compute Murmur hash
set hash [magic::murmur3_32 $input]
# Convert to 6-character base32
set cvals ""
for {set i 0} {$i < 6} {incr i} {
set oval [expr {($hash >> ($i * 5)) & 0x1f}]
if {$oval < 8} {
set bval [expr {$oval + 50}] ;# '2'-'9'
} elseif {$oval < 16} {
set bval [expr {$oval + 57}] ;# 'A'-'H'
} elseif {$oval < 21} {
set bval [expr {$oval + 58}] ;# 'J'-'N'
} else {
set bval [expr {$oval + 59}] ;# 'P'-'Z'
}
append cvals [binary format c* $bval]
}
return $cvals
}
#-------------------------------------------------------------
# gencell_create
#
# Instantiate a new gencell called $gname. If $gname
# does not already exist, create it by calling its
# drawing routine.
#
# Don't rely on pushbox/popbox since we don't know what
# the drawing routine is going to do to the stack!
#-------------------------------------------------------------
proc magic::gencell_create {gencell_type library parameters {orient 0}} {
global Opts
suspendall
set newinstname ""
# Get device defaults
if {$parameters == {}} {
# Pull user-entered values from dialog
set dialogparams [magic::gencell_getparams]
if {[dict exists $dialogparams gencell]} {
# Setting special parameter "gencell" forces the gencell to change type
set gencell_type [dict get $dialogparams gencell]
}
set pdefaults [${library}::${gencell_type}_defaults]
set parameters [dict merge $pdefaults $dialogparams]
set newinstname [.params.title.ient get]
if {$newinstname == "(default)"} {set newinstname ""}
if {[instance list exists $newinstname] != ""} {set newinstname ""}
} else {
if {[dict exists $parameters gencell]} {
# Setting special parameter "gencell" forces the gencell to change type
set gencell_type [dict get $parameters gencell]
}
set pdefaults [${library}::${gencell_type}_defaults]
set parameters [dict merge $pdefaults $parameters]
}
if {[catch {set parameters [${library}::${gencell_type}_check $parameters]} \
checkerr]} {
puts stderr $checkerr
}
magic::gencell_setparams $parameters
if {[dict exists $parameters gencell]} {
set parameters [dict remove $parameters gencell]
}
set curunits [units]
units internal
set savebox [box values]
catch {setpoint 0 0 $Opts(focus)}
if [dict exists $parameters nocell] {
if {[catch {set instname [${library}::${gencell_type}_draw $parameters]} \ drawerr]} {
puts stderr $drawerr
}
set gname [instance list celldef $instname]
eval "box values $savebox"
} else {
set gsuffix [magic::get_gencell_hash ${parameters}]
set gname ${gencell_type}_${gsuffix}
cellname create $gname
pushstack $gname
if {[catch {${library}::${gencell_type}_draw $parameters} drawerr]} {
puts stderr $drawerr
}
property library $library
property gencell $gencell_type
property parameters $parameters
popstack
eval "box values $savebox"
set instname [getcell $gname $orient]
expand
}
if {$newinstname != ""} {
identify $newinstname
set instname $newinstname
}
units {*}$curunits
resumeall
redraw
return $instname
}
#-----------------------------------------------------
# Add a standard entry parameter to the gencell window
#-----------------------------------------------------
proc magic::add_entry {pname ptext parameters} {
if [dict exists $parameters $pname] {
set value [dict get $parameters $pname]
} else {
set value ""
}
set numrows [lindex [grid size .params.body.area.edits] 1]
label .params.body.area.edits.${pname}_lab -text $ptext
entry .params.body.area.edits.${pname}_ent -background white -textvariable magic::${pname}_val
grid .params.body.area.edits.${pname}_lab -row $numrows -column 0 \
-sticky ens -ipadx 5 -ipady 2
grid .params.body.area.edits.${pname}_ent -row $numrows -column 1 \
-sticky ewns -ipadx 5 -ipady 2
.params.body.area.edits.${pname}_ent insert end $value
set magic::${pname}_val $value
}
#----------------------------------------------------------
# Default entry callback, without any dependencies. Each
# parameter changed causes an update to the dialog. Also
# add default callbacks on checkboxes and choice menus,
# using a null function which does not get executed, but
# the dialog gets updated afterward.
#----------------------------------------------------------
proc magic::add_check_callbacks {gencell_type library} {
set wlist [winfo children .params.body.area.edits]
foreach w $wlist {
if {[regexp {\.params\.body\.area\.edits\.(.+)_.+} $w valid pname]} {
magic::add_dependency \{\} $gencell_type $library $pname
}
}
}
#----------------------------------------------------------
# Add a dependency between entries. When one updates, the
# others will be recomputed according to the callback
# function.
#
# The callback function is passed the value of all
# parameters for the device, overridden by the values
# in the dialog. The routine computes the dependent
# values and writes them back to the parameter dictionary.
# The callback function must return the modified parameters
# dictionary.
#
# Also handle dependencies on checkboxes and selection lists
#
# If dependency callbacks exist, then chain them together.
# A final default dependency will be added to all entries
# to run the "check" procedure for the device. Dependencies
# that are more targeted get run first.
#
# NOTE: The "check" procedure must be the first in the
# list, as otherwise, any invalid entry that is corrected
# by the check callback will have been used to evaluate
# dependent values.
#----------------------------------------------------------
proc magic::add_dependency {callback gencell_type library args} {
if {[llength $args] == 0} {
# If no arguments are given, do for all parameters
set parameters ${library}::${gencell_type}_defaults
magic::add_dependency $callback $gencell_type $library \
{*}[dict keys $parameters]
return
}
set clist [winfo children .params.body.area.edits]
foreach pname $args {
if {[lsearch $clist .params.body.area.edits.${pname}_ent] >= 0} {
# Add callback on enter or focus out
set oldbind [bind .params.body.area.edits.${pname}_ent <Return>]
set newbind "magic::update_dialog $callback $pname $gencell_type $library"
if {$oldbind != {}} {set newbind "$oldbind ; $newbind"}
bind .params.body.area.edits.${pname}_ent <Return> $newbind
set oldbind [bind .params.body.area.edits.${pname}_ent <FocusOut>]
set newbind "magic::update_dialog $callback $pname $gencell_type $library"
if {$oldbind != {}} {set newbind "$oldbind ; $newbind"}
bind .params.body.area.edits.${pname}_ent <FocusOut> $newbind
} elseif {[lsearch $clist .params.body.area.edits.${pname}_chk] >= 0} {
# Add callback on checkbox change state
set oldcmd [.params.body.area.edits.${pname}_chk cget -command]
set newcmd "magic::update_dialog $callback $pname $gencell_type $library"
if {$oldcmd != {}} {set newcmd "$oldcmd ; $newcmd"}
.params.body.area.edits.${pname}_chk configure -command $newcmd
} elseif {[lsearch $clist .params.body.area.edits.${pname}_sel] >= 0} {
set smenu .params.body.area.edits.${pname}_sel.menu
set sitems [${smenu} index end]
for {set idx 0} {$idx <= $sitems} {incr idx} {
set oldcmd [${smenu} entrycget $idx -command]
set newcmd "magic::update_dialog $callback $pname $gencell_type $library"
if {$oldcmd != {}} {set newcmd "$oldcmd ; $newcmd"}
${smenu} entryconfigure $idx -command $newcmd
}
}
}
}
#----------------------------------------------------------
# Execute callback procedure, then run bounds checks
#----------------------------------------------------------
proc magic::update_dialog {callback pname gencell_type library} {
set pdefaults [${library}::${gencell_type}_defaults]
set parameters [dict merge $pdefaults [magic::gencell_getparams]]
if {[dict exists $parameters gencell]} {
# Setting special parameter "gencell" forces the gencell to change type
set gencell_type [dict get $parameters gencell]
set pdefaults [${library}::${gencell_type}_defaults]
set parameters [dict merge $pdefaults [magic::gencell_getparams]]
}
if {[catch {set parameters [${library}::${gencell_type}_check $parameters]} \
checkerr]} {
puts stderr $checkerr
}
if {$callback != {}} {
set parameters [$callback $pname $parameters]
}
magic::gencell_setparams $parameters
}
#----------------------------------------------------------
# Add a standard checkbox parameter to the gencell window
#----------------------------------------------------------
proc magic::add_checkbox {pname ptext parameters} {
if [dict exists $parameters $pname] {
set value [dict get $parameters $pname]
} else {
set value ""
}
set numrows [lindex [grid size .params.body.area.edits] 1]
label .params.body.area.edits.${pname}_lab -text $ptext
checkbutton .params.body.area.edits.${pname}_chk -variable magic::${pname}_val
grid .params.body.area.edits.${pname}_lab -row $numrows -column 0 -sticky ens
grid .params.body.area.edits.${pname}_chk -row $numrows -column 1 -sticky wns
set magic::${pname}_val $value
}
#----------------------------------------------------------
# Add a message box (informational, not editable) to the
# gencell window. Note that the text does not have to be
# in the parameter list, as it can be upated through the
# textvariable name.
#----------------------------------------------------------
proc magic::add_message {pname ptext parameters {color blue}} {
if [dict exists $parameters $pname] {
set value [dict get $parameters $pname]
} else {
set value ""
}
set numrows [lindex [grid size .params.body.area.edits] 1]
label .params.body.area.edits.${pname}_lab -text $ptext
label .params.body.area.edits.${pname}_txt -text $value \
-foreground $color -textvariable magic::${pname}_val
grid .params.body.area.edits.${pname}_lab -row $numrows -column 0 -sticky ens
grid .params.body.area.edits.${pname}_txt -row $numrows -column 1 -sticky wns
}
#----------------------------------------------------------
# Add a selectable-list parameter to the gencell window
# (NOTE: Use magic::add_dependency to add a callback to
# the selection list choice.)
#----------------------------------------------------------
proc magic::add_selectlist {pname ptext all_values parameters {itext ""}} {
if [dict exists $parameters $pname] {
set value [dict get $parameters $pname]
} else {
set value $itext
}
set numrows [lindex [grid size .params.body.area.edits] 1]
label .params.body.area.edits.${pname}_lab -text $ptext
menubutton .params.body.area.edits.${pname}_sel -menu .params.body.area.edits.${pname}_sel.menu \
-relief groove -text ${value}
grid .params.body.area.edits.${pname}_lab -row $numrows -column 0 -sticky ens
grid .params.body.area.edits.${pname}_sel -row $numrows -column 1 -sticky wns
menu .params.body.area.edits.${pname}_sel.menu -tearoff 0
foreach item ${all_values} {
set cmdtxt ".params.body.area.edits.${pname}_sel configure -text $item"
.params.body.area.edits.${pname}_sel.menu add radio -label $item \
-variable magic::${pname}_val -value $item \
-command $cmdtxt
}
set magic::${pname}_val $value
}
#----------------------------------------------------------
# Add a selectable-list parameter to the gencell window
# Unlike the routine above, it returns the index of the
# selection, not the selection itself. This is useful for
# keying the selection to other parameter value lists.
#----------------------------------------------------------
proc magic::add_selectindex {pname ptext all_values parameters {ival 0}} {
if [dict exists $parameters $pname] {
set value [dict get $parameters $pname]
} else {
set value $ival
}
set numrows [lindex [grid size .params.body.area.edits] 1]
label .params.body.area.edits.${pname}_lab -text $ptext
menubutton .params.body.area.edits.${pname}_sel -menu .params.body.area.edits.${pname}_sel.menu \
-relief groove -text [lindex ${all_values} ${value}]
grid .params.body.area.edits.${pname}_lab -row $numrows -column 0 -sticky ens
grid .params.body.area.edits.${pname}_sel -row $numrows -column 1 -sticky wns
menu .params.body.area.edits.${pname}_sel.menu -tearoff 0
set idx 0
foreach item ${all_values} {
.params.body.area.edits.${pname}_sel.menu add radio -label $item \
-variable magic::${pname}_val -value $idx \
-command ".params.body.area.edits.${pname}_sel configure -text $item"
incr idx
}
set magic::${pname}_val $value
}
#-------------------------------------------------------------
# gencell_defaults ---
#
# Set all parameters for a device. Start by calling the base
# device's default value list to generate a dictionary. Then
# parse all values passed in 'parameters', overriding any
# defaults with the passed values.
#-------------------------------------------------------------
proc magic::gencell_defaults {gencell_type library parameters} {
set basedict [${library}::${gencell_type}_defaults]
set newdict [dict merge $basedict $parameters]
return $newdict
}
#-------------------------------------------------------------
# Command tag callback on "select". "select cell" should
# cause the parameter dialog window to update to reflect the
# selected cell. If a cell is unselected, then revert to the
# default 'Create' window.
#-------------------------------------------------------------
proc magic::gencell_update {{command {}}} {
if {[info level] <= 1} {
if {![catch {set state [wm state .params]}]} {
if {[wm state .params] == "normal"} {
if {$command == "cell"} {
# If multiple devices are selected, choose the first in
# the list returned by "what -list".
set instname [lindex [lindex [lindex [what -list] 2] 0] 0]
magic::gencell_dialog $instname {} {} {}
}
}
}
}
}
#-------------------------------------------------------------
# updateParamsScrollRegion ---
#
# Change the canvas size when the parameter window changes
# size so that the scrollbar works correctly.
#-------------------------------------------------------------
proc updateParamsScrollRegion {} {
set bbox [.params.body.area bbox all]
.params.body.area configure -scrollregion $bbox
.params.body.area configure -width [lindex $bbox 2]
.params.body.area configure -height [lindex $bbox 3]
}
#-------------------------------------------------------------
# gencell_dialog ---
#
# Create the dialog window for entering device parameters. The
# general procedure then calls the dialog setup for the specific
# device.
#
# 1) If gname is NULL and gencell_type is set, then we
# create a new cell of type gencell_type.
# 2) If gname is non-NULL, then we edit the existing
# cell of type $gname.
# 3) If gname is non-NULL and gencell_type or library
# is NULL or unspecified, then we derive the gencell_type
# and library from the existing cell's property strings
#
# The device setup should be built using the API that defines
# these procedures:
#
# magic::add_entry Single text entry window
# magic::add_checkbox Single checkbox
# magic::add_selectlist Pull-down menu with list of selections
#
#-------------------------------------------------------------
proc magic::gencell_dialog {instname gencell_type library parameters} {
if {$gencell_type == {}} {
# Revert to default state for the device that was previously
# shown in the parameter window.
if {![catch {set state [wm state .params]}]} {
if {$instname == {}} {
set devstr [.params.title.lab1 cget -text]
if {$devstr == "Edit device:"} {
set gencell_type [.params.title.lab2 cget -text]
set library [.params.title.lab4 cget -text]
} else {
return
}
}
}
}
if {$instname != {}} {
# Remove any array component of the instance name
set baseinstname [string map {\\ ""} $instname]
if {[regexp {^(.*)\[[0-9,]+\]$} $baseinstname valid instroot]} {
set originstname $instname
set instname $instroot
} else {
set instroot ""
}
set gname [instance list celldef [subst $instname]]
if {$gname == ""} {
# Check if name inherited brackets but is not an array
if {$instroot != ""} {
set testinstname [string map {\[ \\\[ \] \\\]} $baseinstname]
set gname [instance list celldef [subst $testinstname]]
set instname $originstname
}
}
set gencell_type [cellname list property $gname gencell]
if {$library == {}} {
set library [cellname list property $gname library]
}
if {$parameters == {}} {
set parameters [cellname list property $gname parameters]
}
if {$gencell_type == {} || $library == {}} {return}
if {$parameters == {}} {
set parameters [${library}::${gencell_type}_defaults]
}
# If the default parameters contain "nocell", then set the
# standard parameters for fixed devices from the instance
if {[dict exists $parameters nocell]} {
select cell $instname
set arcount [array -list count]
set arpitch [array -list pitch]
dict set parameters nx [expr [lindex $arcount 1] - [lindex $arcount 0] + 1]
dict set parameters ny [expr [lindex $arcount 3] - [lindex $arcount 2] + 1]
dict set parameters pitchx [lindex $arpitch 0]
dict set parameters pitchy [lindex $arpitch 1]
}
set ttext "Edit device"
set itext $instname
} else {
set parameters [magic::gencell_defaults $gencell_type $library $parameters]
set gname "(default)"
set itext "(default)"
set ttext "New device"
}
# Destroy children, not the top-level window, or else window keeps
# bouncing around every time something is changed.
if {[catch {toplevel .params}]} {
.params.title.lab1 configure -text "${ttext}:"
.params.title.lab2 configure -text "$gencell_type"
.params.title.lab4 configure -text "$library"
.params.title.glab configure -foreground blue -text "$gname"
.params.title.ient delete 0 end
.params.title.ient insert 0 "$itext"
foreach child [winfo children .params.body.area.edits] {
destroy $child
}
foreach child [winfo children .params.buttons] {
destroy $child
}
} else {
frame .params.title
label .params.title.lab1 -text "${ttext}:"
label .params.title.lab2 -foreground blue -text "$gencell_type"
label .params.title.lab3 -text "Library:"
label .params.title.lab4 -foreground blue -text "$library"
label .params.title.clab -text "Cellname:"
label .params.title.glab -foreground blue -text "$gname"
label .params.title.ilab -text "Instance:"
entry .params.title.ient -foreground brown -background white
.params.title.ient insert 0 "$itext"
ttk::separator .params.sep
frame .params.body
canvas .params.body.area
scrollbar .params.body.sb -command {.params.body.area yview}
frame .params.buttons
grid .params.title.lab1 -padx 5 -row 0 -column 0
grid .params.title.lab2 -padx 5 -row 0 -column 1 -sticky w
grid .params.title.lab3 -padx 5 -row 0 -column 2
grid .params.title.lab4 -padx 5 -row 0 -column 3 -sticky w
grid .params.title.clab -padx 5 -row 1 -column 0
grid .params.title.glab -padx 5 -row 1 -column 1 -sticky w
grid .params.title.ilab -padx 5 -row 1 -column 2
grid .params.title.ient -padx 5 -row 1 -column 3 -sticky ew
grid columnconfigure .params.title 3 -weight 1
grid .params.body.area -row 0 -column 0 -sticky nsew
grid .params.body.sb -row 0 -column 1 -sticky ns
grid columnconfigure .params.body 0 -weight 1
grid columnconfigure .params.body 1 -weight 0
grid rowconfigure .params.body 0 -weight 1
grid .params.title -row 0 -column 0 -sticky nsew
grid .params.sep -row 1 -column 0 -sticky nsew
grid .params.body -row 2 -column 0 -sticky nsew
grid .params.buttons -row 3 -column 0 -sticky nsew
grid rowconfigure .params 0 -weight 0
grid rowconfigure .params 1 -weight 0
grid rowconfigure .params 2 -weight 1
grid rowconfigure .params 3 -weight 0
grid columnconfigure .params 0 -weight 1
frame .params.body.area.edits
.params.body.area create window 0 0 -anchor nw -window .params.body.area.edits
.params.body.area config -yscrollcommand {.params.body.sb set}
# Make sure scrollbar tracks any window size changes
bind .params <Configure> updateParamsScrollRegion
# Allow mouse wheel to scroll the window up and down.
bind .params.body.area <Button-4> {.params.body.area yview scroll -1 units}
bind .params.body.area <Button-5> {.params.body.area yview scroll +1 units}
}
if {$instname == {}} {
button .params.buttons.apply -text "Create" -command \
[subst {set inst \[magic::gencell_create \
$gencell_type $library {}\] ; \
magic::gencell_dialog \$inst $gencell_type $library {} }]
button .params.buttons.okay -text "Create and Close" -command \
[subst {set inst \[magic::gencell_create \
$gencell_type $library {}\] ; \
magic::gencell_dialog \$inst $gencell_type $library {} ; \
destroy .params}]
} else {
set instname [string map {\[ \\\[ \] \\\]} $instname]
button .params.buttons.apply -text "Apply" -command \
"magic::gencell_change $instname $gencell_type $library {}"
button .params.buttons.okay -text "Okay" -command \
"magic::gencell_change $instname $gencell_type $library {} ;\
destroy .params"
}
button .params.buttons.reset -text "Reset" -command \
"magic::gencell_dialog {} ${gencell_type} ${library} {}"
button .params.buttons.close -text "Close" -command {destroy .params}
pack .params.buttons.apply -padx 5 -ipadx 5 -ipady 2 -side left
pack .params.buttons.okay -padx 5 -ipadx 5 -ipady 2 -side left
pack .params.buttons.close -padx 5 -ipadx 5 -ipady 2 -side right
pack .params.buttons.reset -padx 5 -ipadx 5 -ipady 2 -side right
# Invoke the callback procedure that creates the parameter entries
${library}::${gencell_type}_dialog $parameters
# Add standard callback to all entry fields to run parameter bounds checks
magic::add_check_callbacks $gencell_type $library
# Make sure the window is raised
raise .params
}
#-------------------------------------------------------------
# Implementation of murmur3 hash, 32 bits
# Code courtesy of ChatGPT.
#-------------------------------------------------------------
proc magic::murmur3_32 {key {seed 0}} {
set length [string length $key]
set c1 0xcc9e2d51
set c2 0x1b873593
set h1 $seed
# Body (process 4 bytes at a time)
set nblocks [expr {$length / 4}]
for {set i 0} {$i < $nblocks} {incr i} {
binary scan [string range $key [expr {$i*4}] [expr {$i*4+3}]] i k1
set k1 [expr {($k1 * $c1) & 0xffffffff}]
set k1 [expr {(($k1 << 15) | (($k1 & 0xffffffff) >> 17)) & 0xffffffff}]
set k1 [expr {($k1 * $c2) & 0xffffffff}]
set h1 [expr {$h1 ^ $k1}]
set h1 [expr {(($h1 << 13) | (($h1 & 0xffffffff) >> 19)) & 0xffffffff}]
set h1 [expr {(($h1 * 5) + 0xe6546b64) & 0xffffffff}]
}
# Tail
set k1 0
set tail_index [expr {$nblocks * 4}]
set tail [string range $key $tail_index end]
set remaining [string length $tail]
if {$remaining >= 3} {
binary scan [string index $tail 2] c b
set k1 [expr {$k1 ^ (($b & 0xff) << 16)}]
}
if {$remaining >= 2} {
binary scan [string index $tail 1] c b
set k1 [expr {$k1 ^ (($b & 0xff) << 8)}]
}
if {$remaining >= 1} {
binary scan [string index $tail 0] c b
set k1 [expr {$k1 ^ ($b & 0xff)}]
set k1 [expr {($k1 * $c1) & 0xffffffff}]
set k1 [expr {(($k1 << 15) | (($k1 & 0xffffffff) >> 17)) & 0xffffffff}]
set k1 [expr {($k1 * $c2) & 0xffffffff}]
set h1 [expr {$h1 ^ $k1}]
}
# Finalization (fmix)
set h1 [expr {$h1 ^ $length}]
set h1 [expr {$h1 ^ (($h1 & 0xffffffff) >> 16)}]
set h1 [expr {($h1 * 0x85ebca6b) & 0xffffffff}]
set h1 [expr {$h1 ^ (($h1 & 0xffffffff) >> 13)}]
set h1 [expr {($h1 * 0xc2b2ae35) & 0xffffffff}]
set h1 [expr {$h1 ^ (($h1 & 0xffffffff) >> 16)}]
return $h1
}
#-------------------------------------------------------------
# Numerical normalization: Avoid having different suffixes
# for cells with the same parameter values due to Tcl handling
# numerical values as strings, which makes "2", "2.0", and "2e0"
# all separate values. If hashed on the verbatim values, then
# the same device with the same parameters can have many
# different cell names, even though the layout is exactly the
# same. Avoid this by detecting when a parameter value is
# numeric and enforcing a consistent format (fixed precision,
# four decimal places).
#
# Code courtesy of ChatGPT
#-------------------------------------------------------------
proc magic::normalize_value {value} {
# Detect if value is numeric
if {[string is double -strict $value]} {
set num [expr {double($value)}]
# Check if effectively integer
if {abs($num - round($num)) < 1e-9} {
return [format "%d" [expr {int(round($num))}]]
}
# Otherwise, format to fixed precision (3 decimal places)
set str [format "%.3f" $num]
# Strip trailing zeros
regsub {\.?0+$} $str "" str
return $str
}
# Non-numeric: return as-is
return $value
}