hdk/common/verif/scripts/compile_cl_ips.py (168 lines of code) (raw):

#!/usr/bin/env python3 # ============================================================================= # Amazon FPGA Hardware Development Kit # # Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. # # Licensed under the Amazon Software License (the "License"). You may not use # this file except in compliance with the License. A copy of the License is # located at # # http://aws.amazon.com/asl/ # # or in the "license" file accompanying this file. This file is distributed on # an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, express or # implied. See the License for the specific language governing permissions and # limitations under the License. # ============================================================================= import os import sys import shutil import fileinput import subprocess from glob import glob from argparse import ArgumentParser from typing import List, Dict, Callable, Union parser = ArgumentParser(prog="Compile IPs", description="Compile the IPs using Xilinx's compilation scripts") parser.add_argument('--simulator', dest='simulator', required=True) parser.add_argument('--complib_dir', dest='complib_dir', required=True) parser.add_argument('--compile_cl_ip_dir', dest='compile_cl_ip_dir', required=True) parser.add_argument('--in_progress_file', dest='in_progress_file', required=True) if __name__ == '__main__': args = parser.parse_args() print(f"Using Python {sys.version} from '/usr/bin/env python3'") is_python3 = sys.version_info >= (3, 0) and sys.version_info < (4, 0) is_greater_than_3_8 = sys.version_info >= (3, 8) if not (is_python3 and is_greater_than_3_8): if os.path.exists(args.in_progress_file): os.remove(args.in_progress_file) raise Exception(f"Python {sys.version} from this system's '/usr/bin/env python3' is not usable. Please review and make sure Python 3.8+ is installed\n") def run_cmd(cmd: List[str], working_directory: str = os.getcwd(), do_print: bool = False) -> str: if working_directory is None: working_directory = os.getcwd() result = subprocess.run(cmd, stdout=subprocess.PIPE, cwd=working_directory) stdout = result.stdout.decode('utf-8').strip() if do_print: print(stdout) return stdout def get_git_root() -> str: git_root_cmd: List[str] = ['git', 'rev-parse', '--show-toplevel'] return run_cmd(git_root_cmd) XSIM = 'xsim' VCS = 'vcs' QUESTA = 'questa' class Compiler: orig_file_ext: str = '.orig' cl_dir: str = os.getenv('CL_DIR') default_xilinx_library_name: str = 'xil_defaultlib' cl_ip_sim_scripts_dir: str = f'{get_git_root()}/hdk/common/ip/cl_ip/cl_ip.ip_user_files/sim_scripts' init_files: Dict[str,str] = {XSIM: 'xsim.ini', VCS: 'synopsys_sim.setup', QUESTA: 'modelsim.ini'} def __init__(self, args): self.backed_up_files: List[str] = [] self.in_progress_file: str = args.in_progress_file self.simulator: str = args.simulator.lower() if not self.simulator in self.init_files.keys(): self.clean_failure(f"Unknown simulator: {self.simulator}") self.complib_dir: str = args.complib_dir self.compile_cl_ip_dir: str = args.compile_cl_ip_dir self.results_file: str = f'{os.getcwd()}/{self.simulator}_cl_ip_compilation.log' self.sim_initfile: str = f'{self.complib_dir}/{self.init_files[self.simulator]}' def compile_cl_ips(self) -> None: self.add_xil_defaultlib_path_to_initfile() xilinx_ip_compile_scripts: List[str] = self.get_all_cl_ip_compilation_script_paths() with open(self.results_file, 'w') as f: f.write(str(xilinx_ip_compile_scripts)) for xilinx_ip_path in xilinx_ip_compile_scripts: self.compile_ip(xilinx_ip_path, f) self.check_for_errors() print(f"__PYTHON_INFO__: Moving {self.results_file} to {self.compile_cl_ip_dir}/done") shutil.move(self.results_file, f'{self.compile_cl_ip_dir}/done') def check_for_errors(self) -> None: with open(self.results_file) as output: for line in output: if "Error" in line and not "Errors: 0" in line: self.clean_failure(f"FOUND COMPILATION ERRORS! See {self.results_file}") def add_xil_defaultlib_path_to_initfile(self) -> None: lib_path_seperator: str = ':' if self.simulator == VCS else '=' new_line: str = f'{self.default_xilinx_library_name} {lib_path_seperator} {self.compile_cl_ip_dir}\n' line_insertion_map: Dict[str, Dict[str, Union[Callable, List[str]]]] = { XSIM: {'func': self.append_line_to_file, 'args': [self.sim_initfile, new_line, self.default_xilinx_library_name]}, VCS: {'func': self.append_line_to_file, 'args': [self.sim_initfile, new_line, self.default_xilinx_library_name]}, QUESTA: {'func': self.insert_line_above_target, 'args': [self.sim_initfile, '[DefineOptionset]', new_line]}} insertion_func: Callable = line_insertion_map[self.simulator]['func'] insertion_args: List[str] = line_insertion_map[self.simulator]['args'] insertion_func(*insertion_args) def append_line_to_file(self, file_path: str, new_line: str, exception: str) -> None: with open(file_path, 'r+') as f: for line in f: if exception is not None and exception in line: break else: f.write(new_line) def insert_line_above_target(self, file_path: str, target: str, new_line: str) -> None: exists: bool = False for line in fileinput.input(file_path, inplace=True): if self.default_xilinx_library_name in line: exists: bool = True if not exists and line.startswith(target): print(new_line, end='') print(line, end='') def get_all_cl_ip_compilation_script_paths(self) -> List[str]: ip_compile_scripts: List[str] = [] for ip_name in [ip.name for ip in os.scandir(self.cl_ip_sim_scripts_dir) if ip.is_dir()]: ip_sim_dir: str = f'{self.cl_ip_sim_scripts_dir}/{ip_name}/{self.simulator}' shell_scripts: List [str] = glob(f'{ip_sim_dir}/*.sh') if len(shell_scripts) != 1: self.clean_failure(f"Found {shell_scripts} at {ip_sim_dir}") ip_compile_scripts.append(shell_scripts[0]) return ip_compile_scripts def compile_ip(self, xilinx_ip_script_path: str, compile_log) -> None: ip_script_dir: str = os.path.dirname(xilinx_ip_script_path) symlink_dst: str = f'{ip_script_dir}/{self.init_files[self.simulator]}' if not os.path.exists(symlink_dst): os.symlink(self.sim_initfile, symlink_dst) self.prepare_ip_script_for_compilation(ip_script_dir, xilinx_ip_script_path) subprocess.check_call([xilinx_ip_script_path, '-lib_map_path', self.complib_dir], cwd=ip_script_dir, stdout=compile_log) self.cleanup_compilation_dir(ip_script_dir, symlink_dst) def prepare_ip_script_for_compilation(self, dir_path: str, file_path: str) -> None: self.backup_file(file_path) if self.simulator == QUESTA: self.remove_lines(f'{dir_path}/compile.do', line_prefixes_to_remove=['vlib', 'vmap']) self.append_line_to_file(file_path, new_line='compile\n', exception=None) self.remove_lines(file_path, line_prefixes_to_remove=['run $*']) self.replace_hardcoded_xilninx_path(file_path) def remove_lines(self, file_path: str, line_prefixes_to_remove: List[str]) -> None: self.backup_file(file_path) for line in fileinput.input(file_path, inplace=True): if not any([line.startswith(prefix) for prefix in line_prefixes_to_remove]): print(line, end='') def replace_hardcoded_xilninx_path(self, file_path) -> None: hardcoded_xilinx_path: str = '/tools/Xilinx/Vivado/2024.1' xilinx_path_env_var: str = '$XILINX_VIVADO' for line in fileinput.input(file_path, inplace=True): if hardcoded_xilinx_path in line: line = line.replace(hardcoded_xilinx_path, xilinx_path_env_var) print(line, end='') def cleanup_compilation_dir(self, ip_script_dir: str, symlink_dst: str) -> None: os.remove(symlink_dst) self.remove_compile_artifacts(ip_script_dir) self.move_backup_files_back() def remove_compile_artifacts(self, ip_script_dir: str) -> None: artifacts_to_remove = { XSIM: ['compile.log', 'xvhdl.log', 'xvhdl.pb', 'xvlog.log', 'xvlog.pb', 'xsim.dir'], VCS: ['vhdlan.log', 'vlogan.log'], QUESTA: ['compile.log']} for artifact_name in artifacts_to_remove[self.simulator]: artifact_path: str = f'{ip_script_dir}/{artifact_name}' if os.path.exists(artifact_path): if os.path.isdir(artifact_path): shutil.rmtree(artifact_path) else: os.remove(artifact_path) def move_backup_files_back(self) -> None: for modified_file_path in self.backed_up_files: original_file_path: str = f'{modified_file_path}{self.orig_file_ext}' if os.path.exists(original_file_path): shutil.move(original_file_path, modified_file_path) def backup_file(self, file_path: str) -> None: if file_path not in self.backed_up_files: self.backed_up_files.append(file_path) shutil.copy(file_path, f'{file_path}{self.orig_file_ext}') def clean_failure(self, message: str) -> None: if os.path.exists(self.in_progress_file): os.remove(self.in_progress_file) raise Exception(message) if __name__ == '__main__': try: compiler = Compiler(args) if os.path.exists(compiler.compile_cl_ip_dir): shutil.rmtree(compiler.compile_cl_ip_dir) os.makedirs(compiler.compile_cl_ip_dir) compiler.compile_cl_ips() except: if os.path.exists(args.in_progress_file): os.remove(args.in_progress_file) raise Exception("The Python script experienced a failure. Please review and make sure Python 3.8+ is installed\n")