339 lines
11 KiB
Python
339 lines
11 KiB
Python
|
|
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按回车键关闭窗口...")
|