def __write_asterics_core__( self, path: str, use_symlinks: bool = True, allow_deletion: bool = False, module_driver_dirs: bool = False, ) -> bool: path = self.__check_and_get_output_path__(path) if not path: return False try: as_build.prepare_output_path(None, path, allow_deletion) except IOError as err: LOG.error( ("Could not prepare the output directory '%s'" "! - '%s'"), path, str(err), ) return False try: self.__write_hw__( append_to_path(path, "hardware"), use_symlinks, allow_deletion ) self.__write_sw__( append_to_path(path, "software"), use_symlinks, allow_deletion, module_driver_dirs, ) except (IOError, AsError) as err: LOG.error(str(err)) return False return True
def __write_system__( self, path: str, use_symlinks: bool = True, allow_deletion: bool = False, module_driver_dirs: bool = False, add_vears: bool = False, ): # Make sure path is good path = self.__check_and_get_output_path__(path) ip_path = append_to_path(path, self.IP_CORE_REL_PATH) if not path: return False # Clean up if not empty try: src_path = append_to_path(self.asterics_dir, self.SYSTEM_TEMPLATE_PATH) as_build.prepare_output_path(src_path, path, allow_deletion) except IOError as err: LOG.error( ("Could not prepare the output directory '%s'" "! - '%s'"), path, str(err), ) return False # Get paths for HW and SW gen sw_path = append_to_path(ip_path, self.DRIVERS_REL_PATH) hw_path = append_to_path(ip_path, self.HW_SRC_REL_PATH) # Generate and collect source files try: self.__gen_sw__(sw_path, use_symlinks, module_driver_dirs) self.__gen_hw__(hw_path, use_symlinks) except (IOError, AsError) as err: LOG.error(str(err)) return False # Run packaging LOG.info("Running packaging in '%s'.", ip_path) add_c1 = self.TCL_ADDITIONS.format( design=self.design_name, part=self.partname_hw, board=self.board_target, display_name=self.ipcore_name, description=self.ipcore_descr, ) as_build.run_vivado_packaging(self.current_chain, ip_path, add_c1) # Add VEARS core if add_vears: try: as_build.add_vears_core( append_to_path(path, "vivado_cores"), self.asterics_dir, use_symlinks, ) except (IOError, AsError) as err: LOG.error(str(err)) return False return True
def _write_ip_core_xilinx( self, path: str, use_symlinks: bool = True, allow_deletion: bool = False, module_driver_dirs: bool = False, ): err_mgr = self.current_chain.err_mgr # Make sure path is good path = self._check_and_get_output_path(path) if not path: return False # Clean up if not empty try: src_path = append_to_path(self.asterics_home, self.IP_CORE_TEMPLATE_PATH) as_build.prepare_output_path(src_path, path, allow_deletion) except IOError as err: LOG.error( ("Could not prepare the output directory '%s'" "! - '%s'"), path, str(err), ) return False # Get paths for HW and SW gen sw_path = append_to_path(path, self.DRIVERS_REL_PATH) hw_path = append_to_path(path, self.HW_SRC_REL_PATH) # Generate and collect source files try: self._gen_sw(sw_path, use_symlinks, module_driver_dirs) if err_mgr.has_errors(): LOG.critical("Abort! Errors occurred during system build:") err_mgr.print_errors() return False self._gen_hw(hw_path, use_symlinks) if err_mgr.has_errors(): LOG.critical("Abort! Errors occurred during system build:") err_mgr.print_errors() return False except (IOError, AsError) as err: LOG.error(str(err)) return False add_c1 = self.TCL_ADDITIONS.format( design=self.design_name, part=self.partname_hw, board=self.board_target, display_name=self.ipcore_name, description=self.ipcore_descr, ) # Run packaging LOG.info("Running packaging in '%s'.", path) try: as_build.run_vivado_packaging(self.current_chain, path, add_c1) except (IOError, AsError) as err: LOG.error(str(err)) return False return True
def run_vivado_packaging(chain: AsProcessingChain, output_path: str, tcl_additions: str = ""): """! @brief Write the necessary TCL fragments and run the TCL packaging script. Requires Vivado to be installed and in callable from the current terminal. Parameters: chain: The current processing chain. output_path: The root of the output folder structure. No return value.""" # Write the necessary tcl fragments write_vivado_package_tcl(chain, output_path, tcl_additions) # Clean path path = append_to_path(os.path.realpath(output_path), "/") # Input path to launch string launch_string = VIVADO_TCL_COMMANDLINE_TEMPLATE.format( automatics_home + "/", path) cwd = os.getcwd() LOG.info("Running Vivado IP-Core packaging...") try: # Move to output path os.chdir(output_path) # Launch Vivado there, packaging the IP Core os.system(launch_string) # Move back home os.chdir(cwd) except Exception as err: LOG.critical("Packaging via Vivado has failed!\nError: '%s'", str(err)) raise err # Cleanup ooc_runs_present = any( (isinstance(mod, AsModuleWrapper) for mod in chain.modules)) if not ooc_runs_present: for target_suf in HOUSE_CLEANING_LIST_VIVADO: target = append_to_path(output_path, target_suf, False) # Choose remove function if os.path.isdir(target): rm_op = rmtree else: rm_op = os.unlink # Try to remove file/directory try: rm_op(target) except FileNotFoundError: pass # This is fine, we wanted it gone anyways ;) except IOError as err: LOG.warning( "Packaging: Can't remove temporary file(s) '%s' - %s.", target, str(err), ) else: LOG.warning( "Vivado Project including Out-of-Context runs generated to '{}'.". format(output_path)) LOG.info("Packaging complete!")
def write_config_hc(chain, output_path: str): """! @brief Write the files 'as_config.[hc]' The files contain the build date, version string and configuration macros.""" LOG.info("Generating as_config.c source file...") # Fetch and format todays date date_string = datetime.today().strftime("%Y-%m-%d") version_string = chain.parent.version hashstr = "'" + "', '".join(chain.get_hash()) + "'" filepath = append_to_path(output_path, AS_CONFIG_C_NAME, add_trailing_slash=False) try: with open(filepath, "w") as file: file.write( AS_CONFIG_C_TEMPLATE.format( header=ASTERICS_HEADER_SW.format( filename=AS_CONFIG_C_NAME, description=AS_CONFIG_C_DESCRIPTION, ), hashstr=hashstr, date=date_string, version=version_string, )) except IOError as err: LOG.error("Could not write '%s'! '%s'", AS_CONFIG_C_NAME, str(err)) raise AsFileError( filepath, detail=str(err), msg="Could not write to file", ) LOG.info("Generating as_config.h source file...") filepath = append_to_path(output_path, AS_CONFIG_H_NAME, add_trailing_slash=False) try: with open(filepath, "w") as file: file.write( AS_CONFIG_H_TEMPLATE.format(header=ASTERICS_HEADER_SW.format( filename=AS_CONFIG_H_NAME, description=AS_CONFIG_H_DESCRIPTION, ))) except IOError as err: LOG.error("Could not write '%s'! '%s'", AS_CONFIG_H_NAME, str(err)) raise AsFileError( filepath, detail=str(err), msg="Could not write to file", )
def __write_sw__( self, path: str, use_symlinks: bool = True, allow_deletion: bool = False, module_driver_dirs: bool = False, ): # Make sure path is good path = self.__check_and_get_output_path__(path) if not path: return False path = append_to_path(path, "/") # Clean up if not empty try: as_build.prepare_output_path(None, path, allow_deletion) except IOError as err: LOG.error( ("Could not prepare the output directory '%s'" "! - '%s'"), path, str(err), ) return False # Generate and collect software files try: self.__gen_sw__(path, use_symlinks, module_driver_dirs) except (IOError, AsError) as err: LOG.error(str(err)) return False return True
def __get_modules_from_dir__(cls, module_dir: str) -> Sequence[AsModule]: # Make sure the module path is valid syntactically and ends in a "/" module_dir = append_to_path(module_dir, "/") module_list = [] # Get all module initialization scripts script_list = cls.__get_module_scripts_in_dir__(module_dir) for script in script_list: module_folder = script[0] script_path = script[1] script_name = script_path.rsplit("/", maxsplit=1)[-1] # For each valid script: run the contained function # 'get_module_instance' LOG.debug("Modlib importing script '%s' ...", script_name) # Get Python module spec spec = importutil.spec_from_file_location(script_name, script_path) # Get Python module and run / load it imported_script = importutil.module_from_spec(spec) spec.loader.exec_module(imported_script) LOG.debug("Modlib calls 'get_module_inst' of script '%s'", script_name) # Execute the function "get_module_instance" module_inst = imported_script.get_module_instance(module_folder) LOG.debug("Modlib received '%s' from script '%s'", str(module_inst), script_name) # If the output is an AsModule, add it to the library if isinstance(module_inst, AsModule): # Add the module source dir, making sure it module_inst.module_dir = module_folder module_list.append(module_inst) return module_list
def write_module_group_vhd(self, folder: str, module_group: AsModuleGroup): """! @brief Generate the VHDL file for a module group (AsModuleGroup)""" LOG.info("Writing ASTERICS module group file '%s'.", module_group.name) filename = "{}.vhd".format(module_group.name) outfile = as_help.append_to_path(folder, filename, add_trailing_slash=False) # Generate the list of glue signals self.generate_glue_signal_strings(module_group.signals) header = self._generate_header_and_library_string(module_group) vhdl_list = [] # Generate the module's entity declaration vhdl_list.extend(self._generate_entity(module_group, None)) # Generate the toplevel architecture vhdl_list.extend( self._generate_module_group_architecture(module_group, None)) # Open the output file with open(outfile, "w") as ofile: # Make sure we can write to the file if not ofile.writable(): raise AsFileError(msg="File not writable", filename=filename) ofile.write(header) vhdl_write.write_list_to_file(vhdl_list, ofile) # Reset to init state self.clear_lists()
def __write_hw__( self, path: str, use_symlinks: bool = True, allow_deletion: bool = False ): # Make sure path is good opath = self.__check_and_get_output_path__(path) if not opath: raise AsFileError(path, "Could not create output folder!") opath = append_to_path(opath, "/") # Clean up if not empty try: as_build.prepare_output_path(None, opath, allow_deletion) except IOError as err: LOG.error( ("Could not prepare the output directory '%s'" "! - '%s'"), opath, str(err), ) raise AsFileError(opath, "Could not write to output folder!") # Generate and collect hardware files try: self.__gen_hw__(opath, use_symlinks) except IOError: LOG.error( ("Cannot write to '%s'! - " "Could not create VHDL source files!"), opath, ) return False except AsError as err: LOG.error(str(err)) return False
def __get_modules_from_dir__(cls, module_dir: str) -> Sequence[AsModule]: # Make sure the module path is valid syntactically and ends in a "/" module_dir = append_to_path(module_dir, "/") module_list = [] # Get all module initialization scripts script_list = cls.__get_module_scripts_in_dir__(module_dir) for script in script_list: module_folder = script[0] script_path = script[1] script_name = script_path.rsplit("/", maxsplit=1)[-1] # For each valid script: run the contained function # 'get_module_instance' LOG.debug("Modlib importing script '%s' ...", script_name) # Get Python module spec spec = importutil.spec_from_file_location(script_name, script_path) # Get Python module and run / load it imported_script = importutil.module_from_spec(spec) spec.loader.exec_module(imported_script) LOG.debug("Modlib calls 'get_module_inst' of script '%s'", script_name) # Execute the function "get_module_instance" module_inst = imported_script.get_module_instance(module_folder) LOG.debug( "Modlib received '%s' from script '%s'", str(module_inst), script_name, ) # If the output is an AsModule, add it to the library if isinstance(module_inst, AsModule): # Add the module source dir, making sure it module_inst.module_dir = module_folder module_list.append(module_inst) # Discover driver files for this module: # If this module already has files manually assigned, # don't scan default location if not module_inst.driver_files: # Find files in default location module_inst.driver_files = get_software_drivers_from_dir( append_to_path(module_folder, cls.DRIVER_FOLDER)) else: # If files are manually assigned, normalize path format module_inst.driver_files = [ os.path.realpath(df) for df in module_inst.driver_files ] return module_list
def _write_asterics_core( self, path: str, use_symlinks: bool = True, allow_deletion: bool = False, module_driver_dirs: bool = False, ) -> bool: err_mgr = self.current_chain.err_mgr path = self._check_and_get_output_path(path) if not path: return False try: as_build.prepare_output_path(None, path, allow_deletion) except IOError as err: LOG.error( ("Could not prepare the output directory '%s'" "! - '%s'"), path, str(err), ) return False try: self._write_hw(append_to_path(path, "hardware"), use_symlinks, allow_deletion) if err_mgr.has_errors(): LOG.critical("Abort! Errors occurred during system build:") err_mgr.print_errors() return False self._write_sw( append_to_path(path, "software"), use_symlinks, allow_deletion, module_driver_dirs, ) if err_mgr.has_errors(): LOG.critical("Abort! Errors occurred during system build:") err_mgr.print_errors() return False except (IOError, AsError) as err: LOG.error(str(err)) err_mgr.print_errors() return False return True
def set_asterics_directory(path: str) -> bool: # Cleanup path input path = os.path.realpath(append_to_path(path, "/")) if os.path.isdir(path): Auto.asterics_dir = path LOG.info("ASTERICS home directory set to '%s'.", path) return True # -> Else LOG.error("Path '%s' is not a directory! ASTERICS home not set!", path) return False
def new_chain() -> AsProcessingChain: """Provide a new AsProcessingChain object. This allows you to specify and build a new ASTERICS processing chain. When building multiple systems in one script, make sure build the system, before calling this again to start the second system!""" AsProcessingChain.err_mgr = as_err.AsError.err_mgr # Add "standard" ASTERICS modules Auto.add_module_repository(append_to_path(as_path, "modules"), "default") Auto.current_chain = AsProcessingChain(Auto.library, parent=Auto) return Auto.current_chain
def __get_module_scripts_in_dir__(cls, module_dir: str) -> Sequence[str]: scripts = [] module_dir = os.path.realpath(module_dir) try: mod_folders = os.listdir(append_to_path(module_dir, "")) except IOError: LOG.error("Could not find module repository folder '%s'!", module_dir) raise AsFileError( "Could not find module repository folder {}!".format( module_dir)) for folder in mod_folders: folder_path = append_to_path(module_dir, folder) if not os.path.isdir(folder_path): continue script_path = append_to_path(folder_path, cls.SCRIPT_FOLDER) if not os.path.isdir(script_path): continue for file in os.listdir(script_path): if cls.__script_name_valid__(file): scripts.append((folder_path, script_path + file)) return scripts
def write_asterics_vhd(self, folder: str): """Generate the Toplevel ASTERICS VHDL file""" LOG.info("Writing ASTERICS toplevel VHDL file...") # Generate path to the output file outfile = as_help.append_to_path(folder, as_static.AS_TOP_FILENAME, False) # Generate the list of glue signals self.generate_glue_signal_strings(self.chain.top.signals) library_use_str = "" lib_use_template = "use asterics.{};\n" module_entities = [] for mod in self.chain.top.modules: if mod.entity_name not in module_entities: module_entities.append(mod.entity_name) for entity in module_entities: library_use_str += lib_use_template.format(entity) # Open the output file with open(outfile, "w") as ofile: # Make sure we can write to the file if not ofile.writable(): raise AsFileError( msg="File not writable", filename=as_static.AS_TOP_FILENAME ) # Write the header ofile.write( as_static.HEADER.format( entity_name=as_static.AS_TOP_ENTITY, filename=as_static.AS_TOP_FILENAME, longdesc=as_static.AS_TOP_DESC, briefdesc=as_static.AS_TOP_DESC, ) ) # and the basic library 'use' declaration ofile.write( "\n{}{}{}\n".format( as_static.LIBS_IEEE, as_static.LIBS_ASTERICS, library_use_str ) ) # Generate and write the entity descritpion self.__write_entity__(self.chain.top, ofile) # Generate and write the toplevel architecture self.__write_module_group_architecture__(self.chain.top, ofile) # Done writing to file # Reset to init state self.clear_lists()
def __init__(self, asterics_home: str, version_no: str): self.asterics_home = append_to_path(os.path.realpath(asterics_home), "//") self.version = version_no self.library = AsModuleLibrary(asterics_home) self.current_chain = None self.windowpipes = [] self.design_name = "asterics" self.board_target = "" self.partname_hw = "xc7z010clg400-1" self.ipcore_name = "ASTERICS" self.ipcore_descr = "ASTERICS Image Processing Chain" # Construct and assign interface templates as_templates.add_templates()
def write_asterics_h(chain: AsProcessingChain, output_path: str): """Write the toplevel ASTERICS driver header containing include statements for all driver header files and the definition of the register ranges and base addresses.""" LOG.info("Generating ASTERICS main software driver header file...") asterics_h_path = append_to_path(output_path, "/") include_list = "" include_template = '#include "{}" \n' module_base_reg_template = "#define AS_MODULE_BASEREG_{modname} {offset}\n" module_base_addr_template = ( "#define AS_MODULE_BASEADDR_{modname} ((uint32_t*)(ASTERICS_BASEADDR + " "(AS_MODULE_BASEREG_{modname} * 4 * AS_REGISTERS_PER_MODULE)))\n") # Build a list of module additions to asterics.h # Additions should be unique # (two HW instances of an ASTERICS module use the same driver) module_additions = set() for module in chain.modules: module_additions.update(module.get_software_additions()) # Format module additions: One line per additional string module_additions = "\n".join(module_additions) module_additions = ( "/************************** Module Defines and " "Additions ***************************/\n\n") + module_additions # Get list of modules unique_modules = get_unique_modules(chain) # Collect driver header files that need to be included for entity in unique_modules: module = chain.library.get_module_template(entity) driver_path = module.module_dir + "software/driver/" if not os.path.isdir(driver_path): continue for file in os.listdir(driver_path): if file[-2:] == ".h": if not any(file in include for include in include_list): include_list += include_template.format(file) # Register base definition list and register range order reg_bases = "" reg_addrs = "" # Generate register definitions for addr in chain.address_space: # Get register interface object regif = chain.address_space[addr] # Build register interface module name regif_modname = regif.parent.name if regif.name_suffix: regif_modname += regif.name_suffix reg_bases += module_base_reg_template.format( modname=regif_modname.upper(), offset=regif.regif_num) reg_addrs += module_base_addr_template.format( modname=regif_modname.upper()) try: with open(asterics_h_path + ASTERICS_H_NAME, "w") as file: # Write asterics.h file.write( ASTERICS_H_TEMPLATE.format( base_addr=chain.asterics_base_addr, regs_per_mod=chain.max_regs_per_module, module_driver_includes=include_list, base_regs=reg_bases, addr_map=reg_addrs, module_additions=module_additions, )) except IOError as err: print("Couldn't write {}: '{}'".format( asterics_h_path + ASTERICS_H_NAME, err)) raise AsFileError( asterics_h_path + ASTERICS_H_NAME, detail=str(err), msg="Could not write to file", )
def gather_sw_files( chain: AsProcessingChain, output_folder: str, use_symlinks: bool = True, sep_dirs: bool = False, ) -> bool: """Collect all available software driver files in 'drivers' folders of the module source directories in the output folder structure. Parameters: chain: The current AsProcessingChain. Source of the modules list. output_folder: The root of the output folder structure. Returns True on success, False otherwise.""" LOG.info("Gathering ASTERICS module software driver source files...") out_path = os.path.realpath(output_folder) # We don't want the trailing slash if we're not using separate folders out_path = append_to_path(out_path, "/", sep_dirs) # Collect all module entity names unique_modules = get_unique_modules(chain) for entity in unique_modules: module = chain.library.get_module_template(entity) source_path = os.path.realpath( append_to_path(module.module_dir, "/software/driver/")) if not os.path.exists(source_path): # If driver folder not present: # This module doesn't need drivers -> skip LOG.debug( ("Gather SW files: Skipped '%s' (%s): " "Directory doesn't exist."), entity, source_path, ) continue if sep_dirs: # Build destination path: out + entity name (cut off trailing '/') dest_path = append_to_path(out_path, entity, False) else: # If drivers should not be stored in separate directories dest_path = out_path # Linking / copying the driver folders / files if use_symlinks and sep_dirs: LOG.debug("Gather SW files: Link '%s' to '%s'", source_path, dest_path) try: os.symlink(source_path, dest_path, target_is_directory=True) except FileExistsError: os.unlink(dest_path) try: os.symlink(source_path, dest_path, target_is_directory=True) except IOError as err: LOG.critical( "Could not link drivers of module '%s'! - '%s", entity, str(err)) return False except IOError as err: LOG.critical("Could not link drivers of module '%s'! - '%s", entity, str(err)) return False elif not use_symlinks and sep_dirs: LOG.debug("Gather SW files: Copy '%s' to '%s'", source_path, dest_path) try: os.makedirs(dest_path, 0o755) except FileExistsError: rmtree(dest_path) os.makedirs(dest_path, 0o755) try: copytree(source_path, dest_path) except IOError as err: LOG.critical("Could not copy drivers of module '%s'! - '%s", entity, str(err)) return False else: # Get mode of operation mode = "link" if use_symlinks else "copy" copy_op = os.symlink if use_symlinks else copy LOG.debug("Gather SW files: %sing drivers of module '%s'...", mode.title(), entity) # For every drive file for sw_file in os.listdir(source_path): source_file = append_to_path(source_path, sw_file, False) if use_symlinks: dest = append_to_path(dest_path, sw_file, False) else: dest = dest_path # try to copy/link try: copy_op(source_file, dest) # Remove existing files on error except FileExistsError: os.unlink(append_to_path(dest_path, sw_file, False)) # Retry copy/link try: copy_op(source_file, dest) except IOError as err: LOG.critical( ("Could not %s driver file '%s' of module '%s'!" " - '%s"), mode, sw_file, entity, str(err), ) return False except IOError as err: LOG.critical( ("Could not %s driver file '%s' of module '%s'" "! - '%s"), mode, sw_file, entity, str(err), ) return False return True
def add_vears_core( output_path: str, asterics_path: str, use_symlinks: bool = True, force: bool = False, ): """! @brief Link or copy the VEARS IP-Core. VEARS is copied/linked from the ASTERICS installation to the output path. @param output_path: Directory to link/copy VEARS to. @param asterics_path: Toplevel folder of the ASTERICS installation. @param use_symlinks: If True, VEARS will be linked, else copied. @param force: If True, the link or folder will be deleted if already present.""" vears_path = append_to_path(asterics_path, VEARS_REL_PATH) vears_path = os.path.realpath(vears_path) target = append_to_path(output_path, "VEARS", add_trailing_slash=False) if force and os.path.exists(target): if os.path.islink(target) or os.path.isfile(target): try: os.remove(target) except IOError as err: LOG.error("Could not remove file '%s'! VEARS not added!", target) raise AsFileError( target, "Could not remove file to link/copy VEARS!", str(err), ) else: try: rmtree(target) except IOError as err: LOG.error("Could not remove folder '%s'! VEARS not added!", target) raise AsFileError( target, "Could not remove folder to link/copy VEARS!", str(err), ) if use_symlinks: if not os.path.exists(output_path): try: os.makedirs(output_path, mode=0o755, exist_ok=True) except IOError as err: LOG.error( "Could not create directory for VEARS: '%s'! - '%s'", output_path, str(err), ) raise AsFileError( output_path, "Could not create directory for VEARS!", str(err), ) try: target = os.path.realpath(target) os.symlink(vears_path, target, target_is_directory=True) except IOError as err: LOG.error("Could not create a link to the VEARS IP-Core! - '%s'", str(err)) raise AsFileError(target, "Could not create link to VEARS!", str(err)) else: try: os.makedirs(target, mode=0o755, exist_ok=True) target = os.path.realpath(target) copytree(vears_path, target) except IOError as err: LOG.error("Could not copy the VEARS IP-Core!") raise AsFileError(output_path, "Could not copy VEARS!", str(err))
def write_asterics_h(chain: AsProcessingChain, output_path: str): """! @brief Write the toplevel ASTERICS driver C header The header contains include statements for all driver header files and the definition of the register ranges and base addresses.""" LOG.info("Generating ASTERICS main software driver header file...") asterics_h_path = append_to_path(output_path, "/") include_list = set() include_str = "" include_template = '#include "{}" \n' module_base_reg_template = "#define AS_MODULE_BASEREG_{modname} {offset}\n" module_base_addr_template = ( "#define AS_MODULE_BASEADDR_{modname} ((uint32_t*)(ASTERICS_BASEADDR + " "(AS_MODULE_BASEREG_{modname} * 4 * AS_REGISTERS_PER_MODULE)))\n") # Build a list of module additions to asterics.h # Additions should be unique # (two HW instances of an ASTERICS module use the same driver) module_additions = set() for module in chain.modules: module_additions.update(module.get_software_additions()) # Format module additions: One line per additional string module_additions = "\n".join(module_additions) module_additions = ( "/************************** Module Defines and " "Additions ***************************/\n\n") + module_additions # Get list of modules unique_modules = get_unique_modules(chain) # Collect driver header files that need to be included def add_header(mod: AsModule): for driver in mod.driver_files: if driver.endswith(".h"): include_list.add(driver.rsplit("/", maxsplit=1)[-1]) for mod in list(ittls.chain(chain.modules, chain.pipelines)): add_header(mod) for entity in unique_modules: mod = chain.library.get_module_template(entity) if mod is not None: add_header(mod) # Build include list include_str = "".join( [include_template.format(i) for i in sorted(include_list)]) # Register base definition list and register range order reg_bases = "" reg_addrs = "" # Generate register definitions for addr in chain.address_space: # Get register interface object regif = chain.address_space[addr] # Build register interface module name regif_modname = regif.parent.name if regif.name_suffix: regif_modname += regif.name_suffix reg_bases += module_base_reg_template.format( modname=regif_modname.upper(), offset=regif.regif_num) reg_addrs += module_base_addr_template.format( modname=regif_modname.upper()) try: with open(asterics_h_path + ASTERICS_H_NAME, "w") as file: # Write asterics.h file.write( ASTERICS_H_TEMPLATE.format( header=ASTERICS_HEADER_SW.format( filename=ASTERICS_H_NAME, description=ASTERICS_H_DESCRIPTION, ), base_addr=chain.asterics_base_addr, regs_per_mod=chain.max_regs_per_module, module_driver_includes=include_str, base_regs=reg_bases, addr_map=reg_addrs, module_additions=module_additions, )) except IOError as err: print("Couldn't write {}: '{}'".format( asterics_h_path + ASTERICS_H_NAME, err)) raise AsFileError( asterics_h_path + ASTERICS_H_NAME, detail=str(err), msg="Could not write to file", )
def write_vivado_package_tcl(chain: AsProcessingChain, output_path: str, additions_c1: str = "") -> bool: """! @brief Write two TCL script fragments sourced by the packaging TCL script. This function generates Vivado-specific TCL commands! The first fragment is sourced very early in the packaging script and contains basic information like the project directory, source file directory, etc. The second fragment contains packaging commands setting up the AXI interface configuration. This function needs to access the toplevel modules in the current processing chain to generate the TCL code. Parameters: chain - AsProcessingChain: The current processing chain to build output_path - String: Toplevel folder of the current project Returns a boolean value: True on success, False otherwise""" # Class to manage the AXI interface information for the TCL packaging # script class TCL_AXI_Interface: """'Private' class capsuling methods to write the TCL fragments required for the packaging script for Xilinx targets.""" def __init__(self, axi_type: str): # Type of AXI interface ("AXI Slave" / "AXI Master") self.axi_type = axi_type self.refname = "" # Variable name of this interface self.bus_if_name = "" # Name of this bus interface in Vivado TCL self.clock_name = "_aclk" # Name of the associated clock self.reset_name = "_aresetn" # Name of the associated reset self.if_type = "" # AXI slave register address block name (Slave) self.memory_range = 0 # Range of the address block (Slave) def update(self): """Update the clock and reset names after setting the bus interface name""" self.clock_name = self.bus_if_name + self.clock_name self.reset_name = self.bus_if_name + self.reset_name def get_tcl_bus_assoc_commands(self) -> str: """Generate the bus interface association TCL commands""" return BUS_IF_ASSOCIATION_TCL_TEMPLATE.format( busif=self.bus_if_name, clk=self.clock_name, rst=self.reset_name, ref=self.refname, ) def get_tcl_mem_commands(self) -> str: """Generate the memory mapping TCL commands""" if any((not self.if_type, not self.memory_range)): LOG.debug( ("Generating TCL packaging script: Interface type " "of slave memory range not set for '%s'!"), self.bus_if_name, ) return "" # Else -> return MEMORY_MAP_TCL_TEMPLATE.format( ref=self.refname, axi_type=self.if_type, mem_range=self.memory_range, busif=self.bus_if_name, mem_map_ref=self.refname + "_mem_ref", ) LOG.info("Generating TCL packaging scripts...") # Generate the necessary paths: # IP-Core project directory project_dir = append_to_path(os.path.realpath(output_path), "/") # Subdirectory containing the HDL sources hdl_dir = append_to_path(project_dir, "/hw/hdl/vhdl/") # Populate the fragment files with a generic file header content1 = TCL_HEADER.format( TCL1_NAME, "TCL fragment (1) part of the IP packaging TCL script") content2 = TCL_HEADER.format( TCL2_NAME, "TCL fragment (2) part of the IP packaging TCL script") content3 = TCL_HEADER.format( TCL3_NAME, "TCL fragment (2) part of the IP packaging TCL script") # Generate the commands for the first basic fragment content1 += "\nset projdir " + project_dir content1 += "\nset hdldir " + hdl_dir if additions_c1: content1 += "\n" + additions_c1 + "\n" # Possible AXI interface types templates = ("AXI_Slave", "AXI_Master") for inter in chain.top.interfaces: temp = None if inter.type.lower() in ("axi_slave_external", "axi_master_external"): slave = templates[0] in inter.type if slave: # For AXI slaves: Populate the TCL AXI class temp = TCL_AXI_Interface(templates[0]) # And set parameters for the memory map (slave registers) temp.memory_range = 65536 temp.if_type = "axi_lite" else: temp = TCL_AXI_Interface(templates[1]) temp.refname = (minimize_name(inter.name_prefix).replace( temp.axi_type.lower(), "").strip("_")) temp.bus_if_name = inter.ports[0].code_name.rsplit("_", 1)[0] # Set the reference name and update the clock and reset names temp.update() # Generate the bus association commands for all interfaces content2 += temp.get_tcl_bus_assoc_commands() if slave: # Only slaves have a memory map content2 += temp.get_tcl_mem_commands() # Build interface instantiation strings for as_iic modules iic_if_inst_str = ["\n"] for module in chain.modules: if module.entity_name == "as_iic": if_name = module.name top_if = chain.top.__get_interface_by_un_fuzzy__( module.get_interface("in", if_type="iic_interface").unique_name) if top_if is None: LOG.error( ("Was not able to determine port names for IIC " "interface '%s' - IIC interface not found!"), if_name, ) mod_prefix = "" else: mod_prefix = minimize_name( top_if.name_prefix, chain.NAME_FRAGMENTS_REMOVED_ON_TOPLEVEL) iic_if_inst_str.append( "#Instantiate interface for {}\n".format(if_name)) iic_if_inst_str.append( AS_IIC_MAP_TCL_TEMPLATE.format(iic_signal_prefix=mod_prefix, iic_if_name=if_name)) iic_if_inst_str.append( "# END Instantiate interface for {}\n\n".format(if_name)) # Assemble the IIC interface string and add to the TCL fragment content2 += "\n".join(iic_if_inst_str) tcl_ooc_remove_outsourced_template = "set_property is_enabled false -quiet [get_files -quiet -of_objects [get_filesets sources_1] {{{files}}}]\n" outsourced_files = [] ooc_modules = [ mod for mod in chain.modules if isinstance(mod, AsModuleWrapper) ] if ooc_modules: tcl_ooc_commands = [ "# Create OOC blocks\n", 'puts "Generating Out-of-Context Synthesis Runs..."\n', ] else: tcl_ooc_commands = [] count = 1 for mod in ooc_modules: source_files = set() modfilename = mod.name + ".vhd" source_files.add(modfilename) outsourced_files.append(modfilename) source_files.update(mod.modules[0].files) dep_mods = [] get_dependencies(chain, mod.modules[0], dep_mods) if dep_mods: for dmod in dep_mods: modtemplate = chain.library.get_module_template(dmod) source_files.update(modtemplate.files) source_files = (sf.rsplit("/", maxsplit=1)[-1] for sf in sorted(source_files)) tcl_ooc_commands.append( TCL_OOC_TEMPLATE.format( ent_name=mod.entity_name, source_files=" ".join(source_files), top_source=mod.name + ".vhd", progress=str(count) + " of " + str(len(ooc_modules)), )) count += 1 tcl_ooc_commands.append( tcl_ooc_remove_outsourced_template.format( files=" ".join(outsourced_files))) content3 += "\n".join(tcl_ooc_commands) # Make sure all files are newline-terminated content1 += "\n" content2 += "\n" content3 += "\n" for name, content in zip((TCL1_NAME, TCL2_NAME, TCL3_NAME), (content1, content2, content3)): try: with open(project_dir + name, "w") as file: file.write(content) except IOError as err: LOG.error("Could not write '%s' in '%s'.", name, project_dir) raise AsFileError(project_dir + name, "Could not write file!", str(err))
# Plz no remove :3 - Thank! from as_automatics_env import AsAutomatics from as_automatics_proc_chain import AsProcessingChain from as_automatics_module import AsModule from as_automatics_port import Port from as_automatics_interface import Interface from as_automatics_module_lib import AsModuleLibrary from as_automatics_generic import Generic from as_automatics_templates import AsMain, AsTop from as_automatics_helpers import append_to_path print("Success!") # Init Automatics and load standard modules... print("Getting default modules from '{}'...".format(asterics_dir)) auto = AsAutomatics(asterics_dir) auto.add_module_repository(append_to_path(asterics_dir, "modules"), "default") # User prompt: print("Done.") print("") print("Automatics initialized!") print("Use 'as_help()' to list available functions.") # User functions start: # ---------------------- def as_help(): """Print list of available functions""" print("") print("ASTERICS Automatics: Automatic Processing Chain generator tool") print("")
def gather_sw_files( chain: AsProcessingChain, output_folder: str, use_symlinks: bool = True, sep_dirs: bool = False, ) -> bool: """! @brief Copy or link to software files for an ASTERICS chain. Collect all available software driver files in 'drivers' folders of the module source directories in the output folder structure. @param chain: The current AsProcessingChain. Source of the modules list. @param output_folder: The root of the output folder structure. @param use_symlinks: Whether or not to link (True) or copy (False) files @param sep_dirs: Whether or not to generate separate directories per module driver @return True on success, False otherwise.""" LOG.info("Gathering ASTERICS module software driver source files...") out_path = os.path.realpath(output_folder) # We don't want the trailing slash if we're not using separate folders out_path = append_to_path(out_path, "/", sep_dirs) # Collect all module entity names unique_modules = get_unique_modules(chain) modules = list(ittls.chain(chain.modules, chain.pipelines)) handled_entities = [] # Initialize driver list with asterics support package drivers drivers = [( "as_support", list( asp_driver.format(asterics_root=asterics_home) for asp_driver in ASP_FILES), )] for mod in modules: if mod.entity_name not in handled_entities: drivers.append((mod.entity_name, mod.driver_files)) handled_entities.append(mod.entity_name) for entity in filter(lambda ent: ent not in handled_entities, unique_modules): module = chain.library.get_module_template(entity) drivers.append((module.entity_name, module.driver_files)) driver_dir = {} for entity, paths in drivers: if not paths: continue try: driver_dir[entity].extend(paths) except KeyError: driver_dir[entity] = paths for entity in driver_dir: driverlist = driver_dir[entity] if sep_dirs: # Build destination path: out + entity name (cut off trailing '/') dest_path = append_to_path(out_path, entity, False) else: # If drivers should not be stored in separate directories dest_path = out_path # Linking / copying the driver files # Get mode of operation mode = "link" if use_symlinks else "copy" copy_op = os.symlink if use_symlinks else copy for driverfile in driverlist: filename = driverfile.rsplit("/", maxsplit=1)[-1] dest_file = append_to_path(dest_path, filename, False) if os.path.exists(dest_file): os.unlink(dest_file) try: copy_op(driverfile, dest_file) except IOError as err: LOG.critical( ("Could not %s driver file '%s' of module '%s'!" " - '%s"), mode, filename, entity, str(err), ) return False return True ## @}
def gather_hw_files(chain: AsProcessingChain, output_folder: str, use_symlinks: bool = True) -> bool: """! @brief Copy or link to module VHDL files of an ASTERICS chain. Collect all required hardware descriptive files for the current ASTERICS system. Mainly looks at the AsModule.files, .module_dir and .dependencies attributes to find required files. @param chain: current processing chain @param output_folder: The root of the output folder structure @param use_symlinks: Whether or not to link (True) or copy (False) files @return True on success, else False""" LOG.info("Gathering HDL source files...") out_path = os.path.realpath(output_folder) # Collect all module entity names unique_modules = get_unique_modules(chain) for entity in unique_modules: this_path = append_to_path(out_path, entity) try: os.makedirs(this_path, 0o755, exist_ok=True) except FileExistsError: pass module = chain.library.get_module_template(entity) if module is None: continue module_dir = os.path.realpath(module.module_dir) for hw_file in module.files: source = os.path.realpath(hw_file) if not hw_file.startswith("/"): comp = os.path.commonprefix([module_dir, source]) if comp != module_dir: # Append the file path to the module directory, cutting off '/' source = append_to_path(module_dir, hw_file, add_trailing_slash=False) if not os.path.isfile(source): raise AsFileError(source, "File not found!") filename = source.rsplit("/", maxsplit=1)[-1] dest = this_path + filename LOG.debug("Gather HW files: Link '%s' to '%s'", source, dest) if use_symlinks: # Try to create a symlink try: os.symlink(source, dest) except FileExistsError: # If a symlink already exists, delete it and retry os.unlink(dest) try: os.symlink(source, dest) except IOError as err: LOG.critical( ("Could not link file '%s' of module '%s'!" " - '%s'"), filename, entity, str(err), ) return False except IOError as err: LOG.critical( ("Could not link file '%s' of module '%s'! " "- '%s'"), filename, entity, str(err), ) return False else: try: copy(source, dest) except IOError as err: LOG.critical( ("Could not copy file '%s' of module '%s'!" " - '%s'"), filename, entity, str(err), ) return False return True
def write_vivado_package_tcl(chain: AsProcessingChain, output_path: str, additions_c1: str = "") -> bool: """Write two TCL script fragments sourced by the packaging TCL script. This function generates Vivado-specific TCL commands! The first fragment is sourced very early in the packaging script and contains basic information like the project directory, source file directory, etc. The second fragment contains packaging commands setting up the AXI interface configuration. This function needs to access the toplevel modules in the current processing chain to generate the TCL code. Parameters: chain - AsProcessingChain: The current processing chain to build output_path - String: Toplevel folder of the current project Returns a boolean value: True on success, False otherwise""" as_iic_map_tcl_template = ( "# Set up interface properties:\n" "ipx::add_bus_interface {iic_if_name} [ipx::current_core]\n" "set_property abstraction_type_vlnv xilinx.com:interface:iic_rtl:1.0 " "[ipx::get_bus_interfaces {iic_if_name} -of_objects [ipx::current_core]]\n" "set_property bus_type_vlnv xilinx.com:interface:iic:1.0 " "[ipx::get_bus_interfaces {iic_if_name} -of_objects [ipx::current_core]]\n" "set_property interface_mode master " "[ipx::get_bus_interfaces {iic_if_name} -of_objects [ipx::current_core]]\n" "set_property display_name asterics_{iic_if_name} " "[ipx::get_bus_interfaces {iic_if_name} -of_objects [ipx::current_core]]\n" "# IIC port mapping:\n" "ipx::add_port_map SCL_T [ipx::get_bus_interfaces {iic_if_name} " "-of_objects [ipx::current_core]]\n" "set_property physical_name {iic_signal_prefix}scl_out_enable " "[ipx::get_port_maps SCL_T -of_objects " "[ipx::get_bus_interfaces {iic_if_name} -of_objects [ipx::current_core]]]\n" "ipx::add_port_map SDA_O [ipx::get_bus_interfaces {iic_if_name} " "-of_objects [ipx::current_core]]\n" "set_property physical_name {iic_signal_prefix}sda_out " "[ipx::get_port_maps SDA_O -of_objects " "[ipx::get_bus_interfaces {iic_if_name} -of_objects [ipx::current_core]]]\n" "ipx::add_port_map SDA_I [ipx::get_bus_interfaces {iic_if_name} " "-of_objects [ipx::current_core]]\n" "set_property physical_name {iic_signal_prefix}sda_in " "[ipx::get_port_maps SDA_I -of_objects " "[ipx::get_bus_interfaces {iic_if_name} -of_objects [ipx::current_core]]]\n" "ipx::add_port_map SDA_T [ipx::get_bus_interfaces {iic_if_name} " "-of_objects [ipx::current_core]]\n" "set_property physical_name {iic_signal_prefix}sda_out_enable " "[ipx::get_port_maps SDA_T -of_objects " "[ipx::get_bus_interfaces {iic_if_name} -of_objects [ipx::current_core]]]\n" "ipx::add_port_map SCL_O [ipx::get_bus_interfaces {iic_if_name}" " -of_objects [ipx::current_core]]\n" "set_property physical_name {iic_signal_prefix}scl_out " "[ipx::get_port_maps SCL_O -of_objects " "[ipx::get_bus_interfaces {iic_if_name} -of_objects [ipx::current_core]]]\n" "ipx::add_port_map SCL_I [ipx::get_bus_interfaces {iic_if_name} " "-of_objects [ipx::current_core]]\n" "set_property physical_name {iic_signal_prefix}scl_in " "[ipx::get_port_maps SCL_I -of_objects " "[ipx::get_bus_interfaces {iic_if_name} -of_objects [ipx::current_core]]]\n" ) # Class to manage the AXI interface information for the TCL packaging # script class TCL_AXI_Interface: """'Private' class capsuling methods to write the TCL fragments required for the packaging script for Xilinx targets.""" # TCL command templates: bus_if_association_tcl_template = ( "ipx::associate_bus_interfaces -clock {clk} -busif {busif} -clear " "[ipx::current_core]\n" "# ^ Dissassociate any signals with this AXI interface" " (to be save)\n" "# Associate the correct clock and reset signals\n" "ipx::associate_bus_interfaces -clock {clk} -reset {rst} " "-busif {busif} [ipx::current_core]\n" "# Store the interface object in a variable with known name\n" "set {ref} [ipx::get_bus_interfaces -of_objects " "[ipx::current_core] {busif}]\n") memory_map_tcl_template = ( "ipx::remove_memory_map slave_s_axi [ipx::current_core]\n" "ipx::remove_memory_map {ref} [ipx::current_core]\n" "# ^ Remove any memory maps from the interface\n" "# (potentially) Re-add a memory map\n" "ipx::add_memory_map {mem_map_ref} [ipx::current_core]\n" "# Add a slave memory map reference\n" "set_property slave_memory_map_ref {mem_map_ref} ${ref}\n" "# Add an address block to the memory map " "using the above reference\n" "ipx::add_address_block {{{axi_type}}} [ipx::get_memory_maps " "{mem_map_ref} -of_objects [ipx::current_core]]\n" "# Set the address block range\n" "set_property range {{{mem_range}}} [ipx::get_address_blocks " "{{{axi_type}}} -of_objects [ipx::get_memory_maps " "{{{mem_map_ref}}} -of_objects [ipx::current_core]]]\n") def __init__(self, axi_type: str): # Type of AXI interface ("AXI Slave" / "AXI Master") self.axi_type = axi_type self.refname = "" # Variable name of this interface self.bus_if_name = "" # Name of this bus interface in Vivado TCL self.clock_name = "_aclk" # Name of the associated clock self.reset_name = "_aresetn" # Name of the associated reset self.if_type = "" # AXI slave register address block name (Slave) self.memory_range = 0 # Range of the address block (Slave) def update(self): """Update the clock and reset names after setting the bus interface name""" self.clock_name = self.bus_if_name + self.clock_name self.reset_name = self.bus_if_name + self.reset_name def get_tcl_bus_assoc_commands(self) -> str: """Generate the bus interface association TCL commands""" return self.bus_if_association_tcl_template.format( busif=self.bus_if_name, clk=self.clock_name, rst=self.reset_name, ref=self.refname, ) def get_tcl_mem_commands(self) -> str: """Generate the memory mapping TCL commands""" if any((not self.if_type, not self.memory_range)): LOG.debug( ("Generating TCL packaging script: Interface type " "of slave memory range not set for '%s'!"), self.bus_if_name, ) return "" # Else -> return self.memory_map_tcl_template.format( ref=self.refname, axi_type=self.if_type, mem_range=self.memory_range, busif=self.bus_if_name, mem_map_ref=self.refname + "_mem_ref", ) LOG.info("Generating TCL packaging scripts...") # Generate the necessary paths: # IP-Core project directory project_dir = append_to_path(os.path.realpath(output_path), "/") # Subdirectory containing the HDL sources hdl_dir = append_to_path(project_dir, "/hw/hdl/vhdl/") # Populate the fragment files with a generic file header content1 = TCL_HEADER.format( TCL1_NAME, "TCL fragment (1) part of the IP packaging TCL script") content2 = TCL_HEADER.format( TCL2_NAME, "TCL fragment (2) part of the IP packaging TCL script") # Generate the commands for the first basic fragment content1 += "\nset projdir " + project_dir content1 += "\nset hdldir " + hdl_dir if additions_c1: content1 += "\n" + additions_c1 + "\n" # Possible AXI interface types templates = ("AXI_Slave", "AXI_Master") for inter in chain.top.interfaces: temp = None if inter.type.lower() in ("axi_slave_external", "axi_master_external"): slave = templates[0] in inter.type if slave: # For AXI slaves: Populate the TCL AXI class temp = TCL_AXI_Interface(templates[0]) # And set parameters for the memory map (slave registers) temp.memory_range = 65536 temp.if_type = "axi_lite" else: temp = TCL_AXI_Interface(templates[1]) temp.refname = (as_help.minimize_name(inter.name_prefix).replace( temp.axi_type.lower(), "").strip("_")) temp.bus_if_name = inter.ports[0].code_name.rsplit("_", 1)[0] # Set the reference name and update the clock and reset names temp.update() # Generate the bus association commands for all interfaces content2 += temp.get_tcl_bus_assoc_commands() if slave: # Only slaves have a memory map content2 += temp.get_tcl_mem_commands() # Build interface instantiation strings for as_iic modules iic_if_inst_str = ["\n"] for module in chain.modules: if module.entity_name == "as_iic": if_name = module.name top_if = chain.top.__get_interface_by_un_fuzzy__( module.get_interface("in", if_type="iic_interface").unique_name) if top_if is None: LOG.error( ("Was not able to determine port names for IIC " "interface '%s' - IIC interface not found!"), if_name, ) mod_prefix = "" else: mod_prefix = as_help.minimize_name( top_if.name_prefix, chain.NAME_FRAGMENTS_REMOVED_ON_TOPLEVEL) iic_if_inst_str.append( "#Instantiate interface for {}\n".format(if_name)) iic_if_inst_str.append( as_iic_map_tcl_template.format(iic_signal_prefix=mod_prefix, iic_if_name=if_name)) iic_if_inst_str.append( "# END Instantiate interface for {}\n\n".format(if_name)) # Assemble the IIC interface string and add to the TCL fragment content2 += "\n".join(iic_if_inst_str) # Make sure both files are newline-terminated content1 += "\n" content2 += "\n" # Write fragment 1 try: with open(project_dir + TCL1_NAME, "w") as file: file.write(content1) except IOError as err: LOG.error("Could not write '%s' in '%s'.", TCL1_NAME, project_dir) raise AsFileError(project_dir + TCL1_NAME, "Could not write file!", str(err)) # Write fragment 2 try: with open(project_dir + TCL2_NAME, "w") as file: file.write(content2) except IOError as err: LOG.error("Could not write '%s' in '%s'.", TCL2_NAME, project_dir) raise AsFileError(project_dir + TCL2_NAME, "Could not write file!", str(err))