3、综合脚本进阶:Yosys脚本编写,约束文件(SDC)的导入与处理
好,咱们接着聊。上一章我们把Yosys的基本流程跑通了,说白了就是「读代码 → 综合 → 出网表」这三板斧。但实际项目中,光靠这三板斧可不够。你想想看,芯片里那么多时钟域,那么多路径,你总得告诉工具哪些路径是假的、哪些时钟是异步的、哪些端口要设多重的负载吧?
嗯,这就是我们今天要聊的核心——约束文件(SDC)的导入与处理。我个人习惯把SDC比作「综合的指挥棒」,没有它,Yosys就像个盲人摸象,综合出来的网表大概率是废的。
3.1 为什么Yosys需要SDC?
很多刚接触开源工具链的朋友会问我:「Yosys不是只做逻辑综合吗?时序约束不是后端才用的吗?」
其实这是个误区。Yosys虽然不像Synopsys DC那样有完整的STA引擎,但它确实能读取并理解SDC中的部分约束,尤其是时钟定义和I/O延迟。我在项目中遇到过这样的情况:RTL代码写得挺好,Yosys综合也没报错,但到了后端布局布线时,时序一团糟。后来一查,发现是综合时没加时钟约束,Yosys默认把所有路径都当成了理想路径。
说白了,综合阶段不加约束,后端就要替你背锅。
3.2 Yosys支持的SDC命令
Yosys对SDC的支持是有限的,它主要支持以下几类命令:
| SDC命令 | Yosys支持情况 | 说明 |
|---|---|---|
create_clock |
✅ 完全支持 | 定义时钟名称、周期、波形 |
set_input_delay |
✅ 支持 | 设置输入端口相对于时钟的延迟 |
set_output_delay |
✅ 支持 | 设置输出端口相对于时钟的延迟 |
set_false_path |
✅ 支持 | 标记不需要时序检查的路径 |
set_multicycle_path |
⚠️ 部分支持 | 需要配合Yosys的特定命令 |
set_clock_uncertainty |
❌ 不支持 | Yosys内部不处理时钟抖动 |
set_load |
❌ 不支持 | Yosys不进行驱动能力分析 |
report_clock命令检查一下时钟是否真的被识别了。
3.3 在Yosys脚本中导入SDC
Yosys导入SDC的方式很简单,用read_sdc命令就行。但要注意顺序——先读设计,再读约束。为什么?因为Yosys需要先知道有哪些端口和时钟,才能把约束挂上去。
来看一个完整的脚本示例:
# 综合脚本:synth_with_sdc.ys
# 作者:我(一个踩过坑的工程师)
# 1. 读取设计文件
read_verilog -sv top.v
read_verilog -sv module_a.v
read_verilog -sv module_b.v
# 2. 读取约束文件
read_sdc constraints.sdc
# 3. 执行综合
synth -top top
# 4. 映射到目标工艺库
dfflibmap -liberty my_library.lib
abc -liberty my_library.lib
# 5. 输出网表
write_verilog synth_output.v
write_json synth_output.json
对应的SDC文件长这样:
# constraints.sdc
# 时钟定义
create_clock -name clk_main -period 10.0 [get_ports clk]
create_clock -name clk_aux -period 25.0 [get_ports clk_aux]
# 输入延迟
set_input_delay -clock clk_main 2.0 [get_ports data_in*]
set_input_delay -clock clk_main 1.5 [get_ports addr*]
# 输出延迟
set_output_delay -clock clk_main 3.0 [get_ports data_out*]
# 异步路径(跨时钟域)
set_false_path -from [get_clocks clk_main] -to [get_clocks clk_aux]
set_false_path -from [get_clocks clk_aux] -to [get_clocks clk_main]
clock.sdc只放时钟定义,timing.sdc放I/O延迟和false_path。这样调试时,可以只重新加载时钟部分,不用每次都读整个SDC。Yosys的read_sdc支持多次调用,后读的约束会覆盖先读的。
3.4 时钟约束的细节处理
时钟定义是SDC里最重要的部分。Yosys对create_clock的支持很完整,但有几个细节要注意:
- 时钟名不能重复:如果你定义了同名时钟,Yosys会报错。我建议用
-name参数显式指定,别偷懒用默认名。 - 波形定义要准确:
-waveform {0 5}表示50%占空比,上升沿在0ns,下降沿在5ns。如果你用的是非50%占空比,比如{2 8},Yosys也能处理,但后端工具可能会不理解。 - 生成时钟(Generated Clock):Yosys不支持
create_generated_clock。如果你有分频时钟,建议在RTL里用PLL实例化,或者直接在SDC里用create_clock手动定义。
举个例子,假设你的设计里有一个通过寄存器分频得到的时钟:
// RTL中:always @(posedge clk) clk_div <= ~clk_div;
// 你不能在SDC里写 create_generated_clock
// 只能手动定义:
create_clock -name clk_div_manual -period 20.0 [get_pins reg_clk_div/Q]
嗯,这里要注意:get_pins的语法在Yosys里是支持的,但路径写法要和网表一致。我建议你先综合一次,看看生成的网表里寄存器叫什么名字,再回来写SDC。
3.5 处理多时钟域与异步路径
现代芯片几乎没有单时钟域的设计了。多时钟域带来的问题就是——跨时钟域路径(CDC)必须标记为false_path,否则Yosys会认为这些路径需要在一个时钟周期内完成,这显然不合理。
我曾经接手过一个项目,前同事在SDC里漏写了CDC的false_path,结果Yosys综合出来的网表面积大了30%。为什么?因为工具为了满足那个不存在的时序要求,拼命插缓冲器和大驱动单元。你说冤不冤?
正确的做法是:
# 标记所有跨时钟域路径
set_false_path -from [get_clocks clk_100M] -to [get_clocks clk_50M]
set_false_path -from [get_clocks clk_50M] -to [get_clocks clk_100M]
# 如果还有异步复位,也要标记
set_false_path -from [get_ports rst_n] -to [get_clocks clk_100M]
3.6 综合后的约束检查
综合完成后,别急着跑后端。先检查一下Yosys是否真的读懂了你的约束。我一般用这几个命令:
# 在Yosys交互模式下
yosys> read_sdc constraints.sdc
yosys> synth -top top
yosys> # 检查时钟
yosys> rename -top top
yosys> select -list top/t:FDRE # 看看触发器数量
yosys> # 检查约束是否生效
yosys> stat -top top
Yosys没有像DC那样的report_timing命令,但你可以通过stat命令看面积和单元数量。如果发现触发器数量异常多,或者面积比预期大很多,八成是约束没加对。
我个人的检查流程是这样的:
- 先不加SDC综合一次,看基线面积和触发器数。
- 加上SDC再综合一次,对比两次结果。
- 如果加了SDC后面积明显增大,说明有路径被过度约束了。
- 如果面积没变化,说明SDC可能根本没生效。
嗯,这个方法虽然土,但很实用。毕竟Yosys没有图形界面,咱们只能靠对比来发现问题。
3.7 小结
今天的内容其实就三个核心点:
- SDC是综合的指挥棒,Yosys虽然支持有限,但时钟和I/O延迟必须加。
- 注意Yosys不支持的命令,比如set_load、set_clock_uncertainty,写了也白写。
- 多时钟域一定要标记false_path,否则综合工具会帮你「优化」出很多没用的逻辑。
下一章我们会聊到Yosys的时序分析插件——sta命令。说实话,Yosys自带的STA功能很弱,但配合外部工具(比如OpenSTA)就能做完整的时序签核。到时候我会分享一个我实际用过的脚本模板,保证你拿来就能用。
好,今天就到这儿。有问题欢迎在评论区留言,我看到会回复。