import os import shutil import re import xml.etree.ElementTree as ET from pathlib import Path from datetime import datetime # ================================================= # 用户配置区(唯一需要用户关心的地方) # ================================================= def get_project_root(): """ 获取用户输入的项目根目录,并验证路径是否存在 """ while True: project_root = input("请输入你的工程根目录路径: ").strip() if not project_root: print("路径不能为空,请重新输入。") continue # 处理路径中的引号 if project_root.startswith('"') and project_root.endswith('"'): project_root = project_root[1:-1] elif project_root.startswith("'") and project_root.endswith("'"): project_root = project_root[1:-1] # 检查路径是否存在 if os.path.isdir(project_root): return project_root else: print(f"错误: 路径 '{project_root}' 不存在,请重新输入。") # ================================================= # Testbench 命名规则 # ================================================= TB_PATTERN = re.compile(r'(^tb_)|(_tb_)|(_tb\.)', re.IGNORECASE) # ================================================= # 通用工具函数 # ================================================= def find_latest_xpr_in_dir(search_dir): """ 在指定目录下查找最新修改的 xpr 文件 """ files = [ os.path.join(search_dir, f) for f in os.listdir(search_dir) if f.lower().endswith('.xpr') ] if not files: raise FileNotFoundError(f"在目录 {search_dir} 下未找到 .xpr 文件") return max(files, key=os.path.getmtime) def load_extensions(config_path): """ 从 config.txt 读取需要的文件后缀集合 """ exts = set() with open(config_path, "r", encoding="utf-8") as f: for line in f: line = line.strip().lower() if not line or line.startswith("#"): continue if not line.startswith("."): line = "." + line exts.add(line) return exts def is_testbench_filename(file_path): """ 根据文件名判断是否是 testbench """ return bool(TB_PATTERN.search(Path(file_path).name.lower())) # ================================================= # 模糊路径解析(核心) # ================================================= def extract_tail_from_psrcdir(path_str): """ 从 $PSRCDIR/xxx/yyy 提取 xxx/yyy """ marker = "$PSRCDIR/" if marker not in path_str: raise ValueError(f"路径中不包含 $PSRCDIR: {path_str}") return path_str.split(marker, 1)[1].replace("\\", "/") def find_file_by_tail(root_dir, tail_path): """ 在 root_dir 下递归搜索, 找到唯一一个以 tail_path 结尾的文件 """ matches = [] tail_path = tail_path.replace("/", os.sep) for dirpath, _, filenames in os.walk(root_dir): for name in filenames: full_path = os.path.join(dirpath, name) if full_path.endswith(tail_path): matches.append(full_path) if not matches: # 如果没找到文件,返回 None 表示未找到 return None if len(matches) > 1: # 如果找到多个匹配,返回第一个,但打印警告 print(f"警告: 发现多个匹配文件,将使用第一个:\n" + "\n".join(matches)) return matches[0] return matches[0] def resolve_xpr_path(path_str, project_root, xpr_file_path): """ 将 xpr 中的 $PSRCDIR/... 解析为真实绝对路径 优先在与xpr文件同名的.srcs文件夹中查找,如果找不到再在根目录下进行全局匹配 """ if "$PSRCDIR/" not in path_str: # 如果不包含 $PSRCDIR/ 标记,返回 None return None tail = extract_tail_from_psrcdir(path_str) # 获取xpr文件名(不含扩展名)来构造.srcs目录名 xpr_filename = os.path.splitext(os.path.basename(xpr_file_path))[0] srcs_dir = os.path.join(project_root, xpr_filename + ".srcs") # 优先在.srcs目录中查找 if os.path.isdir(srcs_dir): real_path = find_file_by_tail(srcs_dir, tail) if real_path is not None: return real_path # 如果在.srcs目录中没找到,则在根目录下进行全局匹配 return find_file_by_tail(project_root, tail) # ================================================= # 核心逻辑 # ================================================= def extract_from_xpr_and_copy(xpr_path, config_path, project_root): """ 从 xpr 中解析 DesignSrcs + synthesis 文件集合, 使用模糊匹配解析真实路径并复制 """ include_exts = load_extensions(config_path) tree = ET.parse(xpr_path) root = tree.getroot() result_files = [] tb_files = [] failed_files = [] # 记录未找到的文件 # 只遍历 DesignSrcs for fileset in root.iter("FileSet"): if fileset.get("Type") != "DesignSrcs": continue for file_elem in fileset.findall("File"): file_path = file_elem.get("Path") if not file_path: continue # config.txt 后缀过滤 - 排除不需要的文件 suffix = Path(file_path).suffix.lower() if include_exts and suffix not in include_exts: continue # testbench 命名判断 is_tb = is_testbench_filename(file_path) fileinfo = file_elem.find("FileInfo") if fileinfo is None: continue used_in_synth = False auto_disabled = False user_disabled = False for attr in fileinfo.findall("Attr"): name = attr.get("Name") val = (attr.get("Val") or "").lower() if name == "UsedIn" and val == "synthesis": used_in_synth = True elif name == "AutoDisabled" and val in ("1", "true"): auto_disabled = True elif name == "UserDisabled" and val in ("1", "true"): user_disabled = True if used_in_synth and not auto_disabled and not user_disabled: real_path = resolve_xpr_path(file_path, project_root, xpr_path) if real_path is not None: if is_tb: tb_files.append(real_path) else: result_files.append(real_path) else: failed_files.append(file_path) # 记录未找到的文件路径 print(f"解析并定位到 {len(result_files)} 个非testbench文件") print(f"解析并定位到 {len(tb_files)} 个testbench文件") if failed_files: print(f"未能找到 {len(failed_files)} 个文件") # ------------------------------------------------- # 创建输出目录(秒级时间戳) # ------------------------------------------------- out_dir = datetime.now().strftime("Copy_Files_%Y%m%d_%H%M%S") os.makedirs(out_dir) print(f"创建输出目录: {out_dir}") # ------------------------------------------------- # 创建tb文件目录(如果存在tb文件) # ------------------------------------------------- tb_out_dir = None if tb_files: tb_out_dir = os.path.join(out_dir, "tb文件") os.makedirs(tb_out_dir) print(f"创建testbench输出目录: {tb_out_dir}") # ------------------------------------------------- # 复制非testbench文件 # ------------------------------------------------- copied_normal = 0 skipped_normal = 0 for src in result_files: if not os.path.exists(src): print(f"文件不存在,跳过: {src}") skipped_normal += 1 continue base = os.path.basename(src) dst = os.path.join(out_dir, base) # 防止重名覆盖 if os.path.exists(dst): name, ext = os.path.splitext(base) idx = 1 while True: new_dst = os.path.join(out_dir, f"{name}_{idx}{ext}") if not os.path.exists(new_dst): dst = new_dst break idx += 1 shutil.copy2(src, dst) copied_normal += 1 # ------------------------------------------------- # 复制testbench文件 # ------------------------------------------------- copied_tb = 0 skipped_tb = 0 for src in tb_files: if not os.path.exists(src): print(f"testbench文件不存在,跳过: {src}") skipped_tb += 1 continue base = os.path.basename(src) dst = os.path.join(tb_out_dir, base) if tb_out_dir else os.path.join(out_dir, base) # 防止重名覆盖 if os.path.exists(dst): name, ext = os.path.splitext(base) idx = 1 while True: new_dst = os.path.join(tb_out_dir, f"{name}_{idx}{ext}") if tb_out_dir else os.path.join(out_dir, f"{name}_{idx}{ext}") if not os.path.exists(new_dst): dst = new_dst break idx += 1 shutil.copy2(src, dst) copied_tb += 1 print("复制完成") print(f" 非testbench文件:") print(f" 成功复制: {copied_normal}") print(f" 跳过文件: {skipped_normal}") print(f" 未找到文件: {len(failed_files)}") if failed_files: print(" 未找到的文件列表:") for failed_file in failed_files: print(f" - {failed_file}") print(f" 输出目录: {os.path.abspath(out_dir)}") if tb_files: print("\n!!!!!!!!!!!!!!!!!!") print("识别到项目中存在testbench文件,请在vivado中检查是否为源代码") print("!!!!!!!!!!!!!!!!!!") # ================================================= # main # ================================================= if __name__ == "__main__": try: PROJECT_ROOT = get_project_root() if not os.path.isdir(PROJECT_ROOT): raise RuntimeError(f"PROJECT_ROOT 不存在: {PROJECT_ROOT}") xpr_file = find_latest_xpr_in_dir(PROJECT_ROOT) config_file = os.path.join(os.getcwd(), "config.txt") if not os.path.exists(config_file): raise FileNotFoundError("当前目录下未找到 config.txt") print(f"使用 xpr 文件 : {xpr_file}") print(f"使用 config 文件: {config_file}") print(f"项目根目录 : {PROJECT_ROOT}") extract_from_xpr_and_copy(xpr_file, config_file, PROJECT_ROOT) except Exception as e: print("程序执行失败:") print(e) finally: input("\n按回车键关闭窗口...")