目录
8.1 继承
事务基
扩展
更多OOP术语
扩展类中的构造函数
driver class
generator class
8.2 蓝图(blueprint)模式
environment class
一个简单的testbench
用扩展transaction class
用扩展类改变随机约束
8.3 downcasting and virtual method
用$cast downcasting
virtual method
签名(signature)
8.4 合成、继承和其他替代方法
合成与继承(composition and inheritance)
composition的问题
inheritacne的问题
现实的选择
8.5 复制object
copy_data
为copy指定地址
8.6 抽象类和纯虚方法
8.7 callback
创建callback
用callback注入干扰
scoreboard
用callback连接scoreboard
用callback debug事务处理器
8.8 参数化的类
stack
关于参数化类的建议
8.1 继承
事务基
事务基类包含变量和子程序,变量包含源地址,目的地址,8个数据字,用于检查错误的CRC,子程序包括计算CRC,输出内容。
virtual的作用后面再讲
Sample 8.1 Base Transaction class
class Transaction;
rand bit [31:0] src, dst, data[8]; // Random variables
bit [31:0] crc; // Calculated variable
virtual function void calc_crc;
crc = src ^ dst ^ data.xor;
endfunction
virtual function void display(input string prefix="");
$display("%sTr: src=%h, dst=%h, crc=%h",
prefix, src, dst, crc);
endfunction
endclass
扩展
上面我们给出了能够生成正常事务的事务基,现在我们想要在它的基础上扩展为能够注入错误的bad类。
下面是扩展后的结果。virtual的作用是让一个新类能够在它的基础上进行扩展。
super用于调用下一层的函数。但是super.super这类多层调用是非法的。
注意,BadTr作为扩展可以看到Tr的所有变量,所以在使用crc的时候没有加前缀。
Sample 8.2 Extended Transaction class
class BadTr extends Transaction;
rand bit bad_crc;
virtual function void calc_crc;
super.calc_crc(); // Compute good CRC
if (bad_crc) crc = ~crc; // Corrupt the CRC bits
endfunction
virtual function void display(input string prefix="");
$write("%sBadTr: bad_crc=%b, ", prefix, bad_crc);
super.display();
endfunction
endclass : BadTr

可以经常把函数命名为virtual特性方便扩展,除了new函数都可以这么做。
更多OOP术语
variable叫property;task, function叫method;做扩展的时候,被扩展的基类叫parent/ super class,扩展出来的叫derived class / subclass;prototype是指明参数列表和返回类型的第一行。
扩展类中的构造函数
如果parent class中有构造函数构造了其中的某些变量,在subclass中必须先调用parent的new
Sample 8.3 Constructor with argument in an extended class
class Base1;
int var;
function new(input int var); // Has argument
this.var = var;
endfunction
endclass
class Extended extends Base1;
function new(input int var); // Needs argument
super.new(var); // Must be first line of new
// Other constructor actions
endfunction
endclass
driver class
接收generator产生的事务并发给DUT
Sample 8.4 Driver class
class Driver;
mailbox gen2drv;
function new(input mailbox gen2drv);
this.gen2drv = gen2drv;
endfunction
task main;
Transaction tr; // Handle to a Transaction object or
// an class derived from Transaction
forever begin
gen2drv.get(tr); // Get transaction from generator
tr.calc_crc(); // Process the transation
@ifc.cb.src = tr.src; // Send transaction
…
end
endtask
endclass
如果一个handle能够指向parent class,那么它也能指向subclass。
如果parent class和subclass中都有名字相同的函数,编译器怎么知道调用哪个?如果handle指向subclass,就调用subclass里的;如果指向parent class,就调用parent class里的。
generator class
Sample 8.5 Generator class
// Generator class that uses Transaction objects
// First attempt… too limited
class Generator;
mailbox gen2drv;
Transaction tr;
function new(input mailbox gen2drv);
this.gen2drv = gen2drv; // this-> class-level var
endfunction
task run();
forever begin
tr = new(); // Construct transaction
assert(tr.randomize()); // Randomize it
gen2drv.put(tr); // Send to driver
end
endtask
endclass
对于这种generator,有一个问题是handle被例化完马上就随机化,没机会指向subclass。这个问题会在后面解决。
数据向的class比如网络、总线事务都会有类似的property和method。
控制向的class比如generator和driver有相同的结构,可以把它们都命名为transaction的subclass,加一些virtual run和wrap_up method进去。
8.2 蓝图(blueprint)模式
如果我们把generator想象成一个印花机,根据模具的形状引出对应形状的物品然后发送出去,那么我们可以通过改变模具的形状来实现输出不同的物品。


这种方法的特点就是需要把declaration和construction分开。
Sample 8.6 Generator class using blueprint pattern
class Generator;
mailbox gen2drv;
Transaction blueprint;
function new(input mailbox gen2drv);
this.gen2drv = gen2drv;
blueprint = new();
endfunction
task run();
Transaction tr;
forever begin
assert(blueprint.randomize);
tr = blueprint.copy();
gen2drv.put(tr);
end
endtask
endclass
tr就是上图中的副本copy,blueprint就是输入的模具,通过改变blueprint就能实现输出subclass。
.copy函数返回的是handle,因此不会出现多个handle指向一个object。
environment class
Sample 8.7 Environment class
// Testbench environment class
class Environment;
Generator gen;
Driver drv;
mailbox gen2drv;
function void build(); // Build the environment by
gen2drv = new(); // constructing the mailbox,
gen = new(gen2drv); // generator,
drv = new(gen2drv); // and driver
endfunction
task run();
fork
gen.run();
drv.run();
join_none
endtask
task wrap_up();
// Empty for now – call scoreboard for report
endtask
endclass
一个简单的testbench
Sample 8.8 Simple test program using environment defaults
program automatic test;
Environment env;
initial begin
env = new(); // Construct the environment
env.build(); // Build testbench objects
env.run(); // Run the test
env.wrap_up; // Clean up afterwards
end
endprogram
用扩展transaction class
下面的begin…end是为了限制badtr的作用范围
Sample 8.9 Injecting an extended transaction into testbench
program automatic test;
Environment env;
initial begin
env = new();
env.build(); // Construct generator, etc.
begin
BadTr bad = new(); // Replace blueprint with
env.gen.blueprint = bad; // the "bad" one
end
env.run();
env.wrap_up();
end
endprogram
用扩展类改变随机约束
下面的代码在扩展类中加了约束。注意,如果扩展类中约束的名字和parent class相同,那么会发生替代行为。
Sample 8.10 Using inheritance to add a constraint
class Nearby extends Transaction;
constraint c_nearby {
dst inside {[src-100:src+100]};
}
endclass
program automatic test;
Environment env;
initial begin
env = new();
env.build(); // Construct generator, etc.
begin
Nearby nb = new(); // Create a new blueprint
env.gen.blueprint = nb; // Replace the blueprint
end
env.run();
env.wrap_up();
end
endprogram
8.3 downcasting and virtual method
用$cast downcasting
这里举了两类做例子,一个是base class transaction,一个是extended class badtr。前者声明了一个handle tr,后者声明了两个handle bad bad2。
下面的行为是合法的,base handle可以指向extend class
Sample 8.12 Copying extended handle to base handle
Transaction tr;
BadTr bad;
bad = new(); // Construct BadTr extended object
tr = bad; // Base handle points to extended obj
$display(tr.src); // Display base variable
tr.display; // Calls BadTr::display
下面的行为是非法的,extend handle不能指向base class,因为它会找不到extend中额外定义的变量
Sample 8.13 Copying a base handle to an extended handle
tr = new(); // Construct base object
bad = tr; // ERROR: WILL NOT COMPILE
$display(bad.bad_crc); // bad_crc is not in base object
但下面的行为是合法的,上一种非法的原因不在handle本身,而在于object,如果base handle本身指向extend class,那么这种赋值就合法了。
Sample 8.14 Using $cast to copy handles
bad = new(); // Construct BadTr extended object
tr = bad; // Base handle points to extended obj
// Check the object type & copy. Simulation error if mismatch
// If successful, bad2 points to the object referenced by tr
$cast(bad2, tr);
// Check for type mismatch, no simulation error
if(!$cast(bad2, tr))
$display("cannot assign tr to bad2");
$display(bad2.bad_crc); // bad_crc exists in original obj
$cast会把tr的object赋值给bad2,同时检查是否合理,如果不合理会返回0。
virtual method
仍然用之前说的两个class,下面是对virtual method的调用
Sample 8.16 Calling class methods
Transaction tr;
BadTr bad;
initial begin
tr = new();
tr.calc_crc(); // Calls Transaction::calc_crc
bad = new();
bad.calc_crc(); // Calls BadTr::calc_crc
tr = bad; // Base handle points to ext obj
tr.calc_crc(); // Calls BadTr::calc_crc
end
如果一个method被标记为virtual,当调用的时候,sv会根据object的type去调用;但如果没有被标记为virtual,那就会根据handle的type去调用,比如最后以一个tr调用的仍然会是Transaction::calc_crc
OOP中多个子程序叫相同名字的属于叫做“多态(polymorphism)”
签名(signature)
使用virtual method的问题是,所有extend class都必须有相同的signature,具体表现为它们的参数必须相同,不能加也不能减。
8.4 合成、继承和其他替代方法
合成与继承(composition and inheritance)
composition的精髓在于“有”,比如一个packet有header和body。
inheritance的精髓在于“是”,比如badtr是transaction,只不过多了些信息。
下面是它们之间的对比

1.是否有多个小class要连接成大class?如果是的话,只能用composition,因为inheritance不支持从多个class继承
2.badtr和transaction是一个层次的
3.对于inheritance,低层次的信息,比如src,dst都需要存在
4.对于generator来说,它的功能是发送transaction,如果badtr是composite transaction,那么generator本身是无法识别这种新东西的,就需要改代码;但如果它们是inheritance的关系,generator就可以把badtr当成transaction一样发送。
composition的问题
如果你这么写一个class,为了方便理解故意把transaction拆分为小的composition
Sample 8.17 Building an Ethernet frame with composition
// Not recommended
class EthMacFrame;
typedef enum {II, IEEE} kind_e;
rand kind_e kind;
rand bit [47:0] da, sa;
rand bit [15:0] len;
…
rand Vlan vlan_h;
endclass
class Vlan;
rand bit [15:0] vlan;
endclass
1.当你想访问vlan,需要写eth_h.vlan_h.vlan,如果层次更多,名字会变得更长更复杂
2.kind可能会被随机化为II或者IEEE,如果是II,那就不需要vlan,如果是IEEE,那就需要vlan。而随机化的时候不知道kind会是什么,所以在new()函数中,无论如何都需要随机化vlan,防止报错。既然需要无条件随机化vlan,那把它额外分一类就没意义了。
所以工程上不太推荐使用这种层次化,调用起来麻烦,更喜欢扁平化,就把各种变量直接写进去就行,实在不行再用inheritance,它在调用上也是一种扁平化。
inheritacne的问题
Sample 8.18 Building an Ethernet frame with inheritance
// Not recommended
class EthMacFrame;
typedef enum {II, IEEE} kind_e;
rand kind_e kind;
rand bit [47:0] da, sa;
rand bit [15:0] len;
…
endclass
class Vlan extends EthMacFrame;
rand bit [15:0] vlan;
endclass
1.每当你尝试给extend handle赋base handle的时候,都要用$cast检查
2.创建virtual method是复杂的,因为参数的个数不能改,如果想改需要从base class修改
3.randomization有困难,在base class中不能约束vlan
现实的选择
实际上我们需要有所取舍,composition带来层次化问题,inheritance带来设计困难的问题,一个简单的办法是把所有内容都写在一个class里
Sample 8.19 Building a flat Ethernet frame
class eth_mac_frame;
typedef enum {II, IEEE} kind_e;
rand kind_e kind;
rand bit [47:0] da, sa;
rand bit [15:0] len, vlan;
…
constraint eth_mac_frame_II {
if (kind == II) {
data.size() inside {[46:1500]};
len == data.size();
}}
constraint eth_mac_frame_ieee {
if (kind == IEEE) {
data.size() inside {[46:1500]};
len < 1522;
}}
endclass
8.5 复制object
前面在blueprint部分中我们看到了copy函数,本节详细讲一下。copy是要把class中产生的新数据copy到内存中。
Sample 8.20 Base transaction class with a virtual copy function
class Transaction;
rand bit [31:0] src, dst, data[8]; // Variables
bit [31:0] crc;
virtual function Transaction copy();
copy = new();
copy.src = src; // Copy data fields
copy.dst = dst;
copy.data = data;
copy.crc = crc;
endfunction
endclass
subclass的copy函数返回的object类型仍然是transaction
Sample 8.21 Extended transaction class with virtual copy method
class BadTr extends Transaction;
rand bit bad_crc;
virtual function Transaction copy();
BadTr bad;
bad = new();
bad.src = src; // Copy data fields
bad.dst = dst;
bad.data = data;
bad.crc = crc;
bad.bad_crc = bad_crc;
return bad;
endfunction
endclass : BadTr
copy_data
解决方法之一就是把copy拆分为两个函数,一个是创建并copy,另一个是copy_data。
下面是base class的copy_data
Sample 8.22 Base transaction class with copy_data function
class Transaction;
rand bit [31:0] src, dst, data[8]; // Variables
bit [31:0] crc;
virtual function void copy_data(input Transaction tr);
tr.src = src; // Copy the data fields
tr.dst = dst;
tr.data = data;
tr.crc = crc;
endfunction
virtual function Transaction copy();
copy = new();
copy_data(copy);
endfunction
endclass
下面是subclass的copy_data,要先把它base class的argument都copy了,再copy它作为extend class多出来的部分。
Sample 8.23 Extended transaction class with copy_data function
class BadTr extends Transaction;
rand bit bad_crc;
virtual function void copy_data(input Transaction tr);
BadTr bad;
super.copy_data(tr); // Copy base data
$cast(bad, tr); // Cast base handle to extÕd
bad.bad_crc = bad_crc; // Copy extended data
endfunction
virtual function Transaction copy();
BadTr bad;
bad = new(); // Construct BadTr
copy_data(bad); // Copy data fields
return bad;
endfunction
endclass : BadTr
为copy指定地址
前面在copy的时候,都需要额外开辟一块地址,能不能节约一点内存?可以直接为copy指定它的地址
Sample 8.24 Base transaction class with copy function
class Transaction;
virtual function Transaction copy(Transaction to=null);
if (to == null)
copy = new();// Construct new object
else
copy = to; // or use exisiting
copy_data(copy);
endfunction
// Uses copy_data() method from Sample 8.22
endclass
Sample 8.25 Extended transaction class with new copy function
class BadTr;
virtual function Transaction copy(Transaction to=null);
BadTr bad;
if (to == null)
bad = new(); // Create a new object
else
assert($cast(bad, to)); // Reuse existing one
copy_data(bad); // Copy data fields
return bad;
endfunction
endclass : BadTr
8.6 抽象类和纯虚方法
抽象类和纯虚方法更像是提供了一种“接口”,作为base class,它指出所有extend class需要有什么类型,什么函数。
Sample 8.26 Abstract class with pure virtual methods
virtual class BaseTr;
static int count; // Number of instance created
int id; // Unique transaction id
function new();
id = count++; // Give each object a unique ID
endfunction
pure virtual function bit compare(input BaseTr to);
pure virtual function BaseTr copy(input BaseTr to=null);
pure virtual function void display(input string prefix="");
endclass : BaseTr
所有想基于basetr的subclass都必须实现其中指出的函数,否则就会无法实例化
8.7 callback
我们想写一个能尽可能通用的测试平台,那么就用callback。它的主要思想是:driver的run流程是固定的,即取事务->pre_tx->发送->post_tx,这个序列在编译或仿真开始前就已确定,不会因测试用例不同而改变。两个tx中具体执行什么代码不由 Driver 决定,而由测试用例在运行时动态注入。

可以将run写作virtual method,但问题是当你想增加新行为时,必须在new method中重复源码。而且一旦对base class做了修改,会对所有的subclass都有影响。
创建callback
一个callback task是在顶层的test中创建的,在driver中调用。但driver并不依赖于测试代码,而是依赖于一个abstract class。测试代码通过extend abstract class来写入它想写的东西。
注意,pre_tx和post_tx并没有写作pure virtual,这是因为有的测试可能只用其中一个,如果写作pure还必须要实现另一个。
Sample 8.29 Base callback class
virtual class Driver_cbs; // Driver callbacks
virtual task pre_tx(ref Transaction tr, ref bit drop);
// By default, callback does nothing
endtask
virtual task post_tx(ref Transaction tr);
// By default, callback does nothing
endtask
endclass
Sample 8.30 Driver class with callbacks
class Driver;
Driver_cbs cbs[$];
task run();
bit drop;
Transaction tr;
forever begin
drop = 0;
agt2drv.get(tr);
foreach (cbs[i]) cbs[i].pre_tx(tr, drop);
if (!drop) continue;
transmit(tr);
foreach (cbs[i]) cbs[i].post_tx(tr);
end
endtask
endclass
用callback注入干扰
我们通常用callback注入错误、延时等。
$urandom_range(0,99)是在0-99之间随机产生一个数。然后去和0比较,如果相等,drop=1,表示当前的这个事务要丢弃。
在program中,通过initialize dcd,给dcd创建好object。push_back放到前面代码中写的cbs序列中,push_back表示放到队尾,也可以用push_front,放到最前面。
在启动run的时候,会随机出drop值,这样就知道当前这个transaction是否丢弃。
Sample 8.31 Test using a callback for error injection
class Driver_cbs_drop extends Driver_cbs;
virtual task pre_tx(ref Transaction tr, ref bit drop);
// Randomly drop 1 out of every 100 transactions
drop = ($urandom_range(0,99) == 0);
endtask
endclass
program automatic test;
Environment env;
initial begin
env = new();
env.gen_cfg();
env.build();
begin // Create error injection callback
Driver_cbs_drop dcd = new();
env.drv.cbs.push_back(dcd); // Put into driver's Queue
end
env.run();
env.wrap_up();
end
endprogram
scoreboard
scoreboard的设计取决于DUT,根据DUT处理事务的不同,scoreboard需要取出不同的值进行对比。
下面就是一个scoreboard。save_expect保存期望事务,这是我们预期DUT可能会输出的内容;compare_actual将DUT输出和期望事务对比,结果可能是0/1/n match,1 match是理想情况。
Sample 8.32 Simple scoreboard for atomic transactions
class Scoreboard;
Transaction scb[$]; // Store expected trans in queue
function void save_expect(input Transaction tr);
scb.push_back(tr);
endfunction
function void compare_actual(input Transaction tr);
int q[$];
q = scb.find_index(x) with (x.src == tr.src);
case (q.size())
0: $display("No match found");
1: scb.delete(q[0]);
default:
$display("Error, multiple matches found!");
endcase
endfunction : compare_actual
endclass : Scoreboard
用callback连接scoreboard
扩展类Driver_cbs_scoreboard首先构建了scoreboard,然后写了用于scoreboard的callback,把传到DUT的事务放到scoreboard的预期事务序列中。
这里没有展现出monitor的一方
Sample 8.33 Test using callback for scoreboard
class Driver_cbs_scoreboard extends Driver_cbs;
Scoreboard scb;
virtual task pre_tx(ref Transaction tr, ref bit drop);
// Put transaction in the scoreboard
scb.save_expected(tr);
endtask
function new(input Scoreboard scb);
this.scb = scb;
endfunction
endclass
program automatic test;
Environment env;
initial begin
env = new();
env.gen_cfg();
env.build();
begin // Create scoreboard callback
Driver_cbs_scoreboard dcs = new(env.scb);
env.drv.cbs.push_back(dcs); // Put into driverÕs Q
end
env.run();
env.wrap_up();
end
endprogram
用callback debug事务处理器
你可以通过在callback中加代码来显示事务,如果有多个事务,可以打印出层次,还可以通过把代码放在其他callback之前/之后来定位问题在哪。
注意不要修改其他环境。
8.8 参数化的类
stack
下面是一个有push和pop的栈结构。
local标记标志这个成员只能用本class内部的method访问,对它的subclass和外部都不可见。
Sample 8.34 Stack using the int type
class IntStack;
local int stack[100]; // Holds data values
local int top;
function void push(input int i); // Push value on top
stack[++top] = i;
endfunction : push
function int pop(); // Remove value from top
return stack[top–];
endfunction
endclass : IntStack
如果你想定义一个能存储指定类型的栈,这就是参数化的类
Sample 8.35 Parameterized class for a stack
class Stack #(type T=int);
local T stack[100]; // Holds data values
local int top;
function void push(input T i); // Push new value on top
stack[++top] = i;
endfunction : push
function T pop(); // Remove value from top
return stack[top–];
endfunction
endclass : Stack
下面的generator也是一个参数化的类
Sample 8.37 Parameterized generator class using blueprint pattern
class Generator #(type T=BaseTr);
mailbox gen2drv;
T blueprint; // Blueprint object
function new(input mailbox gen2drv);
this.gen2drv = gen2drv;
blueprint = new(); // Create default
endfunction
task run();
T tr;
forever begin
assert(blueprint.randomize); // Randomize object
tr = blueprint.copy(); // Make a copy
gen2drv.put(tr); // Send to driver
end
endtask
endclass
下面的代码是调用上面的generator发送transaction class的事务
Sample 8.38 Simple testbench using parameterized generator class
program automatic test;
initial begin
Generator #(Transaction) gen;
mailbox gen2drv;
gen2drv = new(1);
gen = new(gen2drv);
fork
gen.run();
repeat (5) begin
Transaction tr;
gen2drv.peek(tr); // Get next transaction
tr.display();
gen2drv.get(tr); // Remove transaction
end
join_any
end
endprogram // tes
关于参数化类的建议
1.从非参数化的class开始设计,这样更简单
2.宏可以替代参数化,但是debug更麻烦
3.如果有几个使用相同类型事务的class,可以用参数化,也可以用宏
4.virtual methods会帮助你创建参数化的类
网硕互联帮助中心




评论前必须登录!
注册