User-Defined Properties

The SystemRDL standard allows users to extend components with custom properties. These UDPs are declared within the user’s SystemRDL source code prior to use. Inevitably, these UDPs are processed by a downstream tool that has some well-defined semantics for these language extensions. This compiler provides an API that allows tool develoers to define additional validation for these properties.

Pre-registering a UDP

UDP semantics can be pre-loaded into the compiler namespace as a way to formalize extensions to SystemRDL that your tool supports.

Registration of a UDP is done as follows:

from systemrdl.udp import UDPDefinition
from systemrdl.components import Field, Signal

# 1. Describe your UDP
class MyUDPDefinition(UDPDefinition):
    name = "my_udp"
    valid_components = {Field, Signal}
    valid_type = int

    # Optional callback that validates values assigned to 'my_udp'
    def validate(self, node, value):
        if(value == 42):
            self.msg.error(
                "The value assigned to 'my_udp' cannot be 42! That number is reserved.",
                self.get_src_ref(node)
            )

# 2. Register it with your RDLCompiler instance
rdlc.register_udp(MyUDPDefinition)

The above definition is equivalent to the following SystemRDL:

property my_udp {
    type = longint unsigned;
    component = field | signal;
};

Soft UDPs

By default, register_udp() registers UDPs as “soft” definitions. Soft UDP definitions behave as follows:

  • The UDP is not available to be used until it is explicitly defined in the SystemRDL source. If a user attempts to use a soft UDP prior to it being declared, the compiler will flag an error.

  • Upon definition, the user’s declaration shall be equivalent to the pre-registered definition. If the user’s declaration does not match, the compiler will flag an error. This ensures that the user’s declaration matches the expectations of your tool.

  • If the user’s RDL source never defines the UDP, querying it via node.get_property() will gracefully return its unassigned default (as defined by get_unassigned_default()) instead of a LookupError exception. This simplifies how tool developers interact with users’ RDL code.

  • Once defined by the user in RDL source, the UDP is no longer considered ‘soft’, and can be assigned normally.

Important

Although it may initially seem like more steps for the end-user, having them declare the UDP in the RDL source is preferred over pre-declaring “hard” UDPs within your tool.

Silently declaring hard UDPs not recommended since it encourages users to write SystemRDL that uses UDP extensions that are not formally declared in the RDL source. This bends the rules of the SystemRDL specification and hurts the cross-vendor compatibility of your users’ SystemRDL source code.

Using soft UDPs has the benefit of enforcing that the user defines and uses UDPs correctly whilst not violating the official SystemRDL language specification.

The UDPDefinition descriptor

The full details of the UDPDefinition class is as follows:

Class variables and methods that you can define

class systemrdl.udp.UDPDefinition(env: RDLEnvironment)

UDP definition descriptor class.

name: str = ''

User-defined property name

valid_components: Set[Type[Component]] = {<class 'systemrdl.component.Addrmap'>, <class 'systemrdl.component.Field'>, <class 'systemrdl.component.Mem'>, <class 'systemrdl.component.Reg'>, <class 'systemrdl.component.Regfile'>, <class 'systemrdl.component.Signal'>}

Set of Component types the UDP can be bound to. By default, the UDP can be bound to all components.

valid_type: Any = None

Data type of the assignment value that this UDP will enforce. If this is a reference, either specify the specific component type class (eg. Field), or the generic representation of all references: RefType

default_assignment: Any = None

Specifies the value assigned if a value is not specified when the UDP is bound to a component. Value must be compatible with valid_type

constr_componentwidth = False

If set to True, enables a validation check that enforces that the assigned value of the property shall not have a value of 1 for any bit beyond the width of the field. This can only be used if valid_type is int

validate(node: Node, value: Any) None

Optional user-defined validation function.

This function is called after design elaboration on every assignment of the user defined property. This provides a mechanism to further validate the value assigend to your user-defined property.

If the user-assigned value fails your validation test, be sure to flag it as an error as follows:

self.msg.error("My error message", self.get_src_ref(node))
get_unassigned_default(node: Node) Any

According to the SystemRDL spec, if a user-defined property is not explicitly assigned, then it does not get bound with any implied default value.

For convenience to developers, this callback allows you to specify an implied default value if the UDP was never explicitly assigned. This only affects the behavior of Node.get_property() and does not change the semantics of how SystemRDL is interpreted during compilaton.

Utilities

class systemrdl.udp.UDPDefinition(env: RDLEnvironment)

UDP definition descriptor class.

get_src_ref(node: Node) SourceRefBase

Get the src_ref object for this property assignment.

This function is useful when emitting error messages from within validate().

property msg: MessageHandler

Reference to the compiler’s message handler. Use this to emit error messages from within validate().