Monday, September 15, 2014

an example of VMM TB

//////////////////////////////////////////////////
//./rtl/dut.v
//////////////////////////////////////////////////
`timescale 1ns/1ps

`define PKT_RECV_IDLE   0
`define PKT_RECV_START  1
`define PKT_RECV_RECV   2
`define PKT_RECV_VALID  3
`define PKT_RECV_END    4

`define PKT_SENT_IDLE   0
`define PKT_SENT_VALID  1
`define PKT_SENT_END    2
`define PKT_SENT_FIN    3

module dut(
    clk    ,
    rst_n  ,

    addr   ,
    din    ,
    rw     ,
    dout   ,

    txd    ,
    tx_vld ,
    rxd    ,
    rx_vld
);

input         clk    ;
input         rst_n  ;

input  [7:0 ] addr  ;
input  [31:0] din   ;
input         rw    ;
output [31:0] dout  ;

input  [7:0 ] rxd    ;
input         rx_vld ;
output [7:0 ] txd    ;
output        tx_vld ;

wire        clk   ;
wire        rst_n ;

wire [7:0 ] addr  ;
wire [31:0] din   ;
wire        rw    ;
reg  [31:0] dout  ;

wire [7:0 ] rxd    ;
wire        rx_vld ;
reg  [7:0 ] txd    ;
reg         tx_vld ;

wire        write ;
wire        read  ;

wire [31:0] config_reg ;
reg         pkt_en;
reg  [31:0] min_pkt_size ;
reg  [31:0] max_pkt_size ;

reg  [7:0 ] mem[0:512];
reg  [9:0 ] mem_wr_ptr;
reg  [9:0 ] mem_rd_ptr;
reg  [9:0 ] need_to_sent_pkt_size;
reg  [9:0 ] sending_pkt_size;
reg  [15:0] header;

reg  [2:0 ] pkt_recv_status;
reg  [1:0 ] pkt_sent_status;

reg         new_pkt;
reg         one_pkt_sent;

reg  [1:0 ] pkt_no;

assign write = (rw == 1'b0);
assign read  = (rw == 1'b1);

assign config_reg = {31'h0, pkt_en};

always @(posedge clk or negedge rst_n) begin
    if(rst_n == 1'b0) begin
        pkt_en <= 1'b0;
    end
    else begin
        if((addr == 8'h0) && (write == 1'b1)) begin
            pkt_en <= din[0];
        end
    end
end

always @(posedge clk or negedge rst_n) begin
    if(rst_n == 1'b0) begin
        min_pkt_size <= 32'd64;
    end
    else begin
        if((addr == 8'h4) && (write == 1'b1)) begin
            if((din[9:0] >= 64) && (din[9:0] < max_pkt_size[9:0]))
                min_pkt_size[9:0] <= din[9:0];
        end
    end
end

always @(posedge clk or negedge rst_n) begin
    if(rst_n == 1'b0) begin
        max_pkt_size <= 32'd512;
    end
    else begin
        if((addr == 8'h8) && (write == 1'b1)) begin
            if((din[9:0] <= 512) && (din[9:0] > min_pkt_size[9:0]))
                max_pkt_size[9:0] <= din[9:0];
        end
    end
end

always @(*) begin
    case(addr)
        8'h0    : dout = config_reg;
        8'h4    : dout = min_pkt_size;
        8'h8    : dout = max_pkt_size;
        default : dout = 32'h0;
    endcase
end

always @(posedge clk or negedge rst_n) begin
    if(rst_n == 1'b0) begin
        pkt_recv_status <= 'h0;
        pkt_recv_status <= `PKT_RECV_IDLE;
        mem_wr_ptr <= 8'h0;
        new_pkt    <= 1'b0;
    end
    else begin
        case(pkt_recv_status)
            `PKT_RECV_IDLE: begin
                mem_wr_ptr <= 8'h0;
                if((rx_vld == 1'b1) && (pkt_en == 1'b1)) begin
                    mem_wr_ptr <= mem_wr_ptr + 1;
                    pkt_recv_status <= `PKT_RECV_START;
                    mem[mem_wr_ptr] <= rxd;
                    header[15:8] = rxd;
                end
            end
            `PKT_RECV_START: begin
                if(rx_vld == 1'b1) begin
                    mem_wr_ptr <= mem_wr_ptr + 1;
                    pkt_recv_status <= `PKT_RECV_RECV;
                    mem[mem_wr_ptr] <= rxd;
                    if(mem_wr_ptr == 1) begin
                        header[7:0] = rxd;
                    end
                end
                else begin
                    pkt_recv_status <= `PKT_RECV_END;
                end
            end
            `PKT_RECV_RECV: begin
                if(rx_vld == 1'b1) begin
                    if(mem_wr_ptr == max_pkt_size) begin
                        pkt_recv_status <= `PKT_RECV_END;
                    end
                    else begin
                        mem_wr_ptr <= mem_wr_ptr + 1;
                        pkt_recv_status <= `PKT_RECV_RECV;
                        mem[mem_wr_ptr] <= rxd;
                    end
                end
                else begin
                    pkt_recv_status <= `PKT_RECV_VALID;
                end
            end
            `PKT_RECV_VALID: begin
                if(mem_wr_ptr >= min_pkt_size && header == 16'h55d5) begin
                    new_pkt <= 1'b1;
                    need_to_sent_pkt_size <= mem_wr_ptr;
                end
                pkt_recv_status <= `PKT_RECV_END;
            end
            `PKT_RECV_END: begin
                new_pkt <= 1'b0;
                if(rx_vld == 1'b0) begin
                    pkt_recv_status <= `PKT_RECV_IDLE;
                end
            end
            default : begin
            end
        endcase
    end
end

always @(posedge clk or negedge rst_n) begin
    if(rst_n == 1'b0) begin
        pkt_sent_status <= `PKT_SENT_IDLE;
        pkt_sent_status <= 'h0;
        mem_rd_ptr <= 'h0;
        sending_pkt_size <= 'h0;
        txd <= 'h0;
        tx_vld <= 1'b0;
        one_pkt_sent <= 1'b0;
    end
    else begin
        case(pkt_sent_status)
            `PKT_SENT_IDLE: begin
                if(pkt_no > 0) begin
                    pkt_sent_status <= `PKT_SENT_VALID;
                    sending_pkt_size <= need_to_sent_pkt_size;
                    mem_rd_ptr <= 'h0;
                end
            end
            `PKT_SENT_VALID: begin
                if(mem_rd_ptr < sending_pkt_size) begin
                    mem_rd_ptr <= mem_rd_ptr + 1;
                    txd <= mem[mem_rd_ptr];
                    tx_vld <= 1'b1;
                end
                else begin
                    pkt_sent_status <= `PKT_SENT_END;
                    txd <= 'h0;
                    tx_vld <= 1'b0;
                end
            end
            `PKT_SENT_END: begin
                pkt_sent_status <= `PKT_SENT_FIN;
                one_pkt_sent <= 1'b1;
            end
            `PKT_SENT_FIN: begin
                pkt_sent_status <= `PKT_SENT_IDLE;
                one_pkt_sent <= 1'b0;
            end
            default: begin
            end
        endcase
    end
end

always @(posedge clk or negedge rst_n) begin
    if(rst_n == 1'b0) begin
        pkt_no <= 'h0;
    end
    else begin
        if(new_pkt == 1'b1) begin
            pkt_no <= pkt_no + 1;
        end
        else if(one_pkt_sent == 1'b1) begin
            pkt_no <= pkt_no - 1;
        end
    end
end

endmodule

//////////////////////////////////////////////////
//./env/cpu_interface_env/cpu_interface.svi
//////////////////////////////////////////////////
`ifndef __CPU_INTERFACE_SVI__
`define __CPU_INTERFACE_SVI__
interface cpu_interface (input logic clk);

logic        rst_n ;
logic [7:0 ] addr  ;
logic [31:0] din   ;
logic        rw    ;
logic [31:0] dout  ;

clocking cb @(posedge clk);
    default input #1 output #1;
    output addr ;
    output rw   ;
    output dout ;
    input  din  ;
endclocking

modport master(clocking cb, output rst_n);

endinterface
`endif


//////////////////////////////////////////////////
//./env/cpu_interface_env/cpu_trans.sv
//////////////////////////////////////////////////
`ifndef __CPU_TRANS_SV__
`define __CPU_TRANS_SV__

class cpu_trans extends vmm_data;

static  vmm_log log = new("cpu_trans", "class");

rand logic [7:0] addr;
rand logic rw;
rand logic [31:0] dout;
logic [31:0] din = 32'h0;

extern function new();
extern virtual function vmm_data copy(vmm_data to);
extern virtual function string psdisplay(string prefix );

endclass

function cpu_trans::new();
    super.new(this.log);
endfunction

function vmm_data cpu_trans::copy(vmm_data to = null);
    cpu_trans cpy;

    if(to == null)
        cpy = new();
    else if(!$cast(cpy, to)) begin
        `vmm_fatal(this.log, "Cannot cast to to cpy in cpu_trans");
        copy = null;
        return copy;
    end

    super.copy_data(cpy);

    cpy.addr = this.addr;
    cpy.rw   = this.rw;
    cpy.dout = this.dout;
    cpy.din  = this.din;

    copy = cpy;
endfunction

function string cpu_trans::psdisplay(string prefix = "Note");
  $write(psdisplay, "[%s]%t addr = %0h, dout = %0h, rw = %d, din = %0h", prefix, $realtime, addr, dout, rw, din);
endfunction

`vmm_channel(cpu_trans)
`vmm_atomic_gen(cpu_trans, "CPU Transaction Atomic")
`vmm_scenario_gen(cpu_trans, "CPU Transaction Scenario")

`endif

//////////////////////////////////////////////////
//./env/cpu_interface_env/cpu_driver.sv
//////////////////////////////////////////////////
`ifndef __CPU_DRIVER_SV__
`define __CPU_DRIVER_SV__

`include "vmm.sv"
`include "cpu_interface.svi"
`include "cpu_trans.sv"
`include "cpu_sb.sv"

class cpu_driver extends vmm_xactor;

string name;
virtual cpu_interface.master cpu_intf;
cpu_trans_channel gen2drv_chan  ;

extern function new (
    string instance,
    integer stream_id = -1,
    virtual cpu_interface.master cpu_intf,
    cpu_trans_channel gen2drv_chan = null
);

extern virtual task main() ;
extern virtual task reset() ;

endclass

//callback class
virtual class cpu_driver_callbacks extends vmm_xactor_callbacks;

   virtual task driver_pre_tx (
       cpu_driver    xactor,
       ref cpu_trans trans,
       ref bit        drop
   );
   endtask

   virtual task driver_post_tx (
       cpu_driver xactor,
       cpu_trans  trans
   );
   endtask

endclass

class cpu_driver_to_sb extends cpu_driver_callbacks;
    cpu_sb cpu_scb;

    function new(cpu_sb cpu_scb);
        this.cpu_scb = cpu_scb;
    endfunction: new

    virtual task driver_post_tx (cpu_driver xactor, cpu_trans trans);
        cpu_scb.compare(trans);
    endtask

endclass

function cpu_driver::new(
    string instance,
    integer stream_id,
    virtual cpu_interface.master cpu_intf,
    cpu_trans_channel gen2drv_chan
);

    super.new("CPU Driver", instance, stream_id) ;

    this.cpu_intf = cpu_intf;

    if(gen2drv_chan == null)
        gen2drv_chan = new("CPU generator to driver channel", instance);
    this.gen2drv_chan = gen2drv_chan;

endfunction

task cpu_driver::main() ;
    cpu_trans tr;
    cpu_trans cpy_tr;
    bit drop;

    fork
        super.main();
    join_none

    while (1) begin
        this.wait_if_stopped_or_empty(this.gen2drv_chan) ;
        gen2drv_chan.get(tr);

        `vmm_callback(cpu_driver_callbacks, driver_pre_tx(this, tr, drop));
        if (drop == 1) begin
            `vmm_note(log, tr.psdisplay("Dropped"));
            continue;
        end

        @(cpu_intf.cb);
        cpu_intf.cb.addr <= tr.addr;
        cpu_intf.cb.dout <= tr.dout;
        cpu_intf.cb.rw   <= tr.rw;
        if(tr.rw == 1'b1) begin
            @(cpu_intf.cb);
            tr.din  = cpu_intf.cb.din;
        end

        $cast(cpy_tr, tr.copy());
        `vmm_callback(cpu_driver_callbacks, driver_post_tx(this, cpy_tr));

        `vmm_debug(log, tr.psdisplay("CPU Driver ==>"));

    end
endtask

task cpu_driver::reset() ;
    @(cpu_intf.cb);
    cpu_intf.rst_n = 1'b0;
    @(cpu_intf.cb);
    cpu_intf.rst_n = 1'b1;
    @(cpu_intf.cb);
endtask

`endif

//////////////////////////////////////////////////
//./env/cpu_interface_env/cpu_sb.sv
//////////////////////////////////////////////////
`ifndef __CPU_SB_SV__
`define __CPU_SB_SV__

`include "vmm.sv"
`include "cpu_trans.sv"
`include "cpu_driver.sv"

class cpu_sb;

string name;

logic [31:0] cpu_reg [7:0];
integer error_no = 0;
integer total_no = 0;

extern function new(string name = "CPU Scoreboard");
extern virtual function bit compare(vmm_data data);
extern virtual function report(string prefix);

endclass

//class cpu_driver_to_sb extends cpu_driver_callbacks;
//    cpu_sb cpu_scb;
//
//    function new(cpu_sb cpu_scb);
//        this.cpu_scb = cpu_scb;
//    endfunction: new
//
//    virtual task driver_post_tx (cpu_driver xactor, cpu_trans trans);
//        cpu_scb.compare(trans);
//    endtask
//
//endclass

function cpu_sb::new(string name);
    this.name = name;

    for(int i=0; i<128; i++) begin
        cpu_reg[i] = 32'h0;
    end
endfunction

function bit cpu_sb::compare(vmm_data data);
    cpu_trans tr;
    string message;

    $cast(tr, data);

    if(tr.rw == 1'b0) begin
        cpu_reg[tr.addr] = tr.din;
    end
    else if(tr.rw == 1'b1) begin
        if(cpu_reg[tr.addr] != tr.din) begin
            message = $psprintf("[Error] %t Comparision result is not correct\n", $realtime);
            message = { message, $psprintf("cpu_reg[%d] = %0h, tr.din = %0h\n", tr.addr, cpu_reg[tr.addr], tr.din) };
            $display(message);
            error_no++;
        end
        else begin
            $display("[Note] %t comparison correct", $realtime);
        end
    end
    else begin
        $display("[Error] tr.rw can only be 0 or 1");
        error_no++;
    end
endfunction

function cpu_sb::report(string prefix);
    $display("Total: %d, Error: %d", total_no, error_no);
endfunction

`endif

//////////////////////////////////////////////////
//./env/cpu_interface_env/cpu_env.sv
//////////////////////////////////////////////////
`ifndef __CPU_ENV_SV__
`define __CPU_ENV_SV__

`include "cpu_interface.svi"
`include "cpu_trans.sv"
`include "cpu_driver.sv"
`include "cpu_sb.sv"

class cpu_env extends vmm_env;

virtual cpu_interface.master cpu_intf;
vmm_log log;

cpu_trans_channel     gen2drv_chan;
cpu_trans_atomic_gen  cpu_gen;
cpu_driver            cpu_drv;
cpu_sb                cpu_scb;
cpu_driver_to_sb      cpu_drv_cbs_scb;

extern function new(virtual cpu_interface.master cpu_intf);

extern virtual function void gen_cfg();
extern virtual function void build();
extern virtual task reset_dut();
extern virtual task cfg_dut();
extern virtual task start();
extern virtual task wait_for_end();
extern virtual task stop();
extern virtual task cleanup();
extern virtual task report();

endclass

function cpu_env::new(virtual cpu_interface.master cpu_intf);
    super.new("CPU ENV");

    this.cpu_intf = cpu_intf;

    log = new("cpu", "env");

endfunction

function void cpu_env::gen_cfg();
    super.gen_cfg();
endfunction

function void cpu_env::build();
    super.build();

    gen2drv_chan = new("CPU Generator to Driver Channel", "channel");
    cpu_gen = new("CPU Generator", 1, gen2drv_chan);
    cpu_drv = new("CPU Driver", 1, cpu_intf, gen2drv_chan);
    cpu_scb  = new("CPU Scoreboard");

    cpu_drv_cbs_scb = new(cpu_scb);
    cpu_drv.append_callback(cpu_drv_cbs_scb);

endfunction

task cpu_env::reset_dut();
    super.reset_dut();

    cpu_drv.reset();

endtask

task cpu_env::cfg_dut();
    super.cfg_dut();

endtask

task cpu_env::start();
    super.start();

    cpu_gen.start_xactor();
    cpu_drv.start_xactor();
endtask

task cpu_env::wait_for_end();
    super.wait_for_end();

    fork
        cpu_gen.notify.wait_for(cpu_trans_atomic_gen::DONE);
    join_any

endtask

task cpu_env::stop();
    super.stop();

    cpu_gen.stop_xactor();
    cpu_drv.stop_xactor();

endtask

task cpu_env::cleanup();
    super.cleanup();

endtask

task cpu_env::report();
    super.report();

endtask

`endif

//////////////////////////////////////////////////
//./env/packet_env/pkt_interface.svi
//////////////////////////////////////////////////
`ifndef __PKT_INTERFACE_SVI__
`define __PKT_INTERFACE_SVI__
interface pkt_interface (input logic clk);

logic       rst_n  ;
logic [7:0] rxd    ;
logic       rx_vld ;
logic [7:0] txd    ;
logic       tx_vld ;

clocking cb @(posedge clk);
    default input #1 output #1;
    output rst_n  ;
    output txd    ;
    output tx_vld ;
    input  rxd    ;
    input  rx_vld ;
endclocking

modport master(clocking cb, output rst_n);

endinterface
`endif

//////////////////////////////////////////////////
//./env/packet_env/pkt_trans.sv
//////////////////////////////////////////////////
`ifndef __PKT_TRANS_SV__
`define __PKT_TRANS_SV__

`include "vmm.sv"

class pkt_trans extends vmm_data;

static  vmm_log log = new("pkt_trans", "class");

typedef enum {GOOD, BAD} head_type;
typedef enum {LONG, SHORT, NORMAL} packet_length;
rand head_type     pkt_head_type     ;
rand packet_length pkt_packet_length ;
rand logic [15:0] header     ;
rand logic [7:0 ] payload[$] ;

extern function new();
extern virtual function vmm_data copy(vmm_data to);
extern virtual function string psdisplay(string prefix );
extern function bit compare(vmm_data to, output string diff, input int kind = -1);
extern function int unsigned byte_pack(ref logic [7:0] bytes[], input int unsigned offset, input int kind = -1);

constraint con_head_type {
    solve pkt_head_type before header;
    pkt_head_type dist {GOOD := 5, BAD := 1};
    (pkt_head_type == GOOD) -> (header == 16'h55d5);
    (pkt_head_type == BAD) -> (header inside {[0:16'h55d4], [16'h55d6:16'hffff]});
}

constraint con_pkt_len {
    solve pkt_packet_length before payload;
    pkt_packet_length dist {LONG := 1, SHORT := 1, NORMAL := 5};
    (pkt_packet_length == LONG) -> (payload.size() inside {[0:49]});
    (pkt_packet_length == SHORT) -> (payload.size() inside {[50:500]});
    (pkt_packet_length == NORMAL) -> (payload.size() inside {[501:600]});
}

endclass

function pkt_trans::new();
     super.new(this.log);
endfunction

function bit pkt_trans::compare(vmm_data to, output string diff, input int kind = -1);
    pkt_trans tr;

    $cast(tr, to);

    if(header != tr.header) begin
        diff = "Header Mismatch:\n";
        diff = { diff, $psprintf("Header Sent:  %p\nHeader Received: %p", header, tr.header) };
        return 0;
    end

    if (payload.size() != tr.payload.size()) begin
        diff = "Payload Size Mismatch:\n";
        diff = { diff, $psprintf("payload.size() = %0d, tr.payload.size() = %0d\n", payload.size(), tr.payload.size()) };
        return 0;
    end

    if (payload == tr.payload) ;
    else begin
        diff = "Payload Content Mismatch:\n";
        diff = { diff, $psprintf("Packet Sent:  %p\nPkt Received: %p", payload, tr.payload) };
        return 0;
    end

    diff = "Successfully Compared";
    return 1;
endfunction

function string pkt_trans::psdisplay(string prefix);
    $write(psdisplay, "[%s]%t", prefix, $realtime);
    $write(psdisplay, "    [%s]%t header = %0h, payload size = %0d", prefix, $realtime, header, payload.size());
    foreach(payload[i])
        $display(psdisplay, "    [%s]%t payload[%0d] = %0d", prefix, $realtime, i, payload[i]);
endfunction

function int unsigned pkt_trans::byte_pack(ref logic [7:0] bytes[], input int unsigned offset, input int kind = -1);
    if(bytes.size() >= 2) begin
        header[15:8] = bytes[0];
        header[7:0]  = bytes[1];
    end

    for(int i=2; i<bytes.size(); i++) begin
        payload.push_back(bytes[i]);
    end

    return 1;

endfunction

function vmm_data pkt_trans::copy(vmm_data to = null);
    pkt_trans cpy;

    if(to == null)
        cpy = new();
    else if(!$cast(cpy, to)) begin
        `vmm_fatal(this.log, "Cannot cast to to cpy in pkt_trans");
        copy = null;
        return copy;
    end

    super.copy_data(cpy);

    cpy.pkt_head_type     = this.pkt_head_type ;
    cpy.pkt_packet_length = this.pkt_packet_length ;
    cpy.header = this.header;
    foreach(this.payload[i]) begin
        cpy.payload.push_back(this.payload[i]);
    end

    copy = cpy;
endfunction

`vmm_channel(pkt_trans)
`vmm_atomic_gen(pkt_trans, "Packet Transaction Atomic")
`vmm_scenario_gen(pkt_trans, "Packet Transaction Scenario")

`endif

//////////////////////////////////////////////////
//./env/packet_env/pkt_driver.sv
//////////////////////////////////////////////////
`ifndef __PKT_DRIVER_SV__
`define __PKT_DRIVER_SV__

`include "vmm.sv"
`include "pkt_interface.svi"
`include "pkt_trans.sv"

class pkt_driver extends vmm_xactor;

string name;
virtual pkt_interface.master pkt_intf;
pkt_trans_channel gen2drv_chan;

extern function new(
    string instance,
    integer stream_id = -1,
    virtual pkt_interface.master pkt_intf,
    pkt_trans_channel gen2drv_chan = null
);
extern virtual task main() ;
extern virtual task reset() ;

endclass

//callback class
virtual class pkt_driver_callbacks extends vmm_xactor_callbacks;

   virtual task driver_pre_tx (
       pkt_driver    xactor,
       ref pkt_trans trans,
       ref bit        drop
   );
   endtask

   virtual task driver_post_tx (
       pkt_driver xactor,
       pkt_trans  trans
   );
   endtask

endclass

function pkt_driver::new(
    string instance,
    integer stream_id = -1,
    virtual pkt_interface.master pkt_intf,
    pkt_trans_channel gen2drv_chan = null
 );

    super.new("Packet Driver", instance, stream_id) ;

    this.pkt_intf = pkt_intf;

    if(gen2drv_chan == null)
        gen2drv_chan = new("Packet generator to driver channel", instance);
    this.gen2drv_chan = gen2drv_chan;

endfunction

task pkt_driver::main();
    integer delay = 0;
    pkt_trans tr;
    pkt_trans tr_cpy;
    bit drop;

    fork
        super.main();
    join_none

    while (1) begin
        this.wait_if_stopped_or_empty(this.gen2drv_chan) ;
        gen2drv_chan.get(tr);

        $cast(tr_cpy, tr.copy());

        `vmm_callback(pkt_driver_callbacks, driver_pre_tx(this, tr_cpy, drop));
        if (drop == 1) begin
            `vmm_note(log, tr.psdisplay("Dropped"));
            continue;
        end

        @(pkt_intf.cb);
        pkt_intf.cb.tx_vld <= 1'b1;
        pkt_intf.cb.txd    <= tr.header[15:8];
        @(pkt_intf.cb);
        pkt_intf.cb.txd    <= tr.header[7:0];

        foreach(tr.payload[i]) begin
            @(pkt_intf.cb);
            pkt_intf.cb.txd <= tr.payload[i];
        end

        delay = tr.payload.size() + 2;
        repeat(delay) begin
            @(pkt_intf.cb);
            pkt_intf.cb.tx_vld <= 1'b0;
        end

        `vmm_callback(pkt_driver_callbacks, driver_post_tx(this, tr));

        `vmm_debug(log, tr.psdisplay("Packet Driver ==>"));

    end
endtask

task pkt_driver::reset();
    @(pkt_intf.cb);
    pkt_intf.cb.txd <= 8'h0;
    pkt_intf.cb.tx_vld <= 1'b0;

    @(pkt_intf.cb);
    pkt_intf.rst_n = 1'b0;
    @(pkt_intf.cb);
    pkt_intf.rst_n = 1'b1;
    @(pkt_intf.cb);
endtask

`endif

//////////////////////////////////////////////////
//./env/packet_env/pkt_monitor.sv
//////////////////////////////////////////////////
`ifndef __PKT_MONITOR_SV__
`define __PKT_MONITOR_SV__

`include "vmm.sv"
`include "pkt_interface.svi"
`include "pkt_trans.sv"

class pkt_monitor extends vmm_xactor;

string name;
virtual pkt_interface.master pkt_intf;

extern function new(
    string instance,
    integer stream_id = -1,
    virtual pkt_interface.master pkt_intf
);
extern virtual task main() ;

endclass

//callback class
virtual class pkt_monitor_callbacks extends vmm_xactor_callbacks;

   virtual task monitor_pre_tx (
       pkt_monitor   xactor,
       ref pkt_trans trans,
       ref bit       drop
   );
   endtask

   virtual task monitor_post_tx (
       pkt_monitor xactor,
       pkt_trans   trans
   );
   endtask

endclass

function pkt_monitor::new (
    string instance,
    integer stream_id = -1,
    virtual pkt_interface.master pkt_intf
);

    super.new("Packet Monitor", instance, stream_id) ;

    this.pkt_intf = pkt_intf;

endfunction

task pkt_monitor::main();
    logic [7:0] rxd[$];
    logic [7:0] rxd_1[];
    pkt_trans tr;
    bit drop;

    fork
        super.main();
    join_none

    while (1) begin
        tr = new();

        `vmm_callback(pkt_monitor_callbacks, monitor_pre_tx(this, tr, drop));
        if (drop == 1) begin
            `vmm_note(log, tr.psdisplay("Dropped"));
            continue;
        end

        @(pkt_intf.cb);
        while(pkt_intf.cb.rx_vld == 1'b0) begin
            @(pkt_intf.cb);
        end

        while(pkt_intf.cb.rx_vld == 1'b1) begin
            rxd.push_back(pkt_intf.cb.rxd);
            @(pkt_intf.cb);
        end

        rxd_1 = new[rxd.size()];
        foreach(rxd[i]) begin
            rxd_1[i] = rxd[i];
        end
        tr.byte_pack(rxd_1, 0);

        rxd.delete();
        rxd_1.delete();

        `vmm_callback(pkt_monitor_callbacks, monitor_post_tx(this, tr));

        `vmm_debug(log, tr.psdisplay("Packet monitor ==>"));

    end

endtask

`endif

//////////////////////////////////////////////////
//./env/packet_env/pkt_sb.sv
//////////////////////////////////////////////////
`ifndef __PKT_sb_SV__
`define __PKT_sb_SV__

`include "vmm.sv"
`include "vmm_sb.sv"
`include "pkt_trans.sv"
`include "pkt_driver.sv"
`include "pkt_monitor.sv"

class pkt_sb extends vmm_sb_ds;

logic [9:0] min_pkt_size;
logic [9:0] max_pkt_size;

extern function new();
extern virtual function bit compare(vmm_data actual, vmm_data expected);
extern function void report(int exp_stream_id = -1, int inp_stream_id = -1);

endclass

class pkt_driver_to_sb extends pkt_driver_callbacks;
   pkt_sb pkt_scb;

   function new(pkt_sb pkt_scb);
      this.pkt_scb = pkt_scb;
   endfunction: new

   virtual task driver_pre_tx (pkt_driver xactor, ref pkt_trans trans, ref bit drop);
       if(trans.pkt_head_type == pkt_trans::GOOD) begin
           if(trans.payload.size() >= (pkt_scb.min_pkt_size - 2) && trans.payload.size() <= (pkt_scb.max_pkt_size - 2)) begin
               pkt_scb.insert(trans, vmm_sb_ds::INPUT, .exp_stream_id(0));
           end
       end
   endtask

endclass

class pkt_monitor_to_sb extends pkt_monitor_callbacks;
    pkt_sb pkt_scb;

    function new(pkt_sb pkt_scb);
        this.pkt_scb = pkt_scb;
    endfunction: new

    virtual task monitor_post_tx (pkt_monitor xactor, pkt_trans trans);
        pkt_scb.expect_in_order(trans, 0);
    endtask

endclass

function pkt_sb::new();
      super.new("Packet Scoreboard");

      this.define_stream(0, "Master",  INPUT);
      this.define_stream(0, "Slave 0", EXPECT);

endfunction


function bit pkt_sb::compare(vmm_data actual, vmm_data expected);
    pkt_trans act, exp;
    string diff;

    $cast(act, actual);
    $cast(exp, expected);

    return act.compare(exp, diff);

endfunction

function void pkt_sb::report(int exp_stream_id = -1, int inp_stream_id = -1);
    super.report(exp_stream_id, inp_stream_id);
endfunction

`endif

//////////////////////////////////////////////////
//./env/packet_env/pkt_env.sv
//////////////////////////////////////////////////
`ifndef __PKT_ENV_SV__
`define __PKT_ENV_SV__

`include "pkt_interface.svi"
`include "pkt_trans.sv"
`include "pkt_driver.sv"
`include "pkt_sb.sv"
`include "pkt_monitor.sv"

class pkt_env extends vmm_env;

virtual pkt_interface.master pkt_intf;
vmm_log log;

pkt_trans_channel  gen2drv_chan;

pkt_trans_atomic_gen pkt_gen;
pkt_driver           pkt_drv;
pkt_sb               pkt_scb;
pkt_monitor          pkt_mon;
pkt_driver_to_sb     pkt_drv_cbs_scb;
pkt_monitor_to_sb    pkt_mon_cbs_scb;

extern function new(virtual pkt_interface.master pkt_intf);

extern virtual function void gen_cfg();
extern virtual function void build();
extern virtual task reset_dut();
extern virtual task cfg_dut();
extern virtual task start();
extern virtual task wait_for_end();
extern virtual task stop();
extern virtual task cleanup();
extern virtual task report();

endclass

function pkt_env::new(virtual pkt_interface.master pkt_intf);
    super.new("Packet ENV");

    this.pkt_intf = pkt_intf;

    log = new("packet", "env");

endfunction

function void pkt_env::gen_cfg();
    super.gen_cfg();
endfunction

function void pkt_env::build();
    super.build();

    gen2drv_chan = new("Packet Generator to Driver Channel", "channel");
    pkt_gen = new("Packet Generator", 1, gen2drv_chan);
    pkt_drv = new("Packet Driver", 1, pkt_intf, gen2drv_chan);
    pkt_mon = new("Packet Monitor", 1, pkt_intf);
    pkt_scb  = new();

    pkt_drv_cbs_scb = new(pkt_scb);
    pkt_drv.append_callback(pkt_drv_cbs_scb);

    pkt_mon_cbs_scb = new(pkt_scb);
    pkt_mon.append_callback(pkt_mon_cbs_scb);

endfunction

task pkt_env::reset_dut();
    super.reset_dut();

    pkt_drv.reset();

endtask

task pkt_env::cfg_dut();
    super.cfg_dut();

endtask

task pkt_env::start();
    super.start();

    pkt_gen.start_xactor();
    pkt_drv.start_xactor();
    pkt_mon.start_xactor();
endtask

task pkt_env::wait_for_end();
    super.wait_for_end();

    fork
        pkt_gen.notify.wait_for(pkt_trans_atomic_gen::DONE);
    join_any

endtask

task pkt_env::stop();
    super.stop();

    pkt_gen.stop_xactor();
    pkt_drv.stop_xactor();
    pkt_mon.stop_xactor();

endtask

task pkt_env::cleanup();
    super.cleanup();

endtask

task pkt_env::report();
    super.report();

endtask

`endif

//////////////////////////////////////////////////
//./env/env.sv
//////////////////////////////////////////////////
`ifndef __ENV_SV__
`define __ENV_SV__

`include "cpu_interface.svi"
`include "pkt_interface.svi"
`include "cpu_env.sv"
`include "pkt_env.sv"

class env extends vmm_env;
vmm_log log;

cpu_env cpu_e;
pkt_env pkt_e;

extern function new(virtual cpu_interface.master cpu_intf, virtual pkt_interface.master pkt_intf);

extern virtual function void gen_cfg();
extern virtual function void build();
extern virtual task reset_dut();
extern virtual task cfg_dut();
extern virtual task start();
extern virtual task wait_for_end();
extern virtual task stop();
extern virtual task cleanup();
extern virtual task report();

endclass

function env::new(virtual cpu_interface.master cpu_intf, virtual pkt_interface.master pkt_intf);
    super.new("Packet ENV");

    cpu_e = new(cpu_intf);
    pkt_e = new(pkt_intf);

    log = new("env", "env");
endfunction

function void env::gen_cfg();
    super.gen_cfg();
endfunction

function void env::build();
    super.build();

    cpu_e.build();
    pkt_e.build();

endfunction

task env::reset_dut();
    super.reset_dut();

    cpu_e.reset_dut();
    pkt_e.reset_dut();

endtask

task env::cfg_dut();
    super.cfg_dut();

endtask

task env::start();
    super.start();

    cpu_e.start();
    pkt_e.start();
endtask

task env::wait_for_end();
    super.wait_for_end();

    cpu_e.wait_for_end();
    pkt_e.wait_for_end();

endtask

task env::stop();
    super.stop();

    cpu_e.stop();
    pkt_e.stop();

endtask

task env::cleanup();
    super.cleanup();

endtask

task env::report();
    super.report();

endtask

`endif

//////////////////////////////////////////////////
//./test/test.sv
//////////////////////////////////////////////////
`ifndef __TEST_SV__
`define __TEST_SV__

`include "env.sv"

program test(cpu_interface.master cpu_intf, pkt_interface.master pkt_intf);

class pkt_trans_ext extends pkt_trans;

//constraint con_pkt_len_test {
//    payload.size() == 100;
//    pkt_head_type == GOOD;
//}

endclass

cpu_trans     cpu_tr;
cpu_trans     cpu_tr_cpy;
pkt_trans_ext pkt_tr;
env e;

initial begin
    cpu_tr = new();
    pkt_tr = new();
    e = new(cpu_intf, pkt_intf);
    e.build();

    //if stop_after_n_insts is 0, the generator will generate transaction forever
    //e.cpu_e.cpu_gen.stop_after_n_insts = 0;
    e.pkt_e.pkt_gen.randomized_obj = pkt_tr;

    e.start();
    e.cpu_e.cpu_gen.stop_xactor();
    e.pkt_e.pkt_gen.stop_xactor();

    cpu_tr.randomize() with {addr == 'h4; rw == 1'b0; dout == 'd65;};
    $cast(cpu_tr_cpy, cpu_tr.copy());
    e.cpu_e.gen2drv_chan.put(cpu_tr_cpy);

    cpu_tr.randomize() with {addr == 'h8; rw == 1'b0; dout == 'd500;};
    $cast(cpu_tr_cpy, cpu_tr.copy());
    e.cpu_e.gen2drv_chan.put(cpu_tr_cpy);

    cpu_tr.randomize() with {addr == 'h0; rw == 1'b0; dout == 'h1;};
    $cast(cpu_tr_cpy, cpu_tr.copy());
    e.cpu_e.gen2drv_chan.put(cpu_tr_cpy);

    repeat(100) begin
        @(cpu_intf.cb);
    end

    e.pkt_e.pkt_scb.min_pkt_size = 65;
    e.pkt_e.pkt_scb.max_pkt_size = 500;
    e.pkt_e.pkt_gen.stop_after_n_insts = 100;
    e.pkt_e.pkt_gen.start_xactor();

    e.pkt_e.wait_for_end();
    repeat(10000) begin
        @(pkt_intf.cb);
    end
    e.report();
    $finish();
end

endprogram
`endif

//////////////////////////////////////////////////
//./env/top.sv
//////////////////////////////////////////////////
`timescale 1ns/1ps

`include "cpu_interface.svi"
`include "pkt_interface.svi"
`include "test.sv"

module top;
parameter clock_cycle = 10;

logic clk;

cpu_interface cpu_intf(clk);
pkt_interface pkt_intf(clk);

test u_test(cpu_intf, pkt_intf);

dut u_dut (
    .clk    (pkt_intf.clk    ) ,
    .rst_n  (pkt_intf.rst_n  ) ,

    .addr   (cpu_intf.addr   ) ,
    .rw     (cpu_intf.rw     ) ,
    .din    (cpu_intf.dout   ) ,
    .dout   (cpu_intf.din    ) ,

    .txd    (pkt_intf.rxd    ) ,
    .tx_vld (pkt_intf.rx_vld ) ,
    .rxd    (pkt_intf.txd    ) ,
    .rx_vld (pkt_intf.tx_vld )
);

initial begin
    $timeformat(-9, 1, "ns", 10);
    clk = 0;
    forever begin
        #(clock_cycle/2) clk = ~clk;
    end
end

`ifdef WAVE_ON
initial begin
    $vcdpluson();
end
`endif

endmodule

##################################################
#./script/Makefile
##################################################
ifeq ($(GUI), 1)
    GUI_ARG = -gui
endif

PROJECT_DIR = /mnt/vmm
RTL_DIR = $(PROJECT_DIR)/rtl
RTL = $(RTL_DIR)/dut.v

TB_DIR = $(PROJECT_DIR)/env
INCDIR = +incdir+$(TB_DIR)

CPU_INTF_TB_DIR = $(TB_DIR)/cpu_interface_env
INCDIR += +incdir+$(CPU_INTF_TB_DIR)

PKT_TB_DIR = $(TB_DIR)/packet_env
INCDIR += +incdir+$(PKT_TB_DIR)

VMM_TB = $(TB_DIR)/env.sv $(TB_DIR)/top.sv

TEST_DIR = $(PROJECT_DIR)/test
TEST_FILE = $(TEST_DIR)/test.sv
INCDIR += +incdir+$(TEST_DIR)

VMM_ARG = -ntb -ntb_opts rvm
COMPILE_LOG_ARG = -l vcs.log

WAVE_ARG = +define+WAVE_ON=1

COMPILE_ARG = -sverilog -debug_all
COMPILE_ARG += $(INCDIR) $(COMPILE_LOG_ARG) $(WAVE_ARG) $(VMM_ARG)

RUN_LOG_ARG = -l simv.log

RUN_ARG  =
RUN_ARG += $(RUN_LOG_ARG) $(GUI_ARG)

SEED = 1

default: test

test: compile run

run:
./simv $(RUN_ARG) +ntb_random_seed=$(SEED)

compile:
vcs $(COMPILE_ARG) $(RTL) $(VMM_TB) $(TEST_FILE)

clean:
rm -rf simv simv.* *log