ARM Technical Support Knowledge Articles

Synchronous Watchpoint from SystemC

Applies to: Fast Models

Answer

Introduction

SCADI and synchronous stop from within SystemC can be extremely useful when implementing ‘external breakpoints’.  Such breakpoints can stop the simulation upon complex conditions which appear in the peripheral components. For example writing invalid register values or writing to invalid or reserved peripheral registers or other invalid conditions within a peripheral. This example is designed to illustrate how SCADI can be used from within a SystemC component.

The System

The example system contains:

SystemC Component

This component implements a single RW register (at offset 0x0). When read, the slave returns the value of this register, when written to the slave performs a synchronous stop of the simulation, in addition to storing the value. This component makes use of the SimpleCADI helper file that is discussed in the following article. In order to enable the component to synchronously stop any core, the syncLevel on the core must be set to syncLevel 2 (POST_INSN_IO). This is done by first obtaining a CADI pointer for each core.

  1. Obtain the global interface and check if the pointer has been successfully initialised.

    // get access to a CADI Interface
    eslapi::CAInterface * caif = System.getGlobalInterface();

    if (caif == 0)
    {
       std::cerr << "Global interface not found!\n";
    }

  1. From this the MTI SystemTraceInterface can be used to obtain a list of component within the system. This is implemented in the end_of_elaboration() function.
    const char *errorMessage = "(no error)";
    eslapi::CADI* core_cadi;
    MTI::SystemTraceInterface *mti = 0;
    
    if (caif)
        mti = sg::obtainComponentInterfacePointer<MTI::SystemTraceInterface>(caif, "mtiRegistry", &errorMessage);
    
    if (mti == 0)
    {
        printf("TEST Peripheral: error: MTI interface not found! (%s)\n", errorMessage);
        return;
    }    

        // check for at least 1 component
        uint32_t number_of_components = mti->GetNumOfTraceComponents();
        if (!number_of_components)
        {
            std::cerr << "Error, no mti components\n";
            return;
        }
  1. The MTI interface can be used to obtain a CADI pointer.  Using SimpleCADI, write to the “syncLevelPostInsnIORegister” register if present.
        for (uint32_t i=0; i<number_of_components;i++)
        {
            //Get trace interface
            caif = mti->GetComponentTrace(i);

            //Obtain CADI interface
            caif = caif->ObtainInterface("eslapi.SCADI2", 0, 0);
            
            //Check if pointer was successfully obtained
            if (caif == NULL)
            {
                std::cerr << "CADI interface not found!\n";
                return;
            }
                       
            simplecadi.setCADI(static_cast<eslapi::CADI*>(caif));

            // if component has a syncLevel register it is a core
            if (simplecadi.hasRegister("syncLevel"))
            {
                //set synclevel to syncLevelPostInsnIORegister
                simplecadi.regWrite("syncLevelPostInsnIORegister", 2);
                core_cadi=static_cast<eslapi::CADI*>(caif);
            }
        }

Once the syncLevel on the cores has been appropriately set, the simulation can be synchronously stopped by using the CADIExecStop() function. The SimpleCADI::Stop() implements the CADIExecStop(). By invoking SimpleCADI::Stop() from within the write access function the simulation will stop whenever a value is being written to the register.

/*
* Write access
*/
amba_pv::amba_pv_resp_t SysC_Component::write(int socket_id,
                                   const sc_dt::uint64 & addr,
                                   unsigned char * data,
                                   unsigned int size,
                                   const amba_pv::amba_pv_control * ctrl,
                                   unsigned char * strb,
                                   sc_core::sc_time & t) {
    switch (addr) {
        case REG_ADDR:
            std::cout << "**DEBUG** " << name() << ": Received write request "
                      << "at address: " << std::showbase << std::hex << "0x"
                      << addr << std::endl;
            
            //Write value to register
            SysC_Component::Register = (* reinterpret_cast<unsigned int *>(data));

            //Stop execution of simulation if register is being written to
            simplecadi.stop();

            break;

        default:
            std::cout << "**ERROR** " << name() << ": received write request "
                      << "with input address out of range: " << std::showbase
                      << std::hex << addr << std::endl;
            return (amba_pv::AMBA_PV_SLVERR);
    }
    return (amba_pv::AMBA_PV_OKAY);
}


Example Application

An example application is provided as assembler within the 'app' folder. A build script is also provided to aid rebuilding of the image. The application performs stores to the slaves register then enters an idle loop.

Running the example

To run the example you need to perform the following steps:

Linux:

1) build the example using the provided Makefile. For debug build with GCC4.1 (32bit): 

   make  dbg_gcc41_32

2) build the application in the apps folder.
3) Launch the resulting executable via command line using the following command:

    ./SysC_Component.x  -a  app\image.axf

Windows:

1) Build the example system using provided nMakefile. For debug build with VC2010 (32bit):

   nmake /f nMakefile  dbg_vs10_32

2) build the application in the apps folder.
3) Launch the resulting executable via command line using the following command:

    SysC_Component.exe  -a  app\image.axf

Why do I have to set the syncLevel? Why is a simple CADIExecStop() not sufficient?

A simple CADIExecStop() without setting the syncLevel POST_INSN_IO beforehand will also stop the simulation, but as the simulation is executed in chunks of instructions (called a quantum) rather than individual instructions, the simulation would only stop at the end of such a quantum. The effect would be that the simulation has run typically about 50-200 instructions past the point where the breakpoint happened. This is often undesirable. Setting the syncLevel to POST_INSN_IO makes the simulation stop precisely after all peripheral accesses.

Why is syncLevel=POST_INSN_IO not the default? It seems so useful!

This is because the simulation gets slower with increasing syncLevels (increasing simulation accuracy and the default is the fastest mode of simulation. Users (LISA+ components) need to tell the simulator which restriction they need. The breakpoint could also be disabled by an end user in which case the syncLevel can be released to the default (OFF, 0).

Attachments: syncwatchpoints.png , MI-syncwatchpoint.zip

Article last edited on: 2013-08-27 13:40:49

Rate this article

[Bad]
|
|
[Good]
Disagree? Move your mouse over the bar and click

Did you find this article helpful? Yes No

How can we improve this article?

Link to this article
Copyright © 2011 ARM Limited. All rights reserved. External (Open), Non-Confidential