云计算百科
云计算领域专业知识百科平台

SV学习记录(八)

目录

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会帮助你创建参数化的类

赞(0)
未经允许不得转载:网硕互联帮助中心 » SV学习记录(八)
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!