不想错过我的推送,记得右上角-查看公众号-设为星标,摘下星星送给我
欢迎大家加入2023届数字IC交流群,QQ群号 628200294
“后台回复COOKBOOK,即可获取PDF笔记以及原版COOKBOOK ”
后台回复即可获取
UVM产生激励是通过sequence sequencer以及driver三者配合实现的。生成激励的flow的框架是围绕sequence构建的,但是生成数据流使用sequence_items作为数据对象。由于 sequence_items 是构建sequence的基础,因此在设计时需要注意一些问题。Sequence_item的内容由driver在pin一级的时序决定的;通过支持随即约束,sequence item能够更加简单地生成新的item;此外,还包括了其他参数如用于分析的回调钩子。
sequence_item 的内容与driver的需求密切相关。driver根据sequence_items 的内容来确定要执行哪种类型的pin级事务。item的内容一般有以下几种:
Sequence_items 在sequence内随机化然后发送到driver中。因此,激励数据属性声明为 rand,并且默认生成值合法或在将他约束到一个合法范围内。在一个sequence中,sequence_items通常使用内联约束随机化,这些约束和item内部的约束共同作用产生最终结果。
由于 sequence_items 用于请求和响应sequence和driver之间的传输的,因此遵循准则。是请求属性应该是 rand,而响应属性不应该是rand。这样可以优化随机化过程,并确保任何收集的响应信息不会被任何随机化破坏。例如,考虑以下总线协议 sequence_item:
class bus_seq_item extends uvm_sequence_item;
// Request data properties are rand
rand logic[31:0] addr;
rand logic[31:0] write_data;
rand bit read_not_write;
rand int delay;
// Response data properties are NOT rand
bit error;
logic[31:0] read_data;
`uvm_object_utils(bus_seq_item)
function new(string name = "bus_seq_item");
super.new(name);
endfunction
// Delay between bus cycles is in a sensible range
constraint at_least_1 { delay inside {[1:20]};}
// 32 bit aligned transfers
constraint align_32 {addr[1:0] == 0;}
// etc
endclass: bus_seq_item
uvm_sequence_item 通过 uvm_transaction 类从 uvm_object 继承而来。uvm_object 有许多虚方法,用于实现常见的数据对象功能(复制、克隆、比较、打印、事务记录),建议覆盖这些虚拟方法让 sequence_item 更具通用性。sequence_item 通常用于分析流量,推荐添加有助于功能覆盖或分析的实用函数。
当使用从 uvm_objects 派生的数据对象类时,包括从 uvm_transactions、uvm_sequence_items 和 uvm_sequences 派生的类,有很多便利的方法。进一步的,这些方法中的每一个都会调用一个或多个虚方法,这些虚方法留给用户根据对象内数据成员做具体实现。这些方法及其相应的虚方法总结在下表中。
“表格就不翻译了,其实这里将相当于内置了一些回调钩子,方法的原型可以自行查阅 ”
do_xxx 方法可以使用 `uvm_field_xxx 宏来实现和,但生成的代码效率低下,难以调试并且容易出错。推荐手动实现这些方法,从而改进测试平台性能和内存占用。更加详细的讨论,可以去查阅宏消耗收益分析章节。
“这里是说,使用域自动化机制的宏,会导致平台存在冗余,从而拖慢仿真,所以从仿真性能的角度来说,根据自己需要去实现这些方法,更加高效。不过个人觉得仁者见仁智者见智,具体还得看规模,毕竟手动去实现还得花时间。 ”
考虑以下 sequence_item,其中包含表示大多数常见数据类型的属性:
class bus_item extends uvm_sequence_item;
// Factory registration
`uvm_object_utils(bus_item)
// Properties - a selection of common types:
rand int delay;
rand logic[31:0] addr;
rand op_code_enum op_code;
string slave_name;
rand logic[31:0] data[];
bit response;
function new(string name = "bus_item");
super.new(name);
endfunction
endclass: bus_item
这个item中所需要实现的常见方法包括:
do_copy 方法的目的是提供一种对数据对象深度拷贝的方法。do_copy 方法可以单独使用,也可以通过 uvm_objects.clone() 方法调用,该方法会创建对象的独立副本。对于 sequence_item 而言,该方法将按如下方式实现:
// do_copy method:
function void do_copy(uvm_object rhs);
bus_item rhs_;
if(!$cast(rhs_, rhs)) begin
uvm_report_error("do_copy:", "Cast failed");
return;
end
super.do_copy(rhs); // Chain the copy with parent classes
delay = rhs_.delay;
addr = rhs_.addr;
op_code = rhs_.op_code;
slave_name = rhs_.slave_name;
data = rhs_.data;
response = rhs_.response;
endfunction: do_copy
// Example of how do_copy would be used:
// Directly:
bus_item A, B;
A.copy(B); // A becomes a deep copy of B
// Indirectly:
$cast(A, B.clone()); // Clone returns an uvm_object which needs
// to be cast to the actual type
“正如前面所说的,个人认为需要根据实际情况在性能和代码编写时间上做tradeoff。 ”
请注意,rhs 参数是 uvm_object 类型,因为它是一个虚方法,因此在复制其字段之前要将其转换为实际事务类型。这种考虑是出于,对所有的字段进行拷贝并不总是有意义的。
“深拷贝是将数据对象中每个单独属性的值复制到另一个的,而不是仅复制数据句柄的浅拷贝。 ”
do_compare 方法由 uvm_object.compare() 方法调用,用于比较两个相同类型的数据对象,以确定它们的内容是否相等。do_compare() 方法应该只对需要比较的字段进行比较。
uvm_comparer 策略对象必须传递给 do_compare() 方法才能与虚方法模板兼容,但在比较函数中没有必要使用它,不使用它可以提高性能。
“不得不吐槽一句,UVM很多特性,cookbook都觉得没必要用,都是为了提高性能 ”
// do_compare implementation:
function bit do_compare(uvm_object rhs, uvm_comparer comparer);
bus_item rhs_;
// If the cast fails, comparison has also failed
// A check for null is not needed because that is done in the compare()
// function which calls do_compare()
if(!$cast(rhs_, rhs)) begin
return 0;
end
return((super.do_compare(rhs, comparer) &&
(delay == rhs_.delay) &&
(addr == rhs.addr) &&
(op_code == rhs_.op_code) &&
(slave_name == rhs_.slave_name) &&
(data == rhs_.data) &&
(response == rhs_.response));
endfunction: do_compare
// Useage example - do_compare is not used directly
bus_item A, B;
if(!A.compare(B)) begin
// Report and handle error
end
else begin
// Report and handle success
end
为了把数据对象将调试或状态信息打印到仿真器的控制行或log里,需要有一种方法将对象内容转换为字符串表示 ,这就是 convert2string() 方法的目的。调用该方法将返回一个字符串,会包含每个属性的值,这些属性的格式是为转录显示或写入文件而设置的。而格式由用户决定:
// Implementation example:
function string convert2string();
string s;
s = super.convert2string();
// Note the use of \t (tab) and \n (newline) to format the data in
columns
// The enumerated op_code types .name() method returns a string
corresponding to its value
$sformat(s, "%s\n delay \t%0d\n addr \t%0h\n op_code \t%s\n slave_name \t%s\n", s, delay, addr, op_code.name(), slave_name);
// For an array we need to iterate through the values:
foreach(data[i]) begin
$sformat(s, "%s data[%0d] \t%0h\n", s, i, data[i]);
end
$sformat(s, "%s response \t%0b\n", s, response);
return s;
endfunction: convert2string
do_print() 方法由 uvm_object.print() 方法调用。它的目的是使用 uvm_printer 策略类之一打印出 uvm 数据对象的字符串表示。实现该方法的最简单方法是将uvm_printer 的字符串设置为 convert2string() 方法返回的值。
function void do_print(uvm_printer printer);
printer.m_string = convert2string();
endfunction: do_print
另一种更高性能的版本可以用 $display() 打印 convert2string() 返回的值,但这就不能用uvm_printer 策略类的各种功能来格式化数据。
function void do_print(uvm_printer printer);
$display(convert2string());
endfunction: do_print
要实现完全优化,请避免同时使用 print() 和 sprint() 方法并直接调用 convert2string() 方法。
do_record() 方法的目的是将数据对象看作波形 GUI 中的事务。与打印数据对象方法一样,其原理是记录的字段能够在事务查看器中查看。do_record() 方法中使用的 `uvm_record_field 宏的底层实现是与仿真器有关,例如对于 Questa 仿真器使用 $add_attribute() 系统调用:
function void do_record(uvm_recorder recorder);
super.do_record(recorder); // To record any inherited data members
`uvm_record_field("delay", delay)
`uvm_record_field("addr", addr)
`uvm_record_field("op_code", op_code.name())
`uvm_record_field("slave_name", slave_name)
foreach(data[i]) begin
`uvm_record_field($sformatf("data[%0d]", i), data[i])
end
`uvm_record_field("response", response)
endfunction: do_record
要在Quseta仿真器里查看事务,你需要:
set_config_int("*", "recording_detail", UVM_FULL);
“这里用的方法就是类型为int的config_db,不知道为什么2019版的cookbook还要用这个,应该在UVM1.2中,废除了set_config_*和set_config_*。可能还是参考的1.1d的版本 ”
实现了do_record并且打开recording_detail后,在sequencer中就能调用事务流句柄,名字是aggregate_items。
“使用do_record方法会把信息记录到数据库文件里,个人没用过这个功能,应该和打印类似,只是更加专注与某时某刻字段的状态,各个仿真器查看的方式应该有所不同,这里不做展开 ”
这两种方法并不常用,它们的目的是将数据对象转换为bit流(即整数),从而在不同的语言之间传递,例如在 SystemVerilog 和 C/C++ 之间。这两种方法的推荐实现可能会随着即将推出的 Questa 版本而改变,这里没有记录。但是,它们记录在论文中,可以在成本效益分析中找到。
to_struct() 和 from_struct() 方法可以选择性地插入到事务中,从而对象的数据成员转换为适合格式,更加有利于emulator地综合。结构体定义本身包含在一个单独的包中,这个包在emulator和simulator之间共享。这个带有 struct 定义的包被导入到包含agent类的包中。也被导入到使用该结构的任何 BFM 中。
“好像是更加方便帕拉丁、zebu之类的硬件仿真加速器使用 ”
to_struct() 和 from_struct() 函数未在 UVM 基类库中定义。因此他们地命名不遵循其他事务函数的 do_*() 约定。
调用事务对象的 to_struct() 函数并返回对象的结构体。然后可以直接使用该结构体将信息发送到emulator。
function item_s to_struct();
to_struct.addr = addr;
to_struct.data = data;
to_struct.injerr = injerr;
endfunction : to_struct
当从仿真器接收到信息时,它应该被转换回一个UVM测试环境中的对象。from_struct() 方法就是实现这一需求的。
function void from_struct(item_s item);
addr = item.addr;
data = item.data;
injerr = item.injerr;
endfunction : from_struct
END