ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

SV学习记录(五)

2026/8/14 15:41:25 拓冰建站 浏览量
SV学习记录(五) 目录5.1 面向对象编程OOP5.2 类5.3 OOP中的术语5.4 创造新对象创建handle定制new()函数new()和new[]a handle on objects5.5 释放object5.6 使用object5.7 静态vs全局变量简单静态变量静态变量初始化静态方法static methods5.8 在class外创建method5.9 作用域this是什么5.10 在一个class中用另一个class一个class该有多大编译顺序问题5.11 理解动态对象传递给method的handle、object传递一/多个objecthandle数组5.12 复制object用new复制object5.13 public vs local5.14 测试平台5.1 面向对象编程OOPOOP要求我们更多地关注名词而非动词。具体来讲传统的测试强调我们要进行的操作比如产生信号发送等等。而在OOP中我们应该更在意整体的结构每一块的功能以及每一块之间如何相互通信。5.2 类什么是类class...endclass就是类。类包含数据和操作数据的函数。Sample 5.1 Simple transaction class class Transaction; bit [31:0] addr, crc, data[8]; function void display; $display(Transaction: %h, addr); endfunction : display function void calc_crc; crc addr ^ data.xor; endfunction : calc_crc endclass : Transaction在哪里定义类program, module, package以及这些地方之外都可以。在哪里用类module, program如果文件比较少你可以把类分别定义在不同文件中但如果文件较多应该把相关的类和声明放到同一个package中比如把都是SCSI/ATA事务的放到一起把不相关的分散在各自的文件中。5.3 OOP中的术语class类包含变量和子程序的基本块。与v中的module相对应。object对象类的例化。和v中对module的例化是一样的。handle句柄指向object的指针。存储着object的address。property属性存储数据的变量。对应v中的reg/wire。method方法task/function中操作变量的代码。prototype原型程序头包含名称类型参数列表。程序体包含可执行的代码。object就像是具体的房子。而class就像房子各个部分的蓝图每个class能造出一部分。handle是房子的地址。房子里有灯有开关灯的电路。就像property和method。5.4 创造新对象v的module在编译的时候就被例化了而sv中的class只有在被testbench用到的时候才会被例化。v的例化是静态的硬件不发生改变sv的object在被用到时创建不用时释放以节省资源。v的顶层文件不需要例化而sv的class必须例化了才能用。v的例化名只会指向一个模块而sv的handle可以指向多个object但同一时间只能指向一个。创建handle下面声明了一个handle指向类型为Transaction的object并用new()为它初始化。声明完tr被初始化为null。new为它分配了地址和默认值2-state赋04-state赋X。Sample 5.2 Declaring and using a handle Transaction tr; // Declare a handle tr new(); // Allocate a Transaction object定制new()函数我们可以按照自己的想法来定制new()函数自己决定初始值。自己写的function也可以没有传入值只执行功能。Sample 5.4 A new() function with arguments class Transaction; logic [31:0] addr, crc, data[8]; function new(logic [31:0] a3, d5); addr a; foreach (data[i]) data[i] d; endfunction endclass ​ initial begin Transaction tr; tr new(10); // data uses default of 5 end如果每个类都有自己独特的new()编译器怎么知道调用哪个答案是根据左侧的句柄类型调用对应类里的new()Sample 5.5 Calling the right new() function class Transaction; ... endclass : Transaction ​ class Driver; Transaction tr; function new();//define new for class Driver tr new();// Call the Transaction new function endfunction endclass : Driver建议把handle的声明和建构分开即不要写成Transaction tr new();new()和new[]new[]是在2.3节中为动态数组分配空间并初始化的函数。它们的区别一是在于作用对象二是new()可以调用参数。a handle on objects如果学过C比较好理解。handle就是指针能指向不同的objectSample 5.6 Allocating multiple objects Transaction t1, t2; t1 new(); // Allocate first Transaction object t2 t1; // t1 t2 point to it t1 new(); // Allocate second Transaction object类比人就是object如果你去参加会议handle就是给你分配的名牌上面写了你的名字也可以擦除写别人的名字。有了名牌主办方才能记住你给你分配位置如果你的名牌没了就得离开。5.5 释放object如果我们要用成百上千的事务因为内存是有限的用完总要把对应的内存释放sv是怎么知道一个事务不再被使用的答案是追踪指向这个object的handle如果没有任何handle指向它就把对应的内存空间释放。Sample 5.7 Creating multiple objects Transaction t; // Create a handle t new(); // Allocate a new Transaction t new(); // Allocate a second one, free the first t null; // Deallocate the secondsv中的handle相对于C, C有两个特点1.一个handle只能指向特定类型不存在void handle。2.sv会自动执行内存回收所以你使用的handle一定一直是有效的不用担心它指向一个不存在的object。如果有子程序有handle正在指向object子程序尚未停止这个object也不会被释放。5.6 使用object使用方法和module的引用方法一样。Sample 5.8 Using variables and routines in an object Transaction t; // Declare a handle to a Transaction t new(); // Construct a Transaction object t.addr 32h42; // Set the value of a variable t.display(); // Call a routine在严格的OOP中只能用公用函数进行操作比如get()put()等。这是因为直接访问property会限制以后对代码的修改。5.7 静态vs全局变量简单静态变量如果我们想有一个能够被所有的特定class共享的variable就在class内部创建static变量。无论给这个class创建了多少个object都只会有一个count。它类似于传统C中的全局变量但是全局变量会被Transaction这个类以外的代码看见这是我们不希望的。Sample 5.9 Class with a static variable class Transaction; static int count 0; // Number of objects created int id; // Unique instance ID function new(); id count; // Set ID, bump count endfunction endclass ​ Transaction t1, t2; initial begin t1 new(); // 1st instance, id0, count1 t2 new(); // 2nd instance, id1, count2 $display(Second id%d, count%d, t2.id, t2.count); end用id是一个很好的办法因为sv中不允许打印object的地址我们可以通过写id判断当前执行到的object。如果你只是单纯想查看count的值也可以不用handle直接用class nameSample 5.10 The class scope resolution operator class Transaction; static int count 0; // Number of objects created ... endclass ​ initial begin run_test(); $display(%d transaction were created, Transaction::count); // Reference static w/o handle end静态变量初始化初始化一定要在declaration的部分不要放在构建的new()中。更详细的初始化可以放在initial块中。一定注意静态变量的初始化要在第一个object创建之前。静态方法static methodsmethod指的是定义在class中的task/function。method默认是automatic的。如果我们现在有一个class1但是它的所有实例化的配置方式依赖于另一个class2相同的object这时handle也可以用staticSample 5.11 Static storage for a handle class Transaction; static Config cfg; // A handle with static storage MODE_E mode; function new(); mode cfg.mode; endfunction endclass ​ Config cfg; initial begin cfg new(MODE_ON); Transaction::cfg cfg; ... end如果静态变量很多也可以写一个专门用来读写静态变量的static function(也叫method)在实例化之前就可以读写。static method不允许读写非静态变量。Sample 5.12 Static method displays static variable class Transaction; static Config cfg; static int count 0; int id; static function void display_statics();// Static method to display static variables. $display(Transaction cfg.mode%s, count%0d, cfg.mode.name(), count); endfunction endclass ​ Config cfg; initial begin cfg new(MODE_ON); Transaction::cfg cfg; Transaction::display_statics(); // Static method call end5.8 在class外创建method我们最好让所有代码都能展现在一页中这样方便理解但有的method很长那么就可以把它定义在class之外的body中。class中的method加上extern关键字body中定义的时候直接用class中的property就可以Sample 5.14 Out-of-block method declarations class Transaction; bit [31:0] addr, crc, data[8]; extern function void display(); endclass ​ function void Transaction::display(); $display(%0t: Transaction addr%h, crc%h, $time, addr, crc); $write(\tdata[0-7]); foreach (data[i]) $write(data[i]); $display(); endfunction注意默认值的定义必须放在class...endclass内不能放在外部function中。5.9 作用域sv允许在未命名的begin...end块如下面的initial块中定义变量。这里的问题是我们在多个层次都定义了相同名称的变量如果想没有歧义地引用最好用绝对路径。但是如果我们在未命名地块中定义了一个变量可能会导致全局的变量路径都发生改变。Sample 5.16 Name scope int limit; // $root.limit program automatic p; int limit; // $root.p.limit class Foo; int limit, array[]; // $root.p.Foo.limit ​ function void print (int limit); // $root.p.Foo.print.limit for (int i0; ilimit; i) $display(%m: array[%0d]%0d, i, array[i]); endfunction endclass initial begin int limit $root.limit; // **see note above Foo bar; bar new(); bar.array new[limit]; bar.print (limit); end endprogram建议在package中声明class在使用到地最内层定义临时变量。下面是常见错误class在使用i的时候忘定义了编译器会自然地向上找使用program中的i。Sample 5.17 Class uses wrong variable program test; int i; // Program-level variable class Bad; logic [31:0] data[]; // Calling this function changes the program variable function void display; // Forgot to declare i in next statement for (i0; idata.size(); i) $display(data[%0d]%x, i, data[i]); endfunction endclass endprogram如果我们把class的定义放在package中它就看不到program层的i了。Sample 5.18 Move class into package to find bug package Mistake; class Bad; logic [31:0] data[]; // Will not compile because of undeclared i function void display; for (i 0; idata.size(); i) $display(data[%0d]%x, i, data[i]); endfunction endclass endpackage program test; int i; // Program-level variable import Mistake::*; ... endprogramthis是什么如果你在class的function中构建method的时候传入值和class中的变量同名可以用this来直指class层的变量。Sample 5.19 Using this to refer to class variable class Scoping; string oname; function new(string oname); this.oname oname; // class oname local oname endfunction endclass5.10 在一个class中用另一个class就像v中一个module能例化另一个module构成层次化设计sv中通过handle也可以在一个class中例化另一个class。注意声明完一定要初始化了才能用初始化最好放在new()中。Sample 5.21 Encapsulating the Statistics class class Transaction; bit [31:0] addr, crc, data[8]; Statistics stats; // Statistics handle function new(); stats new(); // Make instance of stats endfunction task create_packet(); // Fill packet with data stats.start(); // Transmit packet endtask endclass当代码越来越多的时候要及时思考重构代码层次尽量使顶层能在一页放下把相似的封装进更小的class中。一个class该有多大一个class肯定不能太大但也不应该太小。如果一个class只有一两个member那你可能需要在顶层和底层之间来回跳转理解这也是不好的。如果一个小class只用了一次那可以考虑封装进父class中。编译顺序问题有时候从上向下编译先编译到的class1用了其他class2但是class2还没出现过就会报错。解决方式如下Sample 5.22 Using a typedef class statement typedef class Statistics; // Define a lower level class ​ class Transaction; Statistics stats; ... endclass ​ class Statistics; ... endclass5.11 理解动态对象传递给method的handle、object这里要区分好handle和object。对于transmit我们传了一个handle进去这个handle是initial中handle的copy如果你尝试在transmit中修改这个handle比如写tnew()不会对顶层handle产生影响除非你写了ref Transaction t。handle指向的是object这个object是供我们任意修改的。Sample 5.23 Passing objects // Transmit a packet onto a 32-bit bus task transmit(Transaction t); CBbus.rx_data t.data; t.stats.startT $time; ... endtask ​ Transaction t; initial begin t new(); // Allocate the object t.addr 42; // Initialize values transmit(t); // Pass object to task end下面的定义方式就可以修改handle了Sample 5.25 Good transaction creator task with ref on handle function void create(ref Transaction tr); ... endtask传递一/多个object下面展示了两种method的方法method1是坏的method2是好的。他们的区别只在那一句代码出现的位置。为什么1坏2好对于1当我们调用这个class执行repeat行为给DUT传递object时虽然我们用random生成了多个值但是注意所有的handle都指向一个object那么DUT就会收到多个请求但是所有请求的内容一读都是一样的值取决于最后往里写了什么。而method2就解决了这个问题传递多个object进去。Sample 5.27 generator creates many objects task generator(int n); Transaction t; t new(); // method1 Create one new object repeat (n) begin t new(); // method2 Create one new object t.addr $random(); // Initialize variables $display(Sending addr%h, t.addr); transmit(t); // Send it into the DUT end endtaskhandle数组可以通过handle数组传递多个objectSample 5.28 Using an array of handles task generator(); Transaction tarray[10]; foreach (tarray[i]) begin tarray[i] new(); transmit(tarray[i]); end endtask注意没有object数组这个东西虽然我们用起来是object数组但一定要记住handle可能不指向任何一个object。5.12 复制object用new复制objectSample 5.29 Copying a simple class with new class Transaction; bit [31:0] addr, crc, data[8]; endclass ​ Transaction src, dst; initial begin src new(); // Create first object dst new src; // Make a copy with new operator end这种方式的特点是简单但是问题是如果class中有一个handle那么通过new复制时候会创建一个新handle但是指向相同的objectSample 5.30 Copying a complex class with new operator class Transaction; bit [31:0] addr, crc, data[8]; static int count 0; int id; Statistics stats; // Handle points to Statistics object ​ function new(); stats new(); // Construct a new Statistics obecjt id count; endfunction endclass ​ Transaction src, dst; initial begin src new(); // Create a Transaction object src.stats.startT 42; dst new src; // Copy src to dst with new operator dst.stats.startT 96; // Changes stats for dst src $display(src.stats.startT); end上面代码的最终结果如下所示5.13 public vs localsv中默认可见性是公有共有的含义是我们可以通过handle直接改变object的值。传统OOP中默认为私有私有需要通过函数来修改object的值修改完会自动修改CRC循环冗余校验用于检验数据包是否出错但sv本身就是为了检验的所以CRC出错也可以检验DUT是否具有正确的解包能力。5.14 测试平台现在我们再来回看最开始的测试平台其中environment class中每一个小方块就是一个类处理器class transactortest放在例化environment的program中每个transactor的通过结构如下所示Sample 5.38 Basic Transactor class Transactor; // Generic class Transaction tr; task run(); forever begin // Get transaction from upstream block ... // Do some processing ... // Send it to downstream block ... end endtask endclass如果我们想把一个block的事务传递给下一个block就需要调用对应的class但之后我们会学到mailbox