碼限速器:每拍限步、目標(biāo)反向與滿量程跨越的實測驗證)
給 DAC 更新目標(biāo)碼時目標(biāo)可能從正值突然跳到負(fù)值。若希望數(shù)字控制量逐步靠近目標(biāo)需要約束的是“本次更新離舊值最多多遠(yuǎn)”并在最后一步恰好停在目標(biāo)。它與一直加固定增量的累加器不同目標(biāo)和最大步幅都可以變化方向也會反轉(zhuǎn)。本文實現(xiàn)一個 W 位有符號目標(biāo)碼限速器。它限制每個有效更新 tick 的數(shù)字碼差沒有模擬 DAC 輸出級不保證真實電壓無過沖或某個器件的建立時間。先給真實環(huán)境、執(zhí)行命令和結(jié)果2026-10-24 在DESKTOP-396TDVB上執(zhí)行已安裝的 XSim2022.1SW Build3526262、IP Build3524634。主 TB 的 5 個參數(shù)實例檢查 8665 拍獨立 TB 的 7 個實例檢查 13308 拍獨立小位寬窮舉4680 個舊輸出目標(biāo)步幅組合。正常運行三階段退出碼均為 0且有明確 TEST_PASS、沒有 Fatal/ERROR。TEST_PASS DAC_MAIN instances5 TEST_PASS DAC_REVIEW四個故意錯誤實現(xiàn)都成功編譯和展開再被獨立功能斷言拒絕。沒有綜合、STA 或上板TB 的 10 ns 時鐘只是仿真激勵不是 FPGA 已驗證的性能結(jié)論。先創(chuàng)建src子目錄將后文兩個完整代碼塊保存為src/dac_slew_limiter.sv、src/tb_dac_slew_limiter.sv然后在包含src子目錄的項目根目錄打開 PowerShell。以下命令不需要 Python 或私人 ZIP使用已經(jīng)安裝的 Vivado。只需按自己的安裝位置修改$vivadoBin新輸出目錄不可已存在。$vivadoBinD:\tools\Xilinx\Vivado\2022.1\binNew-Item-ItemType Directory-Pathmanual_dac_slew_limiter-ErrorAction StopSet-Locationmanual_dac_slew_limiter$vivadoBin\xvlog.bat--sv../src/dac_slew_limiter.sv../src/tb_dac_slew_limiter.svif($LASTEXITCODE-ne0){throwxvlog failed}$vivadoBin\xelab.battb_dac_slew_limiter-s sim-debug typicalif($LASTEXITCODE-ne0){throwxelab failed} open_vcd trace.vcd log_vcd /tb_dac_slew_limiter/* run 4000 ns close_vcd run all quit |Set-Content-Encoding asciisimulate.tcl$vivadoBin\xsim.batsim-tclbatch simulate.tclif($LASTEXITCODE-ne0){throwxsim process failed}$simTextGet-Content-LiteralPathxsim.log-Rawif($simText-notmatchTEST_PASS-or$simText-matchFatal:|ERROR:){throwRTL self-check failed}Set-Location..本次自動運行同樣執(zhí)行這三種工具及對應(yīng)選項并把完整 argv、UTC 開始/結(jié)束時間、退出碼、編譯源文件副本和 SHA256 留存。xsim.log、console 日志、CSV、trace.vcd與sim.wdb都有保存VCD 只記錄前 4000 ns隨后run all完成剩余測試CSV 覆蓋全程。不能把局部 VCD 說成全部用例的波形。1. 數(shù)學(xué)規(guī)則先于 RTL舊輸出為 Y有效目標(biāo)為 T無符號步幅為 S。每個有效更新 tickd T - Y move min(abs(d), S) Y Y move若 d0 Y Y - move若 d0它應(yīng)滿足兩個關(guān)鍵不變量abs(Y-Y)S且 Y’ 位于舊 Y 與 T 的閉區(qū)間內(nèi)。目標(biāo)固定時輸出單調(diào)靠近目標(biāo)目標(biāo)變化后則按新目標(biāo)重新決定方向。若距離小于步幅必須直接到目標(biāo)不能繼續(xù)走完整步幅再發(fā)生過沖。獨立數(shù)學(xué)參考使用等價但不同寫法Y min(max(T, Y-S), YS)參考計算在 64 位整數(shù)中完成覆蓋本次最大 W32 的有符號端點不使用浮點模型。2. 接口和同拍優(yōu)先級信號契約W至少為 1目標(biāo)和輸出是 W 位二補碼clk / rst上升沿工作高有效同步復(fù)位優(yōu)先于所有控制target_valid / target_code每個 clk 上升沿若 target_valid1 則鎖存 target_code與 enable 無關(guān)enable / sample_tick兩者都為 1 才更新輸出max_stepW 位無符號每個有效 tick 使用當(dāng)前值不鎖存out_codeW 位有符號輸出狀態(tài)sample_valid標(biāo)記本邊沿發(fā)生有效更新 tick即使碼值未變化也為 1目標(biāo)更新與 tick 同拍時明確選擇使用新 target_code。因此組合邏輯先形成effective_targettarget_valid?target_code:target_reg避免非阻塞賦值導(dǎo)致本拍仍使用舊目標(biāo)。enable0 只暫停輸出目標(biāo)依舊能更新重新 enable 后應(yīng)追趕最新已鎖存目標(biāo)。sample_tick0 時同理保持輸出但允許鎖存目標(biāo)。max_step0 時輸出保持同時 sample_valid 仍為 1目標(biāo)已經(jīng)等于輸出時也仍產(chǎn)生有效更新標(biāo)記。復(fù)位清目標(biāo)、輸出和 sample_valid 為 0。同拍所有控制都有效時仍以復(fù)位為準(zhǔn)。所有輸入均須滿足同一 clk 域的同步時序本模塊不負(fù)責(zé) CDCrst 也只在 clk 上升沿采樣。sample_valid不是 ready/valid 背壓接口這里沒有 sample_ready。連續(xù)有效 tick 可以讓 sample_valid 連續(xù)為 1下游發(fā)送器必須按這個節(jié)拍接收每拍高電平都代表一次更新不能只檢測它的上升沿。3. 為什么差值必須擴(kuò)成 W1 位對于 W 位有符號碼最小值為-2^(W-1)最大值為2^(W-1)-1。從負(fù)端點到正端點的差為2^W-1W 位 signed 放不下W1 位 signed 才能同時容納正、負(fù)最大差。例如 W32Y-2147483648T2147483647 d4294967295 max_step4294967295 時一次有效tick準(zhǔn)確到達(dá)TRTL 在相減前分別符號擴(kuò)展 T、Y步幅則零擴(kuò)展。不能把無符號最大步幅直接轉(zhuǎn)成 W 位 signed否則全 1 會變成 -1。moved只在 distancestep 的分支被采用此時向目標(biāo)走一步后的結(jié)果必然仍在兩個 W 位有符號端點之間。組合電路在未選中分支可能產(chǎn)生截斷值不能據(jù)此判斷輸出錯誤決定正確性的是實際選中路徑及其位寬條件。4. 本次真實階梯軌跡下面是 W3 的定向運行整數(shù)范圍 -4…3。主 CSV 在上升沿后 1 ns 寫出。上升沿 / CSV 時刻 ns輸入或狀態(tài)輸出及標(biāo)記25/26enable0鎖存目標(biāo) 3輸出仍 0sample_valid035/36、45/46、55/56tick1step1輸出 1、2、385/86新目標(biāo) -4 與 tick 同拍立即按新方向由 3 變 2145/146enable0目標(biāo)更新為 3輸出保持 -3155/156恢復(fù) enablestep7target_valid0使用已鎖存目標(biāo)輸出到 3165/166、175/176step7目標(biāo)切到 -4 再切回 3精確跨越兩個端點185/186新目標(biāo) -4step0輸出仍 3sample_valid1195/196無 tick目標(biāo)更新 3輸出保持sample_valid0圖 1 是真實逐拍 CSV 的階梯連線包含目標(biāo)反向、暫停更新目標(biāo)、最大步幅和零步幅。它表示數(shù)字狀態(tài)變化不能把線段當(dāng)成模擬 DAC 的電壓斜坡。5. 完整 RTL保存為src/dac_slew_limiter.sv以下與本次實際編譯的最終源碼逐字一致。timescale 1ns/1ps module dac_slew_limiter #(parameter integer W16)( input wire clk,rst,enable,sample_tick,target_valid, input wire signed [W-1:0] target_code, input wire [W-1:0] max_step, output reg signed [W-1:0] out_code, output reg sample_valid ); reg signed [W-1:0] target_reg; wire signed [W-1:0] effective_targettarget_valid ? target_code : target_reg; wire signed [W:0] delta$signed({effective_target[W-1],effective_target})-$signed({out_code[W-1],out_code}); wire [W:0] distancedelta[W] ? $unsigned(-delta) : $unsigned(delta); wire signed [W:0] moveddelta[W] ? $signed({out_code[W-1],out_code})-$signed({1b0,max_step}) : $signed({out_code[W-1],out_code})$signed({1b0,max_step}); always (posedge clk) begin if(rst) begin target_reg0;out_code0;sample_valid0;end else begin if(target_valid) target_regtarget_code; sample_validenable sample_tick; if(enable sample_tick) begin if(distance{1b0,max_step}) out_codeeffective_target; else out_codemoved[W-1:0]; end end end endmodule這段代碼沒有額外“運行到目標(biāo)”的狀態(tài)機。目標(biāo)每拍都可以變化max_step 也可每拍變化每個有效 tick 都重新求當(dāng)前距離并夾住最后一步。它僅承諾數(shù)字域的限步與不越過目標(biāo)。6. 完整自檢 TB保存為src/tb_dac_slew_limiter.sv。主 TB 使用 64 位區(qū)間夾取參考每次檢查 out_code 和 sample_valid不讀內(nèi)部目標(biāo)寄存器。小位寬先用合法最大步幅讓 DUT 到達(dá)指定舊輸出再測試目標(biāo)和步幅組合沒有 force 寄存器繞過真實路徑。timescale 1ns/1ps module dac_case #(parameter W8)(output reg finished); reg clk0,rst1,enable0,sample_tick0,target_valid0; reg signed [W-1:0] target_code0; reg [W-1:0] max_step0; wire signed [W-1:0] out_code; wire sample_valid; dac_slew_limiter #(.W(W)) dut(.*); always #5 if(!finished)clk~clk; longint signed target0,y0,old_y,lo,hi,step_max,left_bound,right_bound; integer fd,phase0,cycles0,updates0,holds0,reversals0,exhaustive0,zeros0,paused_targets0,same_tick0; integer i,a,b,s,previous_direction0,direction; reg expected_valid; reg [31:0] seed32h2410dac5; function automatic [31:0] rng; begin seedseed^(seed13);seedseed^(seed17);seedseed^(seed5);rngseed;end endfunction task automatic tick(input bit en,input bit st,input bit tv,input signed [W-1:0] t,input [W-1:0] step,input bit reset); longint signed target_before,step_integer; begin (negedge clk);rstreset;enableen;sample_tickst;target_validtv;target_codet;max_stepstep; (posedge clk);cycles;old_yy;target_beforetarget;step_integer$unsigned(step); if(reset)begin y0;target0;expected_valid0;previous_direction0;end else begin if(tv)begin target$signed(t);if(!en)paused_targets;end expected_validenst; if(expected_valid)begin updates;if(tv)same_tick;if(step_integer0)zeros; left_boundy-step_integer;right_boundystep_integer; y(targetleft_bound)?left_bound:((targetright_bound)?right_bound:target); direction(yold_y)?1:((yold_y)?-1:0); if(direction previous_direction direction!previous_direction)reversals; if(direction)previous_directiondirection; end else holds; end #1; if($isunknown({sample_valid,out_code}) || sample_valid!expected_valid || $signed(out_code)!y) $fatal(1,DAC_ASSERT W%0d phase%0d cycle%0d valid%b/%b output%0d/%0d target%0d old%0d step%0d,W,phase,cycles,sample_valid,expected_valid,out_code,y,target,old_y,step_integer); if(!reset expected_valid ((yold_y?y-old_y:old_y-y)step_integer || ylo || yhi))$fatal(1,DAC_ASSERT invariant); $fdisplay(fd,%0d,%0d,%0d,%0d,%0d,%0d,%0d,%0d,%0d,%0d,%0d,%0d,%0d,%0d,%0d,%0d, $time,W,phase,reset,en,st,tv,$signed(t),$unsigned(step),target_before,target,old_y,$signed(out_code),sample_valid,cycles,updates); end endtask initial begin finished0;lo-(64sd1(W-1));hi(64sd1(W-1))-1;step_max(64sd1W)-1; fd$fopen($sformatf(dac_w%0d.csv,W),w); $fdisplay(fd,time_ns,w,phase,rst,enable,tick,target_valid,target_code,max_step,target_before,target_after,out_before,out_after,sample_valid,cycles,updates); tick(1,1,1,hi,step_max,1); phase1; tick(0,0,1,hi,1,0);repeat(5)tick(1,1,0,lo,1,0); tick(1,1,1,lo,1,0);repeat(5)tick(1,1,0,hi,1,0); tick(0,1,1,hi,step_max,0);tick(1,1,0,lo,step_max,0); tick(1,1,1,lo,step_max,0);tick(1,1,1,hi,step_max,0); tick(1,1,1,lo,0,0);tick(1,0,1,hi,step_max,0); tick(1,1,0,lo,step_max,0);tick(1,1,0,0,0,0); phase2; if(W3)for(a0;a(1W);a)for(b0;b(1W);b)for(s0;s(1W);s)begin tick(1,1,1,a,step_max,0); tick(1,1,1,b,s,0);exhaustive; end phase3; for(i0;i1500;i)tick(rng()%4!0,rng()%3!0,rng()%2,rng(),rng(),i%1370); phase4; tick(1,1,1,lo,step_max,0);tick(0,0,1,hi,1,0); tick(1,1,1,hi,step_max,1);tick(1,1,0,lo,step_max,0); if(y!0 || target!0 || updates10 || paused_targets1 || zeros2 || same_tick3) $fatal(1,DAC_ASSERT coverage); $display(CASE_PASS DAC W%0d cycles%0d updates%0d holds%0d reversals%0d exhaustive%0d zero_steps%0d paused_targets%0d same_tick%0d,W,cycles,updates,holds,reversals,exhaustive,zeros,paused_targets,same_tick); $fclose(fd);finished1; end endmodule module tb_dac_slew_limiter; wire [4:0] done; dac_case #(1) d1(done[0]);dac_case #(3) d3(done[1]);dac_case #(8) d8(done[2]); dac_case #(16) d16(done[3]);dac_case #(32) d32(done[4]); initial begin wait(done);$display(TEST_PASS DAC_MAIN instances5);$finish;end initial begin #1000000;$fatal(1,DAC_WATCHDOG);end endmodule主測試 W1/W3 窮舉組合分別為 8/512合計 520各寬度還有 1500 周期隨機控制并插入復(fù)位。實際結(jié)果表如下W檢查周期有效更新 tick復(fù)位周期窮舉狀態(tài)/目標(biāo)/步幅組合115417611383254917691351281525745130161525745130321525745130主測試總共 4765 個有效更新 tick它們包含值未變化的有效 tick。只比較相鄰輸出是否變化會漏掉 max_step0 或已經(jīng)到達(dá)目標(biāo)時的有效標(biāo)記錯誤。7. 獨立 TB 和全組合驗證獨立 reviewer 先讀契約獨立編寫 TB 后才審 RTL編寫階段沒有讀取主 TB。獨立 TB 對 W1/W2/W3/W4 窮舉舊輸出、目標(biāo)和所有無符號步幅總計86451240964680組另覆蓋 W8/W16/W32。W檢查周期有效更新 tick復(fù)位周期窮舉狀態(tài)/目標(biāo)/步幅組合1580322108269243610643158813501051248756853410409685643131001656430810032564331100獨立 CSV 逐行重放確認(rèn) 13308 拍其中 11594 個有效 tick、1644 個非復(fù)位保持拍、70 個復(fù)位拍。包含 724 個零步幅有效 tick、1722 個目標(biāo)等于輸出的有效 tick、476 次暫停期間目標(biāo)更新這些類別會重疊不應(yīng)相加當(dāng)作總測試數(shù)。所有小位寬組合既完整又無重復(fù)不僅是 for 循環(huán)次數(shù)正確。圖 2 匯總真實 W4 的 4096 個轉(zhuǎn)移。橫軸是更新前目標(biāo)距離縱軸是無符號步幅顏色是實際輸出變化量同坐標(biāo)的重復(fù)轉(zhuǎn)移被合并點大小反映數(shù)量。圖是結(jié)果統(tǒng)計不是時間波形。故意錯誤變體首個真實功能失敗W32同拍更新仍使用舊目標(biāo)cycle 5應(yīng)到 2147483647實際停在 -2147483648差值縮窄到 W 位cycle 6應(yīng)由 2147483647 減到 2147483646卻跳到 -2147483648距離不足時仍走完整步幅cycle 4應(yīng)到 -2147483648實際變成 1max_step0 時不產(chǎn)生 sample_validcycle 7碼值正確但 valid 為 0預(yù)期 1四個變體的 xvlog、xelab 都成功隨后DAC_REVIEW_ASSERT觸發(fā) Fatal。它們的 XSim 進(jìn)程退出碼也為 0這正說明測試判定必須結(jié)合功能斷言。一個變體中的多個同刻 Fatal 不被重復(fù)計數(shù)也不意味著該失敗運行完成了后續(xù)全組合測試。正常 RTL 在已執(zhí)行范圍內(nèi)沒有發(fā)現(xiàn)功能缺陷。測試先行占位模塊曾因缺 timescale 在 xelab 階段失敗修正后才進(jìn)入預(yù)期功能失敗該工具問題及原日志保留未當(dāng)成最終負(fù)例成績。8. 數(shù)字限步不等于模擬 slew rateADI 的 Types of ADCs and DACs 頁面標(biāo)注 2002-07-22本文于 2026-10-24 核讀了 Settling Time 與 Slew Rate 條目。前者涉及更新命令后進(jìn)入規(guī)定誤差范圍所需時間后者涉及模擬輸出變化速率建立過程還會受輸出級振鈴和過沖影響。這里限制的是碼值如果 tick 間隔固定為 Δt數(shù)字碼單位下每次變化不超過 S可描述成碼變化預(yù)算 S/Δt換算電壓還需要器件傳輸關(guān)系。這不能保證 DAC 實際輸出按同一斜率變化更不能保證無過沖、固定 settling time 或任何特定器件兼容。若 DAC 發(fā)送器一筆事務(wù)需要多個 FPGA 周期必須確保 sample_tick 不會快到覆蓋尚未發(fā)送的數(shù)據(jù)或者在上層加入明確的緩沖/調(diào)度。本文沒有輸出背壓不能把發(fā)送器忙碌當(dāng)成核心會自動等待。繼續(xù)閱讀飽和累加器。具體工程問題是當(dāng)目標(biāo)允許隨時反向、發(fā)送器又需要 24 個時鐘發(fā)送一次數(shù)據(jù)時你應(yīng)怎樣產(chǎn)生 sample_tick才能同時滿足每次數(shù)字限步和“不丟更新”的系統(tǒng)契約