Skip to content

test_size_inheritance_and_adjustment

Contrary to what is defined in the CMSIS SVD standard, in svdconv the inheritance and adjustment of register sizes follow a specific algorithm based on implicit and explicit size definitions at different levels. The calculation of the effective size involves both explicit definitions and inheritance from higher levels, following a recursive approach to determine the maximum size within each level. Here's how the algorithm works:

For each peripheral, if clusters are present, they are resolved from the inside out, starting with the innermost clusters (where children are only registers). Each cluster is examined by iterating through all of its children—either registers or subclusters. The effective size for a register is either explicitly defined or inherited from a higher level, such as from the cluster, peripheral, or ultimately the device level. If no explicit size is defined, it inherits the size from the levels above, taking the first size that is found while traversing upwards. If this traversal reaches the device level and no size is defined there either, the size is set to 32 bits by default.

For a given cluster, its size is determined by finding the maximum size among all of its children, including both registers and subclusters.

This process is repeated recursively, starting with the innermost clusters and moving outward to determine the size of the parent clusters. Finally, the peripheral's size is calculated by taking the maximum size among all of its direct children, including clusters and registers. This approach ensures that the effective size of a peripheral is determined based on the largest size found in its entire hierarchy.

Python code that demonstrates the algorithm (click to expand) ```python from dataclasses import dataclass, field @dataclass(kw_only=True) class Device: size: None | int = None name: str peripherals: list["Peripheral"] = field(default_factory=list) @dataclass(kw_only=True) class Peripheral: size: None | int = None name: str registers_clusters: list["Register | Cluster"] = field(default_factory=list) parent: Device @dataclass(kw_only=True) class Cluster: size: None | int = None name: str registers_clusters: list["Register | Cluster"] = field(default_factory=list) parent: "Cluster | Peripheral" @dataclass(kw_only=True) class Register: size: None | int = None name: str parent: "Cluster | Peripheral" def resolve_size_over_levels(peripheral: Peripheral) -> int: # Start resolving the size for the peripheral by examining all its children inherited_size = resolve_inherited_size(peripheral) effective_size = inherited_size for element in peripheral.registers_clusters: child_size = resolve_size(element, inherited_size) effective_size = max(effective_size, child_size) peripheral.size = effective_size return effective_size def resolve_size(element: Register | Cluster, inherited_size: int) -> int: # Determine effective size for Register or Cluster if isinstance(element, Register): return element.size if element.size is not None else inherited_size if isinstance(element, Cluster): return resolve_cluster_size(element) raise ValueError("Element must be either Register or Cluster") def resolve_cluster_size(cluster: Cluster) -> int: # Start resolving the size for the cluster by examining all its children cluster_inherited_size = resolve_inherited_size(cluster) effective_size = cluster_inherited_size for child in cluster.registers_clusters: child_size = resolve_size(child, cluster_inherited_size) effective_size = max(effective_size, child_size) cluster.size = effective_size return effective_size def resolve_inherited_size(element: Peripheral | Cluster | Register) -> int: # Traverse upwards to find the first defined size, or return 32 if no size is defined current = element.parent while current is not None: if current.size is not None: return current.size current = getattr(current, "parent", None) return 32 # Default to 32 if no size is found in any parent level # Example usage: def print_hierarchy(element: Peripheral | Cluster | Register, path: str = ""): if isinstance(element, Peripheral): path += element.name print(f"{path} (size: {element.size})") for child in element.registers_clusters: print_hierarchy(child, path + ".") elif isinstance(element, Cluster): path += element.name print(f"{path} (size: {element.size})") for child in element.registers_clusters: print_hierarchy(child, path + ".") elif isinstance(element, Register): path += element.name print(f"{path} (size: {element.size})") else: raise ValueError("Element must be either Peripheral, Cluster or Register") device = Device(name="DeviceA") peripheral_a = Peripheral(name="PeripheralA", parent=device) cluster_a = Cluster(name="ClusterA", parent=peripheral_a) register_a = Register(name="RegisterA", parent=cluster_a) register_b = Register(name="RegisterB", parent=cluster_a) cluster_b = Cluster(name="ClusterB", parent=cluster_a) register_a_in_cluster_b = Register(name="RegisterA", parent=cluster_b) register_b_in_cluster_b = Register(name="RegisterB", size=64, parent=cluster_b) cluster_b.registers_clusters.extend([register_a_in_cluster_b, register_b_in_cluster_b]) cluster_a.registers_clusters.extend([register_a, register_b, cluster_b]) cluster_c = Cluster(name="ClusterC", parent=peripheral_a) register_a_in_cluster_c = Register(name="RegisterA", parent=cluster_c) register_b_in_cluster_c = Register(name="RegisterB", parent=cluster_c) cluster_c.registers_clusters.extend([register_a_in_cluster_c, register_b_in_cluster_c]) register_a_in_peripheral = Register(name="RegisterA", parent=peripheral_a) peripheral_a.registers_clusters.extend([cluster_a, cluster_c, register_a_in_peripheral]) device.peripherals.append(peripheral_a) # Resolve sizes and print hierarchy resolve_size_over_levels(peripheral_a) print_hierarchy(peripheral_a) ```

test_complex_size_adjustment

This test examines how the parser handles more intricate scenarios of size inheritance and adjustments across clusters and registers within a peripheral. The SVD file contains clusters and registers with varying degrees of explicit and implicit size definitions. The parser must determine the correct effective sizes based on size inheritance rules and adjustments at different levels. In this case, the sizes within the clusters and registers are adjusted, either explicitly or inherited, following a recursive calculation of the maximum effective size.

Expected Outcome: The parser should process the file without errors, correctly inheriting and adjusting sizes across clusters and registers. ClusterA, which has no explicit size defined, should have its size adjusted to 64 bits, inherited from ClusterB and its child registers. Both RegisterA and RegisterB within ClusterA should also have effective sizes of 64 bits. Similarly, ClusterB should inherit a size of 64 bits from its children, while ClusterC, which also lacks an explicit size, should have a final size of 32 bits, based on its child registers, which inherit the size implicitly from device level (default value). Since the size adjustment for ClusterC is executed before the final size adjustment for PeripheralA, at this point of time, no size is set for PeripheralA. Finally, PeripheralA should adjust its size to 64 bits, reflecting the largest size found in its children. The parser should handle all these size adjustments and inheritance steps accurately, consistent with the behavior of svdconv.

Processable with svdconv: yes

Source code in tests/test_process/test_size_inheritance_and_adjustment.py
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
@pytest.mark.filterwarnings("error::svdsuite.process.ProcessWarning")
def test_complex_size_adjustment(get_processed_device_from_testfile: Callable[[str], Device]):
    """
    This test examines how the parser handles more intricate scenarios of size inheritance and adjustments across
    clusters and registers within a peripheral. The SVD file contains clusters and registers with varying degrees
    of explicit and implicit size definitions. The parser must determine the correct effective sizes based on size
    inheritance rules and adjustments at different levels. In this case, the sizes within the clusters and
    registers are adjusted, either explicitly or inherited, following a recursive calculation of the maximum
    effective size.

    **Expected Outcome:** The parser should process the file without errors, correctly inheriting and adjusting sizes
    across clusters and registers. ClusterA, which has no explicit size defined, should have its size adjusted to
    64 bits, inherited from ClusterB and its child registers. Both `RegisterA` and `RegisterB` within ClusterA
    should also have effective sizes of 64 bits. Similarly, ClusterB should inherit a size of 64 bits from its
    children, while ClusterC, which also lacks an explicit size, should have a final size of 32 bits, based on its
    child registers, which inherit the size implicitly from device level (default value). Since the size
    adjustment for ClusterC is executed before the final size adjustment for PeripheralA, at this point of time,
    no size is set for PeripheralA. Finally, PeripheralA should adjust its size to 64 bits, reflecting the largest
    size found in its children. The parser should handle all these size adjustments and inheritance steps
    accurately, consistent with the behavior of `svdconv`.

    **Processable with svdconv:** yes
    """

    device = get_processed_device_from_testfile("size_inheritance_and_adjustment/complex_size_adjustment.svd")

    assert len(device.peripherals) == 1
    assert len(device.peripherals[0].registers_clusters) == 3

    cluster_a = device.peripherals[0].registers_clusters[0]
    assert isinstance(cluster_a, Cluster)
    assert cluster_a.name == "ClusterA"
    assert cluster_a.address_offset == 0x0
    assert cluster_a.size == 64  # not set, size adjustment results in 64 from ClusterB
    assert len(cluster_a.registers_clusters) == 3

    assert isinstance(cluster_a.registers_clusters[0], Register)
    assert cluster_a.registers_clusters[0].name == "RegisterA"
    assert cluster_a.registers_clusters[0].address_offset == 0x0
    assert cluster_a.registers_clusters[0].size == 64  # not set, effective size results in 64 from ClusterA

    assert isinstance(cluster_a.registers_clusters[1], Register)
    assert cluster_a.registers_clusters[1].name == "RegisterB"
    assert cluster_a.registers_clusters[1].address_offset == 0x8
    assert cluster_a.registers_clusters[1].size == 64  # not set, effective size results in 64 from ClusterA

    cluster_b = cluster_a.registers_clusters[2]
    assert isinstance(cluster_b, Cluster)
    assert cluster_b.name == "ClusterB"
    assert cluster_b.address_offset == 0x10
    assert cluster_b.size == 64  # not set, size adjustment results in 64 from RegisterB
    assert len(cluster_b.registers_clusters) == 2

    assert isinstance(cluster_b.registers_clusters[0], Register)
    assert cluster_b.registers_clusters[0].name == "RegisterA"
    assert cluster_b.registers_clusters[0].address_offset == 0x0
    assert cluster_b.registers_clusters[0].size == 64  # not set, effective size results in 64 from ClusterB

    assert isinstance(cluster_b.registers_clusters[1], Register)
    assert cluster_b.registers_clusters[1].name == "RegisterB"
    assert cluster_b.registers_clusters[1].address_offset == 0x8
    assert cluster_b.registers_clusters[1].size == 64  # explicitly set to 64

    cluster_c = device.peripherals[0].registers_clusters[1]
    assert isinstance(cluster_c, Cluster)
    assert cluster_c.name == "ClusterC"
    assert cluster_c.address_offset == 0x20
    assert cluster_c.size == 32  # not set, size adjustment results implicit in 32 from RegisterA and RegisterB
    assert len(cluster_c.registers_clusters) == 2

    assert isinstance(cluster_c.registers_clusters[0], Register)
    assert cluster_c.registers_clusters[0].name == "RegisterA"
    assert cluster_c.registers_clusters[0].address_offset == 0x0
    assert cluster_c.registers_clusters[0].size == 32  # not set, effective size results in 32 from ClusterC size

    assert isinstance(cluster_c.registers_clusters[1], Register)
    assert cluster_c.registers_clusters[1].name == "RegisterB"
    assert cluster_c.registers_clusters[1].address_offset == 0x8
    assert cluster_c.registers_clusters[1].size == 32  # not set, effective size results in 32 from ClusterC size

    assert isinstance(device.peripherals[0].registers_clusters[2], Register)
    assert device.peripherals[0].registers_clusters[2].name == "RegisterA"
    assert device.peripherals[0].registers_clusters[2].address_offset == 0x30
    assert device.peripherals[0].registers_clusters[2].size == 64  # not set, ef. size results in 64 from peripheral
SVD file: size_inheritance_and_adjustment/complex_size_adjustment.svd
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
<?xml version='1.0' encoding='utf-8'?>
<device xmlns:xs="http://www.w3.org/2001/XMLSchema-instance" xs:noNamespaceSchemaLocation="CMSIS-SVD.xsd" schemaVersion="1.3">
  <name>complex_size_adjustment</name>
  <version>1.0</version>
  <description>Test_Example device</description>
  <cpu>
  <name>CM0</name>
  <revision>r0p0</revision>
  <endian>little</endian>
  <mpuPresent>false</mpuPresent>
  <fpuPresent>false</fpuPresent>
  <nvicPrioBits>4</nvicPrioBits>
  <vendorSystickConfig>false</vendorSystickConfig>
  </cpu>
  <addressUnitBits>8</addressUnitBits>
  <width>32</width>
  <peripherals>
    <peripheral>
      <name>PeripheralA</name>
      <baseAddress>0x40001000</baseAddress>
      <addressBlock>
        <offset>0x0</offset>
        <size>0x1000</size>
        <usage>registers</usage>
      </addressBlock>
      <registers>
        <cluster>
          <name>ClusterA</name>
          <description>ClusterA description</description>
          <addressOffset>0x0</addressOffset>
          <register>
            <name>RegisterA</name>
            <addressOffset>0x0</addressOffset>
          </register>
          <register>
            <name>RegisterB</name>
            <addressOffset>0x8</addressOffset>
          </register>
          <cluster>
            <name>ClusterB</name>
            <description>ClusterB description</description>
            <addressOffset>0x10</addressOffset>
            <register>
              <name>RegisterA</name>
              <addressOffset>0x0</addressOffset>
            </register>
            <register>
              <name>RegisterB</name>
              <addressOffset>0x8</addressOffset>
              <size>64</size>
            </register>
          </cluster>
        </cluster>
        <cluster>
          <name>ClusterC</name>
          <description>ClusterC description</description>
          <addressOffset>0x20</addressOffset>
          <register>
            <name>RegisterA</name>
            <addressOffset>0x0</addressOffset>
          </register>
          <register>
            <name>RegisterB</name>
            <addressOffset>0x8</addressOffset>
          </register>
        </cluster>
        <register>
          <name>RegisterA</name>
          <addressOffset>0x30</addressOffset>
        </register>
      </registers>
    </peripheral>
  </peripherals>
</device>

test_overlap_due_to_size_adjustment

This test verifies how the parser handles size adjustments that lead to register overlaps. The SVD file contains a peripheral where the size inheritance and adjustment algorithm causes an overlap between two registers. Specifically, PeripheralA starts with a defined size of 32 bits, but the effective size is adjusted to 64 bits due to the size of RegisterB. RegisterA, which does not have an explicit size, inherits its size from the peripheral, leading to an overlap with RegisterB, which has an address offset of 0x4.

Expected Outcome: The parser should process the file and issue a warning about the overlap caused by the size adjustment. PeripheralA should have a final size of 64 bits, overriding the original size of 32 bits due to the size of RegisterB. RegisterA, which does not have a size defined, should inherit the size from PeripheralA and have a final size of 64 bits. This leads to an overlap with RegisterB, whose size is explicitly set to 64 bits, starting at address offset 0x4. The parser should handle this case by adjusting the sizes correctly and raising a warning due to the overlap, similar to the behavior in svdconv.

Processable with svdconv: yes

Source code in tests/test_process/test_size_inheritance_and_adjustment.py
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
@pytest.mark.filterwarnings("error::svdsuite.process.ProcessWarning")
def test_overlap_due_to_size_adjustment(get_processed_device_from_testfile: Callable[[str], Device]):
    """
    This test verifies how the parser handles size adjustments that lead to register overlaps. The SVD file
    contains a peripheral where the size inheritance and adjustment algorithm causes an overlap between two
    registers. Specifically, `PeripheralA` starts with a defined size of 32 bits, but the effective size is
    adjusted to 64 bits due to the size of `RegisterB`. `RegisterA`, which does not have an explicit size,
    inherits its size from the peripheral, leading to an overlap with `RegisterB`, which has an address offset of
    0x4.

    **Expected Outcome:** The parser should process the file and issue a warning about the overlap caused by the size
    adjustment. `PeripheralA` should have a final size of 64 bits, overriding the original size of 32 bits due to
    the size of `RegisterB`. `RegisterA`, which does not have a size defined, should inherit the size from
    `PeripheralA` and have a final size of 64 bits. This leads to an overlap with `RegisterB`, whose size is
    explicitly set to 64 bits, starting at address offset 0x4. The parser should handle this case by adjusting the
    sizes correctly and raising a warning due to the overlap, similar to the behavior in `svdconv`.

    **Processable with svdconv:** yes
    """

    with pytest.warns(ProcessWarning):
        device = get_processed_device_from_testfile(
            "size_inheritance_and_adjustment/overlap_due_to_size_adjustment.svd"
        )

    assert len(device.peripherals) == 1
    assert len(device.peripherals[0].registers_clusters) == 2
    assert device.peripherals[0].size == 64  # explicitly set to 32, adjustment overwrite to 64 from RegisterB

    assert isinstance(device.peripherals[0].registers_clusters[0], Register)
    assert device.peripherals[0].registers_clusters[0].name == "RegisterA"
    assert device.peripherals[0].registers_clusters[0].address_offset == 0x0
    assert device.peripherals[0].registers_clusters[0].size == 64  # not set, ef. size results in 64 (PeripheralA)

    assert isinstance(device.peripherals[0].registers_clusters[1], Register)
    assert device.peripherals[0].registers_clusters[1].name == "RegisterB"
    assert device.peripherals[0].registers_clusters[1].address_offset == 0x4
    assert device.peripherals[0].registers_clusters[1].size == 64  # explicitly set to 64
SVD file: size_inheritance_and_adjustment/overlap_due_to_size_adjustment.svd
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<?xml version='1.0' encoding='utf-8'?>
<device xmlns:xs="http://www.w3.org/2001/XMLSchema-instance" xs:noNamespaceSchemaLocation="CMSIS-SVD.xsd" schemaVersion="1.3">
  <name>overlap_due_to_size_adjustment</name>
  <version>1.0</version>
  <description>Test_Example device</description>
  <cpu>
    <name>CM0</name>
    <revision>r0p0</revision>
    <endian>little</endian>
    <mpuPresent>false</mpuPresent>
    <fpuPresent>false</fpuPresent>
    <nvicPrioBits>4</nvicPrioBits>
    <vendorSystickConfig>false</vendorSystickConfig>
  </cpu>
  <addressUnitBits>8</addressUnitBits>
  <width>32</width>
  <peripherals>
    <peripheral>
      <name>PeripheralA</name>
      <baseAddress>0x40001000</baseAddress>
      <size>32</size>
      <addressBlock>
        <offset>0x0</offset>
        <size>0x1000</size>
        <usage>registers</usage>
      </addressBlock>
      <registers>
        <register>
          <name>RegisterA</name>
          <addressOffset>0x0</addressOffset>
        </register>
        <register>
          <name>RegisterB</name>
          <addressOffset>0x4</addressOffset>
          <size>64</size>
        </register>
      </registers>
    </peripheral>
  </peripherals>
</device>

test_simple_size_adjustment

This test evaluates how the parser handles simple size inheritance and explicit size adjustments within a peripheral. In this scenario, the peripheral has both implicit and explicit size definitions, and the parser must adjust these sizes based on the rules described. Specifically, RegisterA has no explicit size set, so it should inherit the size from the peripheral level, while RegisterB has an explicit size defined. svdconv processes this file correctly, applying size adjustments based on the highest level's explicit settings.

Expected Outcome: The parser should correctly adjust the sizes, processing the SVD file without errors. The peripheral should have two registers, RegisterA and RegisterB. The overall size of the peripheral should be set to 64 bits, which overrides the explicit 16-bit setting due to size adjustments. RegisterA, with no explicit size set, should inherit the peripheral's size and be 64 bits. RegisterB, which explicitly has a size of 64 bits, should retain this size. The parser should process the inheritance and size adjustment algorithm correctly, reflecting the behavior of svdconv.

Processable with svdconv: yes

Source code in tests/test_process/test_size_inheritance_and_adjustment.py
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
@pytest.mark.filterwarnings("error::svdsuite.process.ProcessWarning")
def test_simple_size_adjustment(get_processed_device_from_testfile: Callable[[str], Device]):
    """
    This test evaluates how the parser handles simple size inheritance and explicit size adjustments within a
    peripheral. In this scenario, the peripheral has both implicit and explicit size definitions, and the parser
    must adjust these sizes based on the rules described. Specifically, `RegisterA` has no explicit size set, so
    it should inherit the size from the peripheral level, while `RegisterB` has an explicit size defined.
    `svdconv` processes this file correctly, applying size adjustments based on the highest level's explicit
    settings.

    **Expected Outcome:** The parser should correctly adjust the sizes, processing the SVD file without errors. The
    peripheral should have two registers, `RegisterA` and `RegisterB`. The overall size of the peripheral should
    be set to 64 bits, which overrides the explicit 16-bit setting due to size adjustments. `RegisterA`, with no
    explicit size set, should inherit the peripheral's size and be 64 bits. `RegisterB`, which explicitly has a
    size of 64 bits, should retain this size. The parser should process the inheritance and size adjustment
    algorithm correctly, reflecting the behavior of `svdconv`.

    **Processable with svdconv:** yes
    """

    device = get_processed_device_from_testfile("size_inheritance_and_adjustment/simple_size_adjustment.svd")

    assert len(device.peripherals) == 1
    assert len(device.peripherals[0].registers_clusters) == 2
    assert device.peripherals[0].size == 64  # explicitly set to 16, adjustment overwrite to 64

    assert isinstance(device.peripherals[0].registers_clusters[0], Register)
    assert device.peripherals[0].registers_clusters[0].name == "RegisterA"
    assert device.peripherals[0].registers_clusters[0].address_offset == 0x0
    assert device.peripherals[0].registers_clusters[0].size == 64  # not set, effective size results in 64

    assert isinstance(device.peripherals[0].registers_clusters[1], Register)
    assert device.peripherals[0].registers_clusters[1].name == "RegisterB"
    assert device.peripherals[0].registers_clusters[1].address_offset == 0x8
    assert device.peripherals[0].registers_clusters[1].size == 64  # explicitly set to 64
SVD file: size_inheritance_and_adjustment/simple_size_adjustment.svd
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<?xml version='1.0' encoding='utf-8'?>
<device xmlns:xs="http://www.w3.org/2001/XMLSchema-instance" xs:noNamespaceSchemaLocation="CMSIS-SVD.xsd" schemaVersion="1.3">
  <name>simple_size_adjustment</name>
  <version>1.0</version>
  <description>Test_Example device</description>
  <cpu>
  <name>CM0</name>
  <revision>r0p0</revision>
  <endian>little</endian>
  <mpuPresent>false</mpuPresent>
  <fpuPresent>false</fpuPresent>
  <nvicPrioBits>4</nvicPrioBits>
  <vendorSystickConfig>false</vendorSystickConfig>
  </cpu>
  <addressUnitBits>8</addressUnitBits>
  <width>32</width>
  <peripherals>
  <peripheral>
    <name>PeripheralA</name>
    <baseAddress>0x40001000</baseAddress>
    <size>16</size>
    <addressBlock>
    <offset>0x0</offset>
    <size>0x1000</size>
    <usage>registers</usage>
    </addressBlock>
    <registers>
    <register>
      <name>RegisterA</name>
      <addressOffset>0x0</addressOffset>
    </register>
    <register>
      <name>RegisterB</name>
      <addressOffset>0x8</addressOffset>
      <size>64</size>
    </register>
    </registers>
  </peripheral>
  </peripherals>
</device>