ARM Technical Support Knowledge Articles

How to implement CADI breakpoints in a LISA component

Applies to: Fast Models

Answer

Introduction

The Fast Models provide a number of LISA+ keywords to allow components resources to automatically be exposed via CADI (e.g. REGISTER and MEMORY).

Breakpoint functionality cannot, however, be automatically generated, therefore, additional functionality needs to be manually implemented in the LISA+ component. This additional functionality is provided via a slave port called 'cadi_port' implementing the CADIProtocol LISA protocol. 

CADIProtocol

The CADIProtocol provides a number of optional slave behaviours which allow a LISA+ component to handle various CADI requests from an attached debugger.

     // CADI Bpt API
    optional slave behavior CADIBptGetList(uint32_t, uint32_t, uint32_t *, eslapi::CADIBptDescription_t *):eslapi::CADIReturn_t;
    optional slave behavior CADIBptRead(eslapi::CADIBptNumber_t, eslapi::CADIBptRequest_t *):eslapi::CADIReturn_t;
    optional slave behavior CADIBptSet(eslapi::CADIBptRequest_t *, eslapi::CADIBptNumber_t *):eslapi::CADIReturn_t;
    optional slave behavior CADIBptClear(eslapi::CADIBptNumber_t):eslapi::CADIReturn_t;
    optional slave behavior CADIBptConfigure(eslapi::CADIBptNumber_t, eslapi::CADIBptConfigure_t):eslapi::CADIReturn_t;

    // The following method allows to modify the default target features. Note that this method is not
    // part of the CADI specification.
    optional slave behavior CADIModifyTargetFeatures(eslapi::CADITargetFeatures_t *):eslapi::CADIReturn_t;

    // disassembly (this implements both, CADIGetDisassembler() and also ObtainInterface("eslapi.CADIDisassembler2"))
    optional slave behavior CADIGetDisassembler():eslapi::CADIDisassembler*;
        
    // single stepping needs support from the individual model (run and stop are always handled globally)
    optional slave behavior CADIExecSingleStep(uint32_t instructionCount, int8_t stepCycle, int8_t stepOver):eslapi::CADIReturn_t;

    // forward all modeChange() callbacks to the target component
    // (the target should generally ignore all of these except when implementing CADIExecSingleStep())
    optional slave behavior callbackModeChange(uint32_t newMode, eslapi::CADIBptNumber_t bptNumber);

    // get instruction/cycle count
    optional slave behavior CADIGetInstructionCount(uint64_t &instructionCount):eslapi::CADIReturn_t;
    optional slave behavior CADIGetCycleCount(uint64_t &instructionCount, bool systemCycles):eslapi::CADIReturn_t;

In this article we will concentrate on the breakpoint API.

Breakpoints in CADI are controlled and stored as CADIBptRequest_t objects on the target (in this case the LISA+ component). It is the responsibility of the component to create and manage these objects (one for each breakpoint the slave supports). Each CADIBptRequest_t object is identified by a breakpoint number (0 to max number of breakpoints-1).

The CADIBptRequest_t object contains the following member variables:

    CADIAddrComplete_t  address;           ///< The PC or memory address at which the breakpoint should occur.
    uint64_t            sizeOfAddressRange; /**< Used only if type = CADI_BPT_PROGRAM_RANGE or CADI_BPT_MEMORY.
                                             *   Size of the address range in addresses (in bytes for a byte addressable memory).
                                             *   A size of 0 must be treated as a size of 1 by targets for type = CADI_BPT_MEMORY.
                                             */
    int32_t             enabled;            ///< Status of the breakpoint (enabled/disabled). Also initial state of the breakpoint for CADIBptSet().
    char                conditions[CADI_DESCRIPTION_SIZE]; /**< The breakpoint condition. Ultimately the target
                                                                decides if it can implement breakpoint conditions.*/
    bool                useFormalCondition;     /**< 0 = use free-form "conditions",
                                                     1 = use "formalCondition"*/
    CADIBptCondition_t  formalCondition;    ///< Formal conditions
    CADIBptType_t       type;               ///< Type
    uint32_t            regNumber;          ///< For type = CADI_BPT_REGISTER only
    int32_t             temporary;          ///< Temporary breakpoint

    uint8_t             triggerType;        /**< Allow breakpoints that trigger only on read/write/modify
                                             *   This only has meaning for CADI_BPT_REGISTER and
                                             *   CADI_BPT_MEMORY breakpoints.  The debugger should set this to zero
                                             *   for other breakpoint types.  Setting this to zero for
                                             *   CADI_BPT_REGISTER and CADI_BPT_MEMORY results in undefined
                                             *   behaviour and must not be done.
                                             */
    uint32_t            continueExecution;  /**< 1 = Continue execution after breakpoint has been hit
                                             *   \note This field should be obeyed by \e types of breakpoints,
                                             *         including CADI_BPT_INST_STEP, etc.
                                             */

This example supports only register breakpoints therefore only the 'enabled' and 'regNumber' variables will be used.

CADIBptSet(eslapi::CADIBptRequest_t *, eslapi::CADIBptNumber_t *)

The CADIBptSet behaviour allows the debugger to request the target to create/set a breakpoint based on the information in the CADIBptRequest_t object passed to the target.

For register breakpoints the regNum variable in the request object provides information on which register the debugger is requesting a breakpoint to be set on.

In the example, when CADIBptSet is called a new local CADIBptRequest_t object is created. Once created the contents of the requested breakpoint are copied into this local object.

    bptrequest_config = new eslapi::CADIBptRequest_t;
    *bptrequest_config = *request;

The target has to provide back to the debugger a breakpoint ID number to uniquely identify this breakpoint. This is done through the *breakpoint argument

    *breakpoint = 0;

CADIBptClear (eslapi::CADIBptNumber_t breakpointId)

The CADIBptClear behaviour can be seen as the opposite of CADIBptSet. Here the debugger is telling the target to delete an already set breakpoint, using its breakpoint ID number to identify which breakpoint to delete.

In the example this is done by deleting the local CADIBptRequest_t object and setting the pointer to NULL

    delete bptrequest_config;
    bptrequest_config = NULL;

CADIBptConfigure (eslapi::CADIBptNumber_t breakpointId, eslapi::CADIBptConfigure_t configuration)

The CADIBptConfigure behaviour allows the debugger to enable or disable an already set/created breakpoint. The specific breakpoint to re-configure is specified through the breakpoint ID number and the CADIBptConfigure_t argument defines whether the breakpoint is to be set to enabled or disabled

    if (breakpointId == 0)
        bptrequest_config->enabled = (configuration == eslapi::CADI_BPT_Enable);

CADIBptRead (eslapi::CADIBptNumber_t breakpointId, eslapi::CADIBptRequest_t *requestOut)

The CADIBptRead behaviour allows the debugger to obtain a CADIBptRequest_t object for a specific breakpoint. Once again the breakpoint ID number is used to specify the breakpoint to be read and it is the targets responsibility to populate the *requestOut object.

In the example, this is done by copying the contents from the local object.

    if (breakpointId == 0)
        *requestOut = *bptrequest_config;

CADIBptGetList(uint32_t startIndex, uint32_t desiredNumOfBpts, uint32_t *actualNumOfBpts, eslapi::CADIBptDescription_t *bpt_descriptions)

The CADIBptGetList behaviour is used by the debugger to request a list of all set breakpoints on the target (both enabled and disabled). It is the targets responsibility to populate an array of CADIBptDescription_t objects, one element for each set breakpoints.

CADIBptDescription_t contains 2 pieces of information

  1. the CADIbptRequest_t information
  2. the breakpoint ID number

These 2 pieces of information allows the debugger to know everything about a breakpoint.

CADIModifyTargetFeatures(eslapi::CADITargetFeatures_t *targetFeatures)

Before a debugger can try and set a specific breakpoint type, it should query the target's CADITargetFeatures_t object to see if a specific breakpoint type is supported. By default, when a component is built, the Fast Models tool will generate a CADITargetFeatures_t object which does not support any breakpoints. A target can modify these default values by implementing the CADIModifyTargetFeatures behaviour.

This behaviour is called every time a debugger reads the CADITargetFeatures_t object. The default object is passed into the behaviour and it is the targets responsibility to modify any value in the object it needs to.

In the example, the breakpoint type the target supports is set to register breakpoints and the total number of breakpoints supported is set to 2.

    targetFeatures->handledBreakpoints = eslapi::CADI_TARGET_FEATURE_BPT_REGISTER;
    targetFeatures->nrBreakpointsAvailable = 2;

Example system

The example system consists of:

Peripheral

The peripheral contains 2 register, each of which a breakpoint can be set on

    REGISTER {reg_number(0), bitwidth(32), description("general purpose register at offset 0x0")} reg_config;
    REGISTER {reg_number(1), bitwidth(32), description("general purpose register at offset 0x4")} reg_flags;

Each register is memory mapped and therefore can be accessed through offset 0x0 and 0x4 respectively

    behavior read(pv::ReadTransaction tx): pv::Tx_Result
    {
        if (tx.getAddress() == 0)
            return tx.setReturnData32(reg_config);
        else if (tx.getAddress() == 4)
            return tx.setReturnData32(reg_flags);
        else
            return tx.generateSlaveAbort();
    }

When a breakpoint is set on any of the registers, the peripheral will request a simulation stop

    behavior write(pv::WriteTransaction tx): pv::Tx_Result
    {
        if (tx.getAddress() == 0)
        {
            reg_config = tx.getData32();

            if (bptrequest_config) // is bpt set/created
            {            
                if (bptrequest_config->enabled)  // and is it enabled
                {
                    simBreakpointHit(0); // define the bpt number which generated the stop
                    simHalt();
                }
            }
            return tx.writeComplete();
        }

        ... 

On a write, the component first checks to see if that registers breakpoint is set (the CADIBptRequest_t pointer not set to NULL). If breakpoint is present, it is enabled and the peripheral can request a simulation stop upon hitting it. 

To ensure that the attached debugger knows which breakpoint was hit, the simBreakpointHit function needs to be called. This takes as an argument, the breakpoint ID number of the breakpoint which is being hit. Once this has been called the simulation halt function, simHalt, is called.

Synchronous halt

To enable the peripheral to stop execution synchronously on the store instruction accessing it, the syncLevel of the core needs to be set to PostInsnIO. This syncLevel only needs to be at this value when a breakpoint is set. Due to this, in the CADIBptSet behaviour, code has been added to set the syncLevel by writing to the syncLevelPostInsnIORegister register. Additionally, in the CADIBptClear behaviour the peripheral writes to the syncLevelPostInsnIOUnregister register to return the syncLevel to its previous value.

Attachments: example_system.png , registerBpt.zip

Article last edited on: 2013-08-27 13:37:23

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