Callback in System Verilog

One of the main guidelines of this book is to create a single verification environment that you can use for all tests with no changes. The key requirement is that this testbench must provide a “hook” where the test program can inject new code without modifying the original classes.


Your driver may want to do the following:

  • Inject errors
  • Drop the transaction
  • Delay the transaction
  • Put the transaction in the scoreboard
  • Gather functional coverage data

Rather than try to anticipate every possible error, delay, or disturbance in the flow of transactions, the driver just needs to “call back” a method that is defined in the top-level test.
The beauty of this technique is that the callback method 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.


For some drastic behaviors such as dropping a transaction, you need to code this in the class ahead of time, but this is a known pattern. The reason why the transaction is dropped is left to the callback.

As shown in “Developer code” the Driver:run task loops forever with a call to a transmit task. Before sending the transaction, run calls the pre-transmit callback, if any. After sending the transaction, it calls the post-callback task, if any. By default, there are no callbacks, so run just calls transmit.

//——————————————————————-
// Developer Code
//——————————————————————-
// Transaction class
class Transaction;
rand bit [7:0] addr;
rand logic [7:0] data;

constraint addr_range_cn {
addr inside {[10:20]};
}
constraint data_range_cn {
data inside {[100:200]};
}
function string sprint();
sprint = $sformatf(“addr = %0d, data = %0d”, addr, data);
endfunction : sprint
endclass : Transaction

// Generator class
class Generator;
mailbox gen_drv;

function new(input mailbox gen_drv);
this.gen_drv = gen_drv;
endfunction : new

task run();
Transaction tr;
forever begin
tr = new();
if (!tr.randomize()) begin
$fatal(“time=%0t,Randomization Failed in Generator”, $time);
end
else begin
gen_drv.put(tr);
$display(“time=%0t, Generator : after randomization tr put in maibox = %s”, $time, tr.sprint());
end
end
endtask : run
endclass : Generator

// Driver_cbs class
virtual class Driver_cbs; // Driver callbacks
virtual task pre_tx(ref Transaction tr, ref bit drop);
// By default, callback does nothing
endtask : pre_tx
virtual task post_tx(ref Transaction tr);
// By default, callback does nothing
endtask : post_tx
endclass : Driver_cbs

// Driver class
class Driver;
mailbox gen_drv;
Driver_cbs cbs;

function new(input mailbox gen_drv);
this.gen_drv = gen_drv;
endfunction : new

task run();
bit drop;
Transaction tr;
forever begin
#1;
drop = 0;
// Get Transaction from Generator
gen_drv.peek(tr);
// pre_tx hook of callback
cbs.pre_tx(tr, drop);
if (drop == 1) begin
gen_drv.get(tr); // Remove tr from mailbox
$display(“time=%0t, Driver : run : tr dropped, dropped tr = %s”, $time, tr.sprint());
continue;
end
// Actual transmit logic
transmit(tr);
// post_tx hook of callback
cbs.post_tx(tr);
end
endtask : run

task transmit(ref Transaction tr);
$display(“time=%0t, Driver : transmit : get tr from Generator = %s”, $time, tr.sprint());
#5; // Actual logic to drive value on interface
gen_drv.get(tr);
//$display(“time=%0t, Driver : transmit : Value driven in interface”, $time);
endtask : transmit
endclass : Driver

//Agent class
class Agent;
mailbox gen_drv; //generator to driver mailbox
Generator Gen;
Driver Drv;

function void build();
gen_drv = new(1);
Gen = new(gen_drv);
Drv = new(gen_drv);
endfunction : build

task run();
fork
begin
Gen.run();
end
begin
Drv.run();
end
join_none
endtask : run
endclass : Agent

You could make Driver::run a virtual method and then override its behavior in an extended class, perhaps MyDriver::run.

The drawback to this is that you might have to duplicate all the original method’s code in the new method if you are injecting new behavior.

Now if you made a change in the base class, you would have to remember to propagate it to all the extended classes. Additionally, you can inject a callback without modifying the code that constructed the original object.

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.

class Driver_cbs_drop extends Driver_cbs;
virtual task pre_tx(ref Transaction tr, ref bit drop);
bit _drop;
// Randomly drop 1 out of every 5 transactions
void'(std :: randomize(_drop) with {_drop inside {[0:4]};});
drop = (_drop == 0);
endtask : pre_tx
endclass : Driver_cbs_drop

// Test class
class Test1;
Agent Agt;
Driver_cbs_drop cb;

function void build();
Agt = new();
Agt.build();
cb = new();
Agt.Drv.cbs = this.cb;
endfunction : build

task run();
fork
Agt.run();
join_none
endtask:run
endclass : Test1

//Top module
module top1();
Test1 test;

initial begin
test = new();
test.build();
test.run();

#40 $finish;

end
endmodule : top1

//Output:
// time=0, Generator : after randomization tr put in maibox = addr = 19, data = 189
// time=1, Driver : run : tr dropped, dropped tr = addr = 19, data = 189
// time=1, Generator : after randomization tr put in maibox = addr = 14, data = 112
// time=2, Driver : transmit : get tr from Generator = addr = 14, data = 112
// time=7, Generator : after randomization tr put in maibox = addr = 13, data = 115
// time=8, Driver : transmit : get tr from Generator = addr = 13, data = 115
// time=13, Generator : after randomization tr put in maibox = addr = 19, data = 135
// time=14, Driver : transmit : get tr from Generator = addr = 19, data = 135
// time=19, Generator : after randomization tr put in maibox = addr = 19, data = 138
// time=20, Driver : run : tr dropped, dropped tr = addr = 19, data = 138
// time=20, Generator : after randomization tr put in maibox = addr = 13, data = 133
// time=21, Driver : run : tr dropped, dropped tr = addr = 13, data = 133
// time=21, Generator : after randomization tr put in maibox = addr = 15, data = 136
// time=22, Driver : run : tr dropped, dropped tr = addr = 15, data = 136
// time=22, Generator : after randomization tr put in maibox = addr = 13, data = 127
// time=23, Driver : run : tr dropped, dropped tr = addr = 13, data = 127
// time=23, Generator : after randomization tr put in maibox = addr = 12, data = 200
// time=24, Driver : transmit : get tr from Generator = addr = 12, data = 200
// time=29, Generator : after randomization tr put in maibox = addr = 15, data = 104
// time=30, Driver : transmit : get tr from Generator = addr = 15, data = 104
// time=35, Generator : after randomization tr put in maibox = addr = 12, data = 176
// time=36, Driver : transmit : get tr from Generator = addr = 12, data = 176

//——————————————————————
// End User Code 2
//——————————————————————
// User defined extended callback class
class Driver_cbs_modify extends Driver_cbs;
virtual task pre_tx(ref Transaction tr, ref bit drop);
bit _modify;
// Randomly drop 1 out of every 5 transactions
void'(std :: randomize(_modify) with {_modify inside {[0:4]};});
if (_modify == 0) begin
tr.addr = tr.addr + 10;
tr.data = tr.data + 10;
$display(“time=%0t, Driver_cbs_modify : modified tr = %s”, $time, tr.sprint());
end
endtask : pre_tx
endclass : Driver_cbs_modify

// Test class
class Test2;
Agent Agt;
Driver_cbs_modify cb;

function void build();
Agt = new();
Agt.build();
cb = new();
Agt.Drv.cbs = this.cb;
endfunction : build

task run();
fork
Agt.run();
join_none
endtask:run
endclass : Test2

// Top module
module top2();
Test2 test;

initial begin
test = new();
test.build();
test.run();

#40 $finish;

end
endmodule : top2

//Output:
// time=0, Generator : after randomization tr put in maibox = addr = 19, data = 189
// time=1, Driver_cbs_modify : modified tr = addr = 29, data = 199
// time=1, Driver : transmit : get tr from Generator = addr = 29, data = 199
// time=6, Generator : after randomization tr put in maibox = addr = 14, data = 112
// time=7, Driver : transmit : get tr from Generator = addr = 14, data = 112
// time=12, Generator : after randomization tr put in maibox = addr = 13, data = 115
// time=13, Driver : transmit : get tr from Generator = addr = 13, data = 115
// time=18, Generator : after randomization tr put in maibox = addr = 19, data = 135
// time=19, Driver : transmit : get tr from Generator = addr = 19, data = 135
// time=24, Generator : after randomization tr put in maibox = addr = 19, data = 138
// time=25, Driver_cbs_modify : modified tr = addr = 29, data = 148
// time=25, Driver : transmit : get tr from Generator = addr = 29, data = 148
// time=30, Generator : after randomization tr put in maibox = addr = 13, data = 133
// time=31, Driver_cbs_modify : modified tr = addr = 23, data = 143
// time=31, Driver : transmit : get tr from Generator = addr = 23, data = 143
// time=36, Generator : after randomization tr put in maibox = addr = 15, data = 136
// time=37, Driver_cbs_modify : modified tr = addr = 25, data = 146
// time=37, Driver : transmit : get tr from Generator = addr = 25, data = 146

Limitations of SystemVerilog callback (w.r.t. uvm_callback).

  • You cannot control particular nth number of transaction. Callback affects all the transaction or in random manner if you use randomization in extended callback as I had used in above both examples. For example you want initial some (n) transcation to drive without any modification, then for particular (n+1) transaction you want to modified its content.
  • Then again for rest of all transaction you don’t want any modification. This is not possible with SystemVerilog callback.
  • You cannot Add or Delete callback runtime.
 

Leave a Comment

Your email address will not be published. Required fields are marked *

error: Content is protected !!
Scroll to Top