【免责声明】
• 本公众号的主体为个人,作者在本公众号发表的所有文章均是出于交流学习的目的,对于声明原创的文章,欢迎任何人转载分享,但须注明出处。
• 作者在该公众号发表文章纯属个人行为,文章的观点也属个人观点,与作者曾经任职或者正在任职的公司、其他个人或组织没有任何关系。
• 作者已经发表或者即将发表的文章倚赖于各类软件,作者个人没有实力购买这些软件的使用权,但作者保证所获取的软件仅用于业余学习和交流。若软件商有异议,作者将全力配合删除相关软件。
• 文章中可能有些素材源于网络,若有侵权请读者提醒,作者会在第一时间进行更正。
继续进行AI coding的测试,今天上难度让它试着帮忙写一个想写很久的skill脚本。

前置需求:每次仿真器件工作点的时候都需要写很多的公式检查器件工作点是否正常,这个公式的输入过程是个体力活,所以就想写一个这样的脚本。
基础设想:只需要给脚本一个参考公式,同时在原理图中选择一些器件,运行脚本就可以将所有选中器件相关的公式都添加到ADE L/ADE XL的output窗口。
实现功能:由于上面的设想在实现时让cursor试了很多次都没有实现,所以就降低难度,目前实现了一个半自动功能。
给脚本一个参考公式,同时在原理图中选择器件,运行脚本就可以将所有选中器件相关的公式输出到.csv文件,然后用户通过在ADE L/ADE XL的窗口用Import功能将公式导入到Outputs窗口。
如果使用的是ADE L,则可以点击脚本ui界面的Import直接打开导入界面然后选择文件导入到Outputs窗口。
脚本功能
脚本实现ui和使用步骤如下所示:

使用时需要在原理图中选中至少一个器件,否则脚本会报错:

点击Apply All后,脚本会用选中器件的instanceName去分别替换用户填入公式中特定的字符串,然后将生成的公式写入一个.csv文件,一次可以替换多个公式。

脚本运行后在当前工作目录下生成一个.csv文件,用户后续可以在ADE L或者ADE XL界面通过文件导入Outputs功能将生成的公式导入到Outputs窗口:
生成的文件内容如下所示:本质上脚本工作过程是器件遍历->获取器件instanceName->字符串替换:

导入过程如下所示,打开导入窗口后选择:Import,然后选中生成的.csv文件,如果是ADE L窗口,点击脚本ui上的Import按钮,会直接打开导入对话框。

AI coding过程
本次测试用了openclaw+cursor的工具组合,openclaw生成需求描述,cursor用来执行AI coding过程。
首先,我把上面的需求描述简单给到openclaw,并让它向我提问30个问题来完善对需求的理解,于是它生成了下面这个需求描述:
# Cadence Formula Replacer SKILL 脚本 - 需求文档## 任务目标创建 Cadence IC618 平台的 SKILL 脚本,实现原理图器件 Instance Name 批量替换表达式并输出到 ADE Outputs 窗口。## 背景信息用于 ADE 仿真环境,将表达式中的占位文本批量替换为选中器件的 Instance Name。## 功能需求 ### 1. GUI 界面 - **窗口类型:** 独立非模态窗口- **窗口标题:** "Expression Replacer"- **输入区域:** - 1 个"Text to Replace"文本框(区分大小写,不支持正则) - 5 个表达式区域,每个包含: - 表达式文本框(不限制长度,不校验语法) - Display Name 文本框(选填,为空则输出时不写前缀)- **按钮:** - Apply All - 批量处理所有选中器件 - Apply Next - 逐个处理器件 - Help - 显示使用说明 - Close - 关闭窗口- **窗口特性:** 支持大小调整,不记忆位置- **快捷键:** Ctrl+Shift+O 打开窗口### 2. Apply All 功能 - 遍历所有选中器件(上限 100 个)- 超过 100 个时分批处理,每批 10 个- 每个器件替换全部 5 个表达式,共 n×5 条输出- CIW 输出进度:"已处理 X/总共 Y 个器件"- 允许用户中断- 完成后弹出提示### 3. Apply Next 功能- 每次处理一个器件,输出 5 条表达式- 记录当前进度(第几个器件)- CIW 输出当前 instance name- 全部完成后提示- 用户执行前已选择好器件,不需要自动选中下一个### 4. Output 输出- **输出目标:** ADE/ADExL 的 Outputs 窗口(最前面的那个)- **输出格式:** `<display_name>: <表达式>`(display name 为空则不写前缀)- **文件保存:** 同时保存到当前工作目录下的文本文件,文件名与函数名一致### 5. 器件选择- 用户在原理图(schematic)中直接选中器件- 不支持跨原理图/跨 cell/跨 lib 选择- 不过滤器件类型- 非器件自动跳过- 显示当前已选中器件数量- Instance name 从选中器件的 name 属性获取### 6. 替换逻辑 - 区分大小写- 全局替换(表达式中所有匹配都替换)- 表达式中不含待替换文本时原样输出- 待替换文本为空时报错### 7. 错误处理| 场景 | 处理方式 ||------|----------|| 未选中器件 | 提示"至少选择一个器件" || 待替换文本为空 | 报错 || 表达式为空 | 跳过该表达式 || 替换过程出错 | 退出并记录日志 || ADE 未打开 | 提示打开 ADE |### 8. ADE 集成- 检测 ADE 会话是否打开,未打开则提示- 自动添加到 ADE outputs 列表- 输出到最前面的 ADE 或 ADExL 窗口### 9. 日志与调试- 调试模式:输出详细日志- 日志保存到当前工作目录- 全部 log,不支持级别配置### 10. 帮助文档- Help 按钮显示使用说明- 使用说明为 Markdown 格式## 约束条件- **平台:** Cadence Virtuoso IC618(暂不兼容其他版本)- **语言:** SKILL- **最大器件数:** 100 个- **分批处理:** 每批 10 个## 交付物1. `.il` 脚本文件2. `README.md` 使用说明文档## 验收标准 - [ ] GUI 正常打开(快捷键 Ctrl+Shift+O)- [ ] Apply All 功能正常(批量处理、进度显示、可中断)- [ ] Apply Next 功能正常(逐个处理、进度记录)- [ ] 输出到 ADE Outputs 窗口- [ ] 日志文件生成- [ ] 帮助文档显示- [ ] 错误处理正常_文档生成时间:2026-04-13_接下来我将完整的需求文档粘贴给cursor,并告诉它生成的文件保存路径,cursor就开始工作了:

cursor思考了大概5分钟左右,中间又去查一些网站关于skill语法和api的信息,最后完成了初版脚本:

我试着运行了初版脚本,发现语法错误,于是截图报错信息,继续修改语法,语法问题大概用了7-8轮对话才完全解决。
脚本运行后界面大概如图所示(初版ui图找不到了),后续继续测试脚本功能实现情况,发现按照最初的设想实现起来有一堆问题:

于是降低难度,告诉cursor将公式替换并按照一定的格式导出到.csv文件,然后用户自己选择文件导入公式:

这个需求倒是很容易实现,修改3-4次就基本实现了需要的功能,后续让cursor自己检查了代码内容并做了一些简化和备注,完整代码如下:
;===============================================================; Cadence Formula Replacer (IC618);===============================================================; -----------------------------; Global variables; -----------------------------; Author: icskillsharing; Purpose: Replace placeholder text in expressions with selected instance names.hiSetBindKey("Schematics" "Ctrl Shift<key>O" "gfr_open()")(defvar gfr_form nil)(defvar gfr_replaceField nil)(defvar gfr_exprFields nil)(defvar gfr_dispFields nil)(defvar gfr_selectedInsts nil)(defvar gfr_debugMode t)(defvar gfr_logFile nil)(defvar gfr_outFile nil)(defvar gfr_maxDevices 100)(defvar gfr_batchSize 10); -----------------------------; Utilities; -----------------------------(procedure (gfr_nowStr) (getCurrentTime))(procedure (gfr_safeStr s) (if s s ""))(procedure (gfr_isEmpty s) (or (null s) (equal s "")))(procedure (gfr_getCwd) (let ((d nil)) (setq d (getWorkingDir)) (if d d ".") ))(procedure (gfr_log msg) (let ((fp nil)) (when gfr_debugMode (printf "[GFR] %s\n" msg) ) (when gfr_logFile (setq fp (outfile gfr_logFile "a")) (when fp (fprintf fp "%L | %s\n" (gfr_nowStr) msg) (close fp) ) ) ))(procedure (gfr_initFiles) (let ((cwd nil) (lfp nil) (ofp nil)) (setq cwd (gfr_getCwd)) (setq gfr_logFile (strcat cwd "/cadence_formula_replacer.log")) (setq gfr_outFile (strcat cwd "/cadence_formula_replacer_output.csv")) (setq lfp (outfile gfr_logFile "w")) (when lfp (fprintf lfp "Cadence Formula Replacer Log\n") (fprintf lfp "Started: %L\n\n" (gfr_nowStr)) (close lfp) ) (setq ofp (outfile gfr_outFile "w")) (when ofp (fprintf ofp "Name,Type,Output,Plot,Save\n") (close ofp) ) ))(procedure (gfr_csvEscape s) (let ((x "")) ; keep original expression text as-is (no extra quote doubling) ; so output matches ADE expression format exactly. (setq x (gfr_safeStr s)) x ))(procedure (gfr_appendOutput disp expr) (let ((ofp nil) (name "") (outExpr "") (csvLine "")) (setq name (gfr_safeStr disp)) (setq outExpr (gfr_safeStr expr)) ; CSV columns: Name,Type,Output,Plot,Save ; Type fixed to expr, Plot=t, Save=t (setq csvLine (sprintf nil "%s,expr,%s,t,t" (gfr_csvEscape name) (gfr_csvEscape outExpr) ) ) (setq ofp (outfile gfr_outFile "a")) (when ofp (fprintf ofp "%s\n" csvLine) (close ofp) ) )); IC618 rexReplace compatibility: use rexCompile + rexReplace(str repl count); count=0 means replace all matches.(procedure (gfr_replaceAllLiteral expr from to) (let ((res "")) (setq res (gfr_safeStr expr)) (when (gfr_isEmpty from) (error "ERROR: Text to replace cannot be empty\n") ) (rexCompile (gfr_safeStr from)) (setq res (rexReplace res (gfr_safeStr to) 0)) res )); Select editable instances from the current schematic selection.(procedure (gfr_getSelectedInstances) (let ((sel nil) (obj nil) (ot "") (nm "") (insts nil)) (setq sel (geGetSelSet)) (foreach obj sel (setq ot (gfr_safeStr obj~>objType)) (setq nm (gfr_safeStr obj~>name)) (when (or (equal ot "inst") (equal ot "instance") (equal ot "instHeader")) (setq insts (cons obj insts)) ) ) (reverse insts) ))(procedure (gfr_formatOutLine disp expr) (if (gfr_isEmpty disp) expr (sprintf nil "%s: %s" disp expr) ))(procedure (gfr_fnExists sym) (not (null (getd sym)))); -----------------------------; Selection and processing; -----------------------------(procedure (gfr_getSelectedInstances) (let ((sel nil) (obj nil) (ot "") (nm "") (insts nil) (cnt 0) (selCnt 0)) (setq sel (geGetSelSet)) (setq selCnt (length sel)) (printf "[GFR-DIAG] geGetSelSet count = %d\n" selCnt) (foreach obj sel (setq ot (gfr_safeStr obj~>objType)) (setq nm (gfr_safeStr obj~>name)) (printf "[GFR-DIAG] selected objType=%s name=%s\n" ot nm) (when (or (equal ot "inst") (equal ot "instance") (equal ot "instHeader")) (setq insts (cons obj insts)) ) ) (setq insts (reverse insts)) (setq cnt (length insts)) (printf "[GFR-DIAG] recognized instance count = %d\n" cnt) insts )); -----------------------------; Input collection and processing; -----------------------------(procedure (gfr_collectInputs) (let ((replaceTxt "") (exprs nil) (disps nil) (i 0) (ef nil) (df nil) (e "") (d "")) (setq replaceTxt (gfr_safeStr gfr_replaceField~>value)) (when (gfr_isEmpty replaceTxt) (error "ERROR: Text to replace cannot be empty\n") ) (setq exprs nil) (setq disps nil) (for i 0 4 (setq ef (nth i gfr_exprFields)) (setq df (nth i gfr_dispFields)) (setq e (gfr_safeStr ef~>value)) (setq d (gfr_safeStr df~>value)) (setq exprs (append1 exprs e)) (setq disps (append1 disps d)) ) (list replaceTxt exprs disps) ))(procedure (gfr_processOneInst inst replaceTxt exprs disps) (let ((instName "") (i 0) (expr "") (disp "") (newExpr "") (newDisp "") (line "")) (setq instName (gfr_safeStr inst~>name)) (printf "[GFR] Current instance: %s\n" instName) (gfr_log (sprintf nil "Processing instance: %s" instName)) (for i 0 4 (setq expr (nth i exprs)) (setq disp (nth i disps)) ; Empty expression: skip (unless (gfr_isEmpty expr) (setq newExpr (gfr_replaceAllLiteral expr replaceTxt instName)) (setq newDisp (gfr_replaceAllLiteral (gfr_safeStr disp) replaceTxt instName)) (setq line (gfr_formatOutLine newDisp newExpr)) (gfr_appendOutput newDisp newExpr) (gfr_log (sprintf nil "Output: %s" line)) ) ) ))(procedure (gfr_limitInsts insts) (let ((n 0)) (setq n (length insts)) (if (greaterp n gfr_maxDevices) (subseq insts 0 gfr_maxDevices) insts ) )); -----------------------------; Callbacks; -----------------------------(procedure (gfr_applyAllCB @rest args) (let ((inputs nil) (replaceTxt "") (exprs nil) (disps nil) (insts nil) (total 0) (batchStart 0) (batchEnd 0) (idx 0) (inst nil)) (errset (progn (gfr_initFiles) (setq insts (gfr_getSelectedInstances)) (setq total (length insts)) (when (lessp total 1) (error "Please select at least one device in the schematic\n") ) (when (greaterp total gfr_maxDevices) (printf "[GFR] More than %d selected, only first %d will be processed.\n" gfr_maxDevices gfr_maxDevices) (setq insts (gfr_limitInsts insts)) (setq total (length insts)) ) (setq inputs (gfr_collectInputs)) (setq replaceTxt (car inputs)) (setq exprs (cadr inputs)) (setq disps (caddr inputs)) (gfr_log (sprintf nil "Apply All start. Total devices: %d" total)) (setq batchStart 0) (while (lessp batchStart total) (setq batchEnd (plus batchStart gfr_batchSize)) (when (greaterp batchEnd total) (setq batchEnd total) ) (setq idx batchStart) (while (lessp idx batchEnd) (setq inst (nth idx insts)) (gfr_processOneInst inst replaceTxt exprs disps) (printf "[GFR] Processed %d/%d devices\n" (plus idx 1) total) ; user break if available (when (and (boundp 'hiIsUserBreak) (hiIsUserBreak)) (error "User interrupted processing\n") ) (setq idx (plus idx 1)) ) (setq batchStart (plus batchStart gfr_batchSize)) ) (gfr_log "Apply All done") (hiDisplayAppDBox ?name 'gfrDoneAll ?dboxBanner "Expression Replacer" ?dboxText (sprintf nil "Apply All completed. Processed %d devices." total) ?dialogType hicInformationDialog ?buttonLayout 'Close ) ) t ) ))(procedure (gfr_helpText) "# Expression Replacer User Guide\n\n## Features\n- Replace placeholder text in expressions with selected instance names\n- Support 5 expression rows plus display names\n- Apply replacements to all selected devices\n\n## Workflow\n1. Select one or more instances in the schematic\n2. Enter the text to replace\n3. Enter expressions for the rows you want to use\n4. Click Apply All\n5. Use Import to try opening the ADE L import dialog\n\n## Output\n- Generates a CSV file in the current working directory\n- Writes a log file alongside the CSV file\n\n## Notes\n- Matching is case-sensitive\n- Replacement is global within each expression\n- Empty expression rows are skipped\n- Import attempts to open the ADE L import dialog when supported\n")(procedure (gfr_helpCB @rest args) (hiDisplayAppDBox ?name 'gfrHelp ?dboxBanner "Expression Replacer Help" ?dboxText (gfr_helpText) ?dialogType hicInformationDialog ?buttonLayout 'Close ))(procedure (gfr_importCB @rest args) (let ((sess nil) (r nil)) (printf "[GFR] Import button clicked.\n") (when (and (getd 'sevSession) (getd 'sevImportOutputsFromCSV)) (setq sess (errset (sevSession (hiGetCurrentWindow)) t)) (printf "[GFR-DIAG] sevSession(hiGetCurrentWindow()) => %L\n" sess) (when (and sess (car sess)) (setq r (errset (sevImportOutputsFromCSV (car sess)) t)) (printf "[GFR-DIAG] sevImportOutputsFromCSV(sevSession(hiGetCurrentWindow())) => %L\n" r) (when (and r (car r)) (printf "[GFR] ADE L Import Output dialog opened.\n") ) ) ) (hiDisplayAppDBox ?name 'gfrImportInfo ?dboxBanner "Expression Replacer" ?dboxText (sprintf nil "CSV file is ready:\n%s\n\nAutomatic import dialog could not be opened. Please use ADE L -> Outputs -> Import manually if needed." gfr_outFile) ?dialogType hicInformationDialog ?buttonLayout 'Close ) ))(procedure (gfr_closeCB @rest args) (when gfr_form (hiFormDone gfr_form) (setq gfr_form nil) ))(procedure (gfr_clearCB @rest args) (when gfr_form gfr_form->replaceText->value = "" gfr_form->expr1->value = "" gfr_form->expr2->value = "" gfr_form->expr3->value = "" gfr_form->expr4->value = "" gfr_form->expr5->value = "" gfr_form->disp1->value = "" gfr_form->disp2->value = "" gfr_form->disp3->value = "" gfr_form->disp4->value = "" gfr_form->disp5->value = "" ) (setq gfr_selectedInsts nil) (printf "[GFR] All text fields cleared.\n")); -----------------------------; GUI; -----------------------------(procedure (gfr_buildFields) (let ((i 0) (y 78) (dy 46) (exprField nil) (dispField nil) (exprLabel nil) (dispLabel nil) (replaceLabel nil) (placeFields nil) (replaceLabelPlace nil) (replacePlace nil) (exprLabelPlace nil) (exprPlace nil) (dispLabelPlace nil) (dispPlace nil) (applyAllBtn nil) (clearBtn nil) (importBtn nil) (helpBtn nil) (closeBtn nil) (applyAllPlace nil) (clearPlace nil) (importPlace nil) (helpPlace nil) (closePlace nil)) (setq gfr_exprFields nil) (setq gfr_dispFields nil) ; top label + input (setq replaceLabel (hiCreateLabel ?name 'replaceLabel ?labelText "Text to Replace" ) ) (setq gfr_replaceField (hiCreateStringField ?name 'replaceText ?value "" ) ) (setq applyAllBtn (hiCreateButton ?name 'applyAllBtn ?buttonText "Apply All" ?callback "gfr_applyAllCB()")) (setq clearBtn (hiCreateButton ?name 'clearBtn ?buttonText "Clear" ?callback "gfr_clearCB()")) (setq importBtn (hiCreateButton ?name 'importBtn ?buttonText "Import" ?callback "gfr_importCB()")) (setq helpBtn (hiCreateButton ?name 'helpBtn ?buttonText "Help" ?callback "gfr_helpCB()")) (setq closeBtn (hiCreateButton ?name 'closeBtn ?buttonText "Close" ?callback "gfr_closeCB()")) ; placement descriptors for hiCreateAppForm ?fields (setq replaceLabelPlace (list replaceLabel 20:46 150:20 0)) (setq replacePlace (list gfr_replaceField 180:42 480:28 0)) (for i 1 5 (setq exprLabel (hiCreateLabel ?name (stringToSymbol (sprintf nil "exprLabel%d" i)) ?labelText (sprintf nil "Expression %d" i) ) ) (setq exprField (hiCreateStringField ?name (stringToSymbol (sprintf nil "expr%d" i)) ?value "" ) ) (setq dispLabel (hiCreateLabel ?name (stringToSymbol (sprintf nil "dispLabel%d" i)) ?labelText (sprintf nil "Display Name %d" i) ) ) (setq dispField (hiCreateStringField ?name (stringToSymbol (sprintf nil "disp%d" i)) ?value "" ) ) (setq gfr_exprFields (append1 gfr_exprFields exprField)) (setq gfr_dispFields (append1 gfr_dispFields dispField)) (setq exprLabelPlace (list exprLabel 20:(plus y 4) 150:18 0)) (setq exprPlace (list exprField 180:y 250:28 0)) (setq dispLabelPlace (list dispLabel 440:(plus y 4) 120:18 0)) (setq dispPlace (list dispField 560:y 100:28 0)) (setq placeFields (append1 placeFields exprLabelPlace)) (setq placeFields (append1 placeFields exprPlace)) (setq placeFields (append1 placeFields dispLabelPlace)) (setq placeFields (append1 placeFields dispPlace)) (setq y (plus y dy)) ) (setq applyAllPlace (list applyAllBtn 45:334 105:30 0)) (setq clearPlace (list clearBtn 160:334 85:30 0)) (setq importPlace (list importBtn 255:334 85:30 0)) (setq helpPlace (list helpBtn 350:334 85:30 0)) (setq closePlace (list closeBtn 455:334 85:30 0)) (append (append (list replaceLabelPlace replacePlace) placeFields) (list applyAllPlace clearPlace importPlace helpPlace closePlace)) ))(procedure (gfr_createForm) (let ((fields nil)) (setq fields (gfr_buildFields)) (setq gfr_form (hiCreateAppForm ?name 'gfrMainForm ?formTitle "Expression Replacer" ?fields fields ?buttonLayout 'Empty ?initialSize (list 680 460) ?minSize (list 640 420) ?unmapAfterCB nil ) ) (hiDisplayForm gfr_form) gfr_form ))(procedure (gfr_open) (if gfr_form (hiDisplayForm gfr_form) (gfr_createForm) ) (setq gfr_importFile gfr_outFile))(procedure (gfr_start) (gfr_open))(printf "Cadence Formula Replacer loaded. Run (gfr_open).\n")本代码在Virtuoso IC618_320版本验证通过,功能正常,有需要使用的同学可以尝试,快捷键是Ctrl+Shift+O, 如果报版本相关的问题可以继续丢给AI做一下微调。
简介

动动你的发财手,给个“在看”呗!

夜雨聆风