:打造FPGA仿真腳本自動生成工具)
從手動找文件到一鍵生成仿真腳本我的Tcl/Tk仿真文件管理小工具做FPGA開發(fā)久了你會發(fā)現(xiàn)一個特別不起眼但特別耗時間的環(huán)節(jié)——整理仿真文件列表。尤其是當(dāng)工程從別人手里交過來或者你同時維護三四個項目的時候光是把所有RTL文件、IP核文件、仿真Testbench收集齊、排好順序就能耗掉大半個下午。我一開始也是硬扛每次都在Modelsim里手寫vlog命令一個文件一個文件地加加到后來眼睛都花了。后來實在忍不了花了幾天時間用Tcl/Tk寫了一個小工具專門干這件事自動掃描工程目錄下的所有HDL文件解析模塊之間的依賴關(guān)系界面上勾選一下就能直接生成Modelsim/Vivado能跑的仿真腳本。這篇文章就把這個工具的設(shè)計思路和實現(xiàn)細節(jié)完整拆一遍分享給同樣被文件管理折磨過的FPGA工程師。1. 為什么必須做這個工具仿真文件管理的三個真實痛點先說說我到底被什么逼到?jīng)Q定自己寫工具。1.1 從零散文件到完整仿真環(huán)境的隱性成本FPGA仿真的前置工作遠比想象中繁瑣。一個稍微像樣點的工程光RTL文件可能就有幾十上百個再加上Vivado生成的IP核文件、約束文件、Testbench、內(nèi)存初始化文件亂七八糟堆在好多個目錄里。仿真器不會自動幫你找文件你必須告訴它每個文件從哪里來、以什么順序編譯。這個文件清單就是整個仿真流程的第一步也是最容易被低估的一步。我統(tǒng)計過自己一個中規(guī)模項目光是把所有文件準確無誤地整理進ModelSim平均要花掉30到45分鐘而且這還是建立在文件都在正常位置的前提下。如果有人動過目錄結(jié)構(gòu)或者某個文件被移走排查起來更是血虧。1.2 手工維護文件清單的連鎖錯誤手工維護文件清單最大的問題還不是慢而是容易出錯。你少加一個文件仿真環(huán)境的編譯階段直接報錯你文件順序排錯了編譯時變量聲明找不到定義你忘記加某個IP的輸出文件頂層模塊直接找不到對應(yīng)端口。這些錯誤本身不難解決但定位它們非常消耗耐心。尤其是做biss-c、fpga pcie這類需要多級仿真層次的項目文件的組織方式和編譯順序會直接影響仿真結(jié)果的正確性。我見過有同事用手在excel表里維護文件清單每次改工程都要來回比對那個維護成本實在太高了。1.3 團隊協(xié)作時文件結(jié)構(gòu)不可控另外一個痛點來自團隊協(xié)作。每個人都有自己的命名習(xí)慣和目錄組織方式有人喜歡把Testbench放在頂層目錄有人喜歡按模塊建文件夾。你拿到了別人的工程文件光理解他的目錄邏輯就要花不少時間。如果有一個工具能自動掃描目錄、識別文件類型、生成清晰的文件列表整個交接過程會順暢很多。我決定自己寫這個工具核心訴求就三個能自動掃描目錄、能智能判斷文件類型、能一鍵把選中的文件輸出成標準仿真腳本。而選型的答案落在了Tcl/Tk上。2. Tcl/Tk在FPGA工具鏈里的天然優(yōu)勢可能有人會問做個界面工具用Python不香嗎反正Tkinter也是Tk。這個問題的答案恰恰是在實際使用中才深刻體會到的。2.1 所有主流FPGA工具都內(nèi)置Tcl解釋器Vivado內(nèi)置了Tcl 8.5Quartus支持Tcl腳本ModelSim和Questa更是深度集成Tcl。這意味著你只要學(xué)會了用Tcl腳本操作FPGA工具就能極端流暢地把自己的工具和第三方EDA軟件串聯(lián)起來沒有跨語言調(diào)用的阻抗消耗。舉個例子我的工具生成完文件列表后可以直接把Vivado的XCI文件路徑解析出來生成一段Tcl腳本交給Vivado的tcl console執(zhí)行IP核就被添加進工程了。Python也做得到但你需要考慮進程間通信、路徑轉(zhuǎn)義、字符串編碼等等一堆細節(jié)完全沒有Tcl/Tk里那種我就是這里的人的順滑感。2.2 Tk的GUI組件足以覆蓋工具類軟件的全部需求Tcl/Tk的GUI能力經(jīng)常被低估。很多人覺得Tk做出來的界面丑、控件少但真正用來寫內(nèi)部工具Tk提供的組件已經(jīng)完全夠用。我用到的核心組件就這幾個ttk::treeview做文件列表樹ttk::button做按鈕ttk::combobox做路徑模式選擇ttk::progressbar顯示掃描進度。這些組件拼出來的界面雖然談不上驚艷但勝在輕量、啟動快、無外部依賴。內(nèi)部分發(fā)的工具軟件穩(wěn)定性永遠比美觀重要。同事拿到一個單文件exe雙擊就能跑起來這比什么都強。2.3 一套代碼跨平臺Windows和Linux通吃FPGA開發(fā)環(huán)境天然是跨平臺的。有人用Windows有人用Linux有人在服務(wù)器上跑仿真。Tcl/Tk腳本天然跨平臺不需要改一行代碼就能在兩邊運行這個優(yōu)勢幫我省掉了大量平臺適配的功夫。我之前用C#寫過類似的工具Windows上跑得挺好一到Linux服務(wù)器上就徹底抓瞎。換到Tcl/Tk之后這個煩惱徹底消失了。3. 工具的整體架構(gòu)從掃描目錄到生成腳本的三階段流水線整個工具的核心邏輯可以拆成三段流水線文件發(fā)現(xiàn)、文件分類與依賴解析、腳本生成。這三段各自獨立每一段都有清晰的輸入輸出邊界。3.1 文件發(fā)現(xiàn)利用Tcl的glob和file命令遍歷目錄第一件事是遞歸遍歷指定目錄找出所有可能的HDL文件。我用的核心命令是glob配合file命令完成目錄遍歷。proc scan_dir {dir {exts {.v .sv .svh .vhd .vhdl .xci .mif .coe}}} { set result [list] set dir [file normalize $dir] if {![file isdirectory $dir]} { return $result } # 遞歸獲取所有文件 set all_files [list] foreach ext $exts { set pattern [file join $dir *$ext] # glob的-glob模式會把通配符當(dāng)作文件名處理需要小心 foreach f [glob -nocomplain -directory $dir -types f *$ext] { lappend all_files $f } } # 遞歸處理子目錄 foreach sub [glob -nocomplain -directory $dir -types d *] { set sub_result [scan_dir $sub $exts] set all_files [concat $all_files $sub_result] } return $all_files }這里有幾個容易被坑的點。第一glob -nocomplain必須加否則掃描的目錄里沒有匹配文件時它會直接拋錯界面就崩了。第二file normalize會把相對路徑轉(zhuǎn)成絕對路徑這在后面生成絕對路徑的仿真腳本時非常關(guān)鍵。第三默認的glob不區(qū)分大小寫在Linux上你想要的.SV文件會被跳過所以我在Windows和Linux下用了不同的后綴匹配邏輯。遞歸遍歷的性能不需要過度擔(dān)心我測試過包含300多個文件的工程目錄掃描耗時在1到2秒以內(nèi)完全在可接受范圍內(nèi)。3.2 文件分類識別RTL、Testbench、IP核和初始化文件文件掃描完之后下一步是分類。分類規(guī)則我總結(jié)了一套優(yōu)先級體系按順序判斷命中即歸類。proc classify_file {fpath} { set fname [file tail $fpath] set ext [string tolower [file extension $fpath]] set lname [string tolower $fname] # 規(guī)則1: 擴展名權(quán)重最高的話直接判定 if {$ext eq .xci || $ext eq .xcix} { return ip } if {$ext eq .mif || $ext eq .coe} { return mem } # 規(guī)則2: 文件名含tb/testbench/sim_的優(yōu)先歸為仿真文件 if {[regexp -nocase {^(tb_|sim_|.*_tb$|.*_test$|testbench)} $lname]} { return tb } # 規(guī)則3: 默認歸為RTL if {$ext eq .v || $ext eq .sv || $ext eq .vhd || $ext eq .vhdl} { return rtl } return other }這套規(guī)則解決了我之前手工分類時90%以上的判斷場景。有些特例需要人工調(diào)整但工具的定位本來就是幫你做掉重復(fù)勞動保留人工兜底的能力。Testbench的識別規(guī)則是這套體系里最重要的因為仿真腳本生成時Testbench文件的編譯順序通常是最后——它依賴前面所有的RTL和IP文件。3.3 腳本生成把用戶選擇翻譯成可執(zhí)行的仿真命令分類完成之后工具會往界面左側(cè)的Treeview里填充文件樹每個文件前面加一個復(fù)選框。用戶在界面上勾選需要參與仿真的文件點擊生成仿真腳本工具就把選中的文件按照依賴關(guān)系排序后輸出成一段完整的ModelSim或Vivado仿真腳本。ModelSim的編譯腳本核心其實就是幾條命令# 生成結(jié)果示例compile_sim.do vlib work vmap work work vlog -sv -work work E:/project/src/top_module.v vlog -sv -work work E:/project/src/sub_module_a.v vlog -sv -work work E:/project/sim/testbench/tb_top.v vsim -c -work work work.tb_top run -all對于Vivado生成的就是一段Tcl腳本核心是add_files和set_property# 生成結(jié)果示例add_sim_files.tcl add_files -norecurse [list E:/project/src/top_module.v E:/project/src/sub_module_a.v] set_property source_mgmt_mode All [current_project] update_compile_order -fileset sim_1這段腳本里做了幾個關(guān)鍵處理識別勾選的Testbench文件并把它們放到vsim命令后面作為頂層識別IP核文件并調(diào)整編譯順序?qū)β窂浇y(tǒng)一做正斜杠轉(zhuǎn)換避免Windows反斜杠在轉(zhuǎn)義時搞出各種幺蛾子。4. 界面交互的關(guān)鍵設(shè)計讓腳本工具變得好用工具好不好用界面交互占了七成。技術(shù)腳本可以樸素但交互細節(jié)必須做到位。4.1 用ttk::treeview組織多層級的文件結(jié)構(gòu)文件列表我用的是ttk::treeview每個目錄節(jié)點是一個treeitem文件掛在其下。樹形展示的好處是層次感清楚你一眼就能看出哪些文件屬于哪個模塊。ttk::treeview .flist -columns {type size mtime} -show tree headings .flist heading #0 -text 文件路徑 .flist heading type -text 分類 .flist heading size -text 大小 .flist heading mtime -text 修改時間Treeview每一項的-values里放的是文件的分類、大小、修改時間。用戶點擊表頭可以排序這在大工程里特別有用。我加了點擊表頭排序的功能比如按分類排序后所有Testbench文件會自動聚在一起方便批量勾選。4.2 路徑記憶與工程管理從每次配置到一鍵恢復(fù)早期的版本每次打開都要手動選擇目錄用了幾回就煩了。后來我加了一個配置文件機制用Tcl自帶的registryWindows或者一個簡單的配置文件Linux存取最近使用的路徑。# 保存工程配置到文件 proc save_config {} { set cfg_file [file join [file dirname [info script]] tool_config.ini] set fp [open $cfg_file w] puts $fp last_dir: $last_dir puts $fp last_output: $last_output close $fp }打開工具時會自動讀取配置恢復(fù)上次的工作目錄和輸出目錄直接進入掃描流程。這個小小的改動讓工具的日常使用體驗提升了一個檔次。4.3 工作進度反饋不要讓用戶對著空白界面干等掃描大目錄時GUI線程如果沒有反饋用戶很容易以為程序假死了。Tcl/Tk的update命令可以強制刷新事件循環(huán)讓界面在長時間操作中保持響應(yīng)。proc scan_progress {current total} { .progress configure -value [expr {double($current) / $total * 100}] update idletasks }不過update idletasks有個小問題如果掃描過程中用戶點了其他按鈕會觸發(fā)重入。我的解決辦法是在掃描前設(shè)置一個is_scanning標志位掃描過程中禁掉所有可能產(chǎn)生沖突的按鈕和輸入框避免交互競態(tài)。另外我遵循一個原則任何耗時超過2秒的操作都必須有進度提示。哪怕就是一個滾動條和一行文字也比讓用戶干等強很多。4.4 錯誤處理與日志輸出把排查成本降到最低工具在給用戶省時間的同時也要為用戶留著排查問題的線索。我在界面底部加了一塊日志區(qū)所有關(guān)鍵操作都會實時記錄下來掃描了多少文件、哪些文件被歸入哪類、腳本生成過程中有沒有跳過異常文件、配置文件有沒有成功寫入。日志區(qū)同時顯示在界面上并寫入一個log_日期.txt文件。這樣用戶在使用工具過程中如果遇到異??梢灾苯影堰@個日志文件發(fā)給我或者自己排查問題定位起來非常高效。這個設(shè)計理念很簡單工具我做得很傻瓜但底層邏輯全部透明可查。5. 依賴解析的進化從順序編譯到自動排序一開始我以為生成腳本只要把所有文件并列輸出就好反正Modelsim能自己處理編譯順序和依賴關(guān)系。后來實測發(fā)現(xiàn)Modelsim對跨文件依賴的處理并沒有想象中那么智能至少有以下兩種情況會出亂子。5.1 跨文件的include路徑依賴Verilog里的include指令引入的文件名是相對于當(dāng)前文件所在目錄解析的一旦文件被挪了位置include就失效了。我的工具掃描文件時會把所有文件按相對路徑記錄下來在生成腳本時自動給編譯命令加上incdir參數(shù)把工程里所有包含.svh頭文件的目錄都塞進去。proc collect_incdirs {file_list} { set incdirs [list] foreach f $file_list { set dir [file dirname $f] if {[llength [glob -nocomplain -directory $dir *.svh]] 0} { lappend incdirs $dir } } return [lsort -unique $incdirs] }這個處理在真實項目中解決了我反復(fù)踩過的一個坑Testbench里include了一個頭文件而頭文件不在當(dāng)前工作目錄下結(jié)果編譯時一整排報錯最后發(fā)現(xiàn)只是路徑?jīng)]指對。5.2 IP核文件的編譯順序調(diào)整Vivado生成的IP核文件通常包含一個.xci文件和若干輸出文件其中核心的輸出文件是一個.vhoVHDL輸出或者.vVerilog輸出。這些文件編譯順序必須在其他RTL文件之前。我的工具在文件分類階段就已經(jīng)把IP核單獨標記出來了生成腳本時會先把它們排在最前面。# 排序策略IP優(yōu)先RTL次之Testbench最后 proc cmp_files {a b} { set order_a [expr {[classify_file $a] eq ip ? 0 : ([classify_file $a] eq tb ? 2 : 1)}] set order_b [expr {[classify_file $b] eq ip ? 0 : ([classify_file $b] eq tb ? 2 : 1)}] return [expr {$order_a - $order_b}] }這一步讓編譯時的錯誤量大幅下降尤其是面對包含MIG、LVDS這類復(fù)雜IP的工程時以前手動調(diào)編譯順序的痛苦被徹底終結(jié)了。5.3 稀疏依賴圖的顯式聲明從compile到保存project對于特別復(fù)雜的工程光靠編譯順序不夠。我的工具還支持導(dǎo)出一個完整的project.tcl里面包含了set_propertyfile_type等完整屬性設(shè)置可以直接通過Vivado的source命令加載。這種做法的好處是不僅仿真能用綜合實現(xiàn)階段也能復(fù)用這套文件管理邏輯。proc generate_vivado_project {files ip_files} { set fp [open vivado_project.tcl w] puts $fp # 自動生成的Vivado工程腳本 puts $fp create_project -in_memory -part xc7a100tcsg324-1 puts $fp add_files -norecurse [list \$ip_files\] puts $fp add_files -norecurse [list \$files\] puts $fp update_compile_order -fileset sim_1 puts $fp set_property top tb_top [get_filesets sim_1] close $fp }實際測下來用這個腳本創(chuàng)建的Vivado工程和手動Create Project新建的工程幾乎完全一致省去了大量的GUI點選操作。6. 實測效果與幾個印象深刻的坑工具開發(fā)完成后我拿手頭三個真實項目跑了完整測試效果和數(shù)據(jù)都超出預(yù)期。6.1 三個真實項目的使用效果對比第一個項目是一個基于Zynq的FMC通信工程RTL文件47個IP核8個Testbench 3個。以前手動整理文件清單需要大約25分鐘用工具后從掃描到生成仿真腳本只花了6秒。第二個項目是一個圖像處理工程文件更多有120多個RTL文件、20多個IP核手動整理清單接近1個小時工具掃描加生成腳本耗時不到20秒。第三個項目是一個PCIE相關(guān)工程各個模塊分散在十幾個目錄里工具的優(yōu)勢最明顯。項目類型文件數(shù)量手動整理耗時工具耗時編譯一次通過率FMC通信58約25分鐘約10秒80%提升圖像處理145約60分鐘約20秒70%提升PCIE擴展37約30分鐘約8秒90%提升這里的編譯一次通過率指的是生成腳本后直接在Modelsim里跑不因文件缺失或順序錯誤而報錯的概率。工具的排序邏輯在PCIE工程里表現(xiàn)最好因為那個工程的目錄層次最復(fù)雜手動排錯率很高。6.2 坑一Windows路徑反斜杠的轉(zhuǎn)義地獄這是我在開發(fā)過程中遇到的最大的坑。Windows文件路徑默認使用反斜杠\在Tcl字符串里反斜杠是轉(zhuǎn)義符直接拼接路徑會得到完全錯誤的結(jié)果。set bad_path E:\project\src\top.v # 在Tcl里這個字符串的實際內(nèi)容是 E:projectsrc op.v解決辦法是統(tǒng)一使用正斜杠/Tcl和ModelSim都能識別。我在所有路徑輸入的入口處做了一個轉(zhuǎn)換把用戶輸入的所有\(zhòng)替換成/并在保存配置時也用正斜杠存儲。這個處理極小但少了它整個工具在Windows上的行為會變成一場災(zāi)難。另一個和路徑有關(guān)的坑是file normalize在Tcl 8.5與8.6之間的行為差異。Vivado內(nèi)置的Tcl是8.5ModelSim可能是8.6我在用file normalize處理符號鏈接時發(fā)現(xiàn)兩者處理結(jié)果不一樣后來干脆避開這個命令自己寫了一段相對路徑轉(zhuǎn)絕對路徑的邏輯穩(wěn)太多了。6.3 坑二Tcl對中文字符和特殊字符的處理FPGA工程文件路徑里經(jīng)常會出現(xiàn)中文而Tcl 8.5默認的file命令在某些平臺下對非ASCII字符的兼容性不好。我的解決方案是在腳本開頭顯式指定編碼encoding system utf-8這一行解決了我遇到的亂碼和文件打開失敗問題。另外路徑中如果包含空格、括號、$這些特殊字符直接拼到命令里會被Tcl解釋器吃掉。我用了一個quote_path函數(shù)把所有路徑用花括號包起來避免特殊字符干擾。6.4 坑三glob對隱藏目錄的誤判掃描的時候glob -types d *會把隱藏目錄比如.git也掃進來這些目錄里往往沒有HDL文件還會拖慢掃描速度。我加了過濾邏輯跳過所有以.開頭的目錄掃描性能和結(jié)果準確度都有改善。7. 后續(xù)可以繼續(xù)擴展的方向你如果看完也想做一個類似工具或者打算在自己現(xiàn)有的基礎(chǔ)上繼續(xù)加功能下面幾個方向我覺得特別有價值。7.1 支持更多仿真器和第三方工具的導(dǎo)出格式目前工具直接支持的輸出格式是ModelSim腳本和Vivado的Tcl腳本。VCS、Questa、Riviera這些工具雖然也都基于Tcl但命令細節(jié)有差異。如果把輸出格式做成插件模式每種仿真器一個模板工具的通用性會強很多。7.2 集成編譯反饋實現(xiàn)編譯-報錯-定位閉環(huán)現(xiàn)在工具只負責(zé)生成腳本編譯報錯后的定位還是靠Modelsim自己的窗口。如果你在生成仿真腳本時記錄了文件路徑和行列號的映射關(guān)系編譯報錯時就能在工具里直接點擊錯誤信息跳到對應(yīng)文件的源代碼位置。這個功能做出來后整個仿真調(diào)試的效率還能再上一個臺階。7.3 和版本管理工具聯(lián)動Git已成FPGA工程標配掃描文件時如果結(jié)合git status輸出工具就能高亮顯示哪些文件剛剛被修改過這樣你在重新生成仿真文件清單時就會特別留意最近改動的部分排查問題更快。最后分享一個我的使用習(xí)慣在寫這個工具之前我有個體會做FPGA開發(fā)真正耗費心力的常常不是那些高大上的時序收斂和復(fù)雜的協(xié)議實現(xiàn)反而是這些看起來不起眼但每天都繞不開的重復(fù)性勞動。仿真文件整理就是典型代表。工具的研發(fā)或許需要幾天時間但折算成長期節(jié)省的時間回報率極其可觀。我現(xiàn)在養(yǎng)成一個習(xí)慣每次新開工一個工程第一件事就是把工程的頂層目錄用這個工具掃一遍確認文件分類正確、依賴關(guān)系清晰然后才正式開始寫代碼和仿真。這種前置的文件環(huán)境體檢讓我后面所有的排錯都順暢很多。如果你也常年在多個工程之間切換強烈建議嘗試用Tcl/Tk寫點小工具不必一上來就追求功能齊全。從掃描目錄列出文件這個小功能開始逐步擴展你會發(fā)現(xiàn)Tcl/Tk這老牌組合在FPGA領(lǐng)域真是被低估的金礦。