magic/tcltk/toolkit.tcl

1428 lines
48 KiB
Tcl
Raw Normal View History

#-----------------------------------------------------
# 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
#--------------------------------------------------------------
# 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 select "[magic::tag 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 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}i ${posy}i
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 bbox [box values]
set posx [lindex $bbox 2]
set posy [lindex $bbox 1]
box position ${posx}i ${posy}i
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}} {
box size 1um 1um
paint $layer
label $pinname FreeSans 16 0 0 0 c $layer
port make $portnum
box move s 2um
}
# 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
# Create a new subcircuit
load $subname -quiet
box 0 0 0 0
# Generate pins
if {[llength $subpins] > 0} {
set pinlist [split $subpins]
set i 0
foreach pin $pinlist {
# Escape [ and ] in pin name
set pin_esc [string map {\[ \\\[ \] \\\]} $pin]
magic::create_new_pin $pin_esc $i
incr i
}
}
# Set initial position for importing cells
box size 0 0
set posx 0
set posy [expr {round(3 / [cif scale out])}]
box position ${posx}i ${posy}i
# Seed layout with components
foreach comp $complist {
set pinlist {}
set paramlist {}
# NOTE: This routine deals with subcircuit calls and devices
# with models. It needs to determine when a device is instantiated
# without a model, and ignore such devices.
# 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]
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 "'"]]
}
}
}
# 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 {}
lappend outparts "magic::gencell $libdev $instname"
# 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]
}
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
}
#--------------------------------------------------------------
# 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
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 {^[xmcrdq]([^ \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
magic::generate_layout_add $subname $subpins $complist $library
set subname ""
set subpins ""
set complist {}
} elseif {[regexp -nocase {^[xmcrdq]([^ \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} {
magic::generate_layout_add $topname "" $toplist $library
}
# 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 $gencell_type 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.edits]
foreach s $slist {
if {[regexp {^.params.edits.(.*)_ent$} $s valid pname] != 0} {
set value [subst \$magic::${pname}_val]
} elseif {[regexp {^.params.edits.(.*)_chk$} $s valid pname] != 0} {
set value [subst \$magic::${pname}_val]
} elseif {[regexp {^.params.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.edits]
foreach s $slist {
# ignore .params.edits.gencell_sel, as that does not exist in the
# parameters dictionary
if {$s == ".params.edits.gencell_sel"} {continue}
if {[regexp {^.params.edits.(.*)_ent$} $s valid pname] != 0} {
set value [dict get $parameters $pname]
set magic::${pname}_val $value
} elseif {[regexp {^.params.edits.(.*)_chk$} $s valid pname] != 0} {
set value [dict get $parameters $pname]
set magic::${pname}_val $value
} elseif {[regexp {^.params.edits.(.*)_sel$} $s valid pname] != 0} {
set value [dict get $parameters $pname]
set magic::${pname}_val $value
.params.edits.${pname}_sel configure -text $value
} elseif {[regexp {^.params.edits.(.*)_txt$} $s valid pname] != 0} {
if {[dict exists $parameters $pname]} {
set value [dict get $parameters $pname]
.params.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]]
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 old_gname [instance list celldef $instname]
set gsuffix [magic::get_gencell_hash ${parameters}]
set gname ${gencell_type}_${gsuffix}
# 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 snaptype [snap list]
snap 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
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
}
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
}
} 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
# 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"}
}
}
identify $newinstname
eval "box values $savebox"
snap $snaptype
# 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
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 snaptype [snap list]
snap 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"
snap $snaptype
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 similar to elfhash but reduced from 32
# to 30 bits so that the result can form a 6-character value
# in base32 with all characters being valid for a SPICE subcell
# name (e.g., alphanumeric only and case-insensitive).
#----------------------------------------------------------------
proc magic::get_gencell_hash {parameters} {
set hash 0
# Apply hash
dict for {key value} $parameters {
foreach s [split $value {}] {
set hash [expr {($hash << 4) + [scan $s %c]}]
set high [expr {$hash & 0x03c0000000}]
set hash [expr {$hash ^ ($high >> 30)}]
set hash [expr {$hash & (~$high)}]
}
}
# Divide hash up into 5 bit values and convert to base32
# using letters A-Z less I and O, and digits 2-9.
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}]
} elseif {$oval < 16} {
set bval [expr {$oval + 57}]
} elseif {$oval < 21} {
set bval [expr {$oval + 58}]
} else {
set bval [expr {$oval + 59}]
}
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 snaptype [snap list]
snap 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
}
snap $snaptype
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.edits] 1]
label .params.edits.${pname}_lab -text $ptext
entry .params.edits.${pname}_ent -background white -textvariable magic::${pname}_val
grid .params.edits.${pname}_lab -row $numrows -column 0 \
-sticky ens -ipadx 5 -ipady 2
grid .params.edits.${pname}_ent -row $numrows -column 1 \
-sticky ewns -ipadx 5 -ipady 2
.params.edits.${pname}_ent insert end $value
set magic::${pname}_val $value
}
#----------------------------------------------------------
# Default entry callback, without any dependencies. Each
# parameter changed
#----------------------------------------------------------
proc magic::add_check_callbacks {gencell_type library} {
set wlist [winfo children .params.edits]
foreach w $wlist {
if {[regexp {\.params\.edits\.(.+)_ent} $w valid pname]} {
# Add callback on enter or focus out
bind $w <Return> \
"magic::update_dialog {} $pname $gencell_type $library"
bind $w <FocusOut> \
"magic::update_dialog {} $pname $gencell_type $library"
}
}
}
#----------------------------------------------------------
# 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
#----------------------------------------------------------
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.edits]
foreach pname $args {
if {[lsearch $clist .params.edits.${pname}_ent] >= 0} {
# Add callback on enter or focus out
bind .params.edits.${pname}_ent <Return> \
"magic::update_dialog $callback $pname $gencell_type $library"
bind .params.edits.${pname}_ent <FocusOut> \
"magic::update_dialog $callback $pname $gencell_type $library"
} elseif {[lsearch $clist .params.edits.${pname}_chk] >= 0} {
# Add callback on checkbox change state
.params.edits.${pname}_chk configure -command \
"magic::update_dialog $callback $pname $gencell_type $library"
} elseif {[lsearch $clist .params.edits.${pname}_sel] >= 0} {
set smenu .params.edits.${pname}_sel.menu
set sitems [${smenu} index end]
for {set idx 0} {$idx <= $sitems} {incr idx} {
set curcommand [${smenu} entrycget $idx -command]
${smenu} entryconfigure $idx -command "$curcommand ; \
magic::update_dialog $callback $pname $gencell_type $library"
}
}
}
}
#----------------------------------------------------------
# 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 {$callback != {}} {
set parameters [$callback $pname $parameters]
}
if {[catch {set parameters [${library}::${gencell_type}_check $parameters]} \
checkerr]} {
puts stderr $checkerr
}
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.edits] 1]
label .params.edits.${pname}_lab -text $ptext
checkbutton .params.edits.${pname}_chk -variable magic::${pname}_val
grid .params.edits.${pname}_lab -row $numrows -column 0 -sticky ens
grid .params.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.edits] 1]
label .params.edits.${pname}_lab -text $ptext
label .params.edits.${pname}_txt -text $value \
-foreground $color -textvariable magic::${pname}_val
grid .params.edits.${pname}_lab -row $numrows -column 0 -sticky ens
grid .params.edits.${pname}_txt -row $numrows -column 1 -sticky wns
}
#----------------------------------------------------------
# Add a selectable-list parameter to the gencell window
#----------------------------------------------------------
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.edits] 1]
label .params.edits.${pname}_lab -text $ptext
menubutton .params.edits.${pname}_sel -menu .params.edits.${pname}_sel.menu \
-relief groove -text ${value}
grid .params.edits.${pname}_lab -row $numrows -column 0 -sticky ens
grid .params.edits.${pname}_sel -row $numrows -column 1 -sticky wns
menu .params.edits.${pname}_sel.menu -tearoff 0
foreach item ${all_values} {
.params.edits.${pname}_sel.menu add radio -label $item \
-variable magic::${pname}_val -value $item \
-command ".params.edits.${pname}_sel configure -text $item"
}
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.edits] 1]
label .params.edits.${pname}_lab -text $ptext
menubutton .params.edits.${pname}_sel -menu .params.edits.${pname}_sel.menu \
-relief groove -text [lindex ${all_values} ${value}]
grid .params.edits.${pname}_lab -row $numrows -column 0 -sticky ens
grid .params.edits.${pname}_sel -row $numrows -column 1 -sticky wns
menu .params.edits.${pname}_sel.menu -tearoff 0
set idx 0
foreach item ${all_values} {
.params.edits.${pname}_sel.menu add radio -label $item \
-variable magic::${pname}_val -value $idx \
-command ".params.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 {} {} {}
}
}
}
}
}
#-------------------------------------------------------------
# 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 instname [string map {\\ ""} $instname]
if {[regexp {^(.*)\[[0-9,]+\]$} $instname valid instroot]} {
set instname $instroot
}
set gname [instance list celldef [subst $instname]]
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.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.edits
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
pack .params.title -fill x -expand true
pack .params.sep -fill x -expand true
pack .params.edits -side top -fill both -expand true -ipadx 5
pack .params.buttons -fill x
grid columnconfigure .params.edits 1 -weight 1
}
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 {
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
}
#-------------------------------------------------------------