ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

FPGA ADC 码流窗口统计:码型转换、峰峰值与最后一个样本的自检实验

FPGA ADC 码流窗口统计:码型转换、峰峰值与最后一个样本的自检实验 ADC 采样数据到达 FPGA 后常需要每收到 N 个样本就报告一次最小值、最大值、峰峰值以及端点码出现次数。这里的难点并不在比较器本身offset binary 不能直接当二补码比较最后一个样本不能漏算下游停顿时也不能把结果覆盖。本文实现一个独立的数字统计核心统计窗口由N 次实际输入握手定义。气泡和背压会改变窗口持续时间所以它不是固定时间长度的测量窗也不是 ADC 引脚接收器。先给真实环境、执行命令和结果2026-10-24在实际核实的DESKTOP-396TDVB上使用已安装的 Vivado Simulator2022.1SW Build3526262、IP Build3524634。时钟周期为 TB 设置的 10 ns不是目标板已验证的工作频率。本次只做 RTL 仿真综合、STA、上板均未执行。主自检的 12 个参数实例全部通过独立 reviewer 的 20 个实例也通过。主测试实际接受 5948 个样本消费 2212 个窗口这些窗口对应 5876 个样本其余 72 个样本因复位丢弃。独立测试接受 22211 个样本消费 6162 个窗口另有 170 个复位丢弃样本。独立小位宽穷举的2812 个不同窗口都实际被消费不是仅生成了输入向量。TEST_PASS ADC_MAIN instances12 TEST_PASS ADC_REVIEW cases20 all_small_width_tuples_consumed正常测试的 xvlog、xelab、xsim 退出码均为 0且日志无 Fatal/ERROR。四个有意错误变体都成功编译、展开后触发功能断言后文说明它们各自暴露的问题。结论来自当天新日志不复用旧实验成绩。先创建src子目录将后文两个完整代码块保存为src/adc_window_stats.sv、src/tb_adc_window_stats.sv然后在包含src子目录的项目根目录打开 PowerShell。以下命令不需要 Python 或私人 ZIP使用已经安装的 Vivado。只需按自己的安装位置修改$vivadoBin新输出目录不可已存在。$vivadoBinD:\tools\Xilinx\Vivado\2022.1\binNew-Item-ItemType Directory-Pathmanual_adc_window_stats-ErrorAction StopSet-Locationmanual_adc_window_stats$vivadoBin\xvlog.bat--sv../src/adc_window_stats.sv../src/tb_adc_window_stats.svif($LASTEXITCODE-ne0){throwxvlog failed}$vivadoBin\xelab.battb_adc_window_stats-s sim-debug typicalif($LASTEXITCODE-ne0){throwxelab failed} open_vcd trace.vcd log_vcd /tb_adc_window_stats/* 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..本次自动运行同样执行这三种工具及对应选项并把完整 argv、UTC 开始/结束时间、退出码、编译源文件副本和 SHA256 留存。xsim.log、console 日志、CSV、trace.vcd与sim.wdb都有保存VCD 只记录前 4000 ns随后run all完成剩余测试CSV 覆盖全程。不能把局部 VCD 说成全部用例的波形。1. 先把码型变成同一种有符号整数参数W1输入原始位串in_code[W-1:0]。OFFSET_BINARY是编译参数不能在一个窗口内动态切换OFFSET_BINARY1x unsigned(in_code) - 2^(W-1) OFFSET_BINARY0按 W 位二补码解释 in_code L -2^(W-1), H 2^(W-1)-1offset binary 与二补码的位串转换只需翻转 MSB。这个编码关系可在 ADI《Fundamentals of Sampled Data Systems》第 2 章、印刷页 2.9 的 编码章节 核对本文于 2026-10-24 实际访问。这里使用的是整数码单位实际电压换算仍取决于转换器量程、参考电压和具体传输函数。例如 W8规范化整数 xoffset binary 原码二补码原码-1280080-17FFF08000127FF7F表中原码为十六进制。转换后比较对象必须声明为 signed不能只在输入端做一次$signed后面却又用无符号比较破坏顺序。out_low_hits统计xL的次数out_high_hits统计xH的次数。它们不是窗口最小值/最大值的出现次数。比如窗口[3,3,7,7]的 min/max 是 3/7但在 W8 时两个端点计数都为 0。端点码可能是正常量化结果单凭这个计数不能判定模拟过载。2. 接口契约与位宽接口约定W / N正整数包括 W1、N1clk / rst上升沿工作高有效同步复位优先级最高in_valid / in_ready / in_code边沿前 validready 才接受样本阻塞时上游保持 valid 和 codeout_valid / out_ready边沿前 validready 才消费结果out_min / out_maxW 位有符号数out_ppW 位无符号数表示 max-minout_low_hits / out_high_hitsC$clog2(N1)位可表示 0…N峰峰值最大为H-L2^W-1所以 W 位无符号足够W 位有符号数却只能表示到2^(W-1)-1。RTL 先将两个极值分别符号扩展到 W1 位再相减最后保留 W 位非负结果。例如 W16、min-32768、max32767峰峰值为 65535。计数宽度必须能装下 N。N4 时clog2(N)2只能计到 3这里用clog2(N1)3。N1 则 C1不能产生零宽向量。本设计只有一个结果槽。out_valid !out_ready时结果字段稳定in_ready0消费旧结果的同一边沿允许接收下一窗口首样本。N1 时每个样本立即形成结果同拍消费旧结果并产生新结果时out_valid 可以连续为 1。输出无效时字段保持上次值不能只看总线值判断有新结果。同步复位丢弃部分窗口和未消费结果清空输出字段已经消费的结果不能被撤销。虽然in_ready组合地被 rst 抑制统计状态仍只在上升沿复位。3. 用真实逐拍例子检查背压下面取自本次 W8、N4、offset binary 的adc_w8_n4_ob1.csv。CSV 在上升沿后 1 ns 写出输入握手信号保存的是边沿前值结果字段是边沿后值。时钟上升沿 / CSV 时刻 ns事件结果35/36、45/46、55/56、65/66连续接受四个原码 00即四个 -12865 ns 边沿后 valid1minmax-128pp0low_hits475/76、85/86、95/96ready0下一样本的 valid/code 保持不接收新样本所有结果字段保持105/106消费旧结果同时接受下一窗口首个 -128消费计数加 1新窗口已有 1 个样本115/116、125/126、135/136接受三个 127135 ns 后 min-128、max127、pp255端点次数 1/3145/146消费第二个结果窗口消费计数为 2图 1 按 CSV 采样点画阶梯连线明确区分 pre/post 字段它不是示波器截图也不把两次采样之间的线段当作模拟电路波形。前 4000 ns 的 VCD 保存在本次运行目录完整测试用 CSV 核查。图 2 展示前 10 个已消费窗口。全低、全高窗口验证计数能达到 N后面的定向窗口把极值放在最后一项验证它确实被计入。图来自实际消费记录不是只对预期数据作图。4. 完整 RTL保存为src/adc_window_stats.sv。代码块与最终实际编译文件逐字一致。timescale 1ns/1ps module adc_window_stats #( parameter integer W8,N4, parameter bit OFFSET_BINARY1 )( input wire clk,rst,in_valid, output wire in_ready, input wire [W-1:0] in_code, output reg out_valid, input wire out_ready, output reg signed [W-1:0] out_min,out_max, output reg [W-1:0] out_pp, output reg [$clog2(N1)-1:0] out_low_hits,out_high_hits ); localparam integer C$clog2(N1); localparam signed [W-1:0] LOW{1b1,{(W-1){1b0}}}; localparam signed [W-1:0] HIGH{1b0,{(W-1){1b1}}}; reg [C-1:0] count,low_hits,high_hits; reg signed [W-1:0] running_min,running_max; wire signed [W-1:0] sample$signed(in_code ^ (OFFSET_BINARY ? LOW : {W{1b0}})); wire signed [W-1:0] next_min(count0 || samplerunning_min)?sample:running_min; wire signed [W-1:0] next_max(count0 || samplerunning_max)?sample:running_max; wire [C-1:0] next_low(count0 ? {C{1b0}} : low_hits)(sampleLOW); wire [C-1:0] next_high(count0 ? {C{1b0}} : high_hits)(sampleHIGH); wire [W:0] span$signed({next_max[W-1],next_max})-$signed({next_min[W-1],next_min}); assign in_ready!rst (!out_valid || out_ready); always (posedge clk) begin if(rst) begin count0;low_hits0;high_hits0;running_min0;running_max0; out_valid0;out_min0;out_max0;out_pp0;out_low_hits0;out_high_hits0; end else begin if(out_valid out_ready) out_valid0; if(in_valid in_ready) begin running_minnext_min;running_maxnext_max;low_hitsnext_low;high_hitsnext_high; if(countN-1) begin count0;out_valid1; out_minnext_min;out_maxnext_max;out_ppspan[W-1:0]; out_low_hitsnext_low;out_high_hitsnext_high; end else countcount1b1; end end end endmodulenext_min/next_max/next_low/next_high已包含当前输入样本第 N 个样本到来时直接把这些 next 值锁存到输出。若写成out_minrunning_min非阻塞赋值会取旧寄存器值最后一项就被漏掉。count0表示当前样本是新窗口第一项因此初始化极值为这个样本而不是初始化成 0。否则全正窗口的 min 或全负窗口的 max 会被错误的初值污染。5. 完整原始自检 TB参考模型把接受的样本存入数组窗口完成时遍历整个数组计算极值与端点计数。它不读取 DUT 内部状态不调用 DUT 的组合函数。每拍检查 ready、valid、输出数据、未知态以及样本守恒accepted_samples delivered_windows*N discarded_samples partial_samples (pending_result ? N : 0)主 TB 的小宽度穷举为 W1/N1、W2/N3、W3/N2两种码型合计 260 个窗口。随机部分有输入气泡及样本变化输出阻塞另有明确的 3 拍定向场景更密集的随机背压由独立 TB 覆盖不能仅因驱动里有随机 ready 就声称所有时序已覆盖。timescale 1ns/1ps module adc_case #(parameter W3,N3,OB1)(output reg finished); localparam C$clog2(N1); reg clk0,rst1,in_valid0,out_ready0; reg [W-1:0] in_code0; wire in_ready,out_valid; wire signed [W-1:0] out_min,out_max; wire [W-1:0] out_pp; wire [C-1:0] out_low_hits,out_high_hits; adc_window_stats #(.W(W),.N(N),.OFFSET_BINARY(OB)) dut(.*); always #5 if(!finished) clk~clk; longint signed samples[0:N-1],lo,hi,emin0,emax0,x,raw_mask; longint unsigned epp0; integer elow0,ehigh0,partial0,accepted0,delivered0,discarded0; integer cycles0,stalls0,duplex0,fd,phase0,k,j,p,total,word,temporary,reset_age; reg ev0,fire,source_blocked0; reg [W-1:0] saved_source; reg [31:0] seed32h2410a5c3; function automatic [31:0] rng; begin seedseed^(seed13);seedseed^(seed17);seedseed^(seed5);rngseed;end endfunction function automatic longint signed decode(input [W-1:0] v); longint signed z; begin z$unsigned(v);decodeOB ? z-(64sd1(W-1)) : (v[W-1] ? z-(64sd1W) : z);end endfunction function automatic [W-1:0] encode(input longint signed v); begin encodeOB ? v(64sd1(W-1)) : v;end endfunction task automatic tick(input bit v,input [W-1:0] code,input bit ready,input bit reset); bit pre_ready,pre_valid; begin (negedge clk);in_validv;in_codecode;out_readyready;rstreset; (posedge clk);cycles;pre_readyin_ready;pre_validout_valid; if(in_ready ! (!reset (!ev || ready))) $fatal(1,ADC_ASSERT ready W%0d N%0d OB%0d phase%0d,W,N,OB,phase); firevpre_ready; if(reset) begin discardedpartial(ev?N:0);partial0;ev0; emin0;emax0;epp0;elow0;ehigh0;source_blocked0; end else begin if(source_blocked (!v || code!saved_source)) $fatal(1,ADC_STIMULUS_ASSERT blocked source changed); if(ev !ready) stalls; if(ev ready) begin ev0;delivered;if(fire)duplex;end if(fire) begin samples[partial]decode(code);partial;accepted; if(partialN) begin eminsamples[0];emaxsamples[0];elow0;ehigh0; for(integer a0;aN;a) begin if(samples[a]emin)eminsamples[a]; if(samples[a]emax)emaxsamples[a]; if(samples[a]lo)elow; if(samples[a]hi)ehigh; end eppemax-emin;partial0;ev1; end end source_blockedv!pre_ready;saved_sourcecode; end #1; if($isunknown({out_valid,out_min,out_max,out_pp,out_low_hits,out_high_hits}) || out_valid!ev || $signed(out_min)!emin || $signed(out_max)!emax || $unsigned(out_pp)!epp || out_low_hits!elow || out_high_hits!ehigh) $fatal(1,ADC_ASSERT W%0d N%0d OB%0d phase%0d valid%b/%b min%0d/%0d max%0d/%0d pp%0d/%0d hits%0d,%0d/%0d,%0d,W,N,OB,phase,out_valid,ev,out_min,emin,out_max,emax,out_pp,epp,out_low_hits,out_high_hits,elow,ehigh); if(accepted!delivered*Ndiscardedpartial(ev?N:0)) $fatal(1,ADC_ASSERT conservation); $fdisplay(fd,%0d,%0d,%0d,%0d,%0d,%0d,%0d,%0d,%0d,%0d,%0d,%0d,%0d,%0d,%0d,%0d,%0d,%0d,%0d,%0d,%0d,%0d, $time,W,N,OB,phase,reset,v,pre_ready,$unsigned(code),pre_valid,ready,out_valid,$signed(out_min),$signed(out_max),$unsigned(out_pp),out_low_hits,out_high_hits,accepted,delivered,discarded,partial,cycles); end endtask task automatic push(input [W-1:0] code); begin fire0; while(!fire)tick(1,code,(rng()%4)!0,0); end endtask task automatic drain; begin while(ev)tick(0,0,1,0);end endtask initial begin finished0;lo-(64sd1(W-1));hi(64sd1(W-1))-1;raw_mask(64sd1W)-1; fd$fopen($sformatf(adc_w%0d_n%0d_ob%0d.csv,W,N,OB),w); $fdisplay(fd,time_ns,w,n,ob,phase,rst,in_valid,in_ready,in_code,out_valid_pre,out_ready,out_valid_post,out_min,out_max,out_pp,low_hits,high_hits,accepted,delivered,discarded,partial,cycles); tick(0,0,0,1);tick(0,0,1,0); phase1; for(p0;p5;p) begin for(k0;kN;k) begin case(p) 0:xlo;1:xhi;2:x(kN-1)?hi:0; 3:x(kN-1)?lo:0;default:x(k%2)?hi:lo; endcase push(encode(x)); end repeat(3)tick(1,encode(lo),0,0); tick(1,encode(lo),1,0); for(k1;kN;k)push(encode(hi)); drain(); end phase2; if(W*N9) begin total1(W*N); for(word0;wordtotal;word) begin temporaryword; for(k0;kN;k) begin if((wordk)%30)tick(0,rng(),1,0); push(temporary raw_mask);temporarytemporaryW; end drain(); end end phase3; for(j0;j150;j) begin for(k0;kN;k) begin if(rng()%30)tick(0,rng(),1,0); push(rng()); end drain(); end phase4; for(reset_age1;reset_ageN;reset_age) begin for(k0;kreset_age;k)push(encode(k%2?hi:lo)); tick(1,encode(hi),1,1);tick(0,0,1,0); for(k0;kN;k)push(encode(hi)); drain(); end phase5;tick(0,0,1,0); if(partial || ev || accepted!delivered*Ndiscarded || stalls15 || duplex5) $fatal(1,ADC_ASSERT final coverage); $display(CASE_PASS ADC W%0d N%0d OB%0d accepted%0d delivered%0d discarded%0d stalls%0d duplex%0d cycles%0d,W,N,OB,accepted,delivered,discarded,stalls,duplex,cycles); $fclose(fd);finished1; end endmodule module tb_adc_window_stats; wire [11:0] done; adc_case #(1,1,0) a0(done[0]);adc_case #(1,1,1) a1(done[1]); adc_case #(2,3,0) a2(done[2]);adc_case #(2,3,1) a3(done[3]); adc_case #(3,2,0) a4(done[4]);adc_case #(3,2,1) a5(done[5]); adc_case #(8,4,0) a6(done[6]);adc_case #(8,4,1) a7(done[7]); adc_case #(16,5,0) a8(done[8]);adc_case #(16,5,1) a9(done[9]); adc_case #(16,1,0) a10(done[10]);adc_case #(16,1,1) a11(done[11]); initial begin wait(done);$display(TEST_PASS ADC_MAIN instances12);$finish;end initial begin #1000000;$fatal(1,ADC_WATCHDOG);end endmodule主测试各实例实测计数如下表由最终 CSV 重放结果生成WNoffset binary周期接受样本消费窗口丢弃样本穷举消费窗口1103941641631211139416416312230114668722766423111466872276643208474552263643218474552263648401049666164100841104966616410016103891621611016113891621611016501275840165150165112758401651506. 独立复核与故意错误独立 reviewer 在编写自己的 TB 时只读接口契约尚未读取主 RTL/TB完成后再审代码。其整数参考和驱动单独编写覆盖 W1/W2、N1/3/4/5、两种码型的全部输入元组另测 W8/N5、W16/N3。每个穷举窗口必须看到真实输出握手才算完成一项。WNoffset binary周期接受样本消费窗口丢弃样本穷举消费窗口1106353633621211163534734612130695474156681316954591516814076452212810161417635621381016150901685134153215189867513215322106383703691421163735235114230917645213664231919639211664240196215023731025624119641490370102562506851562511221510242516852562511221510248507064959615085170649095150163066145014860163166244114560独立运行共 29461 个实例观察边沿记录 1649 个结果阻塞边沿、2568 次同时输入/输出握手其中 N1 替换结果 1126 次600 周期随机流在每个实例都执行实际随机气泡 2648 次、随机输入受阻 1275 次。输入受阻指 in_valid!in_ready不能与 out_valid!out_ready 的结果阻塞计数混用。每个部分窗口长度和 pending 结果状态都执行复位并核实丢弃计数。以上统计不是功能覆盖率百分比也不是器件性能指标。四个变体均从最终正常 RTL 单点修改保持接口和测试不变故意错误首个实际失败两种码型转换条件反转W2/N1 的最低码被解成 0预期 -2端点计数也错误min 比较改成 unsignedW16/N3 最后一个 -32768 没有成为最小值pp 错成 0预期 65535输出锁存旧 running_min/maxW2/N1 第一个样本就漏算极值仍为旧 0无视 out_ready 清 out_valid阻塞期间 valid 消失且错误重新打开输入 ready这四项都由ADC_REVIEW_ASSERT触发功能 Fatal不是编译或展开失败。XSim 在这些故障运行中返回进程码 0所以必须检查断言和通过标识。一个变体可能打印多个同一时刻的失败仍只计一个被捕获变体不能累计成多轮测试。测试先行阶段还保留过一次真实工具问题未实现占位模块缺少 timescalexelab 拒绝展开另将 TB 的端口初始化移入 initial 块消除了编译警告。修正后主 TB 对未实现核心实际触发功能断言。该起点不是正常 RTL也没有被算进四个最终负例。正常 RTL 在本次范围内未发现功能缺陷。7. 集成时必须守住的边界这里的背压只对可暂停的数字上游成立。真实连续采样 ADC 往往不会因为in_ready0就暂停物理采样必须在前面接缓存、记录丢样或者证明系统吞吐和最大停顿预算。若采样已经丢失统计结果也只描述实际接受的 N 个样本不能冒充连续时域窗口。本模块没有测量模拟电压、噪声或失真也不能凭端点码计数保证发现削顶。码型、对齐、有效位宽和采样接口仍需核对具体 ADC 数据手册。本文不绑定特定转换器不包含厂商 IP也没有宣称综合或上板通过。继续阅读FPGA 位宽与符号扩展。一个可直接用于设计评审的问题是若 ADC 每拍都产生数据而下游最长会停顿 20 拍你的前级缓存深度和丢样计数应如何确定才能让每个统计窗口的样本来源可追踪
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表