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不进行驱动能力分析
注意:Yosys读取SDC时,遇到不支持的命令会直接忽略,不会报错。我曾经因为这个踩过坑——写了一大堆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]
个人经验:我习惯把SDC文件拆成两部分——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]
避坑指南:我曾经犯过一个错误——只标记了单向的false_path。比如只写了从clk_100M到clk_50M,忘了写反向。结果综合工具把反向路径也当成了时序路径,白白浪费了优化时间。记住:CDC路径是双向的,两个方向都要标记

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命令看面积和单元数量。如果发现触发器数量异常多,或者面积比预期大很多,八成是约束没加对。

我个人的检查流程是这样的:

  1. 先不加SDC综合一次,看基线面积和触发器数。
  2. 加上SDC再综合一次,对比两次结果。
  3. 如果加了SDC后面积明显增大,说明有路径被过度约束了。
  4. 如果面积没变化,说明SDC可能根本没生效。

嗯,这个方法虽然土,但很实用。毕竟Yosys没有图形界面,咱们只能靠对比来发现问题。

3.7 小结

今天的内容其实就三个核心点:

  • SDC是综合的指挥棒,Yosys虽然支持有限,但时钟和I/O延迟必须加。
  • 注意Yosys不支持的命令,比如set_load、set_clock_uncertainty,写了也白写。
  • 多时钟域一定要标记false_path,否则综合工具会帮你「优化」出很多没用的逻辑。

下一章我们会聊到Yosys的时序分析插件——sta命令。说实话,Yosys自带的STA功能很弱,但配合外部工具(比如OpenSTA)就能做完整的时序签核。到时候我会分享一个我实际用过的脚本模板,保证你拿来就能用。

好,今天就到这儿。有问题欢迎在评论区留言,我看到会回复。