#-----------------------------------------------------
# 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
}

#-------------------------------------------------------------
