在 Cadence Virtuoso 的 Layout 版图设计中,工程师们经常需要高亮特定的网络(Net)来进行电压标签检查(Voltage Label Check)或走线追踪。然而,Virtuoso 中存在多种不同的高亮方式,而且它们在底层的 SKILL 数据结构甚至是提取 API 上都大相径庭。
本文将深入探讨如何使用 SKILL 脚本,完美兼容并提取以下三种最常用的网络高亮引擎的数据,并通过坐标碰撞提取其上的电压 Label,最终展示在一个支持“三态多级排序”的高级交互界面中。
版图中的三种高亮引擎
在 Virtuoso 中,我们通常有以下三种高亮网络的途径:
- Legacy Probes:传统的探针高亮。
- Layout Net Tracer (LNT):现代版图网络追踪 (
Connectivity -> Net Tracer -> Add)。 - 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的参数和返回值陷阱!
- 它的正确语法是
lntGetTraceInfo(x_traceID),只接受一个整数参数,不能传入 window 对象,否则会报argument #2 should be a database object (type template = "gd")的错误。- 它的返回值不是简单的 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 实现。