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返回数据 + 外设中断”的组合场景。

集成顺序建议:先连通时钟复位,再接入各sub-env的agent,然后挂接RAL,最后接入scoreboard与coverage。每接入一层做一次最小冒烟测试,避免问题叠加后难以定位。

三、顶层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实现,便于复用与调试。

芯片级UVM环境架构与virtual sequence调度 soc_env (顶层) soc_virtual_sequencer 持有各sub-env sqr句柄 soc_scoreboard 跨子系统一致性检查 soc_reg_model 嵌套RAL + adapter soc_coverage cross场景覆盖 cpu_sub_env cpu_sequencer / cpu_agent cpu_reg_block / cpu_sb ddr_sub_env ddr_sequencer / ddr_agent ddr_reg_block / ddr_sb periph_sub_env periph_sequencer / agent periph_reg_block / sb dma_burst_vseq (virtual sequence) 同步点:cfg完成 → 启动rd fork/join + 事件同步,不直接操作寄存器 句柄注册 句柄注册 句柄注册 virtual sequence调度路径 sub-env向顶层注册sequencer句柄 virtual sequence(仅调度,不产生transaction)

四、规划中的常见陷阱与判断依据

第一,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。

芯片级环境一旦集成完成,修改sub-env接口的代价很高。建议在集成前冻结各sub-env的sequencer句柄名、virtual interface名与reg block路径,并在顶层用`uvm_config_db`统一配置,避免后期因命名不一致导致大量返工。