Featured image of post 使用AI写一个将obj放在Grid上的skill GUI

使用AI写一个将obj放在Grid上的skill GUI

在 Virtuoso Layout 编辑器中,将对象精确对齐到网格是布局设计中的常见需求。本文记录了使用 Cursor AI 助手编写一个 Skill 脚本的完整过程,该脚本通过 GUI 表单实现将选中的 pin、instance 或 layer 对象对齐到指定网格的功能。虽然 AI 生成的初始代码在逻辑上基本正确,但在实际运行中遇到了大量 Cadence Skill API 使用错误,包括函数名错误、参数格式不正确、对象属性访问方式错误等问题。经过系统性的调试和修正,最终成功实现了功能。本文详细总结了这些常见错误类型及其正确的修复方法,为后续使用 AI 编写 Skill 脚本提供了宝贵的经验参考.

背景

在实际工作中,遇到把 cell 放到固定格点,也遇到把 layer 如注入层放到格点,还有将 pin 放到 track上的场景, 虽然是简单的 skill , 但是针对每种 ojbect 都有一个 script , 因为简单, 也没有 GUI。 昨天看到一些 AIprompt 技巧, 已经帮我很出色的完成了另一项任务,就尝试看大家一直期望的用 AIskill 脚本。 将 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 了解不全,想当然,就是大家一致认为的 一本正经胡说

  1. prog 未使用 return 返回

    • prog 块中,返回值需要使用 return() 显式返回,不能直接写表达式
  2. API 函数名错误

    • dbGetObjType(obj)obj~>objType(使用对象属性访问)
    • dbGetBBox(obj)obj~>bBox(使用对象属性访问)
    • dbGetOrigin(obj)obj~>xy(使用对象属性访问)
    • ceil()ceiling()(函数名拼写错误)
    • lowerLeftX/Y()leftEdge()/bottomEdge()(函数名错误)
    • upperRightX/Y()rightEdge()/topEdge()(函数名错误)
  3. 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)(直接赋值属性)
  4. 对象属性访问错误

    • obj->isPinShapeobj->pin(属性名错误)
    • obj->net->isPinobj->net->pin(属性名错误)
    • dbGetLayerName(lpp) → 检查 cadr(lpp) 是否为 "pin"(purpose 检查)
  5. 表单创建 API 错误

    • hiCreateAppForm()hiCreateLayoutForm()(应使用 LayoutForm)
    • ?formFields list(...) → 使用 hiCreateVerticalBoxLayout() 作为第三个参数(layout 结构)
    • Field 创建需要 ?name 参数(如 ?name 'xGridField
    • Layout 需要 ?items 参数(如 ?items list(...)
    • ?callback 'putAnyOnGridApply?callback "putAnyOnGridApply(hiGetCurrentForm())"(字符串形式并获取当前 form)
    • hiDisplayAppForm()hiDisplayForm()(显示函数名错误)
  6. 其他函数调用错误

    • getBBoxCenter(bbox)centerBox(bbox)(使用内置函数)
    • hiDisplayPrompt()println()(提示函数错误)
    • geRedraw()hiRedraw()(重绘函数错误)
    • error() vs hiDisplayError() 的使用场景区分

总结

使用 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,操作更方便哦

如日链接到期请关注首页微信公众号留言

陕ICP备20000710号
本站已运行15年5月1天
发表了394篇文章 · 总计13万9千字
最后更新:2026-01-15
Built with Hugo
Theme Stack designed by Jimmy