verilog 面向对象编程基础

本节汇总 verilog(SV)面向对象编程(OOP)的核心概念,面向 UVM 学习或工程使用的入门读者。

说明:本文代码片段相互独立,便于单独阅读与复制。

1. 动物类与对象

  • class 用来定义类,new() 用来创建对象。
  • 类变量保存“句柄”,对象实例分配在堆上。
flowchart TD
    H1["栈区 句柄 c1"] -->|指向| O1["堆区 对象 Cat#1"]
    H2["栈区 句柄 c2"] -->|指向| O2["堆区 对象 Cat#2"]
    O1 -->|动态类型| C["类 Cat"]
    O2 -->|动态类型| C
    C -->|继承自| B["类 Animal"]
    B --> M1["方法 eat"]
    C --> M2["重写 eat"]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Animal;
string name;
function new(string name = "Animal");
this.name = name;
endfunction
function void eat();
$display("%s eats", name);
endfunction
endclass

class Cat extends Animal;
function new(string name = "Cat");
super.new(name);
endfunction
function void eat();
$display("%s eats fish", name);
endfunction
endclass

Cat c1; // 句柄
Cat c2;

initial begin
c1 = new("Mimi");
c2 = new("Lulu");
c1.eat();
c2.eat();
end

2. 成员:属性与方法

  • 属性:类中的变量。
  • 方法:function 或 task,可访问类属性。
1
2
3
4
5
6
7
8
9
10
11
12
13
class Animal;
int age;
function void eat();
$display("Animal eats");
endfunction
endclass

class Cat extends Animal;
string color;
function void eat();
$display("Cat eats fish");
endfunction
endclass

3. 构造函数与 this

  • 构造函数名字固定为 new。
  • this 指向当前对象,避免名字冲突。
flowchart TD
    H["栈区 句柄 c"] -->|指向| O["堆区 对象 Cat"]
    O -->|动态类型| C["类 Cat"]
    C --> N["构造函数 new"]
    C --> F["字段 age"]
    O -->|this 指向自身| T["this"]
    T -->|写入 age| F
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Animal;
string name;
function new(string name = "Animal");
this.name = name;
endfunction
function void eat();
$display("%s eats", name);
endfunction
endclass

class Cat extends Animal;
int age;
function new(string name = "Cat", int age = 1);
super.new(name);
this.age = age;
endfunction
function void eat();
$display("%s eats fish", name);
endfunction
endclass

4. 继承与重写

  • 子类 extends 父类(Cat 继承 Animal)。
  • 可重写父类方法,必要时调用 super。
flowchart TD
    H["栈区 句柄 h: Animal"] -->|指向| O["堆区 对象: Cat"]
    O -->|动态类型| C["子类: Cat"]
    O -->|继承自| B["父类: Animal"]
    C --> M["子类方法: eat"]
    M -->|调用 super.eat| S["父类方法: eat"]
1
2
3
4
5
6
7
8
9
10
11
12
class Animal;
function void eat();
$display("Animal eats");
endfunction
endclass

class Cat extends Animal;
function void eat();
super.eat();
$display("Cat eats fish");
endfunction
endclass

5. 多态与虚方法(详细)

多态的核心是“父类句柄指向子类对象”,方法的实际执行版本由运行时对象类型决定。

5.x 父类句柄指向子类实例对象分析

核心要点

  • 句柄类型决定“可访问的接口范围”,对象真实类型决定“实际执行的实现”。
  • 只有父类方法声明为 virtual,才能在子类中动态绑定。
  • 父类句柄无法直接访问子类新增成员(除非进行类型转换)。

典型示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Animal;
string name;
virtual function void eat();
$display("%s eats", name);
endfunction
endclass

class Cat extends Animal;
string toy;
function void eat();
$display("%s eats fish", name);
endfunction
endclass

Animal h; // 父动物类句柄
initial begin
h = new Cat();
h.name = "Mimi";
// h.toy = "ball"; // 编译错误: 父动物类句柄不可见子猫类字段
h.eat(); // 动态绑定,执行 Cat.eat()
end

必要时的类型转换

1
2
3
4
Cat m;
if ($cast(m, h)) begin
m.toy = "ball"; // 转换成功后访问子类字段
end
flowchart TD
    H["父类句柄 h: Animal"] -->|$cast| M["子类句柄 m: Cat"]
    H -->|失败| F["转换失败"]
    M -->|成功| U["可访问 Cat 专有成员"]

$cast 转换示例(Dog)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Animal;
string name;
function new(string name = "Generic");
this.name = name;
endfunction
function void eat();
$display("%s eats", name);
endfunction
endclass

class Dog extends Animal;
function void fetch();
$display("Dog fetches");
endfunction
endclass

Animal animal = new("Generic");
Dog dog;

// 危险!可能运行时错误
// dog = animal; // 编译错误

// 正确做法
if ($cast(dog, animal)) begin
dog.fetch();
end else begin
$display("Not a Dog instance");
end
flowchart TD
    H["栈区 父动物类句柄 Animal"] -->|指向| O["堆区 对象 Cat"]
    O -->|动态类型| C["子类 Cat"]
    H -->|调用 eat| R["运行时绑定"]
    R --> E["执行 Cat.eat"]
flowchart LR
    H1["栈区 父动物类句柄 a1"] -->|指向| O1["堆区 对象 Cat"]
    H2["栈区 子猫类句柄 c1"] -->|指向| O1
flowchart LR
    H1["栈区 子猫类句柄 c"] -->|new| O1["堆区 对象 Cat"]
    H2["栈区 父动物类句柄 a"] -->|赋值指向| O1
    H3["栈区 父动物类句柄 b"] -->|new| O2["堆区 对象 Animal"]
    O1 --> R1["a.eat 触发子猫类 eat"]
    O2 --> R2["b.eat 触发父动物类 eat"]

5.1 基本多态示例

  • 父类方法需要 virtual,才能在子类中实现动态绑定。
  • 子类方法签名必须一致(参数、返回值匹配)。
1
2
3
4
5
Animal a;
initial begin
a = new Cat();
a.eat(); // 动态绑定,执行 Cat.eat()
end

5.2 多态调用链与 super

  • 子类可以调用 super 保留父类行为,再追加特性。
1
2
3
4
5
6
7
8
9
10
11
12
class Animal;
virtual function void eat();
$display("Animal starts eating");
endfunction
endclass

class Cat extends Animal;
function void eat();
super.eat();
$display("Cat eats fish");
endfunction
endclass

5.3 多态数组:统一管理不同子类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Animal;
virtual function void eat();
$display("Animal eats");
endfunction
endclass

class Cat extends Animal;
function void eat();
$display("Cat eats fish");
endfunction
endclass

class Kitten extends Cat;
function void eat();
$display("Kitten eats small fish");
endfunction
endclass

Animal zoo[$];
initial begin
zoo.push_back(new Cat());
zoo.push_back(new Kitten());
foreach (zoo[i]) begin
zoo[i].eat(); // 根据实际对象类型执行
end
end

5.4 动态绑定 vs 静态绑定

  • 动态绑定:virtual + 父类句柄,运行时根据对象真实类型选择实现。
  • 静态绑定:子类句柄直接调用,编译期就确定目标实现。

用途与场景

  • 动态绑定适合“统一接口 + 不同实现”的场景,例如 UVM 中 driver/monitor 的基类句柄统一调度不同协议实现。
  • 静态绑定适合“已知具体类型”的场景,例如调试/性能路径中直接调用子类方法,逻辑清晰、开销更低。

对比示例

flowchart LR
    D1["动态绑定: 父动物类句柄 a"] -->|指向| O1["堆区 对象 Cat"]
    D1 -->|调用 eat| R1["运行时绑定"]
    R1 --> E1["执行 Cat.eat"]
    S1["静态绑定: 子猫类句柄 c"] -->|指向| O2["堆区 对象 Cat"]
    S1 -->|调用 eat| E2["编译期确定: Cat.eat"]
1
2
3
4
5
6
7
8
9
10
11
12
Animal a;
Cat c;

initial begin
// 动态绑定:父类句柄指向子类对象
a = new Cat();
a.eat(); // 运行时决定,实际调用 Cat.eat()

// 静态绑定:子类句柄直接调用
c = new();
c.eat(); // 编译期确定调用 Cat.eat()
end

5.5 多态常见注意点

  • 父类方法未声明 virtual 时,子类同名方法不会多态触发。
  • 方法签名不一致会导致“隐藏”而非“重写”。
  • 多态配合抽象类与纯虚方法更清晰地约束接口。

5.6 父类方法非 virtual 的情况

现象

  • 父类方法不是 virtual 时,父类句柄调用会固定到父类实现。
  • 子类同名方法只是“隐藏”,不会触发多态。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Animal;
function void eat();
$display("Animal eats");
endfunction
endclass

class Cat extends Animal;
function void eat();
$display("Cat eats fish");
endfunction
endclass

Animal a;
initial begin
a = new Cat();
a.eat(); // 仍然执行 Animal.eat()
end

如何实现多态

  • 把父类方法声明为 virtual。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Animal;
virtual function void eat();
$display("Animal eats");
endfunction
endclass

class Cat extends Animal;
function void eat();
$display("Cat eats fish");
endfunction
endclass

Animal a;
initial begin
a = new Cat();
a.eat(); // 动态绑定,执行 Cat.eat()
end

6. 抽象类与纯虚方法

  • 抽象类不能直接 new。
  • 纯虚方法必须由子类实现。
1
2
3
4
5
6
7
8
9
virtual class AnimalBase;
pure virtual function void eat();
endclass

class Cat extends AnimalBase;
function void eat();
$display("Cat eats fish");
endfunction
endclass

7. 访问控制:local / protected

  • local:仅类内部可见(子类也不可见)。
  • protected:类内部与子类可见。
1
2
3
4
5
6
7
8
9
10
11
12
13
class Animal;
local int secret;
protected int age;
function void eat();
$display("Animal eats");
endfunction
endclass

class Cat extends Animal;
function void eat();
$display("Cat eats fish");
endfunction
endclass

8. 句柄赋值与对象拷贝

  • 句柄赋值只复制“指针”,不是对象内容。
  • 需要深拷贝时自定义 copy()。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Animal;
string name;
function void eat();
$display("%s eats", name);
endfunction
endclass

class Cat extends Animal;
string color;
function Cat copy();
Cat c = new();
c.name = this.name;
c.color = this.color;
return c;
endfunction
function void eat();
$display("%s eats fish", name);
endfunction
endclass

9. 参数化类

1
2
3
4
5
6
class CatCage #(type T = Cat);
T q[$];
function void push(T v);
q.push_back(v);
endfunction
endclass