2. 芯片级验证平台架构设计
模块级UVM环境通常围绕一个DUT、一个agent集合、一个scoreboard构建,结构紧凑、依赖清晰。当验证对象从单个IP升级为包含CPU子系统、总线互连、存储控制器、外设集群的SoC时,环境不能简单地把若干模块级环境“拼”在一起,而需要在架构层面重新划分层次:哪些组件复用、哪些组件重写、哪些组件上移为公共资源,以及顶层如何统一调度各子系统的激励。这一节围绕三个核心问题展开:扩展策略、多子系统集成方法、virtual sequencer与virtual sequence的规划。
一、从模块级到芯片级的三种扩展策略
实际工程中常见的扩展路径有三类,选择依据是子系统接口的稳定程度与复用价值。
- 垂直复用(Vertical Reuse):模块级env整体作为子环境(sub-env)实例化到芯片级env中,保留其agent、sequencer、scoreboard。适用于接口协议稳定、内部检查完备的IP,例如UART、SPI、I2C。代价是芯片级需要额外处理跨子系统的检查,模块级scoreboard只覆盖自身。
- 水平复用(Horizontal Reuse):把多个模块级env中重复的组件(如AXI agent、APB agent、寄存器模型适配器)抽取为公共agent库,芯片级env按需实例化。适用于同一协议在多个子系统重复出现的场景,可显著减少编译与维护成本。
- 重构扩展(Refactor):当模块级env的检查逻辑与芯片级不一致时,保留激励层、重写检查层。例如模块级只检查单端口时序,芯片级需要检查跨端口一致性、QoS、死锁,此时scoreboard与reference model必须重写。
选择策略的判断依据可以归纳为:接口协议是否一致、检查目标是否一致、时序约束是否一致。三者一致则垂直复用,仅协议一致则水平复用,检查目标变化则重构。
二、多子系统集成方法
芯片级env的典型结构是“顶层env + 若干sub-env + 公共资源层”。集成时需重点处理四类问题。
1. 接口与agent归属。每个子系统内部的agent归属该sub-env;跨子系统的互连(如NoC、crossbar)由顶层直接实例化agent,避免同一物理接口被两个sub-env重复驱动。例如CPU通过AXI连到DDR控制器,AXI slave agent应放在DDR sub-env中,CPU侧master agent放在CPU sub-env中,二者通过virtual sequence协调。
2. 时钟与复位域。芯片级通常存在多个时钟域(如core_clk 1.2GHz、ddr_clk 800MHz、periph_clk 50MHz)。集成时需在顶层统一生成时钟与复位,sub-env通过virtual interface获取,禁止各sub-env自行产生时钟,否则会出现相位与复位释放顺序不一致。
3. 寄存器模型(RAL)。芯片级寄存器模型通常由多个子系统的RAL拼接而成。推荐做法是每个sub-env维护自己的reg block,顶层通过uvm_reg_block嵌套组合,adapter与predictor在顶层统一配置,避免多个predictor竞争同一总线。
4. 检查与覆盖率。模块级scoreboard保留在sub-env内做局部检查;顶层新增系统级scoreboard,负责跨子系统数据一致性、端到端事务完整性。覆盖率方面,模块级covergroup保留,顶层新增cross覆盖率,例如“CPU发起DMA + DDR返回数据 + 外设中断”的组合场景。
三、顶层virtual sequencer与virtual sequence规划
芯片级激励的核心矛盾是:多个子系统的sequence需要按特定时序协同,但每个sequence只能在其所属sequencer上启动。virtual sequencer的作用是“持有句柄而不产生transaction”,它聚合各sub-env的sequencer句柄,供virtual sequence统一调度。
class soc_virtual_sequencer extends uvm_sequencer;
`uvm_component_utils(soc_virtual_sequencer)
cpu_sequencer cpu_sqr;
ddr_sequencer ddr_sqr;
periph_sequencer periph_sqr;
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
endclass
class dma_burst_vseq extends uvm_sequence;
`uvm_object_utils(dma_burst_vseq)
`uvm_declare_p_sequencer(soc_virtual_sequencer)
task body();
cpu_cfg_seq cfg = cpu_cfg_seq::type_id::create("cfg");
ddr_rd_seq rd = ddr_rd_seq::type_id::create("rd");
fork
cfg.start(p_sequencer.cpu_sqr);
rd.start(p_sequencer.ddr_sqr);
join
endtask
endclass
规划virtual sequence时需注意三点。第一,明确同步点:哪些操作必须严格先后,哪些可以并行。上例中CPU配置DMA寄存器必须先于DDR读事务,因此不能简单fork,而应在cfg完成后通过cfg.wait_for_sequence_state或事件同步再启动rd。第二,避免在virtual sequence中直接操作寄存器,应通过RAL的write/read接口,保证predictor同步更新。第三,virtual sequence应保持“薄”,只做调度与同步,具体激励内容仍由各子系统的sequence实现,便于复用与调试。
四、规划中的常见陷阱与判断依据
第一,virtual sequencer中误放driver或monitor,导致层次混乱。virtual sequencer只应持有sequencer句柄。第二,多个virtual sequence同时驱动同一sequencer,造成transaction交错。解决方式是在顶层为关键sequencer设置仲裁或使用uvm_sequencer的lock/grab机制。第三,跨时钟域同步仅靠#delay,仿真在不同seed下结果漂移。应使用基于事件的同步,例如等待DDR返回的monitor事件再触发下一阶段。
判断集成是否成功的可观察依据包括:编译无跨模块句柄空指针;冒烟测试中所有sub-env的agent均能正常收发;RAL前门访问与后门访问结果一致;系统级scoreboard在预期场景下无mismatch;覆盖率报告中cross覆盖点能被virtual sequence命中。若其中任一项不满足,应回到对应层次定位,而不是在顶层盲目加延时或改sequence。
`uvm_config_db`统一配置,避免后期因命名不一致导致大量返工。