Monday, August 18, 2014

SystemVerilog: Transaction, driver, monitor, scoreboard, environment

This is my code to verify a switch.
A note of caution when using mailbox, mailbox only holds handles not the object. So if you need to place multiple objects in the mailbox simultaneously, you would need to create (new) objects equivalent number of times.

class packet;
rand bit [47:0] src_addr;
rand bit [31:0] src_data;
 //signal unrelated to rtl
static bit [15:0] pkt_id;
//packet class is no longer talking to RTL, so no need of VI

function new();
pkt_id++;
endfunction

virtual function void print();
$display("src_addr = %h, src_data = %h", src_addr, src_data);
endfunction

virtual function void rec_print();
$display("dst_addr = %h, dst_data = %h", src_addr, src_data);
endfunction

virtual function packet deepcopy();
//$display("I am here: in deepcopy in packet class");
packet funny;
funny = new();
return(funny);
endfunction

endclass

///////////////////////////////////////////////////////////////////////

class transaction extends packet;
virtual function packet deepcopy(); //has to match prototype
packet pkt_deepcopy;
pkt_deepcopy = new();
pkt_deepcopy.src_data = this.src_data;
pkt_deepcopy.src_addr = this.src_addr;
return(pkt_deepcopy);
endfunction
//function pack();
//function unpack();
endclass


/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

// driver must be container class to talk to class packet, class sb

class driver;
packet pkt;
virtual switch_interface vi;
mailbox drv2sb;

function new(input virtual switch_interface vif, input mailbox mb);
this.vi = vif;
this.drv2sb = mb;
endfunction

task send_packet();
pkt = new();
assert(pkt.randomize());
@(posedge vi.clk);
vi.src_addr <= pkt.src_addr;
@(posedge vi.clk);
vi.src_data <= pkt.src_data;

pkt.print();
drv2sb.put(pkt);
endtask

endclass

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////

class monitor;
virtual switch_interface mi;
packet rcv_pkt;
mailbox mon2sb;
function new(input virtual switch_interface mif, input mailbox mb);
this.mi = mif;
this.mon2sb = mb;
endfunction

task collect_packet();
rcv_pkt = new();
@(posedge mi.clk);
rcv_pkt.src_addr = mi.cb.dst_addr;
@(posedge mi.clk);
rcv_pkt.src_data = mi.cb.dst_data;
rcv_pkt.rec_print();
mon2sb.put(rcv_pkt);
endtask
endclass

////////////////////////////////////////////////////////////////////////////////////////////////////////////////

class scoreboard;
mailbox rcv_from_drv;
mailbox rcv_from_mon;
function new(input mailbox drv2sb, input mailbox mon2sb);
this.rcv_from_drv = drv2sb;
this.rcv_from_mon = mon2sb;
endfunction

task compare (input mailbox rcv_from_drv, input mailbox rcv_from_mon);
bit error;
packet pkt_from_drv;
packet pkt_from_mon;
rcv_from_drv.get(pkt_from_drv);
rcv_from_mon.get(pkt_from_mon);
pkt_from_drv.print();
pkt_from_mon.rec_print();

if (pkt_from_mon.src_addr !== (pkt_from_drv.src_addr + 1)) begin
$display("time=%0t ERROR: Packet Mismatch, wrong addr", $time);
error++;
end
else if (pkt_from_mon.src_data !== (pkt_from_drv.src_data + 1)) begin
$display("time=%0t ERROR: Packet Mismatch, wrong data", $time);
error++;
end
else $display("time=%0t PASS: Packet Match", $time);
endtask
endclass

///////////////////////////////////////////////////////////////////////////////////////////////////////////////

class env;
driver drv;
monitor mon;
virtual switch_interface vi;
virtual switch_interface mi;
//add sb n mailbox to env neighbourbood
scoreboard sb;
mailbox drv2sb;
mailbox mon2sb;

function new(input virtual switch_interface vif, input virtual switch_interface mif);
this.vi = vif;
this.mi = mif;

//create objects (data members)
drv2sb = new();
mon2sb = new();

//hand over drv2sb to drv + sb, and mon2sb to mon + sb
drv = new(vif, drv2sb);
mon = new(mif, mon2sb);
sb = new(drv2sb, mon2sb);
endfunction

//env directs driver to send wiggle through run task
task run(input int count_packet);
for(int i=0; i <count_packet; i++) begin
   @(posedge vi.clk) drv.send_packet();
   @(posedge mi.clk) mon.collect_packet();
   sb.compare(drv2sb, mon2sb);
end
endtask
endclass
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

`include "packet6.sv"

program testcase(switch_interface tcifdriver, switch_interface tcifmonitor);

/*class packet_fixed_src_data extends packet;
constraint fixed_src_data { src_data == 32'hbabeface;
}
endclass*/

env env0;
int num_packet;
//packet_fixed_src_data testcasepacket;
packet pkt;
packet deep;
transaction deep_base;

initial begin
env0 = new(tcifdriver, tcifmonitor);

num_packet=$urandom_range(4,32);
//env0.run(num_packet);

pkt = new();
assert(pkt.randomize());
$display("Printing pkt");
pkt.print();
deep = new();
deep = pkt.deepcopy();
$display("Printing deep, after deepcopy from packetclass (just a template function, doesnt actually perform deepcopy)");
deep.print();
deep_base = new();
assert(deep_base.randomize());
$display("Printing deep_base");
deep_base.print();
deep = deep_base.deepcopy();
$display("Printing deep, after deepcopy from extended class");
deep.print();
#100 $finish;
end
endprogram

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////

module testbench();
bit [47:0] dst_addr;
bit [31:0] dst_data;
bit clk;
bit sop, eop;
bit valid;
always #10 clk = ~clk;

switch_interface sif (.clk (clk));

switch sw0 (
.clk (clk),
.src_addr (sif.src_addr),
.src_data (sif.src_data),
.dst_addr (sif.dst_addr), //monitor
.dst_data (sif.dst_data)
);

testcase itest (.tcifdriver (sif.testcase_port),
 .tcifmonitor (sif.testcase_port));

endmodule



SystemVerilog: transaction class with virtual interface

Another place to put a clocking block is inside the interface. The direction of signals is with respect to testcase.

interface switch_interface(input clk);
logic [47:0] src_addr;
logic [31:0] src_data;
logic [47:0] dst_addr;
logic [31:0] dst_data;
bit sop, eop, valid;

default clocking cb @(posedge clk);
output #1 src_addr, src_data, sop, eop;
input #2 dst_addr, dst_data, valid;
endclocking

modport testcase_port (input clk, clocking cb);

modport switch_port (input clk, src_addr, src_data, sop, eop);

endinterface


//using vi + clocking block
class packet;
rand bit [47:0] src_addr;
rand bit [31:0] src_data;
 //signal unrelated to rtl
static bit [15:0] pkt_id;
virtual switch_interface vi;

function new(input virtual switch_interface vif);
this.vi = vif; //vi now pointing to actual interface passed in new
pkt_id++;
endfunction

task send_packet();
@(posedge vi.clk);
vi.cb.sop <= 1'b1;
@(posedge vi.clk);
vi.cb.src_addr <= this.src_addr;
vi.cb.sop <= 1'b0;
repeat(6)@(posedge vi.clk);
vi.cb.src_data <= this.src_data;
repeat(4)@(posedge vi.clk);
vi.cb.eop <= 1'b1;
@(posedge vi.clk);
vi.cb.eop <= 1'b0;
endtask
endclass


`include "packet.sv"
program testcase(switch_interface tcif);
packet testcasepacket;

initial begin
testcasepacket = new();
testcasepacket.randomize();
testcasepacket.send_packet();
#100 $finish;
end
endprogram


module testbench();
bit clk;
always #10 clk = ~clk;

switch_interface sif (.clk (clk));

switch sw0 (
.clk (clk),
.src_addr (sif.src_addr),
.src_data (sif.src_data),
.sop(sif.sop),
.eop(sif.eop),
.valid(sif.valid),
.dst_addr (sif.dst_addr),
.dst_data (sif.dst_data)
);

testcase itest (.tcif (sif.testcase_port));

endmodule

Sunday, August 17, 2014

SystemVerilog: Clocking block + assert statements

We will needto add the keyword default before clocking block if we want to use a ##number_of_cycles delay even if we have just 1 clocking block.
Assign values to variables in clocking block by using blocking assignment.

I used this to actually see the effect of each type of clocking block..added this in initial block of testbench:
`ifdef postprocess
$vcdpluson(0,C1);
$vcdplustraceon(C1);
$vcdplusdeltacycleon;
$vcdplusglitchon;
`endif

program automatic testcase #(parameter WIDTH=4)
                 ( input bit clk,
                   input bit detect,
                   input  logic [WIDTH-1:0]  result,
                   output bit rst,
                   output bit en,
                   output bit pld,
                   output bit mode,
                   output logic [WIDTH-1:0]  preload_data);

`ifdef default_skew
default clocking clk_blk @ (posedge clk);
input detect, result;
output rst, en, pld, mode, preload_data;
endclocking
`endif

`ifdef hash<value>ns_skew
default clocking clk_blk @ (posedge clk);
input #<value1> detect, result;
output #<value2> rst, en, pld, mode, preload_data;
endclocking
`endif

`ifdef 1step_edge_skew
default clocking clk_blk @ (posedge clk);
input #1step detect, result;
output negedge rst, en, pld, mode, preload_data;
endclocking
`endif

initial begin
$monitor ("t=%3t: result=%2d, detect=%b", $time, result, detect);

clk_blk.rst <= 1;
clk_blk.en <= 0;
clk_blk.pld <= 0;
clk_blk.mode <= 0;

repeat (2) @(posedge clk);
clk_blk.rst<=0;

@(posedge clk);
clk_blk.en <= 1;
repeat(17)@(posedge clk);

clk_blk.en <= 0;
repeat (10)@(posedge clk);

$finish;
end

/*Some of the assert statements i used to test different aspects of counter:
assert property (@(posedge clk) result == {WIDTH{1'b1}} -> detect) else flag = 1;
assert property (@(posedge clk) en==0 |=> $stable(result)) else flag = 1;
assert property (@(posedge clk) pld == 1 && en == 1 |=> result == preload_data) else flag = 1;
assert property (@(posedge clk) rst==1 |=> result == {WIDTH{1'b0}}) else flag = 1;


final begin
if (flag) $display ("FAIL");
else $display ("PASS");
end
*/
endprogram


runsim:
if [ -z "$1" ]                           ##true if len of string is 0
   then
     echo "No skew specified, using default skews"
     vcs -full64 +vcs+lic+wait +v2k -sverilog +lint=PCWM -R +define+default_skew -l vcs.log ../../testbench.sv testcase.sv ../../counter.v
     echo "**INFO** Use runsim <skew> to specify skew, valid values for <skew> are default_skew, hash<value>n_skew, 1step_edge_skew"
     echo "**INFO** runsim finished test with +define+default_skew"
else
     echo $1
     vcs -full64 +vcs+lic+wait +v2k -sverilog +lint=PCWM -R +define+$1 -l vcs.log ../../testbench.sv testcase.sv ../../counter.v
     echo "**INFO** runsim finished test with +define+$1"
fi

command to run:
runsim <whtever_you_choose_from_default_skew_etc>


Using tasks:
task init(
          ref bit clk,
  ref bit system_clk,
          output bit rst,
          output bit en,
          output bit pld);
begin
rst <= 1;
en <= 0;
pld <= 0;
end
endtask

`include "tasks.sv"
program automatic testcase #(parameter WIDTH=4)
                 ( ref bit clk, ref bit system_clk,
                   input bit detect,
                   input  logic [WIDTH-1:0]  result,
                   ref bit rst,
                   ref bit en,
                   ref bit pld,
                   ref bit mode,
                   output logic [WIDTH-1:0]  preload_data);

bit flag;
logic [WIDTH-1:0]  X;

default clocking clk_blk @ (posedge clk);
input detect, result;
output rst, en, pld, mode, preload_data;
endclocking


initial begin
$monitor ("t=%3t: en = %b, pld = %b, result=%2d, detect=%b", $time, en, pld, result, detect);
X = $urandom_range(0, {WIDTH{1'b1}});
preload_data = $urandom_range(0, {WIDTH{1'b1}});

init(clk, system_clk, clk_blk.rst, clk_blk.en, clk_blk.pld);
rest of the program follows...

Progressing from Verilog to SysteVerilog: Program

Verilog:
module testbench #(parameter WIDTH=4)();
reg clk, rst, en, pld, mode;
reg [WIDTH-1:0] pld_data;
wire detect;
wire [WIDTH-1:0] result;
counter #(4) C1 (
               .clk (clk),
               .reset (rst),
               .enable (en),
               .preload (pld),
               .preload_data (pld_data),
               .mode (mode),
               .detect (detect),
               .result (result));

initial begin
$monitor ("t=%t: result=%d, detect=%b", $time, result, detect);
clk = 0; 

rst = 1; en = 0; pld = 0; mode = 0;
@(posedge clk) rst=0;

@(posedge clk);
en = 1;

repeat(10)@(posedge clk);
en = 0;

repeat (10)@(posedge clk);
$finish;
end

always
#5 clk = ~clk;
endmodule



SystemVerilog
Note that in the above testbench, the testbench is a module like RTL. This may cause issues. Using a "program" block essentially directs the tool to handle the RTL and stimulus generation aka testcase in separate delta cycle. 
The same code as above, put inside a "program testcase". A module testbench will hookup RTL and testcase. Also note that a program block cannot have always statements. So clock generation is performed in testbench. Initializing clk = 0 in initial block prevents clk = X.
I also had a test when I needed to issue $fisnish if things are idle for say 20 clock cycles. I put the check in testbench using an always block + counter. In my recent project at work, I added this check right in the driver module with a fork-join.  

program testcase #(parameter WIDTH=4)( input wire clk, 
                   input wire detect,
                   input  wire [WIDTH-1:0]  result,
                   output logic rst, 
                   output logic en, 
                   output logic pld, 
                   output logic mode,
                   output logic [WIDTH-1:0]  preload_data);

initial begin
$monitor("check");
$monitor ("t=%t: result=%d, detect=%b", $time, result, detect);

rst = 1; en = 0; pld = 0; mode = 0;

@(posedge clk);
rst=0;

@(posedge clk);
en = 1;
repeat(15)@(posedge clk);

en = 0;
repeat (10)@(posedge clk);

$finish;
end
endprogram


module testbench #(parameter WIDTH=4)();
logic clk;
logic [WIDTH-1:0] pld_data, result1;
testcase #(4) P1 (
               .clk (clk),
               .rst (rst),
               .en (en),
               .pld (pld),
               .preload_data (pld_data),
               .mode (mode),
               .detect (detect1),
               .result (result1)
);

counter #(4) C1 (
               .clk (clk),
               .reset (rst),
               .enable (en),
               .preload (pld),
               .preload_data (pld_data),
               .mode (mode),
               .detect (detect1),
               .result (result1)
);

initial begin
clk = 0;
end

always
#5 clk = ~clk;

endmodule

Run:
vcs +vcs+lic+wait +v2k -sverilog -R -l vcs.log testbench.sv testcase.sv counter.v