Tuesday, 29 December 2015

Callback in SystemVerilog

One of the main guidelines of this book is to create a single verifi cation 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
view raw sv_callback1.sv hosted with ❤ by GitHub


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.
 
//------------------------------------------------------------------
// End User Code 1
//------------------------------------------------------------------
// User defined extended callback class
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
view raw sv_callback2.sv hosted with ❤ by GitHub

//------------------------------------------------------------------
// 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
view raw sv_callback3.sv hosted with ❤ by GitHub

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.

3 comments:

  1. Thanks very much for this article, I found it both informative and interesting.
    sprint error 104

    ReplyDelete
  2. As per limitation listed here, can't we use static variable to give id to all transaction and once that nth number of transaction comes, we can do operation as needed? Please give resolution on this.

    ReplyDelete