哈,这问题我也被问过。除了UVM那些,我觉得最实在的是“面向接口编程”和“数据驱动的测试”。
别把DUT的信号名直接写死在driver和monitor里。我们做法是,用SystemVerilog的interface封装DUT的所有物理接口,然后在验证环境里,通过virtual interface来引用。更关键的一步是,在interface之上再抽象一层“事务层接口”。比如,AXI总线,我们定义一个uvm_axi_transaction类,里面是地址、数据、burst类型等事务级信息。driver和monitor只和这个事务类打交道,不和具体的信号位宽、时序直接绑定。这样,即使下一个项目的AXI数据宽度从32变到128,你只需要改transaction类里的数据和适配器(adapter),驱动和监测的核心算法不用动。
实践经验上,一定要建一个“验证组件库”。把常用的验证IP(VIP),比如UART、I2C、DDR控制器模型,还有通用的记分板、覆盖率收集器,都做成参数化、可配置的包。新项目就像搭积木,从库里调。维护这个库要花时间,但长期看省下的重复劳动太多了。
另外,可扩展性离不开好的配置系统。UVM的config_db是基础,我们会在顶层用一个中心化的配置类(继承uvm_object),把所有可调参数,比如时钟频率、地址映射、测试模式,都放在里面。这个配置对象通过config_db传给所有组件。组件内部用get_config()获取。当需要加新参数时,只在中心配置类里加一个字段,然后更新获取它的组件就行,不用满天飞地改config_db的set调用。
最后提醒个坑:别为了复用而过度设计。先确保当前项目环境稳定好用,再抽象出通用的部分。一开始就想着做万能环境,容易复杂到没法用。