工厂模式基础:设计模式中的工厂模式

聊到UVM的工厂机制,咱们得先搞清楚一个基础问题——设计模式里的工厂模式到底是怎么回事。说实话,我刚开始学UVM时,看到factory、override这些概念,第一反应是:这不就是C++里的多态吗?后来踩了几个坑才明白,事情没那么简单。

工厂模式,说白了就是把对象的创建和使用分离开。你想想看,如果代码里到处都是new操作,哪天想换个对象类型,不得改到吐血?工厂模式就是帮你管好"怎么造对象"这件事。

简单工厂模式

先说说最简单的。简单工厂模式,我习惯叫它"一个工厂管所有"。它就是一个类,专门负责创建各种对象。

核心思想:用一个静态方法,根据传入的参数决定创建哪种对象。

// 简单工厂示例
class SequenceFactory {
  static function Sequence create(string type);
    case type
      "read"  : return ReadSequence::type_id::create("read");
      "write" : return WriteSequence::type_id::create("write");
      "idle"  : return IdleSequence::type_id::create("idle");
    endcase
  endfunction
endclass

嗯,这里要注意:简单工厂虽然方便,但有个硬伤——每次加新类型都得改工厂代码。我在一个项目里就吃过这个亏,验证IP迭代了三个版本,工厂类改了五次,每次改完还得重新回归测试,烦得很。

避坑指南:我曾经在一个多协议验证环境中用简单工厂管理所有sequence,结果协议从3种扩展到7种时,工厂代码变得又臭又长。后来我果断重构,换成了工厂方法模式。

工厂方法模式

工厂方法模式就聪明多了。它把"创建对象"这个动作定义成虚方法,让子类去决定具体创建什么。

说白了就是:父类定规矩,子类去实现

// 工厂方法模式示例
virtual class BaseFactory {
  pure virtual function Sequence create_sequence();
endclass

class ReadFactory extends BaseFactory;
  function Sequence create_sequence();
    return ReadSequence::type_id::create("read_seq");
  endfunction
endclass

class WriteFactory extends BaseFactory;
  function Sequence create_sequence();
    return WriteSequence::type_id::create("write_seq");
  endfunction
endclass

这样做的好处很明显:加新类型不用改已有代码,直接加个子类就行。我在做AXI验证组件时就用这个模式,每个通道类型对应一个工厂子类,扩展起来特别清爽。

个人经验:工厂方法模式特别适合那些"类型稳定但数量会增长"的场景。比如验证环境中的agent类型,一开始可能只有master和slave,后面加monitor、scoreboard,用工厂方法就很舒服。

抽象工厂模式

抽象工厂模式,这是三个里面最"重"的一个。它用来创建一组相关的对象,而不是单个对象。

举个例子你就明白了:假设你要验证一个SoC芯片,里面有CPU子系统、GPU子系统、DMA子系统。每个子系统都有自己的sequence、monitor、scoreboard。抽象工厂就是帮你打包创建这一整套组件

// 抽象工厂模式示例
virtual class SubsystemFactory;
  pure virtual function Sequence create_sequence();
  pure virtual function Monitor create_monitor();
  pure virtual function Scoreboard create_scoreboard();
endclass

class CPUFactory extends SubsystemFactory;
  function Sequence create_sequence();
    return CPUSequence::type_id::create("cpu_seq");
  endfunction
  function Monitor create_monitor();
    return CPUMonitor::type_id::create("cpu_mon");
  endfunction
  function Scoreboard create_scoreboard();
    return CPUScoreboard::type_id::create("cpu_sb");
  endfunction
endclass

关键区别:抽象工厂保证你拿到的是一套"配套"的对象,不会出现CPU的sequence配GPU的monitor这种乌龙。

三种模式的对比

模式 创建粒度 扩展方式 适用场景
简单工厂 单个对象 修改工厂类 类型少且稳定
工厂方法 单个对象 新增子类 类型会增长
抽象工厂 一组对象 新增子类 产品族配套

UVM中的工厂模式

说到UVM,它的工厂机制其实融合了工厂方法和抽象工厂的思想。UVM的uvm_object_factory本质上是一个注册表驱动的工厂,你通过type_id::create创建对象时,它会在内部查找注册信息,然后调用对应的构造函数。

我个人觉得UVM工厂最妙的地方在于override机制。你想想看,在验证环境中,有时候想临时替换某个组件,比如把某个agent从active模式改成passive模式。用传统工厂模式你得改代码,但UVM工厂允许你在运行时动态替换

// UVM工厂的override示例
class test_base extends uvm_test;
  function void build_phase(uvm_phase phase);
    // 把所有的read_agent替换成read_agent_passive
    read_agent::type_id::set_type_override(
      read_agent_passive::get_type()
    );
  endfunction
endclass

注意:UVM工厂的override机制虽然强大,但滥用会导致调试困难。我曾经在一个项目里看到有人override了七八层,最后查bug时根本分不清哪个组件是哪个类型。我的建议是:override尽量用在test层,别在组件内部互相override

总结一下

三种工厂模式,说白了就是从简单到复杂,从固定到灵活。简单工厂适合小项目,工厂方法适合中等规模,抽象工厂适合大型系统。而UVM的工厂机制,则是站在这些模式肩膀上,加上了注册和override的能力。

嗯,下一章我们会深入UVM工厂的内部实现,看看type_id::create到底是怎么工作的。到时候你会发现,理解了这些基础模式,UVM的工厂机制其实一点都不神秘。