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")