OOP encapsulation意为面向对象封装。Lab4做的事情就是将packet信息——sa、da和payload封装成一个Packet类,在generator中创建随机的Packet对象。
Packet class
在我看到的很多sv文件中,都会使用宏定义来防止多次编译。
`ifndef INC_PACKET_SV
`define INC_PACKET_SV
class Packet;
rand bit[3:0] sa, da;
rand logic[7:0] payload[$];
string name;
// property constraints
constraint Limit{sa inside {[0:15]};
da inside {[0:15]};
payload.size() inside {[2:4]};
}
// class method prototypes
extern function new(string name="Packet");
extern function bit compare(Packet pkt2cmp, ref string message);
extern function void display(string prefix="NOTE");
endclass: Packet
`endif
rand
修饰符:修饰的变量会在限定范围内随机取一个值,且每个值被取到的概率是一致的。比如sa的取值是0到15之间的一个数,每个数被取到的概率都是1/16。- 只要类的数据类型是
rand或randc
(周期性随机),且一旦这个类的实例化对象调用了randomzie()
方法,rand/randc
类型的数据将会被随机化。randomize()
的返回值是1或者0,分别表示随机成功或者失败。 constraint
语句块对随机数据进行约束,随机表达式(>、=、<
等操作符,一个表达式只能出现一个操作符!)、inside
操作符、dist
操作符、条件表达式(if-else
语句)等。参考link
- 只要类的数据类型是
extern
关键字:在类内带extern关键字进行声明,类外定义时用“::”
需指明类作用域。name
是每个Packet类的ID,用于区分不同的Packet对象,对debug有很大帮助。
Class method
类外定义3个method,分别用于对象初始化、对象比较和结果打印。
new method
function Packet::new(string name);
this.name = name;
endfunction: new
new()
方法用于初始化对象。类的对象在定义时仅为一个Null
的句柄,直到用new()
初始化后才变为非空。- 个人理解:
name
赋值给this
指针,该指针指向的其他属性也将拥有姓名,形象点说就是同一个类对象拥有同一个姓,同一个类对象的不同属性拥有不同的名。this
指针限定了类的本地变量参数和方法,用在非静态方法中。
compare method
function Packet::compare(Packet pkt2cmp, ref string message);
if( !(payload.size() == pkt2cmp.payload.size()) ) begin
message = "Packet Size Mismatch:\n";
message = {meaasge, $sformatf("payload.size = %0d, pkt2cmp.payload.size = %0d", payload.size(), pkt2cmp.payload.size())};
return(0);
end
if( payload == pkt2cmp.payload );
else begin
message = "Packet Content Mismatch:\n";
message = {meaasge, $sformatf("Packet Sent: %p\nPacket Recevied: %p", payload, pkt2cmp.payload)};
return(0);
end
meassage = "Successfully Compared!";
return(1);
endfucntion: compare
- 第一个参数是
Packet
类的pkt2cmp
,即一个创建好的Packet
对象。与Lab3的compare()
相比,只是在参数列表中加了一个类对象,以及将pkt2cmp_payload
替换成对象的属性pkt2cmp.payload
。
display method
function Packet::display(string prefix);
$display("[%s]%t %s sa=%0d, da=%0d", prefix, $realtime, name, sa, da);
foreach(payload[index])
$display("[%s]%t %s payload[%0d]=%0d", prefix, $realtime, name, index, payload[index]);
endfunction: display
display
方法用于打印payload情况,包含地址和内容。既可以打印发送端的payload情况,也可以打印接收端的payload情况,取决于是pkt2send
还是pkt2cmp
调用这个method。
Modify test.sv
global variables
`include Packet.sv
bit [3:0] sa,da;
logic [7:0] payload[$]; // expected packet data array
logic [7:0] pkt2cmp_payload[$]; // actual packet data array
Packet pkt2send = new(); // expected packet object
Packet pkt2cmp = new(); // actual packet object
- 在原有全局变量的基础上增加两个
Packet
的类对象,并include
类文件。如果类定义错误或者找不到该类文件的话,编译时将提示类名无效:XXX is not a valid type
。
gen task
task gen();
pkt2send.payload.delete();
static int pkts_generated = 0;
pkt2send.name = $sformatf("Packett[%0d]", pkts_generated++);
if( !pkt2send.randomize() ) begin
$display("\n%m\n[ERROR]%t Packet Randomization Failed!", $realtime);
$finish;
end
sa = pkt2send.sa;
da = pkt2send.da;
payload = pkt2send.payload;
endtask: gen
pkt2send
对象调用randomize()
方法时,sa、da、payload的值会在约束范围内被随机化。- 将
pkt2send
的property赋值给全局变量sa、da、payload,使得pkt2send
的属性在类外也可见。send()
任务可以使用全局变量da、payload来发送数据,本Lab中的send()
任务与前一个Lab完全相同。
recv task
task recv();
static int pkt_cnt = 0;
get_payload();
pkt2cmp.da = da;
pkt2cmp.payload = pkt2cmp_payload;
pkt2cmp.name = $sformatf("rcvdPkt[%0d]", pkt_cnt++);
endtask: recv
get_payload()
任务与前一个Lab相同,仅仅是将pkt2cmp_payload
作为payload属性赋值给了类对象pkt2cmp
。- 注意:
static
变量通常在声明时初始化(在最开始的位置),否则编译报错提示:System verilog keyword ‘static’ is not expected to be used in this context
。
check task
task check();
string message;
static int pkts_checked = 0;
if(!pkt2send.compare(pkt2cmp, message)) begin
$display("\n%m\n[ERROR]%t Packet[#%0d] %s\n", $realtime, pkts_checked, message);
pkt2send.display("ERROR");
pkt2cmp.display("ERROR");
$finish;
end
else
$display("[NOTE]%t Packet[#%0d] %s\n", $realtime, pkts_checked, message);
endtask: check
- 调用
compare()
函数时,由于ref
关键字使得传递的实际是message的地址,在函调用结束后message中的字符内容的改变不会被丢弃。 - 疑问:若是
pkt2cmp
调用display
方法,其sa属性在test program中并未被赋值,那将会打印出什么?
Conclusion
面向对象封装的中涉及到很多术语,在此先对已经出现的做一个小结,【】中给出已经出现的例子。
- class——类:包含变量、函数、任务的基本构建块。【Packet】
- object——对象:类的一个实例。【pkt2send、pkt2cmp】
- handle——句柄:即指向对象的指针。【pkt2send、pkt2cmp】
- method——方法:类中操作变量的程序块。【compare、display】
- prototype——原型:程序定义,包含程序名、参数列表、返回类型。【extern function xxx】
更多归纳参考link。