Python调用Vivado自动化:subprocess+Tcl脚本,一键综合实现生成比特流
Vivado是Xilinx FPGA的主要开发工具。通过Python调用Vivado的Tcl接口,可以实现工程创建、综合、实现、生成比特流等操作的自动化,提高开发效率。
1. Vivado命令行基础
# Vivado命令行模式
vivado -mode batch -source script.tcl # 批处理模式
vivado -mode tcl # Tcl交互模式
vivado -mode gui # GUI模式
# 常用参数
vivado -mode batch -source script.tcl -log run.log -journal run.jou
vivado -mode batch -source script.tcl -notrace # 不显示Tcl命令
# 环境变量
# Windows: 添加 Vivado安装路径\bin 到PATH
# 例如: C:\Xilinx\Vivado\2023.1\bin
# 基本Tcl命令示例 (script.tcl)
# 创建工程
create_project my_project ./my_project -part xc7a35tcpg236-1
# 添加源文件
add_files -norecurse {./src/top.v ./src/module1.v}
add_files -fileset constrs_1 -norecurse ./constraints/pins.xdc
# 设置顶层模块
set_property top top_module [current_fileset]
# 运行综合
launch_runs synth_1 -jobs 4
wait_on_run synth_1
# 运行实现
launch_runs impl_1 -jobs 4
wait_on_run impl_1
# 生成比特流
launch_runs impl_1 -to_step write_bitstream -jobs 4
wait_on_run impl_1
# 关闭工程
close_project
2. Python调用Vivado
import subprocess
import os
from pathlib import Path
class VivadoRunner:
"""Vivado运行器"""
def __init__(self, vivado_path: str = None):
"""
初始化
Args:
vivado_path: Vivado可执行文件路径,如果为None则从PATH查找
"""
if vivado_path:
self.vivado_path = vivado_path
else:
# 尝试从PATH查找
self.vivado_path = 'vivado'
self.check_vivado()
def check_vivado(self):
"""检查Vivado是否可用"""
try:
result = subprocess.run(
[self.vivado_path, '-version'],
capture_output=True,
text=True,
timeout=30
)
if result.returncode == 0:
version = result.stdout.strip().split('\n')[0]
print(f"Vivado版本:{version}")
else:
raise RuntimeError("Vivado返回错误")
except FileNotFoundError:
raise RuntimeError(f"找不到Vivado:{self.vivado_path}")
except subprocess.TimeoutExpired:
raise RuntimeError("Vivado响应超时")
def run_tcl(self, tcl_script: str, log_file: str = None,
working_dir: str = None) -> subprocess.CompletedProcess:
"""
运行Tcl脚本
Args:
tcl_script: Tcl脚本路径
log_file: 日志文件路径
working_dir: 工作目录
"""
cmd = [
self.vivado_path,
'-mode', 'batch',
'-source', tcl_script,
'-notrace'
]
if log_file:
cmd.extend(['-log', log_file])
print(f"运行Vivado:{' '.join(cmd)}")
result = subprocess.run(
cmd,
cwd=working_dir,
capture_output=True,
text=True
)
if result.returncode != 0:
print(f"Vivado错误:\n{result.stderr}")
return result
def run_tcl_commands(self, commands: list, working_dir: str = None) -> subprocess.CompletedProcess:
"""
运行Tcl命令列表
Args:
commands: Tcl命令列表
working_dir: 工作目录
"""
# 创建临时Tcl脚本
tcl_content = '\n'.join(commands)
tcl_file = Path(working_dir or '.') / '_temp_script.tcl'
tcl_file.write_text(tcl_content, encoding='utf-8')
try:
result = self.run_tcl(str(tcl_file), working_dir=working_dir)
finally:
tcl_file.unlink() # 删除临时文件
return result
# 使用示例
def example():
vivado = VivadoRunner()
# 运行Tcl脚本
result = vivado.run_tcl('build.tcl', log_file='build.log')
# 运行Tcl命令
commands = [
'puts "Hello from Vivado"',
'puts [version]',
]
result = vivado.run_tcl_commands(commands)
print(result.stdout)
3. Tcl脚本生成
from pathlib import Path
from typing import List, Dict, Optional
from dataclasses import dataclass, field
@dataclass
class VivadoProject:
"""Vivado工程配置"""
name: str
part: str
top_module: str
project_dir: str = '.'
source_files: List[str] = field(default_factory=list)
constraint_files: List[str] = field(default_factory=list)
ip_files: List[str] = field(default_factory=list)
generics: Dict[str, str] = field(default_factory=dict)
jobs: int = 4
class TclGenerator:
"""Tcl脚本生成器"""
def __init__(self, project: VivadoProject):
self.project = project
self.commands = []
def add_command(self, cmd: str):
"""添加命令"""
self.commands.append(cmd)
def generate_create_project(self):
"""生成创建工程命令"""
proj_path = Path(self.project.project_dir) / self.project.name
self.add_command(f'# 创建工程')
self.add_command(f'create_project {self.project.name} {proj_path} -part {self.project.part} -force')
def generate_add_sources(self):
"""生成添加源文件命令"""
if self.project.source_files:
self.add_command(f'\n# 添加源文件')
files = ' '.join(f'{{{f}}}' for f in self.project.source_files)
self.add_command(f'add_files -norecurse {files}')
if self.project.constraint_files:
self.add_command(f'\n# 添加约束文件')
files = ' '.join(f'{{{f}}}' for f in self.project.constraint_files)
self.add_command(f'add_files -fileset constrs_1 -norecurse {files}')
if self.project.ip_files:
self.add_command(f'\n# 添加IP文件')
for ip_file in self.project.ip_files:
self.add_command(f'import_ip -srcset sources_1 {{{ip_file}}}')
def generate_set_top(self):
"""生成设置顶层模块命令"""
self.add_command(f'\n# 设置顶层模块')
self.add_command(f'set_property top {self.project.top_module} [current_fileset]')
def generate_set_generics(self):
"""生成设置泛型参数命令"""
if self.project.generics:
self.add_command(f'\n# 设置泛型参数')
generics_str = ' '.join(f'{k}={v}' for k, v in self.project.generics.items())
self.add_command(f'set_property generic {{{generics_str}}} [current_fileset]')
def generate_synthesis(self):
"""生成综合命令"""
self.add_command(f'\n# 运行综合')
self.add_command(f'launch_runs synth_1 -jobs {self.project.jobs}')
self.add_command(f'wait_on_run synth_1')
self.add_command(f'')
self.add_command(f'# 检查综合结果')
self.add_command(f'if {{[get_property PROGRESS [get_runs synth_1]] != "100%"}} {{')
self.add_command(f' puts "ERROR: 综合失败"')
self.add_command(f' exit 1')
self.add_command(f'}}')
def generate_implementation(self):
"""生成实现命令"""
self.add_command(f'\n# 运行实现')
self.add_command(f'launch_runs impl_1 -jobs {self.project.jobs}')
self.add_command(f'wait_on_run impl_1')
self.add_command(f'')
self.add_command(f'# 检查实现结果')
self.add_command(f'if {{[get_property PROGRESS [get_runs impl_1]] != "100%"}} {{')
self.add_command(f' puts "ERROR: 实现失败"')
self.add_command(f' exit 1')
self.add_command(f'}}')
def generate_bitstream(self):
"""生成比特流命令"""
self.add_command(f'\n# 生成比特流')
self.add_command(f'launch_runs impl_1 -to_step write_bitstream -jobs {self.project.jobs}')
self.add_command(f'wait_on_run impl_1')
def generate_reports(self):
"""生成报告命令"""
self.add_command(f'\n# 打开实现结果')
self.add_command(f'open_run impl_1')
self.add_command(f'')
self.add_command(f'# 生成报告')
self.add_command(f'report_timing_summary -file timing_summary.rpt')
self.add_command(f'report_utilization -file utilization.rpt')
self.add_command(f'report_power -file power.rpt')
def generate_close(self):
"""生成关闭工程命令"""
self.add_command(f'\n# 关闭工程')
self.add_command(f'close_project')
self.add_command(f'puts "构建完成"')
def generate_full_build(self) -> str:
"""生成完整构建脚本"""
self.commands = []
self.add_command('# Vivado自动构建脚本')
self.add_command(f'# 生成时间:{__import__("datetime").datetime.now()}')
self.add_command('')
self.generate_create_project()
self.generate_add_sources()
self.generate_set_top()
self.generate_set_generics()
self.generate_synthesis()
self.generate_implementation()
self.generate_bitstream()
self.generate_reports()
self.generate_close()
return '\n'.join(self.commands)
def save(self, filename: str):
"""保存Tcl脚本"""
script = self.generate_full_build()
Path(filename).write_text(script, encoding='utf-8')
print(f"Tcl脚本已保存到:{filename}")
# 使用示例
def generate_build_script():
project = VivadoProject(
name='my_fpga_project',
part='xc7a35tcpg236-1',
top_module='top',
project_dir='./build',
source_files=[
'./src/top.v',
'./src/uart.v',
'./src/spi.v'
],
constraint_files=[
'./constraints/pins.xdc',
'./constraints/timing.xdc'
],
generics={
'CLK_FREQ': '100000000',
'BAUD_RATE': '115200'
},
jobs=8
)
generator = TclGenerator(project)
generator.save('build.tcl')
# generate_build_script()
4. 工程自动化
import subprocess
import os
import re
from pathlib import Path
from datetime import datetime
from dataclasses import dataclass
from typing import Optional, Dict
@dataclass
class BuildResult:
"""构建结果"""
success: bool
duration: float
synth_status: str
impl_status: str
timing_met: bool
wns: float # Worst Negative Slack
tns: float # Total Negative Slack
utilization: Dict[str, float]
bitstream_path: Optional[str]
log_path: str
class VivadoBuilder:
"""Vivado自动构建器"""
def __init__(self, vivado_path: str = 'vivado'):
self.vivado_path = vivado_path
self.build_dir = Path('build')
self.log_dir = Path('logs')
def setup_directories(self):
"""创建目录"""
self.build_dir.mkdir(exist_ok=True)
self.log_dir.mkdir(exist_ok=True)
def build(self, project: 'VivadoProject') -> BuildResult:
"""执行构建"""
self.setup_directories()
# 生成Tcl脚本
generator = TclGenerator(project)
tcl_file = self.build_dir / 'build.tcl'
generator.save(str(tcl_file))
# 运行Vivado
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
log_file = self.log_dir / f'build_{timestamp}.log'
start_time = datetime.now()
result = subprocess.run(
[self.vivado_path, '-mode', 'batch', '-source', str(tcl_file)],
cwd=str(self.build_dir),
capture_output=True,
text=True
)
duration = (datetime.now() - start_time).total_seconds()
# 保存日志
log_file.write_text(result.stdout + '\n' + result.stderr, encoding='utf-8')
# 解析结果
build_result = self._parse_result(result, duration, str(log_file), project)
return build_result
def _parse_result(self, result: subprocess.CompletedProcess,
duration: float, log_path: str,
project: 'VivadoProject') -> BuildResult:
"""解析构建结果"""
success = result.returncode == 0
# 解析时序
timing_met = True
wns = 0.0
tns = 0.0
timing_file = self.build_dir / project.name / f'{project.name}.runs' / 'impl_1' / 'timing_summary.rpt'
if timing_file.exists():
timing_content = timing_file.read_text()
wns_match = re.search(r'WNS\(ns\)\s*:\s*([-\d.]+)', timing_content)
tns_match = re.search(r'TNS\(ns\)\s*:\s*([-\d.]+)', timing_content)
if wns_match:
wns = float(wns_match.group(1))
if tns_match:
tns = float(tns_match.group(1))
timing_met = wns >= 0
# 解析资源利用率
utilization = {}
util_file = self.build_dir / project.name / f'{project.name}.runs' / 'impl_1' / 'utilization.rpt'
if util_file.exists():
utilization = self._parse_utilization(util_file.read_text())
# 查找比特流
bitstream_path = None
bit_file = self.build_dir / project.name / f'{project.name}.runs' / 'impl_1' / f'{project.top_module}.bit'
if bit_file.exists():
bitstream_path = str(bit_file)
return BuildResult(
success=success,
duration=duration,
synth_status='完成' if success else '失败',
impl_status='完成' if success else '失败',
timing_met=timing_met,
wns=wns,
tns=tns,
utilization=utilization,
bitstream_path=bitstream_path,
log_path=log_path
)
def _parse_utilization(self, content: str) -> Dict[str, float]:
"""解析资源利用率"""
utilization = {}
patterns = {
'LUT': r'Slice LUTs\s*\|\s*(\d+)\s*\|\s*\d+\s*\|\s*([\d.]+)',
'FF': r'Slice Registers\s*\|\s*(\d+)\s*\|\s*\d+\s*\|\s*([\d.]+)',
'BRAM': r'Block RAM Tile\s*\|\s*(\d+)\s*\|\s*\d+\s*\|\s*([\d.]+)',
'DSP': r'DSPs\s*\|\s*(\d+)\s*\|\s*\d+\s*\|\s*([\d.]+)',
}
for name, pattern in patterns.items():
match = re.search(pattern, content)
if match:
utilization[name] = float(match.group(2))
return utilization
def print_result(self, result: BuildResult):
"""打印构建结果"""
print("\n" + "="*50)
print("构建结果")
print("="*50)
print(f"状态:{'成功' if result.success else '失败'}")
print(f"耗时:{result.duration:.1f} 秒")
print(f"时序:{'满足' if result.timing_met else '不满足'}")
print(f"WNS:{result.wns:.3f} ns")
print(f"TNS:{result.tns:.3f} ns")
print(f"\n资源利用率:")
for name, value in result.utilization.items():
print(f" {name}: {value:.1f}%")
if result.bitstream_path:
print(f"\n比特流:{result.bitstream_path}")
print(f"日志:{result.log_path}")
print("="*50)
5. 报告解析
import re
from pathlib import Path
from dataclasses import dataclass
from typing import List, Dict, Optional
@dataclass
class TimingPath:
"""时序路径"""
slack: float
source: str
destination: str
requirement: float
data_path_delay: float
@dataclass
class TimingReport:
"""时序报告"""
wns: float
tns: float
whs: float # Worst Hold Slack
ths: float # Total Hold Slack
timing_met: bool
critical_paths: List[TimingPath]
class VivadoReportParser:
"""Vivado报告解析器"""
def parse_timing_summary(self, filepath: str) -> TimingReport:
"""解析时序摘要报告"""
content = Path(filepath).read_text()
# 解析WNS/TNS
wns = self._extract_value(content, r'WNS\(ns\)\s*:\s*([-\d.]+)')
tns = self._extract_value(content, r'TNS\(ns\)\s*:\s*([-\d.]+)')
whs = self._extract_value(content, r'WHS\(ns\)\s*:\s*([-\d.]+)')
ths = self._extract_value(content, r'THS\(ns\)\s*:\s*([-\d.]+)')
timing_met = wns >= 0 and whs >= 0
# 解析关键路径
critical_paths = self._parse_critical_paths(content)
return TimingReport(
wns=wns,
tns=tns,
whs=whs,
ths=ths,
timing_met=timing_met,
critical_paths=critical_paths
)
def parse_utilization(self, filepath: str) -> Dict[str, Dict]:
"""解析资源利用率报告"""
content = Path(filepath).read_text()
utilization = {}
# 解析各类资源
patterns = {
'LUT': r'Slice LUTs\*?\s*\|\s*(\d+)\s*\|\s*(\d+)\s*\|\s*([\d.]+)',
'LUTRAM': r'LUT as Memory\s*\|\s*(\d+)\s*\|\s*(\d+)\s*\|\s*([\d.]+)',
'FF': r'Slice Registers\s*\|\s*(\d+)\s*\|\s*(\d+)\s*\|\s*([\d.]+)',
'BRAM': r'Block RAM Tile\s*\|\s*([\d.]+)\s*\|\s*(\d+)\s*\|\s*([\d.]+)',
'DSP': r'DSPs\s*\|\s*(\d+)\s*\|\s*(\d+)\s*\|\s*([\d.]+)',
'IO': r'Bonded IOB\s*\|\s*(\d+)\s*\|\s*(\d+)\s*\|\s*([\d.]+)',
}
for name, pattern in patterns.items():
match = re.search(pattern, content)
if match:
utilization[name] = {
'used': float(match.group(1)),
'available': int(match.group(2)),
'percentage': float(match.group(3))
}
return utilization
def parse_power(self, filepath: str) -> Dict[str, float]:
"""解析功耗报告"""
content = Path(filepath).read_text()
power = {}
# 总功耗
total_match = re.search(r'Total On-Chip Power \(W\)\s*\|\s*([\d.]+)', content)
if total_match:
power['total'] = float(total_match.group(1))
# 动态功耗
dynamic_match = re.search(r'Dynamic \(W\)\s*\|\s*([\d.]+)', content)
if dynamic_match:
power['dynamic'] = float(dynamic_match.group(1))
# 静态功耗
static_match = re.search(r'Device Static \(W\)\s*\|\s*([\d.]+)', content)
if static_match:
power['static'] = float(static_match.group(1))
return power
def _extract_value(self, content: str, pattern: str) -> float:
"""提取数值"""
match = re.search(pattern, content)
return float(match.group(1)) if match else 0.0
def _parse_critical_paths(self, content: str) -> List[TimingPath]:
"""解析关键路径"""
paths = []
# 简化的路径解析
path_pattern = r'Slack\s*:\s*([-\d.]+)ns.*?Source:\s*(\S+).*?Destination:\s*(\S+)'
matches = re.finditer(path_pattern, content, re.DOTALL)
for match in matches:
paths.append(TimingPath(
slack=float(match.group(1)),
source=match.group(2),
destination=match.group(3),
requirement=0,
data_path_delay=0
))
if len(paths) >= 10: # 只取前10条
break
return paths
def generate_summary(self, project_dir: str) -> str:
"""生成综合报告摘要"""
report = []
report.append("="*60)
report.append("Vivado构建报告摘要")
report.append("="*60)
# 时序报告
timing_file = Path(project_dir) / 'timing_summary.rpt'
if timing_file.exists():
timing = self.parse_timing_summary(str(timing_file))
report.append("\n时序分析:")
report.append(f" WNS: {timing.wns:.3f} ns")
report.append(f" TNS: {timing.tns:.3f} ns")
report.append(f" WHS: {timing.whs:.3f} ns")
report.append(f" 状态: {'满足' if timing.timing_met else '不满足'}")
# 资源利用率
util_file = Path(project_dir) / 'utilization.rpt'
if util_file.exists():
util = self.parse_utilization(str(util_file))
report.append("\n资源利用率:")
for name, data in util.items():
report.append(f" {name}: {data['used']:.0f}/{data['available']} ({data['percentage']:.1f}%)")
# 功耗
power_file = Path(project_dir) / 'power.rpt'
if power_file.exists():
power = self.parse_power(str(power_file))
report.append("\n功耗估计:")
for name, value in power.items():
report.append(f" {name}: {value:.3f} W")
report.append("="*60)
return '\n'.join(report)
6. 批量处理
import json
from pathlib import Path
from concurrent.futures import ThreadPoolExecutor, as_completed
from dataclasses import dataclass, asdict
from typing import List, Dict
from datetime import datetime
@dataclass
class BuildConfig:
"""构建配置"""
name: str
part: str
top_module: str
sources: List[str]
constraints: List[str]
generics: Dict[str, str]
class BatchBuilder:
"""批量构建器"""
def __init__(self, vivado_path: str = 'vivado', max_workers: int = 2):
self.vivado_path = vivado_path
self.max_workers = max_workers
self.results = []
def load_configs(self, config_file: str) -> List[BuildConfig]:
"""从JSON文件加载配置"""
with open(config_file, 'r', encoding='utf-8') as f:
data = json.load(f)
configs = []
for item in data['builds']:
configs.append(BuildConfig(**item))
return configs
def build_single(self, config: BuildConfig) -> Dict:
"""构建单个配置"""
print(f"开始构建:{config.name}")
start_time = datetime.now()
try:
project = VivadoProject(
name=config.name,
part=config.part,
top_module=config.top_module,
source_files=config.sources,
constraint_files=config.constraints,
generics=config.generics
)
builder = VivadoBuilder(self.vivado_path)
result = builder.build(project)
return {
'name': config.name,
'success': result.success,
'duration': result.duration,
'timing_met': result.timing_met,
'wns': result.wns,
'utilization': result.utilization,
'error': None
}
except Exception as e:
return {
'name': config.name,
'success': False,
'duration': (datetime.now() - start_time).total_seconds(),
'timing_met': False,
'wns': 0,
'utilization': {},
'error': str(e)
}
def build_all(self, configs: List[BuildConfig]) -> List[Dict]:
"""并行构建所有配置"""
results = []
with ThreadPoolExecutor(max_workers=self.max_workers) as executor:
futures = {executor.submit(self.build_single, config): config
for config in configs}
for future in as_completed(futures):
config = futures[future]
try:
result = future.result()
results.append(result)
status = '成功' if result['success'] else '失败'
print(f"完成:{config.name} - {status}")
except Exception as e:
results.append({
'name': config.name,
'success': False,
'error': str(e)
})
self.results = results
return results
def generate_report(self) -> str:
"""生成批量构建报告"""
report = []
report.append("="*70)
report.append("批量构建报告")
report.append(f"时间:{datetime.now()}")
report.append("="*70)
success_count = sum(1 for r in self.results if r['success'])
report.append(f"\n总计:{len(self.results)} 个配置")
report.append(f"成功:{success_count}")
report.append(f"失败:{len(self.results) - success_count}")
report.append("\n详细结果:")
report.append("-"*70)
for result in self.results:
status = '✓' if result['success'] else '✗'
timing = '满足' if result.get('timing_met') else '不满足'
report.append(f"{status} {result['name']}")
report.append(f" 耗时:{result.get('duration', 0):.1f}s")
report.append(f" 时序:{timing}, WNS={result.get('wns', 0):.3f}ns")
if result.get('error'):
report.append(f" 错误:{result['error']}")
report.append("="*70)
return '\n'.join(report)
def save_results(self, filename: str):
"""保存结果到JSON"""
with open(filename, 'w', encoding='utf-8') as f:
json.dump(self.results, f, indent=2, ensure_ascii=False)
# 配置文件示例 (builds.json)
example_config = """
{
"builds": [
{
"name": "design_100mhz",
"part": "xc7a35tcpg236-1",
"top_module": "top",
"sources": ["src/top.v", "src/core.v"],
"constraints": ["constraints/pins.xdc"],
"generics": {"CLK_FREQ": "100000000"}
},
{
"name": "design_150mhz",
"part": "xc7a35tcpg236-1",
"top_module": "top",
"sources": ["src/top.v", "src/core.v"],
"constraints": ["constraints/pins.xdc"],
"generics": {"CLK_FREQ": "150000000"}
}
]
}
"""
7. 实战案例
案例:完整的FPGA构建系统
"""
实战案例:FPGA自动构建系统
"""
import argparse
import json
from pathlib import Path
from datetime import datetime
class FPGABuildSystem:
"""FPGA构建系统"""
def __init__(self, config_file: str = 'fpga_config.json'):
self.config = self._load_config(config_file)
self.vivado_path = self.config.get('vivado_path', 'vivado')
self.output_dir = Path(self.config.get('output_dir', 'output'))
self.output_dir.mkdir(exist_ok=True)
def _load_config(self, config_file: str) -> dict:
"""加载配置"""
if Path(config_file).exists():
with open(config_file, 'r', encoding='utf-8') as f:
return json.load(f)
return {}
def build(self, target: str = 'all'):
"""执行构建"""
print(f"开始构建:{target}")
print(f"时间:{datetime.now()}")
project = VivadoProject(
name=self.config['project_name'],
part=self.config['part'],
top_module=self.config['top_module'],
source_files=self.config['sources'],
constraint_files=self.config['constraints'],
generics=self.config.get('generics', {})
)
builder = VivadoBuilder(self.vivado_path)
result = builder.build(project)
builder.print_result(result)
# 保存结果
self._save_result(result)
return result.success
def clean(self):
"""清理构建文件"""
import shutil
dirs_to_clean = ['build', '.Xil']
for dir_name in dirs_to_clean:
dir_path = Path(dir_name)
if dir_path.exists():
shutil.rmtree(dir_path)
print(f"已删除:{dir_path}")
def program(self, bitstream: str = None):
"""下载比特流"""
if bitstream is None:
# 查找最新的比特流
bit_files = list(Path('build').rglob('*.bit'))
if not bit_files:
print("找不到比特流文件")
return False
bitstream = str(bit_files[0])
tcl_commands = [
'open_hw_manager',
'connect_hw_server',
'open_hw_target',
'set_property PROGRAM.FILE {%s} [current_hw_device]' % bitstream,
'program_hw_devices [current_hw_device]',
'close_hw_manager'
]
runner = VivadoRunner(self.vivado_path)
result = runner.run_tcl_commands(tcl_commands)
return result.returncode == 0
def _save_result(self, result: BuildResult):
"""保存构建结果"""
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
result_file = self.output_dir / f'build_result_{timestamp}.json'
result_dict = {
'timestamp': timestamp,
'success': result.success,
'duration': result.duration,
'timing_met': result.timing_met,
'wns': result.wns,
'tns': result.tns,
'utilization': result.utilization,
'bitstream': result.bitstream_path
}
with open(result_file, 'w', encoding='utf-8') as f:
json.dump(result_dict, f, indent=2)
def main():
parser = argparse.ArgumentParser(description='FPGA构建系统')
parser.add_argument('command', choices=['build', 'clean', 'program'],
help='执行的命令')
parser.add_argument('-c', '--config', default='fpga_config.json',
help='配置文件路径')
parser.add_argument('-b', '--bitstream', help='比特流文件路径')
args = parser.parse_args()
system = FPGABuildSystem(args.config)
if args.command == 'build':
success = system.build()
exit(0 if success else 1)
elif args.command == 'clean':
system.clean()
elif args.command == 'program':
success = system.program(args.bitstream)
exit(0 if success else 1)
if __name__ == '__main__':
main()
8. 常见问题
❌ 问题1:找不到Vivado
# 解决:设置完整路径
vivado_path = r'C:\Xilinx\Vivado\2023.1\bin\vivado.bat'
# 或添加到环境变量
import os
os.environ['PATH'] = r'C:\Xilinx\Vivado\2023.1\bin;' + os.environ['PATH']
❌ 问题2:Tcl脚本编码问题
# 解决:使用UTF-8编码保存Tcl脚本
Path('script.tcl').write_text(content, encoding='utf-8')
# 路径使用正斜杠
path = path.replace('\\', '/')
❌ 问题3:License问题
# 在Tcl脚本中检查License
if {[catch {check_license -quiet} result]} {
puts "License错误:$result"
exit 1
}
9. 总结
🔑 核心要点
| 知识点 | 要点 |
|---|---|
| 命令行模式 | vivado -mode batch -source script.tcl |
| Python调用 | subprocess.run() |
| Tcl脚本生成 | 自动生成构建脚本 |
| 报告解析 | 正则表达式解析时序、资源报告 |
| 批量处理 | 并行构建多个配置 |
✅ 学习检查清单
- 能使用命令行运行Vivado
- 能用Python调用Vivado
- 能生成Tcl构建脚本
- 能解析Vivado报告
- 能实现批量构建
📖 下一步学习
掌握了Vivado自动化后,让我们学习测试向量生成:
常见问题 FAQ
💬 Vivado的Tcl和Python怎么配合?
Python负责生成Tcl脚本和调用Vivado,Vivado内部执行Tcl。两者通过文件和subprocess交互。不要试图在Python里直接调用Vivado的API,走Tcl是官方推荐方式。
💬 怎么判断综合/实现是否成功?
检查Vivado的返回码(0=成功),同时解析日志文件中的ERROR和CRITICAL WARNING。建议把关键指标(时序裕量、资源利用率)也自动提取出来。
� 系列导航
- 上一篇:25 - Python数据可视化
- 当前:26 - Python调用Vivado自动化
- 下一篇:27 - Python生成测试向量