在 Virtuoso Layout 编辑器中,将对象精确对齐到网格是布局设计中的常见需求。本文记录了使用 Cursor AI 助手编写一个 Skill 脚本的完整过程,该脚本通过 GUI 表单实现将选中的 pin、instance 或 layer 对象对齐到指定网格的功能。虽然 AI 生成的初始代码在逻辑上基本正确,但在实际运行中遇到了大量 Cadence Skill API 使用错误,包括函数名错误、参数格式不正确、对象属性访问方式错误等问题。经过系统性的调试和修正,最终成功实现了功能。本文详细总结了这些常见错误类型及其正确的修复方法,为后续使用 AI 编写 Skill 脚本提供了宝贵的经验参考.
背景
在实际工作中,遇到把 cell 放到固定格点,也遇到把 layer 如注入层放到格点,还有将 pin 放到 track上的场景, 虽然是简单的 skill , 但是针对每种 ojbect 都有一个 script , 因为简单, 也没有 GUI。 昨天看到一些 AI 的 prompt 技巧, 已经帮我很出色的完成了另一项任务,就尝试看大家一直期望的用 AI 写 skill 脚本。 将 put on grid 这件事合在一起,并创建 GUI. 因为我一直在用 cursor ,且免费额度还行, 就尝试一下。
最终产出在文末
AI第一步:plan
- 我给
cursor的提示如下
我需要用 cadence/skill语言写一个 putAnyOnGrild的skill脚本,最终能在 virtuoso Layout界面实现以下功能 创建 hiCreateVerticalBoxLayout 第一行, 用hiCreateFloatField, 创建 x grid和y grid值的设定 field 第二行, 需要align的object, 用 hiCreateRadioField, choices包含pin, instance, layer hicerateappform 调用 verticlalboxlayout ok或apply实现 对 pin.instance,layer的调整, 调用以下三个分函数实现我 pin: 中心要在 x/y grid上,必需是pin shape instance: xy要在 x/y grid上 layer: 四边都要在 x/y grid上,但保持box往大变化, 左下坐标往小的方向变,右上往大的方向变 代码中的注释需要用英文
请先考虑方案的可行性 然后对我提问,直到我认可
AI第二步: 问答环节
因为我在有多个 skill 脚本的目录打开了 cursoe, 所以它先查看现有 Skill 脚本,了解代码风格和结构。
然后他给我列出了一些问题
- 脚本应该处理哪些对像
- 对于pin对象,如何判断是否为pin shape
- 对于instance, xy 对齐是指什么?
- 对于 layer(shape), “四边都要在grid上,但保持box往大变化” 的理解是?
- 默认的grid是多少?
它会提供选项,当选择完成后,接着又问了几个细节
- 如果没有选中的对象或选中的对象类型不匹配,应该如何处理
- 是否需要支持 undo/redo?
- 如何判断 shape 是否为 pin shape
同样有选项,鼠标点选就行
AI第三步:计划书
当了解到我的需求后, cursor 写了一份计划书,主要包含了以下内容
- 概述
- 文件结构
- 实现方案
- 技术要点
- 代码结构
- 注意事项
实现方案 是此次项目的精华,cursor 写的还挺详细的
代码结构 如下:
procedure(putAnyOnGrid()
; 创建表单
let((form)
form = hiCreateAppForm(...)
hiDisplayAppForm(form)
)
)
procedure(putAnyOnGridApply(form)
; 处理逻辑
)
procedure(putPinOnGrid(objs xGrid yGrid)
; Pin 对齐逻辑
)
procedure(putInstanceOnGrid(objs xGrid yGrid)
; Instance 对齐逻辑
)
procedure(putLayerOnGrid(objs xGrid yGrid)
; Layer 对齐逻辑
)AI第四步:todo 列表
当 cursor 了解了你的需求后就会制定 todo list.
- 创建 GUI 表单,包含 VerticalBoxLayout、两个 FloatField(xGrid/yGrid)和一个 RadioField(objectType)
- 实现 Apply 按钮回调函数,获取表单值并调用相应的处理函数
- 实现 putPinOnGrid 函数,将 pin 中心对齐到 grid,包含 pin shape 验证
- 实现 putInstanceOnGrid 函数,将 instance origin 对齐到 grid
- 实现 putLayerOnGrid 函数,最小扩展 shape 使其四边都在 grid 上
- 添加辅助函数:snapToGrid、getPinCenter、validatePinShape 等
- 添加错误处理和用户反馈,包括警告消息和统计信息
- 创建主入口函数 putAnyOnGrid() 并完善文档说明
AI第五步:构建
点击 build 之后就开始创建代码,直至结束
当我 load 时,居然没有任何错误,还挺意外的
接着当执行时,就蒙圈了, 于是开始debug
人工纠错
浏览了一下,AI 的思路还是很严谨的,整个代码条理很清析,值得学习
debug主要是开始时还不了解 AI 的行事风格,浪费了一点时间,知道它的套路后就简单了,详细就不说了,
我把更改后能跑通的代码与 cursor 原创做了一个 diff 结果如下,可以很直观的看到我改了哪些地方
/***********************************************
> File Name: putAnyOnGrid.il
> Author: chiplayout.net
> Description: Align selected objects (pin/instance/layer) to grid
> Usage: putAnyOnGrid()
***********************************************/
...
- snapped
+ return(snapped)
...
- floor(value / grid) * grid
+ return(floor(value / grid) * grid)
...
- ceil(value / grid) * grid
+ return(ceiling(value / grid) * grid)
...
- procedure(getBBoxCenter(bbox)
+ ;procedure(getBBoxCenter(bbox)
- prog((llx lly urx ury)
+ ; prog((llx lly urx ury)
- llx = lowerLeftX(bbox)
+ ; llx = lowerLeftX(bbox)
- lly = lowerLeftY(bbox)
+ ; lly = lowerLeftY(bbox)
- urx = upperRightX(bbox)
+ ; urx = upperRightX(bbox)
- ury = upperRightY(bbox)
+ ; ury = upperRightY(bbox)
- list((llx + urx) / 2.0 (lly + ury) / 2.0)
+ ; return(list((llx + urx) / 2.0 (lly + ury) / 2.0))
- )
+ ; )
- )
+ ;)
...
- objType = dbGetObjType(obj)
+ objType = obj~>objType
...
- if(objType == "shape" || objType == "rect" || objType == "path" || objType == "polygon"
+ if(objType == "rect"
...
- when(obj->isPinShape
+ when(obj-> pin
...
- layerName = dbGetLayerName(lpp)
+ purpose = cadr(lpp)
- when(layerName && rexMatchp(".*[Pp][Ii][Nn].*" layerName)
+ when(purpose == "pin"
...
- when(obj->net && obj->net->isPin
+ when(obj->net && obj->net->pin
...
- isPin
+ return(isPin)
...
- bbox = dbGetBBox(obj)
+ bbox = obj~>bBox
- center = getBBoxCenter(bbox)
+ center = centerBox(bbox)
...
- dbMoveFig(obj dx dy)
+ dbMoveFig(obj nil list(dx:dy "R0"))
...
- warnings = cons(sprintf("Skipped: Object is not a pin shape (type: %s)" dbGetObjType(obj)) warnings)
+ warnings = cons(sprintf(nil "Skipped: Object is not a pin shape (type: %s)" obj~>objType) warnings)
...
- list(processedCount skippedCount)
+ return(list(processedCount skippedCount))
...
- objType = dbGetObjType(obj)
+ objType = obj~>objType
...
- origin = dbGetOrigin(obj)
+ origin = obj~>xy
...
- dbMoveInst(obj dx dy)
+ dbMoveFig(obj nil list(dx:dy "R0"))
...
- warnings = cons(sprintf("Skipped: Object is not an instance (type: %s)" objType) warnings)
+ warnings = cons(sprintf(nil "Skipped: Object is not an instance (type: %s)" objType) warnings)
...
- list(processedCount skippedCount)
+ return(list(processedCount skippedCount))
...
- objType = dbGetObjType(obj)
+ objType = obj~>objType
- if(objType == "shape" || objType == "rect" || objType == "path" || objType == "polygon"
+ if(objType == "rect"
...
- bbox = dbGetBBox(obj)
+ bbox = obj~>bBox
- llx = lowerLeftX(bbox)
+ llx = leftEdge(bbox)
- lly = lowerLeftY(bbox)
+ lly = bottomEdge(bbox)
- urx = upperRightX(bbox)
+ urx = rightEdge(bbox)
- ury = upperRightY(bbox)
+ ury = bottomEdge(bbox)
...
- dbSetFigBox(obj newLlx newLly newUrx newUry)
+ obj~>bBox = list(newLlx:newLly newUrx:newUry)
...
- warnings = cons(sprintf("Skipped: Object is not a shape (type: %s)" objType) warnings)
+ warnings = cons(sprintf(nil "Skipped: Object is not a rect (type: %s)" objType) warnings)
...
- list(processedCount skippedCount)
+ return(list(processedCount skippedCount))
...
- hiDisplayError(sprintf("Unknown object type: %s" objectType))
+ error(sprintf(nil "Unknown object type: %s" objectType))
...
- hiDisplayPrompt(sprintf("Alignment complete: %d processed, %d skipped" totalProcessed totalSkipped))
+ println(sprintf(nil "Alignment complete: %d processed, %d skipped" totalProcessed totalSkipped))
...
- geRedraw()
+ hiRedraw()
- t
+ return(t)
...
- putAnyOnGridForm = hiCreateAppForm(
+ putAnyOnGridForm = hiCreateLayoutForm(
...
- ?callback 'putAnyOnGridApply
+ ;?callback 'putAnyOnGridApply
- ?formFields list(
+ ;?formFields list(
...
- list(
+ ?items list(
...
- list(
+ ?items list(
hiCreateFloatField(
- 'xGridField
+ ?name 'xGridField
...
- 'yGridField
+ ?name 'yGridField
...
- 'objectTypeField
+ ?name 'objectTypeField
...
- )
+ ?callback "putAnyOnGridApply(hiGetCurrentForm())"
...
- hiDisplayAppForm(putAnyOnGridForm)
+ hiDisplayForm(putAnyOnGridForm)
- t
+ return(t)
...
错误总结
我又让 cursor 根据 diff 的结果提炼了一个错误列表, 还是很直观的
说白了就是对 cadence skill API 了解不全,想当然,就是大家一致认为的 一本正经胡说
prog未使用return返回- 在
prog块中,返回值需要使用return()显式返回,不能直接写表达式
- 在
API 函数名错误
dbGetObjType(obj)→obj~>objType(使用对象属性访问)dbGetBBox(obj)→obj~>bBox(使用对象属性访问)dbGetOrigin(obj)→obj~>xy(使用对象属性访问)ceil()→ceiling()(函数名拼写错误)lowerLeftX/Y()→leftEdge()/bottomEdge()(函数名错误)upperRightX/Y()→rightEdge()/topEdge()(函数名错误)
API 函数参数格式错误
dbMoveFig(obj dx dy)→dbMoveFig(obj nil list(dx:dy "R0"))(需要指定移动参数格式)dbMoveInst(obj dx dy)→dbMoveFig(obj nil list(dx:dy "R0"))(instance 也用 dbMoveFig)sprintf(format ...)→sprintf(nil ...)(第一个参数应为 nil)dbSetFigBox(obj llx lly urx ury)→obj~>bBox = list(llx:lly urx:ury)(直接赋值属性)
对象属性访问错误
obj->isPinShape→obj->pin(属性名错误)obj->net->isPin→obj->net->pin(属性名错误)dbGetLayerName(lpp)→ 检查cadr(lpp)是否为"pin"(purpose 检查)
表单创建 API 错误
hiCreateAppForm()→hiCreateLayoutForm()(应使用 LayoutForm)?formFields list(...)→ 使用hiCreateVerticalBoxLayout()作为第三个参数(layout 结构)- Field 创建需要
?name参数(如?name 'xGridField) - Layout 需要
?items参数(如?items list(...)) ?callback 'putAnyOnGridApply→?callback "putAnyOnGridApply(hiGetCurrentForm())"(字符串形式并获取当前 form)hiDisplayAppForm()→hiDisplayForm()(显示函数名错误)
其他函数调用错误
getBBoxCenter(bbox)→centerBox(bbox)(使用内置函数)hiDisplayPrompt()→println()(提示函数错误)geRedraw()→hiRedraw()(重绘函数错误)error()vshiDisplayError()的使用场景区分
总结
使用 AI 编写专业工具代码存在一定挑战,主要原因是对工具的 API 了解不足,不同版本间可能存在差异,且训练数据中的实际案例较少。不过 AI 的整体思路值得肯定和学习,其创造力也不可小觑,有时能发现一些原本不知道的实用函数。
对于开发者而言,仍需要具备一定的编程基础,熟悉目标语言的 API。如果只是给 AI 一个想法就期望它完全实现,这在 Python、Java、C、Shell 等成熟且使用广泛的通用语言上可能可行,但对于像 Cadence Skill 这样的专业领域语言则不太现实。
总体而言,使用 AI 辅助编程能够节省时间,代码结构也更加严谨,这个投入产出比还是值得的。
百度网盘
通过网盘分享的文件:chiplayout_putAnyOnGrid.il 链接: https://pan.baidu.com/s/1HYMjdxN2A8-fYSYTLpT7wA?pwd=fys4 提取码: fys4 复制这段内容后打开百度网盘手机App,操作更方便哦
如日链接到期请关注首页微信公众号留言
