UVM-Multi Language solution from Cadence
Today's complex SoC level verification environment are based on advanced verification methodology standards. But these standards usually meant for single language domain. But fact of the life is that the these SoC level testbenches are made of mix of language domains like legacy Specman/e environment along with newly developed UVM-SV and SystemC or C++. Usually, when these scenario arrives, verification teams typically comes up with their own solutions which is mainly short term solution keeping existing needs in to the consideration. To develop this kind of inter operable solutions, there are quite a few challenges involved. Few are listed below.1) Connecting different components of different domains
2) Transferring data/events from different domains
3) Synchronizations of major phases
Having said that, industry standard solution is the need of an hour.
Cadence's Multi Language solution also known as UVM-ML addresses the
above mentioned multi language integration challenges. UVM-ML is in
existence for quite a sometime but least known. It was initially
developed to take care of e-SV and e-SC integrations. UVM-ML is based on
a back-plane library responsible for connecting two or more domains.
Different language domain connect to back-plane library via language
specific adapter layer. Currenty this solution supports integration of
SystemC, UVM-SV, UVM-e.
//-----------------------------------------
//File : producer.sv
//System Verilog Producer
//-----------------------------------------
//System Verilog Producer
//-----------------------------------------
module topmodule;
import uvm_pkg::*;
`include "uvm_macros.svh"
//Declare basic packet which will be passed to SC consumer
class packet extends uvm_object;
rand int data;
endclass
//Declare SV producer which will communicate with SC consumer through out port
class producer extends uvm_component;
//Declare out port. This port will be connected to SC consumer
uvm_analysis_port #(packet) out;
//constructor
function new(string name"producer", uvm_component parent=null);
`include "uvm_macros.svh"
//Declare basic packet which will be passed to SC consumer
class packet extends uvm_object;
rand int data;
endclass
//Declare SV producer which will communicate with SC consumer through out port
class producer extends uvm_component;
//Declare out port. This port will be connected to SC consumer
uvm_analysis_port #(packet) out;
//constructor
function new(string name"producer", uvm_component parent=null);
super.new(name, parent);
out = new("out", this);
//Register out port with UVM-ML
ml_uvm::extnernal_if(out, "packet");
endfunction : new
//Generate dummy packet and pass it to out port
task run_phase (uvm_phase phase);
packet pkt = packet::type_id::create("pkt");
phase.raise_objection(this);
for (int i=0; i<5 br="" i=""> begin
bit res = pkt.randomize();
`uvm_info("TEST", $sformatf("producer.pkt.data='h%h", pkt.data), UVM_LOW)
//Send the generated packet to out port
out.write(pkt);
#10;
end
phase.drop_objection(this);
endtask : run_phase
endclass : producer
out.write(pkt);
#10;
end
phase.drop_objection(this);
endtask : run_phase
endclass : producer
endmodule
//-----------------------------------------
//File : consumer.cpp
//File : consumer.cpp
//SystemC Consumer
//-----------------------------------------
#include "ml_uvm.h";
#include "uvm.h";
using namespace tlm;
using namespace uvm;
#include "ml_uvm.h";
#include "uvm.h";
using namespace tlm;
using namespace uvm;
class packet : public uvm_object
{
UVM_OBJECT_UTILS(packet)
public:
int data;
};
UVM_OBJECT_REGISTER(packet)
//System C Consumer with templated with data type T
template <typename T>
class consumer : public uvm_compnent, public tlm_analysis_if
{
public:
sc_export > in;
//constructor
consumer(sc_module_name nm) : uvm_component(nm), in("in")
{
in(*this);
//Register the export for mixed language communication
ml_uvm::ml_uvm_register(&in);
}
UVM_COMPONENT_UTILS(consumer)
//export implementation
virtual void write( const T& t )
{
cout << sc_time_stamp() << " SystemC consumer receive a packet with data " << t.data << endl;
}
};
UVM_COMPONENT_REGISTER_T(consumer, packet)
template <typename T>
class consumer : public uvm_compnent, public tlm_analysis_if
{
public:
sc_export > in;
//constructor
consumer(sc_module_name nm) : uvm_component(nm), in("in")
{
in(*this);
//Register the export for mixed language communication
ml_uvm::ml_uvm_register(&in);
}
UVM_COMPONENT_UTILS(consumer)
//export implementation
virtual void write( const T& t )
{
cout << sc_time_stamp() << " SystemC consumer receive a packet with data " << t.data << endl;
}
};
UVM_COMPONENT_REGISTER_T(consumer, packet)
//System C testbench
SC_MODULE(TB)
{
consumer c;
SC_CTOR(TB) : c("consumer")
{
//Connect multi language port and export
ml_uvm::ml_uvm_connect("producer.out", cons.in.name());
}
};
{
consumer c;
SC_CTOR(TB) : c("consumer")
{
//Connect multi language port and export
ml_uvm::ml_uvm_connect("producer.out", cons.in.name());
}
};
Output Log:
0 s SC consumer got a packet with data 8
UVM_INFO producer.sv(55) @ 10: producer [TEST] producer.pkt.data='h00000008
10 ns SC consumer got a packet with data 8
UVM_INFO producer.sv(55) @ 20: producer [TEST] producer.pkt.data='h00000003
20 ns SC consumer got a packet with data 3
UVM_INFO producer.sv(55) @ 30: producer [TEST] producer.pkt.data='h00000005
30 ns SC consumer got a packet with data 5
UVM_INFO producer.sv(55) @ 40: producer [TEST] producer.pkt.data='h00000006
40 ns SC consumer got a packet with data 6
UVM bidirectional port example using uvm_tlm_b_initiator_socket and uvm_tlm_b_target_socket ports
In your System Verilog UVM environment, sometimes you want few
components to communicate to each other in both ways. For example, a
master component can request a memory component to pass over the memory
content of specific address. So, how can you impelent such a
communication channel between two components? Well, there are multiple
ways. You can have two one-directional ports like uvm_get_ports and
uvm_put_ports. Initator can send the address through output port and
receives the data from another input port. Or it can be implemented
through mailbox. The other way to implement is to use uvm_tlm_b_initiator_socket and uvm_tlm_b_target_socket
ports. Using this port, you initiator can send a packet with specific
address. Same packet can be updated with data by the memory element and
later on can be read by initiator. Thus, by using single port and
packet, two elements can communicate with each other in bidirectional
way. Since this is a blocking port, request information and response
information should come in a single shot. Here is an example to
illustrate it. You can download the full examples by clicking uvm_bidirectional_port_example.tar.gz
//Memory cell definition
class mem_cell extend umv_sequence_item;
rand bit [1:0] addr;
rand bit [31:0] data;
endclass: mem_cell
//Memory component definition
class memory extends uvm_component;
//Define local memory
local bit [31:0] mem [3:0];
function new(string name = "memory", uvm_component parent = null);
super.new(name, parent);
socket = new("socket", this);
//Initialize the local memory
mem[0] = 32'h00000000;
mem[1] = 32'h11111111;
mem[2] = 32'h22222222;
mem[3] = 32'h33333333;
endfunction : new
//Declare blocking target socket port
uvm_tlm_b_target_socket #(memory, mem_cell) socket;
//Need to write implementation of b_transport() task
task b_transport(mem_cell req, uvm_tlm_time delay);
//Based on the req.addr, update the req.data which will be later read back by initiator
case (req.addr)
2'h0 : begin req.data = mem[0]; end
2'h1 : begin req.data = mem[0]; end
2'h2 : begin req.data = mem[0]; end
2'h3 : begin req.data = mem[0]; end
endcase
endtask : b_transport
endclasss : memory
class initiator extend uvm_component'
//Declare blocking transport socket port
uvm_tlm_b_transport_socket #(mem_cell) socket;
virtual task run_phase(uvm_phase phase);
//instantiate a mem_cell
mem_cell req;
uvm_tlm_time delay = new;
phase.raise_objection(this);
//Create mem_cell
req = mem_cell::type_id::create("req", , get_full_name());
//set address and sent it across to memory component
req.addr = 2'h0;
socket.b_transport(req, delay);
//memory element has now updated the req.data accordingly. it can be used now
`uvm_info("TEST", $sformat(f("For req.add='h%h, received req.data='h%h", req.addr, req.data), UVM_LOW);
//same object can be reused.
//set address and sent it across to memory component
req.addr = 2'h1;
socket.b_transport(req, delay);
//memory element has now updated the req.data accordingly. it can be used now
`uvm_info("TEST", $sformatf("For req.add='h%h, received req.data='h%h", req.addr, req.data), UVM_LOW);
phase.drop_objection(this);
endtask : run_phase
endclass : initator
class env extends uvm_component;
initiator init;
memory mem;
//Connect two ports
function void connect_phase(uvm_phase phase)
init.socket.connect(mem.socket);
endfunction
endclass : env
Log Output:
UVM_INFO initiator.sv(26) @ 20: env.init [TESTCASE] For req.add='h1 received req.data='h11111111
Automatic Coverage Weight Calculator Utility
Specman overall
coverage grade is calculated in hierarchical manner by default. It
means that coverage is calculated from leaf level to the top level in
following manner. I've assumed that weight is not set manually and
default weight value 1 is applied here.
- Individual item grade is calculated by dividing buckets hit by total bucket for that item.
- Individual cover group grade is calculated by dividing total of all items grade by total number of items
- Overall coverage grade is calculated by dividing total of all cover groups graade by total number of cover groups
Take a look at the following example code.
<'
extend sys
{
config : config_s;
};
struct config_s
{
a : uint(bits: 3);
b : uint(bits: 3);
c : bool;
event cov1_e;
event cov2_e;
cover cov1_e is
{
item a using ignore = (a in [0, 1, 7, 8]);
item b using ignore = (b in [0, 1, 7, 8]);
cross a, b using
ignore =
(a in [2] and b in [4]) or
(a in [4] and b in [2] );
};
cover cov2_e is
{
item a using ignore = (a in [0, 1, 7, 8]);
item c using ignore = (c == TRUE);
cross a, c;
};
keep soft a == select
{
40 : 2;
10 : 3;
40 : 4;
10 : 5;
10 : 6;
};
keep soft b == select
{
40 : 2;
10 : 3;
40 : 4;
10 : 5;
10 : 6;
};
keep soft c == TRUE;
run() is also
{
for i from 1 to 10
{
gen a; gen b; gen c;
emit cov1_e;
emit cov2_e;
outf ("a=%d b=%d c=%s\n", a, b, c);
};
};
};
'>
When you run the above code with Intelligen with seed 1, Specman generates following combinations of a, b and c.
a=4 b=4 c=TRUE
a=4 b=4 c=TRUE
a=4 b=3 c=TRUE
a=2 b=2 c=TRUE
a=6 b=2 c=TRUE
a=2 b=4 c=TRUE
a=2 b=2 c=TRUE
a=2 b=4 c=TRUE
a=2 b=2 c=TRUE
a=4 b=6 c=TRUE
Let's analyze the coverage now.
Item | Hit | Valid Space | Grade |
a | 3 | 5 | 60.00% (hit/valid space) |
b | 4 | 5 | 80.00% (hit/valid space) |
cross_a_b | 5 | 23 | 21.74% (hit/valid space) |
Item | Hit | Valid Space | Grade |
a | 3 | 5 | 60.00% (hit/valid space) |
c | 1 | 1 | 100.00% (hit/valid space) |
cross_a_c | 3 | 5 | 60.00% (hit/valid space) |
So, overall grade = (cov1_e grade + cov2_grade)/2 = 1.2725/2 = 63.62 %
But, if you need the linear coverage, it should be
(Total number of hit buckets) / (Total number of buckets in valid space) = ((3+4+5) + (3+1+3))/((5+5+23) + (5+1+5)) = 43.18 %
(Total number of hit buckets) / (Total number of buckets in valid space) = ((3+4+5) + (3+1+3))/((5+5+23) + (5+1+5)) = 43.18 %
This
can be achieved by adjusting the weitage of individual item and cover
group. If you set the weight of each item equal to the total number of
valid buckets, linear coverage is achieved. Let's see how it works.
Item | Hit | Valid Space | Weight | Grade |
a | 3 | 5 | 5 | 60.00 % (hit/valid space) |
b | 4 | 5 | 5 | 80.00 % (hit/valid space) |
cross_a_b | 5 | 23 | 23 | 21.74 % (hit/valid space) |
So, cov1_e cover group's grade = (grade of item a*weight + grade of item b*weight + grade of item cross_a_b*weight)/(weight of a + weight of b + weight of cross_a_b) = (3 + 4 + 5) / (5 + 5 + 23) = 36.36 %
Item | Hit | Valid Space | Weight | Grade |
a | 3 | 5 | 5 | 60.00 % (hit/valid space) |
c | 1 | 1 | 1 | 80.00 % (hit/valid space) |
cross_a_c | 3 | 5 | 5 | 21.74 % (hit/valid space) |
So, cov2_e cover group's grade = (grade of item a*weight + grade of item c*weight + grade of item cross_a_c*weight)/(weight of a + weight of c + weight of cross_a_c) = (3 + 1 + 3) / (5 + 1 + 5) = 63.64 %
So, overall grade = (cov1_e grade*weight + cov2_grade*weight)/(weight of cov1_e + weight of cov2_e) = (12+7)/(33+11) = 43.18%
This number looks linear as we can see we've calculated the coverage based on the number of buckets hit divided by total number of bucket for whole of the coverage space. So, setting the weight of the items based its number of buckets, it gives us the correct result.
Setting the weight manually is tedious process as you need to check the exact number of valid buckets of each item and coverage groups by keeping ignores and ranges into the mind. And even you do that, if you want to change the coverage model again, you might need to calculate the valid space again which is tedious and error prone. So, why not automate it using Coverage API calls?
You can use this small utility which uses coveage API to change the weight of all the items and cover group. It gives you the linear overall grade which is total number of buckets hit in valid coverage space divided by total number of buckets in valid coverage space. This utility can also be used in the post processing of coverage database. This utility uses coverage API to recursively parse the whole coverage space of a given unit which can be sys. It counts the total number of buckets of each item and coverage group and uses covers.set_weight() to set the weight of items and cover groups recursively.
You can use it after loading the linear_coverage.e. It defines a specman command using "define as" macro. Example is given below
This number looks linear as we can see we've calculated the coverage based on the number of buckets hit divided by total number of bucket for whole of the coverage space. So, setting the weight of the items based its number of buckets, it gives us the correct result.
Setting the weight manually is tedious process as you need to check the exact number of valid buckets of each item and coverage groups by keeping ignores and ranges into the mind. And even you do that, if you want to change the coverage model again, you might need to calculate the valid space again which is tedious and error prone. So, why not automate it using Coverage API calls?
You can use this small utility which uses coveage API to change the weight of all the items and cover group. It gives you the linear overall grade which is total number of buckets hit in valid coverage space divided by total number of buckets in valid coverage space. This utility can also be used in the post processing of coverage database. This utility uses coverage API to recursively parse the whole coverage space of a given unit which can be sys. It counts the total number of buckets of each item and coverage group and uses covers.set_weight() to set the weight of items and cover groups recursively.
You can use it after loading the linear_coverage.e. It defines a specman command using "define as" macro. Example is given below
Specman>load linear_coverage.e
Specman>adjust weight sys (for whole coverage space)
Specman>adjust weight config_s (for coverage space defined in config_s struct)
I hope this will help you. Your comments and thoughts are welcome.
-------------------------------------------
liner_coverage.e
-------------------------------------------
<'
define "adjust weight " as {
sys.adjust_weight = new;
sys.adjust_weight.main("<1>");
};
struct cover_info
{
item_name : string;
num_of_buckets: uint;
weight : uint;
};
struct bucket_cover_struct like user_cover_struct
{
collect: bool; // flag to skip specific item
cover_info : list (key: item_name) of cover_info;
main(items: string) is
{
if scan_cover(items) == 0 {
error("Adjust Weight Error: no cover items matching ", items," exist");
};
};
scan_bucket() is
{
if ((collect) and (cross_level == sub_items.size()-1)) then
{
var full_item_name := appendf ("%s.%s.%s", struct_name, group_name, item_name);
if (status in [hole, normal])
{
if cover_info.key_exists(full_item_name)
{
cover_info.first(.item_name
== full_item_name).num_of_buckets = cover_info.first(.item_name ==
full_item_name).num_of_buckets + 1;
}
else
{
var temp_cov_info : cover_info = new;
temp_cov_info.item_name = full_item_name;
temp_cov_info.num_of_buckets = 1;
cover_info.add(temp_cov_info);
};
};
};
};
// Output a line of text for item:
end_item() is
{
if collect then
{
var full_item_name := appendf ("%s.%s.%s", struct_name, group_name, item_name);
var num_of_buckets := cover_info.first(.item_name == full_item_name).num_of_buckets;
outf ("%s - %d (weight=%d)\n", full_item_name, num_of_buckets, item_weight);
covers.set_weight(full_item_name, num_of_buckets, FALSE);
};
};
start_group() is
{
collect = (struct_name != "session");//skip session struct
if collect then
{
outf ("\n------------------------------------\n");
};
};
end_group() is
{
if collect then
{
var group_total : uint;
for each (cov_info) in (cover_info)
{
group_total = group_total + cov_info.num_of_buckets;
};
outf ("------------------------------------\n");
outf ("Group and Total Buckets ---> %s - %d (weight=%d)\n", group_name, group_total, group_weight);
covers.set_weight(appendf("%s.%s", struct_name, group_name), group_total, FALSE);
outf ("------------------------------------\n");
cover_info.clear();
};
};
};
// Define an instance of bucket_cover_struct:
extend sys
{
!adjust_weight: bucket_cover_struct;
init() is also { adjust_weight = new; };
setup() is also { set_config(cover, show_sub_holes, TRUE); };
};
'>
Nguồn chipverification
Không có nhận xét nào:
Đăng nhận xét