1. 从零开始:为什么我们需要一个开源的数字电路综合工具?
如果你和我一样,是个硬件工程师,或者对数字电路设计、FPGA开发感兴趣,那你一定对Vivado、Quartus这些商业EDA工具不陌生。它们功能强大,但同时也伴随着高昂的授权费用、庞大的安装体积和相对封闭的生态。很多时候,我们只是想快速验证一个小的设计想法,或者学习综合、优化的基本原理,并不想启动那个动辄几十个G的“巨无霸”。更不用说,在学术研究、开源硬件项目或者自动化流程中,一个可脚本化、可定制的轻量级工具链是多么重要。
这就是Yosys诞生的背景。Yosys是一个开源的RTL(寄存器传输级)综合框架,用C++编写,支持Verilog-2005标准的一个子集。它的核心目标不是替代那些商业巨头,而是填补一个独特的生态位:成为一个快速、灵活、可编程的“数字电路瑞士军刀”。我第一次接触Yosys,是因为一个开源FPGA项目需要一套完全免费的工具链来进行综合和映射。从最初的磕磕绊绊,到后来将它集成到CI/CD流程中自动检查代码质量,Yosys展现出的潜力和灵活性让我印象深刻。
简单来说,Yosys能帮你做这几件事:1. 将你写的Verilog代码(行为级描述)综合成门级网表;2. 对网表进行优化,比如常数传播、逻辑简化;3. 将优化后的网表转换成特定格式,例如BLIF(伯克利逻辑交换格式)或者EDIF,供后续的布局布线工具使用;4. 进行一些简单的静态时序分析和等价性检查。它的工作流非常清晰:读入设计(read_verilog) -> 高层次综合与转换(proc,opt等) -> 工艺映射(techmap) -> 输出网表(write_verilog/blif)。
对于学习者而言,Yosys是一个绝佳的“显微镜”。商业工具的综合过程像是一个黑盒,你输入RTL,它输出网表,中间的具体优化步骤往往是隐藏的。而Yosys将每一步都拆解成一个个独立的命令(Pass),你可以清晰地看到你的设计是如何一步步从高级语言“坍缩”成基本逻辑门的。这种透明性对于深入理解综合、优化、工艺映射等概念至关重要。接下来,我就结合自己的使用经验,带你一步步搭建环境、跑通第一个流程,并深入几个核心环节看看它到底能做什么。
2. 环境搭建与初体验:编译Yosys并运行第一个综合流程
虽然一些Linux发行版的仓库里可能有Yosys的包,但我强烈建议从源码编译。一来可以确保获得最新版本(开发非常活跃),二来在编译过程中能更好地理解它的依赖,方便后续可能需要的自定义修改。
2.1 编译安装:依赖与踩坑点
Yosys的核心依赖并不多,主要是:
- GCC或Clang:一个现代的C++编译器(支持C++11)。
- Bison和Flex:用于生成Verilog解析器。
- make:构建工具。
- git:克隆源码。
在Ubuntu/Debian系统上,一条命令就能搞定基础依赖:
sudo apt-get install build-essential clang bison flex git libreadline-dev gawk tcl-dev libffi-dev graphviz xdot pkg-config python3 libboost-system-dev libboost-python-dev libboost-filesystem-dev zlib1g-dev这里有几个容易忽略的包:libreadline-dev用于交互式shell的命令行编辑体验;graphviz和xdot用于生成和查看电路图,这个功能非常直观,建议装上;libboost-python-dev是为了支持Yosys的Python绑定,如果你想用Python脚本驱动Yosys,这是必须的。
获取源码并编译:
git clone https://github.com/YosysHQ/yosys.git cd yosys make config-gcc # 如果你用Clang,则是 make config-clang make -j$(nproc) sudo make installmake -j$(nproc)会使用你所有的CPU核心并行编译,速度很快。编译完成后,输入yosys就能进入其交互式命令行环境,看到那个经典的“Yosys Open SYnthesis Suite”欢迎界面。
注意:如果遇到关于
boost库的链接错误,请检查libboost-python-dev是否安装正确。有时需要指定Python版本,例如libboost-python3-dev。如果系统同时存在Python2和Python3,编译系统可能会混淆。一个解决方法是编辑Makefile.conf,手动修正BOOST_PYTHON_LIB和PYTHON_EXECUTABLE等变量的值。
2.2 第一个综合实例:从Verilog到门级网表
让我们用一个最简单的例子——一个带异步复位的D触发器模块——来感受Yosys的工作流。创建文件dff.v:
module dff ( input wire clk, input wire rst_n, input wire d, output reg q ); always @(posedge clk or negedge rst_n) begin if (!rst_n) begin q <= 1'b0; end else begin q <= d; end end endmodule这是一个标准的Verilog描述。现在,我们想用Yosys将其综合成由基本逻辑单元(比如与门、或门、非门、触发器)构成的网表。
我们可以编写一个Yosys脚本synth.ys:
# synth.ys read_verilog dff.v # 读取Verilog文件 hierarchy -top dff # 指定顶层模块名为dff proc # 将always块等进程转化为逻辑网表 opt # 执行逻辑优化(常量传播、冗余逻辑消除等) fsm # 有限状态机提取与优化(本例无FSM,但常用) memory # 推断并优化存储器结构(本例无) techmap # 将抽象单元映射到目标工艺库的基本单元 abc # 使用ABC工具进行逻辑综合与优化 clean # 清理冗余的线网和单元 write_verilog dff_synth.v # 输出优化后的Verilog网表 stat # 打印设计统计信息在命令行中运行:yosys synth.ys。Yosys会依次执行每个命令,并在终端输出详细的过程信息。最后,stat命令会打印出类似下面的信息:
=== dff === Number of wires: 5 Number of wire bits: 5 Number of public wires: 5 Number of public wire bits: 5 Number of memories: 0 Number of memory bits: 0 Number of processes: 0 Number of cells: 2 $_DFF_PN0_ 1 $_NOT_ 1这告诉我们,综合后的网表包含了2个基本单元(cells):一个正边沿触发、低电平有效的D触发器($_DFF_PN0_)和一个反相器($_NOT_)。反相器可能是为了处理复位信号极性。生成的dff_synth.v文件内容将是门级网表,使用Yosys内部定义的基本单元库。
2.3 可视化:用show命令查看电路图
文字网表不够直观?Yosys的show命令是学习利器。在交互式环境中,执行完proc或opt后,输入:
show如果你的环境配置了Graphviz,它会自动调用xdot弹出一个窗口,显示当前设计层次的电路结构图。你可以清晰地看到数据路径、控制信号和寄存器。对于复杂设计,你可以用show -prefix mydesign将图导出为PDF、SVG等格式。这个功能在调试和理解综合结果时极其有用,尤其是当你怀疑综合工具没有正确推断出你想要的硬件结构时。
3. 核心命令深度解析:理解Yosys的“Pass”哲学
Yosys的强大之处在于其模块化设计。每一个功能,如读取文件、综合、优化、映射,都被实现为一个独立的“Pass”(过程)。你可以像搭积木一样,通过组合不同的Pass来构建你的综合流程。理解关键Pass的作用,是灵活运用Yosys的前提。
3.1 前端:read_*与hierarchy
read_verilog/read_ilang:这是设计的入口。read_verilog将Verilog代码解析成Yosys内部的抽象语法树(AST)表示。它支持绝大多数常用的行为级和结构级描述,但对SystemVerilog的支持是选择性的(主要通过插件,如yosys-sv)。对于特别复杂或非标的语法,可能需要预处理。hierarchy:这个命令至关重要。它根据模块的实例化关系,构建整个设计的层次结构,并允许你通过-top <module_name>指定顶层模块。Yosys的许多优化和转换操作都是在整个层次结构的上下文中进行的。如果你不指定-top,Yosys会尝试自动推断,但在多顶层模块或复杂情况下容易出错,手动指定是一个好习惯。
3.2 综合与转换:proc,opt,memory
proc:这是将“行为级”描述转化为“结构级”网表的关键一步。它主要处理always块。对于组合逻辑的always块,它会生成多路选择器(Mux)和锁存器(Latch,如果有时钟或锁存条件)。对于时序逻辑的always块(如带posedge clk的),它会推断出触发器(Flip-Flop)。一个常见误区:proc并不直接映射到具体的工艺库单元,它产生的是由Yosys内部通用原语(如$dff,$mux,$logic_and)组成的网表。opt:优化Pass的集合。它执行一系列经典的逻辑优化:- 常数传播:如果某个信号的逻辑值是常数(如
1‘b0),则将所有使用该信号的地方替换为常数。 - 死代码消除:移除永远不会被执行的逻辑分支或未被驱动的线网。
- 逻辑简化:利用布尔代数规则简化逻辑表达式,例如
A & 1 = A,A & A = A。 - 合并等效的触发器:如果两个触发器驱动完全相同的逻辑,可能会被合并。
opt可以多次运行,通常在每个重要的转换步骤后都会执行一次,以“清理”和优化当前网表。
- 常数传播:如果某个信号的逻辑值是常数(如
memory:专门用于推断和优化存储器结构。它将Verilog中通过寄存器数组(如reg [7:0] mem [0:1023])描述的RAM/ROM,转化为Yosys内部的存储器原语($mem)。后续的memory_mapPass可以将这些原语映射到目标FPGA或ASIC库中特定的存储器块(如Block RAM)。
3.3 工艺映射与优化:techmap与abc
techmap:这是将Yosys内部通用原语映射到目标技术库的桥梁。Yosys自带一个通用的RTLIL(RTL中间语言)库,包含$_AND_,$_OR_,$_DFF_P_等几百个基本单元。techmap命令通过一系列规则文件(.rules文件),将像$dff这样的高层原语,替换为具体技术库(比如一个FPGA的查找表LUT和专用触发器FF)所支持的低层单元组合。你也可以为自己的工艺库编写规则文件。abc:这是一个外部的、强大的逻辑综合和优化工具,Yosys通过插件集成它。abc接收由与/或/非门构成的组合逻辑网表,并对其进行面积、延时等方面的优化。它内部包含多种算法(如布尔可满足性SAT、重写、重分解)。在Yosys流程中,通常在techmap将设计映射到门级网表后调用abc进行深度的组合逻辑优化。你可以给abc传递参数,例如abc -g AND指定使用与门作为基本单元进行优化。
3.4 后端与输出:write_*与stat
write_verilog/write_blif/write_edif:输出Pass。write_verilog会输出用Yosys基本单元描述的Verilog网表,可读性较好,常用于仿真或后续处理。write_blif输出BLIF格式,这是一种更底层的、与工艺无关的网表格式,被很多学术工具和VPR(FPGA布局布线工具)支持。write_edif输出EDIF格式,某些商业工具链可能需要。stat:打印当前设计的统计信息,包括模块数量、线网数量、单元数量及类型分布。这是快速评估综合结果(如设计规模、触发器用量)的最直接方式。
4. 进阶应用与实战技巧:超越基础综合
掌握了基本流程后,Yosys还能在更多场景下大显身手。这里分享几个我实践中觉得非常有用的进阶用法。
4.1 形式验证:用Yosys证明两个电路等价
在修改了RTL代码或者进行了手工优化后,如何确保新设计的功能与原始设计完全一致?形式验证(等价性检查)可以给出数学上严格的证明。Yosys内置了equiv和miter+sat两种方式。
假设我们有原始设计golden.v和修改后的设计revised.v,顶层模块名都是top。我们可以使用如下脚本进行等价性检查:
# equiv_check.ys read_verilog golden.v hierarchy -top top prep -top top # `prep`是`proc; opt`的快捷方式,常用于准备设计 design -stash gold read_verilog revised.v hierarchy -top top prep -top top design -stash rev design -copy-from gold -as gold top design -copy-from rev -as rev top equiv_make gold rev equiv # 创建一个包含两个设计和比较逻辑的“miter”电路 hierarchy -top equiv sat -prove trigger 0 -show-inputs -show-outputs # 使用SAT求解器尝试证明等价性 # 或者使用专门的等价性检查Pass # equiv_simple -undef # equiv_status如果两个设计等价,sat命令会输出UNSATISFIABLE,意味着不存在任何输入序列能使两个设计输出不同。如果输出SATISFIABLE,则意味着找到了一个反例,并会打印出使输出不同的输入激励,这对于调试差异至关重要。
4.2 资源使用分析与RTL检查
Yosys可以用于在综合早期进行设计规则检查(DRC)和资源估算。
- 检查未连接的端口和线网:在
opt之后,使用check命令。它会报告模块中未驱动的输入、未使用的输出、多驱动的线网等常见问题。 - 估算触发器用量:在综合流程中(
proc之后,techmap之前),使用stat查看$_DFF_*类型的单元数量,这大致就是设计所需的触发器数量。 - 识别锁存器(Latch):锁存器在同步设计中通常是不希望出现的,可能导致时序问题。Yosys在综合后,锁存器会体现为
$_DLATCH_*单元。你可以写一个简单的脚本,在综合后搜索这类单元并报错或警告。
# 一个简单的DRC脚本片段 proc opt stat # 检查是否有锁存器 select -list t:\$_DLATCH_* if {[llength [selection]] > 0} { echo "Warning: Found [llength [selection]] latches in the design!" show -format pdf -prefix latches [selection] }4.3 与开源FPGA工具链集成:Yosys + nextpnr
这是Yosys目前最激动人心的应用场景之一,构成了完整的开源FPGA开发流程:Yosys(综合) -> nextpnr(布局布线) -> 芯片厂商的二进制工具(生成比特流)。
以Lattice iCE40 FPGA为例,流程如下:
- Yosys综合:使用针对iCE40的工艺库文件(通常是一个
.techlib文件或一组.lib/.rules文件),将RTL综合为iCE40的基本单元(如LC,逻辑单元)。
这里yosys -p "synth_ice40 -top top_module -json hardware.json" top_module.vsynth_ice40是一个封装好的综合脚本,它内部调用了proc,opt,techmap(使用iCE40规则),abc(针对LUT优化),最后输出一个JSON格式的网表。 - nextpnr布局布线:nextpnr读取JSON网表,根据目标器件(如
ice40-hx1k)进行布局布线。nextpnr-ice40 --hx1k --package tq144 --json hardware.json --asc hardware.asc --pcf constraints.pcf - 生成比特流:使用厂商工具(如
icepack)将ASC文件转换为最终的比特流文件。icepack hardware.asc hardware.bin
这个流程完全开源、免费,并且可以通过脚本高度自动化,非常适合教育、原型验证和小批量产品开发。
4.4 编写自定义的Yosys Pass
Yosys的真正威力在于其可扩展性。你可以用C++编写自己的Pass,对网表进行自定义的分析或转换。例如,你想统计设计中所有加法器的位宽分布,或者实现一种特定的逻辑优化算法。
Yosys源码的passes/目录下有很多例子。编写一个Pass通常需要:
- 继承
Pass类。 - 实现
execute方法,在这里遍历和修改设计。 - 使用Yosys提供的API(如
RTLIL::Module,RTLIL::Cell,RTLIL::Wire)来访问和操作网表元素。 - 将你的Pass注册到Yosys中。
虽然这需要一定的C++和Yosys内部数据结构知识,但它为你打开了无限的可能性,比如为特定的领域专用架构(DSA)创建定制化的综合流程。
5. 常见问题排查与性能调优
在实际使用中,你肯定会遇到各种问题。这里汇总了一些典型场景和解决思路。
5.1 综合结果与预期不符:触发器、锁存器与逻辑优化
问题:代码中明明写的是边沿触发的always块,但综合后没看到触发器,反而变成了锁存器或纯组合逻辑。
排查:首先,检查你的always块敏感列表和条件语句。Yosys对Verilog语义的解读非常严格。例如,一个不完整的
if-else或case语句在组合逻辑中会推断出锁存器。使用时序逻辑时,确保敏感列表只有时钟边沿(和可能的异步复位边沿),所有信号都在正确的条件分支下被赋值。工具:在
proc命令后立即使用show或write_verilog查看中间结果。使用stat -top <module_name>查看单元类型。关注$_DLATCH_*和$_DFF_*的数量。问题:逻辑被过度优化掉了。
排查:Yosys的
opt非常激进。如果你的设计中有一个模块输出暂时没有连接到任何地方(比如在测试顶层中未实例化),opt会认为它是“死代码”并将其整个移除。同样,一个驱动常数的逻辑也会被优化掉。解决:对于需要保留的调试逻辑或未使用的模块,可以将其输出连接到虚拟负载(如
(* keep *)属性修饰的线网),或者使用setattr -set keep 1命令标记需要保留的模块或线网。在调用opt时,可以使用opt -keepdc来保留不关心(don‘t-care)的逻辑。
5.2 处理不支持的SystemVerilog语法
Yosys原生对SystemVerilog的支持有限。常见的always_ff,always_comb,logic关键字可能不被识别。
- 解决:使用预处理。一种方法是使用
sv2v(SystemVerilog to Verilog)工具,它是一个开源的转换器,可以将许多常用的SystemVerilog结构降级到Verilog-2005,然后再交给Yosys处理。sv2v my_design.sv > my_design.v yosys my_design.v ... - 替代:如果只是少数语法,直接重写成等效的Verilog-2005格式往往是更直接的办法。
5.3 大规模设计的综合性能
对于非常大的设计,Yosys可能会消耗较多内存和时间。
- 策略:
- 增量综合:如果只修改了设计的一部分,可以尝试只重新综合受影响的模块。Yosys本身不直接支持增量综合,但可以通过脚本管理,只对修改的模块及其上级模块重新运行流程。
- 层次化综合:使用
flatten命令可以将层次结构打平,这可能有助于某些全局优化,但会大大增加单个模块的复杂度,可能适得其反。通常,保持合理的层次结构对调试和优化都有利。 - 调整ABC参数:
abc命令有很多选项可以权衡优化质量和运行时间。例如,abc -fast会使用更快的(但可能优化程度较低的)算法。 - 内存限制:在Linux下,如果遇到内存不足,可以尝试使用
ulimit -v限制进程内存,但更根本的是优化RTL代码或拆分设计。
5.4 工艺库与映射问题
- 问题:使用
techmap或synth_*脚本时,找不到某个单元(如特定的IO缓冲器、DSP块)的映射规则。 - 解决:工艺库文件(
.lib,.rules)需要正确定义。对于开源FPGA流程(如iCE40, ECP5),社区已经维护了完善的库文件。对于其他器件或ASIC库,你可能需要自己编写或修改映射规则。这需要对目标工艺库的单元和Yosys的techmap规则语法有深入了解。通常可以从Yosys源码的techlibs/目录下找类似器件库作为参考。
从最初为了解决一个具体的开源工具链需求而接触Yosys,到后来在多个小到中型项目中用它进行快速原型验证、形式化检查和教学演示,我越来越觉得它不仅仅是一个工具,更是一种理念的体现:透明、可组合、专注于核心功能。它可能无法处理超大规模、超高性能的先进工艺设计,但在其适用的领域——教育、研究、开源硬件、轻量级自动化流程——它提供了无可比拟的灵活性和控制力。最大的收获不是学会了某个命令,而是通过它,真正看清楚了从RTL代码到门级网表之间那层“魔法”的面纱之下,究竟发生了什么。下次当你对商业综合工具的结果有疑惑时,不妨用Yosys跑一下同样的设计,对比一下中间结果,或许会有意想不到的发现。