在 Cadence Virtuoso 的 Layout 版图设计中,工程师们经常需要高亮特定的网络(Net)来进行电压标签检查(Voltage Label Check)或走线追踪。然而,Virtuoso 中存在多种不同的高亮方式,而且它们在底层的 SKILL 数据结构甚至是提取 API 上都大相径庭。

本文将深入探讨如何使用 SKILL 脚本,完美兼容并提取以下三种最常用的网络高亮引擎的数据,并通过坐标碰撞提取其上的电压 Label,最终展示在一个支持“三态多级排序”的高级交互界面中。

版图中的三种高亮引擎

在 Virtuoso 中,我们通常有以下三种高亮网络的途径:

  1. Legacy Probes:传统的探针高亮。
  2. Layout Net Tracer (LNT):现代版图网络追踪 (Connectivity -> Net Tracer -> Add)。
  3. Traditional Mark Net:传统网络标记 (Connectivity -> Nets -> Mark)。

要让一个自动化脚本变得健壮,我们就必须将这三种底层数据“一网打尽”。下面我们逐一攻破它们的提取难点。

Engine 1: Legacy Probes

最传统的高亮方式,使用探针(Probe)。它的 API 最为清晰,我们可以直接使用 geGetAllProbe 遍历当前窗口的所有探针,提取其绑定的网络即可。

probes = (geGetAllProbe viewWin)
(foreach probe probes
    if( probe~>probeType == "net" then
        netObj = probe~>objectId
        if( (dbIsId netObj) && netObj~>objType == "net" then
            activeFigs[netObj~>name] = netObj~>figs
        )
    )
)

Engine 2: Modern Layout Net Tracer 配置与提取

Net Tracer (lntHiNetTracer) 是 Virtuoso 中非常强悍的追踪引擎,但它的提取 API 极具迷惑性。 使用 lntGetAllTraces(cv) 可以获取到所有的 Trace ID 列表。而要获取具体的 Trace 信息,必须使用 lntGetTraceInfo

[!WARNING] 一定要注意 lntGetTraceInfo 的参数和返回值陷阱!

  1. 它的正确语法是 lntGetTraceInfo(x_traceID),只接受一个整数参数,不能传入 window 对象,否则会报 argument #2 should be a database object (type template = "gd") 的错误。
  2. 它的返回值不是简单的 Association List (alist),而是一个包含底层数据库对象(虚拟 CellView)的 List。

通过打印 cadr(lntGetTraceInfo(1))~>??,我们惊讶地发现,LNT 在底层实际上是在内存中伪造了一个完整的 cellView 对象(例如名为 Trace1),并将追踪到的所有金属块(shapes)和通孔(vias)都打包进了这个虚拟对象中!

因此,正确的提取逻辑是直接扒开这个伪造的 CellView:

traces = (lntGetAllTraces cv)
(foreach traceId traces
    info = (lntGetTraceInfo traceId)
    if( info then
        traceCv = (cadr info) ; 这是个虚拟的 cellView 对象 
        if( (dbIsId traceCv) && traceCv~>objType == "cellView" then
            netName = traceCv~>cellName
            lntShapes = traceCv~>shapes
            lntVias   = traceCv~>vias
            ; 合并金属走线和通孔...
        )
    )
)

Engine 3: Traditional Mark Net 的“黑魔法”

对于 Connectivity -> Nets -> Mark (leHiMarkNet) 高亮的网络,由于 Cadence 官方一直没有提供一个能直接在内存里读取其图形的标准安全 API,我们需要使用官方的终极 Workaround 收容方案:leSaveMarkNet

思路是在内存中创建一个彻底隐形的临时 CellView,通过 leSaveMarkNet 把屏幕上的高亮投影“实例化”为真实的 Shape 放入其中,用完即焚(dbPurge)。

[!IMPORTANT] leSaveMarkNet 的第一个参数是必选的位置参数(String 类型的 target cell name),千万不要加 ?cellName 前缀。正确调用应为:(leSaveMarkNet tmpCvName ?libName tmpCvLib)

坐标反打:找回遗失的真名 leSaveMarkNet 仅仅复制了物理多边形,丢失了原本的网络名字。怎样在 UI 中区分这些无名氏呢?我们可以充当“侦探”,用复制出来的 Shape 的包围盒(bBox),回到原始版图中执行坐标刺探 (dbGetTrueOverlaps),看看底下压着的真正的物理走线的 net~>name 是什么!

(foreach shape shapes
    realNetName = nil
    ; 刺探原本的 cv 中相同位置的图形
    overlapShapes = (dbGetTrueOverlaps cv shape~>bBox shape~>layerName 0)
    (foreach osc overlapShapes
        if( (listp osc) then iterShape = (car osc) else iterShape = osc )
        if( (dbIsId iterShape) && iterShape~>net && iterShape~>net~>name then
            realNetName = iterShape~>net~>name
        )
    )
    ; 加入对应的结果集 ...
)

UI 高级进阶:hiCreateReportField 三态多级稳定排序

找齐了所有的形状,并且用 bBox 碰撞提取到了对应的 Label 后,为了给最终用户最佳的体验,我们要提供一个清爽且支持点击表头排序的表格(Report Field)。

1. 激活表头排序按钮的秘诀

如果仅使用简单的 ?headers (list (list "Net/Source" 180 'string)),Virtuoso 将不会渲染任何排序控件。 经过对底层 API 的深挖,我们发现要想让 hiCreateReportField 的表头变成可点击的按钮,每一个 ?headers 列定义都必须严格提供 5 个参数,最后的 t 才是真正激活排序按钮的锁钥:

; 第五个参数 t 尤为重要
?headers (list 
    (list "Net/Source" 180 'left 'string t) 
    (list "Purpose"     80 'left 'string t) 
    ...
)

全功能完整源码参考

综合上述所有的填坑与突破,我们最终凝结成了下面这段兼容并包、体验丝滑的核心工具脚本。它不仅能跨引擎聚拢网络信息,而且极大方便了版图工程师直接双击定位 Label:

; -----------------------------------------------------------------
; Voltage Labels on Marked & Traced Nets Extractor 
; -----------------------------------------------------------------

(setq myFoundVoltageLabels nil)
(setq myLastSortCol -1)        
(setq mySortState 0)           

(procedure onLabelDoubleClickCB()
    (let (form index labelObj viewWin selectedIndices)
        form = (hiGetCurrentForm)
        viewWin = (hiGetCurrentWindow)
        selectedIndices = form->labelListReport->value
        
        if( selectedIndices then
            index = (car selectedIndices)
            labelObj = (nthelem (add1 index) myFoundVoltageLabels)
            if( (dbIsId labelObj) then
                (geDeselectAll)
                (geSelectObject labelObj)
                (hiZoomIn viewWin labelObj~>bBox)
            )
        )
    )
)

(procedure onLabelListSortCB(fieldName sortInfo)
    (let (colIndex uiDirection combined newChoices newLabels cmpFunc)
        colIndex  = (car sortInfo)
        
        if( colIndex == myLastSortCol then
            if( mySortState == 1 then
                mySortState = 2
            else
                mySortState = 1
            )
        else
            myLastSortCol = colIndex
            mySortState = 1  
        )
        
        (if mySortState == 1 then
            (putpropq fieldName (list colIndex 'ascending) sort)
        else
            (putpropq fieldName (list colIndex 'descending) sort)
        )
        
        currentChoices = (getq fieldName choices)
            combined = nil
            (for i 0 (sub1 (length currentChoices))
                combined = (cons (list (nth i currentChoices) (nth i myFoundVoltageLabels)) combined)
            )
            combined = (reverse combined)
            
            if( mySortState == 1 then
                combined = (sortcar combined (lambda (a b) 
                    (alphalessp (nthelem (add1 colIndex) a) (nthelem (add1 colIndex) b))
                ))
            else
                combined = (sortcar combined (lambda (a b) 
                    (alphalessp (nthelem (add1 colIndex) b) (nthelem (add1 colIndex) a))
                ))
            )
        
        newChoices = nil
        newLabels  = nil
        (foreach item combined
            newChoices = (cons (car item) newChoices)
            newLabels  = (cons (cadr item) newLabels)
        )
        newChoices = (reverse newChoices)
        myFoundVoltageLabels = (reverse newLabels)
        
        (putpropq fieldName newChoices choices)
        t
    )
)

(procedure getHighlightedFiguresMap(cv viewWin)
    (let (activeFigs probes traces netObj netName info netList tmpCvLib tmpCvName tmpCv shapes lntShapes lntVias traceCv overlapShapes realNetName iterShape)
        activeFigs = (makeTable "activeFigs" nil)
        
        ; Engine 1: geGetAllProbe
        probes = (geGetAllProbe viewWin)
        (foreach probe probes
            if( probe~>probeType == "net" then
                netObj = probe~>objectId
                if( (dbIsId netObj) && netObj~>objType == "net" then
                    activeFigs[netObj~>name] = netObj~>figs
                )
            )
        )
        
        ; Engine 2: lntGetAllTraces
        if( (isCallable 'lntGetAllTraces) then
            traces = (lntGetAllTraces cv)
            (foreach traceId traces
                info = (lntGetTraceInfo traceId)
                if( info then
                    traceCv = (cadr info)
                    if( (dbIsId traceCv) && traceCv~>objType == "cellView" then
                        netName = traceCv~>cellName
                        lntShapes = traceCv~>shapes
                        lntVias   = traceCv~>vias
                        if( !netName then netName = "LNT_Traced_Net" )
                        if( activeFigs[netName] == nil then
                            activeFigs[netName] = lntShapes
                        else
                            activeFigs[netName] = (append activeFigs[netName] lntShapes)
                        )
                        if( lntVias then
                            activeFigs[netName] = (append activeFigs[netName] lntVias)
                        )
                    )
                )
            )
        )
        
        ; Engine 3: leHiMarkNet via leSaveMarkNet
        tmpCvName = (sprintf nil "tmp_marknet_%s" cv~>cellName)
        tmpCvLib  = cv~>libName
        tmpCv = (dbOpenCellViewByType tmpCvLib tmpCvName "layout" "maskLayout" "a")
        if( tmpCv then (dbPurge tmpCv) )
        tmpCv = (dbOpenCellViewByType tmpCvLib tmpCvName "layout" "maskLayout" "w")
        
        if( tmpCv then
            (leSaveMarkNet tmpCvName ?libName tmpCvLib)
            shapes = tmpCv~>shapes
            if( shapes && (length shapes) > 0 then
                (foreach shape shapes
                    realNetName = nil
                    overlapShapes = (dbGetTrueOverlaps cv shape~>bBox shape~>layerName 0)
                    (foreach osc overlapShapes
                        if( (listp osc) then iterShape = (car osc) else iterShape = osc )
                        if( (dbIsId iterShape) && iterShape~>net && iterShape~>net~>name then
                            realNetName = iterShape~>net~>name
                        )
                    )
                    if( !realNetName then realNetName = "MarkedNet_Unknown" )
                    if( activeFigs[realNetName] == nil then
                        activeFigs[realNetName] = (list shape)
                    else
                        activeFigs[realNetName] = (cons shape activeFigs[realNetName])
                    )
                )
            )
        )
        
        netList = nil
        (foreach key activeFigs
            if( activeFigs[key] && (length activeFigs[key]) > 0 then
                netList = (cons (list key activeFigs[key]) netList)
            )
        )
        netList
    )
)

(procedure showHighlightedNetVoltageLabelsUI()
    (prog (cv viewWin markedFigsMap netName figsList bBox inBBox choices reportField form foundLabel tmpCvLib tmpCvName tmpCv)
        cv = (geGetEditCellView)
        viewWin = (hiGetCurrentWindow)
        
        markedFigsMap = getHighlightedFiguresMap(cv viewWin)
        
        if( !markedFigsMap || (length markedFigsMap) == 0 then
            (printf "[ERROR] No highlighted shapes found from Probes, Net Tracer, or Mark Net.\n")
            (return nil)
        )
        
        myFoundVoltageLabels = nil
        choices = nil
        foundLabel = nil
        
        (foreach item markedFigsMap
            netName  = (car item)
            figsList = (cadr item)
            
            (foreach shape cv~>shapes
                if( shape~>objType == "label" then
                    inBBox = nil
                    (foreach fig figsList
                        if( (dbIsId fig) then 
                            bBox = fig~>bBox
                            if( (xCoord shape~>xy) >= (xCoord (lowerLeft bBox)) &&
                                (xCoord shape~>xy) <= (xCoord (upperRight bBox)) &&
                                (yCoord shape~>xy) >= (yCoord (lowerLeft bBox)) &&
                                (yCoord shape~>xy) <= (yCoord (upperRight bBox)) then
                               inBBox = t
                            )
                        )
                    )
                    
                    if( inBBox && !(member shape myFoundVoltageLabels) then
                        myFoundVoltageLabels = (append myFoundVoltageLabels (list shape))
                        choices = (append choices 
                            (list (list netName shape~>layerName shape~>purpose shape~>theLabel (lsprintf "%L" shape~>xy)))
                        )
                        foundLabel = t
                    )
                )
            )
        )
        
        tmpCvName = (sprintf nil "tmp_marknet_%s" cv~>cellName)
        tmpCvLib  = cv~>libName
        tmpCv = (dbOpenCellViewByType tmpCvLib tmpCvName "layout" "maskLayout" "a")
        if( tmpCv then (dbPurge tmpCv) )

        if( !foundLabel then
            (printf "No overlapping labels found on any highlighted nets.\n")
            (return nil)
        )
        
        reportField = (hiCreateReportField
            ?name 'labelListReport
            ?title "Double-Click a row to Pan & Highlight:"
            ?headers (list 
                (list "Net/Source" 180 'left 'string t) 
                (list "Layer"       60 'left 'string nil) 
                (list "Purpose"     80 'left 'string t) 
                (list "Label Text" 100 'left 'string nil) 
                (list "Coordinate" 150 'left 'string nil)
            )
            ?choices choices
            ?doubleClickCB "onLabelDoubleClickCB()"
            ?sortCallback "onLabelListSortCB"
            ?sort nil
        )
        
        form = (hiCreateAppForm
            ?name 'NetVoltageLabelForm
            ?formTitle "Voltage Labels on Marked & Traced Nets"
            ?fields (list (list reportField 0:0 600:300 0)) 
            ?buttonLayout 'Close
        )
        
        myLastSortCol = -1
        mySortState = 0
        
        (hiDisplayForm form)
        (printf "Successfully extracted %d labels across all marked nets.\n" (length choices))
        (return t)
    )
)

结语

Cadence 的各种工具在多年演进中沉淀了不同派系的 API 实现。