diff --git a/doc/html/cellname.html b/doc/html/cellname.html index 01b87c85..99100a23 100644 --- a/doc/html/cellname.html +++ b/doc/html/cellname.html @@ -71,12 +71,15 @@ Operations on cell definitions.
rename name newname
Change the name of the cell definition name to newname. -
delete name +
delete name [-noprompt]
Delete the cell definition with name name. If cell name is a descendent of another cell, the command will be prohibited. If the cell name is currently the topmost cell in the window, the window will be loaded - with default cell "(UNNAMED)". + with default cell "(UNNAMED)". If option -noprompt + is specified, then the actions specified above happen + immediately. Otherwise, a dialog box will be raised + asking for confirmation to delete the cell.
dereference name
Perform a flush of the cell (per the "flush" command), first removing any file path associated with the cell, so diff --git a/tcltk/toolkit.tcl b/tcltk/toolkit.tcl index e2610dee..006577a5 100644 --- a/tcltk/toolkit.tcl +++ b/tcltk/toolkit.tcl @@ -15,6 +15,10 @@ # March 26, 2026 # Added behavior to handle ideal devices (resistor, capacitor, # inductor) +# April 2, 2026 +# Changed the hash to MurmurHash3, as the existing hash +# is prone to creating name collisions (rare, but not rare +# enough). #-------------------------------------------------------------- # Sets up the environment for a toolkit. The toolkit must # supply a namespace that is the "library name". For each @@ -205,7 +209,7 @@ proc magic::generate_layout_add {subname subpins complist library} { select cell $inst delete } - cellname delete $child + cellname delete $child -noprompt } } } @@ -844,11 +848,16 @@ proc magic::gencell_change {instname gencell_type library parameters} { 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} } + + # Attempt to set the new instance name as specified in the dialog. + # If the entry is "(default)" or if there is a name collision, + # revert the name to the original name. + + set newinstname [.params.title.ient get] + if {$newinstname == "(default)"} {set newinstname $instname} + if {[instance list exists $newinstname] != ""} {set newinstname $instname} + if {[dict exists $parameters gencell]} { # Setting special parameter "gencell" forces the gencell to change type set gencell_type [dict get $parameters gencell] @@ -866,6 +875,20 @@ proc magic::gencell_change {instname gencell_type library parameters} { set gsuffix [magic::get_gencell_hash ${parameters}] set gname ${gencell_type}_${gsuffix} + # Handle instance name changing first. If no parameters changed, then + # we're done. + if {$newinstname != $instname} { + identify $newinstname + # The buttons "Apply" and "Okay" need to be changed for the new + # instance name + catch {.params.buttons.apply config -command \ + "magic::gencell_change $newinstname $gencell_type $library {}"} + catch {.params.buttons.okay config -command \ + "magic::gencell_change $newinstname $gencell_type $library {} ;\ + destroy .params"} + set instname $newinstname + } + # Guard against instance having been deleted. Also, if parameters have not # changed as evidenced by the cell suffix not changing, then nothing further # needs to be done. @@ -901,31 +924,9 @@ proc magic::gencell_change {instname gencell_type library parameters} { if {$abox != ""} {box values {*}$abox} set newinstname [getcell $gname $orient] select cell $newinstname + set origname $newinstname expand - # If the old instance name was not formed from the old cell name, - # then keep the old instance name. - if {[string first $old_gname $instname] != 0} { - set newinstname $instname - } - - 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} { @@ -939,6 +940,47 @@ proc magic::gencell_change {instname gencell_type library parameters} { "magic::gencell_change $newinstname $gencell_type $library {} ;\ destroy .params"} } + + if {[cellname list parents $old_gname] == []} { + # If the original cell has no intances left, delete it. It can + # be regenerated if and when necessary. + cellname delete $old_gname -noprompt + select cell $origname + } + + } else { + select cell $instname + set orient [instance list orientation] + set abox [instance list abutment] + delete + + # There is no cell of this name, so generate one and instantiate it. + if {$abox != ""} {box values {*}$abox} + set newinstname [magic::gencell_create $gencell_type $library $parameters $orient] + select cell $newinstname + set origname $newinstname + + # If the old instance name was not formed from the old cell name, + # then keep the old instance name. + if {[string first $old_gname $instname] != 0} { + set newinstname $instname + } else { + # The buttons "Apply" and "Okay" need to be changed for the new + # instance name + catch {.params.buttons.apply config -command \ + "magic::gencell_change $newinstname $gencell_type $library {}"} + catch {.params.buttons.okay config -command \ + "magic::gencell_change $newinstname $gencell_type $library {} ;\ + destroy .params"} + } + + # If the old cell is not used anywhere, delete it + if {[cellname list parents $old_gname] == []} { + # If the original cell has no intances left, delete it. It can + # be regenerated if and when necessary. + cellname delete $old_gname -noprompt + select cell $origname + } } identify $newinstname eval "box values $savebox" @@ -1069,38 +1111,47 @@ proc magic::get_gencell_name {gencell_type} { # 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). +# The hash function is murmur3 but reduced from 32 to 30 bits +# so that the result can form a 6-character value in base36 +# with all characters being valid for a SPICE subcell name +# (e.g., alphanumeric only and case-insensitive). This +# reduces the space from ~4 billion unique suffixes to ~1 +# billion; however, even a complex mixed-signal chip design +# is unlikely to have more than a few hundred unique parameter +# sets for any given device. +# +# Code courtesy of ChatGPT, derived from my implementation. #---------------------------------------------------------------- proc magic::get_gencell_hash {parameters} { - 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)}] - } + # Canonicalize: sort by key + set keys [lsort [dict keys $parameters]] + + # Build input string (values only, but delimited) + set input "" + foreach k $keys { + set value [magic::normalize_value [dict get $parameters $k]] + append input "${value};" } - # Divide hash up into 5 bit values and convert to base32 - # using letters A-Z less I and O, and digits 2-9. + + # Compute Murmur hash + set hash [magic::murmur3_32 $input] + + # Convert to 6-character base32 set cvals "" for {set i 0} {$i < 6} {incr i} { - set oval [expr {($hash >> ($i * 5)) & 0x1f}] + 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] + set bval [expr {$oval + 50}] ;# '2'-'9' + } elseif {$oval < 16} { + set bval [expr {$oval + 57}] ;# 'A'-'H' + } elseif {$oval < 21} { + set bval [expr {$oval + 58}] ;# 'J'-'N' + } else { + set bval [expr {$oval + 59}] ;# 'P'-'Z' + } + append cvals [binary format c* $bval] } return $cvals } @@ -1674,3 +1725,99 @@ proc magic::gencell_dialog {instname gencell_type library parameters} { } #------------------------------------------------------------- +# Implementation of murmur3 hash, 32 bits +# Code courtesy of ChatGPT. +#------------------------------------------------------------- + +proc magic::murmur3_32 {key {seed 0}} { + set length [string length $key] + + set c1 0xcc9e2d51 + set c2 0x1b873593 + + set h1 $seed + + # Body (process 4 bytes at a time) + set nblocks [expr {$length / 4}] + for {set i 0} {$i < $nblocks} {incr i} { + binary scan [string range $key [expr {$i*4}] [expr {$i*4+3}]] i k1 + + set k1 [expr {($k1 * $c1) & 0xffffffff}] + set k1 [expr {(($k1 << 15) | (($k1 & 0xffffffff) >> 17)) & 0xffffffff}] + set k1 [expr {($k1 * $c2) & 0xffffffff}] + + set h1 [expr {$h1 ^ $k1}] + set h1 [expr {(($h1 << 13) | (($h1 & 0xffffffff) >> 19)) & 0xffffffff}] + set h1 [expr {(($h1 * 5) + 0xe6546b64) & 0xffffffff}] + } + + # Tail + set k1 0 + set tail_index [expr {$nblocks * 4}] + set tail [string range $key $tail_index end] + set remaining [string length $tail] + + if {$remaining >= 3} { + binary scan [string index $tail 2] c b + set k1 [expr {$k1 ^ (($b & 0xff) << 16)}] + } + if {$remaining >= 2} { + binary scan [string index $tail 1] c b + set k1 [expr {$k1 ^ (($b & 0xff) << 8)}] + } + if {$remaining >= 1} { + binary scan [string index $tail 0] c b + set k1 [expr {$k1 ^ ($b & 0xff)}] + set k1 [expr {($k1 * $c1) & 0xffffffff}] + set k1 [expr {(($k1 << 15) | (($k1 & 0xffffffff) >> 17)) & 0xffffffff}] + set k1 [expr {($k1 * $c2) & 0xffffffff}] + set h1 [expr {$h1 ^ $k1}] + } + + # Finalization (fmix) + set h1 [expr {$h1 ^ $length}] + set h1 [expr {$h1 ^ (($h1 & 0xffffffff) >> 16)}] + set h1 [expr {($h1 * 0x85ebca6b) & 0xffffffff}] + set h1 [expr {$h1 ^ (($h1 & 0xffffffff) >> 13)}] + set h1 [expr {($h1 * 0xc2b2ae35) & 0xffffffff}] + set h1 [expr {$h1 ^ (($h1 & 0xffffffff) >> 16)}] + + return $h1 +} + +#------------------------------------------------------------- +# Numerical normalization: Avoid having different suffixes +# for cells with the same parameter values due to Tcl handling +# numerical values as strings, which makes "2", "2.0", and "2e0" +# all separate values. If hashed on the verbatim values, then +# the same device with the same parameters can have many +# different cell names, even though the layout is exactly the +# same. Avoid this by detecting when a parameter value is +# numeric and enforcing a consistent format (fixed precision, +# four decimal places). +# +# Code courtesy of ChatGPT +#------------------------------------------------------------- + +proc magic::normalize_value {value} { + # Detect if value is numeric + if {[string is double -strict $value]} { + set num [expr {double($value)}] + + # Check if effectively integer + if {abs($num - round($num)) < 1e-9} { + return [format "%d" [expr {int(round($num))}]] + } + + # Otherwise, format to fixed precision (3 decimal places) + set str [format "%.3f" $num] + + # Strip trailing zeros + regsub {\.?0+$} $str "" str + + return $str + } + + # Non-numeric: return as-is + return $value +}