Files
2026-07-13 17:55:17 +08:00

339 lines
11 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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按回车键关闭窗口...")