戰(zhàn):從CIW到自動(dòng)化版圖與PCB設(shè)計(jì)效率提升)
干過(guò)版圖或 PCB 設(shè)計(jì)的人大概都經(jīng)歷過(guò)那種“人肉重復(fù)勞動(dòng)”的崩潰時(shí)刻幾千個(gè)符號(hào)要改屬性、上百個(gè)封裝要核對(duì)位號(hào)、導(dǎo)出 BOM 的時(shí)候格式不對(duì)又得從頭再來(lái)。Cadence 家的 Virtuoso、Allegro、IC251 這些工具圖形界面能幫你干八成的事但剩下那兩成最耗時(shí)的批量操作恰恰是 SKILL 語(yǔ)言的絕對(duì)主場(chǎng)。SKILL 是 Cadence 內(nèi)置的腳本語(yǔ)言和 Lisp 有血緣關(guān)系你在菜單里點(diǎn)到的幾乎所有功能底層都對(duì)應(yīng)著一組 SKILL 函數(shù)把函數(shù)按邏輯串起來(lái)就是能代替你熬夜的自動(dòng)化腳本。這篇指南不打算從語(yǔ)言史講起而是直接按“環(huán)境準(zhǔn)備 → 函數(shù)清單 → 實(shí)戰(zhàn)腳本 → 工程化 → 避坑”的順序把我自己在實(shí)際項(xiàng)目里驗(yàn)證過(guò)的東西一次說(shuō)透。適合正在用 Cadence 做模擬版圖、數(shù)字后端或 PCB 設(shè)計(jì)又不想繼續(xù)在重復(fù)操作里耗時(shí)間的工程師。讀完后你會(huì)拿到可以直接抄的腳本骨架也會(huì)知道哪些坑是文檔里不會(huì)寫的。1. 為什么你的 Cadence 工作流里缺一個(gè) SKILL 腳本1.1 我最早被 SKILL 震撼到的場(chǎng)景入門 SKILL 之前我在 Virtuoso 里做過(guò)一次非常痛苦的版圖整理兩千多個(gè) instance 需要在指定層上加標(biāo)識(shí)文本并且要根據(jù) cellName 自動(dòng)生成內(nèi)容。用界面一個(gè)個(gè)加做得我想把鼠標(biāo)摔了。后來(lái)旁邊一位老工程師看不過(guò)去在我 CIW 窗口敲了幾行命令一段循環(huán)跑完不到一分鐘全部搞定。就是從那一刻起我意識(shí)到同樣的時(shí)間會(huì) SKILL 的人已經(jīng)在喝咖啡了不會(huì)的人還在點(diǎn)鼠標(biāo)。那次經(jīng)歷之后我開始系統(tǒng)查函數(shù)列表、看官方文檔、拿真實(shí)項(xiàng)目練手越深入越覺得這東西才是 Cadence 工具鏈里最被低估的生產(chǎn)力工具。1.2 SKILL 到底是一門怎樣的語(yǔ)言很多人一聽“腳本語(yǔ)言”就以為是 Python 那種風(fēng)格但實(shí)際上 SKILL 的語(yǔ)法非常特別。它有兩種寫法一種是 Lisp 風(fēng)格的括號(hào)形式比如(plus 1 2)另一種是代數(shù)形式比如1 2。兩種寫法在交互環(huán)境下都能被解釋器接受。這門語(yǔ)言從 1980 年代末就出現(xiàn)在 Cadence 工具里了幾十年積累下來(lái)形成了一座非常龐大的函數(shù)庫(kù)。粗略分一下SKILL 函數(shù)大致覆蓋這幾類通用編程列表操作、字符串處理、流程控制、數(shù)學(xué)計(jì)算文件 I/O讀文件、寫文件、目錄掃描數(shù)據(jù)庫(kù)操作訪問(wèn)當(dāng)前設(shè)計(jì)、遍歷元件/Instance/Shape、創(chuàng)建和修改對(duì)象、設(shè)置屬性圖形界面創(chuàng)建 Form、彈出提示框、注冊(cè)菜單命令回調(diào)與控制用戶觸發(fā)器、快捷鍵綁定、命令注冊(cè)也就是說(shuō)SKILL 不只是一個(gè)“宏錄制工具”它是一門能完成復(fù)雜業(yè)務(wù)邏輯的完整語(yǔ)言。提示SKILL 腳本文件常見的擴(kuò)展名是.il系統(tǒng)和用戶自定義的函數(shù)都可以存放在這類文本文件里加載后即可調(diào)用。命名時(shí)盡量別用check、skill這類容易被工具內(nèi)部命令沖突的單詞。1.3 這篇實(shí)戰(zhàn)指南的目標(biāo)讀者如果你滿足下面任一條件這篇文章就是寫給你的你在用 Virtuoso / Allegro / IC251 做設(shè)計(jì)每天有大量重復(fù)性操作你是 CAD 或 EDA 支持工程師經(jīng)常需要幫團(tuán)隊(duì)批量修數(shù)據(jù)你想把團(tuán)隊(duì)里的“老師傅經(jīng)驗(yàn)”沉淀成可復(fù)用的工具你暫時(shí)還沒寫過(guò) SKILL但希望有一條不那么陡峭的學(xué)習(xí)路徑這篇文章不會(huì)停留在語(yǔ)法層面我會(huì)盡量把“函數(shù)列表怎么查、腳本怎么搭、坑怎么躲”講清楚。2. 跑通環(huán)境與第一個(gè)腳本從 CIW 到 Skill Debugger2.1 不用安裝任何額外環(huán)境先找這些入口SKILL 的運(yùn)行時(shí)就在 Cadence 工具里面不需要你單獨(dú)安裝。根據(jù)你用的工具不同入口略有區(qū)別工具入口交互窗口Virtuoso / IC251CIW 窗口下方的命令行直接輸入 SKILL 表達(dá)式Allegro / OrCAD PCB Editor命令窗口輸入skill進(jìn)入支持逐行輸入和腳本加載批量環(huán)境在 shell 里啟動(dòng)工具時(shí)附加腳本參數(shù)以-replay或腳本方式運(yùn)行以 Virtuoso 為例啟動(dòng)后你會(huì)在 Command Interpreter WindowCIW底部看到一行輸入框那就是 SKILL 解釋器。你輸入11回車它會(huì)立刻返回2。Allegro 稍微繞一點(diǎn)默認(rèn)命令窗口接收的是 Allegro 自身的命令要先輸入skill進(jìn)入 SKILL 模式才能寫 SKILL 表達(dá)式。不想手動(dòng)切換的話也可以把調(diào)試內(nèi)容直接寫在.il文件里用load加載。2.2 編輯器選型VSCode、Vim 還是自帶 IDESKILL 腳本本質(zhì)是純文本用系統(tǒng)自帶的文本編輯器就能寫。但實(shí)際開發(fā)中沒有語(yǔ)法高亮和括號(hào)匹配腳本稍微長(zhǎng)一點(diǎn)就非常痛苦。我自己的組合是VSCode SKILL 擴(kuò)展 系統(tǒng)終端。VSCode 里搜索 SKILL 插件可以讓.il文件獲得基礎(chǔ)高亮和括號(hào)匹配還能在文件側(cè)邊欄快速看到函數(shù)定義列表。這個(gè)“函數(shù)列表”能力對(duì)腳本開展特別重要尤其當(dāng)你的腳本積累到幾百行時(shí)能一眼跳到某個(gè) procedure 所在位置效率完全不一樣。如果你習(xí)慣在終端環(huán)境下工作Vim 配合syntax on也能勝任。Cadence 工具鏈自帶的 SKILL IDE在部分版本中可用雖然集成度更高但啟動(dòng)稍重我自己用得反而不多。2.3 加載腳本的基本命令SKILL 里加載一個(gè)腳本文件最常用的命令就是loadload(~/skill_scripts/demo.il)路徑可以寫絕對(duì)路徑也可以寫相對(duì)于當(dāng)前工作目錄的路徑。加載成功后解釋器通常不會(huì)有特別顯眼的提示但腳本里定義的函數(shù)和變量就已經(jīng)可用了。如果你只是臨時(shí)想試一段代碼不想寫文件可以直接在交互窗口粘貼表達(dá)式回車即執(zhí)行。在 Allegro 的 SKILL 模式下也是同理。2.4 用一個(gè)最小腳本驗(yàn)證環(huán)境新建一個(gè)文件命名為hello.il內(nèi)容如下procedure( helloWorld( ) printf(Hello SKILL!\n) ) helloWorld()然后在工具的交互窗口里執(zhí)行l(wèi)oad(hello.il)如果看到Hello SKILL!的輸出說(shuō)明環(huán)境已經(jīng)通了。別小看這個(gè)流程很多人的第一個(gè)坑是路徑問(wèn)題load不支持~展開到某些工作目錄或者腳本文件的權(quán)限不對(duì)。我建議路徑里優(yōu)先用絕對(duì)路徑少用相對(duì)路徑能省掉很多低級(jí)報(bào)錯(cuò)。3. 函數(shù)清單不是背出來(lái)的按用途分類吃透核心 API3.1 列表與字符串處理SKILL 的 Lisp 基因SKILL 里的核心數(shù)據(jù)結(jié)構(gòu)是列表list它同時(shí)扮演了數(shù)組、隊(duì)列、棧等多種角色。最開始寫腳本時(shí)你不需要精通所有列表函數(shù)但這幾個(gè)一定會(huì)頻繁用到函數(shù)作用示例list/()構(gòu)造列表list(1 2 3)或(1 2 3)car/cdr取首元素 / 去掉首元素car((1 2 3))1nth按下標(biāo)取元素下標(biāo)從 0 開始nth(1 (10 20 30))20append合并列表append((1 2) (3 4))cons在頭部插入元素cons(0 (1 2))(0 1 2)length取長(zhǎng)度length((a b c))3member判斷元素是否存在member(b (a b c))foreach遍歷列表foreach(x (1 2 3) printf(%d\n x))mapcar對(duì)每個(gè)元素執(zhí)行函數(shù)mapcar(lambda((x) x*x) (1 2 3))setof按條件篩選列表setof(x (1 2 3 4) x2)(3 4)這里面最常用也最好用的是foreach和setof。setof相當(dāng)于 Python 里的列表推導(dǎo)式比如你想找出當(dāng)前設(shè)計(jì)里所有 cellName 為INV的 instance可以直接寫setof(inst cellView~instances inst~cellName INV)字符串方面重點(diǎn)掌握三個(gè)strcat字符串拼接strcat(a b)返回absprintf格式化生成字符串用于拼報(bào)告非常方便parseString/buildString字符串拆成列表 / 列表拼成字符串strList parseString(a,b,c ,) ; 返回 (a b c) newStr buildString(strList -) ; 返回 a-b-c3.2 對(duì)象與數(shù)據(jù)庫(kù)操作和設(shè)計(jì)數(shù)據(jù)對(duì)話如果說(shuō)列表函數(shù)負(fù)責(zé)“處理數(shù)據(jù)”那么數(shù)據(jù)庫(kù)操作函數(shù)才是 SKILL 真正發(fā)光發(fā)熱的地方。這類函數(shù)通常以db、ge、axl等前綴開頭不同工具覆蓋不同領(lǐng)域。在 Virtuoso 的版圖環(huán)境里最常用的幾個(gè)函數(shù)作用geGetEditCellView()獲取當(dāng)前正在編輯的 cellViewdbOpen/dbClose打開/關(guān)閉數(shù)據(jù)庫(kù)dbGetq查詢對(duì)象屬性dbCreateRect創(chuàng)建矩形dbCreatePath創(chuàng)建路徑dbCreateInstance創(chuàng)建 instancedbGet按條件搜索并返回對(duì)象列表dbCommit提交對(duì)數(shù)據(jù)庫(kù)的修改實(shí)際使用中你經(jīng)常會(huì)看到類似這樣的代碼cellView geGetEditCellView() foreach(inst cellView~instances printf(Instance %s, cellName %s\n inst~name inst~cellName) )在 Allegro/PCB Editor 環(huán)境下函數(shù)前綴通常是axldesign axlDBGetDesign() foreach(comp design~components printf(位號(hào) %s\n comp~refdes) )這里的~是 SKILL 里的屬性訪問(wèn)符類似 C 語(yǔ)言的.。先通過(guò)~取出對(duì)象屬性再通過(guò)函數(shù)修改對(duì)象幾乎就是數(shù)據(jù)庫(kù)操作的全部套路。注意不同版本中元件的“封裝名”字段可能是package也可能是symbol。第一次在陌生版本上寫腳本時(shí)建議先用類似comp~?的方式查看對(duì)象可用屬性再確定字段名。這樣能避免絕大多數(shù)“函數(shù)名或?qū)傩悦麑戝e(cuò)”的冤案。3.3 用戶交互與回顯讓腳本更可用寫腳本時(shí)如果所有結(jié)果都只在終端里輸出別人用起來(lái)會(huì)很不友好。SKILL 提供了一系列交互函數(shù)能讓腳本具備簡(jiǎn)單的“界面感”。最基礎(chǔ)的是回顯函數(shù)printf格式化輸出到當(dāng)前窗口println輸出一行自動(dòng)加換行hiDisplayMessage在圖形界面彈出一段提示更進(jìn)一步你可以用axlUIPopupAllegro或hiDisplayFormVirtuoso創(chuàng)建簡(jiǎn)單的彈窗。對(duì)于大多數(shù)內(nèi)部自動(dòng)化腳本我不建議一上來(lái)就設(shè)計(jì)龐大的圖形界面先用一個(gè)彈窗提示結(jié)果就夠了后面有需要再迭代。菜單注冊(cè)也是交互增強(qiáng)的關(guān)鍵。Allegro 里可以通過(guò)axlCmdRegister把 SKILL 函數(shù)注冊(cè)成命令這樣用戶在命令欄輸入一個(gè)自定義單詞就能執(zhí)行腳本procedure( myCheck( ) printf(運(yùn)行檢查...\n) ) axlCmdRegister(myCheck myCheck)注冊(cè)之后在 Allegro 命令窗口直接輸入myCheck就等價(jià)于調(diào)用這個(gè)函數(shù)。Virtuoso 則可以通過(guò)hiRegMenu往菜單欄掛按鈕方式雖不同思路一致。3.4 如何高效查閱官方函數(shù)參考Cadence 自帶的 SKILL 函數(shù)參考文檔非常厚重第一次看的人容易懵。我的經(jīng)驗(yàn)是遇到不確定的函數(shù)時(shí)在 SKILL 交互窗口使用在線幫助函數(shù)比翻 PDF 快得多。常見的幫助函數(shù)包括describe(axlDBGetDesign)查看函數(shù)說(shuō)明? axlDBGetDesign查看屬性幫助部分環(huán)境支持getSkillPath()查看搜索路徑搜索資料時(shí)要留一個(gè)心眼網(wǎng)絡(luò)上的 SKILL 代碼風(fēng)格差異很大有些老腳本用了不少已經(jīng)被替代的舊函數(shù)跑起來(lái)會(huì)報(bào) deprecated 警告。優(yōu)先以你實(shí)際安裝版本的官方文檔為準(zhǔn)網(wǎng)上的代碼只做參考思路不要無(wú)腦照抄。4. 實(shí)戰(zhàn)拆解用 80 行腳本完成元件位號(hào)與封裝體檢4.1 先別寫代碼先拆需求很多初學(xué)者拿到需求就開寫結(jié)果寫到一半發(fā)現(xiàn)邏輯漏洞百出。正確的做法是先快速拆解任務(wù)邊界。我以一個(gè)真實(shí)場(chǎng)景為例PCB 設(shè)計(jì)交付前需要檢查所有元件是否有位號(hào)、封裝是否缺失、坐標(biāo)是否超出板框范圍。這個(gè)任務(wù)如果靠人工翻版圖看屬性幾千個(gè)元件看到懷疑人生用 SKILL 做邏輯其實(shí)很清晰獲取當(dāng)前設(shè)計(jì)對(duì)象遍歷所有元件逐個(gè)讀取位號(hào)、封裝、坐標(biāo)屬性按規(guī)則判斷是否異常把異常信息收集到列表里生成報(bào)告文件并用彈窗提示摘要這五步不涉及任何復(fù)雜的算法核心就是對(duì)“元件對(duì)象列表”的一次過(guò)濾和統(tǒng)計(jì)。我刻意選擇這個(gè)例子是想說(shuō)明一個(gè)最重要的觀點(diǎn)任何自動(dòng)化腳本的第一產(chǎn)出物不是代碼而是邏輯流程圖。你先把步驟畫出來(lái)在紙上或文檔里再動(dòng)手會(huì)順利很多。4.2 獲取設(shè)計(jì)數(shù)據(jù)和元件清單在 Allegro 環(huán)境下當(dāng)前打開的設(shè)計(jì)可以通過(guò)axlDBGetDesign()獲取。拿到 design 對(duì)象后再通過(guò)design~components就能得到元件列表。design axlDBGetDesign() components design~components printf(當(dāng)前設(shè)計(jì)共 %d 個(gè)元件\n length(components))如果你的環(huán)境里components字段名不一致用design~?查看一下當(dāng)前設(shè)計(jì)對(duì)象有哪些可訪問(wèn)屬性再替換即可。4.3 遍歷元件并執(zhí)行三項(xiàng)檢查位號(hào)檢查最直接判斷comp~refdes是否為空即可。封裝檢查要稍微小心因?yàn)橛械脑热鐪y(cè)試點(diǎn)、安裝孔可能本身就不需要封裝這時(shí)需要額外的排除規(guī)則。坐標(biāo)檢查則要和板框范圍比較。下面是一段核心遍歷代碼foreach(comp components when( comp~refdes nil problems cons(發(fā)現(xiàn)無(wú)位號(hào)元件 problems) ) when( comp~package nil problems cons(sprintf(nil 元件 %s 缺少封裝 comp~refdes) problems) ) when( comp~x 0 || comp~x boardWidth problems cons(sprintf(nil 元件 %s 超出板框 X 范圍 comp~refdes) problems) ) )這里我用cons把問(wèn)題字符串不斷加到列表頭部。之所以用cons而不是append是因?yàn)閏ons是 O(1) 操作append在循環(huán)里反復(fù)調(diào)用會(huì)不斷創(chuàng)建新列表性能差一個(gè)數(shù)量級(jí)。遍歷結(jié)束后再用reverse把列表翻回來(lái)保證報(bào)告里的順序和元件順序一致。提示sprintf(nil 格式串 arg...)這種寫法會(huì)把格式化結(jié)果作為返回值直接交給變量或列表在需要拼接報(bào)告行時(shí)非常常用。注意sprintf的第一個(gè)參數(shù)傳nil在大多數(shù) SKILL 版本里返回生成的字符串但個(gè)別版本行為略有差異遇到問(wèn)題就改成先聲明一個(gè)變量再接收。4.4 生成報(bào)告文件并彈窗提示問(wèn)題收集完接下來(lái)是落盤和提示。文件操作在 SKILL 里非常直觀用outfile打開一個(gè)輸出流用fprintf寫入內(nèi)容最后close關(guān)閉。outPort outfile(reportFile) fprintf(outPort 元件體檢報(bào)告\n) fprintf(outPort 檢查時(shí)間%s\n getCurrentTime()) fprintf(outPort 共檢查 %d 個(gè)元件發(fā)現(xiàn)問(wèn)題 %d 項(xiàng)\n\n length(components) length(problems)) foreach(prob problems fprintf(outPort %s\n prob) ) close(outPort)這一步有幾個(gè)容易忽略的細(xì)節(jié)輸出路徑所在目錄要存在否則outfile會(huì)失敗寫完一定要close否則文件內(nèi)容可能沒有真正刷新到磁盤報(bào)告里最好帶時(shí)間戳和統(tǒng)計(jì)總數(shù)這在多人協(xié)作時(shí)能快速確認(rèn)數(shù)據(jù)是否最新最后用axlUIPopup彈一個(gè)摘要提示用戶體驗(yàn)立刻提升if( problems then axlUIPopup(sprintf(nil 發(fā)現(xiàn) %d 個(gè)問(wèn)題詳見 %s length(problems) reportFile)) else axlUIPopup(檢查通過(guò)未發(fā)現(xiàn)問(wèn)題) )4.5 完整腳本示例與注冊(cè)命令把所有片段拼起來(lái)加上頭注釋和命令注冊(cè)就是一個(gè)完整可用的工具腳本; 元件體檢腳本 ; 適用Allegro / PCB Editor ; 用法load 本文件后在命令窗口輸入 ckdesign procedure( ckDesign( optional (reportFile /tmp/design_check.txt) ) let( (design components problems outPort total) problems nil design axlDBGetDesign() when( design nil error(未獲取到當(dāng)前設(shè)計(jì)對(duì)象請(qǐng)先打開PCB設(shè)計(jì)\n) ) components design~components total length(components) ; 逐項(xiàng)檢查 foreach(comp components when( comp~refdes nil problems cons(發(fā)現(xiàn)無(wú)位號(hào)元件 problems) ) when( comp~package nil problems cons(sprintf(nil 元件 %s 缺少封裝 comp~refdes) problems) ) when( comp~x 0 || comp~x 1000 || comp~y 0 || comp~y 1000 problems cons(sprintf(nil 元件 %s 坐標(biāo)超出預(yù)設(shè)范圍(%.2f, %.2f) comp~refdes comp~x comp~y) problems) ) ) problems reverse(problems) ; 寫報(bào)告 outPort outfile(reportFile) fprintf(outPort 元件體檢報(bào)告\n) fprintf(outPort 檢查對(duì)象%s\n design~name) fprintf(outPort 共檢查 %d 個(gè)元件發(fā)現(xiàn)問(wèn)題 %d 項(xiàng)\n total length(problems)) foreach(prob problems fprintf(outPort %s\n prob) ) close(outPort) ; 彈窗提示 if( length(problems) 0 then axlUIPopup(sprintf(nil 發(fā)現(xiàn) %d 個(gè)問(wèn)題詳見 %s length(problems) reportFile)) else axlUIPopup(sprintf(nil %d 個(gè)元件檢查通過(guò) total)) ) ) ) axlCmdRegister(ckdesign ckDesign)從“函數(shù)列表”到“自動(dòng)化腳本”本質(zhì)上就是這一步先確定你要操作的對(duì)象類型然后去函數(shù)清單里找對(duì)應(yīng)的訪問(wèn)函數(shù)和屬性再按業(yè)務(wù)邏輯用列表函數(shù)把它們組織起來(lái)。這個(gè)腳本雖然不算長(zhǎng)但已經(jīng)具備了一個(gè)生產(chǎn)級(jí)工具的基本要素參數(shù)默認(rèn)值、錯(cuò)誤檢查、日志輸出、用戶反饋、命令注冊(cè)。5. 從“能干”到“高效”腳本工程化改造5.1 把腳本從一次性“臨時(shí)文件”變成復(fù)用工具剛學(xué)會(huì) SKILL 時(shí)很多人寫的腳本是“一次性”的需求來(lái)了臨時(shí)寫一個(gè)幾十行的tmp1.il跑完就扔。這種方式的痛點(diǎn)是三個(gè)月后同樣的需求再來(lái)你發(fā)現(xiàn)自己完全不記得當(dāng)初的邏輯只能重新寫一遍。我建議從第一次寫正式腳本時(shí)就做一個(gè)動(dòng)作把常用功能封裝成procedure并把文件名、函數(shù)名、用途都寫清楚。前面的ckDesign就是一個(gè)例子。你甚至可以往這個(gè)函數(shù)里加一個(gè)optional參數(shù)讓它可以支持不同的板框范圍或報(bào)告路徑這樣它就能在不同項(xiàng)目中復(fù)用。5.2 參數(shù)化設(shè)計(jì)與默認(rèn)值處理SKILL 的procedure支持三種參數(shù)必選參數(shù)、可選參數(shù)optional、關(guān)鍵字參數(shù)key。設(shè)計(jì)一個(gè)可復(fù)用函數(shù)時(shí)優(yōu)先考慮哪些值應(yīng)該暴露成參數(shù)。例如procedure( ckDesign( key (boardWidth 1000) (boardHeight 1000) (reportFile /tmp/design_check.txt) ) let( (...) ... ) )調(diào)用時(shí)的寫法會(huì)變成ckDesign(?boardWidth 2000 ?reportFile ./report.txt)key參數(shù)的優(yōu)點(diǎn)在于調(diào)用時(shí)不需要記住參數(shù)順序代碼可讀性也更好。別把默認(rèn)值寫死在業(yè)務(wù)邏輯里這是腳本工程化的第一課。5.3 注重性能減少重復(fù)查詢和路徑遍歷SKILL 腳本處理幾百個(gè)對(duì)象時(shí)性能根本不是問(wèn)題但一旦碰到上萬(wàn)級(jí)的 instance 或 shape寫法和寫法之間的差距就會(huì)非常明顯。我踩過(guò)的最典型的性能坑是在循環(huán)里反復(fù)調(diào)用dbOpen或反復(fù)訪問(wèn)同一個(gè)對(duì)象屬性導(dǎo)致每次循環(huán)都觸發(fā)一次數(shù)據(jù)查詢最終把一個(gè)本該幾秒完成的腳本拖成幾分鐘。優(yōu)化策略其實(shí)就三條循環(huán)外緩存對(duì)象引用能在foreach外面取到的對(duì)象絕不在循環(huán)里重復(fù)取避免頻繁拼接大字符串反復(fù)用strcat拼接一個(gè)幾千行的報(bào)告性能極差改用列表收集最后一次性buildString或逐行fprintf能用setof遍歷就用setof篩選邏輯優(yōu)先用聲明式寫法而不是手寫foreachwhen再加cons下面這段對(duì)比很直觀; 低效寫法每次判斷都查一次屬性鏈 foreach(inst cellView~instances when( inst~cellName INV invList cons(inst invList) ) ) ; 高效寫法聲明式篩選語(yǔ)義也更清晰 invList setof(inst cellView~instances inst~cellName INV)5.4 錯(cuò)誤恢復(fù)與事務(wù)提交還有個(gè)容易被忽視的問(wèn)題腳本運(yùn)行到一半報(bào)錯(cuò)設(shè)計(jì)數(shù)據(jù)庫(kù)可能處于“半改動(dòng)”狀態(tài)。在 Virtuoso 里很多dbCreate*操作必須顯式調(diào)用dbCommit或依賴特定的事務(wù)機(jī)制在 Allegro 里修改數(shù)據(jù)庫(kù)后要確保設(shè)計(jì)被正確保存。我寫腳本的習(xí)慣是所有可能失敗的輸入先用when或if做保護(hù)判斷對(duì)“只讀類”的檢查腳本不修改數(shù)據(jù)庫(kù)從源頭規(guī)避風(fēng)險(xiǎn)對(duì)“寫操作類”腳本跑完立刻驗(yàn)證關(guān)鍵對(duì)象是否存在再?zèng)Q定是否保存關(guān)鍵步驟之間加printf日志這樣即使中途掛了也能定位到在哪一步掛的6. 翻車記錄與經(jīng)驗(yàn)補(bǔ)丁SKILL 開發(fā)的真實(shí)代價(jià)6.1 改了數(shù)據(jù)庫(kù)卻沒生效事務(wù)和保存的坑我在 Virtuoso 里寫第一個(gè)自動(dòng)創(chuàng)建圖形的腳本時(shí)跑完沒有任何報(bào)錯(cuò)對(duì)象卻始終看不到。查了半天才發(fā)現(xiàn)創(chuàng)建的對(duì)象停在內(nèi)存里沒有提交到數(shù)據(jù)庫(kù)。類似的問(wèn)題在 Allegro 里也會(huì)出現(xiàn)修改了元件屬性replay 結(jié)束一看界面還是老樣子。建議寫寫操作腳本時(shí)把“提交/保存”步驟當(dāng)成一個(gè)獨(dú)立的驗(yàn)證節(jié)點(diǎn)。完成操作后主動(dòng)執(zhí)行保存或同步操作并確認(rèn)這一步真的在腳本流程里被執(zhí)行到了。另外有些版本的 Allegro 在命令行模式下操作完需要手動(dòng)刷新視圖腳本里也可以主動(dòng)執(zhí)行redraw或類似命令讓界面同步。6.2 整數(shù)與浮點(diǎn)數(shù)混用的隱性錯(cuò)誤SKILL 對(duì)數(shù)字類型的處理相對(duì)寬松但并不是沒有坑。尤其是坐標(biāo)計(jì)算如果兩個(gè)整數(shù)相除被當(dāng)作浮點(diǎn)某個(gè)版本的語(yǔ)義可能和你的預(yù)期不一致坐標(biāo)精確到小數(shù)點(diǎn)后幾位時(shí)用整數(shù)計(jì)算和用浮點(diǎn)計(jì)算的結(jié)果可能完全不同。我的建議是涉及坐標(biāo)、長(zhǎng)度、角度等物理量的運(yùn)算一開始就把所有數(shù)值顯式轉(zhuǎn)換成浮點(diǎn)x float(comp~x)。不要依賴 SKILL 的自動(dòng)類型提升顯式轉(zhuǎn)換能讓行為可預(yù)期。6.3 全局變量污染與作用域泄漏SKILL 的變量作用域設(shè)計(jì)很自由自由到容易出事。如果你在腳本頂層寫了一個(gè)problems nil然后在某個(gè)procedure里又直接用了problems這個(gè)變量就會(huì)在全局空間里被共享。多個(gè)腳本加載時(shí)很容易出現(xiàn)“這個(gè)腳本改了那個(gè)腳本的變量”的詭異問(wèn)題。最安全的做法是所有臨時(shí)變量都放進(jìn)let或prog的局部作用域里。procedure的參數(shù)本身就是局部的函數(shù)內(nèi)部再套一個(gè)let包住中間變量基本就能杜絕變量污染。我在前面示例代碼里已經(jīng)用了let包裹這不是炫技是真的被坑怕了。6.4 異常處理不夠健壯導(dǎo)致半途崩潰SKILL 有errset和error機(jī)制可以捕獲運(yùn)行時(shí)異常errset( ; 可能出錯(cuò)的代碼 load(/path/to/some.il) )errset在代碼出錯(cuò)時(shí)不會(huì)中斷整個(gè)腳本而是把錯(cuò)誤信息返回。這在批量處理多個(gè)文件時(shí)很有用一個(gè)文件壞了跳過(guò)它繼續(xù)處理下一個(gè)。我自己在寫批量腳本時(shí)幾乎必用errset它能把腳本的“單點(diǎn)失敗”變成“單點(diǎn)告警”。6.5 命令行注冊(cè)沖突用axlCmdRegister注冊(cè)自定義命令時(shí)一定要確認(rèn)命令名沒有和工具內(nèi)置命令沖突。我遇到過(guò)注冊(cè)一個(gè)叫update的命令結(jié)果某個(gè)操作觸發(fā)了內(nèi)置同名命令行為完全不可控。自定義命令最好加一個(gè)獨(dú)特的前綴比如公司縮寫或你的名字縮寫像qaUpdate、ckdesign這樣。6.6 交付給別人的腳本要留“逃生艙”最后一條建議和語(yǔ)法無(wú)關(guān)但很重要如果你寫的腳本要交給團(tuán)隊(duì)里其他人用一定要讓腳本在最開始、最容易出錯(cuò)的地方給出明確提示。比如判斷當(dāng)前有沒有打開設(shè)計(jì)、文件路徑是否存在、對(duì)象列表是否為空。別讓同事拿到腳本后面對(duì)一個(gè)莫名其妙的nil報(bào)錯(cuò)還要反過(guò)來(lái)找你排查。我通常會(huì)在腳本頭部加一段環(huán)境自檢when( axlDBGetDesign() nil error(請(qǐng)先打開需要檢查的 PCB 設(shè)計(jì)文件\n) )這一句看似多余實(shí)際能節(jié)省所有使用者的理解成本。7. 最后分享一點(diǎn)個(gè)人體會(huì)從函數(shù)列表到自動(dòng)化腳本真正質(zhì)變的節(jié)點(diǎn)不是“記住了多少函數(shù)”而是“看到需求后能穩(wěn)定地拆出數(shù)據(jù)流”。我剛?cè)腴T時(shí)總喜歡到處收集別人的腳本代碼后來(lái)發(fā)現(xiàn)最有用的還是自己翻官方文檔、在交互窗口里一個(gè)個(gè)試函數(shù)、拿真實(shí)設(shè)計(jì)練手。SKILL 的學(xué)習(xí)曲線初期稍陡但只要過(guò)了“列表操作 屬性訪問(wèn) 流程控制”這三關(guān)你就能應(yīng)付絕大多數(shù)日常自動(dòng)化需求。我自己現(xiàn)在的工作習(xí)慣是任何操作如果在界面上需要重復(fù)超過(guò)三次就停下來(lái)想想能不能用 SKILL 解決。哪怕第一次寫腳本花的時(shí)間比手動(dòng)操作還久但它的價(jià)值會(huì)在第二次、第三次使用時(shí)迅速體現(xiàn)出來(lái)。工具鏈里最有價(jià)值的不是某個(gè)菜單項(xiàng)而是你能不能把幾個(gè)函數(shù)列表里的碎片拼成你自己的“時(shí)間機(jī)器”。希望你讀完這篇文章后也能動(dòng)手寫一個(gè)屬于自己的.il腳本然后把省下來(lái)的時(shí)間花在真正需要設(shè)計(jì)師判斷力的事情上。