Vivado 的原生脚本语言是 Tcl(Tool Command Language)。每个 GUI 操作都有等效的 Tcl 命令,会显示在 Tcl Console 中。这意味着你在 Vivado GUI 中执行的任何工作流都可以被捕获、脚本化并自动化,以实现可重复的构建。
| 因素 | GUI | Tcl 脚本 |
|---|---|---|
| 可重复性 | 手动步骤,容易遗漏 | 每次执行完全相同的命令 |
| 速度(重复构建) | 每次通过向导点击 | 一条命令:vivado -source build.tcl |
| 版本控制 | 项目 .xpr 文件是二进制格式 |
.tcl 文件是纯文本,可 diff |
| CI/CD 集成 | 不可能 | 简单:批处理模式运行 |
| 团队协作 | "在我机器上没问题" | 通过脚本获得完全一致的环境 |
| 参数扫描 | 手动修改后重新运行 | 在 Tcl 中循环遍历参数值 |
每个 Vivado 项目都有一个 .jou(日志)文件,记录了执行的每条 Tcl 命令。文件位置:
<project_dir>/<project_name>.jou
从 GUI 会话提取干净的脚本:
# 去除时间戳和交互式专用命令
grep -v '^#' vivado.jou | grep -v '^$' | \
grep -v 'create_gui_project' | \
grep -v 'update_compile_order' > build_clean.tcl
# 批处理模式运行 Tcl 脚本(无 GUI)
vivado -mode batch -source build.tcl
# 带日志记录运行
vivado -mode batch -source build.tcl -log build/vivado.log -journal build/vivado.jou
# 终端模式运行(Tcl 交互 shell,无 GUI)
vivado -mode tcl
Vivado 批处理模式在遇到严重错误时返回非零值,但许多"警告"不会影响退出码。要捕获所有问题:
# 在 build.tcl 末尾添加:
if {[catch {write_bitstream -force build/fpga_top.bit} errmsg]} {
puts "错误: 比特流生成失败: $errmsg"
exit 1
}
exit 0
检查日志中关键短语的包装脚本:
#!/bin/bash
# build_fpga.sh
vivado -mode batch -source build.tcl -log build/vivado.log
VIVADO_EXIT=$?
# 检查不会触发非零退出的错误字符串
if grep -qi "ERROR:\[DRC\|ERROR:\[Vivado 12-\|implementation ERROR" build/vivado.log; then
echo "致命错误: Vivado 日志中发现严重错误"
exit 1
fi
if [ $VIVADO_EXIT -ne 0 ]; then
echo "致命错误: Vivado 退出码为 $VIVADO_EXIT"
exit $VIVADO_EXIT
fi
echo "构建成功"
# 创建新项目(仅 RTL,无开发板)
create_project fpga_top /home/user/work/FPGA_Prj/Project/WebServer/build/vivado \
-part xc7k325tffg900-2 -force
# 或创建内存项目(更快,无磁盘 I/O)
create_project -in_memory -part xc7k325tffg900-2
# 添加源文件
add_files -norecurse rtl/fpga_top.v
add_files -norecurse rtl/wb_interconnect.v
add_files -norecurse rtl/uart_core.v
# 从文件列表添加文件
set fp [open "verible.filelist" r]
while {[gets $fp line] >= 0} {
if {[string match "#*" $line]} {continue}
if {[string length $line] == 0} {continue}
if {[string match "sim/*" $line]} {continue}
add_files -norecurse $line
}
close $fp
# 添加约束
add_files -fileset constrs_1 -norecurse xdc/pinout.xdc
add_files -fileset constrs_1 -norecurse xdc/timing.xdc
# 设置顶层模块
set_property top fpga_top [current_fileset]
# 基本综合
launch_runs synth_1 -jobs 16
wait_on_run synth_1
# 检查严重警告
set crit_warn [get_property STATS.CRITICAL_WARNINGS [get_runs synth_1]]
if {$crit_warn > 0} {
puts "警告: 综合过程中产生 $crit_warn 个严重警告"
# 可选:报告它们
open_run synth_1
report_critical_warnings -file build/synth_critical_warnings.txt
}
# 使用策略综合
set_property strategy Flow_AreaOptimized_high [get_runs synth_1] ;# 面积优化
# set_property strategy Flow_PerfOptimized_high [get_runs synth_1] ;# 速度优化
# set_property strategy Flow_AlternateRoutability [get_runs synth_1] ;# 拥塞优化
# 综合选项
set_property -name {STEPS.SYNTH_DESIGN.ARGS.MORE OPTIONS} -value \
{-fsm_extraction one_hot -resource_sharing off -no_lc} \
-objects [get_runs synth_1]
# 综合后报告
open_run synth_1
report_utilization -file build/synth_utilization.rpt
report_timing_summary -file build/synth_timing.rpt
# 启动实现
launch_runs impl_1 -jobs 16
wait_on_run impl_1
# 实现策略
set_property strategy Performance_ExplorePostRoutePhysOpt [get_runs impl_1]
# 实现后报告
open_run impl_1
report_utilization -file build/impl_utilization.rpt
report_timing_summary -file build/impl_timing.rpt
report_clock_utilization -file build/impl_clocks.rpt
report_power -file build/impl_power.rpt
# 写比特流
launch_runs impl_1 -to_step write_bitstream -jobs 16
wait_on_run impl_1
# 或直接使用 write_bitstream 命令
write_bitstream -force build/fpga_top.bit
# 额外输出格式用于调试
write_debug_probes -force build/fpga_top.ltx ;# ILA 调试探针
# Flash 烧录用的二进制格式
write_cfgmem -format bin -interface spix4 -size 128 \
-loadbit "up 0x0 build/fpga_top.bit" \
-file build/fpga_top.bin
# 生成用于 SPI Flash 的 MCS
write_cfgmem -format mcs -interface spix4 -size 128 \
-loadbit "up 0x0 build/fpga_top.bit" \
-file build/fpga_top.mcs
该脚本通过单条命令实现从源文件到比特流的完整构建。
# /home/user/work/FPGA_Prj/Project/WebServer/build.tcl
# 一键综合 + 实现 + 比特流
# 用法: vivado -mode batch -source build.tcl
set project_name "fpga_top"
set part_name "xc7k325tffg900-2"
set top_module "fpga_top"
set output_dir "build"
set jobs 16
# ── 创建输出目录 ──
file mkdir ${output_dir}
# ── 创建项目 ──
puts "=== 创建项目: ${project_name} ==="
create_project -in_memory -part ${part_name}
# ── 从 verible.filelist 读取源文件 ──
puts "=== 添加源文件 ==="
if {[file exists "verible.filelist"]} {
set fp [open "verible.filelist" r]
while {[gets $fp line] >= 0} {
set line [string trim $line]
if {$line eq ""} {continue}
if {[string match "#*" $line]} {continue}
if {[string match "sim/*" $line]} {continue}
puts " 添加: $line"
add_files -norecurse $line
}
close $fp
} else {
puts "错误: 未找到 verible.filelist"
exit 1
}
# ── 添加约束 ──
puts "=== 添加约束 ==="
if {[file exists "xdc/pinout.xdc"]} {
add_files -fileset constrs_1 -norecurse xdc/pinout.xdc
}
if {[file exists "xdc/timing.xdc"]} {
add_files -fileset constrs_1 -norecurse xdc/timing.xdc
}
# ── 设置顶层模块 ──
set_property top ${top_module} [current_fileset]
puts " 顶层模块: ${top_module}"
# ── 综合 ──
puts "\n=== 运行综合 (${jobs} 个并行任务) ==="
set_property strategy Flow_AreaOptimized_high [get_runs synth_1]
launch_runs synth_1 -jobs ${jobs}
wait_on_run synth_1
set synth_status [get_property STATUS [get_runs synth_1]]
if {$synth_status ne "synth_design Complete!"} {
puts "错误: 综合失败,状态: $synth_status"
exit 1
}
puts " 综合完成"
# ── 综合后检查 ──
open_run synth_1
set synth_crit [get_property STATS.CRITICAL_WARNINGS [get_runs synth_1]]
puts " 严重警告: $synth_crit"
report_utilization -file ${output_dir}/synth_utilization.rpt
report_timing_summary -file ${output_dir}/synth_timing.rpt
# ── 实现 ──
puts "\n=== 运行实现 (${jobs} 个并行任务) ==="
launch_runs impl_1 -jobs ${jobs}
wait_on_run impl_1
set impl_status [get_property STATUS [get_runs impl_1]]
if {$impl_status ne "route_design Complete!"} {
puts "错误: 实现失败,状态: $impl_status"
exit 1
}
puts " 实现完成"
# ── 实现后检查 ──
open_run impl_1
report_timing_summary -file ${output_dir}/impl_timing.rpt
report_utilization -file ${output_dir}/impl_utilization.rpt
report_power -file ${output_dir}/impl_power.rpt
# 检查时序收敛
set wns [get_property SLACK [get_timing_paths -max_paths 1 -setup]]
set ths [get_property SLACK [get_timing_paths -max_paths 1 -hold]]
puts " 建立时间 WNS: ${wns}"
puts " 保持时间 WNS: ${ths}"
if {$wns < -0.100} {
puts "错误: 建立时间违规 (WNS = ${wns} ns)"
exit 1
}
# ── 比特流生成 ──
puts "\n=== 生成比特流 ==="
launch_runs impl_1 -to_step write_bitstream -jobs ${jobs}
wait_on_run impl_1
set bit_status [get_property STATUS [get_runs impl_1]]
if {$bit_status ne "write_bitstream Complete!"} {
puts "错误: 比特流生成失败,状态: $bit_status"
exit 1
}
# 将比特流复制到输出目录
set bit_src [get_property DIRECTORY [get_runs impl_1]]
set bit_src "${bit_src}/${top_module}.bit"
file copy -force ${bit_src} ${output_dir}/${top_module}.bit
puts " 比特流: ${output_dir}/${top_module}.bit"
# ── 生成烧录文件 ──
puts "\n=== 生成 MCS Flash 镜像 ==="
set bin_file "${output_dir}/${top_module}.bin"
set mcs_file "${output_dir}/${top_module}.mcs"
write_cfgmem -format bin -interface spix4 -size 128 \
-loadbit "up 0x0 ${bit_src}" \
-file ${bin_file} -force
write_cfgmem -format mcs -interface spix4 -size 128 \
-loadbit "up 0x0 ${bit_src}" \
-file ${mcs_file} -force
puts " 二进制镜像: ${bin_file}"
puts " MCS 镜像: ${mcs_file}"
# ── 汇总 ──
puts "\n=== 构建完成 ==="
puts " 器件: ${part_name}"
puts " 资源利用率: [exec grep 'Slice LUTs' ${output_dir}/impl_utilization.rpt | head -1]"
puts " 建立 WNS: ${wns} ns"
puts " 保持 WNS: ${ths} ns"
puts " 比特流: ${output_dir}/${top_module}.bit"
close_project
exit 0
对于混合使用 Tcl、Verilator 和 Shell 工具的项目,将 Vivado 包装在 Makefile 中以实现一致的调用。
# /home/user/work/FPGA_Prj/Project/WebServer/Makefile
# 组合 Makefile: 综合、仿真、Lint、标签
PART := xc7k325tffg900-2
TOP := fpga_top
JOBS := 16
BITSTREAM := build/$(TOP).bit
# ── 默认目标 ──
.PHONY: all
all: lint synth
# ── Lint ──
.PHONY: lint
lint:
@echo "=== Verilator Lint ==="
verilator --lint-only -Wall -I rtl/ -I ../../ip_common/rtl/ \
-f verible.filelist --top-module $(TOP)
# ── 格式检查 ──
.PHONY: format-check
format-check:
verible-verilog-format --verify -f verible.filelist
# ── 就地格式化 ──
.PHONY: format
format:
verible-verilog-format --inplace -f verible.filelist
# ── 综合 (Vivado Tcl) ──
.PHONY: synth
synth:
@echo "=== Vivado 综合 ==="
source /home/user/tools/scripts/vivado_env.sh && \
vivado -mode batch -source build.tcl -log build/vivado.log
# ── 仅生成比特流(假设综合已完成) ──
.PHONY: bit
bit: $(BITSTREAM)
$(BITSTREAM): build.tcl
@echo "=== Vivado 比特流 ==="
source /home/user/tools/scripts/vivado_env.sh && \
vivado -mode batch -source build.tcl -log build/vivado.log
# ── 仿真 (Icarus) ──
.PHONY: sim
sim:
@echo "=== Icarus 仿真 ==="
@mkdir -p build
SOURCES=$$(grep -v '^#' verible.filelist | grep -v '^$$' | grep -v 'sim/'); \
iverilog -g2012 -I rtl/ -I ../../ip_common/rtl/ -o build/sim.vvp \
$$SOURCES sim/tb_$(TOP).v
vvp build/sim.vvp
# ── 仿真含波形 ──
.PHONY: wave
wave: sim
gtkwave build/sim.fst &
# ── 打快照标签 ──
.PHONY: tag
tag:
@TAG=snapshot-$$(date +%Y%m%d%H%M%S); \
echo "创建标签: $$TAG"; \
git tag -a "$$TAG" -m "Build snapshot: $$(date -Iseconds)"
# ── 清理 ──
.PHONY: clean
clean:
rm -rf build/
rm -rf *.jou *.log .Xil/
# ── 完整自检 (lint + synth + sim) ──
.PHONY: check
check:
@bash run_all.sh
# ── 帮助 ──
.PHONY: help
help:
@echo "目标:"
@echo " all — lint + 综合"
@echo " lint — Verilator lint 检查"
@echo " format — Verible 就地格式化"
@echo " format-check — 验证格式 (CI)"
@echo " synth — Vivado 综合 + 实现 + 比特流"
@echo " sim — Icarus 仿真"
@echo " wave — Icarus 仿真 + GTKWave"
@echo " tag — Git 快照标签"
@echo " clean — 删除构建产物"
@echo " check — run_all.sh 完整自检"
Vivado Tcl 命令在不同版本间会发生变化。编写版本自适应脚本。
set vivado_version [version -short]
puts "Vivado 版本: $vivado_version"
# 解析主版本号.次版本号
regexp {(\d+)\.(\d+)} $vivado_version -> major minor
if {$major >= 2024} {
# Vivado 2024.x 特有命令
set_param general.maxThreads 32
} elseif {$major >= 2022} {
# Vivado 2022.x / 2023.x
set_param general.maxThreads 16
} else {
puts "错误: Vivado 版本 $vivado_version 过旧 (需要 2022+)"
exit 1
}
| 问题 | 2023 之前 | 2023+ |
|---|---|---|
get_cells -hier 行为 |
返回层次化 Cell | 过滤语法变更;使用 -hierarchical |
report_timing 默认值 |
1 条路径 | 2024.1 中改为 10 条路径 |
write_bitstream 格式 |
仅 .bit |
可输出压缩 .bit |
create_project -in_memory |
不可用 | 可用 (2022.2+) |
wait_on_run 超时 |
无超时参数 | 2023.2 新增 -timeout |
# vivado_helpers.tcl — 版本无关的包装过程
proc safe_add_files {filelist} {
# 从 filelist 读取文件,跳过注释和空行
set fp [open $filelist r]
set files []
while {[gets $fp line] >= 0} {
set line [string trim $line]
if {$line eq "" || [string match "#*" $line]} {continue}
lappend files $line
}
close $fp
# 添加文件(跨 Vivado 版本工作)
foreach f $files {
if {[file exists $f]} {
add_files -norecurse $f
} else {
puts "警告: 文件未找到: $f"
}
}
}
proc safe_wait {run_name {timeout 0}} {
# 带可选超时的等待(向后兼容)
set v [version -short]
if {[string compare $v "2023.2"] >= 0} {
if {$timeout > 0} {
wait_on_run $run_name -timeout $timeout
} else {
wait_on_run $run_name
}
} else {
wait_on_run $run_name
}
}
proc report_timing_safe {args} {
# 安全的时序报告,带一致的路径数量
set num_paths 100
foreach {flag val} $args {
if {$flag eq "-max_paths"} {set num_paths $val}
}
report_timing_summary -max_paths $num_paths {*}$args
}
# program.tcl — 自动 FPGA 烧录
# 用法: vivado -mode batch -source program.tcl
set bitstream_file "build/fpga_top.bit"
set probe_file "build/fpga_top.ltx"
# 打开硬件管理器
open_hw_manager
connect_hw_server -url TCP:localhost:3121
# 查找第一个可用设备
set hw_targets [get_hw_targets]
if {[llength $hw_targets] == 0} {
puts "错误: 未找到硬件目标"
puts " JTAG 线缆是否已连接?"
puts " 检查: lsusb | grep -i xilinx"
exit 1
}
set hw_target [lindex $hw_targets 0]
puts "使用目标: $hw_target"
open_hw_target $hw_target
set hw_devices [get_hw_devices]
if {[llength $hw_devices] == 0} {
puts "错误: JTAG 链中未找到设备"
exit 1
}
set hw_device [lindex $hw_devices 0]
# 烧录设备
puts "正在烧录设备: $bitstream_file"
set_property PROGRAM.FILE $bitstream_file $hw_device
program_hw_devices $hw_device
# 可选:设置 ILA 调试探针
if {[file exists $probe_file]} {
puts "正在从 $probe_file 设置调试探针"
set_property PROBES.FILE $probe_file $hw_device
}
puts "烧录完成"
close_hw_manager
#!/bin/bash
# parse_vivado_log.sh — 从 Vivado 日志中提取关键指标
# 用法: ./parse_vivado_log.sh build/vivado.log
LOG="$1"
echo "=== Vivado 构建汇总 ==="
echo ""
# 时序
echo "时序:"
grep -A1 "Worst Negative Slack" "$LOG" | tail -1
grep -A1 "Worst Hold Slack" "$LOG" | tail -1
echo ""
# 资源利用率
echo "资源利用率:"
grep -E "Slice LUTs|Slice Registers|Block RAM|DSP" "$LOG" | head -20
echo ""
# 时钟频率
echo "时钟:"
grep -E "clock.*period|Clk.*frequency" "$LOG" | head -10
echo ""
# 严重警告
echo "严重警告:"
grep -c "CRITICAL WARNING" "$LOG"
echo ""
# 构建耗时
echo "构建耗时:"
grep -E "INFO.*Total.*time|INFO.*Time spent" "$LOG" | head -5
| 任务 | 命令 |
|---|---|
| 从文件列表创建项目 | vivado -mode batch -source build.tcl |
| 构建前检查格式 | verible-verilog-format --verify -f verible.filelist |
| 完整构建流程 | make synth 或 make bit |
| 运行自检 | make check (调用 run_all.sh) |
| 烧录 FPGA | vivado -mode batch -source program.tcl |
| 清理构建产物 | make clean |
| 捕获 GUI 工作流到脚本 | 从 <project>.jou 日志文件复制 |