Skip to content

Commit 3b341c7

Browse files
committed
Add better UDP docs. Refine implementation slightly
1 parent 381640a commit 3b341c7

File tree

5 files changed

+131
-63
lines changed

5 files changed

+131
-63
lines changed

docs/api/udp.rst

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
.. _api_udp:
2+
3+
User-Defined Properties
4+
=======================
5+
6+
The SystemRDL standard allows users to extend components with custom properties.
7+
These UDPs are declared within the user's SystemRDL source code prior to use.
8+
Inevitably, these UDPs are processed by a downstream tool that has some
9+
well-defined semantics for these language extensions.
10+
This compiler provides an API that allows tool develoers to define additional
11+
validation for these properties.
12+
13+
14+
Pre-registering a UDP
15+
---------------------
16+
UDP semantics can be pre-loaded into the compiler namespace as a way to formalize
17+
extensions to SystemRDL that your tool supports.
18+
19+
Registration of a UDP is done as follows:
20+
21+
.. code-block:: python
22+
23+
from systemrdl.udp import UDPDefinition
24+
from systemrdl.components import Field, Signal
25+
26+
# 1. Describe your UDP
27+
class MyUDPDefinition(UDPDefinition):
28+
name = "my_udp"
29+
valid_components = {Field, Signal}
30+
valid_type = int
31+
32+
# Optional callback that validates values assigned to 'my_udp'
33+
def validate(self, node, value):
34+
if(value == 42):
35+
self.msg.error(
36+
"The value assigned to 'my_udp' cannot be 42! That number is reserved.",
37+
self.get_src_ref(node)
38+
)
39+
40+
# 2. Register it with your RDLCompiler instance
41+
rdlc.register_udp(MyUDPDefinition)
42+
43+
The above definition is equivalent to the following SystemRDL:
44+
45+
.. code-block:: systemrdl
46+
47+
property my_udp {
48+
type = longint unsigned;
49+
component = field | signal;
50+
};
51+
52+
53+
Soft UDPs
54+
---------
55+
By default, :meth:`~systemrdl.RDLCompiler.register_udp()` registers
56+
UDPs as "soft" definitions. Soft UDP definitions behave as follows:
57+
58+
* The UDP is not available to be used until it is explicitly defined in the
59+
SystemRDL source. If a user attempts to use a soft UDP prior to it being
60+
declared, the compiler will flag an error.
61+
* Upon definition, the user's declaration shall be equivalent to the
62+
pre-registered definition. If the user's declaration does not match, the
63+
compiler will flag an error.
64+
This ensures that the user's declaration matches the expectations of your tool.
65+
* If the user's RDL source never defines the UDP, querying it via
66+
``node.get_property()`` will gracefully return ``None`` instead of a
67+
``LookupError`` exception. This simplifies how tool developers interact with
68+
users' RDL code.
69+
* Once defined by the user in RDL source, the UDP is no longer considered
70+
'soft', and can be assigned normally.
71+
72+
73+
.. important::
74+
Although it may initially seem like more steps for the end-user, having them
75+
declare the UDP in the RDL source is preferred over pre-declaring "hard"
76+
UDPs within your tool.
77+
78+
Silently declaring hard UDPs not recommended since it encourages users to write
79+
SystemRDL that uses UDP extensions that are not formally declared in the
80+
RDL source. This bends the rules of the SystemRDL specification and hurts
81+
the cross-vendor compatibility of your users' SystemRDL source code.
82+
83+
Using soft UDPs has the benefit of enforcing that the user
84+
defines and uses UDPs correctly whilst not violating the official
85+
SystemRDL language specification.
86+
87+
88+
The UDPDefinition descriptor
89+
----------------------------
90+
91+
The full details of the ``UDPDefinition`` class is as follows:
92+
93+
Class variables and methods that you can define
94+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
95+
.. autoclass:: systemrdl.udp.UDPDefinition
96+
:members: name, valid_components, valid_type, default_assignment, constr_componentwidth, validate, get_unassigned_default
97+
:member-order: bysource
98+
99+
Utilities
100+
^^^^^^^^^
101+
102+
.. autoclass:: systemrdl.udp.UDPDefinition
103+
:noindex:
104+
:members: msg, get_src_ref

docs/index.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,10 @@ Links
6969

7070
.. toctree::
7171
:hidden:
72-
:caption: Class Reference
72+
:caption: API Reference
7373

7474
api/compiler
75+
api/udp
7576
api/node
7677
api/walker
7778
api/component

systemrdl/__about__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "1.25.0-b2"
1+
__version__ = "1.25.0"

systemrdl/compiler.py

Lines changed: 0 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -141,70 +141,13 @@ def register_udp(self, definition_cls: 'Type[UDPDefinition]', soft: bool=True) -
141141
"""
142142
Pre-register a User Defined Property into the compiler.
143143
144-
UDP semantics can be pre-loaded into the compiler namespace as a way to formalize
145-
extensions to SystemRDL that your tool supports. Registration of a UDP is done as follows:
146-
147-
.. code-block:: python
148-
149-
from systemrdl.udp import UDPDefinition
150-
from systemrdl.components import Field, Signal
151-
152-
# 1. Describe your UDP
153-
class MyUDPDefinition(UDPDefinition):
154-
name = "my_udp"
155-
valid_components = {Field, Signal}
156-
valid_type = int
157-
158-
# Optional callback that validates values assigned to 'my_udp'
159-
def validate(self, node, value):
160-
if(value == 42):
161-
self.msg.error(
162-
"Value of 'my_udp' cannot be 42! That number is reserved for philosophical reasons",
163-
self.get_src_ref(node)
164-
)
165-
166-
# 2. Register it with your RDLCompiler instance
167-
rdlc.register_udp(MyUDPDefinition)
168-
169-
The above definition is equivalent to the following SystemRDL:
170-
171-
.. code-block:: systemrdl
172-
173-
property my_udp {
174-
type = longint unsigned;
175-
component = field | signal;
176-
};
177-
178-
By default, UDPs are registered as "soft" definitions. Soft UDPs behave as follows:
179-
180-
* The UDP is not available to be used until it is explicitly defined in the SystemRDL source.
181-
* Upon definition, the user's declaration shall be equivalent to the pre-loaded definition.
182-
* If the user's RDL source never defines the UDP, querying it via ``node.get_property()``
183-
will gracefully return ``None`` instead of a ``LookupError`` exception.
184-
* Once defined by the user in RDL source, the UDP is no longer soft.
185-
186-
The full details of the ``UDPDefinition`` class is as follows:
187-
188-
.. autoclass:: systemrdl.udp.UDPDefinition
189-
:members:
190-
191144
Parameters
192145
----------
193146
definition_cls: :class:`systemrdl.udp.UDPDefinition`
194147
Reference to the container class that defines your new UDP.
195148
soft: bool
196149
Override to False to register the UDP as a hard definition.
197150
198-
.. important::
199-
Registering hard UDPs not recommended since it encourages users to write
200-
SystemRDL that uses UDP extensions that are not formally
201-
declared in the RDL source. This bends the rules of the SystemRDL
202-
specification and hurts the cross-vendor compatibility of your SystemRDL.
203-
204-
Using soft UDPs has the benefit of enforcing that the user
205-
defines and uses UDPs correctly whilst not violating the official
206-
SystemRDL language specification.
207-
208151
209152
.. versionadded:: 1.25
210153
"""

systemrdl/udp.py

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,15 @@
77
from .compiler import RDLEnvironment
88
from .node import Node
99
from .messages import MessageHandler
10+
from .source_ref import SourceRefBase
1011

1112
class UDPDefinition:
1213
"""
1314
UDP definition descriptor class.
1415
"""
1516

16-
#: UDP definition name
17-
name = ""
17+
#: User-defined property name
18+
name = "" # type: str
1819

1920
#: Set of :class:`~systemrdl.component.Component` types the UDP can be bound to.
2021
#: By default, the UDP can be bound to all components.
@@ -59,15 +60,34 @@ def __init__(self, env: 'RDLEnvironment') -> None:
5960

6061
@property
6162
def msg(self) -> 'MessageHandler':
63+
"""
64+
Reference to the compiler's message handler.
65+
Use this to emit error messages from within :meth:`validate()`.
66+
"""
6267
return self.env.msg
6368

69+
def get_src_ref(self, node: 'Node') -> 'SourceRefBase':
70+
"""
71+
Get the src_ref object for this property assignment.
72+
73+
This function is useful when emitting error messages from within :meth:`validate()`.
74+
"""
75+
return node.inst.property_src_ref.get(self.name, node.inst.inst_src_ref)
76+
6477
def validate(self, node: 'Node', value: Any) -> None:
6578
"""
6679
Optional user-defined validation function.
6780
6881
This function is called after design elaboration on every assignment
6982
of the user defined property. This provides a mechanism to further
7083
validate the value assigend to your user-defined property.
84+
85+
If the user-assigned value fails your validation test, be sure to flag
86+
it as an error as follows:
87+
88+
.. code-block:: python
89+
90+
self.msg.error("My error message", self.get_src_ref(node))
7191
"""
7292

7393
def get_unassigned_default(self, node: 'Node') -> Any:
@@ -78,8 +98,8 @@ def get_unassigned_default(self, node: 'Node') -> Any:
7898
7999
For convenience to developers, this callback allows you to specify an implied
80100
default value if the UDP was never explicitly assigned.
81-
This only affects the behavior of Node.get_property() and does not
82-
affect the semantics of SystemRDL during compilaton.
101+
This only affects the behavior of :meth:`Node.get_property()` and does not
102+
change the semantics of how SystemRDL is interpreted during compilaton.
83103
"""
84104

85105
# If a user-defined property is not explicitly assigned, then it

0 commit comments

Comments
 (0)