#------------------------------------------------------------------------ # EDIF file parser for XCircuit #------------------------------------------------------------------------ # Written by Tim Edwards, MultiGiG, Inc., Scotts Valley, CA #------------------------------------------------------------------------ # Revision history: # Revision 0: July 18, 2006 by Tim Edwards # Revision 1: July 24, 2006 by Jan Sundermeyer # Revision 2: July 30, 2006 by Jan Sundermeyer # Revision 3: July 31, 2006 by Jan Sundermeyer #------------------------------------------------------------------------ set XCOps(module,edif) 1 # "Standard" scaling: converts a typical factors-of-10 coordinate system # into XCircuit's factors-of-16 coordinate system (16/10 = 8/5) set Opts(scalen) 8 set Opts(scaled) 5 set symbolview {symbol symbolr spectre hspice spectreS} set subst_list {} #------------------------------------------------------------------------ # EDIF rotations are the reverse of xcircuit rotations. #------------------------------------------------------------------------ proc rotinvert {value} { set newvalue [expr 360 - $value] if {$newvalue == 360} {set newvalue 0} return $newvalue } #------------------------------------------------------------------------ # Scale a point by factor (scalen) / (scaled) # "point" is a point in EDIF format: {pt } #------------------------------------------------------------------------ proc scalepoint {point} { global Opts set xin [lindex $point 1] set yin [lindex $point 2] set xout [expr $xin * $Opts(scalen) / $Opts(scaled)] set yout [expr $yin * $Opts(scalen) / $Opts(scaled)] return [list $xout $yout] } #------------------------------------------------------------------------ # Convert arc 3-point form to center-radius-endpoint angles # Formula thanks to Yumnam Kirani Singh, found on geocities.com. # Saved me from having to work it out for myself. #------------------------------------------------------------------------ proc convert_arc {point1 point2 point3} { set x1 [lindex $point1 0] set y1 [lindex $point1 1] set x2 [lindex $point2 0] set y2 [lindex $point2 1] set x3 [lindex $point3 0] set y3 [lindex $point3 1] set m11 [expr $x2 * $x2 + $y2 * $y2 - ($x1 * $x1 + $y1 * $y1)] set m21 [expr $y2 - $y1] set m12 [expr $x3 * $x3 + $y3 * $y3 - ($x1 * $x1 + $y1 * $y1)] set m22 [expr $y3 - $y1] set n11 [expr $x2 - $x1] set n21 $m11 set n12 [expr $x3 - $x1] set n22 $m12 set d11 $n11 set d21 $m21 set d12 $n12 set d22 $m22 set absm [expr ($m11 * $m22) - ($m12 * $m21)] set absn [expr ($n11 * $n22) - ($n12 * $n21)] set absd [expr ($d11 * $d22) - ($d12 * $d21)] set cx [expr $absm / (2.0 * $absd)] set cy [expr $absn / (2.0 * $absd)] set r [expr sqrt(($cx - $x2) * ($cx - $x2) + ($cy - $y2) * ($cy - $y2))] set a1 [expr atan2($y1 - $cy, $x1 - $cx)] set a2 [expr atan2($y2 - $cy, $x2 - $cx)] set a3 [expr atan2($y3 - $cy, $x3 - $cx)] # To be certain that the end angles are correct, make sure a2 is between # a1 and a3. if {($a1 < $a2) && ($a2 > $a3)} { set a3 [expr $a3 + 3.1415927] } elseif {($a1 > $a2) && ($a2 < $a3)} { set a3 [expr $a3 - 3.1415927] } set cx [expr round($cx)] set cy [expr round($cy)] set r [expr round($r)] set a1 [expr $a1 * 180 / 3.1415927] set a3 [expr $a3 * 180 / 3.1415927] return [list [list $cx $cy] $r $a1 $a3] } #------------------------------------------------------------------------ # Quiet deselection #------------------------------------------------------------------------ proc quiet_deselect {} { set handle [select get] if {$handle != {}} {deselect} } #------------------------------------------------------------------------ # Parse a "shape" statement #------------------------------------------------------------------------ proc parse_shape {elemlist} { set handle {} set shapedata [lindex $elemlist 0] set shapetype [lindex $shapedata 0] # Any keywords to handle other than "curve"? switch $shapetype { curve { foreach phrase [lrange $shapedata 1 end] { # Can a curve have any statement other than "arc"? set curveType [lindex $phrase 0] switch $curveType { arc { set pointlist {} foreach point [lrange $phrase 1 end] { lappend pointlist [scalepoint $point] } set xca [eval "convert_arc $pointlist"] set handle [eval "arc make $xca"] } default {puts "Unknown curve type $curveType"} } } } default {puts "Unsupported shape type $shapetype"} } return $handle } #------------------------------------------------------------------------ # decimal to octal code conversion #------------------------------------------------------------------------ proc dec_to_oct {dval} { set oval "\\" set o3 [expr $dval / 64] set r3 [expr $dval % 64] set o2 [expr $r3 / 8] set o1 [expr $r3 % 8] set oval "\\$o3$o2$o1" } #------------------------------------------------------------------------ # Parse a raw geometry statement in the EDIF data. #------------------------------------------------------------------------ proc parse_geometry {elemtype elemdata} { global Opts quiet_deselect switch $elemtype { openshape - shape { set handle [parse_shape $elemdata] } path - polygon { set polydata [lrange [lindex $elemdata 0] 1 end] set pointlist {} foreach point $polydata { lappend pointlist [scalepoint $point] } set numpoints [llength $pointlist] # puts "polygon make $numpoints $pointlist" set handle [eval "polygon make $numpoints $pointlist"] if {"$elemtype" == "polygon"} { element $handle border closed } } rectangle { set pointlist {} foreach point $elemdata { lappend pointlist [scalepoint $point] } # puts "polygon make box $pointlist" set handle [eval "polygon make box $pointlist"] } name - stringDisplay { # Parse various kinds of strings used by Cadence set dstring [lindex $elemdata 0] if {[lindex $dstring 0]== "array"} { set dstring [subst_name [lindex $dstring 1]] } else { set dstring [subst_name $dstring] } # Do EDIF-format ASCII escape sequence substitution while {[regexp -- {%([0-9]+)%} $dstring temp code] > 0} { set oval [dec_to_oct $code] regsub -- {%[0-9]+%} $dstring [subst "$oval"] dstring } set handle {} if {[string first "cds" $dstring] == 0} { switch -glob $dstring { cdsName* {set handle [label make normal [object name] {0 0}]} cdsParam* {set handle {}} cdsTerm* { if {[regexp {"(.+)"} $dstring temp pinname] > 0} { set handle [label make pin $pinname {0 0}] } else { set handle [label make pin $dstring {0 0}] } } default {set handle [label make normal $dstring {0 0}]} } set cds 1 } else { set cds 0 } foreach dparams [lrange $elemdata 1 end] { if {$cds == 0} { quiet_deselect set handle [label make normal $dstring {0 0}] } if {$handle != {}} { foreach dtext $dparams { set dtk [lindex $dtext 0] switch $dtk { justify { set jval [lindex $dtext 1] switch $jval { UPPERRIGHT { label $handle justify top label $handle justify right } CENTERRIGHT { label $handle justify middle label $handle justify right } LOWERRIGHT { label $handle justify bottom label $handle justify right } UPPERCENTER { label $handle justify top label $handle justify center } CENTERCENTER { label $handle justify middle label $handle justify center } LOWERCENTER { label $handle justify bottom label $handle justify center } UPPERLEFT { label $handle justify top label $handle justify left } CENTERLEFT { label $handle justify middle label $handle justify left } LOWERLEFT { label $handle justify bottom label $handle justify left } } } orientation { set oval [lindex $dtext 1] set odeg [string range $oval 1 end] element $handle rotate [rotinvert $odeg] } origin { set plist [lindex $dtext 1] label $handle position [scalepoint $plist] } } } } } } default { puts "Unsupported geometry block keyword $elemtype" set handle {} } } return $handle } #------------------------------------------------------------------------ # execute substitution of net names #------------------------------------------------------------------------ proc subst_name {net} { global subst_list if {[llength $net] > 1} { if {[lindex $net 0] == "rename"} { lappend subst_list [lrange $net 1 2] return [lindex $net 2] } else { return $net } } else { set pos [lsearch $subst_list "$net *"] if {$pos > -1} { return [lindex [lindex $subst_list $pos] 1] } else { return $net } } } #------------------------------------------------------------------------ # Parse a "net" statement in the EDIF data. #------------------------------------------------------------------------ proc parse_net {netdata} { set netString [lindex $netdata 0] if {[llength $netString] > 1} { if {[lindex $netString 0] == "name"} { parse_geometry [lindex $netString 0] [lrange $netString 1 end] } elseif {[lindex $netString 0] == "rename"} { set elemdata [lrange $netString 1 end] if { [llength [lindex $elemdata 1]]>1 } { parse_geometry [lindex [lindex $elemdata 1] 0] [lrange [lindex $elemdata 1] 1 end] } } elseif {[lindex $netString 0] == "array"} { parse_net [list [lindex $netString 1]] } else { puts "Unknown net name $netString" return } } foreach phrase [lrange $netdata 1 end] { set keyword [lindex $phrase 0] switch $keyword { joined { # ignore net information (for now) } figure { # Figure types defined in the technology block. set figureType [lindex $phrase 1] set geolist [lindex $phrase 2] parse_geometry [lindex $geolist 0] [lrange $geolist 1 end] } default {puts "Unsupported net block keyword $keyword"} } } } #------------------------------------------------------------------------ # Parse an "instance" statement in the EDIF data. #------------------------------------------------------------------------ proc parse_instance {instName instdata} { quiet_deselect set handle {} if {[lindex $instName 0] == "array"} { set instName [subst_name [lindex $instName 1]] } else { set instName [subst_name $instName] } foreach phrase $instdata { set keyword [lindex $phrase 0] switch $keyword { viewRef { # Next keyword should be "symbol" or "symbolr"? set objtype [lindex $phrase 1] set instinfo [lindex $phrase 2] # 1st keyword should be "cellRef", 3rd "libraryRef". Ignore these. set cellname [lindex $instinfo 1] # Create the instance if {[catch {set handle [instance make $cellname "0 0"]}]} { puts "ERROR: Attempt to instance non-existant object $cellname" } else { puts "Created instance $instName of $cellname" } } transform { if {$handle == {}} { puts "Error: Transform specified without reference instance" } else { foreach trans [lrange $phrase 1 end] { set trk [lindex $trans 0] switch $trk { orientation { set ogood 0 set oval [lindex $trans 1] if {[string first "MX" $oval] >= 0} { element $handle flip vertical set ogood 1 } elseif {[string first "MY" $oval] >= 0} { element $handle flip horizontal set ogood 1 } set rpos [string first "R" $oval] if {$rpos >= 0} { incr rpos element $handle rotate [rotinvert [string range \ $oval $rpos end]] set ogood 1 } if {$ogood == 0} { puts "Unsupported orientation $oval in transform" } } origin { set plist [lindex $trans 1] instance $handle center [scalepoint $plist] } } } } } property { if {$handle != {}} { push $handle parse_property [lindex $phrase 1] [lrange $phrase 2 end] pop } } default {puts "Unsupported instance block keyword $keyword"} } } # Set standard parameters idx and class from the instance name if {$handle != {}} { push $handle if {[catch {parameter set class [string range $instName 0 0]}]} { if {[catch {parameter set instName $instName}]} { parameter make substring instName ? parameter set instName $instName } } else { parameter set idx [string range $instName 1 end] } pop } } #------------------------------------------------------------------------ # Parse a "portImplementation" statement #------------------------------------------------------------------------ proc parse_port {labellist portdata} { set portString [lindex $portdata 0] set plabel {} if {[llength $portString] > 1} { if {[lindex $portString 0] == "name"} { set plabel [parse_geometry [lindex $portString 0] [lrange $portString 1 end]] set portName [subst_name [lindex $portString 1]] } else { puts "Unknown portImplementation pin $portString" return } } else { set portName [subst_name $portString] } if {$labellist != {}} { foreach llabel $labellist { if {"$portName" == [label $llabel text]} { set plabel $llabel break } } } else { if {$plabel == {}} { quiet_deselect set plabel [label make normal $portName {0 0}] } } if {$plabel == {}} { puts "Can't determine port name in $portdata" return } # If we were not given a label list, then this is a pin on the schematic # and should be converted into a pin. if {[label $plabel type] == "normal"} { label $plabel type local } foreach portProp [lrange $portdata 1 end] { set portkey [lindex $portProp 0] switch $portkey { figure { set figureType [lindex $portProp 1] set geolist [lindex $portProp 2] parse_geometry [lindex $geolist 0] [lrange $geolist 1 end] } connectLocation { # Expect a (figure pin (dot (pt x y)))) # Use the point position to reposition the label. set cpoint [scalepoint [lindex [lindex [lindex $portProp 1] 2] 1]] label $plabel position $cpoint } instance { parse_instance [lindex $portProp 1] [lrange $portProp 2 end] } default {puts "Unsupported portImplementation keyword $portkey"} } } } #------------------------------------------------------------------------ # Parse a "page" statement in the EDIF data. #------------------------------------------------------------------------ proc parse_page {cellname portlist pagedata} { # Go to the next empty page set cpage 1 page $cpage -force while {[object parts] != {}} {incr cpage ; page $cpage -force} page label $cellname foreach phrase $pagedata { set keyword [lindex $phrase 0] switch $keyword { instance { set instName [lindex $phrase 1] parse_instance $instName [lrange $phrase 2 end] } net { parse_net [lrange $phrase 1 end] } portImplementation { parse_port {} [lrange $phrase 1 end] } commentGraphics { set comment [lindex $phrase 1] set ctype [lindex $comment 0] ;# from the technology block... set geolist [lindex $comment 1] parse_geometry [lindex $geolist 0] [lrange $geolist 1 end] } default {puts "Unsupported page block keyword $keyword"} } } if {$portlist != {}} { quiet_deselect set obbox [object bbox] set x [expr [lindex $obbox 0] + 128] set y [expr [lindex $obbox 1] - 64] set itext [list {Text "spice1:.subckt %n"}] set pstring "" foreach pname $portlist { if {[string length $pstring] > 60} { lappend itext [subst {Text "$pstring"}] lappend itext {Return} set pstring "+" incr y -32 } set pstring [join [list $pstring "%p$pname"]] ;# preserves whitespace } lappend itext [subst {Text "$pstring"}] label make info "$itext" "$x $y" incr y -32 label make info "spice-1:.ends" "$x $y" } } #------------------------------------------------------------------------ # Parse a "contents" statement in the EDIF data. #------------------------------------------------------------------------ proc parse_contents {celltype cellname portlist contentdata} { global symbolview set labellist {} foreach phrase $contentdata { set keyword [lindex $phrase 0] switch $keyword { boundingBox { set bbox [lindex $phrase 1] set handle [parse_geometry [lindex $bbox 0] [lrange $bbox 1 end]] element $handle border bbox true } commentGraphics { set comment [lindex $phrase 1] set ctype [lindex $comment 0] ;# from the technology block... set geolist [lindex $comment 1] set plabel [parse_geometry [lindex $geolist 0] [lrange $geolist 1 end]] if {$plabel != {}} {lappend labellist $plabel} } figure { # Figure type is defined in the technology section, and defines # color, linewidth, etc. set figureType [lindex $phrase 1] set geolist [lindex $phrase 2] parse_geometry [lindex $geolist 0] [lrange $geolist 1 end] } portImplementation { parse_port $labellist [lrange $phrase 1 end] } page { # Note: Handle multiple pages here! parse_page $cellname $portlist [lrange $phrase 1 end] } default {puts "Unsupported content/symbol block keyword $keyword"} } } # search for all symbol views # following list contains these viewnames if {[lsearch $symbolview $celltype] > -1} { # Create standard parameters parameter make substring class "X" parameter make substring idx "?" if {$portlist != {}} { quiet_deselect set obbox [object bbox] set x [expr [lindex $obbox 0] + 128] set y [expr [lindex $obbox 1] - 64] set itext [list {Text "spice:"} {Parameter class} {Parameter idx}] set pstring "" foreach pname $portlist { if {[string length $pstring] > 60} { lappend itext [subst {Text "$pstring"}] lappend itext {Return} set pstring "+" incr y -32 } set pstring [join [list $pstring "%p$pname"]] ;# preserves whitespace } set pstring [join [list $pstring "%n"]] ;# preserves whitespace lappend itext [subst {Text "$pstring"}] label make info "$itext" "$x $y" } } } #------------------------------------------------------------------------ # Parse a "property" statement in the EDIF data. # (To do: generate an xcircuit parameter for the object) #------------------------------------------------------------------------ proc parse_property {key value} { set keylen [llength $key] set property [lindex $value 0] set proptype [lindex $property 0] set pvalue [lindex $property 1] # Any other proptypes to handle? switch $proptype { string { set paramtype substring } integer { set paramtype numeric } boolean { set paramtype numeric switch $pvalue { true - True - TRUE - t - T { set pvalue 1} false - False - FALSE - f - F { set pvalue 0} } } default { puts "Unknown property type $proptype" ; return } } # Attempt to set the parameter value. If the parameter doesn't # exist, then create it. if [catch {parameter set $key $pvalue}] { parameter make $paramtype $key $pvalue } } #------------------------------------------------------------------------ # Parse an "interface" block in the EDIF data. #------------------------------------------------------------------------ proc parse_interface {cellname ifacedata} { set portlist {} foreach phrase $ifacedata { set keyword [lindex $phrase 0] # Possible other keywords to handle: # port (ignoring ports for now, just dealing with portImplementation) # property (is a property of what??) --- should translate instNamePrefix # to parameter "class". switch $keyword { port { if {[lindex [lindex $phrase 1] 0] == "array"} { set zw [subst_name [lindex [lindex $phrase 1] 1]] } else { set zw [subst_name [lindex $phrase 1]] } lappend portlist $zw } symbol {parse_contents symbol $cellname $portlist [lrange $phrase 1 end]} default {puts "Unsupported interface block keyword $keyword"} } } return $portlist } #------------------------------------------------------------------------ # Parse a "view schematic" statement in the EDIF data. #------------------------------------------------------------------------ proc parse_schematic {cellname viewdata} { set portlist {} foreach phrase $viewdata { set keyword [lindex $phrase 0] # Possible other keywords to handle: # viewType, interface switch $keyword { interface {set portlist [parse_interface $cellname [lrange $phrase 1 end]]} contents {parse_contents schematic $cellname $portlist [lrange $phrase 1 end]} property {parse_property [lindex $phrase 1] [lrange $phrase 2 end]} default {puts "Unsupported schematic view block keyword $keyword"} } } } #------------------------------------------------------------------------ # Parse a "view symbol" statement in the EDIF data. #------------------------------------------------------------------------ proc parse_symbol {libname cellname viewdata} { # If an object of this name exists, rename it first. catch {object [object handle $cellname] name "_$cellname"} set handle [object make $cellname $libname -force] if {$handle == ""} { puts "Error: Couldn't create new object!" return } set cpage [page] push $handle symbol type fundamental set portlist {} foreach phrase $viewdata { set keyword [lindex $phrase 0] # Possible other keywords to handle: # viewType, interface switch $keyword { interface {set portlist [parse_interface $cellname [lrange $phrase 1 end]]} contents {parse_contents fundamental $cellname $portlist [lrange $phrase 1 end]} property {parse_property [lindex $phrase 1] [lrange $phrase 2 end]} default {puts "Unsupported symbol view block keyword $keyword"} } } pop if [catch {delete $handle}] { page $cpage if [catch {delete $handle}] { puts "Error: Element handle $handle does not exist?" puts "Page objects: [object parts]" } } # Find the instance in the library and force a recomputation of its # bounding box. library $libname goto foreach inst [library $libname handle] { if {[instance $inst object] == $cellname} { instance $inst bbox recompute break } } page $cpage } #------------------------------------------------------------------------ # Parse a "cell" statement in the EDIF data. #------------------------------------------------------------------------ proc parse_cell {libname cellname celldata} { global symbolview foreach phrase $celldata { set keyword [lindex $phrase 0] # Possible other keywords to handle: # cellType switch $keyword { view { set viewType [lindex $phrase 1] if {[lsearch $symbolview $viewType] > -1} { parse_symbol $libname $cellname [lrange $phrase 2 end] } else { switch $viewType { schematic {parse_schematic $cellname [lrange $phrase 2 end]} default {puts "Unsupported view type $viewType"} } } } default {puts "Unsupported cell block keyword $keyword"} } } } #------------------------------------------------------------------------ # Parse a "library" statement in the EDIF data. #------------------------------------------------------------------------ proc parse_library {libname libdata} { library make $libname foreach phrase $libdata { set keyword [lindex $phrase 0] # Possible other keywords to handle: # basic, edifLevel, technology switch $keyword { cell { parse_cell $libname [lindex $phrase 1] [lrange $phrase 2 end] } default {puts "Unsupported library block keyword $keyword"} } } # Regenerate the library library $libname compose } #------------------------------------------------------------------------ # Main file reader routine. Read the file into a single string, and # replace parentheses so we turn the LISP phrasing into a nested TCL # list. #------------------------------------------------------------------------ proc read_edif {filename} { if [catch {open $filename r} fileIn] { puts stderr "Cannot find file $filename" return; } # Remove some tag callbacks that cause problems. . . set paramtag [tag parameter] tag parameter {} # Convert the file into a nested list by converting () to {}. set everything [read $fileIn] set masterlist [lindex [string map {( \{ ) \} \n " "} $everything] 0] unset everything close $fileIn # Now parse the file. . . set cpage [page] config suspend true foreach phrase $masterlist { set keyword [lindex $phrase 0] # Possible other keywords to handle: # edif, edifVersion, edifLevel, keywordMap, status switch $keyword { library {parse_library [lindex $phrase 1] [lrange $phrase 2 end]} default {puts "Unsupported primary keyword $keyword"} } } tag parameter $paramtag # Return to (and redraw) original page page $cpage zoom view config suspend false } #------------------------------------------------------------------------ # Procedure that creates the dialog to find an EDIF file to parse and # calls procedure read_edif. #--------------------------------------------------------------------------- proc xcircuit::promptreadedif {} { global XCOps .filelist.bbar.okay configure -command \ {read_edif [.filelist.textent.txt get] ; \ wm withdraw .filelist} .filelist.listwin.win configure -data "edf edif" .filelist.textent.title.field configure -text "Select EDIF 2.0.0 file to parse:" .filelist.textent.txt delete 0 end xcircuit::popupfilelist xcircuit::removelists .filelist }