ARTICLE DETAIL

资讯详情

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

UVM实战入门:基于AHB SRAM控制器的验证平台搭建全记录

UVM实战入门:基于AHB SRAM控制器的验证平台搭建全记录 学UVM最难受的不是概念听不懂而是听懂了之后对着一个真实项目不知道第一行代码写在哪。我笔记1做的是把UVM环境跑通、看懂UVM树的构建笔记2折腾了sequence和uvm_do的用法到了笔记3就想找个真实总线项目练手。掂量了一圈发现AHB SRAMCAHB接口的SRAM控制器是最合适的协议不太复杂、状态机一眼能看完但又有地址流水线、burst传输、HREADY反压这些足够磨人的细节。这篇文章就是这次从零搭建验证平台的完整记录适合看完UVM基础、想拿一个真实DUT练手的人也适合正在准备验证岗位面试、想补一补UVM实战经验的同学。选这个项目还有一个原因它不像以太网或PCIe那样有海量协议文档需要啃但又能把UVM里最核心的机制——sequence驱动激励、analysis port传输事务、参考模型比对——全部串起来。一开始我也犹豫过要不要直接学AXI后来发现步子迈大了容易卡在接口时序上反而学不到UVM本身的东西。先把这个平台吃透后面再往AXI走会顺很多。1. 为什么是AHB SRAMC——一个看着不起眼却最适合练手的验证对象1.1 这块DUT到底长什么样先说清楚我们要验证的DUT长什么样免得后面代码对不上。本文采用一个很常见的SRAM控制器结构控制器作为AHB slave挂到总线上内部管一块64KB的同步SRAMAHB侧数据总线32位。地址空间占用系统地址空间中从0x0000_0000开始的64KB区间HADDR[15:2]作为word地址支持8位、16位、32位访问也支持INCR和WRAP两类burst传输。读路径为了时序收敛做了一拍流水输出所以读传输会出现一个wait state写路径内部会根据HSIZE和HADDR[1:0]生成byte mask保证字节写和半字写不出错。这个配置不是芯片手册里的固定参数而是我为了练习而选择的一版典型设计——你在实际工作中拿到的RTL可能支持的操作窄一些或宽一些但验证平台的骨架和思路是完全一样的。这块电路的价值在于它同时覆盖了两类验证要素一类是总线协议时序即AHB的地址/数据两相流水、HREADY握手、burst地址推进另一类是存储逻辑的正确性即写mask生成、读数据返回、burst环绕地址计算。这两个要素在UVM里恰好对应了transaction设计、driver时序建模、scoreboard参考模型这几块核心工作练一圈下来基本就把UVM主路径走全了。1.2 UVM概念和真实总线的第一次映射很多UVM初学者卡住的点在于知道有driver、sequencer、monitor这些组件但不知道它们和真实电路里的什么对应。AHB SRAMC这个项目最大的好处就是把映射关系摆得很清楚。总线上的一次传输对应一个transaction对象里面放地址、读写方向、传输类型、数据宽度、burst类型和写数据。sequence负责生成激励模式单笔读写、连续读写、随机burst、地址边界穿越这些都是一段段可复用的sequence代码。driver从sequencer拿到transaction后把它翻译成HCLK沿上的HADDR、HTRANS、HWRITE、HWDATA等引脚时序monitor做反向工作把总线上采到的时序还原成一个transaction发给scoreboard。scoreboard里运行着一个参考模型用一份软件维护的“影子SRAM”来预测DUT的行为写操作更新影子存储读操作从影子存储返回值然后和monitor从DUT侧实际采到的读数据做比对。这个“激励-观测-比对”闭环就是UVM验证平台运转的核心逻辑。AHB SRAMC把所有环节都压缩在一个相对小的项目里每个组件的代码量都不大非常适合第一次完整走一遍。1.3 学习这个项目需要的前置条件如果你决定跟着搭一遍我建议先确认自己具备这几样东西不然中途会在语法和机制上被绊住很久。SystemVerilog基础要过关尤其是class、interface、随机化约束这几块UVM基础概念至少要明白factory机制、phase机制、config_db、sequence机制、analysis port这五个东西是干什么用的AHB协议不需要背得很熟但要知道信号名字、两相流水、HTRANS和HBURST的含义后面边写边查也来得及。工具方面Linux环境下用VCS或者Questa都可以我用的是VCS加Verdi但代码本身不依赖厂商库换成QuestaSim只需要改脚本。如果你只有Windows环境QuestaSim也能跑只是仿真性能差一点。准备好这些之后建议先建一个干净的目录不要让旧实验的代码混进来。2. 动手写平台前先把AHB时序和SRAMC内部行为嚼碎2.1 AHB总线两相流水线的工作方式AHB传输从宏观上看分两个阶段地址阶段和数据阶段。master在第一个HCLK上升沿之后给出地址和控制信号slave在下一个时钟沿采样与此同时数据阶段紧随地址阶段开始写数据在地址之后的下一个周期出现在HWDATA上读数据由slave在约定的时刻驱动到HRDATA上。这里最关键的是HREADY信号。它的作用和普通握手中的valid/ready类似HREADY为高表示当前数据传输完成为低表示slave还没准备好当前传输会被拉长一个周期。由于AHB是流水线的HREADY拉低不仅让当前数据阶段停下来还会阻塞新地址进入地址阶段——换句话说slave一旦反压整条流水线都会暂停。这个机制对验证平台的driver建模极其重要很多新手在写driver时只考虑了无等待的happy path一旦slave插入wait state地址和控制信号的驱动就开始乱套。可以这样理解AHB流水线地址阶段和数据阶段是相邻的两个火车车厢HREADY是两个车厢之间的挂钩。挂钩松开时前一节车厢数据可以继续往前滑但后一节车厢地址想顶上来也会被挡住。所以slave反压时我们看到的现象往往是HADDR保持不动HTRANS也保持不变总线在那个状态下“卡住”直到HREADY重新拉高。2.2 控制信号组合决定了每一次传输的类型AHB的每个transfer由一组控制信号共同描述验证平台的transaction设计就是把这组控制信号变成可随机化的字段。HTRANS表示当前transfer的性质四种取值是IDLE、BUSY、NONSEQ、SEQNONSEQ表示一次新burst的第一个transferSEQ表示burst后续的连续transfer。HSIZE决定数据宽度0代表8位1代表16位2代表32位。HBURST决定burst类型SINGLE表示单笔INCR是定长增址WRAP4/WRAP8/WRAP16是回卷传输INCR4/INCR8/INCR16是不回卷的定长传输。地址推进规则是所有验证者最容易出错的地方。INCR类burst的地址逐拍递增size字节数WRAP类burst则有一个回卷边界当地址越过burst的边界时它会绕回burst起始地址对应的边界处。举个例子WRAP4且HSIZE232位时4个beat覆盖16字节区域起始地址可以是0x…00、04、08、0C访问到0x0C后下一拍回卷到0x00。对于验证平台来说driver要用同一套规则驱动地址scoreboard要用同一套规则预测下一次读地址两边的公式必须完全一致否则环境无论如何都会报错。2.3 本文所用SRAMC的配置约定由于SRAM控制器不是工业标准IP不同项目里行为细节差别很大我这里把本文后续代码所依据的设计假设写清楚你看的时候对照自己的RTL做调整。存储容量64KB地址范围0x0000_0000到0x0000_FFFF数据总线32位支持HSIZE0、1、2三种访问宽度写操作根据HSIZE和HADDR[1:0]生成字节写使能比如HSIZE1且HADDR[0]0时写低16位HWDATA[15:0]HADDR[0]1时写高16位HWDATA[31:16]读操作固定插入一个wait state即地址被接收后的下一个周期HREADY拉低再下一个周期HREADY拉高同时HRDATA有效HRESP在本项目中始终返回OKAY不处理ERROR响应更复杂的错误注入留到后续笔记扩展。这些约定看似细碎但它们直接影响scoreboard的参考模型怎么写。特别是读wait state的约定它意味着读传输在data phase需要至少两个周期提醒我们在monitor采样时要采集HREADY拉高且HTRANS非IDLE的那个时刻作为传输完成的边界而不是看到地址有效就急着采样数据。2.4 “slave不响应时最多只能发8个包”为什么是8个网络上有句很经典的面试总结AHB master在slave一直不回HREADY的情况下最多发出8个包就会停下来。单独看这句话容易误解成协议规定实际上这是AHB master内部outstanding缓冲深度的体现。AHB协议允许master在HREADY为高时连续发起多个地址这些尚未完成数据阶段的transfer会被记录在master的内部FIFO里FIFO深度通常做成8。当slave长期不响应HREADY持续拉低时master虽然可以尝试发起新地址但内部缓冲很快被未完成的transfer填满之后就只能把新地址憋在肚子里总线上的现象是地址和控制信号保持不变driver无法推进。验证平台里模拟这种极限场景很有价值它能暴露monitor在反压状态下重复采样、driver burst状态机提前退出等问题。我们后面在搭建driver和monitor时专门为这个场景写了回归用例这也是“不回respond但只能发八个包”这个说法的实战出处。3. 平台设计图纸UVM组件分配与连接方式3.1 从UVM树看整个验证平台动手写代码前我习惯先画一张组件树明确每个UVM组件放在哪一层、负责什么。整个验证平台的结构如下tb_top ├── uvm_test_topahb_sramc_base_test │ └── envahb_sramc_env │ ├── ahb_mst_agent │ │ ├── ahb_mst_sequencer │ │ └── ahb_mst_driver │ ├── ahb_monitorpassive agent │ ├── sramc_scoreboard │ └── sramc_covtb_top不是UVM组件它是仿真顶层负责生成时钟和复位、例化DUT和接口、在initial块里调用run_test。env持有agent、monitor和scoreboard负责它们之间的连接。这里的ahb_mst_agent是active agent里面放driver和sequencer负责发起激励ahb_monitor是passive的不做驱动只做采样观测。有一点要说明UVM里的agent可以通过is_active参数在active和passive之间切换。对这个项目来说写激励和采总线是两类动作我把它们分开成两个组件结构上更清晰也方便复用。如果以后要把这个AHB侧封装成通用VIP可以把monitor塞回agent内部通过is_active控制。3.2 哪些组件必须自己写哪些可以直接复用UVM框架本身提供了大量现成基类但每个项目的核心逻辑还是要自己写。transaction类必须自己设计AHB传输有哪些字段、哪些字段可随机化、哪些字段需要在对比时忽略这些设计决策会直接影响后续所有组件。driver必须自己写它是唯一知道AHB时序细节的组件代码质量决定了激励是否符合协议。monitor必须自己写它要准确识别一次完整传输的边界并还原transaction。scoreboard的参考模型更是项目定制逻辑写错一个字节mask规则整个比对就失去意义。可以直接复用的部分是sequencer直接用uvm_sequencer参数化即可例如uvm_sequencer#(ahb_trans)不需要额外派生类。sequence则是慢慢积累的过程第一版只需要几个基础sequence后面每加一种测试就多写一个最后形成序列库。env和test虽然要自己写但结构相对固定环境里例化组件、连接analysis porttest里配置虚接口、启动sequence模式化的东西比较多。3.3 为什么不用更复杂的“通用VIP式”架构刚开始我也犹豫要不要直接上完整的总线功能模型比如同时例化master agent和slave agent支持多个master竞争总线那种。后来想明白了AHB SRAMC验证场景本质上是一个单master单slave的闭环DUT就是AHB slave上面只需要一个master agent发激励不需要额外的slave agent去响应master因为DUT自己就是应答者。使用过重的架构会带来两个问题一是组件数量膨胀新手容易迷失在级联的agent和多个analysis port连接里二是多余的组件可能掩盖协议时序问题比如用现成VIP的slave model去帮DUT应答反而看不到DUT自身的HREADY行为。对练手项目来说保持组件少而精每个组件都能被理解比搭一套“看起来很专业”的庞然大物有价值得多。4. 从空目录到首个用例通过平台实现的骨干代码4.1 项目目录结构、filelist和Makefile我习惯把DUT、验证环境和仿真脚本分开目录放这样后期维护和回归都方便。目录结构如下ahb_sramc_sv/ ├── rtl/ │ └── ahb_sramc.sv ├── tb/ │ ├── ahb_if.sv │ ├── sram_model.sv │ └── tb_top.sv ├── src/ │ ├── pkg/ │ │ └── ahb_sramc_pkg.sv │ ├── seq_lib/ │ ├── env/ │ └── test/ ├── sim/ │ ├── filelist.f │ ├── Makefile │ └── logs/filelist.f里按顺序罗列文件编译时用vcs -sverilog -ntb_opts uvm -f filelist.f -timescale1ns/1ps -debug_accessall这条命令。常用选项的作用我解释一下-sverilog开启SystemVerilog支持-ntb_opts uvm让VCS加载UVM库-debug_accessall是为了后面用Verdi或QuestaSim的波形调试功能。Makefile里主要维护clean、compile、sim三个目标sim时通过UVM_TESTNAME指定要跑的测试用例。对Linux环境比较陌生的朋友注意一点UVM库路径不需要手动加到filelist里VCS的-ntb_opts uvm会自动包QuestaSim则是用vlog -sv -f filelist.f配合vsim UVM_TESTNAMExxx -c -do run -all来跑。第一次编译大概率会报一堆语法错不用慌多半是文件包含顺序问题把package文件排在其它文件前面就能解决。4.2 interface和transaction的先行定义写代码的第一步不是env而是interface和transaction这两个基础类型定了后面的组件才能围绕它们展开。ahb_if定义如下注意HREADY既是输入又是输出的情况interface ahb_if(input logic hclk, input logic hrstn); logic [31:0] haddr; logic [1:0] htrans; logic hwrite; logic [2:0] hsize; logic [2:0] hburst; logic [31:0] hwdata; logic [31:0] hrdata; logic hready; logic [1:0] hresp; logic hsel; endinterfacetransaction的设计要覆盖AHB传输的所有信息。我第一版设计少了hburst字段结果REGRESSION时INCR4和WRAP4根本没法区分后来补上才算完整。一个可用的transaction类如下class ahb_trans extends uvm_sequence_item; rand bit [31:0] addr; rand bit [1:0] htrans; rand bit hwrite; rand bit [2:0] hsize; rand bit [2:0] hburst; rand bit [31:0] wdata[]; bit [31:0] rdata[]; rand int burst_len; bit error_resp; uvm_object_utils_begin(ahb_trans) uvm_field_int(addr, UVM_ALL_ON) // 其它字段注册略 uvm_object_utils_end constraint c_size_valid { hsize inside {[0:2]}; } constraint c_burst_len { if (hburst inside {3,4,5}) // INCR4, WRAP4, INCR8... burst_len (1 (hburst[2:1])); // 简化的映射实际需要查表 } endclass这里我故意没把约束写完整因为HBURST编码的映射关系需要查协议表直接摆在代码里容易误导。transaction字段的随机化在UVM里通过uvm_field_*宏实现但要注意字段宏会影响打印和复制性能有损耗。序列级联的burst传输可以只发首地址和burst类型每个beat的地址由driver根据规则推进这样transaction可以设计成只描述“一次burst”而不是“一个beat”。4.3 driverAHB时序的核心建模driver是验证平台上最需要打磨的组件。它的职责清楚但细节多从sequencer取到transaction后把一个burst拆成多个beat逐个驱动到总线上。我第一版写成“一拍发一个beat”的简单逻辑后来加了HREADY反压和流水线处理代码量翻了一倍但才真正接近AHB时序。核心思路是维护一个地址和控制信号的“输出锁存”在地址阶段输出当前beat的地址在数据阶段根据HREADY决定是否推进到下一个beat。简化代码如下task ahb_mst_driver::run_phase(uvm_phase phase); ahb_trans req; forever begin seq_item_port.get_next_item(req); drive_burst(req); seq_item_port.item_done(); end endtask task ahb_mst_driver::drive_burst(ahb_trans req); int beat; for (beat 0; beat req.burst_len; beat) begin (posedge vif.hclk); if (!vif.hready) begin while (!vif.hready) (posedge vif.hclk); end // 驱动当前beat的地址和控制信号 vif.haddr req.addr beat * (1 req.hsize); vif.htrans (beat 0) ? NONSEQ : SEQ; vif.hwrite req.hwrite; // 写数据同样延迟一拍 if (req.hwrite beat 0) vif.hwdata req.wdata[beat]; end // 最后一个beat后还需处理数据阶段的收尾 endtask这个版本为了讲原理已经做了大幅简化真正的driver还要处理边界burst开始前必须等上一个传输的HREADY为高写数据与地址之间的相位差读传输时在数据阶段采样HRDATA存回transaction。你写代码时建议先在纸上画一条burst的完整波形标出每个时钟沿HADDR、HWDATA、HREADY的状态再对照着写驱动语句比上来就堆代码靠谱得多。4.4 monitor采样门控是重中之重monitor的工作看起来比driver简单就是观察总线、识别传输边界、打包成transaction发出去但实际写起来比预想麻烦。第一个版本我写出来的monitor在无反压的情况下工作正常一旦DUT插入wait state一个burst被采样成了好几个transactionscoreboard立刻连环报错。关键点在采样门控。AHB传输的“有效”不是看到HTRANS非IDLE就算而是要在HREADY为高、且地址阶段对应的那个时钟沿采样。换句话说一次data phase完成的标志是HREADY在这个时钟沿为高。于是monitor的采样逻辑用HREADY作为门控task ahb_monitor::run_phase(uvm_phase phase); forever begin (posedge vif.hclk); if (vif.hsel vif.hready) begin if (vif.htrans ! IDLE) begin trans_collected new(); trans_collected.addr vif.haddr; trans_collected.htrans vif.htrans; trans_collected.hwrite vif.hwrite; trans_collected.hsize vif.hsize; if (vif.hwrite) trans_collected.wdata[0] vif.hwdata; else trans_collected.rdata[0] vif.hrdata; item_collected_port.write(trans_collected); end end end endtaskhsel是AHB从设备选择信号在单slave场景下可以一直为高但保留它会让代码以后往多slave架构迁移时更省事。monitor每采到一个有效的transfer就通过analysis port写出去scoreboard和coverage模块都会订阅这个端口。4.5 scoreboard和参考模型影子SRAM的读写预测scoreboard是验证平台的“裁判”。它的结构分两半参考模型根据driver发出的激励预测DUT行为比对逻辑把预测结果和monitor采到的真实结果做比较。参考模型内部维护一份和DUT同样大小的影子存储通常是一个位宽32位、深度16K的数组64KB / 4B。写操作预测的逻辑是计算地址对应的word index再根据HSIZE和HADDR[1:0]决定要更新哪些字节。这里推荐写成独立的函数方便测试function void sramc_ref_model::write_access(ahb_trans t); bit [3:0] be; int word_idx; be get_byte_enable(t.addr, t.hsize); word_idx t.addr[15:2]; if (be[0]) shadow_mem[word_idx][7:0] t.wdata[0][7:0]; if (be[1]) shadow_mem[word_idx][15:8] t.wdata[0][15:8]; if (be[2]) shadow_mem[word_idx][23:16] t.wdata[0][23:16]; if (be[3]) shadow_mem[word_idx][31:24] t.wdata[0][31:24]; endfunctionget_byte_enable函数则是把HSIZE和地址低位翻译成字节写使能这个函数看似简单实际上最容易写错。HSIZE0时只有一个字节有效具体是第几个字节取决于HADDR[1:0]HSIZE1时低16位或高16位有效取决于HADDR[0]HSIZE2时全部有效。这块逻辑后面在第5节单独讲因为它在验证中给我带来了很长时间的困扰。读操作的预测更简单从影子存储读出来即可并且要处理DUT读wait state的问题monitor采到的读数据比地址晚两个周期scoreboard的FIFO里需要做延迟对齐或者直接在参考模型里也等两个周期。我的做法是参考模型不模拟延迟而是让比对逻辑在zls后等待读数据到达用transaction的ID或顺序进行匹配因为AHB对同一slave的响应顺序是保序的FIFO匹配足够。4.6 env、test和tb_top串起整个仿真有了上述基础组件env的作用就是把它们例化并连接起来。env需要完成三件事创建agent、monitor、scoreboard把monitor的analysis port分别连到scoreboard的expected fifo和coverage模块通过config_db把virtual interface分发给每个需要它的组件。代码不必贴完整关键连接逻辑如下function void ahb_sramc_env::connect_phase(uvm_phase phase); ahb_monitor.item_collected_port.connect(sramc_scoreboard.actual_imp); ahb_monitor.item_collected_port.connect(sramc_cov.cov_export); endfunctiontest_base的职责是配置UVM树在build_phase里读取virtual interface并传给env在run_phase里启动主sequence。tb_top里则例化DUT、SRAM行为模型、ahb_ifinitial块里设置时钟和UVM_TESTNAME。这里有个小技巧clock generator尽量放在tb_top的initial块里不要在interface内部用always生成时钟这样不同测试换频率时不用改interface代码。5. 跑通之前那几天我踩过的五个真正耽误时间的坑5.1 monitor在HREADY反压时重复采样这个坑几乎每个从无到有写AHB验证环境的人都会踩。当时我在sequence里构造了一个“slave长时间反压”的用例driver已经把burst头两个beat发出去然后HREADY被拉低过20个时钟才恢复。跑完一看scoreboard报了二十几条mismatch把所有transaction打印出来才发现monitor在HREADY为低的20个周期里每次都因为HTRANS还是SEQ而生成了一条transaction。原因就是我上面说的采样门控问题monitor判断“传输发生了”只用HTRANS没有用HREADY做握手确认。HREADY为低时数据阶段根本没完成总线还停在同一个transfer上HTRANS当然不会变但这不是一次新的传输。把HREADY加进采样条件之后这类重复采样立即消失。这也从侧面解释了“不回respond只能发8个包”这类极限场景的验证价值——只有在这种场景下流水线阻塞和采样门控的错误才会如此明显。5.2 WRAP burst地址回卷公式写错WRAP类burst的地址推进规则比INCR多一个回卷操作很容易写错。我第一版写的地址递增是简单的addr addr size_bytesWRAP4跑在0x0C地址时下一拍算成0x10但AHB协议要求回卷到0x00。结果scorebook和driver各算各的比对当然失败。正确的写法是引入一个wrap边界掩码。假设burst宽度是总字节数B地址掩码就是~(B-1)下一拍地址等于(addr ~(B-1)) ((addr size_bytes) (B-1))也就是基地址加偏移量并回卷。用这个公式后WRAP4、WRAP8都能正确工作。建议把地址推进逻辑封装成driver和scoreboard共享的函数写在package里避免两处各自实现导致不一致。5.3 HREADY输出和输入的概念混淆AHB协议里slave输出的是HREADYOUT总线上所有模块看到的是HREADY有时也叫HREADYIN二者之间可能经过仲裁或组合逻辑。单slave场景下两者可以直连但我在搭建多组件环境时一度把两者混用导致driver采样的HREADY始终为1DUT明明插入了wait state却完全观测不到。区分方法很简单在interface里把HREADYOUT作为单独的logic通过assign语句连接到总线HREADY在driver和monitor里一律采样总线上的HREADY因为这才是master观测到的握手信号。写RTL top连接时也注意分清DUT输出和总线信号别为了图省事把两个信号短接否则后续想在总线上插监控逻辑时会非常痛苦。5.4 字节写mask和未对齐访问的比对陷阱这是最隐蔽的一个坑。AHB协议允许未对齐访问于是当我的sequence随机化出HSIZE1且addr[0]1的写操作时DUT会把这个半字写到SRAM的高16位。我的参考模型第一版写的是“总是写低16位”结果DUT写的是高16位Scoreboard用低16位的数据去比对当然对不上。更麻烦的是这种错误报出来之后很容易让人误以为是DUT的问题。解决方法是把“字节使能生成”和“数据总线对齐”做成两件事字节使能决定SRAM的BE信号数据总线决定HWDATA的哪16位有效。参考模型里必须单独写一个跨字节lane的搬移逻辑不能用简单的if-else硬编码。写完这版逻辑后我又补了一组专门的未对齐访问用例才敢让随机约束里放开HSIZE和addr的组合。5.5 config_db路径类型不匹配和UVM_TESTNAME大小写这类问题不涉及协议纯粹是UVM使用习惯。第一版平台的interface传参我在tb_top里写的是uvm_config_db#(virtual ahb_if)::set(null, uvm_test_top.env.*, vif, vif)结果env里get不出来一度以为是UVM树路径写错了。后来发现是类型不匹配set时用了virtual ahb_if但get时用的类型参数写成了ahb_if少了virtual关键字UVM在类型不匹配时不会报错只是静默返回空指针。另外运行测试时UVM_TESTNAME指定的类名必须和uvm_component_utils注册的名字完全一致区分大小写空格也不能多。我见过有人写uvm_test_top.ahb_sramc_test想在命令行里指定完整路径的实际上UVM_TESTNAME只需要测试类的简短名字。6. 平台能跑之后下一步还能往哪走6.1 功能覆盖率应该怎么加基础用例跑通、scoreboard能稳定报对之后就该考虑覆盖率了。UVM功能覆盖率不是看代码覆盖率而是看协议场景有没有被覆盖到。对AHB SRAMC项目至少要覆盖这样几个维度工作方向读和写、传输宽度8位、16位、32位、burst类型SINGLE、INCR4、WRAP4、WRAP8等、地址边界0地址、块末地址、回卷边界。把这些维度定义成covergroup挂在monitor后面每个采到的transaction自动采样covergroup ahb_cov (posedge vif.hclk); cp_hwrite: coverpoint vif.hwrite; cp_hsize: coverpoint vif.hsize { bins b8{0}; bins b16{1}; bins b32{2}; } cp_hburst: coverpoint vif.hburst { bins single{0}; bins incr4{3}; bins wrap4{2}; bins incr8{5}; bins wrap8{4}; } cp_addr: coverpoint vif.haddr[15:2] { bins low {[0:3]}; bins high {[16381:16383]}; bins mid {[4:16380]}; } cross cp_hwrite, cp_hsize; endgroup功能覆盖率的价值在于告诉你“测了哪些”而不是“代码执行了哪些行”。比如代码覆盖率显示地址计算分支全跑过但可能所有地址都集中在0地址附近的低地址区间边界和回卷场景根本没测到。分析功能覆盖报告时看到哪个bin没覆盖到就去补对应的sequence约束这个过程比单纯堆测试用例更有方向感。6.2 寄存器模型RAL什么时候引入AHB SRAMC如果只是纯粹的存储器控制器可以在很长一段时间里不引入寄存器模型直接用sequence访问地址就可以了。但如果你的DUT版本带了控制寄存器比如使能位、地址重映射寄存器、中断状态寄存器这时候用UVM寄存器模型RAL来管理寄存器读写会省很多事。我建议在平台跑通基础读写用例之后再引入RAL否则容易把寄存器模型的调试和总线时序的调试混在一起。RAL的核心价值在于维护一份寄存器的镜像mirror value软件读之前可以用mirror值预测写之后自动更新desired值再通过predict操作把镜像值和DUT实际值同步。在SRAMC这类存储控制器里如果寄存器启用了某种memory保护模式RAL的镜像值还能参与scoreboard的行为预测这算是验证平台里的高级玩法了。6.3 agent封装复用与更复杂的总线升级当你的AHB验证平台稳定跑过一轮回归之后值得做的一个重构就是把AHB侧的driver、monitor、sequencer封装成可以参数化的agent让它在其它项目里也能直接被例化。封装要点是让transaction类型可参数化接口类型可配置agent的active/passive模式可切换。做完这个封装以后遇到AHB-to-APB桥、AHB DMA控制器就能直接复用这一整套AHB master侧逻辑只需要重新写scoreboard和sequence库。更进一步的学习路径是向AXI协议迁移。AXI比AHB多了独立的读写通道、outstanding乱序返回、burst长度可变等特性对monitor的采样逻辑和scoreboard的匹配算法都是更高难度的考验。但有了AHB平台这套骨架迁移过程中只需要替换协议相关部分UVM的框架结构和组件划分几乎不变。这也是为什么我一直推荐用AHB SRAMC作为UVM入门项目的原因——它的协议细节足够练手但又不至于把UVM本身的实现淹没在协议海洋里。搭完这个平台再回头看UVM里那些“八股”概念比如factory覆盖、phase机制、analysis port的push方式会明显感觉它们有了血肉。我自己的体会是学UVM最快的路径永远是亲手搭一个小而完整的项目在踩坑和调试中把每个机制用一遍。你把AHB SRAMC这个项目照着搭完可以再自己加一种burst类型、加一个ERROR响应注入的用例或者把SRAM容量改大跑一轮大的随机回归每一次改动都会逼着你把某个协议细节或UVM机制理解得更深。
返回列表