这一篇在干嘛?
你在 TD 图形界面里点的每个按钮,背后都是一条 TCL 命令。这一篇教你把这些命令”串”成脚本:先搞清楚命令在哪运行(Console 窗口、TD 命令行),再学会三种脚本模式(工程模式、非工程模式、single run)的标准写法,最后把最常用的命令分组讲透——工程管理、参数设置、文件输入输出、分步运行和生成位流。读完你就能写一份脚本,一条
source从建工程跑到出 bit 文件。时序约束与分析类命令见同系列第二篇:TCL 命令手册(2):时序约束与设计分析。
为什么 FPGA 工程师要会 TCL
先回答”不用会怎样”。只用 GUI 点鼠标跑流程,对小作业没问题,但会遇到三个麻烦:
- 重复劳动:每次改一行代码,都要重新走一遍 Add Sources → Synthesize → Place & Route → Generate Bitstream,五六步点击一次都不能少。
- 没法做”多方案对比”:FPGA 时序差一点时,标准做法是换 seed(随机种子)、换布局策略跑很多遍挑最好的,手动跑几十遍不现实。
- 没法接入自动化:竞赛/公司的持续集成流水线(每天晚上自动编译最新代码)只能靠命令行脚本驱动,GUI 无法无人值守。
TD 软件对此的答案是:整个设计流程全部开放为 TCL 命令。你既可以在软件的 Console 窗口单敲一条命令,也可以把几十条命令写进 .tcl 文件一次性执行。SWUG105 这本手册就是这些命令的官方字典——它很长(上百条命令),但常用的高频命令其实只有三十来条,本篇和下一篇就把它们按功能分组讲清楚。
一个重要的心智模型:TCL 脚本 = 你在 GUI 里的操作序列。GUI 里”新建工程时选器件 PH1A100SFG676”,对应脚本里 create_project test ./ -family ph1 -device PH1A100SFG676 -speed 2;GUI 里”双击 Run Place”,对应脚本里 place。看不懂 GUI 的操作时,反过来查命令手册往往更精确。
命令在哪运行:Console 与 TD 命令模式
TD 提供两个运行 TCL 的入口,对应两种使用场景。
入口一:GUI 里的 Console 窗口。 打开 TD 界面后,底部有 Console 窗口,可以直接敲单条命令。它的定位是”边操作边查询”:比如你想确认设计里有哪些时钟,敲一句 all_clocks -o 就能看到结果;GUI 某个操作做完后,Console 里也会回显对应的命令,这是自学命令语法的绝佳途径。

注意 Console 有个阶段限制:命令必须在”适当的阶段”运行。比如工程还没 open_run phy_1(打开布局布线结果)时,你不能在 Console 里跑综合阶段的分步命令(elaborate/optimize_rtl/optimize_gate 等)。简单说:当前打开的是哪个阶段的数据库(db),就只能操作那个阶段的数据。
入口二:TD 命令模式(无 GUI)。 这才是脚本自动化的主战场。
- Windows:开始菜单 → 所有程序 →
td_commands_prompt,打开一个预置好环境变量的 CMD 窗口。在这个窗口里可以直接敲单条 TCL 命令;要跑脚本则进入脚本所在目录,执行source demo.tcl。 - Linux:终端里运行
/td_install_dir/td.sh进入命令模式,同样用source test.tcl跑脚本;也可以一步到位,启动时直接带脚本参数:/td_install_dir/td.sh demo.tcl,适合写进 shell 脚本做无人值守编译。

常见坑:
td_commands_prompt不是普通 CMD,它预置了 TD 的环境变量。直接开一个普通 CMD 敲source会报”命令不认识”。source的相对路径以当前目录为准,跑脚本前先cd到脚本所在目录,避免”找不到文件”的迷茫。- Console 窗口里不要跨阶段执行命令(比如打开的是布局结果却想跑综合分步命令),TD 会直接拒绝。
三种脚本模式:工程模式、非工程模式、single run
官方手册第 3 章给出了三种完整的脚本骨架。它们的区别在于”要不要维护一个 .al 工程文件”,选哪种取决于你的场景。
模式一:工程模式(multi run)——最常用。先用 create_project 建一个 TD 工程(.al 文件),后续所有命令都基于这个工程。它天然支持 multi run(同一工程下多套综合/布局布线方案并行)和 multi seed(多随机种子)跑批。标准结构是九步:
1. create_project 创建工程
2. open_project 打开工程
3. top_module 设置顶层模块名
4. add_files 添加源文件和约束文件
5. create_run (可选)添加新的 run;syn_1 和 phy_1 默认已存在
6. set_param 设置各阶段参数
7. launch_runs / wait_run 启动运行并等待结束
8. save_best_bits 多 run/多 seed 时筛选保存最优结果
9. close_project 关闭工程
下面是手册给出的含 multi run 和 multi seed 的完整示例(这是最值得背下来的模板):
create_project test ./ -family ph1 -device PH1A100SFG676 -speed 2
open_project ./test.al
top_module run_led
add_files ./run_led.v
add_files ./top.adc
add_files ./top.sdc
# 新建一个 phy_2,复用 syn_1 的综合结果、换一套约束集
create_run -constrset constraint_1 -syn_run syn_1 phy_2
set_param -run syn_1 flow qor_monitor on
set_param -run syn_1 rtl reg_threshold 0
set_param -run syn_1 gate pack_seq_in_io off
set_param -run phy_1 place pr_strategy 1
set_param -run phy_2 place pr_strategy 2
# 给 phy_1 开多 seed
set_param -run phy_1 flow enable_seed on
set_seed_param -num 6 -init 5 -step 5 -jobs 3
# 回退综合 run 会连带回退其下所有 phy run
reset_runs syn_1
launch_runs {syn_1 phy_1 phy_2} -jobs 2
wait_run syn_1 -quiet
wait_run phy_1 -quiet
wait_run phy_2 -quiet
save_best_bits
close_project
几个细节值得咀嚼:launch_runs 用大括号包多个 run 名,-jobs 2 表示最多两个并行;wait_run 逐个等待(脚本必须等 run 结束才能继续);save_best_bits 会把时序最好的 bit 文件复制到 <project_name>_Runs/best_result 目录。
模式二:非工程模式——不要 .al 工程,像”流式处理”一样从器件导入一路做到出 bit。适合轻量场景或全程脚本化管理。结构是:import_device 导入器件 → set_param → read_hdl 读源码 → 读约束 + 综合分步命令 → legalize_phy_inst → 再次 read_sdc → 布局布线分步命令 → 报告 → bitgen。手册示例:
import_device ph1_100.db -package PH1A100SFG676 -speed 2
set_param flow qor_monitor on
set_param rtl reg_threshold 0
set_param gate pack_seq_in_io off
set_param place pr_strategy 1
read_hdl -file {./run_led.v} -top run_led
read_adc ./top.adc
optimize_rtl
read_sdc ./top.sdc
optimize_gate
legalize_phy_inst
read_sdc ./top.sdc
export_db ./gate.db
report_qor -step gate -file gate.qor
report_area -file gate.area
place
report_qor -step place -file place.qor
update_timing -mode manhattan
report_timing_summary -file place_timing_smry.rpt
route
export_db ./route.db
report_qor -step route -file route.qor
report_area -file route.area
update_timing -mode final
report_timing_summary -file route_timing_smry.rpt
bitgen -bit ./run_led.bit
注意两处约束时序规则:read_adc 必须在 optimize_rtl 之前执行;read_sdc(及 read_sdc -ip)必须在 optimize_gate 或 map_macro 之前执行,且 legalize_phy_inst 之后要再读一遍 SDC,否则时序约束在布局阶段不生效——这是新手脚本最常踩的坑。
模式三:用现有工程跑 single run——介于两者之间。源文件已经通过 GUI 添加进工程了,只想用脚本重跑流程。关键是 open_project 加 -single_run 选项,然后像非工程模式一样走分步命令,最后 close_project:
import_device ph1_100.db -package PH1A100SFG676 -speed 2
open_project -single_run ./test.al
set_param -run syn_1 flow qor_monitor on
...(与非工程模式相同的分步命令,但 elaborate 要 -top 指定顶层)
elaborate -top run_led
read_adc ./top.adc
optimize_rtl
read_sdc ./top.sdc
optimize_gate
legalize_phy_inst
read_sdc ./top.sdc
...
bitgen -bit ./run_led.bit
close_project
还有一个实用技巧:想在某个中间步骤后多要一份非默认报告或文件,可以在脚本中”分段启动”。比如用 launch_runs -step gate syn_1 让综合只跑到 gate 这一步,open_run syn_1 打开结果后执行 write_verilog ./gate.v 导出网表,再继续 launch_runs -step bitgen phy_1 跑到底。-step 可取 design/rtl/gate/place/route/bitgen。
通关标准:
- 能对着上面三个模板说出每种模式的第一个命令和最后一个命令分别是什么。
- 能解释
read_adc/read_sdc与分步命令的先后顺序为什么不能乱。- 能把一份 GUI 工程改造成 single run 脚本重跑。
工程管理命令组
这一组命令负责”管工程”和”管 run”,是工程模式脚本的主干。按使用顺序过一遍:
import_device —— 非工程模式/single run 的第一步,导入器件数据库:
import_device -package <package_name> [-speed <speed_number>] <db_name>
例如 import_device ph1_100.db -package PH1A100SFG676 -speed 2。db 文件名和器件型号的对应关系见手册附录一(PH1 系列对应 ph1_60/90/100/180/400.db,PH2 系列对应 ph2_106.db)。-speed 不写就用默认速度等级。
create_project —— 建工程,四个必选/可选参数:
create_project <project_name> <project_directory> -family <device_family> -device <device_part_number> [-speed <speed_grade>]
工程名就是 .al 文件名(不带后缀)。注意命名限制:工程名、目录名不能以数字开头,禁用 \ / | : " ? < > 等字符。
open_project —— 通过 .al 文件打开工程,默认 multi_run 模式;加 -single_run 则进入 single run 模式。
close_project / top_module —— 关闭工程;top_module run_led 指定顶层模块名(工程模式必需)。
add_files / add_dir / add_include_dir —— 添加文件三兄弟:
add_files {./src/define.v ./src/top.v} # 多文件用大括号、空格分隔
add_files -list srcfiles.list # 用 GUI 导出的列表文件
add_files -list srcfiles.slist # 用户自建的列表文件
add_dir -r ./src # 递归添加目录下所有 .v/.vhd/.vhdl/.sv/.ipc(不含约束文件)
add_include_dir ./src/include_files # 添加 include 搜索路径(默认递归)
.slist 文件就是每行一个路径的纯文本,相对路径以 .al 文件所在目录为基准——批量管理源码时比一条条 add_files 干净得多。
create_run —— 在 multi run 模式下新建 run:
create_run -constrset constraint_1 syn_1 # 新建综合 run
create_run -syn_run syn_1 -constrset constraint_1 phy_2 # 新建 phy run,继承 syn_1
-constrset 必选(指定约束集);建 phy run 时必须用 -syn_run 指明继承哪个综合 run。创建工程时 syn_1 和 phy_1 已默认存在,不需要新建。
launch_runs / wait_run / open_run / reset_runs —— run 的启动、等待、打开与回退:
launch_runs {phy_1 phy_2} -jobs 2 # 并行启动,-jobs 取值 1~6,默认 1
launch_runs -step gate syn_1 # 只跑到指定 step(design/rtl/gate/place/route/bitgen)
wait_run syn_1 -quiet # -quiet 关闭等待期间的滚屏输出
open_run syn_1 # 打开该 run 已完成 step 的 db,才能对其做报告/修改
reset_runs {phy_1 phy_2} # 回退 run 到初始状态
reset_runs syn_1 -prev_step # 只回退到前一个步骤
reset_runs syn_1 -f # 连 run 的文件夹一起删除
不指定 -step 时,综合 run 默认跑到 gate,phy run 默认跑到 bitgen。reset_runs 有个隐含规则:回退综合 run 会自动回退它下面所有 phy run。
参数与 seed 的辅助命令:report_param(打印 db 中的参数,默认只打印非默认值,可加 -detail、-stage place);clear_param(全部重置为默认值);report_seed_param(打印 multi seed 设置);save_best_bits(多 run 结果挑时序最好的 bit 存到 best_result 目录);save_design(在 Console 里改过打开的 db 后,把修改保存回 db 文件);generate_elaborate_package(导出含 elaborate.db、约束文件和运行脚本的文件包,供他人复现)。
set_seed_param —— multi seed 的核心命令,只在 multi run 模式脚本中有效。常用法:
set_seed_param -num 10 -jobs 5 -save 2 # 跑 10 个 seed,同时跑 5 个,保留最优 2 个
全参数速览:-num(seed 数量,默认 1)、-init(最小 seed 值,默认 0)、-step(步长,默认 10)、-jobs(并行数,默认 3)、-save(保留数,默认 1)、-genBit(是否生成 bit,默认 on)、-disable(关闭 multi seed)、-random(随机生成 seed,默认 off)、-filter(提前淘汰无望的 seed,默认 off,配合 -rwns/-pwns/-congestion 三个阈值使用)、-farm/-ip/-queue(LSF 服务器集群运行)。注意 multi seed 设置是有记忆的,不改就沿用上一次的设置。
常见坑:
- multi run 模式下
set_param不加-run <run_name>会不知道参数作用到哪个 run——GUI Console 和 multi run 脚本里-run是必选项。reset_runs误用会白跑几个小时:重跑综合前想清楚要不要连带回退 phy run。- seed 参数”有记忆”:上次设了
-num 6,这次想跑 1 个必须显式改回,否则默认沿用 6。
set_param:用命令行设置综合与布局布线参数
GUI 里 Process → Properties 窗口的每一个选项,都能用一条命令设置:
set_param [-run <run_name>] <step_name> <param> <value>
step_name可取:flow、design、rtl、gate、place、route、timing、sim、bitgen。- multi run 脚本中
-run必选;参数必须放在对应 step 的命令之前设置才生效,例如:
set_param -run syn_1 rtl default_reg_initial 0
launch_runs -step design syn_1
下表是 Syn(综合)侧的完整参数表(表 4-1):
| step | param | value | 默认值 |
|---|---|---|---|
| flow | message | standard/simple/verbose | standard |
| flow | qor_monitor | on/off | on |
| flow | syn_ip_flow | off/ip | off |
| flow | thread | auto/2/4/8/16/32 | auto |
| rtl (Read Design) | default_reg_initial | auto/0/1 | auto |
| rtl (Read Design) | hdl_warning_level | normal/simple | normal |
| rtl (Optimize RTL) | fix_undriven | 0/1/auto | 0 |
| rtl (Optimize RTL) | infer_fsm | off/manual/auto/one_hot/sequential | off |
| rtl (Optimize RTL) | infer_rom | on/off | on |
| rtl (Optimize RTL) | infer_shifter | on/off | on |
| rtl (Optimize RTL) | keep_hierarchy | auto/flatten/all | auto |
| rtl (Optimize RTL) | merge_equal | on/off | on |
| rtl (Optimize RTL) | min_control_set | 1~999999(整数) | 8 |
| rtl (Optimize RTL) | min_ripple_len | auto/0~999999(整数) | auto |
| rtl (Optimize RTL) | seq_syn | on/off | on |
| gate | eram_cascade_height | auto/0~16 | auto |
| gate | map_mode | auto/area_prior/logic_level_prior/balance_explore | auto |
| gate | map_strategy | 1/2/3/4/5 | 1 |
| gate | pack_effort | medium/high/low | medium |
| gate | pack_seq_in_io | auto/on/off | auto |
| gate | report | standard/simple/verbose | standard |
Phy(布局布线)侧的参数表(表 4-2):
| step | param | value | 默认值 |
|---|---|---|---|
| flow | enable_seed | off/on | off |
| flow | hfn_to_clock | 1000000000 or less | 1000000000 |
| flow | speedup_effort | 0/1/2/3/4/5 | 0 |
| place | effort | medium/high | medium |
| place | hfn_opt_effort | medium/low/high | medium |
| place | logic_level_opt | off/on | off |
| place | macro_performance | auto/dsp_wirelength/dsp_crit_path/ram_wirelength/ram_crit_path/dsp_ram_wirelength/dsp_ram_crit_path/macro_congestion | auto |
| place | max_fanout | 9~999999(整数) | 999999 |
| place | opt_timing | medium/off/low/high/ultra | medium |
| place | pack_mode | auto/lower_pin_density/adaptive_pin_density/more_lut_pair | auto |
| place | post_place_opt | auto/medium/high/ultra | auto |
| place | pr_strategy | PH1: 1 | 1 |
| route | effort | normal/routability | normal |
| route | fix_hold | off/on/High(High 仅 PH2) | off |
| route | post_route_opt | auto/medium/high/ultra/off | auto |
| route | timing | normal/off/greedy | normal |
竞赛中最值得记住的几个:时序差 → 调 place effort high、opt_timing high、post_route_opt ultra、route fix_hold on;资源爆 → 试 map_mode area_prior、keep_hierarchy flatten;想跑多方案 → pr_strategy 换 1~11 组合。
文件输入输出命令组
非工程模式没有 add_files,读文件全靠这一组命令:
read_hdl —— 读入并解析 HDL 源文件(非工程模式核心命令):
read_hdl -file "in1.v in2.sv in3.vhd" -top top # 多文件放双引号内,按后缀自动选语法
read_hdl -file "sv::in1.v" -top top # 强制按 SystemVerilog 解析
read_hdl -file "vlib::lib1::in3.vhd" -top top # 按 VHDL 库 lib1 解析
read_hdl -dir ./src -top top # 读整个目录
read_hdl -define "NAME1 NAME2=VALUE2" -top top # 宏定义
-file 和 -dir 至少用其一;-top 必选。还有 -global_include(全局 include 文件)、-included_dir(include 目录)两个可选参数。
read_adc / read_ip_adc —— 读工程级 adc 约束文件(管脚、物理约束):
read_adc ./src/constraints/top.adc
read_ip_adc -ip my_ip -file ./ip_srcs/my_ip.adc # IP 的 adc,需 -ip 指明归属
read_sdc —— 读时序约束文件,同样支持 -ip 区分 IP 约束:
read_sdc ./src/constraints/top.sdc
read_sdc -ip my_ip ./ip_srcs/my_ip.sdc
clear_adc / clear_sdc —— 清空已读入的约束,用于”换一套约束重跑”的场景:
clear_adc
read_adc my_new_pin_location.adc
write_verilog / write_sdf —— 导出中间网表和标准延时文件,做后仿真时必需:
write_verilog ./sim_netlist/top.v # 不指定路径则默认生成 <top>_out.v
write_sdf -check_corner SLOW_FAST ./sim_netlist/top.sdf
import_db / export_db —— 设计数据库(design checkpoint)的导入导出,相当于”存档/读档”:
export_db ./route.db # 布线完成后存档
import_db ./route.db # 之后可从这一步继续跑 fix_hold、phys_opt 等
常见坑:
- 约束读入时机:
read_adc必须在optimize_rtl前,read_sdc必须在optimize_gate/map_macro前,legalize_phy_inst后要重读 SDC。- 所有路径类参数都禁用
\ / | : " ? < >等字符且不能以数字开头,Windows 下建议统一用正斜杠/。
分步运行与优化命令
非工程模式和 single run 模式没有 launch_runs 代劳,需要手动一条条执行分步命令——这其实也是理解 TD 流水线的最好方式:
elaborate -top my_top # 解析 HDL(工程模式下用)
optimize_rtl # RTL 级优化
optimize_gate # 资源映射与打包 = map_macro + map + pack 三步集成
legalize_phy_inst # 网表规范化处理
place # 布局
route # 布线
其中 optimize_gate 可以用 -maparea/-packarea 指定 map/pack 子步骤的面积报告输出路径。如果想要更细的粒度,三个子步骤也有独立命令:map_macro(把 RAM/DSP/SHIFTER/进位链/PAD 等映射到器件宏单元)、map(组合逻辑优化并映射为 LUT)、pack(把相关资源打包成 SLICE)。
legalize_phy_inst 是硬性要求:optimize_gate(或 pack)之后必须执行它,然后再 read_sdc 重读约束,才能进入 place/route。手册给出的最小骨架:
open_project -single_run ./test.al
elaborate -top my_top
optimize_rtl
optimize_gate
legalize_phy_inst
read_sdc ./test.sdc
place
route
两个”补刀”优化命令,可以在 import_db 之后对结果继续优化:
phys_opt -post_route -effort high # 布线后的物理优化,-effort 从 low 到 ultra
fix_hold # 单独调用布线后的 fix_hold 修复保持时间
例如布线后发现 hold 违例,可以 import_db route.db 后执行 fix_hold 再重新出 bit,不必从头重跑。
生成位流文件:bitgen
流程最后一站。bitgen 设置位流生成参数并产出 .bit/.bin 文件:
bitgen [-bit <bit文件>] [-bin <bin文件>] [-gen_mask] [-hd <string>] [-id <string>] [-version <string>] [-security] [-no_date_info] [-compress] [-embedded_compress] [-seu_all] [-unused_io_status <pullup/pulldown/keeper/none>] [-g {param:value ...}]
基础用法很简单:bitgen -bit ./run_led.bit。但几个选项在特定场景很关键:
-compress:bit 文件压缩(同时生成压缩与非压缩两份);-embedded_compress:bin 文件压缩,用于嵌入式加载。-version <2位16进制>:把版本号写进位流,Download 界面可回读显示——多个 bit 文件时靠它区分芯片里加载的是哪一版。-security:屏蔽 SRAM 回读,保护设计。-unused_io_status:统一设置未使用 IO 的状态,默认 pullup(悬空引脚上拉,避免干扰)。-no_date_info:不写时间戳,方便逐字节比较两个 bit 的差异。-seu_all:全码流 SEU 检测,仅 PH1A100/PH1A400 支持。
GUI 里 Generate Bitstream Properties 窗口的 control/startup/attribute 选项,在命令行用 -g 配置,param 与界面上的 Option 名大小写完全一致:

bitgen -bit top.bit -g {security:1}
bitgen -bit top.bit -g {mclk_freq:5MHz boot_mode:mspix4}
第二条例子把加载时钟频率设为 5MHz、启动模式设为 mspix4——竞赛中用 SPI Flash 固化程序时经常要动这两个值。
通关标准:
- 能独立写一份非工程模式脚本,从
import_device跑到bitgen,并在 gate/place/route 三个节点各导出一份 qor 报告。- 能用
set_seed_param配出”8 个 seed、并行 4、保留 2 个最优结果”的跑批设置。- 能说出
bitgen -g里mclk_freq和boot_mode的作用,并生成一个用于 SPI Flash 固化的 bin 文件。
自测:工程模式下
set_param为什么必须带-run?因为工程模式下同时存在多个 run(syn_1、phy_1、phy_2……),每个 run 可以有独立的参数配置(比如 phy_1 用 pr_strategy 1、phy_2 用 pr_strategy 2)。不带
-run时工具不知道这条参数属于谁。而非工程模式只有一个”当前设计”,所以不需要-run。
自测:
launch_runs不加-step时,syn run 和 phy run 分别跑到哪?syn run 默认跑到 gate step,phy run 默认跑到 bitgen step。想中途停在某一阶段用
-step design/rtl/gate/place/route/bitgen指定。
自测:为什么
legalize_phy_inst之后还要再read_sdc一次?因为网表规范化处理后设计数据库被重建,之前读入的 SDC 约束信息不再挂在当前数据库上。不重读的话,布局布线阶段就没有时序约束可用,工具要么报未约束路径,要么按错误假设优化。
自测:
read_hdl -file "sv::in1.v"是什么意思?无视文件后缀,强制按 SystemVerilog 语法解析 in1.v。同理
vlib::lib1::in3.vhd表示按 VHDL 库 lib1 解析。当你把 .sv 代码存成了 .v 后缀,或需要指定 VHDL 工作库时会用到。
自测:多 seed 跑批后怎么拿到最好的那份结果?
在所有
wait_run结束后执行save_best_bits,它会在多个 run 的结果中挑出时序最好的 bitgen 结果,复制到<project_name>_Runs/best_result目录。