ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Cadence数模混合仿真Verilog调试全流程:从环境配置到波形收敛

Cadence数模混合仿真Verilog调试全流程:从环境配置到波形收敛 前两周有个师弟抱着一堆报错截图来找我说是Cadence数模混合仿真里接了自己写的Verilog数字滤波模块结果仿真器一会儿报器件未定义一会儿瞬态不收敛波形图里数字输出清一色高阻X。我帮他把config、库路径和connect rule从头捋了一遍花了一个下午跑通。这种体验在混合仿真调试里太典型了因为问题往往不在某一个模块而是数字世界和模拟世界观不一样。这篇文章就拿这个场景做主线把Cadence数模混合仿真中Verilog代码调试的完整路线讲清楚适合第一次在Virtuoso里跑混合仿真的模拟工程师也适合刚接触数模混合验证的数字方向同学。真正跑过混合仿真的人都知道环境配置、代码素养、日志分析和波形观察这四件事是串在一起的任何一环掉链子都会让你误以为“Verilog写错了”实际上可能是库没加对、端口没连上、连接规则没设好。下面我按自己平时调试的顺序把每一步的要点和坑位都拆开讲。1. 凑齐混合仿真环境前先搞懂这四件事很多人在这一步就卡住了。混合仿真和纯模拟仿真最大的差别在于一个跑起来的环境里有两个求解器在同时工作模拟部分交给Spectre数字部分交给Xcelium老版本叫Incisive/NC-Sim。这两个引擎之间怎么协作完全由Cadence的config视图、连接规则和库配置决定。1.1 config view数字模块在这里换脸我见过太多人拿到别人发来的工程直接点ADE L就开始run结果报出各种view not found。config视图本质上是一个层次映射表同一个cell下面可以有不同的view实现模拟链路看的是schematic数字RTL则要指向verilog或functional view。新建config时在Virtuoso的Library Manager里选中顶层cellCreate - Cellview - Configuration。进去后会看到一张层次表每一行对应一个instance及其当前view。你需要把数字模块那一行的view手动改成verilog然后保存。这个操作有两个常见漏网之鱼第一个改完view之后忘记确认那个cell确实存在verilog view。如果Library Manager里展开cell只看到schematic和layout你就算把config里的view写成verilog仿真器一样找不到文件。第二个顶层testbench明明是个模拟schematic但你非要在config里把顶层view也改成verilog整个环境会彻底乱掉。一个判断标准很直接顶层testbench管连接和激励配置那边选schematic数字DUT或行为模型配置那边选verilog。这样两个引擎的边界就清楚多了。1.2 仿真器选择与连接规则的牵线作用Virtuoso的ADE L或者ADE Assembler里会有一个Simulator下拉框混合仿真通常选ams或xcelium不同版本叫法略有差异。选择之后Cadence会自动搭建SpectreXcelium的协同流程但数字域和模拟域之间的信号怎么互相解释靠的是connect rule。connect rule分成两类a2d把模拟电压按阈值转换成数字0/1d2a把数字0/1映射成两个模拟电压并在跳变沿指定slew转换斜率。很多工艺库会提供自己的connect rule cell通常是一组Verilog-AMS模型里面包含逻辑阈值、slew、上下拉能力等参数。如果config的Boundary处没有显式放置connect rule工具会套用一套默认规则而这套默认规则往往不设置slew后果就是数字信号跳变沿无限陡Spectre为了收敛被迫把步长压到飞起最后报出Timestep too small。所以我的习惯是在真正跑混合仿真之前先确认工艺库自带connect rule的cell名或参数然后单独建一个config或环境变量去指定它。不同工艺差别挺大网上没法一概而论但核心要看的参数就两个逻辑阈值和slew。后面第4章实战部分会再展开讲。1.3 库路径与cds.lib器件未定义的第一嫌疑热搜词里有一条“cadence仿真器件未定义”在混合仿真里这个报错的常见原因根本不是器件本身而是数字库没有被正确识别。Cadence用cds.lib和lib.defs描述库路径模拟库和数字库的声明方式是有差异的。在cds.lib里模拟库一般是这样的DEFINE analog_lib ./analog_lib DEFINE dig_lib ./dig_lib但数字库还需要在lib.defs中补充一句hdl类型声明DEFINE dig_lib ./dig_lib hdl Verilog ENDDEF如果没有这一句xrun/Xcelium只会把这个库当成模拟库处理后面去找数字cell的verilog view时就什么都找不到报出来的错误五花八门其中就包括“仿真器件未定义”。排查顺序我也固定下来了先看cds.lib有没有定义这个库再看lib.defs有没有hdl声明最后看config中对应的view名是否拼写一致。这三步走完九成器件未定义问题都能解决。1.4 混合仿真跑哪条命令GUI和脚本都是同一个流程Virtuoso在启动混合仿真时会在工程目录下生成一个run目录里面有run脚本和一堆日志。脚本的核心调用就是xrun -ams后面跟着config编译出来的文件列表、connect rule选项、库路径等等。很多人习惯全程GUI但遇到复杂问题我还是建议打开run目录手动看一遍脚本内容至少确认以下几点-ams选项是否存在没有它就不是混合模式connect rule的库路径是否正确拼写数字文件列表是否包含了所有需要的verilog文件有没有漏加-sv选项如果用了SystemVerilog语法。看一遍脚本比在GUI里瞎点半小时有效得多。等你在run目录下执行过一次脚本理解了它打印出来的编译和elaboration信息后面再遇到类似问题就有方向了。2. Verilog代码在混合仿真里的写法红线与自由区数字RTL在纯数字仿真里的写法自由度很大但放进混合仿真环境之后有些写法真的会坑到自己。不是说必须写出可综合代码而是要考虑混合仿真器的调度机制和Spectre求解器的脾气。2.1 可综合子集不是必须但延时控制和事件等待要收着用混合仿真的数字引擎有自己的时间调度机制模拟引擎则靠连续时间求解。两者之间通过模拟步长来同步这导致Verilog里的#5这种延时控制非常不可靠。比如有的数字testbench里用initial begin #100 rst_n 0; #100 rst_n 1; end来产生复位信号。在纯数字仿真器里这个写法完全没问题但在混合仿真的阈值下这种initial块可能只在t0被触发一次后续相对延时并不像你预期的那样推进结果是复位永远不释放整个数字模块全部处于X态或恒定复位态。我的建议是混合仿真中凡是跟时序相关的激励尽量用时钟沿来驱动或者用一个Verilog顶层模块来产生时钟和复位让事件沿和时钟对齐。如果非要用延时控制至少先确认数字引擎的time unit和time precision设置避免出现毫微妙级误会。总之把“不依赖精确延时”当成混合仿真的一个基本素养能少踩很多坑。还有一个典型问题是无限循环。数字模块里写了一个while(1)等待某个事件结果该事件在混合仿真环境下迟迟不来整个数字引擎卡在那里仿真时间不推进界面上看起来像死机。排查时先看log最后停在哪个模块再回去检查那个模块里有没有异步等待。2.2 端口连接、电源地、三态与高阻X态的传播路径混合仿真中数字模块的端口连接检查比纯数字严格。模拟侧net是连续电压数字侧是离散0/1/X/Z两边有本质区别。如果你在数字模块内部用了三态输出或者干脆没接电源地那波形里出现X、Z就很正常。我自己调试时见过一个挺隐蔽的情况数字模块的某个输出端口在config层次里根本没连到模拟net上仿真器不会直接报error但波形里这个端口一直是Z。后来我用$display打印这个端口值才发现它根本没被驱动。排查方法很简单在config里把那条net的高亮点亮跟着走一遍看看源和负载是不是真的都挂在上面。电源地端口也要提一嘴。行为级Verilog模块有时候不显式声明电源地这在纯数字仿真里没问题因为综合工具会帮你映射但混合仿真中如果你希望数字输出高电平和模拟侧的电源域一致最好还是把pwr/gnd引到数字模块端口上或者用supply net连到模拟的VDD/GND。这样能避免出现“数字逻辑输出1但模拟侧只看到0.7V”的奇葩现象。2.3 常量和存储器的参数化数组与$readmemh的实用姿势热搜词里有一条“verilog数组parameter”这个值得展开说。Verilog-2001标准里parameter本身不支持定义数组。如果你想定义一组常量系数最稳的姿势是配合initial块赋值给reg数组或者用$readmemh从文件加载。举个例子localparam integer COEFF_NUM 8; localparam integer WIDTH 16; reg [WIDTH-1:0] coeff [0:COEFF_NUM-1]; integer i; initial begin coeff[0] 16h0001; coeff[1] 16h0002; // ... end如果非要用parameter数组那得换成SystemVerilog的写法parameter logic [7:0] CFG [4] {8h01, 8h02, 8h03, 8h04};这种写法在Xcelium下支持得不错但文件后缀必须是.sv并且在xrun命令里要加-sv选项。混合仿真中很多人忘了这两点结果编译报出一堆语法错误还以为是自己代码问题。$readmemh也有一个经典坑文件路径。混合仿真的工作目录是run目录但你的RTL文件可能放在完全不同的路径下如果写相对路径‘./coeff.dat’仿真器会到run目录里找必然找不到。我的习惯是在代码里用$readmemh配合一个可以从外部传入路径的parameter或者在run脚本里把数据文件软链过来。最省事的还是写绝对路径虽然不太优雅但至少不会让调试卡在这种问题上。2.4 时钟与复位从激励角度避免数字域“卡死”混合仿真里最常见的“数字域卡死”现象不是死循环而是没时钟、没复位。模拟工程师习惯了用analogLib里的vsource去产生三角波或者正弦波但对数字模块来说最省事的时钟还是Verilog testbench里的always块initial clk 0; always #5 clk ~clk;注意这个#5在混合仿真里同样存在延时不精确的问题。稳妥一点的方案是在config的testbench schematic里加一个vpulse作为时钟源设置好周期和上升下降时间利用Spectre和Xcelium的同步机制把时钟沿转进去。这个方法在模拟工程师圈子里更通用因为大家本来就会用vpulse。复位信号也是一样我推荐用一个数字顶层模块来生成时钟和复位这个模块只负责激励不参与DUT逻辑。把激励和DUT分开调试时可以单独看rst_n的波形配合$display打印很快能定位是激励问题还是DUT问题。3. 调试前先学会看日志和波形不然只会乱猜很多人一看到仿真报错就直接去翻代码这其实是效率最低的路径。混合仿真的报错信息量很大必须先学会分类哪些是编译期错误哪些是elaboration错误哪些是运行期数值问题哪些只是警告。3.1 编译期信息哪些W可以直接无视xrun/Xcelium的日志里信息级别前缀有*N、*W、*E、*F等。*N是note通常是告诉你正在编译哪个文件不用管。*W是warning部分可以无视但有几个要警惕。我按频率整理了一张表日志关键词实际含义处理方式*W,DSIM5unused port如果端口确实不需要可以在模块定义里加注释一般可忽略*W,DSIM端口方向不匹配需要检查方向反了会导致仿真行为完全错乱*W,ELAB信号未被驱动或未连接需要认真查经常对应X态*W,NADC异步断言或延时问题混合仿真中常见重点看是否影响复位与时序*E,ELABelaboration错误直接终止仿真必须解决看到*W,DSIM这种端口方向不匹配最典型的场景是config里数字模块的symbol是旧版本新代码加了端口但schematic symbol没有重新生成。这个跟后面实战案例里的第一轮报错是一样的套路。3.2 运行期报错器件未定义、端口不连接、不收敛运行期报错才是混合仿真真正头疼的部分。我按出现频率排序三个最经典的第一个器件未定义。通常会在elaboration阶段报出来提示找不到某个器件或cell。排查链路是cds.lib库路径是否完整、lib.defs中数字库是否声明hdl、config里view名是否和实际库一致。这步我在1.3已经讲过不再重复。第二个端口不连接。有些工具只给warning不会终止仿真但结果就是某个端口悬空波形一片X。我之前遇到过一个模块输入数据总线是[11:0]config里连的net只挂了高8位低4位浮空结果仿了半天平均滤波输出全是乱的。后来用波形窗口检查bus的各bit才发现低4位根本没有有效电平。第三个瞬态不收敛。Spectre报Timestep too small、Newton iterations failed等错误在混合仿真里十有八九和数字信号的跳变沿有关。数字0到1的跳变如果没有任何slew限制对模拟求解器来说就是一个导数无穷大的激励任何数值算法都很难收敛。解决办法我会在第4章实战里给实际案例这里先记住一个大方向给connect rule加slew或者用d2a转换时让数字输出变成带斜坡的电压。3.3 波形观测数字信号和模拟节点一起看波形是混合仿真调试最直观的工具。Cadence自带的SimVision或者新版vault/wavescan可以同时显示数字bus和模拟net。多数模拟工程师习惯只看模拟波形容易忽略数字内部节点的状态。我的做法是先把数字模块的输入输出端口和内部关键reg全部拖进波形窗口再添加模拟侧与数字侧的交界节点。如果数字输出是bus记得设置成bus display模式否则每个bit单独显示很难看出数值变化。等数字侧展现出清晰的计数、状态转移或数据流后再去对照模拟节点的电压波形这样才能判断问题出在哪一侧。另外混合仿真的波形文件有时会很大因为Spectre的步长可能极小。为了方便定位我通常会在Verilog模块里加$display在关键状态切换时打印时间和值。数字打印信息会汇总到run日志里搜索对应时间点就能和波形对齐。这个方法简单但极其好用比盯着波形猜高效得多。4. 一个滑动窗口滤波Verilog模块的混合仿真完整调试记录理论基础说了这么多下面用一个具体例子把整个流程串起来。假设我有模拟前端产生一个电压量经过数字接口进入Verilog模块做8点滑动窗口平均平均结果再通过d2a接口输出到模拟负载电容上观察滤波后的模拟电压。4.1 模块设计参数化的滑动窗口平均滑动窗口平均在信号处理里非常经典RTL实现也不复杂。核心思路是用一个环形缓冲区存最近N个采样再用一个累加器保存窗口内总和每来一个新数据就把最旧的数据从总和里减掉同时加入新数据最后除以窗口长度输出。模块代码如下module sliding_avg #( parameter WIDTH 8, parameter DEPTH 8 ) ( input wire clk, input wire rst_n, input wire [WIDTH-1:0] din, output reg [WIDTH-1:0] dout ); localparam AWIDTH $clog2(DEPTH); reg [WIDTH-1:0] buf [0:DEPTH-1]; reg [AWIDTH-1:0] ptr; reg [WIDTHAWIDTH-1:0] sum; integer i; always (posedge clk or negedge rst_n) begin if (!rst_n) begin ptr 0; sum 0; dout 0; for (i 0; i DEPTH; i i 1) begin buf[i] 0; end end else begin sum sum - buf[ptr] din; buf[ptr] din; ptr ptr 1b1; dout sum AWIDTH; end end endmodule这里有个细节各位注意sum的位宽不是WIDTH而是WIDTH加上$clog2(DEPTH)因为要容纳DEPTH个采样值的累加和。如果位宽不够累加和会溢出平均结果完全错误。我用AWIDTH $clog2(DEPTH)来自动计算这个扩展位宽这样每次修改DEPTH不用手动改位宽也避免了一改参数就翻车的隐患。4.2 混合testbench搭建vsource激励connect rule数字侧我用一个简单的data_gen模块产生斜坡数据模拟现实中的ADC采样值每个时钟周期din递增1。顶层还是模拟schematic里面放一个vsource给整个电源域再加一个电阻和电容组成模拟负载网络。数字侧的输出dout经过d2a转换后连接到电阻的一端电阻另一端接电容到地观察电容上的电压变化。连接规则在这里就很关键了。我没有让Cadence用默认规则而是显式指定了一个带slew的d2a设置。比如在config的Boundary处放入connect rule设置d2a的slew为5ns意思就是数字信号0-1不是瞬时跳变而是用5ns上升沿过渡到模拟高电平。这个设置对Spectre的瞬态求解压力影响巨大后面第三轮报错就是栽在这个上面。4.3 三轮仿真报错的定位链路第一轮elaboration直接报*E,ELAB提示clk端口未连接。我检查代码里clk端口明明存在问题出在Virtuoso的schematic symbol还是旧版本新加的clk端口没有反映在symbol上。解决办法是在Virtuoso里对数字模块重新生成symbol再回到config里刷新hierarchy。这个过程虽然简单但特别容易忘。从log里先看到端口未连接、再去重新生成symbol这个排查顺序基本可以固定下来。第二轮elaboration过了仿真也能跑但dout一直保持0。这个比较隐蔽。我先用$display打印din和sum发现din在正常变化sum却始终是0。查来查去最后发现是复位释放信号的initial块写法有问题initial begin rst_n 0; #200 rst_n 1; end在混合仿真环境下这个延时控制没有按预期工作复位一直没有释放导致计数器指针ptr没有正常轮转sum自然一直是0。后来我改成用时钟沿计数来释放复位reg [3:0] rst_cnt; always (posedge clk or negedge rst_n) begin if (!rst_n) rst_cnt 0; else if (rst_cnt 4d10) rst_cnt rst_cnt 1b1; end assign rst_n_internal (rst_cnt 4d10);这个方式更贴近真实上电复位逻辑在混合仿真里也不会因为延时控制而翻车。第三轮dout终于有值了但Spectre开始报Timestep too small。CRITICAL日志里能看到spectre在数字输出翻转的瞬间步长被压到几十飞秒仿真时间几乎停止。定位思路很清晰问题在数字-模拟转换边界。我的对策就是在连接规则的d2a里显式设置slew同时把模拟负载电容增大到几十皮法级别。跑完之后spectre的收敛压力明显下降仿真速度恢复正常。4.4 修完后的验证结果与合理性判断修复完三轮问题后跑一个20us的瞬态。波形上能看到din斜坡进入dout在稳定后呈阶梯状变化每个台阶对应一次窗口平均更新。d2a输出端的模拟电压没有尖峰毛刺经过RC负载网络后更是平滑得像一条缓变的直流电压曲线。判断合理性的标准也很简单窗口长度为8输入每时钟加1理论上平均输出会比实时输入延迟约4个时钟周期并且数值上等于最近8个采样点的均值。用$display打印几个周期比对确认输出和手工计算一致功能才算真正验证通过。5. 混合仿真高频坑位的优先级排序我的踩坑统计带过几次项目、处理过不少混合仿真问题之后我把高频坑按出现次数做了个排序基本都是真实项目中撞过的。优先级坑位现象根因对策1数字接口电源地没接输出X态或逻辑混乱config里电源域没映射到数字块显式连接pwr/gnd或核对supply net2connect rule不匹配数字沿太陡、spectre不收敛默认规则没有slew设置显式设置d2a/a2d的slew和阈值3config view选错找不到数字模块或器件未定义view映射错误、数字库未声明hdl统一检查config view和lib.defs4数字库路径缺失编译报器件未定义cds.lib或lib.defs漏配按1.3的排查顺序逐项核实5$readmemh文件路径错误存储器初始化全为0或一直X工作目录变成run目录用绝对路径或在run目录建立软链6复位或时钟激励不释放dout恒为0或Xinitial延时在混合仿真中不可靠用时钟沿同步释放复位7行为模型事件等待挂起仿真卡住不动wait语句或无限while改写为时钟同步事件避免异步阻塞8参数位宽截断输出数值偏差大sum位宽不够累加溢出位宽扩展为WIDTHlog2(DEPTH)这8个坑的前四个基本覆盖了“仿真器报错类”问题后四个覆盖了“波形不对、仿真卡住类”问题。遇到任何混合仿真调试先把报错归类再按表找对策通常能省下一半时间。5.1 一套通用的混合仿真数字调试顺序既然要做成流程我总结了一下自己常用的调试顺序不保证最优但至少能让你少走弯路先在纯数字仿真器里把Verilog模块单独跑通排除代码本身的逻辑错误。再进Cadence检查config中数字模块的view映射正确性。检查端口连接和位序确认symbol与代码一致性。设置合适的connect rule重点关注d2a的slew和a2d的阈值这是混合仿真特有的变量。跑仿真时不要盯着波形先看log把所有E和关键W摘出来分析。波形核对先看数字域再看模拟域两条线分开确认。遇到不收敛优先怀疑数字沿太陡用slew和RC负载降速。实在查不出来把数字输出和模拟边界断开分别跑一次纯数字、一次纯模拟确定问题在哪一侧。这个顺序背后其实有一个思想混合仿真设计的初衷是验证数字模块和模拟模块的接口交互而不是用来debug RTL内部逻辑。如果你在混合仿真波形里发现RTL逻辑错了赶紧回去用纯数字testbench重新跑效率高得多。把混合仿真当作边界验证工具而不是万能debugger心态会稳很多。最后再分享一个小技巧。我习惯在仿真run目录里写一个grep脚本专门搜索log中的*E、Timestep too small、Error found by spectre这类关键字跑完仿真直接一键过滤。混合仿真的log动辄几十MB肉眼根本看不过来。有了这个脚本定位问题的速度能快上一大截遇到多人协作的项目也可以直接把这个脚本放进公共目录大家统一用它来抓错误信息。
返回列表