Introduction

This Tutorial explains how to create a design rule checker using the API. It also shows how to customize the Popup menu and add custom methods. After you have read and understood this tutorial you should be able to implement simple extensions to the tool by yourself.

How to Run an Userware Script

There are three possibilities to execute an Userware script:

  1. Load a file choosing File  Load Userware from the menu or by entering the Tcl command source <scriptname> in Console window.

  2. Start RTLvision PRO with the commandline option -userware <scriptname>. If you use this method, make sure that you register your script using the gui database registerChangedCallback API command.

  3. Extend the GUI and invoke your Userware script on demand, e.g. from the main menu or popup menu.

The first four examples below support the methods (1) and (2). The last example also supports method (3).

The example scripts are stored in the demo/api/ folder. To run them, please read the @example section in the header comment of the corresponding script.

Overview

Find Heavy Nodes

As an introductory example we describe a simple script that finds "heavy nodes", i.e. nets with a large number of connected pins.

This example script loops over all nets in each module and counts the number of pins for each net. Power and ground nets are skipped, as well as nets with constant values. The 10 biggest nets are stored into the Mem window.

proc heavyNodes {db} {

Since this script may be evaluated every time the database has changed (see below), we need to handle the case where the database has just been closed. In this case we do nothing and return:

    if {$db == ""} {    ;# if database changed to null-string
        return
    }

We need a list that stores the result. The elements will be ('value', OID) pairs, where 'value' is the number of pins the net connects to and OID is the unique database name of this net. We initialize the list with no elements:

    set topList {}              ;# top-ten list

Now we loop over all modules in the database using the database API command foreach. Inside this loop, we start a nested loop over all nets of this module, skipping power, ground, negpower and constant value nets:

    $db foreach module mod {
        $db foreach net $mod net {
            if {[$db isPgNet $net] || ([$db value $net] != "")} {
                continue
            }

Count the number of connected pins for the current net using another nested database loop:

            set cnt 0
            $db foreach pin $net p {
                incr cnt
            }

Insert this net into top-ten list topList. We’re using a small helper procedure addTopTen (see below) for managing the ('value', OID) pairs. The addTopTen procedure returns the resulting list that we store in the same variable topList:

            set topList [addTopTen $topList $cnt $net]
        }
    }

The result is stored in the Mem window. Therefore we clear and activate the Mem window before we add anything into it:

    gui mem clear
    gui window show Mem
    gui window show Schem

Before we can store the result in the Mem window we must convert our result list topList into a valid OID list, here called res. We can achieve this using a small helper procedure getTopTenA (see below) that extracts the OIDs from topList and returns a new list that we store in variable res:

    set res [getTopTenA $db $topList]
    gui mem append $res
}

The addTopTen procedure inserts the given ('value', OID) pair into a given sorted list with max length of 10. The result is an updated top-ten list in descending order. The code is pure Tcl and doesn’t use any API commands:

proc addTopTen {list value oid} {
    set entry [list $value $oid]
    set i 0
    foreach e $list {
        if {$value > [lindex $e 0]} {
            # insert entry before $e and limit list length to max 10.
            set list [linsert $list $i $entry]
            return [lrange $list 0 9]
        }
        incr i
    }
    if {$i < 10} {
        # we didn't insert, but the list length is < 10 - so we append
        lappend list $entry
    }
    return $list
}

The getTopTenA procedure returns a list of OIDs from the given top-ten list. In addition, the count fields are added as "count" attribute to the nets and the tooltipsWithAttrs flag is set (to display attributes in tooltips). The result is a new list that contains only OIDs:

proc getTopTenA {db list} {
    set res {}
    foreach n $list {
        set cnt [lindex $n 0]
        set net [lindex $n 1]
        $db attr $net set count=$cnt
        lappend res $net
    }
    gui settings set "tooltipsWithAttrs" 1
    return $res
}

The last part of the script is the entry point for the Userware code. We use gui database runOrRegisterChangedCallback to either immediately run the registered heavyNodes procedure if we have a database, or otherwise register the procedure to be executed after the database is available.

gui database runOrRegisterChangedCallback heavyNodes

Since the number of connected pins is counted inside a module only, the result of the above script is of little practical use. In the real world a net does not stop at a module boundary. A net can be interconnected with many other nets through multiple levels of hierarchy. To reflect this situation we have to traverse the net through the design hierarchy tree. Such an example is discussed in the next chapter.

Find Heavy Nodes (Design Tree Based)

Next we explain the Userware script demo/api/heavyNodes.tcl (design tree based) in the demo/api folder. This example is very similar to the previous one, except that it traverses the database in an instance-tree oriented way (rather than looping over all modules). It searches for signals, i. e. nets that are connected across module boundaries. Both scripts have many code fragments in common - in particular the procedures getTopTen and addTopTen. Only the different parts are explained in detail.

The heavyNodes procedure is the main procedure of our script. It loops over all signals in the design, starting in the top modules and counts the number of pins for each signal. Signals connected to power or ground are skipped, as well as signals with constant values. The 10 biggest signals are stored into the Mem window.

proc heavyNodes {db} {

Since this script may be evaluated every time the database has changed (see below), we need to handle the case where the database has just been closed. In this case we do nothing and return:

    if {$db == ""} {   ;# Return if the database is empty.
        return
    }

We need a list that stores the result. The elements will be ('value', OID) pairs, where 'value' is be the number of pins the signal connects to and OID is the unique database name of this signal. We initialize the list with no elements:

    set topList {} ;# top-ten list

Now we loop over all top modules in the database using the database API command foreach. For each top module found, we loop over all signals contained therein:

    $db foreach top mod {
        $db flat foreach signal $mod sig {

We skip all signals that carry power, ground or negpower:

            # skip power, ground and negpower nets
            if {[$db isPgNet $sig]} {
                continue
            }

We also skip all signals that contain nets with a constant value.

            set skip 0
            $db flat foreach net $sig net {
                if {[$db value $net] != ""} {
                    set skip 1
                    break
                }
            }
            if {$skip} {
                continue
            }

Now, again using an API foreach command, we count the number of pins that are connected to this signal:

            # count the number of connected pins
            set cnt 0
            $db flat foreach pin $sig p {
                incr cnt
            }

As the last action in this loop we insert this signal together with the pin count into top-ten list:

            set topList [addTopTenHeavyNodes $topList $cnt $sig]
        }
    }

Now, after having looped through all top modules, we display the result in the same way as in the example before. The procedures addTopTenHeavyNodes and getTopTenA are identical.

The script’s entry point is also the same. The command

gui database runOrRegisterChangedCallback heavyNodes

is used to immediately run heavyNodes if we have a database, or otherwise register the proc to be executed after the database is available.

Find Heavy CAP or RES

Here, we explain the Userware script demo/api/heavyCR.tcl in the demo/api folder.

This example searches the current database for the capacitors and resistors with the highest values.

The heavyCR procedure is the main procedure of our script. It loops over all capacitors (primitive function CAP) and resistors (primitive function RES) and checks each value (capacitance or resistance) keeping the highest ten values of each in two lists. The lists are then combined and stored in the Mem window.

Since this script may be evaluated every time the database has changed (see below), we need to handle the case where the database has just been closed. In this case we do nothing and return:

proc heavyCR {db} {
    if {$db == ""} {       ;# if database changed to null-string
        return
    }

We need two lists storing the highest C and R values and the corresponding OIDs. The elements will be ('value', OID) pairs. 'value' will be capacitance or resistance values and OID is the unique database name of the C or R found. We initialize them with no elements:

    set CAPList {}              ;# top-ten list
    set RESList {}              ;# top-ten list

Now we loop over all modules in the database using the database API command foreach. Inside this loop, we start a nested loop over all instances of this module skipping all instances of modules:

    $db foreach module mod {
        $db foreach inst $mod inst {
            if {[$db isModule $inst]} {
                continue
            }

Get the primitive type of the instance (i.e. "CAP" for capacitor or "RES" for resistor):

            set func [$db primFuncOf $inst]

The Spice parser creates an attribute "R" or "C" which contains the value it has parsed (capacitance or resistance). We query the database using the API function "$db attr $oid getValue" to retrieve the value we are interested in. Since the values may not be in base units, we have a small procedure map_spice_unit (see below) which translates the parsed values into base units.

If the function is a capacitor, then we add its value to the top ten capacitor list. We do this with the small helper procedure addTopTen (introduced above) for managing the ('value', OID) pairs:

            if {$func == "CAP"} {
                set value [$db attr $inst getValue "C"]
                if {$value == ""} {
                    continue
                }
                set value [map_spice_unit $value]
                if {$value == ""} {
                    continue
                }
                set CAPList [addTopTen $CAPList $value $inst]
            }

The same approach is followed for the resistor values:

            if {$func == "RES"} {
                set value [$db attr $inst getValue "R"]
                if {$value == ""} {
                    continue
                }
                set value [map_spice_unit $value]
                if {$value == ""} {
                    continue
                }
                set RESList [addTopTen $RESList $value $inst]
            }
        }
    }

We clear and activate the Mem window as in the example above:

    # Clear and activate the Mem window
    gui mem clear
    gui window show Mem
    gui window show Schem

Now we create a new list res by concatenating the two result lists using a small helper procedure getTopTen (see below). Actually, we create a new list with the OIDs from $CAPList then a separator line and then the OIDs from $RESList.

    set res [concat [getTopTen $CAPList] ------ [getTopTen $RESList]]

The OID list res can finally be stored into the Mem window:

    gui mem append $res

To visually verify the result shown in Mem window, we turn on the display of attributes in tooltips:

    gui settings set "tooltipsWithAttrs" 1
}

The map_spice_unit procedure maps spice values like 0.5n or 12.8k to the base unit or returns "" if the value is not ok. The code is pure Tcl and doesn’t use any API commands:

proc map_spice_unit {val} {
    if {[scan $val "%g%s" v unit] == 2} {
        set u 1
        switch -glob -- [string tolower $unit] {
            t*          {set u 1e12}
            g*          {set u 1e9}
            meg*        {set u 1e6}
            k*          {set u 1e3}
            mil*        {set u 25.4e-6}
            m*          {set u 1e-3}
            u*          {set u 1e-6}
            n*          {set u 1e-9}
            p*          {set u 1e-12}
            f*          {set u 1e-15}
        }
        return [expr $v * $u]
    } else {
        if {[string is double $val]} {
            return $val
        }
    }
    return ""   ;# bad
}

The getTopTen procedure returns a list of OIDs from the given top-ten list. The code is pure Tcl and doesn’t use any API commands:

proc getTopTen {list} {
    set res {}
    foreach n $list {
        lappend res [lindex $n 1]
    }
    return $res
}

The entry point of our Userware code is almost identical to the previous example, except that the procedure name is heavyCR.

Find Heavy CAP or RES (Design Tree Based)

Here, we explain the Userware script demo/api/heavyCRt.tcl in the demo/api folder.

This example is very similar to the previous one, except that it traverses the database in an instance-tree oriented way (rather than looping over all modules). Both have many code fragments in common - in particular the procedures getTopTen, addTopTen and map_spice_unit. Only the different parts are explained in details.

The heavyCRt procedure is the main procedure of our script. It traverses the design hierarchy tree for all Cs and Rs and checks each value (capacitance or resistance) and collects the 10 objects with the highest values. The 10 highest Cs and 10 highest Rs are stored into the Mem window.

Since this script may be evaluated every time the database has changed (see below), we need to handle the case where the database has just been closed. In this case we do nothing and return:

proc heavyCRt {db} {
    if {$db == ""} {       ;# if database changed to null-string
        return
    }

The two result lists we need must be processed in other procedures, too. Therefore we need to declare them global:

    global CAPList RESList

    set CAPList {}              ;# top-ten list
    set RESList {}              ;# top-ten list

Now we loop over all top modules in the database using the database API command foreach. For each top module found, we call the recursive procedure traverseCRt (see below) that examines the Cs and Rs:

    $db foreach top mod {
        traverseCRt $db $mod
    }

We need to clear the 'yellow' flag that was previously set in traverseCRt during recursive design hierarchy traversal:

    $db foreach module mod {
        $db flag $mod clear yellow
    }

As in the example above, we clear and activate the Mem window. Here we also store the result in the Mem window and activate the option to display attributes in the tooltips:

    # Clear and activate the Mem window
    gui mem clear
    gui window show Mem
    gui window show Schem

    # Store the result into the Mem window.
    set res [concat [getTopTen $CAPList] ------ [getTopTen $RESList]]
    gui mem append $res

    # Display attributes in tooltips
    gui settings set "tooltipsWithAttrs" 1
}

The traverseCRt procedure actually traverses the design hierarchy tree by recursively calling itself for each down-module. It collects the Rs and Cs in two global result lists CAPList and RESList:

proc traverseCRt {db mod} {
    global CAPList RESList

    $db foreach inst $mod inst {

If the instance ($inst) is non-primitive (i.e. it has a down-module), we dive down into its down-module and call this procedure recursively; but only if the "yellow" flag is not set for this module. We use the "yellow" flag to indicate that a module has been visited before. This way we can avoid traversing the same module multiple times (speed-up).

        if {[$db isModule $inst]} {
            set down [$db moduleOf $inst]
            if {![$db flag $down is yellow]} {
                traverseCRt $db $down
                $db flag $down set yellow
            }
            continue
        }

        # get primitive function
        set func [$db primFuncOf $inst]

        # check attribute R and C (the Spice parser stores values there).
        # and insert this C or R into top-ten list

        if {$func == "CAP"} {
            set value [$db attr $inst getValue "C"]
            if {$value == ""} {
                continue
            }
            set value [map_spice_unit $value]
            if {$value == ""} {
                continue
            }
            set CAPList [addTopTen $CAPList $value $inst]
        }
        if {$func == "RES"} {
            set value [$db attr $inst getValue "R"]
            if {$value == ""} {
                continue
            }
            set value [map_spice_unit $value]
            if {$value == ""} {
                continue
            }
            set RESList [addTopTen $RESList $value $inst]
        }
    }
}

The helper procedure map_spice_unit is used as in the example above.

The entry point of our Userware code is almost identical to the first example, except that the procedure name is heavyCRt.

Find Floating Nodes

Here, we explain the userware script demo/api/floatingNodes.tcl in the demo/api folder.

This example loops over all nets to find floating nodes. The floatingNodes procedure loops over all nets and counts the number of pins for each net. Power and ground nets are skipped. All nets with exactly one pin are stored into the Mem window.

Since this script may be evaluated every time the database has changed (see below), we need to handle the case where the database has just been closed. In this case we do nothing and return:

proc floatingNodes {db} {
    if {$db == ""} {       ;# if database changed to null-string
        return
    }

    set resList {}              ;# result List

Now we loop over all modules in the database using the database API command foreach. Inside this loop, we start a nested loop over all nets of this module, skipping supply and constant nets:

    $db foreach module mod {
        $db foreach net $mod net {
            # skip power, ground, negpower and constant nets
            if {[$db isPgNet $net] || ([$db value $net] != "")} {
                continue
            }

Count the number of connected pins and show the overall result in the Mem window:

            set cnt 0
            $db foreach pin $net p {
                incr cnt
                if {$cnt > 1} {
                    # no need to count more
                    break
                }
            }
            if {$cnt == 1} {
                lappend resList $net
            }
        }
    }

    gui mem clear
    gui window show Mem
    gui window show Schem
    gui mem append $resList
}

The entry point of our Userware code is almost identical to the first example, except that the procedure name is floatingNodes.

Customize the Popup Menu

Here, we explain the userware script demo/api/customPopup.tcl in the demo/api folder.

This example adds two entries to the Userware submenu of the Popup Menu. The first entry "Show Net" invokes the procedure ShowNet to load a net and all connected objects to the Cone window. The second entry "Show Signal" invokes the procedure ShowNet in signal mode to load a signal and all connected objects to the Cone window:

proc ShowNet {signalMode oidList} {

First check if the database or the oidList is not empty and if the object type is a net.

    ##
    # Return if the database is empty.
    #
    set db [gui database get]
    if {$db == ""} {
        return
    }

    ##
    # We expect one net object in $oidList.
    #
    if {[llength $oidList] != 1} {
        return
    }
    set net [lindex $oidList 0]
    if {[$db oid type $net] != "net"} {
        return
    }

Decide if the procedure ShowNet is running in signal mode or not. If signal mode is on, then loop over all nets belonging to the signal and collect a list of pins. In the other case loop over all pins of $net and collect a list of pins.

    if {$signalMode} {
       ##
       # Collect all pins at all nets of the given signal.
       #
       $db flat foreach net $net n {
          $db foreach pin $n pin {
              lappend pinList $pin
           }
       }
    } else {
       ##
       # Collect all pins at this net.
       #
       $db foreach pin $net pin {
           lappend pinList $pin
       }
    }
}

Load $pinList in the Cone window of and activate the Tree and Cone tab, also zoom to fullfit.

    gui cone load $pinList
    gui window show Tree
    gui window show Cone
    gui cone zoom fullfit

In case you need to remove existing custom entries in the Popup menu add

gui popup reset

to the script.

Append the new entry "Show Net" at the end of the Popup menu.

gui popup append "Show Net"    [list ShowNet 0]

Append the new entry "Show Signal" at the end of the Popup menu.

gui popup append "Show Signal" [list ShowNet 1]