advanced OOP
1 If you assign an extended handle to a base handle, nothing special is
needed. you can use the basd handle to access the extended data and methods.
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
2 But what if you try going in the opposite direction, copying a base object
into an extended handle, This
fails because the
base object is missing
properties that only exist in the
extended class, such as
bad_crc.
3
It is not always illegal to assign a base handle to an extended handle. It is
allowed when the base handle actually points to an extended object
bad = new; // Construct BadTr extended object
tr = bad; // Base handle points to extended obj
// Check object type & copy. Simulation error if mismatch
$cast(b2, tr);
// User check for mismatch, no simulation error
if(!$cast(b2, tr))
$display("cannot assign tr to b2");
$dislay(b2.bad_crc); // bad_crc exists in original obj
$cast routine checks the type of object, not just the handle.
3 using $cast as a function and checking the result - 0
for incompatible types, and non-0 for compatible types.
4 When you use
virtual methods, System
verilog uses the
type of the object,not the
handle to decide which routine to call. In the final statement, tr points
to an extended object (BadTr) and so BadTr::calc_crc is called.
5 If you left out the virtual modifier on calc_crc, SystemVerilog
would use the type of the handle (Transaction), not the object
6 There is one downside to using
virtual routines – once you define one, all
extended classes that define
the same virtual routine must use the same“signature,” i.e., the same number and type of arguments
7 If two objects seem to be related by both “is-a” and “has-a,” you may need
to break them down into smaller components.
8 Does the additional data need to remain
attached to the original class while it is being
processed by pre-existing code?
section 4.16 on public vs. private attributes, testbenches are not standard
software development projects. Concepts such as information hiding (using
private variables) conflict with building a testbench that needs maximum
visibility and
controllability. Similarly, dividing a transaction into smaller
pieces may cause more problems than it solves.
9 If composition leads to large hierarchies, but inheritance requires extra
code and planning to deal with all the different classes, and both have difficult
construction and randomization, what can you do? You can instead make a
single, flat class that has all the variables and routines. This approach leads to
a very large class, but it handles all the variants cleanly. You have to use the
discriminant variable often to tell which variables are valid, as shown in
Example 8-18. It contains several conditional constraints, which apply in
different cases, depending on the value of kind.
10 class Transaction;
rand bit [31:0] src, dst, data[8]; // Variables
bit [31:0] crc;
virtual function Transaction copy;
copy = new;//in this implementation, this copy is to make a new object, so we have the second implementation,see below , you can do the real copy or the copy between handle or father/child objects.copy.src = src; // Copy data fields
copy.dst = dst;
copy.data = data;
copy.crc = crc;
endfunction
endclass
When you extend the Transaction class to make the class BadTr, the
copy function still has to return a Transaction object. This is because the
extended virtual function must match the base Transaction::copy,
including all arguments and return type
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
11 class Transaction;
rand bit [31:0] src, dst, data[8]; // Variables
bit [31:0] crc;
virtual function void copy_data(Transaction copy);
copy.src = src; // Copy the data fields
copy.dst = dst;
copy.data = data;
copy.crc = crc;
endfunction
virtual function Transaction copy;
copy = new;
copy_data(copy);
endfunction
endclass
class BadTr extends Transaction;
rand bit bad_crc;
virtual function void copy_data(Transaction tr);
BadTr bad;
super.copy_data(tr); // Copy base data$cast(bad, tr); // Cast base handle to ext’dbad.bad_crc = bad_crc; // Copy extended dataendfunction
virtual function Transaction copy;
BadTr bad;
bad = new; // Construct BadTr
copy_data(bad); // Copy data fields
return bad;
endfunction
endclass : BadTr
xxx.copy is to copy the xxx to other object!!
in this example copy_data just
copy the content, the copy is to
new a object,and copy is to copy this object to the argument object
The existing copy routine always constructs a new object. An
improvement for copy is to specify the location where the copy should be
put.
This technique is useful when you want to reuse an existing object, andnot allocate a new one.
12 Rather than try to anticipate every possible error, delay, or disturbance in
the flow of transactions, the driver just needs to “
call back” a routine defined
in the top-level test. The beauty of this technique is that the callback11 routine
can be defined differently in every test. As a result, the test can add new
functionality to the driver using callbacks without editing the Driver class.
13 The following testbench randomly drops packets using a
callback object. Callbacks can also be used to send data to the
scoreboard or
to gather
functional coverage values
14 A callback task is created in the top-level test and called from the driver,
the lowest level of the environment. However, the driver does not have to
have any knowledge of the test – it just has to use a generic class that the test
can extend.
15
class Driver_cbs; // Driver callbacks
virtual task pre_tx(Transaction tr, ref bit drop);
// By default, callback does nothing
endtask
virtual task post_tx(Transaction tr);
// By default, callback does nothing
endtask
endclass
Example 8-25 Driver class with callbacks
class Driver;
Driver_cbs cbs[$];
task run;
bit drop;
Transaction tr;
forever begin
agt2drv.get(tr);
foreach (cbs[i]) cbs.pre_tx(tr, drop);
if (!drop) transmit(tr);
foreach (cbs[i]) cbs.post_tx(tr);
end
endtask
endclass
class Driver_cbs_drop extends Driver_cbs;
virtual task pre_tx(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;
// Callback injection
begin
Driver_cbs_drop dcd;
dcd = new; // Create scb callback
env.drv.cbs.push_back(dcd); // Put into driver’s Q
end
env.run;
env.wrapup;
end
endprogram
class Driver_cbs_scoreboard extends Driver_cbs;
virtual task pre_tx(Transaction tr, ref bit drop);
// Put transaction in the scoreboard
...
endtask
endclass
program automatic test;
Environment env;
initial begin
env = new;
env.gen_cfg;
env.build;
begin
Driver_cbs_scoreboard dcs;
dcs = new; // Create scb callback
env.drv.cbs.push_back(dcs); // Put into driver’s Q
end
env.run;
env.wrapup;
end
endprogram
Always use
callbacks for
scoreboards and
functional coverage.The
monitor transactor can use a callback to compare received transactions with
expected ones. The monitor callback is also the perfect place to gather
functional coverage on transactions that are actually sent by the DUT.
By using
virtual routines and providing sufficient callback points, your test can modify
the behavior. of the testbench without changing its code. The result is a robust
testbench that does not need to anticipate every type of disturbance (errorinjection,
delays, synchronization) that you may want as long as you leave a
hook where the test can inject its own behavior