Skip to content

Instantly share code, notes, and snippets.

@brabect1
Last active January 1, 2024 12:29
Show Gist options
  • Save brabect1/4b1f45db2aae8f5f0d635ec22b5060ca to your computer and use it in GitHub Desktop.
Save brabect1/4b1f45db2aae8f5f0d635ec22b5060ca to your computer and use it in GitHub Desktop.

IP-XACT

Resources

Validation

xmllint -noout -schema schema/1685-2014/index.xsd <xml_file>

Components

  • model

    • (opt.) instantiations

      • componentInstantiation (s): Collects information needed to instantiate a component model.

        • name: Instantiation ID of xs:NMTOKEN type (i.e. word chars with :, - and _).

        • (opt.) language: Specifies the HDL used for a specific instantiation [IEEE Std. 1685-2014]. However, there seems to be no restriction on the actual value and hence may be used to indicate whatever "language" is relevant to the fileSetRef(s) in the enclosing instantiation.

        • (opt.) moduleName: Can be used identify (top) module represented by the instantiation/model.

        • (opt.) moduleParameters: Enumerates module parameters. Can be used if the instantiation/model represents a parameterized HDL model.

          .. todo:: How does it relate or differ from other parameter elements? There is no clear
             difference betweem ``component.model.instantiations.componentInstantiation.moduleParameters``
             and ``component.model.instantiations.componentInstantiation.parameters`` except that the
             former is more pertinent to HDL models (as IEEE Std. gives examples of various representations
             in different HDLs).
          
          
        • (opt.) fileSetRef (s): Reference to a fileSet in the containing document (i.e. the same IP-XACT document) or another document referenced by VLNV.

          The fact that there can be multiple fileSetRef elements in the instantiation and that they can point to an arbitrary IP-XACT document could be used to capture dependencies. One inconvenience is that it the dependency would better be captured at the instantiation/model level rather than pointing to a file set. If using this mechanism for capturing dependencies, a well-defined file set naming convention would likely be inevitable.

          Xilinx seems to use similar approach except that it puts the dependency information into a fileSet.vendorExtension. See https://github.com/Xilinx/revCtrl/blob/master/ip/axi_iic_0/axi_iic_0.xml and search for xilinx_vhdlbehavioralsimulation_xilinx_com_ip_blk_mem_gen_8_2__ref_view_fileset.

        • (opt.) parameters: Describes additional parameters for the enclosing instantiation element.

        • (opt.) vendorExtensions

    • (opt.) ports

      • port (s)
    • (opt.) views

      Note

      The only thing that a view element does is referring to a particular instantiation and optionally binding it to a set of tools/environments. If the tool binding information is irrelevant for the IP-XACT recipient, it seems that processing the views sub-tree can be avoided.

      • view (s)
        • name
        • (opt.) componentInstantiationRef
        • (opt.) envIdentifier (s): This elements can specify tool/environment for which the enclosing view applies. For example, it may specify a synthesis tool or a simulation tool to be used for a particular view.
  • fileSets

    • fileSet (s):

      • name: fileSet's ID.

      • (opt.) displayName

      • (opt.) description

      • (opt.) group: A single, descriptive word identifying the enclosing file sets' purpose.

      • file (s): Describes a file/directory within a file set.

        Note

        file XML element can have any XML attributes. Hence vendors may use whatever custom attributes they like. Obviously, anything custom here is non-standard and rises chances for attribute name conlisions and misinterpretation.

        • name: A file path. The value is of ipxact:stringURIExpression type.

        • fileType (s): Identifies the file type. Can be one of the Std.-defined types (e.g. verilogSource, see C.8 in 1685-2014; 1685-2022 defines more pre-defined types) or user. For custom types use: <ipxact:fileType user="...">user</ipxact:fileType>.

          There can be more fileTypes for a file. For example Xilinx uses the user fileType element to describe various xilinx-specific use cases of the file:

          <!-- IEEE std. 1685-2009 -->
          <spirit:file>
              <spirit:name>src/rgb2dvi.xdc</spirit:name>
              <spirit:userFileType>xdc</spirit:userFileType>
              <spirit:userFileType>IMPORTED_FILE</spirit:userFileType>
              <spirit:userFileType>USED_IN_implementation</spirit:userFileType>
              <spirit:userFileType>USED_IN_synthesis</spirit:userFileType>
          </spirit:file>
          
        • (opt.) vendorExtensions: file can have vendor extensions.

      • (opt.) vendorExtensions: fileSet can have vendor extensions.

Perameterized Ports

Many reusable IPs come with parameters and parameterized ports. See a GPIO controller IP (VHDL, https://github.com/tudortimi/ipxact/tree/master/tests/Leon2/xml/spiritconsortium.org/Leon2RTL/gpio/1.2):

entity gpio is
generic (
      GPI_BITS : integer := 8;
      ...
      );

port (
      gpi:        in     std_logic_vector(GPI_BITS-1 downto 0);
      ...
      );
end gpio;

Corresponding IP-XACT 2014 would look like follows:

<ipxact:component ...>
   ...
   <ipxact:model>
      ...
      <ipxact:instantiations>
         <ipxact:componentInstantiation>
            <ipxact:name>vhdlsource</ipxact:name>
            <ipxact:language>vhdl</ipxact:language>
            <ipxact:moduleName>gpio(rtl)</ipxact:moduleName>
            <ipxact:moduleParameters>
               <ipxact:moduleParameter minimum="1" maximum="32" dataType="integer">
                  <ipxact:name>GPI_BITS</ipxact:name>
                  <ipxact:value>gpi</ipxact:value>            <!-- Notice the use of `gpi` as value variable/ID -->
               </ipxact:moduleParameter>
               ...
            </ipxact:moduleParameters>
         </ipxact:componentInstantiation>
      </ipxact:instantiations>
      <ipxact:ports>
         <ipxact:port>
            <ipxact:name>gpi</ipxact:name>
            <ipxact:wire>
               <ipxact:direction>in</ipxact:direction>
               <ipxact:vectors>
                  <ipxact:vector>
                     <ipxact:left>gpi - 1</ipxact:left>
                     <ipxact:right>0</ipxact:right>           <!-- Notice the use of `gpi` as value variable/ID -->
                  </ipxact:vector>
               </ipxact:vectors>
            </ipxact:wire>
         </ipxact:port>
         ...
      </ipxact:ports>
      ...
   </ipxact:model>
   ...
   <ipxact:parameters>
      <ipxact:parameter parameterId="gpi" resolve="user" order="0" configGroups="requiredConfig"
                        prompt="Number of input:"
                        minimum="1"
                        maximum="32">
         <ipxact:name>GPI_BITS</ipxact:name>
         <ipxact:value>8</ipxact:value>
      </ipxact:parameter>
      ...
   </ipxact:parameters>
</ipxact:component>

One problem arises with parameters that in HDL representation have no default value, as IP-XACT requires a non-empty <ipxact:value> for <ipxact:parameter>. See https://forums.accellera.org/topic/7051-ipxact-parameter-with-emptyno-value/

ipyxact

printing IpxactItem:

from ipyxact.ipyxact import Component, Catalog

# https://stackoverflow.com/a/65808327
def _xml_pretty_print(current, parent=None, index=-1, depth=0):
    for i, node in enumerate(current):
        _xml_pretty_print(node, current, i, depth + 1)
    if parent is not None:
        if index == 0:
            parent.text = '\n' + ('\t' * depth)
        else:
            parent[index - 1].tail = '\n' + ('\t' * depth)
        if index == len(parent) - 1:
            current.tail = '\n' + ('\t' * (depth - 1))

if __name__ == "__main__":
    catalog = Catalog();
    catalog.load(io.StringIO(data['kactus2-spi_example']));
    root = ET.Element('' + catalog._tag)
    catalog._write(root, '')

    #---->>>>
    # in python 3.9+: tree = ET.ElementTree(root)
    # in python 3.9+: ET.indent(tree, space="\t", level=0)
    _xml_pretty_print(root)
    #<<<<----
    s = ET.tostring(root, encoding="unicode")
    sys.stdout.write(s);

create an IP-XACT element manually:

import ipyxact.ipyxact

if __name__ == "__main__":
    catalog = ipyxact.ipyxact.Catalog();
    #catalog.load(io.StringIO(data['kactus2-spi_example']));

    catalogs = ipyxact.ipyxact.Catalogs();
    catalog.catalogs = catalogs;

    vlnv = ipyxact.ipyxact.Vlnv();
    vlnv.vendor = 'my_vendor'
    vlnv.version = '1.1'
    vlnv.name = 'my_name'
    vlnv.library = 'my_lib'

    ipxactFile = ipyxact.ipyxact.IpxactFile();
    ipxactFile.name = '../some/path'
    ipxactFile.vlnv = vlnv

    catalogs.ipxactFile.append( ipxactFile );

    catalog.write(sys.stdout,indent='  ')
# Copyright 2023 Tomas Brabec
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import sys
import os
import io
import pathlib
import argparse
from systemrdl import RDLCompiler, RDLCompileError
from peakrdl_ipxact import IPXACTImporter
from systemrdl import RDLListener, RDLWalker
# add `.` source tree into PYTHONPATH
sys.path.append(os.path.join(os.path.dirname(__file__), '.'))
import rdlWriter
# Instantiate the parser
parser = argparse.ArgumentParser(description='Converts IP-XACT register model to System RDL model.')
parser.add_argument('-o', '--output', dest='output', required=True, type=pathlib.Path,
help='System RDL output file, stdout if not given')
parser.add_argument('-i', '--input', dest='file', required=True, type=pathlib.Path,
help='IP-XACT file to convert')
opts = parser.parse_args()
rdlc = RDLCompiler()
ipxact = IPXACTImporter(rdlc)
root = None
iof = None
if opts.output is None:
iof = io.StringIO()
try:
ipxact.import_file( opts.file )
# if opts.output is not None:
# rdlc.compile_file( opts.output )
root = rdlc.elaborate()
except RDLCompileError:
sys.exit(1)
# Traverse the register model!
walker = RDLWalker(unroll=True)
listener = rdlWriter.rdlWriterListener()
walker.walk(root, listener)
# modified `examples/convert_to_ipxact.py` from https://github.com/SystemRDL/PeakRDL-ipxact
import sys
import io
import pathlib
import argparse
import logging
from typing import Union, Optional, TYPE_CHECKING, Any
from xml.dom import minidom
import xml.etree.ElementTree as et
from systemrdl import RDLCompiler, RDLCompileError
from systemrdl.node import AddressableNode, RootNode, Node
from systemrdl.node import AddrmapNode, MemNode
from systemrdl.node import RegNode, RegfileNode, FieldNode
from peakrdl_ipxact import IPXACTExporter, Standard
# https://stackoverflow.com/a/65808327
def _pretty_print(current, parent=None, index=-1, depth=0, indent=' '):
for i, node in enumerate(current):
_pretty_print(node, current, i, depth + 1, indent)
if parent is not None:
if index == 0:
parent.text = '\n' + (indent * depth)
else:
parent[index - 1].tail = '\n' + (indent * depth)
if index == len(parent) - 1:
current.tail = '\n' + (indent * (depth - 1))
class XactNamespace(object):
ns = {'ipxact':"http://www.accellera.org/XMLSchema/IPXACT/1685-2014"};
def __init__(self, ns = None):
self.ns = ns;
def compileTag(self, tag:str, prefix:str = 'ipxact'):
ns = self.ns or XactNamespace.ns;
if ns and prefix is not None:
if prefix in ns:
tag = f'{{{ns[prefix]}}}{tag}';
else:
logging.error(f"Unregistered namespace prefix (geistered: {''.join([i for i in ns])}): {prefix}");
tag = prefix + ':' + tag;
elif ns and prefix is None and len(ns) == 1:
# when a sole namespace registered, then use it
prefix = list(ns.values())[0];
tag = f'{{{prefix}}}{tag}';
elif prefix is not None:
tag = prefix + ':' + tag;
return tag;
def minidom2elementtree(node: minidom.Node):
if node is None:
return None;
try:
s = node.toxml();
# ElementTree namespaces for XML parsing
# (the proper IP-XACT/XML namespaces shall use `xmlns:` prefix to
# namespace names; however, ElementTree does not support it for
# `ElementTree.register_namespace()`.)
ns = {'xsi':"http://www.w3.org/2001/XMLSchema-instance",
'ipxact':"http://www.accellera.org/XMLSchema/IPXACT/1685-2014"
};
for p,u in ns.items():
logging.debug(f"registering namespace {p}:{u}");
et.register_namespace(p, u);
root = et.fromstring(s);
return et.ElementTree(root);
except Exception as e:
logging.error(e);
return None;
def add_mmap(node: Union[AddrmapNode, RootNode], comp: minidom.Element, ns: XactNamespace):
if node is None or comp is None:
return;
if ns is None: ns = XactNamespace();
dom = comp.ownerDocument;
# get existing `memoryMaps` (if already exists)
mmaps = None;
if comp.hasChildNodes():
for n in comp.childNodes:
if n.nodeType != minidom.Node.ELEMENT_NODE: continue;
if n.tagName == ns.compileTag('memoryMaps'):
mmaps = n;
break;
# create new `memoryMaps` (if none exists)
if mmaps is None:
mmaps = dom.createElement(ns.compileTag("memoryMaps"));
# insert into correct position (so that resulting XML validates
# to IP-XACT schema)
inserted = False;
if comp.hasChildNodes():
elemseq = ['vendor', 'library', 'name', 'version',
'busInterfaces', 'indirectInterfaces', 'channels',
'remapStates', 'addressSpaces', 'memoryMaps',
'model', 'componentGenerators', 'choices',
'fileSets', 'whiteboxElements', 'cpus',
'otherClockDrivers', 'resetTypes', 'description',
'parameters', 'assertions', 'vendorExtensions'];
predecesors = elemseq[:elemseq.index('memoryMaps')];
for n in comp.childNodes:
if n.nodeType != minidom.Node.ELEMENT_NODE: continue;
_, _, tag = n.tagName.rpartition(':');
if tag not in predecesors:
comp.insertBefore(mmaps,n);
inserted = True;
break;
if not inserted:
comp.appendChild(mmaps);
# get existing `memoryMaps.memoryMap` (if already exists)
#TODO ...
exporter = IPXACTExporter();
exporter.doc = dom;
#---->>>> GPL licensed code (from peakrdl_ipxact.Exporter)
# Determine if top-level node should be exploded across multiple
# addressBlock groups
explode = False
# If top node is an addrmap, and it contains 1 or more children that
# are:
# - exclusively addrmap or mem
# - and None of them are arrays
# ... then it makes more sense to "explode" the
# top-level node and make each of its children their own addressBlock
# (explode --> True)
#
# Otherwise, do not "explode" the top-level node
# (explode --> False)
if isinstance(node, AddrmapNode):
addrblockable_children = 0
non_addrblockable_children = 0
for child in node.children(skip_not_present=False):
if not isinstance(child, AddressableNode):
continue
if isinstance(child, (AddrmapNode, MemNode)) and not child.is_array:
addrblockable_children += 1
else:
non_addrblockable_children += 1
if (non_addrblockable_children == 0) and (addrblockable_children >= 1):
explode = True
#<<<<----
# Do the export!
# --------------
# top-node becomes the memoryMap
mmap = dom.createElement(ns.compileTag("memoryMap"));
mmaps.appendChild(mmap);
#---->>>> GPL licensed code (from peakrdl_ipxact.Exporter)
if explode:
exporter.add_nameGroup(mmap,
node.inst_name,
node.get_property("name", default=None),
node.get_property("desc")
);
# Top-node's children become their own addressBlocks
for child in node.children(skip_not_present=False):
if not isinstance(child, AddressableNode):
continue;
exporter.add_addressBlock(mmap, child);
else:
# Not exploding apart the top-level node
# Wrap it in a dummy memoryMap that bears its name
exporter.add_nameGroup(mmap, "%s_mmap" % node.inst_name);
# Export top-level node as a single addressBlock
exporter.add_addressBlock(mmap, node);
#<<<<----
class CustomIPXACTExporter(IPXACTExporter):
def __init__(self, **kwargs: Any) -> None:
super().__init__(**kwargs);
def write_out(self, node: Union[AddrmapNode, RootNode], f: io.TextIOBase=sys.stdout, **kwargs: Any) -> None:
"""
Parameters
----------
node: AddrmapNode
Top-level SystemRDL node to export.
path:
Path to save the exported XML file.
component_name: str
IP-XACT component name. If unspecified, uses the top node's name
upon export.
"""
self.msg = node.env.msg
component_name = kwargs.pop("component_name", None) or node.inst_name
# Check for stray kwargs
if kwargs:
raise TypeError("got an unexpected keyword argument '%s'" % list(kwargs.keys())[0])
# If it is the root node, skip to top addrmap
if isinstance(node, RootNode):
node = node.top
if not isinstance(node, (AddrmapNode, MemNode)):
raise TypeError("'node' argument expects type AddrmapNode or MemNode. Got '%s'" % type(node).__name__)
if isinstance(node, AddrmapNode) and node.get_property('bridge'):
self.msg.warning(
"IP-XACT generator does not have proper support for bridge addmaps yet. The 'bridge' property will be ignored.",
node.inst.property_src_ref.get('bridge', node.inst.inst_src_ref)
)
# Initialize XML DOM
self.doc = minidom.getDOMImplementation().createDocument(None, None, None)
tmp = self.doc.createComment("Generated by PeakRDL IP-XACT (https://github.com/SystemRDL/PeakRDL-ipxact)")
self.doc.appendChild(tmp)
# Create top-level component
comp = self.doc.createElement(self.ns + "component")
if self.standard == Standard.IEEE_1685_2014:
comp.setAttribute("xmlns:ipxact", "http://www.accellera.org/XMLSchema/IPXACT/1685-2014")
comp.setAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance")
comp.setAttribute("xsi:schemaLocation", "http://www.accellera.org/XMLSchema/IPXACT/1685-2014 http://www.accellera.org/XMLSchema/IPXACT/1685-2014/index.xsd")
elif self.standard == Standard.IEEE_1685_2009:
comp.setAttribute("xmlns:spirit", "http://www.spiritconsortium.org/XMLSchema/SPIRIT/1685-2009")
comp.setAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance")
comp.setAttribute("xsi:schemaLocation", "http://www.spiritconsortium.org/XMLSchema/SPIRIT/1685-2009 http://www.spiritconsortium.org/XMLSchema/SPIRIT/1685-2009/index.xsd")
else:
raise RuntimeError
self.doc.appendChild(comp)
# versionedIdentifier Block
self.add_value(comp, self.ns + "vendor", self.vendor)
self.add_value(comp, self.ns + "library", self.library)
self.add_value(comp, self.ns + "name", component_name)
self.add_value(comp, self.ns + "version", self.version)
mmaps = self.doc.createElement(self.ns + "memoryMaps")
comp.appendChild(mmaps)
# Determine if top-level node should be exploded across multiple
# addressBlock groups
explode = False
# If top node is an addrmap, and it contains 1 or more children that
# are:
# - exclusively addrmap or mem
# - and None of them are arrays
# ... then it makes more sense to "explode" the
# top-level node and make each of its children their own addressBlock
# (explode --> True)
#
# Otherwise, do not "explode" the top-level node
# (explode --> False)
if isinstance(node, AddrmapNode):
addrblockable_children = 0
non_addrblockable_children = 0
for child in node.children(skip_not_present=False):
if not isinstance(child, AddressableNode):
continue
if isinstance(child, (AddrmapNode, MemNode)) and not child.is_array:
addrblockable_children += 1
else:
non_addrblockable_children += 1
if (non_addrblockable_children == 0) and (addrblockable_children >= 1):
explode = True
# Do the export!
if explode:
# top-node becomes the memoryMap
mmap = self.doc.createElement(self.ns + "memoryMap")
self.add_nameGroup(mmap,
node.inst_name,
node.get_property("name", default=None),
node.get_property("desc")
)
mmaps.appendChild(mmap)
# Top-node's children become their own addressBlocks
for child in node.children(skip_not_present=False):
if not isinstance(child, AddressableNode):
continue
self.add_addressBlock(mmap, child)
else:
# Not exploding apart the top-level node
# Wrap it in a dummy memoryMap that bears its name
mmap = self.doc.createElement(self.ns + "memoryMap")
self.add_nameGroup(mmap, "%s_mmap" % node.inst_name)
mmaps.appendChild(mmap)
# Export top-level node as a single addressBlock
self.add_addressBlock(mmap, node)
# Write out XML dom
if f is None:
f = sys.stdout;
self.doc.writexml(
f,
addindent=self.xml_indent,
newl=self.xml_newline,
encoding="UTF-8"
)
# Instantiate the parser
parser = argparse.ArgumentParser(description='Reads in System RDL file and exports it to IP-XACT register model.')
parser.add_argument('-o', '--output', dest='output', required=False, type=pathlib.Path,
help='IP-XACT output file, stdout if not given')
#TODO parser.add_argument('-i', '--input', dest='file', required=True, type=pathlib.Path,
#TODO help='System RDL file to convert')
parser.add_argument('--xact', dest='xact', required=False, type=pathlib.Path,
help='IP-XACT 2014 to be updated with view information.')
parser.add_argument('--xact-library', dest='library', required=False, type=str,
help='IP-XACT component library name.');
parser.add_argument('--xact-version', dest='version', required=False, type=str,
help='IP-XACT component version number.');
parser.add_argument('--xact-vendor', dest='vendor', required=False, type=str,
help='IP-XACT component vendor name.');
parser.add_argument('--xact-name', dest='name', required=False, type=str,
help='IP-XACT component name.');
parser.add_argument('--rwd', dest='rwd', required=False, type=pathlib.Path,
help='Relative Working Directory (RWD), which to make file paths relative to. Applies only if `output` not specified.');
parser.add_argument('--log-level', dest='loglevel', required=False, type=str, default='ERROR',
help='Logging severity, one of: DEBUG, INFO, WARNING, ERROR, FATAL. Defaults to ERROR.');
parser.add_argument('-l', '--log-file', dest='logfile', required=False, type=pathlib.Path, default=None,
help='Path to a log file. Defaults to stderr if none given.');
parser.add_argument('file', type=pathlib.Path,
help='System RDL file to convert');
# parse CLI options
opts = parser.parse_args();
# default logging setup
logging.basicConfig(level=logging.ERROR);
# setup logging destination (file or stderr)
# (stderr is already set as default in the logging setup)
if opts.logfile is not None:
logFileHandler = None;
try:
# using `'w'` will make the FileHandler overwrite the log file rather than
# append to it
logFileHandler = logging.FileHandler(str(opts.logfile),'w');
except Exception as e:
logging.error(e);
if logFileHandler is not None:
rootLogger = logging.getLogger();
fmt = None;
if len(rootLogger.handlers) > 0:
fmt = rootLogger.handlers[0].formatter;
if fmt is not None:
logFileHandler.setFormatter(fmt);
rootLogger.handlers = []; # remove default handlers
rootLogger.addHandler(logFileHandler);
# setup logging level
try:
logging.getLogger().setLevel(opts.loglevel);
except Exception as e:
logging.error(e);
# output directory
# (`None` means to use absolute paths)
if opts.output:
outputDir = str(opts.output.parent);
elif opts.rwd:
outputDir = str(opts.rwd);
else:
outputDir = None;
# Create an instance of the compiler
rdlc = RDLCompiler();
try:
rdlc.compile_file(opts.file);
root = rdlc.elaborate();
except RDLCompileError as e:
# A compilation error occurred. Exit with error code
logging.error(f"Failed to parse {opts.file}: {e}");
sys.exit(1);
# Override `XactNamespace` mapping as it would otherwise yield ElementTree
# namespace prefix of `{uri}tag`. Setting it to `None` would make `compileTag()`
# yield the minidom expected `prefix:tag`.
XactNamespace.ns = None;
ns = XactNamespace();
dom = None;
if opts.xact:
try:
dom = minidom.parse(str(opts.xact));
except Exception as e:
logging.error(f"Failed to parse {opts.xact}: {e}");
sys.exit(1);
if not dom:
# proper IP-XACT 2014 XML namespaces
xactns = {
'xmlns:ipxact':"http://www.accellera.org/XMLSchema/IPXACT/1685-2014",
'xmlns:xsi':"http://www.w3.org/2001/XMLSchema-instance",
'xsi:schemaLocation':"http://www.accellera.org/XMLSchema/IPXACT/1685-2014 http://www.accellera.org/XMLSchema/IPXACT/1685-2014/index.xsd"
};
dom = minidom.getDOMImplementation().createDocument(None, None, None);
comp = dom.createElement(ns.compileTag('component'));
dom.appendChild(comp);
#TODO dom = minidom.getDOMImplementation().createDocument(
#TODO xactns['xmlns:ipxact'], ns.compileTag('component'), None);
#TODO comp = dom.documentElement;
for k,v in xactns.items():
if k[:5] == 'xmlns:':
comp.setAttributeNS('',k,v);
else:
comp.setAttribute(k,v);
# default XML element values (unless relevant options defined
# through CLI options)
defaults = {'version':'0.0.0', 'name':'manifest'};
for tag in ['vendor', 'library', 'name', 'version', 'description']:
e = dom.createElement(ns.compileTag(tag));
if hasattr(opts,tag) and getattr(opts,tag) is not None:
text = str(getattr(opts,tag));
elif tag in defaults:
text = defaults[tag];
else:
text = tag;
e.appendChild( dom.createTextNode(text) );
comp.appendChild(e);
if dom:
comp = dom.documentElement;
add_mmap(root.top, comp, ns);
#TODO # printing using minidom formattinf/pretty print
#TODO sys.stdout.buffer.write( dom.toprettyxml(indent=' ', newl='\n', encoding='UTF-8'));
# convert from minidom to ElementTree
# (as minidom pretty print sucks)
tree = minidom2elementtree(dom);
# reformat XML
_pretty_print(tree.getroot());
# print XML
if opts.output:
with open(str(opts.output), 'w') as f:
tree.write(f, encoding='unicode', xml_declaration=True);
else:
tree.write(sys.stdout, encoding='unicode', xml_declaration=True);
# Copyright 2023 Tomas Brabec
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import sys
import os
import io
import pathlib
import argparse
from systemrdl import RDLCompiler, RDLCompileError
from peakrdl_ipxact import IPXACTImporter
from systemrdl import RDLListener, RDLWalker
# add `.` source tree into PYTHONPATH
sys.path.append(os.path.join(os.path.dirname(__file__), '.'))
import rdlWriter
# Instantiate the parser
parser = argparse.ArgumentParser(description='Reads in System RDL file and exports it to System RDL model.')
parser.add_argument('-o', '--output', dest='output', required=True, type=pathlib.Path,
help='System RDL output file, stdout if not given')
parser.add_argument('-i', '--input', dest='file', required=True, type=pathlib.Path,
help='System RDL file to convert')
opts = parser.parse_args()
rdlc = RDLCompiler()
root = None
iof = None
if opts.output is None:
iof = io.StringIO()
try:
rdlc.compile_file( opts.file )
root = rdlc.elaborate()
except RDLCompileError:
sys.exit(1)
# Traverse the register model!
walker = RDLWalker(unroll=True)
listener = rdlWriter.rdlWriterListener()
walker.walk(root, listener)
# Copyright 2023 Tomas Brabec
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import sys
from systemrdl import RDLListener, RDLWalker
from systemrdl.node import FieldNode, AddressableNode, RegNode
# Define a listener that will print out the register model hierarchy
class rdlWriterListener(RDLListener):
f = sys.stdout;
def __init__(self, f=None):
self.indent = 0
if f is not None:
self.f = f;
def enter_Component(self, node):
if not isinstance(node, FieldNode):
s = "\t"*self.indent;
type_name = None; # node.type_name;
if type_name is None:
if isinstance(node, RegNode):
type_name = 'reg';
elif isinstance(node, AddressableNode):
type_name = 'addrmap';
else:
type_name = '???';
s += type_name;
if self.indent == 0:
s += ' ' + node.get_path_segment();
s += ' {';
self.indent += 1
print(s, file=self.f)
def enter_Field(self, node):
# Print some stuff about the field
bit_range_str = "[%d:%d]" % (node.high, node.low)
sw_access_str = "sw=%s" % node.get_property('sw').name
s = "\t"*self.indent + 'field {' + sw_access_str + ';} ' + node.get_path_segment() + bit_range_str + ';';
print(s, file=self.f)
def exit_Component(self, node):
if not isinstance(node, FieldNode):
self.indent -= 1
s = "\t"*self.indent + '}';
if self.indent > 0:
s += ' ' + node.get_path_segment();
if isinstance(node, RegNode):
s += ' @0x{:x}'.format( node.address_offset );
s += ';'
print(s, file=self.f)
  • create a regular github repository

    • start adding unit tests
  • vlog2ipxact

    • do not add <moduleParameters> if there are none (XML validation would fail otherwise)

    • fix output port in Verilog

      example (problem with q recognized as in, other potential problem is port type; root cause is the Verilog-style ports are defined as kPortReference and hence need to find the referenced port and type declaration(s)):

      module foo(a,b,q);
      input a, b;
      output q;
      wire a, b;
      wire q;
      endmodule
      
  • rdl2ipxact

    • test existing <memoryMap>
# Copyright 2023 Tomas Brabec
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import sys
import pathlib
import logging
import argparse
import xml.etree.ElementTree as et
from typing import Iterable, Optional, List
# https://stackoverflow.com/a/65808327
def _pretty_print(current, parent=None, index=-1, depth=0, indent=' '):
for i, node in enumerate(current):
_pretty_print(node, current, i, depth + 1, indent)
if parent is not None:
if index == 0:
parent.text = '\n' + (indent * depth)
else:
parent[index - 1].tail = '\n' + (indent * depth)
if index == len(parent) - 1:
current.tail = '\n' + (indent * (depth - 1))
class XactNamespace(object):
ns = {'ipxact':"http://www.accellera.org/XMLSchema/IPXACT/1685-2014"};
def __init__(self, ns = None):
self.ns = ns;
def compileTag(self, tag:str, prefix:str = 'ipxact'):
ns = self.ns or XactNamespace.ns;
if ns and prefix is not None:
if prefix in ns:
tag = f'{{{ns[prefix]}}}{tag}';
else:
logging.error(f"Unregistered namespace prefix (geistered: {''.join([i for i in ns])}): {prefix}");
tag = prefix + ':' + tag;
elif ns and prefix is None and len(ns) == 1:
# when a sole namespace registered, then use it
prefix = list(ns.values())[0];
tag = f'{{{prefix}}}{tag}';
elif prefix is not None:
tag = prefix + ':' + tag;
return tag;
def xact_add_view(tree, viewname:str, files:List[pathlib.Path], outputDir:str = None):
if tree is None:
return;
ns = XactNamespace();
comp = tree.getroot();
if comp is None or comp.tag != ns.compileTag('component'):
logging.error(f'Expecting `ipxact:component` root in {opts.xact}: {comp.tag}');
return;
compinstname = viewname + '_implementation';
filesetname = viewname + '_files';
model = comp.find('ipxact:model',XactNamespace.ns);
views = comp.find('ipxact:model/ipxact:views',XactNamespace.ns);
insts = comp.find('ipxact:model/ipxact:instantiations',XactNamespace.ns);
filesets = comp.find('ipxact:fileSets',XactNamespace.ns);
# sanity check that the new view does not exit yet
if views is not None:
names = views.findall('ipxact:view/ipxact:name',XactNamespace.ns);
for e in names:
if e.text == viewname:
logging.error(f'View `{viewname}` already exists!');
return;
# sanity check that the new componentInstantiation does not exit yet
if insts is not None:
names = insts.findall('ipxact:componentInstantiation/ipxact:name',XactNamespace.ns);
for e in names:
if e.text == compinstname:
logging.error(f'Component instantiation `{compinstname}` already exists!');
return;
# sanity check that the new fileset does not exit yet
if filesets is not None:
names = filesets.findall('ipxact:fileset/ipxact:name',XactNamespace.ns);
for e in names:
if e.text == filesetname:
logging.error(f'File set `{filesetname}` already exists!');
return;
elemseq = ['vendor', 'library', 'name', 'version',
'busInterfaces', 'indirectInterfaces', 'channels',
'remapStates', 'addressSpaces', 'memoryMaps',
'model', 'componentGenerators', 'choices',
'fileSets', 'whiteboxElements', 'cpus',
'otherClockDrivers', 'resetTypes', 'description',
'parameters', 'assertions', 'vendorExtensions'];
# create new model (if needed)
if model is None:
logging.warning(f'No `ipxact:model` element found!');
model = et.Element(ns.compileTag('model'));
predecesors = elemseq[:elemseq.index('model')];
inserted = False;
for i,e in enumerate(comp):
_, _, tag = e.tag.rpartition('}');
if tag not in predecesors:
comp.insert(i,model);
inserted = True;
break;
if not inserted: comp.append(model);
# create new views element (if needed)
if views is None:
logging.warning(f'No `ipxact:views` element found!');
views = et.Element(ns.compileTag('views'));
# `views` is the 1st element under `model`
model.insert(0,views);
# create new view element
view = et.SubElement(views, ns.compileTag('view'));
e = et.Element(ns.compileTag('name'));
e.text = viewname;
view.append(e);
e = et.Element(ns.compileTag('componentInstantiationRef'));
e.text = compinstname;
view.append(e);
# create new instantiations element (if needed)
if insts is None:
logging.warning(f'No `ipxact:instantiations` element found!');
insts = et.Element(ns.compileTag('instantiations'));
# `instantiations` is the 2nd element under `model`
model.insert(1,insts);
# create new component instantiation element
compinst = et.SubElement(insts, ns.compileTag('componentInstantiation'));
e = et.Element(ns.compileTag('name'));
e.text = compinstname;
compinst.append(e);
e = et.Element(ns.compileTag('fileSetRef'));
compinst.append(e);
e = et.Element(ns.compileTag('localName'));
e.text = filesetname;
compinst[-1].append(e);
# create new filesets element (if needed)
if filesets is None:
logging.warning(f'No `ipxact:filesets` element found!');
filesets = et.Element(ns.compileTag('fileSets'));
predecesors = elemseq[:elemseq.index('fileSets')];
inserted = False;
for i,e in enumerate(comp):
_, _, tag = e.tag.rpartition('}');
if tag not in predecesors:
comp.insert(i,filesets);
inserted = True;
break;
if not inserted: comp.append(filesets);
# create new fileset element
fileset = et.SubElement(filesets,ns.compileTag('fileSet'));
e = et.Element(ns.compileTag('name'));
e.text = filesetname;
fileset.append(e);
e = et.Element(ns.compileTag('localName'));
e.text = filesetname;
for f in files:
fileSetFile = et.SubElement(fileset, ns.compileTag('file'));
e = et.SubElement(fileSetFile, ns.compileTag('name'));
if outputDir:
e.text = str(f.relative_to(outputDir));
else:
e.text = str(f.absolute());
e = et.SubElement(fileSetFile, ns.compileTag('fileType'));
e.text = 'unknown';
return;
parser = argparse.ArgumentParser(description='Adds IP view into IP-XACT 2014.');
parser.add_argument('-o', '--output', dest='output', required=False, type=pathlib.Path,
help='IP-XACT output file, stdout if not given.');
#TODO parser.add_argument('-m', '--module', dest='module', required=False, type=str,
#TODO help='Name of the root module.');
parser.add_argument('--xact', dest='xact', required=False, type=pathlib.Path,
help='IP-XACT 2014 to be updated with view information.')
parser.add_argument('--xact-library', dest='library', required=False, type=str,
help='IP-XACT component library name.');
parser.add_argument('--xact-version', dest='version', required=False, type=str,
help='IP-XACT component version number.');
parser.add_argument('--xact-vendor', dest='vendor', required=False, type=str,
help='IP-XACT component vendor name.');
parser.add_argument('-n', '--view-name', dest='viewname', required=True, type=str,
help='IP view name.');
parser.add_argument('--rwd', dest='rwd', required=False, type=pathlib.Path,
help='Relative Working Directory (RWD), which to make file paths relative to. Applies only if `output` not specified.');
parser.add_argument('--log-level', dest='loglevel', required=False, type=str, default='ERROR',
help='Logging severity, one of: DEBUG, INFO, WARNING, ERROR, FATAL. Defaults to ERROR.');
parser.add_argument('-l', '--log-file', dest='logfile', required=False, type=pathlib.Path, default=None,
help='Path to a log file. Defaults to stderr if none given.');
parser.add_argument('files', type=pathlib.Path, nargs='+',
help='List of files in the view.');
# parse CLI options
opts = parser.parse_args();
# default logging setup
logging.basicConfig(level=logging.ERROR);
# setup logging destination (file or stderr)
# (stderr is already set as default in the logging setup)
if opts.logfile is not None:
logFileHandler = None;
try:
# using `'w'` will make the FileHandler overwrite the log file rather than
# append to it
logFileHandler = logging.FileHandler(str(opts.logfile),'w');
except Exception as e:
logging.error(e);
if logFileHandler is not None:
rootLogger = logging.getLogger();
fmt = None;
if len(rootLogger.handlers) > 0:
fmt = rootLogger.handlers[0].formatter;
if fmt is not None:
logFileHandler.setFormatter(fmt);
rootLogger.handlers = []; # remove default handlers
rootLogger.addHandler(logFileHandler);
# setup logging level
try:
logging.getLogger().setLevel(opts.loglevel);
except Exception as e:
logging.error(e);
# input files
file_paths = [str(f) for f in opts.files];
# output directory
# (`None` means to use absolute paths)
if opts.output:
outputDir = str(opts.output.parent);
elif opts.rwd:
outputDir = str(opts.rwd);
else:
outputDir = None;
# ElementTree namespaces for XML parsing
# (the proper IP-XACT/XML namespaces shall use `xmlns:` prefix to
# namespace names; however, ElementTree does not support it for
# `ElementTree.register_namespace()`.)
ns = {'xsi':"http://www.w3.org/2001/XMLSchema-instance",
'ipxact':"http://www.accellera.org/XMLSchema/IPXACT/1685-2014"
};
for p,u in ns.items():
logging.debug(f"registering namespace {p}:{u}");
et.register_namespace(p, u);
ns = XactNamespace();
tree = None;
if opts.xact:
try:
tree = et.parse(str(opts.xact));
except et.ParseError as e:
logging.error(f"Failed to parse {opts.xact}: {e}");
sys.exit(1);
if not tree:
# proper IP-XACT 2014 XML namespaces
xactns = {'xmlns:xsi':"http://www.w3.org/2001/XMLSchema-instance",
'xsi:schemaLocation':"http://www.accellera.org/XMLSchema/IPXACT/1685-2014 http://www.accellera.org/XMLSchema/IPXACT/1685-2014/index.xsd"
};
comp = et.Element(ns.compileTag('component'), xactns);
# default XML element values (unless relevant options defined
# through CLI options)
defaults = {'version':'0.0.0', 'name':'manifest'};
for tag in ['vendor', 'library', 'name', 'version', 'description']:
e = et.SubElement(comp, ns.compileTag(tag));
if hasattr(opts,tag) and getattr(opts,tag) is not None:
e.text = str(getattr(opts,tag));
elif tag in defaults:
e.text = defaults[tag];
else:
e.text = tag;
tree = et.ElementTree(comp);
else:
# test if root is an ipxact component
comp = tree.getroot();
if comp is None or comp.tag != ns.compileTag('component'):
logging.error(f'Expecting `ipxact:component` root in {opts.xact}: {comp.tag}');
sys.exit(1);
# sanity check for required VLNV elements
for i,tag in enumerate(['vendor','library','name','version']):
fulltag = 'ipxact:'+tag;
elem = comp.find(fulltag, XactNamespace.ns);
if elem is None:
elem = et.Element(ns.compileTag(tag));
comp.insert(i,elem);
if hasattr(opts,tag) and getattr(opts,tag) is not None:
logging.warning(f'Missing `{fulltag}` element in {opts.xact}!');
elem.text = getattr(opts,tag);
else:
logging.error(f'Missing `{fulltag}` element in {opts.xact}!');
elem.text = tag;
elif hasattr(opts,tag):
attr = getattr(opts,tag);
if attr is not None and attr != elem.text:
logging.error(f'User `{fulltag}` element `{attr}` not match `{elem.text}` in {opts.xact}');
tree = et.ElementTree(comp);
# add new IP-XACT view
xact_add_view( tree, opts.viewname, opts.files, outputDir );
# reformat XML
_pretty_print(tree.getroot());
# print XML
if opts.output:
with open(str(opts.output), 'w') as f:
tree.write(f, encoding='unicode', xml_declaration=True);
else:
tree.write(sys.stdout, encoding='unicode', xml_declaration=True);
# Copyright 2023 Tomas Brabec
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import sys
import pathlib
import anytree
import logging
import argparse
import verible_verilog_syntax
import xml.etree.ElementTree as et
from typing import Iterable, Optional, List
# https://stackoverflow.com/a/65808327
def _pretty_print(current, parent=None, index=-1, depth=0, indent=' '):
for i, node in enumerate(current):
_pretty_print(node, current, i, depth + 1, indent)
if parent is not None:
if index == 0:
parent.text = '\n' + (indent * depth)
else:
parent[index - 1].tail = '\n' + (indent * depth)
if index == len(parent) - 1:
current.tail = '\n' + (indent * (depth - 1))
# Declaring own SyntaxTree iterator that can search only
# within a certain depth of the tree.
class PreOrderDepthTreeIterator(verible_verilog_syntax._TreeIteratorBase):
def __init__(self, tree: "Node",
filter_: Optional[verible_verilog_syntax.CallableFilter] = None,
reverse_children: bool = False,
depth: int = -1):
self.tree = tree
self.reverse_children = reverse_children
self.filter_ = filter_ if filter_ else lambda n: True
self.depth = depth
def _iter_tree_depth(self, tree: Optional["Node"], depth=-1) -> Iterable["Node"]:
if self.filter_(tree):
yield tree
elif depth < 0 or depth > 0:
if depth > 0: depth -= 1;
for child in self._iter_children(tree):
yield from self._iter_tree_depth(child,depth)
def _iter_tree(self, tree: Optional["Node"]) -> Iterable["Node"]:
yield from self._iter_tree_depth(tree, self.depth)
class XactNamespace(object):
ns = {'ipxact':"http://www.accellera.org/XMLSchema/IPXACT/1685-2014"};
def __init__(self, ns = None):
self.ns = ns;
def compileTag(self, tag:str, prefix:str = 'ipxact'):
ns = self.ns or XactNamespace.ns;
if ns and prefix is not None:
if prefix in ns:
tag = f'{{{ns[prefix]}}}{tag}';
else:
logging.error(f"Unregistered namespace prefix (geistered: {''.join([i for i in ns])}): {prefix}");
tag = prefix + ':' + tag;
elif ns and prefix is None and len(ns) == 1:
# when a sole namespace registered, then use it
prefix = list(ns.values())[0];
tag = f'{{{prefix}}}{tag}';
elif prefix is not None:
tag = prefix + ':' + tag;
return tag;
class TypeDimension(object):
def __init__(self, left, right):
self.left = left;
self.right = right;
def __str__(self):
return '['+str(self.left)+':'+str(self.right)+']';
def etXact(self):
vector = et.Element('ipxact:vector');
left = et.SubElement(vector, 'ipxact:left');
left.text = self.left;
right = et.SubElement(vector, 'ipxact:right');
right.text = self.right;
return vector;
class Port(object):
attrs = ['direction', 'datatype', 'dimensions', 'name'];
lutDirection = {'input': 'in', 'output': 'out', 'inout': 'inout'};
def __init__(self, name, **kwargs):
self.name = name;
for attr in Port.attrs:
if hasattr(self,attr):
continue;
if attr in kwargs:
setattr(self,attr,kwargs[attr]);
else:
setattr(self,attr,None);
if self.direction is None:
self.direction = 'input';
def __str__(self):
attrs = [];
for attr in Port.attrs:
val = getattr(self,attr);
if val:
# dimensions need to be treated specifically as it is
# a list of `TypeDimension` instances
if attr=='dimensions':
attrs.append(''.join([str(d) for d in val]));
else:
attrs.append(val);
return ' '.join(attrs);
def etXact(self):
ns = XactNamespace();
p = et.Element(ns.compileTag('port'));
name = et.SubElement(p, ns.compileTag('name'));
name.text = self.name;
signal = et.SubElement(p, ns.compileTag('wire'));
direction = et.SubElement(signal, ns.compileTag('direction'));
if self.direction in Port.lutDirection:
direction.text = Port.lutDirection[self.direction];
else:
#TODO report error/warning
direction.text = Port.lutDirection['input'];
if self.dimensions and len(self.dimensions) > 0:
vectors= et.SubElement(signal, ns.compileTag('vectors'));
for dimension in self.dimensions:
vectors.append(dimension.etXact());
if self.datatype:
wiredefs = et.SubElement(signal, ns.compileTag('wireTypeDefs'));
wiredef = et.SubElement(wiredefs, ns.compileTag('wireTypeDef'));
typename = et.SubElement(wiredef, ns.compileTag('typeName'));
typename.text = self.datatype;
return p;
class Parameter(object):
attrs = ['datatype', 'dimensions', 'name', 'value'];
def __init__(self, name, **kwargs):
self.name = name;
for attr in Parameter.attrs:
if hasattr(self,attr):
continue;
if attr in kwargs:
setattr(self,attr,kwargs[attr]);
else:
setattr(self,attr,None);
def __str__(self):
attrs = [];
for attr in Parameter.attrs:
val = getattr(self,attr);
if val:
# dimensions need to be treated specifically as it is
# a list of `TypeDimension` instances
if attr=='dimensions':
attrs.append(''.join([str(d) for d in val]));
elif attr=='value':
attrs.append('=');
attrs.append(val);
else:
attrs.append(val);
return ' '.join(attrs);
def etXact(self, elementTag='moduleParameter'):
ns = XactNamespace();
p = et.Element(ns.compileTag(elementTag));
name = et.SubElement(p, ns.compileTag('name'));
name.text = self.name;
value = et.SubElement(p, ns.compileTag('value'));
if hasattr(self,'value') and self.value:
value.text = str(self.value);
else:
# use the parameter name as "symolic" value
value.text = self.name;
if self.datatype:
p.set('dataType', self.datatype);
return p;
def get_instances(module_data: verible_verilog_syntax.SyntaxData):
insts = None;
for inst in module_data.iter_find_all({"tag": ["kInstantiationBase"]}):
instname = inst.find({"tag": ["kGateInstance"]});
if not instname: continue;
insttype = inst.find({"tag": ["kInstantiationType"]});
if insttype:
modulename = insttype.find({"tag": ["kUnqualifiedId"]});
if not modulename: continue;
if len(modulename.children) > 0 and hasattr(modulename.children[0],'tag') and modulename.children[0].tag == 'SymbolIdentifier':
name = modulename.children[0].text;
if not insts: insts = [];
insts.append(name);
return insts;
def get_parameters(module_data: verible_verilog_syntax.SyntaxData):
params = [];
paramlist = module_data.find({"tag": ["kFormalParameterList"]});
if not paramlist:
return params;
lastParamDecl = None;
for param in paramlist.iter_find_all({"tag": ["kParamDeclaration"]}):
if param.tag == 'kParamDeclaration':
lastParamDecl = param;
name = param.find({"tag": ["kUnqualifiedId", "SymbolIdentifier", "EscapedIdentifier"]});
if name:
name = name.text;
else:
name = 'undefined_port';
dimensions = None;
datatype = None;
value = None;
if lastParamDecl:
paramDimensions = lastParamDecl.find({'tag': ['kDeclarationDimensions']});
if paramDimensions:
dimensions = [];
paramDimensions = paramDimensions.find_all({'tag': ['kDimensionRange','kDimensionScalar']});
for paramDimension in paramDimensions:
if paramDimension.tag == 'kDimensionRange':
dimensionRange = paramDimension.find_all({'tag':['kExpression']}, iter_ = PreOrderDepthTreeIterator, depth=1);
left = dimensionRange[0].text;
right = dimensionRange[1].text;
dimensions.append( TypeDimension(left,right) );
elif paramDimension.tag == 'kDimensionScalar':
size = paramDimension.find({'tag':['kExpressionList']});
if size:
dimensions.append( TypeDimension('0',size.text+'-1') );
datatype = lastParamDecl.find({'tag': ['kTypeInfo']});
if datatype:
datatype = datatype.text;
if len(datatype) == 0:
datatype = None;
else:
if hasattr(lastParamDecl.children[1], 'tag'):
datatype = lastParamDecl.children[1].text;
else:
datatype = None;
value = lastParamDecl.find({'tag': ['kTrailingAssign']});
if value:
value = value.find({'tag': ['kExpression']});
if value:
value = value.text;
#TODO print(anytree.RenderTree(param));
#TODO print(param.children[0]);
params.append( Parameter(name, datatype=datatype, dimensions=dimensions, value=value) );
return params;
def get_ports(module_data: verible_verilog_syntax.SyntaxData):
ports = [];
lastPortDecl = None;
for port in module_data.iter_find_all({"tag": ["kPortDeclaration", "kPort"]}):
if port.tag == 'kPortDeclaration':
lastPortDecl = port;
name = port.find({"tag": ["SymbolIdentifier", "EscapedIdentifier"]});
if name:
name = name.text;
else:
name = 'undefined_port';
dimensions = None;
direction = 'input ';
datatype = None;
if lastPortDecl:
portDimensions = lastPortDecl.find({'tag': ['kDeclarationDimensions']});
if portDimensions:
dimensions = [];
portDimensions = portDimensions.find_all({'tag': ['kDimensionRange','kDimensionScalar']});
for portDimension in portDimensions:
if portDimension.tag == 'kDimensionRange':
dimensionRange = portDimension.find_all({'tag':['kExpression']}, iter_ = PreOrderDepthTreeIterator, depth=1);
left = dimensionRange[0].text;
right = dimensionRange[1].text;
dimensions.append( TypeDimension(left,right) );
elif portDimension.tag == 'kDimensionScalar':
size = portDimension.find({'tag':['kExpressionList']});
if size:
dimensions.append( TypeDimension('0',size.text+'-1') );
datatype = lastPortDecl.find({'tag': ['kDataTypePrimitive']});
if datatype:
datatype = datatype.text;
else:
if hasattr(lastPortDecl.children[1], 'tag'):
datatype = lastPortDecl.children[1].text;
else:
datatype = None;
direction = lastPortDecl.children[0].text;
#TODO print(anytree.RenderTree(port));
#TODO print(port.children[0]);
ports.append( Port(name, direction=direction, datatype=datatype, dimensions=dimensions) );
return ports;
def process_files(parser: verible_verilog_syntax.VeribleVerilogSyntax, files: List[str]):
modules = [];
for f in files:
try:
data = parser.parse_file(f);
except verible_verilog_syntax.Error as e:
logging.error(f"Failed to parse {f}: {e}");
continue;
for module in data.tree.iter_find_all({"tag": "kModuleDeclaration"}):
name = module.find({"tag": "kModuleHeader"});
if name:
name = name.find({"tag": ["SymbolIdentifier", "EscapedIdentifier"]},iter_=anytree.PreOrderIter);
if name:
name = name.text;
if name:
logging.debug(f"[{name}]");
ports = get_ports(module);
if ports:
for port in ports:
logging.debug(f"\t{port}");
params = get_parameters(module);
if params:
for param in params:
logging.debug(f"\t# {param}");
insts = get_instances(module);
if insts:
for inst in insts:
logging.debug(f"\t[{inst}]");
modules.append( {'name':name, 'path':f, 'ports':ports, 'parameters':params, 'instances':insts} );
# add "is_leaf" attribute
for m in modules:
m['is_leaf'] = m['instances'] is None;
# add "is_root" attribute
for m in modules:
if 'is_root' not in m:
m['is_root'] = True;
if m['instances']:
for s in [sm for sm in modules if sm['name'] in m['instances']]:
s['is_root'] = False;
roots = ','.join([m['name'] for m in modules if m['is_root']]);
logging.debug(f"roots: {roots}");
return modules;
def node_depth(node: anytree.Node):
if not node:
return -1;
cnt = 0;
p = node.parent;
while p is not None:
cnt += 1;
p = p.parent;
return cnt;
def get_module_hierarchy(modules: List, root: str):
if not modules:
return None;
module = None;
for m in modules:
if m['name'] == root:
module = m;
break;
if not module:
return None;
mdict = {};
for m in modules:
mdict[m['name']] = m;
r = anytree.Node(module['name']);
nodes = [];
nodes.append( r );
while len(nodes) > 0:
n = nodes.pop(0);
# sanity check
if node_depth(n) > len(modules):
logging.error('Something wrong with module hierarchy!');
raise Exception('Too deep module hierarchy, cycle may exist.');
m = mdict[n.name];
if not m['is_leaf']:
l = [];
for i in m['instances']:
if i not in l:
l.append(i);
s = anytree.Node(i, parent=n);
nodes.append( s );
return r;
# returns module file paths in a reverse dependency order
def get_files_in_hierarchy(modules: List, root: str):
hierarchy = get_module_hierarchy(modules, root);
paths = [];
if hierarchy:
l = [node.name for node in anytree.PreOrderIter(hierarchy)];
l.reverse();
pdict = {};
for m in modules:
pdict[m['name']] = m['path'];
for n in l:
if n in pdict:
p = pdict[n];
if p not in paths:
paths.append(p);
del pdict[n];
return paths;
parser = argparse.ArgumentParser(description='Extracts SystemVerilog/Verilog module interface into IP-XACT 2014.');
parser.add_argument('-o', '--output', dest='output', required=False, type=pathlib.Path,
help='IP-XACT output file, stdout if not given.');
parser.add_argument('-m', '--module', dest='module', required=False, type=str,
help='Name of the root module.');
parser.add_argument('--xact', dest='xact', required=False, type=pathlib.Path,
help='IP-XACT 2014 to be updated with module information.')
parser.add_argument('--verible', dest='verible', required=False, type=pathlib.Path,
help='Path to `verible-verilog-syntax` binary.');
parser.add_argument('--xact-library', dest='library', required=False, type=str,
help='IP-XACT component library name.');
parser.add_argument('--xact-version', dest='version', required=False, type=str,
help='IP-XACT component version number.');
parser.add_argument('--xact-vendor', dest='vendor', required=False, type=str,
help='IP-XACT component vendor name.');
parser.add_argument('--rwd', dest='rwd', required=False, type=pathlib.Path,
help='Relative Working Directory (RWD), which to make file paths relative to. Applies only if `output` not specified.');
parser.add_argument('--log-level', dest='loglevel', required=False, type=str, default='ERROR',
help='Logging severity, one of: DEBUG, INFO, WARNING, ERROR, FATAL. Defaults to ERROR.');
parser.add_argument('-l', '--log-file', dest='logfile', required=False, type=pathlib.Path, default=None,
help='Path to a log file. Defaults to stderr if none given.');
parser.add_argument('files', type=pathlib.Path, nargs='+',
help='List of SystemVerilog/Verilog files to process.');
# parse CLI options
opts = parser.parse_args();
# default logging setup
logging.basicConfig(level=logging.ERROR);
# setup logging destination (file or stderr)
# (stderr is already set as default in the logging setup)
if opts.logfile is not None:
logFileHandler = None;
try:
# using `'w'` will make the FileHandler overwrite the log file rather than
# append to it
logFileHandler = logging.FileHandler(str(opts.logfile),'w');
except Exception as e:
logging.error(e);
if logFileHandler is not None:
rootLogger = logging.getLogger();
fmt = None;
if len(rootLogger.handlers) > 0:
fmt = rootLogger.handlers[0].formatter;
if fmt is not None:
logFileHandler.setFormatter(fmt);
rootLogger.handlers = []; # remove default handlers
rootLogger.addHandler(logFileHandler);
# setup logging level
try:
logging.getLogger().setLevel(opts.loglevel);
except Exception as e:
logging.error(e);
# `verible` parser binary
parser_path='verible-verilog-syntax';
if opts.verible:
parser_path = str(opts.verible);
# input SystemVerilog/Verilog file
file_paths = [str(f) for f in opts.files];
if opts.output:
outputDir = str(opts.output.parent);
elif opts.rwd:
outputDir = str(opts.rwd);
else:
outputDir = None;
# ElementTree namespaces for XML parsing
# (the proper IP-XACT/XML namespaces shall use `xmlns:` prefix to
# namespace names; however, ElementTree does not support it for
# `ElementTree.register_namespace()`.)
ns = {'xsi':"http://www.w3.org/2001/XMLSchema-instance",
'ipxact':"http://www.accellera.org/XMLSchema/IPXACT/1685-2014"
};
for p,u in ns.items():
logging.debug(f"registering namespace {p}:{u}");
et.register_namespace(p, u);
parser = verible_verilog_syntax.VeribleVerilogSyntax(executable=parser_path);
modules = process_files(parser, file_paths);
if len(modules) > 0:
module = None;
if opts.module:
for m in modules:
if m['name'] == opts.module:
module = m;
break;
if not module:
logging.error(f'Failed to find module \'{opts.module}\'!');
sys.exit(1);
else:
# use the first root module
roots = [m for m in modules if m['is_root']];
module = roots[0];
logging.debug(anytree.RenderTree( get_module_hierarchy(modules, module['name']) ));
xactns = {'xmlns:xsi':"http://www.w3.org/2001/XMLSchema-instance",
'xsi:schemaLocation':"http://www.accellera.org/XMLSchema/IPXACT/1685-2014 http://www.accellera.org/XMLSchema/IPXACT/1685-2014/index.xsd"
};
ns = XactNamespace();
comp = et.Element(ns.compileTag('component'), xactns);
# default XML element values (unless relevant options defined
# through CLI options)
defaults = {'version':'0.0.0', 'name':'manifest'};
for tag in ['vendor', 'library', 'name', 'version']:
e = et.SubElement(comp, ns.compileTag(tag));
# treat `name` element specifically
if tag == 'name':
e.text = module['name'];
continue;
if hasattr(opts,tag) and getattr(opts,tag) is not None:
e.text = str(getattr(opts,tag));
elif tag in defaults:
e.text = defaults[tag];
else:
e.text = tag;
model = et.SubElement(comp, ns.compileTag('model'));
views = et.SubElement(model, ns.compileTag('views'));
rtlView = et.SubElement(views, ns.compileTag('view'));
viewName = et.SubElement(rtlView, ns.compileTag('name'));
viewName.text = 'rtl';
compInstRef = et.SubElement(rtlView, ns.compileTag('componentInstantiationRef'));
compInstRef.text = viewName.text + '_implementation';
insts = et.SubElement(model, ns.compileTag('instantiations'));
compInst = et.SubElement(insts, ns.compileTag('componentInstantiation'));
instName = et.SubElement(compInst, ns.compileTag('name'));
instName.text = compInstRef.text;
if 'parameters' in module:
params = et.SubElement(compInst, ns.compileTag('moduleParameters'));
for param in module['parameters']:
params.append( param.etXact() );
instFileSetRef = et.SubElement(compInst, ns.compileTag('fileSetRef'));
instFileSetRef = et.SubElement(instFileSetRef, ns.compileTag('localName'));
instFileSetRef.text = viewName.text + '_files';
if 'ports' in module:
ports = et.SubElement(model, ns.compileTag('ports'));
for port in module['ports']:
ports.append( port.etXact() );
fileSets = et.SubElement(comp, ns.compileTag('fileSets'));
fileSet = et.SubElement(fileSets, ns.compileTag('fileSet'));
fileSetName = et.SubElement(fileSet, ns.compileTag('name'));
fileSetName.text = instFileSetRef.text;
for p in get_files_in_hierarchy(modules, module['name']):
f = pathlib.Path(p);
fileSetFile = et.SubElement(fileSet, ns.compileTag('file'));
fileSetFileName = et.SubElement(fileSetFile, ns.compileTag('name'));
if outputDir:
fileSetFileName.text = str(f.relative_to(outputDir));
else:
fileSetFileName.text = str(f.absolute());
fileSetFileType = et.SubElement(fileSetFile, ns.compileTag('fileType'));
fileExt = f.suffix;
if fileExt:
if fileExt == 'v' or fileExt == 'vh':
fileSetFileType.text = 'verilogSource';
else:
fileSetFileType.text = 'systemVerilogSource';
else:
fileSetFileType.text = 'systemVerilogSource';
_pretty_print(comp);
tree = et.ElementTree(comp);
if opts.output:
with open(str(opts.output), 'w') as f:
tree.write(f, encoding='unicode', xml_declaration=True);
else:
tree.write(sys.stdout, encoding='unicode', xml_declaration=True);
# Copyright 2023 Tomas Brabec
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import sys
import pathlib
import anytree
import logging
import argparse
import xml.etree.ElementTree as et
from typing import Iterable, Optional, List
# https://stackoverflow.com/a/65808327
def _pretty_print(current, parent=None, index=-1, depth=0, indent=' '):
for i, node in enumerate(current):
_pretty_print(node, current, i, depth + 1, indent)
if parent is not None:
if index == 0:
parent.text = '\n' + (indent * depth)
else:
parent[index - 1].tail = '\n' + (indent * depth)
if index == len(parent) - 1:
current.tail = '\n' + (indent * (depth - 1))
class XactNamespace(object):
ns = {'ipxact':"http://www.accellera.org/XMLSchema/IPXACT/1685-2014"};
def __init__(self, ns = None):
self.ns = ns;
def compileTag(self, tag:str, prefix:str = 'ipxact'):
ns = self.ns or XactNamespace.ns;
if ns and prefix is not None:
if prefix in ns:
tag = f'{{{ns[prefix]}}}{tag}';
else:
logging.error(f"Unregistered namespace prefix (geistered: {''.join([i for i in ns])}): {prefix}");
tag = prefix + ':' + tag;
elif ns and prefix is None and len(ns) == 1:
# when a sole namespace registered, then use it
prefix = list(ns.values())[0];
tag = f'{{{prefix}}}{tag}';
elif prefix is not None:
tag = prefix + ':' + tag;
return tag;
def strip_tag(element: et.Element):
if element is None:
return None;
_, _, tag = element.tag.rpartition('}');
return tag;
class Vlnv(object):
attrs = ['vendor', 'library', 'name', 'version'];
def __init__(self, **kwargs):
for a in Vlnv.attrs:
if kwargs is not None and a in kwargs:
setattr(self,a,kwargs[a]);
else:
setattr(self,a,None);
def __str__(self):
l = [];
for a in Vlnv.attrs:
l.append(f'{a}={getattr(self,a)}');
return ', '.join(l);
def __eq__(self, other):
if other is None or not isinstance(other,Vlnv):
return False;
else:
res = True;
for a in Vlnv.attrs:
res = res and (getattr(self,a) == getattr(other,a));
return res;
def isComplete(self):
return len([a for a in Vlnv.attrs if getattr(self,a) == None]) == 0;
def toList(self):
return [getattr(self,a) for a in Vlnv.attrs];
def toDict(self):
return {a: getattr(self,a) for a in Vlnv.attrs};
@classmethod
def fromElements(cls, element: et.Element):
if element is None:
return None;
vlnv = {};
for tag in Vlnv.attrs:
fulltag = 'ipxact:'+tag;
value = None;
for e in element:
if tag != strip_tag(e):
continue;
else:
value = e.text;
break;
if value is None:
logging.error(f'Missing `{fulltag}` VLNV element in {strip_tag(element)} element!');
vlnv[tag] = value;
return Vlnv(**vlnv);
@classmethod
def fromAttributes(cls, element: et.Element):
if element is None:
return None;
vlnv = {};
for tag in Vlnv.attrs:
value = None;
if tag in element.attrib:
value = element.attrib[tag];
else:
logging.error(f'Missing `{tag}` VLNV attribute in {strip_tag(element)} element!');
vlnv[tag] = value;
return Vlnv(**vlnv);
def xact_add_components(tree, files:List[pathlib.Path], outputDir:str = None):
if tree is None:
return;
catalog = tree.getroot();
trees = [];
for f in files:
try:
tree = et.parse(str(f));
trees.append([tree,f]);
except et.ParseError as e:
logging.error(f"Failed to parse {f}: {e}");
if len(trees) == 0:
return;
ns = XactNamespace();
components = catalog.find('ipxact:components',XactNamespace.ns);
elemseq = ['vendor', 'library', 'name', 'version',
'description', 'catalogs'
'busDefinitions', 'abstractionDefinitions', 'components',
'abstractors', 'designs', 'designConfigurations',
'generatorChains',
'vendorExtensions'];
for [comptree,path] in trees:
comp = comptree.getroot();
if comp is None or comp.tag != ns.compileTag('component'):
logging.error(f'Expecting `ipxact:component` root in {path}: {comp.tag}');
continue;
vlnv = Vlnv.fromElements(comp);
logging.debug(f'{path} vlnv: {vlnv}');
# skip adding a new element if not all VLNV defined
if not vlnv.isComplete():
logging.error(f'Missing complete VLNV information in {path}!');
continue;
# create new `components` element (if needed)
if components is None:
logging.warning(f'No `ipxact:components` element found!');
components = et.Element(ns.compileTag('components'));
predecesors = elemseq[:elemseq.index('components')];
inserted = False;
for i,e in enumerate(catalog):
tag = strip_tag(e);
if tag not in predecesors:
catalog.insert(i,components);
inserted = True;
break;
if not inserted: catalog.append(components);
# check if component already registered
comp = None;
for i,e in enumerate(components):
# sanity check of the components sub-element type
tag = strip_tag(e);
if tag != 'ipxactFile':
logging.error(f'Unexpected element under `ipxact:components`: {tag}');
continue;
# get `vlnv` element
evlnv = e.find(ns.compileTag('vlnv'), ns.ns);
# check vlnv
if evlnv is not None and vlnv == Vlnv.fromAttributes(evlnv):
logging.error(f'Component already regoistered: {path}');
comp = e;
break;
# add new component
if comp is None:
comp = et.Element(ns.compileTag('ipxactFile'));
e = et.Element(ns.compileTag('vlnv'), **vlnv.toDict());
comp.append(e);
e = et.Element(ns.compileTag('name'));
if outputDir:
e.text = str(path.relative_to(outputDir));
else:
e.text = str(path.absolute());
comp.append(e);
components.append(comp);
return;
parser = argparse.ArgumentParser(description='Creates or adds to references to IP-XACT 2014 components into IP-XACT 2014 catalog.');
parser.add_argument('-o', '--output', dest='output', required=False, type=pathlib.Path,
help='IP-XACT output file, stdout if not given.');
#TODO parser.add_argument('-m', '--module', dest='module', required=False, type=str,
#TODO help='Name of the root module.');
parser.add_argument('--xact', dest='xact', required=False, type=pathlib.Path,
help='IP-XACT 2014 catalog to be updated with component refereces.')
parser.add_argument('--xact-library', dest='library', required=False, type=str,
help='IP-XACT catalog library name.');
parser.add_argument('--xact-version', dest='version', required=False, type=str,
help='IP-XACT catalog version number.');
parser.add_argument('--xact-vendor', dest='vendor', required=False, type=str,
help='IP-XACT catalog vendor name.');
parser.add_argument('--xact-name', dest='name', required=False, type=str, default='mainfest',
help='IP-XACT catalog name.');
parser.add_argument('--xact-description', dest='description', required=False, type=str, default='mainfest',
help='IP-XACT catalog/library description.');
parser.add_argument('--rwd', dest='rwd', required=False, type=pathlib.Path,
help='Relative Working Directory (RWD), which to make file paths relative to. Applies only if `output` not specified.');
parser.add_argument('--log-level', dest='loglevel', required=False, type=str, default='ERROR',
help='Logging severity, one of: DEBUG, INFO, WARNING, ERROR, FATAL. Defaults to ERROR.');
parser.add_argument('-l', '--log-file', dest='logfile', required=False, type=pathlib.Path, default=None,
help='Path to a log file. Defaults to stderr if none given.');
parser.add_argument('files', type=pathlib.Path, nargs='+',
help='List of IP-XACT 2014 component files to be added to the catalog.');
# parse CLI options
opts = parser.parse_args();
# default logging setup
logging.basicConfig(level=logging.ERROR);
# setup logging destination (file or stderr)
# (stderr is already set as default in the logging setup)
if opts.logfile is not None:
logFileHandler = None;
try:
# using `'w'` will make the FileHandler overwrite the log file rather than
# append to it
logFileHandler = logging.FileHandler(str(opts.logfile),'w');
except Exception as e:
logging.error(e);
if logFileHandler is not None:
rootLogger = logging.getLogger();
fmt = None;
if len(rootLogger.handlers) > 0:
fmt = rootLogger.handlers[0].formatter;
if fmt is not None:
logFileHandler.setFormatter(fmt);
rootLogger.handlers = []; # remove default handlers
rootLogger.addHandler(logFileHandler);
# setup logging level
try:
logging.getLogger().setLevel(opts.loglevel);
except Exception as e:
logging.error(e);
# output directory
# (`None` means to use absolute paths)
if opts.output:
outputDir = str(opts.output.parent);
elif opts.rwd:
outputDir = str(opts.rwd);
else:
outputDir = None;
# ElementTree namespaces for XML parsing
# (the proper IP-XACT/XML namespaces shall use `xmlns:` prefix to
# namespace names; however, ElementTree does not support it for
# `ElementTree.register_namespace()`.)
ns = {'xsi':"http://www.w3.org/2001/XMLSchema-instance",
'ipxact':"http://www.accellera.org/XMLSchema/IPXACT/1685-2014"
};
for p,u in ns.items():
logging.debug(f"registering namespace {p}:{u}");
et.register_namespace(p, u);
ns = XactNamespace();
tree = None;
if opts.xact:
try:
tree = et.parse(str(opts.xact));
except et.ParseError as e:
logging.error(f"Failed to parse {opts.xact}: {e}");
sys.exit(1);
if not tree:
# proper IP-XACT 2014 XML namespaces
xactns = {'xmlns:xsi':"http://www.w3.org/2001/XMLSchema-instance",
'xsi:schemaLocation':"http://www.accellera.org/XMLSchema/IPXACT/1685-2014 http://www.accellera.org/XMLSchema/IPXACT/1685-2014/index.xsd"
};
# new root element
catalog = et.Element(ns.compileTag('catalog'), xactns);
# default XML element values (unless relevant options defined
# through CLI options)
defaults = {'version':'0.0.0', 'name':'manifest'};
for tag in ['vendor', 'library', 'name', 'version', 'description']:
e = et.SubElement(catalog, ns.compileTag(tag));
if hasattr(opts,tag) and getattr(opts,tag) is not None:
e.text = str(getattr(opts,tag));
elif tag in defaults:
e.text = defaults[tag];
else:
e.text = tag;
tree = et.ElementTree(catalog);
else:
# test if root is an ipxact component
catalog = tree.getroot();
if catalog is None or catalog.tag != ns.compileTag('catalog'):
logging.error(f'Expecting `ipxact:catalog` root in {opts.xact}: {catalog.tag}');
sys.exit(1);
# sanity check for required VLNV elements
for i,tag in enumerate(['vendor','library','name','version']):
fulltag = 'ipxact:'+tag;
elem = catalog.find(fulltag, XactNamespace.ns);
if elem is None:
elem = et.Element(ns.compileTag(tag));
catalog.insert(i,elem);
if hasattr(opts,tag) and getattr(opts,tag) is not None:
logging.warning(f'Missing `{fulltag}` element in {opts.xact}!');
elem.text = getattr(opts,tag);
else:
logging.error(f'Missing `{fulltag}` element in {opts.xact}!');
elem.text = tag;
elif hasattr(opts,tag):
attr = getattr(opts,tag);
if attr is not None and attr != elem.text:
logging.error(f'User `{fulltag}` element `{attr}` not match `{elem.text}` in {opts.xact}');
tree = et.ElementTree(catalog);
# add description (if defined)
if opts.description:
description = catalog.find('ipxact:description', XactNamespace.ns);
if description is None:
logging.warning(f'No `ipxact:description` element found in catalog!');
description = et.Element(ns.compileTag('description'));
predecesors = ['vendor','library','name','version'];
inserted = False;
for i,e in enumerate(tree.getroot()):
tag = strip_tag(e);
if tag not in predecesors:
tree.getroot().insert(i,description);
inserted = True;
break;
if not inserted: tree.getroot().append(description);
description.text = opts.description;
# add new IP-XACT view
xact_add_components( tree, opts.files, outputDir );
# reformat XML
_pretty_print(tree.getroot());
# print XML
if opts.output:
with open(str(opts.output), 'w') as f:
tree.write(f, encoding='unicode', xml_declaration=True);
else:
tree.write(sys.stdout, encoding='unicode', xml_declaration=True);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment