Code
// *******************************************************
// Date Created   : 01 June, 2019
// Author         : :P
// *******************************************************

// NOTE
// -------------------------------------------------------
// Here we will keep track of the component level progress.
// Features that have been implemented in the monitor:
// 1. Basic Scoreboardig
// 2. Read transaction PADDR, PWRITE values check in access phase.
// 3. Write transaction PADDR, PWRITE, PSTRB % PWDATA values check in access phase.
// 4. Use of error ids and their definition in the error definitios file.
// 5. Read transaction protocol check.
//  a. PSEL timeout check.
// 6. Write transaction protocol check.
//  a. PSEL timeout check.
//
// Features Pending Implementation:
// 1. Read transaction protocol check.
// 2. Write transaction protocol check.
// -------------------------------------------------------

class vip_amba_apb_monitor extends uvm_monitor;

	`uvm_component_utils( vip_amba_apb_monitor )
    
    uvm_analysis_port #( vip_amba_apb_txn_item ) cov_collector_port;

	virtual vip_amba_apb_interface monitor_bus_intf0;

    int env_num;
    
    // Monitor Masking Control Bits
    // ----------------------------
    bit [2:0] EID[];

    //// Monitor Error Expectation
    //// -------------------------
    //int EID_expected_count[$];
    //int EID_active_count[$];
    
    // Test Configuration
    // ------------------
    vip_amba_apb_test_config test_config;

	// Debug Log Handle
	// ----------------
	int monitor_debug;

    // Varibals Declarations
    // ---------------------
    logic [8-1:0] data_q[bit [32-1:0]]; // :P -> Byte Addressed Memory
    logic          data_rd_wr_q[$]; // :P -> 0 : READ, 1 : WRITE
    int read_transactions_initiated, read_transactions_completed, read_transactions_pending, read_timeouts;
    int write_transactions_initiated, write_transactions_completed, write_transactions_pending, write_timeouts;
    int resets_asserted, resets_deasserted;
    bit global_txn_ongoing;

	function new( string name="vip_amba_apb_monitor", uvm_component parent );
		super.new( name,parent );
		$display( "Monitor Inst New Called" );

        cov_collector_port = new( "cov_collector_port",this );
	endfunction

	function void build_phase( uvm_phase phase );
		super.build_phase( phase );
		$display( "Monitor Inst Build Phase Called" );

        `ifdef VIP_MONITOR_DEBUG
		    if( !uvm_config_db#( int )::get( 	.cntxt( this ),
		    				 	.inst_name( "" ),
		    					.field_name( $sformatf( "monitor_debug[%0d]", env_num ) ),
		    					.value( monitor_debug ) ) )
		    	`uvm_fatal( "APB_MON_COM"," Failed to get the monitor_debug handle" )
        `endif
	endfunction

	function void connect_phase( uvm_phase phase );
		super.connect_phase( phase );
	endfunction
	
    function void start_of_simulation_phase( uvm_phase phase );
		super.start_of_simulation_phase( phase );
		
		// Get the Bus Interface
		if( !uvm_config_db#( virtual vip_amba_apb_interface )::get( 	.cntxt( this ),
										.inst_name( "" ),
										.field_name( $sformatf( "bus_intf0[%0d]", env_num ) ),
										.value( monitor_bus_intf0 ) ) )
			`uvm_fatal( "APB_MON_COM", " Failed to get the UVM Bus Interface" )
		
        // Get the test config file
        // ------------------------
		if( !uvm_config_db#( vip_amba_apb_test_config )::get( 	.cntxt( this ),
						 	                                    .inst_name( "" ),
							                                    .field_name( $sformatf( "test_config[%0d]", env_num ) ),
							                                    .value( test_config ) ) )
        begin
			`uvm_fatal( "DB_GET_ERR","Failed to get the test_config in sequence" )
            EID = new[1000];
        end
        else
        begin
            // BUG TODO
            EID = test_config.EID;
        end

	endfunction

    task main_phase( uvm_phase phase );
		phase.raise_objection( this );

        `mdisplay( " Monitor_Master & Monitor_Slave Task Started...",0 )

        fork
            begin
                monitor_preset_counter();
            end
            forever
            begin
                fork
                    begin
                        @( posedge `PRESETn );
                        `mdisplay( $sformatf( " PRESETn signal de-asserted!" ), 0 )
                    end
                    monitor_master_task();
                    monitor_runtime_checks();
                    monitor_transaction_counter();
                    //monitor_slave_task();
                    monitor_scorboarding();
                join_none
                
                @( negedge `PRESETn );
                `mdisplay( $sformatf( " PRESETn signal asserted!" ), 0 )
                disable fork;
                global_txn_ongoing = 0;
            end
        join_none

		phase.drop_objection( this );
    endtask

    // Extract Phase
    // -------------
    function void extract_phase( uvm_phase phase );
		super.extract_phase( phase );
		
        `mdisplay( $sformatf( " [ AMBA_APB_EID_ERR ] Extract Phase Called! Error Count Checking ---" ), 0 )

        // Error Expectation Checking Logic
        // --------------------------------
        foreach( test_config.EID_expected_count[i] )
        begin
            if( test_config.EID_expected_count[i] != test_config.EID_actual_count[i] )
            begin
                `uvm_error( "APB_MON_COM", $sformatf( " [ AMBA_APB_EID_ERR ] Total number of expected errors and received errors mismatch : EID [ %0d ] - Expected ( %0d ) vs Received ( %0d ) | EID[ %0d ] = %2d", i, test_config.EID_expected_count[i], test_config.EID_actual_count[i], i, test_config.EID[i] ) )
            end
            else
            begin
                `mdisplay( $sformatf( " [ AMBA_APB_EID_ERR ] Total number of expected errors and received errors matched  : EID [ %0d ] - Expected ( %0d ) vs Received ( %0d ) | EID[ %0d ] = %2d", i, test_config.EID_expected_count[i], test_config.EID_actual_count[i], i, test_config.EID[i] ), 0 )
            end
        end
        
        // Transaction Expectation Result
        // ------------------------------
        $display( $sformatf( "# " ) );
        $display( $sformatf( "# ==================================================================" ) );
        $display( $sformatf( "# =                  Monitor Transaction Details                   =" ) );
        $display( $sformatf( "# ==================================================================" ) );
        $display( $sformatf( "#  Total Read Transactions Initiated  : %0d                         ", read_transactions_initiated ) );
        $display( $sformatf( "#  Total Read Transactions Finished   : %0d                         ", read_transactions_completed ) );
        $display( $sformatf( "#  Total Read Transactions Timeouts   : %0d                         ", read_timeouts ) );
        $display( $sformatf( "# ==================================================================" ) );
        $display( $sformatf( "#  Total Write Transactions Initiated : %0d                          ", write_transactions_initiated ) );
        $display( $sformatf( "#  Total Write Transactions Finished  : %0d                          ", write_transactions_completed ) );
        $display( $sformatf( "#  Total Write Transactions Timeouts  : %0d                          ", write_timeouts ) );
        $display( $sformatf( "# ==================================================================" ) );
        $display( $sformatf( "#  Total PRESETn Assertions           : %0d                          ", resets_asserted ) );
        $display( $sformatf( "#  Total PRESETn De-Assertions        : %0d                          ", resets_deasserted ) );
        $display( $sformatf( "# ==================================================================" ) );


	endfunction
    

    // Monitor Scorboarding Task
    task monitor_scorboarding();
    begin
        vip_amba_apb_txn_item pkt_item;

        forever
        begin
            `mdisplay( $sformatf( " Waiting for read and write data queues to become non zero" ), 0 )
            wait( data_rd_wr_q.size() );
            `mdisplay( $sformatf( " Done waiting for read and write data queues to become non zero" ), 0 )

            pkt_item = new;
            pkt_item.txn_ADDR = `PADDR;
            pkt_item.txn_rd_wr = data_rd_wr_q[0];
            
            if( !data_rd_wr_q[0] ) // :P -> READ
            begin
                bit [4-1:0] mem_exist;

                begin
                    logic [32-1:0] temp_data;

                    if( `PSLVERR !== 1 )
                    begin
                        foreach( `PSTRB[i] ) // Simply usgin PSTRB to iterate through the number of bytes in memory per base address
                        begin
                            if( data_q.exists( `PADDR+i ) )
                            begin
                                temp_data[i*8+:8] = data_q[`PADDR+i];
                            end
                            else
                            begin
                                temp_data[i*8+:8] = 'dx;
                            end
                        end
                    end
                    else if( `PSLVERR === 'd1 )
                    begin
                        if( `PSLVERR_RDATA_VALUE == 0 )
                        begin
                            temp_data = 'd0;
                        end
                        else
                        begin
                            temp_data = 'dx;
                        end
                    end

                    // Coverage pkt Trasnmission
                    // -------------------------
                    pkt_item.txn_slverr = `PSLVERR;
                    pkt_item.txn_slave_sel = `PSELx;
                    pkt_item.txn_RDATA = `PRDATA;

                    if( `PSLVERR === 'd1 && ( `PRDATA !== temp_data ) )
                    begin
                        `AMBA_APB_EID_8
                    end
                    else if( temp_data === `PRDATA )
                    begin
                        if( `PSLVERR )
                        begin
                            `mdisplay( $sformatf( " [SB] READ Data Match : Scoreboard and Slave Read ( When PSLVERR is High ) ( Expected Data : %h , Received Data : %h )", temp_data, `PRDATA ), 0 )
                        end
                        else
                        begin
                            `mdisplay( $sformatf( " [SB] READ Data Match : Scoreboard and Slave Read ( Expected Data : %h , Received Data : %h )", temp_data, `PRDATA ), 0 )
                        end
                    end
                    else
                    begin
                        `mdisplay( $sformatf( " [SB] READ Data Mismatch : Expected Data - %h, Received Data - %h", temp_data, `PRDATA ), 0 )
                        `AMBA_APB_EID_6
                    end
                end
                //else
                //begin
                //    //`AMBA_APB_EID_7 TODO
                //    `mdisplay( $sformatf( " [SB] READ Data on an uninitialised or unwritten memory location" ), 0 )
                //end
            end
            else if( data_rd_wr_q[0] ) // :P -> WRITE
            begin
                logic [32-1:0] temp_data;

                foreach( `PSTRB[i] )
                begin
                    if( `PSTRB[i] )
                    begin
                        data_q[`PADDR+i] = `PWDATA[i*8+:8];
                        temp_data[i*8+:8] = `PWDATA[i*8+:8];
                    end
                end

                // Coverage pkt Trasnmission
                // -------------------------
                pkt_item.txn_slverr = `PSLVERR;
                pkt_item.txn_slave_sel = `PSELx;
                pkt_item.txn_WDATA = `PWDATA;
                pkt_item.txn_STRB = `PSTRB;

                `mdisplay( $sformatf( " [SB] WRITE Data to the Scroeboard's Memory" ), 0 )
                `mdisplay( $sformatf( " [SB] Data Written : %h", temp_data ), 0 )
            end

            cov_collector_port.write( pkt_item );

            data_rd_wr_q.delete();
        end
    end
    endtask

    // Monitor Master Task
    task monitor_master_task();
    begin
        logic [32-1:0]  master_address;
        logic [4-1:0]   master_strobe;
        logic [32-1:0]  master_data;
        logic           master_rd_wr;

        bit             check_for_write;
        bit             check_for_read;
        bit             psel_set;
        bit             direct_to_access;

        forever
        begin
            direct_to_access = 0;

            fork
                begin
                    psel_timeout_check(); // PSELx Timeout Check
                end
                forever
                begin
                    @( posedge `PCLK ); // :P Sampling on positive edge
                    //`mdisplay( $sformatf( " [BFM] posedge" ), 0 )

                    if( `PSELx )
                    begin
                        `mdisplay( $sformatf( " [BFM] PSELx Sampled" ), 0 )
                        check_for_read = 0;
                        check_for_write = 0;

                        //if( !direct_to_access ) // :P -> If True,  Idle to Setup else Access to Setup
                        //begin
                        //    @( posedge `PCLK ); 
                        //    direct_to_access = 1;
                        //end

                        begin
                            master_address = `PADDR;
                            master_rd_wr = `PWRITE;
                            if( master_rd_wr )
                            begin
                                `mdisplay( $sformatf( " [BFM] Write detected as High" ), 0 )
                                master_data = `PWDATA;
                                master_strobe = `PSTRB;
                                check_for_write = 1;
                                check_for_read = 0;
                            end
                            else
                            begin
                                `mdisplay( $sformatf( " [BFM] Read detected as High" ), 0 )
                                check_for_write = 0;
                                check_for_read = 1;
                            end
                            
                            `mdisplay( $sformatf( " [BFM] Inside Access Phase" ), 0 )
                            @( posedge `PCLK ); // Moving into Access Phase
                        end

                        forever
                        begin
                            if( !(master_address === `PADDR && master_data === `PWDATA && master_strobe === `PSTRB && master_rd_wr === `PWRITE ) && check_for_write ) // :P CHECK
                            begin
                                if( master_address !== `PADDR )
                                begin
                                    `AMBA_APB_EID_1
                                end
                                if( master_data !== `PWDATA )
                                begin
                                    `AMBA_APB_EID_2
                                end
                                if( master_strobe !== `PSTRB)
                                begin
                                    `AMBA_APB_EID_3
                                end
                                if( master_rd_wr !== `PWRITE )
                                begin
                                    `AMBA_APB_EID_4
                                end

                                `mdisplay( $sformatf( " [BFM] Write break called" ), 0 )
                            end
                            
                            if( check_for_write && `PREADY && `PENABLE )
                            begin
                                `mdisplay( $sformatf( " [BFM] Write PUSHED" ), 0 )
                                data_rd_wr_q.push_front( `PWRITE );
                                break;
                            end
                            
                            if( !( master_address === `PADDR && master_rd_wr === `PWRITE ) && check_for_read ) // :P CHECK
                            begin
                                if( master_address !== `PADDR )
                                begin
                                    `AMBA_APB_EID_1
                                end
                                if( master_rd_wr !== `PWRITE )
                                begin
                                    `AMBA_APB_EID_4
                                end

                                `mdisplay( $sformatf( " [BFM] Read break called" ), 0 )
                            end
                            
                            if( check_for_read && `PREADY && `PENABLE )
                            begin
                                `mdisplay( $sformatf( " [BFM] Read PUSHED" ), 0 )
                                data_rd_wr_q.push_front( `PWRITE );
                                break;
                            end
                            
                            @( posedge `PCLK ); // :P Still in Access Phase
                        end
                    end
                end
                forever
                begin
                    @( posedge `PCLK );
                    if( `PSELx )
                    begin
                        break;
                    end
                end
            join_any

            forever
            begin
                @( posedge `PCLK );
                if( !`PSELx )
                begin
                    @( negedge `PCLK );
                    break;
                end
            end
            // :P Disable this transaction iteration because PSELx is low
            //    again
            disable fork;
        end
    end
    endtask

    // Monitor Runtime Checks
    task monitor_runtime_checks();
        fork
            // Setup Phase Check
            forever
            begin
                @( posedge `PCLK );
                if( `PSELx && !`PENABLE )
                begin
                    @( posedge `PCLK );
                    if( !( `PSELx && `PENABLE ) )
                    begin
                        `AMBA_APB_EID_12
                    end
                end
            end
            // Minimim 2 Clocks PSELx Check
            forever
            begin
                @( posedge `PCLK );
                if( `PSELx && !`PENABLE )
                begin
                    if( !`PSELx )
                    begin
                        `AMBA_APB_EID_11
                    end
                end
            end
            // PENABLE cannot be high without PSELx Asserted
            forever
            begin
                @( posedge `PCLK );
                if( !`PSELx && `PENABLE )
                begin
                    `AMBA_APB_EID_10
                end
            end
            // PSTRB must be low for READ transfers
            forever
            begin
                @( posedge `PCLK );
                if( `PSELx && !`PENABLE )
                begin
                    if( !`PWRITE && `PSTRB )
                    begin
                        `AMBA_APB_EID_9                       
                    end
                end
            end
            // Runnaway PREADY Received, ie unexpected PREADY
            forever
            begin
                @( posedge `PCLK );
                if( global_txn_ongoing && `PREADY && !( `PSELx && `PENABLE ) )
                begin
                    `AMBA_APB_EID_13
                end
            end
        join
    endtask
 
    // Monitor Transaction Counter
    task monitor_transaction_counter();
        int timeout_counter;
        bit rd, wr;
        bit txn_ongoing;

        fork
            // Transaction Counter
            forever
            begin
                @( posedge `PCLK );
                if( `PSELx && `PENABLE )
                begin
                    if( `PWRITE && !txn_ongoing )
                    begin
                        write_transactions_initiated++;
                        wr = 1;
                        rd = 0;
                        timeout_counter++;
                        txn_ongoing = 1;
                        global_txn_ongoing = 1;
                    end
                    if( !`PWRITE && !txn_ongoing )
                    begin
                        read_transactions_initiated++;
                        wr = 0;
                        rd = 1;
                        timeout_counter++;
                        txn_ongoing = 1;
                        global_txn_ongoing = 1;
                    end
                    if( `PWRITE && `PREADY )
                    begin
                        write_transactions_completed++;
                        wr = 0;
                        rd = 0;
                        timeout_counter = 0;
                        txn_ongoing = 0;
                        global_txn_ongoing = 0;
                    end
                    if( !`PWRITE && `PREADY )
                    begin
                        read_transactions_completed++;
                        wr = 0;
                        rd = 0;
                        timeout_counter = 0;
                        txn_ongoing = 0;
                        global_txn_ongoing = 0;
                    end
                end
                else if( !`PSELx && !`PENABLE && txn_ongoing )
                begin
                    //if( timeout_counter == ( `PSELx_TIMEOUT + 'd1 ) && txn_ongoing ) // TODO Test Later
                    begin
                        timeout_counter = 0;
                        if( rd )
                        begin
                            read_timeouts++;
                        end
                        if( wr )
                        begin
                            write_timeouts++;
                        end
                        wr = 0;
                        rd = 0;
                        txn_ongoing = 0;
                        global_txn_ongoing = 0;
                    end
                end
            end
        join_none
    endtask

    // Monitor PRESETn Counter
    task monitor_preset_counter();
        fork
            // Reset Assertion Counter
            forever
            begin
                @( negedge `PRESETn );
                resets_asserted++;
            end
            // Reset De-Assertion Counter
            forever
            begin
                @( posedge `PRESETn );
                resets_deasserted++;
            end
        join_none
    endtask

    // Monitor Slave Task TODO Maybe I won't need this
    task monitor_slave_task();
    begin
        logic [32-1:0]  slave_address;
        logic [4-1:0]   slave_strobe;
        logic [32-1:0]  slave_data;

        forever
        begin
            @( negedge `PCLK ); // :P Sampling on positive edge in order to keep synchronization simple
        end
    end
    endtask

    task psel_timeout_check();
        int psel_timeout_counter;

        psel_timeout_counter = 0;

        `mdisplay( $sformatf( " PSEL Timeout Check Started..." ), 0 )

        forever
        begin
            @( posedge `PCLK );

            if( `PSELx && `PENABLE && `PREADY )
            begin
                psel_timeout_counter = 0;
            end
            else if( `PSELx )
            begin
                if( psel_timeout_counter == (`PSELx_TIMEOUT+1) )
                begin
                    psel_timeout_counter = 0;

                    `AMBA_APB_EID_5
                    break;
                end
                else
                begin
                    psel_timeout_counter++;
                end
            end
            else
            begin
                psel_timeout_counter = 0;
            end
        end
    endtask

endclass