HDL硬件描述语言 | OOP面向对象编程 | |
Verilog | SystemVerilog | |
模块定义 | module | class |
模块实例 | instance | object |
模块名称 | instance name | handle |
数据类型 | registers & wires | properties: variables |
索引 | 通过层次化的索引来找到结构中的设计实例 | 通过句柄来索引对象的变量和方法 |
例化 |
是静态的, 在编译链接时完成 |
动态的, 在任意时间点发生,更加灵活和节省空间 |
执行代码 |
behavioral blocks (always, initial,) task, function |
Methods: task & function |
模块间通信 |
ports or cross-module task calls |
calls, event, mailboxes,semaphores |
特性 |
分开处理数据结构和算法; |
通过封装的方式对数据进行组织和管理 |
class Transaction; bit [31: 0] addr, crc, data[8]; //class properities function void display; //class method $display("Transaction: %h", addr) ; endfunction: display function void calc_crc: crc=addr^data. xor; endfunction: calc_crc endclass: Transaction
Transaction tr1, tr2; // 声明句柄tr1, tr2, 此时 tr1 = tr2 = null tr1=new(); // 创建对象 tr2 = tr1; // 此刻tr1和tr2指向同一个对象 tr1=new(); // 创建第二个对象,并且由tr1指向
释放句柄指向的对象的内存空间
// 如果一个变量需要被其他对象所共享,如果没有OPP,就需要创建全局变量,这样会污染全局名字空间,导致你想定义局部变量,但变量对每个人都是可见的。 // 类中static变量,将被这个类的所有实例(对象)所共享,使用范围仅限于这个类。当你打算创建一个全局变量的时候,首先考虑创建一个类的静态变量。 class transaction; Static int count=0; Int id; Endclass Trasaction tr1,tr2;
// Id不是静态变量,所以每个trasaction对象都有自己的id;count 是静态变量,所有对象只有一个count变量。
当类的每一个对象,都需要从同一个对象(另一个类)中获取信息的时候。如果定义成非静态句柄,则每个对象都会有一份copy,造成内存浪费。
transactio::count; // class::var tr1.count; // obj.var
// 因为在调用静态方法时,可能并没有创建具体的对象,也没有为动态成员变量开辟空间;因此在静态方法中使用类的动态成员变量时禁止的,可能会造成内存泄漏;但是静态方法可能使用类的静态变量,因为静态方法和静态变量一样在编译阶段就已经为其分配好内存空间
类的封装特性使得类可以根据需要来确定外部访问的权限级别,一般可以将变量声明为以下三种形式:
class Foo; int id; local int count; ... endclass Foo f1; f1=new(); f1.id; f1.count; // 报错 // 默认情况下,通过 handle.id 就可以访问实例的成员变量id,默认的变量申明都是public; // 成员变量 count 被声明成local类型,那么只有 class 内部方法可以访问count,其子类不能以继承的方式访问它,也不可以通过 f1.count 这种外部方式访问它,否则VCS工具会报错:Could not find member ‘num‘ in class ‘xxx‘。
类的继承特性使得子类可以使用父类的成员变量(variable)或方法(method),当我们在子类实例上调用某一个变量或者方法时,分两种情况说明:
在需要转换的表达式前加上单引号即可,该方式不会对转换值做检查;如果转换失败,无从得知
使用系统函数$cast( tgt, src)做转换
隐式转换
在编译阶段就可以确定下来调用方法所处作用域的方式称之为静态绑定
在调用方法时,会在运行时来确定句柄指向对象的类型,在动态指向应该调用的方法
class axi_sbx_item extends uvm_object; typedef axi_sbx_item this_type_t; this_type_t original_item; this_type_t split_item; function void split(); if(!$cast(this.split_item, this.clone())) `uvm_error("split", "cast failed!") ... this.split_item.original_item = this; endfunction endclass // this指axi_sbx_item类创建的对象; // 由于clone()是uvm的方法,uvm事先不知道axi_sbx_item的存在,因此this.clone()必定返回"指向子类对象的父类句柄" // 而this.split_item是用子类声明的句柄,执行$cast(this.split_item, this.clone())后,this.split_item就指向了clone出的对象;
class base_test; int data; int crc; virtual function base_test copy(); base_test t = new(); // 首先创建父类对象,由于在父类中实现,因此该对象类型为base_test; copy_data(t); return t; endfucntion virtual function void copy_data(base_test t); t.data = data; t.crc = crc; endfunction endclass class my_test extends base_test; int id; function base_test copy(); // 为了实现虚方法重载,子类方法/参数的名字和类型要与父类保持一致,因此被迫声明为base_test类型; my_test t = new(); // 首先创建子类对象,在创建子类对象时,会自动调用super.new()方法; copy_data(t); return t; endfucntion
function void copy_data(base_test t); // 为了实现虚方法重载,子类方法/参数的名字和类型要与父类保持一致,因此被迫声明为base_test类型; my_test h; super.copy_data(t); $cast(h, t); h.id = id; endfunction endclass module tb; my_test m_t1; my_test m_t2; initial begin m_t1 = new(); $cast(m_t2, m_t1.copy()); ...... end endmodule
// 相比于UVM中通过Field Automation提供了比较简单的对象复制,SV中需要我们人工定义copy()方法来实现对象复制,上面的例子就是SV中用于复制对象的代码; // 将成员复制函数copy_data()和新对象生成函数copy()分为两个方法,便于子类的继承和方法的复用; // 从自顶向下构建copy()/copy_data()方法的角度来看,首先我们在父类中定义copy()/copy_data()方法,实现父类新对象的生成以及父类成员变量的复制。但是为什么父类的copy()/copy_data()方法要声明为virtual呢
// 如果想在"指向子类对象的父类句柄"上调用copy()/copy_data()方法来复制对象,必须要将父类的copy()/copy_data()方法声明为virtual,否则子类的变量无法复制; // 一旦将父类的copy()/copy_data()方法声明成virtual,麻烦也就接踵而至,因为虚方法要求子类方法/参数的名字和类型必须与父类一致,因此子类方法的类型也必须声明为base_test;
// 那么子类在调用copy()方法时,返回一个"指向子类对象的父类句柄",必须通过$cast()方法实现句柄转换。
原文:https://www.cnblogs.com/gareth-yu/p/14502058.html