Metadata-Version: 2.4
Name: svdsuite
Version: 0.2.0
Summary: A Python package to parse, process, manipulate, validate, and generate CMSIS SVD files
Author: Christian Kudera
License: MIT License
        
        Copyright (c) 2024 ARMify
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
Project-URL: Documentation, https://github.com/ARMify-Project/SVDSuite?tab=readme-ov-file
Project-URL: Issues, https://github.com/ARMify-Project/SVDSuite/issues
Project-URL: Source, https://github.com/ARMify-Project/SVDSuite
Keywords: CMSIS,SVD,Embedded Systems,ARM,Cortex-M,Microcontroller
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Science/Research
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Programming Language :: Python :: Implementation :: PyPy
Classifier: Topic :: Software Development :: Embedded Systems
Classifier: Topic :: Scientific/Engineering
Classifier: Typing :: Typed
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: lxml>=4.9.3
Requires-Dist: rustworkx>=0.15.1
Provides-Extra: dev
Requires-Dist: pytest>=8.1.1; extra == "dev"
Dynamic: license-file

# SVDSuite

**SVDSuite** is a Python package to parse, process, manipulate, validate, and generate [CMSIS SVD](https://open-cmsis-pack.github.io/svd-spec/main/index.html) files. Currently, the suite supports CMSIS-SVD standard 1.3.10-dev16, whereas the validation additionally supports all previously released standards.

> The CMSIS System View Description format (CMSIS-SVD) formalizes the description of the system contained in Arm Cortex-M processor-based microcontrollers, in particular, the memory-mapped registers of peripherals. The detail contained in system view descriptions is comparable to the data in device reference manuals. The information ranges from high-level functional descriptions of a peripheral all the way down to the definition and purpose of an individual bit field in a memory-mapped register.
>
> CMSIS-SVD files are developed and maintained by silicon vendors. Silicon vendors distribute their descriptions as part of CMSIS Device Family Packs. Tool vendors use CMSIS-SVD files for providing device-specific debug views of peripherals in their debugger. Last but not least, CMSIS-compliant device header files are generated from CMSIS-SVD files. [^1]

### Compatibility with SVDConv

SVDSuite was designed with a strong focus on compatibility with *SVDConv*, the official CMSIS tool for validating and processing SVD files. To ensure full compatibility, all generated and processed SVD files were tested against SVDConv ([checkout the comparison repository](https://github.com/ARMify-Project/svdsuite-svdconv-compare)). Parsing and transformation behavior is verified to match SVDConv’s output, ensuring reliable integration with existing CMSIS workflows and tooling.

### Extended Functionality

In addition to faithfully supporting the CMSIS-SVD specification, SVDSuite offers several enhancements beyond the standard:

- **Forward references**: SVDSuite allows references to elements that are defined later in the same file, providing more flexibility in SVD authoring and structuring.
- **Full resolution of `enumeratedValues`**: Wildcards, default values, and all inheritance scenarios within `enumeratedValues` are fully resolved, enabling consistent and complete interpretation of value definitions.

[^1]: https://open-cmsis-pack.github.io/svd-spec/main/index.html


> [!NOTE]  
> The long-term goal is to merge **SVDSuite** into the [cmsis-svd](https://github.com/cmsis-svd/cmsis-svd) repository and release it as version 1.0. The author of SVDSuite is also an active maintainer of the [CMSIS-SVD](https://github.com/cmsis-svd) project.

## Installation

Install **SVDSuite** with `pip`:

```bash
  pip install svdsuite
```
    
## Usage/Examples

### Process

To process a CMSIS-SVD file, which includes parsing, you can utilize the `Process` class.

```python
from svdsuite import Process
from svdsuite.model import Cluster, Register

svd_str = """\
<?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>STM32F0</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>TimerExt</name>
      <baseAddress>0x40000000</baseAddress>
      <addressBlock>
        <offset>0x0</offset>
        <size>0x1000</size>
        <usage>registers</usage>
      </addressBlock>
      <registers>
        <register derivedFrom="Timer0.CR1">
          <name>CR1</name>
          <addressOffset>0x00</addressOffset>
        </register>
      </registers>
    </peripheral>
    <peripheral>
      <dim>2</dim>
      <dimIncrement>0x1000</dimIncrement>
      <name>Timer[%s]</name>
      <baseAddress>0x40001000</baseAddress>
      <addressBlock>
        <offset>0x0</offset>
        <size>0x1000</size>
        <usage>registers</usage>
      </addressBlock>
      <registers>
        <cluster>
          <dim>4</dim>
          <dimIncrement>0x20</dimIncrement>
          <name>CC[%s]</name>
          <description>Capture/Compare Channel</description>
          <addressOffset>0x0</addressOffset>
          <register>
            <name>CCR</name>
            <description>Capture/Compare Register</description>
            <addressOffset>0x00</addressOffset>
          </register>
          <register>
            <name>CCMR</name>
            <description>Capture/Compare Mode Register</description>
            <addressOffset>0x04</addressOffset>
          </register>
          <register>
            <name>CCER</name>
            <description>Capture/Compare Enable Register</description>
            <addressOffset>0x08</addressOffset>
          </register>
        </cluster>
        <register>
          <name>CR1</name>
          <description>Control Register 1</description>
          <addressOffset>0x80</addressOffset>
          <size>32</size>
          <access>read-write</access>
          <fields>
            <field>
              <name>CEN</name>
              <description>Counter Enable</description>
              <bitOffset>0</bitOffset>
              <bitWidth>1</bitWidth>
              <enumeratedValues>
                <name>CEN_Values</name>
                <enumeratedValue>
                  <name>Disabled</name>
                  <description>Counter disabled</description>
                  <value>0</value>
                </enumeratedValue>
                <enumeratedValue>
                  <name>Enabled</name>
                  <description>Counter enabled</description>
                  <value>1</value>
                </enumeratedValue>
              </enumeratedValues>
            </field>
            <field>
              <name>CMS</name>
              <description>Center-aligned Mode Selection</description>
              <bitOffset>5</bitOffset>
              <bitWidth>2</bitWidth>
              <enumeratedValues>
                <name>CMS_Values</name>
                <enumeratedValue>
                  <name>EdgeAligned</name>
                  <description>Edge-aligned mode</description>
                  <value>0b00</value>
                </enumeratedValue>
                <enumeratedValue>
                  <name>CenterAligned1</name>
                  <description>Center-aligned mode 1</description>
                  <value>0b01</value>
                </enumeratedValue>
                <enumeratedValue>
                  <name>CenterAligned2</name>
                  <description>Center-aligned mode 2</description>
                  <value>0b10</value>
                </enumeratedValue>
                <enumeratedValue>
                  <name>CenterAligned3</name>
                  <description>Center-aligned mode 3</description>
                  <value>0b11</value>
                </enumeratedValue>
              </enumeratedValues>
            </field>
            <field>
              <name>Status</name>
              <description>Status Field with Don't Care Bits</description>
              <bitOffset>8</bitOffset>
              <bitWidth>8</bitWidth>
              <access>read-only</access>
              <enumeratedValues>
                <name>Status_Values</name>
                <enumeratedValue>
                  <name>OK</name>
                  <description>No error</description>
                  <value>0b0xxxxxxx</value>
                </enumeratedValue>
                <enumeratedValue>
                  <name>Error</name>
                  <description>Error condition</description>
                  <value>0b1xxxxxxx</value>
                </enumeratedValue>
              </enumeratedValues>
            </field>
          </fields>
        </register>
      </registers>
    </peripheral>
  </peripherals>
</device>
    """

# Process the SVD string. Alternatively, you can use the `from_svd_file` method to parse a SVD file
# or the `from_xml_content` method to parse svd byte content.
# Additionally, you can specify a resolver_logging_file_path to debug the resolver step by step.
process = Process.from_xml_str(svd_str)
# parser = Parser.from_svd_file("path/to/svd_file.svd")
# parser = Parser.from_xml_content(svd_str.encode())

# Get the SVDDevice object
device = process.get_processed_device()

print("Print the peripheral names and the register count for each peripheral:")
for peripheral in device.peripherals:
    print(f"Peripheral: {peripheral.name}, Register Count: {len(peripheral.registers)}")

print("\n\nPrint the register names and their base address for each peripheral:")
for peripheral in device.peripherals:
    print(f"Peripheral: {peripheral.name}")
    for register in peripheral.registers:
        print(f"  Register: {register.name}, Base Address: 0x{register.base_address:X}")

print("\n\nPrint the field names and their bit offsets for each register:")
for peripheral in device.peripherals:
    print(f"Peripheral: {peripheral.name}")
    for register in peripheral.registers:
        print(f"  Register: {register.name}")
        for field in register.fields:
            print(f"    Field: {field.name}, Bit Offset: {field.bit_offset}")

print("\n\nPrint the enumerated values for each field:")
for peripheral in device.peripherals:
    print(f"Peripheral: {peripheral.name}")
    for register in peripheral.registers:
        print(f"  Register: {register.name}")
        for field in register.fields:
            print(f"    Field: {field.name}")
            for enum_value_cont in field.enumerated_value_containers:
                for enum_value in enum_value_cont.enumerated_values:
                    print(f"      Enumerated Value: {enum_value.name}, Value: {enum_value.value}")

print("\n\nPrint peripherals with registers but include clusters:")
for peripheral in device.peripherals:
    print(f"Peripheral: {peripheral.name}")
    # We ignore that clusters could be nested here for simplicity
    # In a real-world scenario, you would need to handle nested clusters appropriately
    for reg_cluster in peripheral.registers_clusters:
        if isinstance(reg_cluster, Cluster):
            print(f"  Cluster: {reg_cluster.name}")
            for register in reg_cluster.registers_clusters:
                print(f"    Register: {register.name}, Base Address: 0x{register.base_address:X}")
        elif isinstance(reg_cluster, Register):
            print(f"  Register: {reg_cluster.name}, Base Address: 0x{reg_cluster.base_address:X}")
```

Have a look into `svdsuite/model/process.py` for all the models (dataclasses).

### Parse

To parse a CMSIS-SVD file you can utilize the `Parser` class.

```python
import pprint
from svdsuite import Parser

svd_str = """\
    <device xmlns:xs="http://www.w3.org/2001/XMLSchema-instance"
        xs:noNamespaceSchemaLocation="CMSIS-SVD.xsd" schemaVersion="1.3">
        <name>STM32F0</name>
        <version>1.0</version>
        <description>STM32F0 device</description>
        <cpu>
            <name>CM52</name>
            <revision>r0p0</revision>
            <endian>little</endian>
            <mpuPresent>false</mpuPresent>
            <fpuPresent>false</fpuPresent>
            <fpuDP>false</fpuDP>
            <dspPresent>false</dspPresent>
            <icachePresent>false</icachePresent>
            <dcachePresent>false</dcachePresent>
            <itcmPresent>false</itcmPresent>
            <dtcmPresent>false</dtcmPresent>
            <vtorPresent>false</vtorPresent>
            <nvicPrioBits>2</nvicPrioBits>
            <vendorSystickConfig>false</vendorSystickConfig>
            <deviceNumInterrupts>6</deviceNumInterrupts>
            <sauNumRegions>2</sauNumRegions>
            <sauRegionsConfig enabled="true" protectionWhenDisabled="s">
                <region enabled="true" name="Region1">
                    <base>0x1000</base>
                    <limit>0x2000</limit>
                    <access>n</access>
                </region>
            </sauRegionsConfig>
        </cpu>
        <addressUnitBits>8</addressUnitBits>
        <width>32</width>
        <peripherals>
            <peripheral derivedFrom="test">
                <name>Timer1</name>
                <version>1.0</version>
                <description>Timer 1 is a standard timer</description>
                <alternatePeripheral>Timer1_Alt</alternatePeripheral>
                <groupName>group_name</groupName>
                <prependToName>prepend</prependToName>
                <appendToName>append</appendToName>
                <headerStructName>headerstruct</headerStructName>
                <disableCondition>discond</disableCondition>
                <baseAddress>0x40002000</baseAddress>
            </peripheral>
        </peripherals>
    </device>
    """

# Parse the SVD string. Alternatively, you can use the `from_svd_file` method to parse a SVD file
# or the `from_xml_content` method to parse svd byte content.
parser = Parser.from_xml_str(svd_str)
# parser = Parser.from_svd_file("path/to/svd_file.svd")
# parser = Parser.from_xml_content(svd_str.encode())

# Get the SVDDevice object
device = parser.get_parsed_device()

# Print the device object
pprint.pprint(device)
```

Output:
```python
SVDDevice(
  size=None,
  access=None,
  protection=None,
  reset_value=None,
  reset_mask=None,
  xs_no_namespace_schema_location='CMSIS-SVD.xsd',
  schema_version='1.3',
  vendor=None,
  vendor_id=None,
  name='STM32F0',
  series=None,
  version='1.0',
  description='STM32F0 device',
  license_text=None,
  cpu=SVDCPU(
    name=<CPUNameType.CM52: 'CM52'>,
    revision='r0p0',
    endian=<EndianType.LITTLE: 'little'>,
    mpu_present=False,
    fpu_present=False,
    fpu_dp=False,
    dsp_present=False,
    icache_present=False,
    dcache_present=False,
    itcm_present=False,
    dtcm_present=False,
    vtor_present=False,
    nvic_prio_bits=2,
    vendor_systick_config=False,
    device_num_interrupts=6,
    sau_num_regions=2,
    sau_regions_config=SVDSauRegionsConfig(
      enabled=True,
      protection_when_disabled=<ProtectionStringType.SECURE: 's'>,
      regions=[
        SVDSauRegion(
          enabled=True,
          name='Region1',
          base=4096,
          limit=8192,
          access=<SauAccessType.NON_SECURE: 'n'>
        )
      ]
    )
  ),
  header_system_filename=None,
  header_definitions_prefix=None,
  address_unit_bits=8,
  width=32,
  peripherals=[
    SVDPeripheral(
      size=None,
      access=None,
      protection=None,
      reset_value=None,
      reset_mask=None,
      dim=None,
      dim_increment=None,
      dim_index=None,
      dim_name=None,
      dim_array_index=None,
      name='Timer1',
      version='1.0',
      description='Timer 1 is a standard timer',
      alternate_peripheral='Timer1_Alt',
      group_name='group_name',
      prepend_to_name='prepend',
      append_to_name='append',
      header_struct_name='headerstruct',
      disable_condition='discond',
      base_address=1073750016,
      address_blocks=[],
      interrupts=[],
      registers_clusters=[],
      derived_from='test'
    )
  ]
)
```

Have a look into `svdsuite/model/parse.py` for all the models (dataclasses).

### Create/Manipulate

To create or manipulate a CMSIS-SVD file you can utilize the `Serializer` class.

```python
from svdsuite import Serializer
from svdsuite.model import (
    SVDDevice,
    SVDCPU,
    CPUNameType,
    EndianType,
    SVDSauRegionsConfig,
    ProtectionStringType,
    SVDSauRegion,
    SauAccessType,
    SVDPeripheral,
)

# Create an example device. Alternatevily, you can parse a CMSIS-SVD file and manipulate it.
device = SVDDevice(
    xs_no_namespace_schema_location="CMSIS-SVD.xsd",
    schema_version="1.3",
    name="STM32F0",
    version="1.0",
    description="STM32F0 device",
    cpu=SVDCPU(
        name=CPUNameType.CM52,
        revision="r0p0",
        endian=EndianType.LITTLE,
        nvic_prio_bits=2,
        vendor_systick_config=False,
        sau_regions_config=SVDSauRegionsConfig(
            enabled=True,
            protection_when_disabled=ProtectionStringType.SECURE,
            regions=[
                SVDSauRegion(
                    enabled=True,
                    name="Region1",
                    base=4096,
                    limit=8192,
                    access=SauAccessType.NON_SECURE,
                )
            ],
        ),
    ),
    address_unit_bits=8,
    width=32,
    peripherals=[
        SVDPeripheral(
            name="Timer1",
            description="Timer 1 is a standard timer",
            base_address=1073750016,
        )
    ],
)

# Serialize the device object. Alternatively, you can use the `device_to_svd_file` method to serialize the device
# object to an SVD file, or the `device_to_svd_content` method to serialize the device object to bytes string.
svd_str = Serializer.device_to_svd_str(device, pretty_print=True)
# SVDSerializer.device_to_svd_file("path/to/svd_file.svd", device)
# svd_str = SVDSerializer.device_to_svd_content(device, pretty_print=True).decode()

# Print the serialized device object
print(svd_str)
```

Output:
```xml
<device xmlns:xs="http://www.w3.org/2001/XMLSchema-instance" xs:noNamespaceSchemaLocation="CMSIS-SVD.xsd" schemaVersion="1.3">
  <name>STM32F0</name>
  <version>1.0</version>
  <description>STM32F0 device</description>
  <cpu>
    <name>CM52</name>
    <revision>r0p0</revision>
    <endian>little</endian>
    <nvicPrioBits>2</nvicPrioBits>
    <vendorSystickConfig>false</vendorSystickConfig>
    <sauRegionsConfig enabled="true" protectionWhenDisabled="s">
      <region enabled="true" name="Region1">
        <base>0x1000</base>
        <limit>0x2000</limit>
        <access>n</access>
      </region>
    </sauRegionsConfig>
  </cpu>
  <addressUnitBits>8</addressUnitBits>
  <width>32</width>
  <peripherals>
    <peripheral>
      <name>Timer1</name>
      <description>Timer 1 is a standard timer</description>
      <baseAddress>0x40002000</baseAddress>
    </peripheral>
  </peripherals>
</device>
```

### Validate

To validate a CMSIS-SVD file you can utilize the `Validator` class.

```python
from svdsuite import Validator

xml_str = """\
<device xmlns:xs="http://www.w3.org/2001/XMLSchema-instance"
    xs:noNamespaceSchemaLocation="CMSIS-SVD.xsd" schemaVersion="1.3">
    <name>STM32F0</name>
    <version>1.0</version>
    <description>STM32F0 device</description>
    <addressUnitBits>8</addressUnitBits>
    <width>32</width>
    <peripherals>
        <peripheral derivedFrom="test">
            <name>Timer1</name>
            <baseAddress>0x40002000</baseAddress>
        </peripheral>
    </peripherals>
</device>
"""

# Validate the XML string. If the XML is invalid, an exception is raised, since get_exception=True.
# If get_exception=False, the function returns False on invalid XML without an exception.
# Alternatively, you can use the `validate_xml_file` or `validate_xml_content` methods.
if Validator.validate_xml_str(xml_str, get_exception=True):
    print("SVD is valid")
```

Output:
```
SVD is valid
```


## Running Tests

To run tests, run the following command:

```
  git clone https://github.com/ARMify-Project/SVDSuite.git svdsuite
  cd svdsuite/
  pytest tests/
```


## Acknowledgement

This project was made possible with funding from [NGI Zero Entrust Fund](https://nlnet.nl/thema/NGI0Entrust.html). NGI Zero Entrust Fund is part of the European Commission's [Next Generation Internet](https://www.ngi.eu/) initiative.

Project webpage: https://nlnet.nl/project/ARMify/
