/****************************************************************************
    +-+-+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+-+-+-+-+ +-+-+ +-+-+-+-+-+-+-+-+
    |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            i2c_monitor.sv

    DESCRIPTION     

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

`ifndef I2C_MONITOR_SV
`define I2C_MONITOR_SV

/*
 * Class: i2c_monitor
 */
class i2c_monitor extends uvm_monitor;
    
    // i2c virtual interface
    virtual i2c_if vif;
    
    // configuration
    i2c_config cfg;
    
    // TLM - from monitor to other components
    uvm_analysis_port #(i2c_transaction) item_collected_port;
    
    // keep track of number of transactions
    int unsigned num_transactions = 0;
    
    // current transaction
    i2c_transaction tr_collected;

    // start and stop helper events
    event start_e;
    event stop_e;
    
    // UVM factory registration
    `uvm_component_utils_begin(i2c_monitor)
        `uvm_field_object(cfg, UVM_DEFAULT | UVM_REFERENCE)
    `uvm_component_utils_end    
        
    // coverage
    covergroup cg_i2c_monitor;
        // cover direction - read or write
        cp_direction : coverpoint tr_collected.dir {
            bins write = {I2C_WRITE};
            bins read  = {I2C_READ};
        }
        // cover address ack
        cp_addr_ack : coverpoint tr_collected.addr_ack {
            bins ack  = {I2C_ACK};
            bins nack = {I2C_NACK};            
        }
        // cover data ack
        cp_data_ack : coverpoint tr_collected.data_ack {
            bins ack  = {I2C_ACK};
            bins nack = {I2C_NACK};            
        }        
        // TODO : add others
    endgroup : cg_i2c_monitor;

    // new - constructor
    function new(string name = "i2c_monitor", uvm_component parent = null);
        super.new(name, parent);
        item_collected_port = new("item_collected_port", this);
        cg_i2c_monitor = 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#(i2c_config)::get(this, "", "i2c_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 i2c_if)::get(this, "", "i2c_if", vif))
            `uvm_fatal("NOVIF",{"virtual interface must be set for: ",get_full_name(),".vif"})    
    endfunction : connect_phase

    // additional class methods
    extern virtual task start_condition(ref event start_e);
    extern virtual task stop_condition(ref event stop_e);
    extern virtual task run_phase(uvm_phase phase);
    extern virtual task collect_transactions();
    extern virtual function void report_phase(uvm_phase phase);

endclass : i2c_monitor

// UVM run_phase
task i2c_monitor::run_phase(uvm_phase phase);    
    forever begin
        
        @(negedge vif.rst); // reset dropped
        `uvm_info(get_type_name(), "Reset dropped", UVM_MEDIUM)
        
        fork
            @(posedge vif.rst); // reset is active high
            start_condition(start_e);
            stop_condition(stop_e);
            collect_transactions();
        join_any
        disable fork;
        // only way to get here is after reset
        
    end
endtask : run_phase
 
// trigger event when start
task i2c_monitor::start_condition(ref event start_e);
    forever begin
        wait(vif.sda_wire !== 1'bx); // don't trigger from an X to 0 transition
        @(negedge vif.sda_wire);
        if(vif.scl_wire === 1'b1) begin
            ->start_e;
        end
    end
endtask : start_condition

// trigger event when stop
task i2c_monitor::stop_condition(ref event stop_e);
    forever begin
        wait(vif.sda_wire !== 1'bx); // don't trigger from an X to 1 transition
        @(posedge vif.sda_wire);
        if(vif.scl_wire === 1'b1) begin
            ->stop_e;
        end
    end
endtask : stop_condition    

// monitor i2c interface and collect transactions
task i2c_monitor::collect_transactions();
    forever begin
        wait(start_e.triggered);
        tr_collected = i2c_transaction::type_id::create("tr_collected", this);
              
        // address
        tr_collected.addr = 0;
        repeat(ADDR_WIDTH) begin
            @(posedge vif.scl_wire);
            #1;
            tr_collected.addr = {tr_collected.addr[ADDR_WIDTH - 2 : 0], vif.sda_wire};
        end 
     
        // read / write bit
        @(posedge vif.scl_wire);
        #1;
        tr_collected.dir = i2c_direction_enum'(vif.sda_wire);
    
        // ack bit
        @(posedge vif.scl_wire);
        #1;
        if(vif.sda_wire === 1'b0) tr_collected.addr_ack = I2C_ACK;
        else tr_collected.addr_ack = I2C_NACK;
        if(cfg.has_checks) begin // check for NACK
	        asrt_addr_nack : assert (tr_collected.addr_ack == I2C_ACK)
	        else
	            `uvm_error(get_type_name(), $sformatf("Observed address NACK during %s", tr_collected.dir.name))    
        end
        // only if ack
        if(tr_collected.addr_ack == I2C_ACK) begin
            // data
            repeat(DATA_WIDTH) begin
                @(posedge vif.scl_wire);
                #1;
                tr_collected.data = {tr_collected.data[DATA_WIDTH - 2 : 0], vif.sda_wire};
            end
    
            // ack bit
            @(posedge vif.scl_wire);
            #1;
            if(vif.sda_wire === 1'b0) tr_collected.data_ack = I2C_ACK;
            else tr_collected.data_ack = I2C_NACK;
            if(cfg.has_checks) begin // check for NACK
	            asrt_data_nack : assert (tr_collected.data_ack == I2C_ACK)
	            else
	                `uvm_error(get_type_name(), $sformatf("Observed data NACK during %s", tr_collected.dir.name))
            end
        end
        
        wait(stop_e.triggered);
        
        item_collected_port.write(tr_collected); // TLM
        // collect coverage if enabled
        if(cfg.has_coverage == 1) begin
            cg_i2c_monitor.sample();
        end
        `uvm_info(get_type_name(), $sformatf("Tr collected :\n%s", tr_collected.sprint()), UVM_MEDIUM)
        num_transactions++;           
    end
endtask : collect_transactions

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

`endif

