2.2.3. Registers

Use the REGISTER keyword to specify registers in the resources section. Registers are resources that store data and a bit-width and type can be specified for the register. The default data type is unsigned int and the default bit width is 32. Optional parameters that can be given to registers are listed in Table 2.1.

Table 2.1. Optional parameters for registers 

ParameterTypeDefaultDescription
addressintegernone

An address parameter is required to map memory-mapped register accesses. The address specified is translated into the unique register ID.

attributeaccess typeread_write

Access type as one of:

  • read_write

  • read_only

  • write_only.

bitwidthinteger32

Data bit-width. Admissible values are:

  • 8, 16, 32, and 64 for integer types.

  • 32 and 64 for floating point types

  • 1 for boolean types

  • the bitwidth is ignored for string registers.

descriptionstring""Description of the resource
display_float_formatstring"%g"Printf() format string for debugger display for floating point registers, only used for type(float) and display_format(float).
display_formatstring"hex"Default display format for debuggers, supported formats are: hex, uint, int, bool, float, and string. Debuggers can always override this setting.
display_symbolsstring-list-Comma-separated list of strings replacing the numerical display. Not all tools implement this feature.
dwarf_idinteger-Dwarf register ID. If not set, the register does not have a DWARF register ID. these are typically defined by the architecture ABI.
groupsstring-list-List of register groups that the register is assigned to. Groups are separated by commas.
has_side_effectsbooleanfalseSet to true if register access has side effects.
is_program_counterbooleanfalseSet to true if the resource is the program counter.
lsb_offset(Bit)--Specifies the bit offset in the parent register. See Component registers.
namestringresource name

Register name to display in, for example, a debugger. The default is:

  • the resource variable name for non-array resources, for example “R” for register R.

  • the resource variable name followed by the decimal index for register arrays, for example R[0], R[1], … R[7] for register array R[8]. The name string can contain a single printf() integer specifier for register arrays. For example:

    REGISTER { name("R%u") } a[4];

    results in registers R0, R1, R2 and R3 in the debugger.

name_index_baseinteger0For register arrays with format specifiers in the name, such as name(“R%u”), this parameter specifies the start index. For example, name(“R%u”) and name_index_base(3) sets the first register array element to R3.
partof(ParentReg)--Specifies the parent register. See Component registers.
pv_portintegernoneSpecifies the internal pv_port used to map read and write access to peripheral registers. The port must be a slave port of type PVDevice. If only one unique port of type PVDevice exists, this parameter can be omitted.
read_behaviorstringnoneSpecifies a user-defined read behavior to use if the automated mechanisms are not sufficient. Use of this behavior depends on the internal state.
read_functionstringnoneName of the debug read access behavior.
read_maskintegernoneSpecifies the value used for read accesses.
read_sec_maskintegernoneSpecifies the value used for read accesses. For secure accesses, this value overwrites other masks.
reg_numberintegerautoCADI register ID. The value of reg_number can be any 32-bit unsigned integer constant except for the reserved value 0xFFFFFFFF.
reg_number_incrementinteger1Specifies the reg_number increment between array elements, starting with reg_number. Applies only to register arrays and if reg_number is specified.
reset_value<type>0, 0.0, "", or false

Reset value. The value specified by the parameter is assigned to the register at initialization and reset execution.

If the parameter is omitted, the register is reset with:

  • zero value for numeric type registers

  • false for boolean type registers

  • "" for string type registers.

To avoid register initialization on reset, you must use UNINITIALIZED as the parameter argument.

Admissible types are bool, int, uint, float, and string.

type<type>uintData type. Admissible types are bool, int, uint, float, and string.
virtualbooleanfalse

Optimizes code generation by preventing the allocation of host memory for the resource. If virtual is true, no variable is generated for the register or memory, the resource must have read and write access functions, and must not be referenced in LISA+ code. By default, all registers and memory are not virtual. virtual does not apply to parameters. See Example 2.3.

visible_in_debuggerbooleantrueDebug switch. Set to true to show the register in debugger or set to false to hide the register.
write_behaviorstringnoneSpecifies a user-defined write behavior to use if the automated mechanisms are not sufficient. Use of this behavior depends on the internal state.
write_functionstringnone

Name of the debug write access behavior.

Note

The access functions must match the arguments and return type.

write_maskintegernoneSpecifies the value used for write accesses.
write_sec_maskintegernoneSpecifies the value used for write accesses. For secure accesses, this value overwrites other masks.

A register set R consisting of 32 registers that are 32 bits wide and a register file using the default types is specified as shown in Example 2.2:

Example 2.2. Register set specification

resources
{
    REGISTER { bitwidth(32) } R[32];
    int a, b; // not visible in a debugger
    REGISTER { is_program_counter(true) } pc;
    REGISTER gpr[32];
    REGISTER { bitwidth(64), type(int) } accu;
    REGISTER { type(float) } fpr[16];
}

IDs in register arrays

If a register array is assigned an ID, the first register receives the value of the ID. Each subsequent register in the array is assigned the ID of the previous register incremented by one. For example:

    REGISTER { reg_number(5) } R[32];

Register R[0] is assigned ID 5, R[1] is assigned ID 6, R[2] is assigned ID 7, and so forth. You can use the reg_number_increment parameter to step between registers in an array, starting with reg_number. If, for example, reg_number_increment was set to 2 in the earlier example, R[1] would not be used.

Virtual registers

Example 2.3 shows how to use the virtual parameter. The parameter enables the abstract definition of a resource in much the same as C++ permits the definition of pure virtual functions. If virtual functions are used, the C++ programmer of the derived class is forced to implement them. Similarly, the LISA+ programmer is forced to implement a read and a write access behavior and not rely on the existence of a variable that has the resource name.

Example 2.3. Use of virtual parameter

component foo
{
	resources
	{
		REGISTER { type(uint32_t), virtual(true), read_function(GetStatus),
           write_function(SetStatus) } STATUS;
		REGISTER { type(uint32_t) } ENABLED;
		REGISTER { type(uint32_t) } ACTIVE;
	}

	behavior GetStatus(uint32_t id, uint64_t *data, bool doSideEffects) :
       AccessFuncResult
	{
		*data = ENABLED & ACTIVE;
		return ACCESS_FUNC_OK;
	}

	behavior SetStatus(uint32_t id, const uint64_t *data, bool doSideEffects) :
       AccessFuncResult
	{
		return ACCESS_FUNC_OK;
	}
}

In Example 2.3, component foo has registers STATUS, ENABLED and ACTIVE:

  • STATUS is visible to the debugger as a value that is the bit-wise AND operation of ACTIVE and ENABLED.

  • There is no reason to access STATUS from LISA+ code, so the design is enforced.

  • There is also no reason to write a value to STATUS, so using SetStatus simply returns the success flag.

Register access

Registers are accessed in the same way as C variables. See Example 2.4.

Example 2.4. Register assignment

behavior
{
    R[4] = a;
    R[b] = gpr[a];
}

Component registers

LISA+ supports registers that are embedded within other registers. That is, they are a component, or child, of a parent register. The implementation of component registers requires that:

  • Component registers must be wholly embedded in their parent register. Each bit of the child register is found in the parent. Each component register therefore has exactly one parent.

  • Component registers must have the same bit sequence as the parent. The bits of the component register are in the same order as the corresponding bits of the parent register. The bit sequence can be shifted in position, but cannot be split or manipulated in any other way.

The specification of the component register can be fully defined by the following parameters:

partof(Parent)

Parent is the name of the parent register. This parameter is represented in LISA+ by the partof resource attribute. The value specified in partof is the name of the parent register.

lsb_offset(Bit)

Bit is the Least Significant Bit (LSB) offset of the child in the parent register. This parameter is represented by the lsb_offset attribute that contains the LSB offset, in bits, of the child in the parent register. Specifying the LSB offset is optional and the default value is zero

The registers behave like an integer of the size specified in the bitwidth attribute. However, all modifications on a child or parent register affect the value of the corresponding parent or child registers. This is handled automatically. A child register can also be a parent. This enables component register relationships to extend to component register hierarchies. The consistency of the hierarchies is enforced automatically.

A simple example of a component register hierarchy is shown in Example 2.5:

Example 2.5. Component register of a parent register

REGISTER { bitwidth(64) } RAX;
REGISTER { bitwidth(32), partof(RAX) } EAX;
REGISTER { bitwidth(16), partof(EAX) } AX;
REGISTER { bitwidth(8), partof(AX) } AL;
REGISTER { bitwidth(8), partof(AX), lsb_offset(8) } AH;

Debugger register access functions

The default functions that a debugger calls to access registers can be overridden by using behaviors that conforms to a specific prototype. The prototypes for these functions are listed in Table 2.2:

Table 2.2. Debugger register access functions

Function Prototype
Register read function[a]
behavior <name>(uint32_t reg_id, uint64_t *data,
       bool side_effects) : AccessFuncResult
Register write function[b]
behavior <name>(uint32_t reg_id, const uint64_t *data,
       bool side_effects) : AccessFuncResult
String register read function
behavior <name>(uint32_t reg_id, string &data,
       bool side_effects) : AccessFuncResult
String register write function
behavior <name>(uint32_t reg_id, const string &data,
       bool side_effects) : AccessFuncResult

[a] Refer to the CADIRegRead function description. See also the Model Debugger for Fast Models User Guide.

[b] Refer to the CADIRegWrite function description. See also the Model Debugger for Fast Models User Guide.


Where:

reg_id

holds the register ID of the register that is being accessed, that is, the argument used in the reg_number attribute. You can modify the array index step size by using reg_number_increment if you have a register array.

For each register with name name, a constant REGISTER_ID_name is generated. An array register has one id for the base and one for each index. The index ID is formed by appending an "_" and the index of the array entry.

data

is a buffer in which read access functions must export to and write access functions must import from:

  • If the bitwidth is less than or equal to 64, the data pointer points to a single 64 bit quantity.

  • For larger registers, the data pointer points to an array of uint64_t that holds the entire register value. data[0] contains the least significant bits and so on in this case.

Note

The write access function prototypes declare the data parameter as const.

A separate pair of prototypes exist specifically for registers of type string. To simplify use, the data parameter is also of type string for the string access functions.

side_effects

is a parameter indicating whether side effects of the access are enforced.

The side_effects parameter specifies whether a read or write involving a register or memory invokes specific side effects associated with that particular register or memory. The semantics of the side_effects parameter is slightly different for read_function and write_function. For read_function, the semantics for side_effects are:

side_effects == false

The read must only return the value of the register and not cause any other side effects. The debugger calls the function with side_effects == false to display the value of the register.

side_effects == true

The read returns the value of the register and causes side effects that are associated with reading the register. Invoking side effects while reading a register is not common. The debugger only calls read_function with side_effects == true if the user explicitly wants to trigger side effects.

For write_function, the semantics for side_effects are:

side_effects == false

The function might or might not cause side effects, depending on what the component can handle. Some side effects are required even if side_effects == false to keep the component in a consistent state. The only side effects invoked are those required to retain consistency.

The side effects are highly dependent on the modeled hardware. For example, if writing to a SIZE register adjusts the ENDPTR register, update the value of SIZE must reasonably also cause the side effect of updating ENDPTR. For this example, the side_effects parameter must be ignored for writes.

side_effects == true

The function causes all side effects that a normal bus write would cause. Invoking side effects for register writes is the most common use case.

Register and memory access functions must inform the calling code whether or not the access operation was successful. The following symbols have been added to the LISA+ language for this purpose:

ACCESS_FUNC_OK

The call was successful.

ACCESS_FUNC_GeneralError

This indicates an error that cannot be sufficiently explained by one of the other error return values.

ACCESS_FUNC_UnknownCommand

The command is not recognized.

ACCESS_FUNC_IllegalArgument

At least one of the argument values is illegal.

ACCESS_FUNC_CmdNotSupported

The command is recognized but not supported.

ACCESS_FUNC_ArgNotSupported

An argument to the command is recognized but not supported. For example, the target does not support a particular type of complex breakpoint.

ACCESS_FUNC_InsufficientResources

Not enough memory or other resources exist to fulfil the command.

ACCESS_FUNC_TargetNotResponding

A time out has occurred across the CADI interface and the target did not respond to the command.

ACCESS_FUNC_TargetBusy

The target received a request, but is unable to process the command. The call can be attempted again after some time.

ACCESS_FUNC_BufferSize

Buffer too small, for char* types.

ACCESS_FUNC_SecurityViolation

Request was not fulfilled because of a security violation.

ACCESS_FUNC_PermissionDenied

Request was not fulfilled because permission was denied.

In Example 2.6, registers R1 and R2 are assigned CADI ids and a shared read access function. The access function returns one's complement for R1 and two's complement for R2. If the access function is assigned to a different register, it reports an illegal argument.

Example 2.6. Read access function

resources
{
    REGISTER { read_function(my_read), reg_number(1) } R1;
    REGISTER { read_function(my_read), reg_number(2) } R2;
}

behavior my_read(uint32_t id, uint64_t *data, bool se) : AccessFuncResult
{
    if (id == 1)
        *data = ~R1;
    else if (id == 2)
        *data = ~R2 + 1;
    else
        return ACCESS_FUNC_IllegalArgument;

    return ACCESS_FUNC_OK;
}

Memory-mapped register access

PV peripherals implement memory-mapped register by connecting an external slave port of type PVBus to an internal slave port of type PVDevice.

Register IDs are specified explicitly and have the same value as the address offset. The register accesses are implemented by an internal read/write behavior. Memory-mapped port accesses and debug accesses are directed to this behavior.

If a unique internal port of type PVDevice exists, all memory-mapped register read and write operations over this port are directed to the automatically generated access functions.

If not already overwritten by read_function and write_function parameters the new access functions are also used for debug accesses.

Masks are used to influence the register access:

  • There are masks for read and write operations.

  • If the device distinguishes between secure and non-secure accesses, an additional set of read and write masks can be provided for secure accesses.

  • If the mask parameter, is omitted full access is permitted.

  • A zero mask ignores the access but returns a complete response.

Two additional tokens can be used to generate error responses:

  • ABORT for an abort error response

  • DECODEABORT for a decode error response.

If the automatic mechanisms are not sufficient, you can provide a local implementation that overrides the access behaviors. The arguments for the access behaviors are:

register_read_behavior(uint32_t reg_id, pv::ReadTransaction tx) :
    pv::Tx_Result
register_write_behavior(uint32_t reg_id, pv::WriteTransaction tx) :
    pv::Tx_Result

Each register resource has an ID that must be used in the read and write behaviors. See Debugger register access functions.

Note

Using memory-mapped register access features requires the Fast Models include files.

Copyright © 2007-2010 ARM Limited. All rights reserved.ARM DUI 0372I
Non-Confidential