
EtherCAT DC 分布式時鐘同步的 ModelSim 仿真從幀模板到從站行為建模關(guān)鍵詞EtherCAT、DC 分布式時鐘、ESC、0x0918、0x0110 DL Status、ModelSim、Verilog testbench、行為級建模、線型拓?fù)溥m用人群正在做 EtherCAT 主站 FPGA 實現(xiàn)、需要驗證 DC 同步流程的工程師開篇為什么 DC 同步必須靠仿真驗證EtherCAT 的 DCDistributed Clock分布式時鐘是精密運動控制的地基。多軸協(xié)同能做到亞微秒級同步靠的就是每個從站 ESC 里的本地時鐘被主站反復(fù)校準(zhǔn)。但 DC 同步有個特點它是一個長流程狀態(tài)機。一次完整的 DC 初始化要經(jīng)歷廣播寫0x0900鎖存時間戳讀0x0918拿各從站接收時間戳算 offset廣播寫 offset讀端口時間戳算傳播延遲pdelay / 系統(tǒng)延遲寫系統(tǒng)延遲 時鐘漂移補償周期性發(fā) ARMW/FRMW 做運行期補償每一步都要發(fā)不同的幀模板、等不同的應(yīng)答、解析不同的寄存器。32 個從站的話這個流程要跑幾千個時鐘周期。上板調(diào)試的問題抓波形只能看到最后成沒成中間哪一步卡住、是幀沒發(fā)出去還是應(yīng)答沒回來、DL status 解析對不對——全都看不見。所以正確的做法是先搭一套能跑的仿真環(huán)境把狀態(tài)機每一步都跑通再上板。本文以一個真實的 DC 同步模塊ecat_sync_dc為例講解配套 testbench 的搭建方法。核心是三件事怎么模擬發(fā)送crc32_tx_cnt字節(jié)計數(shù)器怎么模擬應(yīng)答resp_ack_sync 200μs 超時怎么模擬從站時間戳 DL status含線型拓?fù)涞哪恼咀R別一、整體架構(gòu)tb_ecat_sync_dc ├── sub1行為級模型 ← 64 位減法器替代 Altera LPM_ADD_SUB IP ├──ecat_frame_module ← 46 字節(jié) EtherCAT 幀模板 ├── ecat_sync_dcDUT ← 被測模塊DC 同步狀態(tài)機 └── TB 模擬部分 ├── ecat_tx 模型 ← crc32_tx_cnt 發(fā)送字節(jié)計數(shù)74 拍/幀 ├── ecat_rx 模型 ← resp_ack_sync 應(yīng)答 overtime 超時 └── 從站模型 ← esc_rec_time / esc_cap_time / DL status1.1 sub1四兩撥千斤的 IP 替代//-------------------------------------------------------------------------------- // sub1 行為級模型 (64 位減法: result dataa - datab) // 替代 Altera LPM_ADD_SUB IP 核, 用于 offset / pdelay 計算 //-------------------------------------------------------------------------------- module sub1 ( input [63:0] dataa, input [63:0] datab, output [63:0] result ); assign result dataa - datab; endmodule這一句是整個 tb 能脫離 Quartus 獨立運行的關(guān)鍵。DC 同步要算 64 位的時間差offset 參考時鐘 - 本地時鐘、pdelay 等RTL 里通常例化了 Altera 的LPM_ADD_SUB。仿真時如果直接拉這個 IP會遇到需要220model.v/altera_mf.v仿真庫ModelSim 要配incdir和庫映射第三方Icarus / Verilator根本跑不了解法同名模塊sub1行為級一行實現(xiàn)。仿真時 tb 里的sub1覆蓋了工程里的 IP 例化或者在仿真文件列表里排在前面。??注意這只是仿真替身。綜合上板時必須換回真正的 LPM IP否則 64 位減法直接用assign會綜合出一根巨大的減法器鏈時序大概率不過。建議在文件頭注釋里寫清楚或者用ifdef SIMULATION隔離。1.2 DUT 例化端口分組看得很清楚 | 方向 | 信號 | 含義 | |---|---|---| | 輸入 | wkc_max | 從站個數(shù)本例 32 | | 輸入 | crc32_tx_cnt | 發(fā)送字節(jié)計數(shù)幀發(fā)送進度 | | 輸入 | resp_ack_sync | 幀應(yīng)答從站回來了 | | 輸入 | overtime_plus_sync | 200μs 超時脈沖 | | 輸入 | esc_rec_time | 從站 0x0918 接收時間戳 | | 輸入 | esc_cap_time | 端口時間戳 / DL status | | 輸入 | ecat_module_data | 幀模板 | 輸出 | sync_sm | 同步狀態(tài)機當(dāng)前狀態(tài) | | 輸出 | ecat_module_adr | 幀模板地址 | | 輸出 | sync_done | 同步完成標(biāo)志 | **ecat_module_adr 是理解整個流程的鑰匙**——DUT 通過它選不同的幀模板tb 只要監(jiān)視這個地址就知道 DUT 當(dāng)前在發(fā)什么幀、走到哪一步了。 ## 二、關(guān)鍵設(shè)計 1發(fā)送字節(jié)計數(shù)器74 拍一幀 這是 tb 里**最容易寫錯**的部分。必須嚴(yán)格對照真實以太網(wǎng)幀結(jié)構(gòu) verilog // // 模擬 ETH_DAT_TX ETH_TX: 發(fā)送字節(jié)計數(shù)器 crc32_tx_cnt // 完整以太網(wǎng)幀結(jié)構(gòu) (對照 ETH_TX.v / ETH_DAT_TX.v): // crc32_tx_cnt 1~7 : 前導(dǎo)碼 0x55 (7 字節(jié)) // crc32_tx_cnt 8 : SFD 0xD5 // crc32_tx_cnt 9~14 : 目的 MAC (6 字節(jié)) // crc32_tx_cnt 15~20 : 源 MAC (6 字節(jié)) // crc32_tx_cnt 21~22 : EtherType (2 字節(jié)) ← ecat_sync 在 cnt22 加載 send_buf // crc32_tx_cnt 23~68 : EtherCAT 報文 (46 字節(jié), 每拍左移 send_buf 輸出 1 字節(jié)) // crc32_tx_cnt 69~72 : CRC32 (4 字節(jié)) // crc32_tx_cnt 73 : ETH_TX 停止寫 RAM (tx_wea0) // 一幀共需 crc32_tx_cnt 從 0 遞增到 73 (74 拍) // reg [8:0] tx_cnt; reg tx_active; always (posedge clk or negedge rst_n) begin if (!rst_n) begin tx_cnt 9d0; tx_active 1b0; end else begin if (sync_respone_valid !tx_active) begin tx_active 1b1; // DUT 請求發(fā)幀 → 啟動 tx_cnt 9d0; end else if (tx_active) begin if (tx_cnt 9d73) tx_cnt tx_cnt 1b1; else tx_active 1b0; // 一幀發(fā)完 end end end always (*) crc32_tx_cnt tx_active ? tx_cnt : 9d0;2.1 為什么是 74 拍7(前導(dǎo)) 1(SFD) 6(目的MAC) 6(源MAC) 2(EtherType) 46(EtherCAT報文) 4(CRC) 1(停止) 73 拍增量2.2 關(guān)鍵節(jié)拍點crc32_tx_cnt事件21~22EtherTypeDUT 在此加載frame_tx_buf23~68逐字節(jié)輸出 46 字節(jié) EtherCAT 報文73發(fā)送結(jié)束??坑crc32_tx_cnt必須在非發(fā)送期間歸零。原 tb 用always (*) crc32_tx_cnt tx_active ? tx_cnt : 9d0;處理了這點。如果忘了歸零DUT 會誤判一直在發(fā)幀狀態(tài)機卡死。三、關(guān)鍵設(shè)計 2應(yīng)答與超時模型reg [15:0] overtime_cnt; reg res_ack_pending; always (posedge clk or negedge rst_n) begin if (!rst_n) begin res_ack_sync 1b0; overtime_plus_sync 1b0; overtime_cnt 16d0; res_ack_pending 1b0; end else begin res_ack_sync 1b0; // 默認(rèn)清零單拍脈沖 overtime_plus_sync 1b0; if (tx_active (tx_cnt 9d73)) begin // 幀發(fā)送完成, 開始等待應(yīng)答 res_ack_pending 1b1; overtime_cnt 16d0; end else if (res_ack_pending) begin overtime_cnt overtime_cnt 1b1; // 模擬從站應(yīng)答延遲 (約 2 拍) if (overtime_cnt 16d2) begin res_ack_sync 1b1; res_ack_pending 1b0; end else if (overtime_cnt 16d8000) begin // 200us 超時 (40MHz × 200μs 8000 拍) overtime_plus_sync 1b1; res_ack_pending 1b0; end end end end3.1 兩個數(shù)字的含義數(shù)值換算說明2拍50ns模擬從站應(yīng)答延遲實驗室短距離幾乎無延遲8000拍200μs200μs 超時門限200μs 這個數(shù)字不是隨便定的——它是 EtherCAT 主站判斷幀丟了的常用門限對應(yīng) 40MHz 下 8000 個周期。3.2 怎么測超時場景把應(yīng)答關(guān)掉即可// 測試超時注釋掉應(yīng)答分支只留超時分支 if (overtime_cnt 16d8000) begin overtime_plus_sync 1b1; resp_ack_pending 1b0; end或者做成可控的reg ack_enable; // 1正常應(yīng)答, 0模擬丟幀 ... if (ack_enable (overtime_cnt 16d2)) begin resp_ack_sync 1b1; ... end四、關(guān)鍵設(shè)計 3從站行為建模本文核心這是整個 tb 最有技術(shù)含量的部分——怎么讓 tb 扮演32 個從站。4.1 時間戳基礎(chǔ)reg [63:0] slave_time_base; reg [31:0] port_time_base; always (posedge clk or negedge rst_n) begin if (!rst_n) begin slave_time_base 64d0; port_time_base 32d0; end else begin slave_time_base slave_time_base 64d25; // 從站時鐘運行 (40MHz, 25ns/拍) port_time_base port_time_base 32d25; end end每拍 25ns模擬從站本地時鐘自由運行。4.2 影子計數(shù)器tb 怎么知道我是第幾個從站這是行為級建模的核心妥協(xié)。真實硬件里從站是根據(jù)自身物理位置返回 DL status 的——它知道自己上游有沒有連接、下游有沒有連接完全不依賴主站內(nèi)部計數(shù)器。4.3 DL status 線型拓?fù)洳呗訣therCAT 常見的是線型拓?fù)鋎aisy chain主站 → 從站1 → 從站2 → … → 從站N末端。主站 ──Port0──[從站1]──Port3──┬──Port0──[從站2]──Port3──┬─...─┬──Port0──[從站N] 鏈路 鏈路 (末從站) Port3 懸空DL status 寄存器 0x0110 的位定義16-bit位含義bit 0PDI Operationalbit 1DL User Watchdogbit 2Extended Link Detectionbit [7:4]物理鏈路 LINKPort0~Port31 link upbit [15:12]通信環(huán)路 LOOPPort0~Port31 在環(huán)路內(nèi)活躍 LINK LOOP兩個位都為 1 才算這個口真正通著。對應(yīng)到 tbalways (*) begin if (reg918_rec_valid) esc_rec_time slave_time_base; // 從站 0x0918 時間戳 else esc_rec_time 64d0; if (rec_port_time_valid) esc_cap_time port_time_base; // 端口時間戳 else if (sync_aux_cnt 3d1) begin // DL status 0x0110: 末從站 Port3 不活躍 if (tb_slave_idx (wkc_nu - 1b1)) begin // 末從站: 僅 Port0 活躍 (bit[4]1, bit[9]1) // byte00x10, byte10x02 → port3_active0, is_port3_inactive1 esc_cap_time 32h0000_0210; end else begin // 非末從站: Port0 Port3 均活躍 // byte00x90(bit[4,7]1), byte10x82(bit[9,15]1) esc_cap_time 32h0000_8290; end end else esc_cap_time 32d0; end把這兩個常量拆開看關(guān)鍵非末從站 0x0000_8290 0000_0000_0000_0000_1000_0010_1001_0000 (bin) │ │ │ │ bit15 ┘ bit9 ┘ │ └─ bit4 (Port0 LINK) (Port3 LOOP) └───── bit7 (Port3 LINK) → Port0: LINK1(bit4) LOOP1(bit9) ? 活躍 → Port3: LINK1(bit7) LOOP1(bit15) ? 活躍 末從站 0x0000_0210 0000_0000_0000_0000_0000_0010_0001_0000 (bin) │ └─ bit4 (Port0 LINK) bit9 ┘ → Port0: LINK1(bit4) LOOP1(bit9) ? 活躍 → Port3: LINK0(bit7) LOOP0(bit15) ? 不活躍末從站的 Port3 沒有下游連接所以 LINK 和 LOOP 都是 0——這正是主站判斷拓?fù)涞筋^了的依據(jù)。tb_slave_idx 0 1 2 ... 30 31(末) esc_cap_time 8290 8290 8290 ... 8290 0210 ↑ 最后一拍突變 port3_active 1 1 1 ... 1 0截圖要點跑完 32 個從站看esc_cap_time在前 31 個是0x8290、最后一個變成0x0210。這一拍的突變就是末從站識別邏輯被驗證成功的標(biāo)志。4.4 為什么末從站識別這么重要主站靠 DL status 判斷拓?fù)浣Y(jié)構(gòu)用于確定環(huán)網(wǎng)是否閉合末從站 Port3 不活躍 線型Port3 活躍且連回主站 環(huán)型計算傳播延遲時排除未連接的端口電纜冗余場景判斷斷線位置如果這段建模錯了比如末從站也返回0x8290主站會認(rèn)為拓?fù)錄]到頭繼續(xù)等待導(dǎo)致sync_done永遠(yuǎn)不出來——這是 DC 仿真最常見的跑不完原因之一。五、測試流程initial begin $display(); $display(TEST: my_ecat_sync (DC Synchronization)); $display(); test_cnt 0; pass_cnt 0; fail_cnt 0; // 初始化 rst_n 1b0; wkc_nu 7d32; // 32 個從站 crc32_tx_cnt 9d0; res_ack_sync 1b0; overtime_plus_sync 1b0; esc_rec_time 64d0; esc_cap_time 32d0; #100; rst_n 1b1; $display([Reset released]); // 等待 DC 同步完成或超時 sync_done_flag 1b0; timeout_flag 1b0; fork begin (posedge sync_done); sync_done_flag 1b1; $display([sync_done asserted at time%0t], $time); end begin #50_000_000; // 50ms 超時 timeout_flag 1b1; $display([WARNING: sync_done timeout]); end join // 檢查結(jié)果 check(sync_done_flag, DC sync completed successfully); check(sync_error 1b0, no sync error); check(error_code 8d0, error code is 0); check(sync_rst_n 1b1, sync_rst_n released); $display(); $display(SUMMARY: %0d total, %0d passed, %0d failed, test_cnt, pass_cnt, fail_cnt); if (fail_cnt 0) $display( ALL TESTS PASSED ); else $display( %0d TESTS FAILED , fail_cnt); $display(); $finish; end5.1 這個 fork/join 有個隱患fork begin (posedge sync_done); sync_done_flag 1b1; end begin #50_000_000; timeout_flag 1b1; end joinfork...join會等兩條分支都結(jié)束才繼續(xù)。所以即使sync_done在第 5ms 就來了tb 也要白等到 50ms超時分支走完才繼續(xù)。這不是功能 bug但很浪費仿真時間——32 從站的 DC 流程本來就長每次跑都白等 45ms。改進ModelSim 支持join_any的話fork begin (posedge sync_done); sync_done_flag 1b1; end begin #50_000_000; timeout_flag 1b1; end join_any // ← 任一分支結(jié)束就繼續(xù) disable fork; // 可選殺掉其余分支如果用的是 ModelSim 10.1b 等不支持join_any的老版本用 flag 輪詢// 兼容寫法超時計數(shù)器 標(biāo)志輪詢 timeout_cnt 0; while (!sync_done_flag (timeout_cnt 50_000)) begin (posedge clk); timeout_cnt timeout_cnt 1; end if (!sync_done_flag) $display([WARNING: sync_done timeout]);5.2 check 任務(wù)task check; input cond; input [255:0] msg; begin test_cnt test_cnt 1; if (cond) begin pass_cnt pass_cnt 1; $display( PASS: %0s, msg); end else begin fail_cnt fail_cnt 1; $display( FAIL: %0s (time%0t), msg, $time); end end endtask簡單夠用。建議在FAIL分支里多打幾個關(guān)鍵信號的值方便定位$display( FAIL: %0s (time%0t) sm%0d idx%0d err%0d code%0d, msg, $time, sync_sm, ecat_index_sync, sync_error, error_code);六、仿真結(jié)果分析與截圖指南6.1 波形 do 文件# wave_dc.do vlog defineSIMULATION ../rtl/*.v tb_my_ecat_sync.v vsim -voptargsacc work.tb_my_ecat_sync set TOP sim:/tb_my_ecat_sync set DUT ${TOP}/u_ecat_sync # --- 全局控制 --- add wave -divider {--- Control ---} add wave -radix binary ${TOP}/clk add wave -radix binary ${TOP}/rst_n add wave -radix unsigned ${TOP}/wkc_nu # --- 狀態(tài)機最重要--- add wave -divider {--- FSM ---} add wave -radix unsigned -color Chartreuse ${TOP}/sync_sm add wave -radix unsigned ${TOP}/ecat_const_adr add wave -radix unsigned ${TOP}/ecat_index_sync add wave -radix unsigned ${TOP}/sync_aux_cnt add wave -radix binary -color Green ${TOP}/sync_done add wave -radix binary -color Red ${TOP}/sync_error add wave -radix unsigned ${TOP}/error_code # --- 幀收發(fā) --- add wave -divider {--- Frame TX/RX ---} add wave -radix binary -color Yellow ${TOP}/sync_respone_valid add wave -radix unsigned ${TOP}/crc32_tx_cnt add wave -radix hex ${TOP}/tx_data_sync add wave -radix binary ${TOP}/res_ack_sync add wave -radix binary -color Orange ${TOP}/overtime_plus_sync # --- 從站數(shù)據(jù) --- add wave -divider {--- Slave Data ---} add wave -radix decimal ${TOP}/esc_rec_time add wave -radix hex -color Magenta ${TOP}/esc_cap_time add wave -radix binary ${TOP}/reg918_rec_valid add wave -radix binary ${TOP}/rec_port_time_valid add wave -radix unsigned ${TOP}/tb_slave_idx configure wave -timelineunits ns wave zoom full run -all七、這個 tb 的三個隱患與改進隱患 1fork...join白等 50ms問題前面說過join等兩條分支都結(jié)束即使sync_done早就來了也要等滿 50ms。改進用join_any新版本或 flag 輪詢老版本見 5.1。隱患 2tb_slave_idx是白盒建模問題觸發(fā)條件sync_sm 7d3 sync_aux_cnt 3d7 res_ack_sync硬編碼對齊 DUT 內(nèi)部時序。DUT 一改就失效且失效方式隱蔽返回錯誤的 DL status看起來像拓?fù)溴e了。改進改用 DUT 輸出信號ecat_index_sync作為從站索引來源見 4.2。如果 DUT 沒有這個輸出建議給 DUT 加一個——這是個很有價值的調(diào)試輸出。隱患 3esc_recv_time用組合邏輯賦 64 位問題always (*) begin if (reg918_rec_valid) esc_recv_time slave_time_base; else esc_recv_time 64d0; endreg918_recv_valid是 DUT 輸出slave_time_base是 tb 寄存器。組合邏輯賦值可能與 DUT 的采樣沿競爭——DUT 在這個時鐘沿采樣esc_rec_time時tb 的slave_time_base也剛更新讀到的是新值還是舊值取決于仿真器調(diào)度順序。八、完整 checklist搭 DC 同步仿真環(huán)境時逐條確認(rèn)時鐘timescale 1ns/1ps#12.540MHz自檢 100 拍 2500nsIP 替代sub1等行為級模型仿真脫離 Quartus 庫幀結(jié)構(gòu)74 拍一幀關(guān)鍵節(jié)拍 22加載/ 73結(jié)束非發(fā)送期crc32_tx_cnt歸零應(yīng)答延遲 2 拍超時門限 8000 拍200μs從站時鐘每拍 25ns影子計數(shù)器觸發(fā)條件與 DUT 對齊或改用 DUT 輸出DL status非末從站0x8290、末從站0x0210esc_recv_time/esc_cap_time用時序賦值避免競爭fork用join_any或 flag 輪詢不白等check失敗時打印sync_sm/ecat_index_sync/error_codevlog defineSIMULATION斷言生效vsim -voptargsacc內(nèi)部信號可見改了源碼重新 vlog九:仿真圖十、結(jié)語DC 同步仿真的難點不在 RTL而在怎么讓 tb 扮演好32 個從站。三件事做對了環(huán)境就通了發(fā)送節(jié)拍嚴(yán)格對照真實以太網(wǎng)幀結(jié)構(gòu)74 拍應(yīng)答/超時用明確的拍數(shù)建模2 拍 / 8000 拍從站行為用 DL status 反映真實拓?fù)淠恼?Port3 不活躍其中最容易被忽視、也最容易出錯的是第三點——末從站的 DL status 如果建模錯了主站會一直等拓?fù)涞筋^sync_done永遠(yuǎn)出不來你會以為是狀態(tài)機有 bug其實是 tb 的鍋。另外提醒一句tb 里凡是依賴 DUT 內(nèi)部信號的地方都是白盒建模屬于技術(shù)債。能改成用 DUT 輸出信號的盡量改——DUT 一改 tb 就廢的教訓(xùn)踩過一次就記住了。如果這篇文章對你有幫助歡迎點贊收藏。有問題可以在評論區(qū)交流——尤其是你也在做 EtherCAT 主站 DC 同步的話歡迎一起探討拓?fù)鋻呙?、電纜冗余那些坑。