
// Filename: tlm2_base_protocol_checker.h

//----------------------------------------------------------------------
//  Copyright (c) 2008-2013 by Doulos Ltd.
//
//  Licensed under the Apache License, Version 2.0 (the "License");
//  you may not use this file except in compliance with the License.
//  You may obtain a copy of the License at
//
//  http://www.apache.org/licenses/LICENSE-2.0
//
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.
//----------------------------------------------------------------------

// Author: John Aynsley, Doulos

// Version  1, 11 July 2008
// Version  2, 16 July 2008  Only generate ref_count > 1 warning from 1st checker of path
// Version  3, 17 July 2008  Support compilation under SystemC 2.1.v1
// Version  4, 12 Aug  2008  Add header #include <map>
// Version  5, 08 Sep  2008  Fix bugs in message text
// Version  6, 01 Aug  2010  Update messages to refer to OSCI TLM-2.0 LRM of July 2009
// Version  7, 25 Oct  2011  Minor bug fix for certain compilers: replace u_char with uchar_t
// Version  8, 02 Nov  2011  Support the endianness conversion functions by excluding the
//                           tlm_endian_context extension from the protocol checks
// Version  9, 17 Aug  2012  Fix LRM reference on line 805 (should be 8.2.11 a) [NOT YET RELEASED]
// Version 10,  3 Jan  2013  Updated messages to refer to IEEE Std 1666-2011, the combined SystemC + TLM-2.0 LRM
//                           Added checks related to the generic payload option attribute
// Version 11, 14 Mar  2016  Fix minor bug - start_phase should be a copy, not a reference

// TLM-2.0 Base Protocol Compliance Checker

/*
Instantiate this checker module in-line between initiator and target, initiator and interconnect,
or interconnect and target by binding the target_socket and initiator_socket
Binding two checkers either side of an interconnect component, or interleaving a series of
checkers with interconnect components, will enable some deeper checks as against having just
a single checker

For example

  Initiator *initiator;
  Bus       *bus;
  Memory    *memory;
  ...
  initiator->socket.bind(bus->target_socket);
  bus->initiator_socket.bind(memory->socket);

might become

  tlm_utils::tlm2_base_protocol_checker<32> *checker1;
  tlm_utils::tlm2_base_protocol_checker<32> *checker2;
  ...
  initiator->socket.bind(checker1->target_socket);
  checker1->initiator_socket.bind(bus->target_socket);
  bus->initiator_socket.bind(checker2->target_socket);
  checker2->initiator_socket.bind(memory->socket);


GENERAL FEATURES OF THE BASE PROTOCOL CHECKER

The checks are relatively expensive, hence by default the number of checks is limited.
A maximum number can be set explicitly by calling set_num_checks(max)
Checking can be deactivated at any time by calling set_num_checks(0)
All checkers decrement a single global count, because having some checkers running and
others not can cause bogus violation reports
It is not permitted to turn checks on by calling set_num_checks() once checking has been
deactivated, because this could cause bogus violation reports

The DMI and debug checks are unaffected by the num_checks count (because they are cheap)

The error messages contain User Manual references

The checker is designed to be used with a transaction pool: otherwise it could consume
a lot of memory. The checker keeps a local copy of each transaction object
Failures are reported with a severity of SC_ERROR. The actions may be overridden by calling:
   sc_report_handler::set_actions("tlm2_protocol_checker", ...);

SPECIFIC CHECKS

nb_transport: phase sequence BEGIN_REQ -> END_REQ -> BEGIN_RESP -> END_RESP
Must not have two outstanding requests or responses (exclusion rules)
Must not have decreasing timing annotations on calls to or returns from nb_transport_fw/bw
Phase extensions permitted and ignored
Must not call b_transport during nb_transport phase sequence and vice-versa

nb_transport: memory manager must be set
nb_transport: reference count must be non-zero
First checker in BEGIN_REQ path should see ref_count == 1 (warning only)
An interconnect component that sets a memory manager should also clear it
An interconnect component that sets extensions with no memory manager should also clear them
(Does not bother with these memory manager checks for DMI and debug)

Transaction object must be properly initialized
Many generic payload attributes must not be modified during the transaction lifetime
Transaction object must not be re-allocated for a new transaction while still in use
DMI descriptor must be properly initialized
Debug transaction must be properly initialized
Debug byte count must be less than data_length

Checks that require multiple checkers to be instantiated along a transaction path:
The BEGIN_RESP path must be the reverse of the BEGIN_REQ path
Transaction object must not be sent with BEGIN_REQ while participating in a previous response
Interconnect component must not set response status attribute to TLM_OK_RESPONSE
Interconnect component must not modify data array on the response path

Generic payload option attribute (IEEE Std 1666-2011, SystemC 2.3.x)
gp_option must be properly initialized and only used for DMI and debug transport
When gp_option is used, other gp attributes must be initalized and used as per the transport interfaces
*/


// ******************** PREAMBLE ********************


#ifndef __tlm2_base_protocol_checker__
#define __tlm2_base_protocol_checker__

#include "systemc"
using std::cout;
using std::endl;
using std::dec;
using std::hex;

#include "tlm.h"
#include <sstream>
#include <map>


namespace tlm_utils {


// Number of checks remaining
const  sc_dt::uint64 default_num_checks = 100000;
static sc_dt::uint64 num_checks = default_num_checks;


// Types used when building a trace of the transaction path
typedef unsigned char uchar_t;
typedef std::deque<sc_core::sc_module *> deque_t;

struct path_t {
    path_t ()
    {
        response_in_progress = false;
        ok_response = false;
        resp_data_ptr = 0;
    }

    bool      response_in_progress;
    bool      ok_response;
    deque_t   path;
    uchar_t  *resp_data_ptr;  // Copy of data on response path
};

// Global variable used for checks involving multiple checkers along a transaction path
static std::map<tlm::tlm_generic_payload *, path_t> shared_map;


// ******************** CLASS DEFINITION ********************


template <unsigned int  BUSWIDTH = 32>
class tlm2_base_protocol_checker

    : public sc_core::sc_module
    , public tlm::tlm_fw_transport_if<tlm::tlm_base_protocol_types>
    , public tlm::tlm_bw_transport_if<tlm::tlm_base_protocol_types>
{
public:

    // Instantiate and bind checker inline between an existing pair of initiator and target sockets

    tlm::tlm_target_socket   <BUSWIDTH, tlm::tlm_base_protocol_types, 1>
    target_socket;
    tlm::tlm_initiator_socket<BUSWIDTH, tlm::tlm_base_protocol_types, 1>
    initiator_socket;

    SC_CTOR(tlm2_base_protocol_checker)
        : m_request_in_progress(0), m_response_in_progress(0)
    {
        target_socket   .bind( *this );
        initiator_socket.bind( *this );
    }


    // Access methods for num_checks count

    static void set_num_checks(sc_dt::uint64 n)
    {
        if (num_checks == 0)
            SC_REPORT_FATAL("tlm2_protocol_checker",
                            "Method set_num_checks called after checking has stopped due to maximum number of checks being reached");
        num_checks = n;
    }

    static sc_dt::uint64 get_num_checks()
    {
        return num_checks;
    }


    // TLM-2.0 interface methods for initiator and target sockets, instrumented with checks

    virtual tlm::tlm_sync_enum nb_transport_fw(
        tlm::tlm_generic_payload &trans, tlm::tlm_phase &phase, sc_core::sc_time &delay)
    {
        tlm::tlm_phase start_phase = phase;

        if (num_checks)
            nb_transport_fw_pre_checks( trans, phase, delay );

        tlm::tlm_sync_enum status;
        status = initiator_socket->nb_transport_fw( trans, phase, delay );

        if (num_checks)
            nb_transport_fw_post_checks( trans, start_phase, phase, delay, status );

        return status;
    }

    virtual tlm::tlm_sync_enum nb_transport_bw(
        tlm::tlm_generic_payload &trans, tlm::tlm_phase &phase, sc_core::sc_time &delay)
    {
        if (num_checks)
            nb_transport_bw_pre_checks( trans, phase, delay );

        tlm::tlm_sync_enum status;
        status = target_socket->nb_transport_bw( trans, phase, delay );

        if (num_checks)
            nb_transport_bw_post_checks( trans, phase, delay, status );

        return status;
    }

    virtual void b_transport( tlm::tlm_generic_payload &trans,
                              sc_core::sc_time &delay )
    {
        if (num_checks)
            b_transport_pre_checks( trans, delay );

        initiator_socket->b_transport( trans, delay );

        if (num_checks)
            b_transport_post_checks( trans, delay );
    }

    virtual bool get_direct_mem_ptr(tlm::tlm_generic_payload &trans,
                                    tlm::tlm_dmi  &dmi_data)
    {
        get_direct_mem_ptr_pre_checks( trans, dmi_data );

        bool status;
        status = initiator_socket->get_direct_mem_ptr( trans, dmi_data );

        get_direct_mem_ptr_post_checks( trans, dmi_data );
        return status;
    }

    virtual void invalidate_direct_mem_ptr(sc_dt::uint64 start_range,
                                           sc_dt::uint64 end_range)
    {
        target_socket->invalidate_direct_mem_ptr(start_range, end_range);
    }

    virtual unsigned int transport_dbg(tlm::tlm_generic_payload &trans)
    {
        transport_dbg_pre_checks( trans );

        unsigned int count;
        count = initiator_socket->transport_dbg( trans );

        transport_dbg_post_checks( trans, count );
        return count;
    }


private:
    void b_transport_pre_checks( tlm::tlm_generic_payload &trans,
                                 sc_core::sc_time &delay);

    void b_transport_post_checks( tlm::tlm_generic_payload &trans,
                                  sc_core::sc_time &delay);

    void nb_transport_fw_pre_checks(
        tlm::tlm_generic_payload &trans, tlm::tlm_phase &phase,
        sc_core::sc_time &delay);

    void nb_transport_fw_post_checks(
        tlm::tlm_generic_payload &trans, tlm::tlm_phase &start_phase,
        tlm::tlm_phase &phase,
        sc_core::sc_time &delay, tlm::tlm_sync_enum status);

    void nb_transport_bw_pre_checks(
        tlm::tlm_generic_payload &trans, tlm::tlm_phase &phase,
        sc_core::sc_time &delay);

    void nb_transport_bw_post_checks(
        tlm::tlm_generic_payload &trans, tlm::tlm_phase &phase, sc_core::sc_time &delay,
        tlm::tlm_sync_enum status);

    void nb_transport_response_checks(
        tlm::tlm_generic_payload &trans, tlm::tlm_phase &phase, sc_core::sc_time &delay,
        const char *txt2, const char *txt3, const char *txt4);

    void check_initial_state(       tlm::tlm_generic_payload &trans,
                                    const char *txt2 );
    void check_trans_not_modified(  tlm::tlm_generic_payload &trans,
                                    const char *txt2 );
    void check_response_path(       tlm::tlm_generic_payload &trans,
                                    const char *txt2 );
    void remember_gp_option(        tlm::tlm_generic_payload &trans );

    void get_direct_mem_ptr_pre_checks( tlm::tlm_generic_payload &trans,
                                        tlm::tlm_dmi &dmi_data );

    void get_direct_mem_ptr_post_checks( tlm::tlm_generic_payload &trans,
                                         tlm::tlm_dmi &dmi_data );

    void transport_dbg_pre_checks( tlm::tlm_generic_payload &trans );

    void transport_dbg_post_checks( tlm::tlm_generic_payload &trans,
                                    unsigned int count );

    void tlm2error( tlm::tlm_generic_payload &trans, const char *ref,
                    bool warning = false  );

private:

    struct state_t {
        state_t()
        {
            b_call = 0;
            ph = tlm::UNINITIALIZED_PHASE;
            gp = 0;
        }

        bool                      has_mm;
        unsigned int              b_call;    // Number of b_transport calls in progress
        tlm::tlm_phase            ph;
        sc_core::sc_time          time;      // Current time + annotated delay
        tlm::tlm_generic_payload
        *gp;        // Points to new data and byte enable buffers
        uchar_t                  *data_ptr;  // Stores original pointers
        uchar_t                  *byte_enable_ptr;
    };

    // Transaction state for the specific hop where this checker is inlined
    std::map<tlm::tlm_generic_payload *, state_t> m_map;

    // Flags for exclusion rules
    tlm::tlm_generic_payload *m_request_in_progress;
    tlm::tlm_generic_payload *m_response_in_progress;

    std::ostringstream txt;

};



// ******************** MEMBER FUNCTION DEFINITIONS ********************


#define BOILERPLATE \
template <unsigned int BUSWIDTH> \
void tlm2_base_protocol_checker<BUSWIDTH>::


BOILERPLATE
b_transport_pre_checks(
    tlm::tlm_generic_payload &trans, sc_core::sc_time & /*delay*/)
{
    ++ m_map[&trans].b_call;

    if ( trans.has_mm() && trans.get_ref_count() == 0) {
        txt << "Transaction passed to b_transport with memory manager and reference count of 0";
        tlm2error(trans, "14.5 t)");
    }
    check_initial_state(trans, "b_transport");

#if !(defined SYSTEMC_VERSION & SYSTEMC_VERSION <= 20050714)
    if (sc_core::sc_get_current_process_handle().proc_kind() ==
            sc_core::SC_METHOD_PROC_) {
        txt << "b_transport called from method process";
        tlm2error(trans, "11.1.1.4 b)");
    }
#endif

    if (m_map[&trans].ph > 0 && m_map[&trans].ph < 4) {
        txt << "b_transport called during a sequence of nb_transport calls";
        tlm2error(trans, "15.2.10 c)");
    }
}


BOILERPLATE
b_transport_post_checks(
    tlm::tlm_generic_payload &trans, sc_core::sc_time & /*delay*/)
{
    check_response_path(trans, "b_transport");
    check_trans_not_modified(trans, "b_transport");
    -- m_map[&trans].b_call;
}


BOILERPLATE
nb_transport_fw_pre_checks(
    tlm::tlm_generic_payload &trans, tlm::tlm_phase &phase, sc_core::sc_time &delay)
{
    if ( !trans.has_mm() ) {
        txt << "Transaction passed to nb_transport_fw with no memory manager set";
        tlm2error(trans, "14.5 i)");
    }
    if ( trans.get_ref_count() == 0) {
        txt << "Transaction passed to nb_transport_fw with reference count of 0";
        tlm2error(trans, "14.5 t)");
    }

    switch (phase) {
    case tlm::BEGIN_REQ:
        check_initial_state(trans, "nb_transport_fw");

        if (m_map[&trans].ph > 0
                &&  m_map[&trans].ph < 4) { // END_RESP -> BEGIN_REQ is legal
            txt << "Phase " << phase <<
                " sent out-of-sequence on forward path, detected in nb_transport_fw";
            tlm2error(trans, "15.2.4");
        }

        if (m_request_in_progress) {
            txt << "Transaction violates BEGIN_REQ exclusion rule, detected in nb_transport_fw";
            tlm2error(trans, "15.2.6 e)");
        }
        m_request_in_progress = &trans;

        if (m_map[&trans].b_call) {
            txt << "nb_transport_fw called during a b_transport call";
            tlm2error(trans, "15.2.10 c)");
        }
        break;

    case tlm::END_REQ:
    case tlm::BEGIN_RESP:
    case tlm::UNINITIALIZED_PHASE:
        txt << "Phase " << phase <<
            " sent on forward path, detected in nb_transport_fw";
        tlm2error(trans, " 15.2.3 c)");
        break;

    case tlm::END_RESP:
        if (m_map[&trans].ph != tlm::BEGIN_RESP) {
            txt << "Phase " << phase <<
                " sent out-of-sequence on forward path, detected in nb_transport_fw";
            tlm2error(trans, "15.2.4");
        }
        m_response_in_progress = 0;
        break;
    }

    if (phase < 5)  // Ignore extended phases
        m_map[&trans].ph = phase;

    if (sc_core::sc_time_stamp() + delay < m_map[&trans].time) {
        txt << "nb_transport_fw called with decreasing timing annotation:"
            << " delay = " << delay
            << ", sc_time_stamp() + delay from previous call = " << m_map[&trans].time;
        tlm2error(trans, "15.2.7 c)");
    }
    m_map[&trans].time = sc_core::sc_time_stamp() + delay;
}


BOILERPLATE
nb_transport_fw_post_checks(
    tlm::tlm_generic_payload &trans, tlm::tlm_phase &start_phase,
    tlm::tlm_phase &phase,
    sc_core::sc_time &delay, tlm::tlm_sync_enum status)
{
    if (status == tlm::TLM_UPDATED) {
        nb_transport_response_checks(
            trans, phase, delay, "(forward) return", "Return from nb_transport_fw",
            "nb_transport_fw");
    } else if (status == tlm::TLM_COMPLETED) {
        if (start_phase == tlm::BEGIN_REQ)
            check_response_path(trans, "nb_transport_fw");
        m_request_in_progress = 0;
        m_map[&trans].ph = tlm::UNINITIALIZED_PHASE;
    }

    // Transaction object should not be re-allocated, even during the END_RESP phase
    //if (phase != tlm::END_RESP)
    {
        std::ostringstream txt;
        txt << "nb_transport_fw, phase = " << phase;
        check_trans_not_modified(trans, txt.str().c_str());
    }
}


BOILERPLATE
nb_transport_bw_pre_checks(
    tlm::tlm_generic_payload &trans, tlm::tlm_phase &phase, sc_core::sc_time &delay)
{
    if ( !trans.has_mm() ) {
        txt << "Transaction passed to nb_transport_bw with no memory manager set";
        tlm2error(trans, "14.5 i)");
    }
    if ( trans.get_ref_count() == 0) {
        txt << "Transaction passed to nb_transport_bw with reference count of 0";
        tlm2error(trans, "14.5 t)");
    }
    nb_transport_response_checks(
        trans, phase, delay, "backward", "nb_transport_bw called", "nb_transport_bw");
}


BOILERPLATE
nb_transport_bw_post_checks(
    tlm::tlm_generic_payload &trans, tlm::tlm_phase &phase, sc_core::sc_time &delay,
    tlm::tlm_sync_enum status)
{
    if (status == tlm::TLM_UPDATED) {
        switch (phase) {
        case tlm::BEGIN_REQ:
            txt << "Phase " << phase <<
                " sent out-of-sequence on (backward) return path, detected in nb_transport_bw";
            tlm2error(trans, "15.2.4");
            break;

        case tlm::END_REQ:
        case tlm::BEGIN_RESP:
        case tlm::UNINITIALIZED_PHASE:
            txt << "Phase " << phase <<
                " sent on (backward) return path, detected in nb_transport_bw";
            tlm2error(trans, "15.2.3 c)");
            break;

        case tlm::END_RESP:
            if (m_map[&trans].ph != tlm::BEGIN_RESP) {
                txt << "Phase " << phase <<
                    " sent out-of-sequence on (backward) return path, detected in nb_transport_bw";
                tlm2error(trans, "15.2.4");
            }

            m_response_in_progress = 0;
            break;
        }

        if (phase < 5)  // Ignore extended phases
            m_map[&trans].ph = phase;

        if (sc_core::sc_time_stamp() + delay < m_map[&trans].time) {
            txt << "Return from nb_transport_bw with decreasing timing annotation:"
                << " delay = " << delay
                << ", sc_time_stamp() + delay from previous call = " << m_map[&trans].time;
            tlm2error(trans, "15.2.7 c)");
        }
        m_map[&trans].time = sc_core::sc_time_stamp() + delay;
    } else if (status == tlm::TLM_COMPLETED) {
        m_response_in_progress = 0;
        m_map[&trans].ph = tlm::UNINITIALIZED_PHASE;
    }

    // Transaction object should not be re-allocated, even during the END_RESP phase
    //if (phase != tlm::END_RESP)
    {
        std::ostringstream txt;
        txt << "nb_transport_bw, phase = " << phase;
        check_trans_not_modified(trans, txt.str().c_str());
    }
}


BOILERPLATE
nb_transport_response_checks(
    tlm::tlm_generic_payload &trans, tlm::tlm_phase &phase, sc_core::sc_time &delay,
    const char *txt2, const char *txt3, const char *txt4)
{
    if (trans.is_response_ok())
        if (shared_map[&trans].response_in_progress
                && !shared_map[&trans].ok_response) {
            txt << "Interconnect component sets response status attribute to TLM_OK_RESPONSE"
                << ", detected in " << txt4;
            tlm2error(trans, "14.7");

        }

    switch (phase) {
    case tlm::BEGIN_REQ:
    case tlm::END_RESP:
    case tlm::UNINITIALIZED_PHASE:
        txt << "Phase " << phase << " sent on " << txt2 << " path"
            << ", detected in " << txt4;
        tlm2error(trans, "15.2.3 c)");
        break;

    case tlm::END_REQ:
        if (m_map[&trans].ph != tlm::BEGIN_REQ) {
            txt << "Phase " << phase << " sent out-of-sequence on " << txt2 << " path"
                << ", detected in " << txt4;
            tlm2error(trans, "15.2.4");
        }

        m_request_in_progress = 0;
        break;

    case tlm::BEGIN_RESP:
        if (m_map[&trans].ph != tlm::BEGIN_REQ && m_map[&trans].ph != tlm::END_REQ) {
            txt << "Phase " << phase << " sent out-of-sequence on " << txt2 << " path"
                << ", detected in " << txt4;
            tlm2error(trans, "15.2.4");
        }

        if (&trans == m_request_in_progress)
            m_request_in_progress = 0;

        if (m_response_in_progress) {
            txt << "Transaction violates BEGIN_RESP exclusion rule"
                << ", detected in " << txt4;
            tlm2error(trans, "15.2.6 f)");
        }
        m_response_in_progress = &trans;

        check_response_path(trans, txt4);
        break;
    }

    if (phase < 5)  // Ignore extended phases
        m_map[&trans].ph = phase;

    if (sc_core::sc_time_stamp() + delay < m_map[&trans].time) {
        txt << txt3 << " with decreasing timing annotation:"
            << " delay = " << delay
            << ", sc_time_stamp() + delay from previous call = " << m_map[&trans].time;
        tlm2error(trans, "15.2.7 c)");
    }
    m_map[&trans].time = sc_core::sc_time_stamp() + delay;
}


BOILERPLATE
check_initial_state(
    tlm::tlm_generic_payload &trans, const char *txt2 )
{
    if (num_checks > 0) {
        --num_checks;
        if (num_checks == 0)
            SC_REPORT_INFO("tlm2_protocol_checker",
                           "Checkers deactivated after executing the set number of checks");
    }

    if ( trans.has_mm() && trans.get_ref_count() > 1
            && shared_map[&trans].path.empty() ) {
        txt << "New transaction passed to " << txt2 << " with reference count = "
            << trans.get_ref_count();
        tlm2error(trans, "14.5 t)", true);
    }
    if (trans.get_data_ptr() == 0
            && trans.get_command() != tlm::TLM_IGNORE_COMMAND) {
        txt << "Transaction not properly initialized: data_ptr == 0, detected in " <<
            txt2;
        tlm2error(trans, "14.11 e)");
    }
    if (trans.get_data_length() == 0
            && trans.get_command() != tlm::TLM_IGNORE_COMMAND) {
        txt << "Transaction not properly initialized: data_langth == 0, detected in " <<
            txt2;
        tlm2error(trans, "14.12 d)");
    }
    if (trans.get_byte_enable_ptr() != 0 && trans.get_byte_enable_length() == 0) {
        txt << "Transaction not properly initialized: "
            << "byte_enable_ptr != 0 and byte_enable_length == 0, detected in " << txt2;
        tlm2error(trans, "14.14 f)");
    }
    if (trans.get_streaming_width() == 0) {
        txt << "Transaction not properly initialized: streaming_width == 0, detected in "
            << txt2;
        tlm2error(trans, "14.15 f)");
    }
    if (trans.is_dmi_allowed()) {
        txt << "Transaction not properly initialized: dmi_allowed == true, detected in "
            << txt2;
        tlm2error(trans, "14.16");
    }
    if (trans.get_response_status() != tlm::TLM_INCOMPLETE_RESPONSE) {
        txt << "Transaction not properly initialized: response_status != TLM_INCOMPLETE_RESPONSE, detected in "
            << txt2;
        tlm2error(trans, "14.17 e)");
    }
    if (trans.get_gp_option() != tlm::TLM_MIN_PAYLOAD) {
        txt << "Transaction not properly initialized: gp_option != TLM_MIN_PAYLOAD, detected in "
            << txt2;
        tlm2error(trans, "14.8 g)");
    }

    // Setup clones of transaction and buffers in map
    tlm::tlm_generic_payload *gp = m_map[&trans].gp;
    if (gp == 0)
        gp = new tlm::tlm_generic_payload;  // Memory leak: transactions are never cleared from map
    else {
        delete [] gp->get_data_ptr();
        gp->free_all_extensions();
    }
    gp->set_data_ptr( new uchar_t[trans.get_data_length()] );
    m_map[&trans].data_ptr = trans.get_data_ptr();

    if (gp->get_byte_enable_ptr())
        delete [] gp->get_byte_enable_ptr();
    if (trans.get_byte_enable_ptr())
        gp->set_byte_enable_ptr( new uchar_t[trans.get_byte_enable_length()] );
    else
        gp->set_byte_enable_ptr(0);
    m_map[&trans].byte_enable_ptr = trans.get_byte_enable_ptr();

    gp->deep_copy_from(trans);
    m_map[&trans].gp = gp;
    m_map[&trans].time = sc_core::SC_ZERO_TIME;
    m_map[&trans].has_mm = trans.has_mm();

    // Store request path checker sequence
    if (shared_map[&trans].resp_data_ptr) {
        delete [] shared_map[&trans].resp_data_ptr;
        shared_map[&trans].resp_data_ptr = 0;
    }
    if (shared_map[&trans].response_in_progress) {
        txt << "Transaction object sent with BEGIN_REQ while still being used on a previous response path, detected in "
            << txt2;
        tlm2error(trans, "14.5 x)");
    }
    shared_map[&trans].ok_response = false;
    shared_map[&trans].path.push_back(this);
}


BOILERPLATE
remember_gp_option(
    tlm::tlm_generic_payload &trans)
{
    // Setup clone of transaction in map in order to check gp_option only
    tlm::tlm_generic_payload *gp = m_map[&trans].gp;
    if (gp == 0)
        gp = new tlm::tlm_generic_payload;  // Memory leak: transactions are never cleared from map
    gp->set_gp_option( trans.get_gp_option() );
    m_map[&trans].gp = gp;
}


BOILERPLATE
check_trans_not_modified(
    tlm::tlm_generic_payload &trans, const char *txt2 )
{
    tlm::tlm_generic_payload *init = m_map[&trans].gp;

    if (trans.get_command() != init->get_command()) {
        txt << "Command attribute modified during transaction lifetime, detected in " <<
            txt2;
        tlm2error(trans, "14.7");
    }
    if (trans.get_data_ptr() != m_map[&trans].data_ptr) {
        txt << "Data pointer attribute modified during transaction lifetime, detected in "
            << txt2;
        tlm2error(trans, "14.7");
    }
    if (trans.get_data_length() != init->get_data_length()) {
        txt << "Data length attribute modified during transaction lifetime, detected in "
            << txt2;
        tlm2error(trans, "14.7");
    }
    if (trans.get_command() == tlm::TLM_WRITE_COMMAND)
        for (unsigned int i = 0; i < init->get_data_length(); i++)
            if (trans.get_data_ptr()[i] != init->get_data_ptr()[i]) {
                txt << "Data array modified during transaction lifetime, detected in " << txt2;
                tlm2error(trans, "14.7");
            }
    if (trans.get_byte_enable_ptr() != m_map[&trans].byte_enable_ptr) {
        txt << "Byte enable pointer attribute modified during transaction lifetime, detected in "
            << txt2;
        tlm2error(trans, "14.7");
    }
    if (trans.get_byte_enable_length() != init->get_byte_enable_length()) {
        txt << "Byte enable length attribute modified during transaction lifetime, detected in "
            << txt2;
        tlm2error(trans, "14.7");
    }
    if (trans.get_byte_enable_ptr())
        for (unsigned int i = 0; i < init->get_byte_enable_length(); i++)
            if (trans.get_byte_enable_ptr()[i] != init->get_byte_enable_ptr()[i]) {
                txt << "Byte enable array modified during transaction lifetime, detected in " <<
                    txt2;
                tlm2error(trans, "14.7");
            }
    if (trans.get_streaming_width() != init->get_streaming_width()) {
        txt << "Streaming width attribute modified during transaction lifetime, detected in "
            << txt2;
        tlm2error(trans, "14.7");
    }
    if (init->get_gp_option() == tlm::TLM_MIN_PAYLOAD
            && trans.get_gp_option() != tlm::TLM_MIN_PAYLOAD) {
        txt << "Generic payload option attribute modified during transaction lifetime, detected in "
            << txt2;
        tlm2error(trans, "14.8 g)");
    }
    if ( !m_map[&trans].has_mm ) {
        if (trans.has_mm()) {
            txt << "Interconnect component sets a memory manager, but does not clear it on return, detected in "
                << txt2;
            tlm2error(trans, "14.5 aa)");
        }

        for (unsigned int i = 0; i < tlm::max_num_extensions(); i++)
            // Exclude tlm_endian_context extension from the check because it is not cloned in m_map
            if (i != tlm::tlm_endian_context::ID)
                if (trans.get_extension(i))
                    if ( !m_map[&trans].gp->get_extension(i) ) {
                        txt << "Extension set (index = " << i <<
                            ") without also being deleted in the absence of a memory manager, detected in "
                            << txt2;
                        tlm2error(trans, "14.5 aa)");
                    }
    }

    uchar_t *resp_data_ptr = shared_map[&trans].resp_data_ptr;
    if (resp_data_ptr)
        for (unsigned int i = 0; i < trans.get_data_length(); i++)
            if (trans.get_data_ptr()[i] != resp_data_ptr[i]) {
                txt << "Transaction data array modified in interconnect component on response path, detected in "
                    << txt2;
                tlm2error(trans, "14.7");
            }
}


BOILERPLATE
check_response_path(
    tlm::tlm_generic_payload &trans, const char *txt2 )
{
    if ( !shared_map[&trans].path.empty() ) {
        if ( this != shared_map[&trans].path.back() ) {
            txt << "BEGIN_RESP path is not the reverse of the BEGIN_REQ path.";
            txt << "\nBEGIN_REQ path includes these checkers: -> ";
            deque_t path = shared_map[&trans].path;
            for (deque_t::iterator i = path.begin(); i < path.end(); i++)
                txt << (*i)->name() << " -> ";
            txt << "\nDetected in " << txt2;
            tlm2error(trans, "15.2.11 a)");
        }
        shared_map[&trans].path.pop_back();
        shared_map[&trans].response_in_progress = !shared_map[&trans].path.empty();
        shared_map[&trans].ok_response = trans.is_response_ok();

        // Create a copy of the data array for comparison on the response path
        if ( !shared_map[&trans].resp_data_ptr ) {
            shared_map[&trans].resp_data_ptr = new uchar_t[trans.get_data_length()];
            memcpy(shared_map[&trans].resp_data_ptr, trans.get_data_ptr(),
                   trans.get_data_length());
        }
    }
}


BOILERPLATE
get_direct_mem_ptr_pre_checks(
    tlm::tlm_generic_payload &trans, tlm::tlm_dmi &dmi_data )
{
    remember_gp_option(trans);

    if (dmi_data.get_dmi_ptr() != 0) {
        txt << "DMI descriptor not properly initialized: dmi_ptr != 0";
        tlm2error(trans, "11.2.5 f)");
    }
    if (!dmi_data.is_none_allowed()) {
        txt << "DMI descriptor not properly initialized: granted_access != DMI_ACCESS_NONE";
        tlm2error(trans, "11.2.5 a)");
    }
    if (dmi_data.get_start_address() != 0) {
        txt << "DMI descriptor not properly initialized: start_address != 0";
        tlm2error(trans, "11.2.5 u)");
    }
    if (dmi_data.get_end_address() != (sc_dt::uint64)(-1)) {
        txt << "DMI descriptor not properly initialized: end_address != 0";
        tlm2error(trans, "11.2.5 u)");
    }
    if (dmi_data.get_read_latency() != sc_core::SC_ZERO_TIME) {
        txt << "DMI descriptor not properly initialized: read_latency != SC_ZERO_TIME";
        tlm2error(trans, "11.2.5 ac)");
    }
    if (dmi_data.get_write_latency() != sc_core::SC_ZERO_TIME) {
        txt << "DMI descriptor not properly initialized: write_latency != SC_ZERO_TIME";
        tlm2error(trans, "11.2.5 ac)");
    }

    if (trans.get_gp_option() == tlm::TLM_FULL_PAYLOAD) {
        /*
        if (trans.is_dmi_allowed())  // Would be rather brutal to flag dmi_allowed as an arror for a DMI transaction!
        {
          txt << "DMI transaction not properly initialized: dmi_allowed == true";
          tlm2error(trans, "14.8 e) & 14.16");
        }
        */
        if (trans.get_response_status() != tlm::TLM_INCOMPLETE_RESPONSE) {
            txt << "DMI transaction not properly initialized: response_status != TLM_INCOMPLETE_RESPONSE";
            tlm2error(trans, "14.8 e) & 14.17 e)");
        }
    } else if (trans.get_gp_option() == tlm::TLM_FULL_PAYLOAD_ACCEPTED) {
        txt << "DMI transaction not properly initialized: gp_option == TLM_FULL_PAYLOAD_ACCEPTED";
        tlm2error(trans, "14.8 c) & e) & j)");
    }
}


BOILERPLATE
get_direct_mem_ptr_post_checks( tlm::tlm_generic_payload &trans,
                                tlm::tlm_dmi & /*dmi_data*/ )
{
    tlm::tlm_generic_payload *init = m_map[&trans].gp;

    if (init->get_gp_option() == tlm::TLM_MIN_PAYLOAD
            && trans.get_gp_option() != tlm::TLM_MIN_PAYLOAD) {
        txt << "DMI transaction gp_option attribute value TLM_MIN_PAYLOAD modified during transaction lifetime";
        tlm2error(trans, "14.8 h)");
    } else if (init->get_gp_option() == tlm::TLM_FULL_PAYLOAD
               && trans.get_gp_option() == tlm::TLM_MIN_PAYLOAD) {
        txt << "DMI transaction gp_option attribute value changed from TLM_FULL_PAYLOAD to TLM_MIN_PAYLOAD";
        tlm2error(trans, "14.8 j)");
    }
}


BOILERPLATE
transport_dbg_pre_checks( tlm::tlm_generic_payload &trans )
{
    remember_gp_option(trans);

    if (trans.get_data_length() > 0 && trans.get_data_ptr() == 0) {
        txt << "Debug transaction has data_ptr == 0";
        tlm2error(trans, "11.3.4 l)");
    }

    if (trans.get_gp_option() == tlm::TLM_FULL_PAYLOAD) {
        if (trans.get_byte_enable_ptr() != 0 && trans.get_byte_enable_length() == 0) {
            txt << "Debug transaction not properly initialized: "
                << "byte_enable_ptr != 0 and byte_enable_length == 0";
            tlm2error(trans, "14.8 f) & 14.14 f)");
        }
        if (trans.get_streaming_width() == 0) {
            txt << "Debug transaction not properly initialized: streaming_width == 0";
            tlm2error(trans, "14.8 f) & 14.15 f)");
        }
        if (trans.is_dmi_allowed()) {
            txt << "Debug transaction not properly initialized: dmi_allowed == true";
            tlm2error(trans, "14.8 f) & 14.16");
        }
        if (trans.get_response_status() != tlm::TLM_INCOMPLETE_RESPONSE) {
            txt << "Debug transaction not properly initialized: response_status != TLM_INCOMPLETE_RESPONSE";
            tlm2error(trans, "14.8 f) & 14.17 e)");
        }
    } else if (trans.get_gp_option() == tlm::TLM_FULL_PAYLOAD_ACCEPTED) {
        txt << "Debug transaction not properly initialized: gp_option == TLM_FULL_PAYLOAD_ACCEPTED";
        tlm2error(trans, "14.8 c) & f) & l)");
    }
}


BOILERPLATE
transport_dbg_post_checks( tlm::tlm_generic_payload &trans, unsigned int count )
{
    tlm::tlm_generic_payload *init = m_map[&trans].gp;

    if (trans.get_data_length() > 0 && trans.get_data_ptr() == 0) {
        txt << "Debug transaction has data_ptr == 0";
        tlm2error(trans, "11.3.4 l)");
    }
    if (count > trans.get_data_length()) {
        txt << "Count returned from transport_dbg is greater than data_length";
        tlm2error(trans, "11.3.4 s)");
    }

    if (init->get_gp_option() == tlm::TLM_MIN_PAYLOAD
            && trans.get_gp_option() != tlm::TLM_MIN_PAYLOAD) {
        txt << "Debug transaction gp_option attribute value TLM_MIN_PAYLOAD modified during transaction lifetime";
        tlm2error(trans, "14.8 h)");
    } else if (init->get_gp_option() == tlm::TLM_FULL_PAYLOAD
               && trans.get_gp_option() == tlm::TLM_MIN_PAYLOAD) {
        txt << "Debug transaction gp_option attribute value changed from TLM_FULL_PAYLOAD to TLM_MIN_PAYLOAD";
        tlm2error(trans, "14.8 l)");
    }
}


BOILERPLATE
tlm2error( tlm::tlm_generic_payload &trans, const char *ref, bool warning )
{
    txt << "\n\nRefer to IEEE Std 1666-2011, clause " << ref;
    txt << "\n\nChecker instance: " << this->name();
    txt << "\n\nTransaction details:";
    txt << "\n  has_mm             = " << dec << trans.has_mm() << " (bool)";
    txt << "\n  ref_count          = " << dec << trans.get_ref_count() << " (int)";
    txt << "\n\n  gp_option          = " <<
        (trans.get_gp_option() == tlm::TLM_MIN_PAYLOAD  ? "TLM_MIN_PAYLOAD"
         : trans.get_gp_option() == tlm::TLM_FULL_PAYLOAD ? "TLM_FULL_PAYLOAD"
         : "TLM_FULL_PAYLOAD_ACCEPTED");
    txt << "\n  command            = " <<
        (trans.get_command() == tlm::TLM_READ_COMMAND  ? "TLM_READ_COMMAND"
         : trans.get_command() == tlm::TLM_WRITE_COMMAND ? "TLM_WRITE_COMMAND"
         : "TLM_IGNORE_COMMAND");
    txt << "\n  address            = " << hex << trans.get_address() << " (hex)";
    txt << "\n  data_ptr           = " << hex
        << reinterpret_cast<int *>(trans.get_data_ptr()) << " (hex)";
    txt << "\n  data_length        = " << hex << trans.get_data_length() <<
        " (hex)";
    txt << "\n  streaming_width    = " << hex << trans.get_streaming_width() <<
        " (hex)";
    txt << "\n  byte_enable_ptr    = " << hex
        << reinterpret_cast<int *>(trans.get_byte_enable_ptr()) << " (hex)";
    txt << "\n  byte_enable_length = " << hex << trans.get_byte_enable_length() <<
        " (hex)";
    txt << "\n  dmi_allowed        = " << dec << trans.is_dmi_allowed() <<
        " (bool)";
    txt << "\n  response_status    = " << trans.get_response_string();

    bool extensions_present = false;
    for (unsigned int i = 0; i < tlm::max_num_extensions(); i++) {
        tlm::tlm_extension_base *ext = trans.get_extension(i);
        if (ext) {
            if (!extensions_present)
                txt << "\n\n  extensions:";
            txt << "\n    index = " << i << "   type = " << typeid(*ext).name();
            extensions_present = true;
        }
    }

    txt << "\n\n";
    if (warning)
        SC_REPORT_WARNING("tlm2_protocol_checker", txt.str().c_str());
    else
        SC_REPORT_ERROR("tlm2_protocol_checker", txt.str().c_str());
}



} // namespace tlm_utils

#endif // __tlm2_base_protocol_checker__
