| |||
| Home > Components > Resources section > 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
| Parameter | Type | Default | Description |
|---|---|---|---|
address | integer | none | An address parameter is required to map memory-mapped register accesses. The address specified is translated into the unique register ID. |
attribute | access type | read_write | Access type as one of:
|
bitwidth | integer | 32 | Data bit-width. Admissible values are:
|
description | string | "" | Description of the resource |
display_float_format | string | "%g" | Printf() format string
for debugger display for floating point registers, only used for type(float) and display_format(float). |
display_format | string | "hex" | Default display format for debuggers, supported
formats are: hex, uint, int, bool, float,
and string. Debuggers can always override this
setting. |
display_symbols | string-list | - | Comma-separated list of strings replacing the numerical display. Not all tools implement this feature. |
dwarf_id | integer | - | Dwarf register ID. If not set, the register does not have a DWARF register ID. these are typically defined by the architecture ABI. |
groups | string-list | - | List of register groups that the register is assigned to. Groups are separated by commas. |
has_side_effects | boolean | false | Set to true if register
access has side effects. |
is_program_counter | boolean | false | Set to true if the resource
is the program counter. |
lsb_offset(Bit) | - | - | Specifies the bit offset in the parent register. See Component registers. |
name | string | resource name | Register name to display in, for example, a debugger. The default is:
|
name_index_base | integer | 0 | For 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_port | integer | none | Specifies 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_behavior | string | none | Specifies a user-defined read behavior to use if the automated mechanisms are not sufficient. Use of this behavior depends on the internal state. |
read_function | string | none | Name of the debug read access behavior. |
read_mask | integer | none | Specifies the value used for read accesses. |
read_sec_mask | integer | none | Specifies the value used for read accesses. For secure accesses, this value overwrites other masks. |
reg_number | integer | auto | CADI register ID. The value of reg_number can
be any 32-bit unsigned integer constant except for the reserved
value 0xFFFFFFFF. |
reg_number_increment | integer | 1 | Specifies 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:
To
avoid register initialization on reset, you must use Admissible types are |
type | <type> | uint | Data type. Admissible types are bool, int, uint, float,
and string. |
virtual | boolean | false | Optimizes code generation by preventing
the allocation of host memory for the resource. If |
visible_in_debugger | boolean | true | Debug switch. Set to true to
show the register in debugger or set to false to
hide the register. |
write_behavior | string | none | Specifies a user-defined write behavior to use if the automated mechanisms are not sufficient. Use of this behavior depends on the internal state. |
write_function | string | none | Name of the debug write access behavior. NoteThe access functions must match the arguments and return type. |
write_mask | integer | none | Specifies the value used for write accesses. |
write_sec_mask | integer | none | Specifies 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];
}
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.
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.
Registers are accessed in the same way as C variables. See Example 2.4.
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) is
the name of the parent register. This parameter is represented in LISA+
by the Parentpartof resource attribute. The value specified
in partof is the name of the parent register.
lsb_offset(Bit) is
the Least Significant Bit (LSB) offset of the
child in the parent register. This parameter is represented by the Bitlsb_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;
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 [b] Refer
to the | |
Where:
reg_idholds 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 ,
a constant nameREGISTER_ID_ 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.name
datais 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.
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_effectsis 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:
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.
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:
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.
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_OKThe call was successful.
ACCESS_FUNC_GeneralErrorThis indicates an error that cannot be sufficiently explained by one of the other error return values.
ACCESS_FUNC_UnknownCommandThe command is not recognized.
ACCESS_FUNC_IllegalArgumentAt least one of the argument values is illegal.
ACCESS_FUNC_CmdNotSupportedThe command is recognized but not supported.
ACCESS_FUNC_ArgNotSupportedAn argument to the command is recognized but not supported. For example, the target does not support a particular type of complex breakpoint.
ACCESS_FUNC_InsufficientResourcesNot enough memory or other resources exist to fulfil the command.
ACCESS_FUNC_TargetNotRespondingA time out has occurred across the CADI interface and the target did not respond to the command.
ACCESS_FUNC_TargetBusyThe target received a request, but is unable to process the command. The call can be attempted again after some time.
ACCESS_FUNC_BufferSizeBuffer too small, for char* types.
ACCESS_FUNC_SecurityViolationRequest was not fulfilled because of a security violation.
ACCESS_FUNC_PermissionDeniedRequest 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;
}
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.
Using memory-mapped register access features requires the Fast Models include files.