碼限速器:每拍限步、目標(biāo)反向與滿(mǎn)量程跨越的實(shí)測(cè)驗(yàn)證)
給 DAC 更新目標(biāo)碼時(shí)目標(biāo)可能從正值突然跳到負(fù)值。若希望數(shù)字控制量逐步靠近目標(biāo)需要約束的是“本次更新離舊值最多多遠(yuǎn)”并在最后一步恰好停在目標(biāo)。它與一直加固定增量的累加器不同目標(biāo)和最大步幅都可以變化方向也會(huì)反轉(zhuǎn)。本文實(shí)現(xiàn)一個(gè) W 位有符號(hào)目標(biāo)碼限速器。它限制每個(gè)有效更新 tick 的數(shù)字碼差沒(méi)有模擬 DAC 輸出級(jí)不保證真實(shí)電壓無(wú)過(guò)沖或某個(gè)器件的建立時(shí)間。先給真實(shí)環(huán)境、執(zhí)行命令和結(jié)果2026-10-24 在DESKTOP-396TDVB上執(zhí)行已安裝的 XSim2022.1SW Build3526262、IP Build3524634。主 TB 的 5 個(gè)參數(shù)實(shí)例檢查 8665 拍獨(dú)立 TB 的 7 個(gè)實(shí)例檢查 13308 拍獨(dú)立小位寬窮舉4680 個(gè)舊輸出目標(biāo)步幅組合。正常運(yùn)行三階段退出碼均為 0且有明確 TEST_PASS、沒(méi)有 Fatal/ERROR。TEST_PASS DAC_MAIN instances5 TEST_PASS DAC_REVIEW四個(gè)故意錯(cuò)誤實(shí)現(xiàn)都成功編譯和展開(kāi)再被獨(dú)立功能斷言拒絕。沒(méi)有綜合、STA 或上板TB 的 10 ns 時(shí)鐘只是仿真激勵(lì)不是 FPGA 已驗(yàn)證的性能結(jié)論。先創(chuàng)建src子目錄將后文兩個(gè)完整代碼塊保存為src/dac_slew_limiter.sv、src/tb_dac_slew_limiter.sv然后在包含src子目錄的項(xiàng)目根目錄打開(kāi) 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..本次自動(dòng)運(yùn)行同樣執(zhí)行這三種工具及對(duì)應(yīng)選項(xiàng)并把完整 argv、UTC 開(kāi)始/結(jié)束時(shí)間、退出碼、編譯源文件副本和 SHA256 留存。xsim.log、console 日志、CSV、trace.vcd與sim.wdb都有保存VCD 只記錄前 4000 ns隨后run all完成剩余測(cè)試CSV 覆蓋全程。不能把局部 VCD 說(shuō)成全部用例的波形。1. 數(shù)學(xué)規(guī)則先于 RTL舊輸出為 Y有效目標(biāo)為 T無(wú)符號(hào)步幅為 S。每個(gè)有效更新 tickd T - Y move min(abs(d), S) Y Y move若 d0 Y Y - move若 d0它應(yīng)滿(mǎn)足兩個(gè)關(guān)鍵不變量abs(Y-Y)S且 Y’ 位于舊 Y 與 T 的閉區(qū)間內(nèi)。目標(biāo)固定時(shí)輸出單調(diào)靠近目標(biāo)目標(biāo)變化后則按新目標(biāo)重新決定方向。若距離小于步幅必須直接到目標(biāo)不能繼續(xù)走完整步幅再發(fā)生過(guò)沖。獨(dú)立數(shù)學(xué)參考使用等價(jià)但不同寫(xiě)法Y min(max(T, Y-S), YS)參考計(jì)算在 64 位整數(shù)中完成覆蓋本次最大 W32 的有符號(hào)端點(diǎn)不使用浮點(diǎn)模型。2. 接口和同拍優(yōu)先級(jí)信號(hào)契約W至少為 1目標(biāo)和輸出是 W 位二補(bǔ)碼clk / rst上升沿工作高有效同步復(fù)位優(yōu)先于所有控制target_valid / target_code每個(gè) clk 上升沿若 target_valid1 則鎖存 target_code與 enable 無(wú)關(guān)enable / sample_tick兩者都為 1 才更新輸出max_stepW 位無(wú)符號(hào)每個(gè)有效 tick 使用當(dāng)前值不鎖存out_codeW 位有符號(hào)輸出狀態(tài)sample_valid標(biāo)記本邊沿發(fā)生有效更新 tick即使碼值未變化也為 1目標(biāo)更新與 tick 同拍時(shí)明確選擇使用新 target_code。因此組合邏輯先形成effective_targettarget_valid?target_code:target_reg避免非阻塞賦值導(dǎo)致本拍仍使用舊目標(biāo)。enable0 只暫停輸出目標(biāo)依舊能更新重新 enable 后應(yīng)追趕最新已鎖存目標(biāo)。sample_tick0 時(shí)同理保持輸出但允許鎖存目標(biāo)。max_step0 時(shí)輸出保持同時(shí) sample_valid 仍為 1目標(biāo)已經(jīng)等于輸出時(shí)也仍產(chǎn)生有效更新標(biāo)記。復(fù)位清目標(biāo)、輸出和 sample_valid 為 0。同拍所有控制都有效時(shí)仍以復(fù)位為準(zhǔn)。所有輸入均須滿(mǎn)足同一 clk 域的同步時(shí)序本模塊不負(fù)責(zé) CDCrst 也只在 clk 上升沿采樣。sample_valid不是 ready/valid 背壓接口這里沒(méi)有 sample_ready。連續(xù)有效 tick 可以讓 sample_valid 連續(xù)為 1下游發(fā)送器必須按這個(gè)節(jié)拍接收每拍高電平都代表一次更新不能只檢測(cè)它的上升沿。3. 為什么差值必須擴(kuò)成 W1 位對(duì)于 W 位有符號(hào)碼最小值為-2^(W-1)最大值為2^(W-1)-1。從負(fù)端點(diǎn)到正端點(diǎn)的差為2^W-1W 位 signed 放不下W1 位 signed 才能同時(shí)容納正、負(fù)最大差。例如 W32Y-2147483648T2147483647 d4294967295 max_step4294967295 時(shí)一次有效tick準(zhǔn)確到達(dá)TRTL 在相減前分別符號(hào)擴(kuò)展 T、Y步幅則零擴(kuò)展。不能把無(wú)符號(hào)最大步幅直接轉(zhuǎn)成 W 位 signed否則全 1 會(huì)變成 -1。moved只在 distancestep 的分支被采用此時(shí)向目標(biāo)走一步后的結(jié)果必然仍在兩個(gè) W 位有符號(hào)端點(diǎn)之間。組合電路在未選中分支可能產(chǎn)生截?cái)嘀挡荒軗?jù)此判斷輸出錯(cuò)誤決定正確性的是實(shí)際選中路徑及其位寬條件。4. 本次真實(shí)階梯軌跡下面是 W3 的定向運(yùn)行整數(shù)范圍 -4…3。主 CSV 在上升沿后 1 ns 寫(xiě)出。上升沿 / CSV 時(shí)刻 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精確跨越兩個(gè)端點(diǎn)185/186新目標(biāo) -4step0輸出仍 3sample_valid1195/196無(wú) tick目標(biāo)更新 3輸出保持sample_valid0圖 1 是真實(shí)逐拍 CSV 的階梯連線(xiàn)包含目標(biāo)反向、暫停更新目標(biāo)、最大步幅和零步幅。它表示數(shù)字狀態(tài)變化不能把線(xiàn)段當(dāng)成模擬 DAC 的電壓斜坡。5. 完整 RTL保存為src/dac_slew_limiter.sv以下與本次實(shí)際編譯的最終源碼逐字一致。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這段代碼沒(méi)有額外“運(yùn)行到目標(biāo)”的狀態(tài)機(jī)。目標(biāo)每拍都可以變化max_step 也可每拍變化每個(gè)有效 tick 都重新求當(dāng)前距離并夾住最后一步。它僅承諾數(shù)字域的限步與不越過(guò)目標(biāo)。6. 完整自檢 TB保存為src/tb_dac_slew_limiter.sv。主 TB 使用 64 位區(qū)間夾取參考每次檢查 out_code 和 sample_valid不讀內(nèi)部目標(biāo)寄存器。小位寬先用合法最大步幅讓 DUT 到達(dá)指定舊輸出再測(cè)試目標(biāo)和步幅組合沒(méi)有 force 寄存器繞過(guò)真實(shí)路徑。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主測(cè)試 W1/W3 窮舉組合分別為 8/512合計(jì) 520各寬度還有 1500 周期隨機(jī)控制并插入復(fù)位。實(shí)際結(jié)果表如下W檢查周期有效更新 tick復(fù)位周期窮舉狀態(tài)/目標(biāo)/步幅組合115417611383254917691351281525745130161525745130321525745130主測(cè)試總共 4765 個(gè)有效更新 tick它們包含值未變化的有效 tick。只比較相鄰輸出是否變化會(huì)漏掉 max_step0 或已經(jīng)到達(dá)目標(biāo)時(shí)的有效標(biāo)記錯(cuò)誤。7. 獨(dú)立 TB 和全組合驗(yàn)證獨(dú)立 reviewer 先讀契約獨(dú)立編寫(xiě) TB 后才審 RTL編寫(xiě)階段沒(méi)有讀取主 TB。獨(dú)立 TB 對(duì) W1/W2/W3/W4 窮舉舊輸出、目標(biāo)和所有無(wú)符號(hào)步幅總計(jì)86451240964680組另覆蓋 W8/W16/W32。W檢查周期有效更新 tick復(fù)位周期窮舉狀態(tài)/目標(biāo)/步幅組合1580322108269243610643158813501051248756853410409685643131001656430810032564331100獨(dú)立 CSV 逐行重放確認(rèn) 13308 拍其中 11594 個(gè)有效 tick、1644 個(gè)非復(fù)位保持拍、70 個(gè)復(fù)位拍。包含 724 個(gè)零步幅有效 tick、1722 個(gè)目標(biāo)等于輸出的有效 tick、476 次暫停期間目標(biāo)更新這些類(lèi)別會(huì)重疊不應(yīng)相加當(dāng)作總測(cè)試數(shù)。所有小位寬組合既完整又無(wú)重復(fù)不僅是 for 循環(huán)次數(shù)正確。圖 2 匯總真實(shí) W4 的 4096 個(gè)轉(zhuǎn)移。橫軸是更新前目標(biāo)距離縱軸是無(wú)符號(hào)步幅顏色是實(shí)際輸出變化量同坐標(biāo)的重復(fù)轉(zhuǎn)移被合并點(diǎn)大小反映數(shù)量。圖是結(jié)果統(tǒng)計(jì)不是時(shí)間波形。故意錯(cuò)誤變體首個(gè)真實(shí)功能失敗W32同拍更新仍使用舊目標(biāo)cycle 5應(yīng)到 2147483647實(shí)際停在 -2147483648差值縮窄到 W 位cycle 6應(yīng)由 2147483647 減到 2147483646卻跳到 -2147483648距離不足時(shí)仍走完整步幅cycle 4應(yīng)到 -2147483648實(shí)際變成 1max_step0 時(shí)不產(chǎn)生 sample_validcycle 7碼值正確但 valid 為 0預(yù)期 1四個(gè)變體的 xvlog、xelab 都成功隨后DAC_REVIEW_ASSERT觸發(fā) Fatal。它們的 XSim 進(jìn)程退出碼也為 0這正說(shuō)明測(cè)試判定必須結(jié)合功能斷言。一個(gè)變體中的多個(gè)同刻 Fatal 不被重復(fù)計(jì)數(shù)也不意味著該失敗運(yùn)行完成了后續(xù)全組合測(cè)試。正常 RTL 在已執(zhí)行范圍內(nèi)沒(méi)有發(fā)現(xiàn)功能缺陷。測(cè)試先行占位模塊曾因缺 timescale 在 xelab 階段失敗修正后才進(jìn)入預(yù)期功能失敗該工具問(wèn)題及原日志保留未當(dāng)成最終負(fù)例成績(jī)。8. 數(shù)字限步不等于模擬 slew rateADI 的 Types of ADCs and DACs 頁(yè)面標(biāo)注 2002-07-22本文于 2026-10-24 核讀了 Settling Time 與 Slew Rate 條目。前者涉及更新命令后進(jìn)入規(guī)定誤差范圍所需時(shí)間后者涉及模擬輸出變化速率建立過(guò)程還會(huì)受輸出級(jí)振鈴和過(guò)沖影響。這里限制的是碼值如果 tick 間隔固定為 Δt數(shù)字碼單位下每次變化不超過(guò) S可描述成碼變化預(yù)算 S/Δt換算電壓還需要器件傳輸關(guān)系。這不能保證 DAC 實(shí)際輸出按同一斜率變化更不能保證無(wú)過(guò)沖、固定 settling time 或任何特定器件兼容。若 DAC 發(fā)送器一筆事務(wù)需要多個(gè) FPGA 周期必須確保 sample_tick 不會(huì)快到覆蓋尚未發(fā)送的數(shù)據(jù)或者在上層加入明確的緩沖/調(diào)度。本文沒(méi)有輸出背壓不能把發(fā)送器忙碌當(dāng)成核心會(huì)自動(dòng)等待。繼續(xù)閱讀飽和累加器。具體工程問(wèn)題是當(dāng)目標(biāo)允許隨時(shí)反向、發(fā)送器又需要 24 個(gè)時(shí)鐘發(fā)送一次數(shù)據(jù)時(shí)你應(yīng)怎樣產(chǎn)生 sample_tick才能同時(shí)滿(mǎn)足每次數(shù)字限步和“不丟更新”的系統(tǒng)契約