Source code for chiptools.parsers.xml_project

import logging
import traceback
import xml
import os
import time

from xml.dom import minidom

from chiptools.common import utils
from chiptools.common.filetypes import ProjectAttributes

log = logging.getLogger(__name__)


[docs]class XmlProjectParser: """ The XmlProjectParser class implements an XML parser for project files using the following format. .. note:: All paths appearing in a project file are relative to the location of the project file. **<project>** The project parent tag encapsulates all configuration and file tags belonging to a project file. Existing project files may be imported into a project by including a project tag with the *path* attribute pointing to the existing XML file. +-----------+--------+----------------------------------------------------+ | Attribute | Value | Description | +===========+========+====================================================+ | synthesise| True | (default) These files are included for synthesis. | | +--------+----------------------------------------------------+ | | False | Exclude these files from synthesis. | +-----------+--------+----------------------------------------------------+ | path |*string*| Path to existing project file to include. | +-----------+--------+----------------------------------------------------+ **<library>** The *library* tag is used to group all child file tags into the same library. If a file is not associated with a library it will default to the *work* library. +-----------+--------+----------------------------------------------------+ | Attribute | Value | Description | +===========+========+====================================================+ | synthesise| True | (default) These files are included for synthesis. | | +--------+----------------------------------------------------+ | | False | Exclude these files from synthesis. | +-----------+--------+----------------------------------------------------+ | name |*string*| (required) Name of the HDL library for these files.| +-----------+--------+----------------------------------------------------+ **<file>** The *file* tag is used to define a source file to include in the project. Source files can either be text based HDL source files (VHDL or Verilog) or they can be netlists. Tool wrapper plugins will check the file extension to determine how they should process the file, for example .VHD and .V files will be processed with vcom and vlog respectively by Modelsim and .ngc files will be copied into the synthesis folder by ISE. +-----------+--------+----------------------------------------------------+ | Attribute | Value | Description | +===========+========+====================================================+ | synthesise| True | (default) This file is included for synthesis. | | +--------+----------------------------------------------------+ | | False | Exclude this file from synthesis. | +-----------+--------+----------------------------------------------------+ | path |*string*| (required) Path to the source file. | +-----------+--------+----------------------------------------------------+ .. note:: If a file tag is used outside of a library tag the file will automatically be associated with the *work* library and a warning will be displayed. .. note:: File tags support additional optional attributes of the form *args_toolname_compile* where *toolname* is the name of a specific tool wrapper (for example: *modelsim*). The attribute value is passed to the simulation tool during compilation if it is the selected tool. *args_modelsim_compile='-2008'* would pass the command line arg '-2008' to Modelsim when it compiles the file. **<constraints>** The *constraints* tag defines the path to a constraints file to be included when performing synthesis on the project. Constraints can be limited to a specific synthesis tool via use of the *flow* attribute. +-----------+--------+----------------------------------------------------+ | Attribute | Value | Description | +===========+========+====================================================+ | path |*string*| (required) Path to the constraints file. | +-----------+--------+----------------------------------------------------+ | flow |*string*| (optional) Name of the associated synthesis tool. | +-----------+--------+----------------------------------------------------+ **<unittest>** The *unittest* tag defines the path to a Python based unit test suite to be included in the project test suite. Unit tests must be valid Python files with a .py extension. If the file is invalid or contains syntax errors it will be excluded from the project test suite. Runtime errors occurring from a unit test will result in that test failing. +-----------+--------+----------------------------------------------------+ | Attribute | Value | Description | +===========+========+====================================================+ | path |*string*| (required) Path to the unit test file. | +-----------+--------+----------------------------------------------------+ **<generic>** The *generic* tag defines a generic value setting for the top level entity during synthesis. Generic attribute names map to the name of a generic on the top level entity and the associated value is passed as the generic value. +----------+-------+------------------------------------------------------+ | Attribute| Value | Description | +==========+=======+======================================================+ | (name) |(value)| Set top level generic *name* to *value* at synthesis.| +----------+-------+------------------------------------------------------+ **<config>** The *config* tag defines a config value setting for the project. Config attribute names map to the name of a configuration item in the project and the associated value is passed as the config value. +----------+-------+------------------------------------------------------+ | Attribute| Value | Description | +==========+=======+======================================================+ | (name) |(value)| Set the configuration item *name* to *value*. | +----------+-------+------------------------------------------------------+ The following configuration items can be set in a project: +----------------------+--------------------------------------------------+ | Config | Description | +======================+==================================================+ | simulation_directory | Directory to use as simulation working directory.| +----------------------+--------------------------------------------------+ | synthesis_directory | Directory to use as synthesis working directory. | +----------------------+--------------------------------------------------+ | simulator | Default simulator to use for this project. | +----------------------+--------------------------------------------------+ | synthesiser | Default synthesiser to use for this project. | +----------------------+--------------------------------------------------+ | part | FPGA part to target when performing synthesis. | +----------------------+--------------------------------------------------+ In addition to the above configuration items, the *config* tag also allows tool-specific argument passing through the use of config attributes using the following naming convention: *args_toolname_flowname*, where *toolname* is the name of the tool to target and *flowname* is the name of a specific tool flow stage. For example: args_ise_par='-mt 4 -ol high -xe n' would pass the arguments *-mt 4 -ol high -xe n* to the place and route stage of an ISE synthesis flow. Each tool wrapper implements its own specific flow stage names. .. note:: If a configuration item is already defined any new definitions will be ignored. A warning will be displayed if a redefinition is attempted. """ @staticmethod def load_project(path, project_object): """ Load the project XML file provided by the *path*. """ log.info('Loading project: ' + str(path)) project_object.initialise() base_name = os.path.basename(path).split('.')[0] cache_path = os.path.join( os.path.dirname(path), '.' + base_name ) project_object.set_cache_path(cache_path) # Parse the project file XmlProjectParser.parse_project(path, project_object) @staticmethod def parse_project( filepath, project_object, synthesise=None ): """Parse the XML project and update the project_dictionary or return a new dictionary if one is not supplied. """ log.info('Parsing: ' + str(filepath) + ' synthesis=' + str(synthesise)) start_time = time.time() project_root = os.path.dirname(os.path.realpath(filepath)) try: xml_obj = minidom.parse(filepath) for project_node in xml_obj.getElementsByTagName( ProjectAttributes.XML_NODE_PROJECT ): # Project attributes (if any) # If this whole node should not be synthesised, ignore any # child flags otherwise get the child synthesis flag and use # that. if synthesise is None: project_attribs = ProjectAttributes.process_attributes( project_node.attributes, project_root ) synthesis_enabled = project_attribs.get( ProjectAttributes.ATTRIBUTE_SYNTHESIS, None ) else: synthesis_enabled = synthesise for child in project_node.childNodes: if child.nodeName == ProjectAttributes.XML_NODE_PROJECT: attribs = ProjectAttributes.process_attributes( child.attributes, project_root, defaults=ProjectAttributes.PROJECT_NODE_DEFAULTS ) # If this whole node should not be synthesised, ignore # any child flags otherwise get the child synthesis # flag and use that. if synthesis_enabled is None: synthesise = attribs.get( ProjectAttributes.ATTRIBUTE_SYNTHESIS, None ) else: synthesise = synthesis_enabled if ProjectAttributes.ATTRIBUTE_PATH in attribs: log.debug( 'Found sub-project: ' + str( attribs[ ProjectAttributes.ATTRIBUTE_PATH ] ) ) # Recursively call this parser with the new project # path XmlProjectParser.parse_project( str( attribs[ ProjectAttributes.ATTRIBUTE_PATH ] ), project_object, synthesise ) elif child.nodeName == ProjectAttributes.XML_NODE_CONFIG: XmlProjectParser._add_config( child, project_root, project_object ) elif child.nodeName == ProjectAttributes.XML_NODE_LIBRARY: XmlProjectParser._add_library( child, project_root, project_object, synthesis_enabled ) elif child.nodeName == ( ProjectAttributes.XML_NODE_CONSTRAINTS ): XmlProjectParser._add_constraints( child, project_root, project_object, ) elif child.nodeName == ( ProjectAttributes.XML_NODE_UNITTEST ): XmlProjectParser._add_unittest( child, project_root, project_object, ) elif child.nodeName == ProjectAttributes.XML_NODE_GENERIC: # Build a dictionary of generics using the attribute # name and value attribs = child.attributes if attribs is None: continue attribs = dict(attribs.items()) for attrName, attrVal in attribs.items(): project_object.add_generic( attrName, attrVal ) elif child.nodeName == ProjectAttributes.XML_NODE_FILE: # Files should not be left unassociated with a library # unless you wish to add all files to the work library. # The default behavior will be to add parentless files # to the work library, but a configuration option could # make this post an error instead. log.warning( 'Found file with no parent library, ' + 'defaulting to work library' ) # If this whole node should not be synthesised, ignore # any child flags otherwise get the child synthesis # flag and use that. if synthesis_enabled is None: synthesise = ( ProjectAttributes.get_processed_attribute( child.attributes.get( ProjectAttributes.ATTRIBUTE_SYNTHESIS, None ), project_root, ProjectAttributes.ATTRIBUTE_SYNTHESIS ) ) else: synthesise = synthesis_enabled XmlProjectParser._add_file( child, 'work', project_root, project_object, synthesise=synthesise ) elif child.nodeName == ProjectAttributes.XML_NODE_TEXT: pass elif child.nodeName == ProjectAttributes.XML_NODE_COMMENT: pass except xml.parsers.expat.ExpatError: log.error( 'Error found in XML file, check the formatting. ' + 'Refer to the traceback below for the line number and file.' ) log.error(traceback.format_exc()) project_object.initialise() return log.debug(filepath + ' parsed in ' + utils.time_delta_string( start_time, time.time()) ) @staticmethod def _add_config(child, root, project_object): """Process and add all child attributes to the given configuration dict. Return a reference to the modified dict. """ config = {} for k, v in child.attributes.items(): if v is not None: config[k] = v project_object.add_config_dict(**config) @staticmethod def _add_file( file_node, library_name, root, project_object, synthesise ): """Add the given file to the given library and ensure that any relative file paths are correctly converted into absolute paths using the project_root as a reference""" attribs = ProjectAttributes.process_attributes( file_node.attributes, root, defaults=ProjectAttributes.FILE_NODE_DEFAULTS ) if attribs[ProjectAttributes.ATTRIBUTE_PATH] is not None: # Override the file synthesis flag if the library is marked for # exclusion from synthesis if synthesise is not None: attribs[ProjectAttributes.ATTRIBUTE_SYNTHESIS] = synthesise # Path is passed directly, so remove it from kwargs path = attribs[ProjectAttributes.ATTRIBUTE_PATH] del attribs[ProjectAttributes.ATTRIBUTE_PATH] project_object.add_file( path=path, library=library_name, **attribs ) else: log.warning('Ignoring file with no path.') @staticmethod def _add_library( child, root, project_object, synthesise, ): """Process the given library node and add it to the project_dictionary. Any files containedwithin the library will be added to the project_dictionary under that library""" attribs = ProjectAttributes.process_attributes( child.attributes, root, defaults=ProjectAttributes.LIBRARY_NODE_DEFAULTS ) if attribs[ProjectAttributes.ATTRIBUTE_NAME] is None: log.warning('Ignoring library with no name specified') return library_name = attribs[ProjectAttributes.ATTRIBUTE_NAME] if synthesise is None: synthesise = attribs[ProjectAttributes.ATTRIBUTE_SYNTHESIS] # Add all files in this library node to the project for file_node in filter( lambda x: x.nodeName == ProjectAttributes.XML_NODE_FILE, child.childNodes ): XmlProjectParser._add_file( file_node, library_name, root, project_object, synthesise, ) @staticmethod def _add_constraints(child, root, project_object): attribs = ProjectAttributes.process_attributes( child.attributes, root, defaults=ProjectAttributes.CONSTRAINTS_NODE_DEFAULTS ) path = attribs[ProjectAttributes.ATTRIBUTE_PATH] # Path is passed separately, so delete it from the kwargs dict. del attribs[ProjectAttributes.ATTRIBUTE_PATH] project_object.add_constraints( path, **attribs ) @staticmethod def _add_unittest(child, root, project_object): attribs = ProjectAttributes.process_attributes( child.attributes, root, defaults=ProjectAttributes.UNITTEST_NODE_DEFAULTS ) path = attribs[ProjectAttributes.ATTRIBUTE_PATH] # Path is passed separately, so delete it from the kwargs dict. del attribs[ProjectAttributes.ATTRIBUTE_PATH] project_object.add_unittest( path, **attribs )