/****************************************************************************
    +-+-+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+-+-+-+-+ +-+-+ +-+-+-+-+-+-+-+-+
    |F|u|n|c|t|i|o|n|a|l| |V|e|r|i|f|i|c|a|t|i|o|n| |o|f| |H|a|r|d|w|a|r|e|
    +-+-+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+-+-+-+-+ +-+-+ +-+-+-+-+-+-+-+-+

    FILE            apb_master_monitor.sv

    DESCRIPTION     

 ****************************************************************************/

`ifndef APB_MASTER_MONITOR_SV
`define APB_MASTER_MONITOR_SV

/**
 * Class: apb_master_monitor
 */
class apb_master_monitor extends uvm_monitor;
    
    // apb virtual interface
    virtual apb_if vif;
    
    // configuration
    apb_config cfg;
    
    // TLM - from monitor to other components
    uvm_analysis_port #(apb_transaction) item_collected_port;
    
    // keep track of number of transactions
    int unsigned num_transactions = 0;
    
    // current transaction
    apb_transaction tr_collected;
    
    // UVM factory registration
    `uvm_component_utils_begin(apb_master_monitor)
        `uvm_field_object(cfg, UVM_DEFAULT | UVM_REFERENCE)
    `uvm_component_utils_end
    
    // coverage
    covergroup cg_apb_master;
        // cover direction - read or write
        cp_direction : coverpoint tr_collected.dir {
            bins write = {APB_WRITE};
            bins read  = {APB_READ};
        }
        // cover delay - zero or more
        cp_delay : coverpoint tr_collected.delay {
            bins zero = {0};
            bins other = default;
        }
        // TODO : add others
    endgroup : cg_apb_master;

    // new - constructor
	function new(string name = "apb_master_monitor", uvm_component parent = null);
		super.new(name, parent);
        item_collected_port = new("item_collected_port", this);
        cg_apb_master = new();
	endfunction : new
    
    // UVM build_phase
    function void build_phase(uvm_phase phase);
        super.build_phase(phase);
        // get configuration object from db
        if(!uvm_config_db#(apb_config)::get(this, "", "apb_config", cfg))
            `uvm_fatal("NOCONFIG",{"Config object must be set for: ",get_full_name(),".cfg"})
    endfunction: build_phase
    
    // UVM connect_phase
    function void connect_phase(uvm_phase phase);
        super.connect_phase(phase);
        // get interface from db
        if(!uvm_config_db#(virtual apb_if)::get(this, "", "apb_if", vif))
            `uvm_fatal("NOVIF",{"virtual interface must be set for: ",get_full_name(),".vif"})    
    endfunction : connect_phase
    
    // additional class methods
    extern virtual task run_phase(uvm_phase phase);
    extern virtual task collect_transactions();
    extern virtual function void report_phase(uvm_phase phase);

endclass : apb_master_monitor

// UVM run_phase
task apb_master_monitor::run_phase(uvm_phase phase);
    forever begin
        @(posedge vif.presetn); // reset dropped
        `uvm_info(get_type_name(), "Reset dropped", UVM_MEDIUM)
    
        fork
            collect_transactions(); // thread killed at reset
            @(negedge vif.presetn); // reset is active low
        join_any
        disable fork;
    end
endtask : run_phase

// monitor apb interface and collect transactions
task apb_master_monitor::collect_transactions();
    forever begin
        tr_collected = apb_transaction::type_id::create("tr_collected");
        
        // wait for valid transaction
        @(posedge vif.pclk iff (vif.psel != 0));
        tr_collected.addr = vif.paddr;
        tr_collected.dir = apb_direction_enum'(vif.pwrite);        
        if(tr_collected.dir == APB_WRITE)
            tr_collected.wdata = vif.pwdata;
        
        @(posedge vif.pclk); // enable
        @(posedge vif.pclk); // ready
        while (vif.pready !== 1'b1) begin
            @(posedge vif.pclk);
            tr_collected.delay++;
        end
        if(tr_collected.dir == APB_READ) begin
            tr_collected.rdata = vif.prdata;
            tr_collected.error = vif.pslverr;
        end

        item_collected_port.write(tr_collected); // TLM
        // collect coverage if enabled
        if(cfg.has_coverage == 1) begin
            cg_apb_master.sample();
        end
        `uvm_info(get_type_name(), $sformatf("Tr collected :\n%s", tr_collected.sprint()), UVM_MEDIUM)
        num_transactions++;
    end // forever
endtask : collect_transactions

// UVM report_phase
function void apb_master_monitor::report_phase(uvm_phase phase);
    // final report
    `uvm_info(get_type_name(), $sformatf("Report: APB monitor collected %0d transfers", num_transactions), UVM_LOW);
endfunction : report_phase


`endif

