FPGA 入门 37:SystemVerilog 设计子集——更安全的可综合写法
更新时间:2026-09-12。本文是
fpga/入门层第 39 篇。Verilog 基础见 Verilog 设计基础,状态机原理见 状态机入门,与 VHDL 对比见 Verilog vs VHDL。
本文要回答的问题
- 为什么说 SV 设计子集是「带编译器守护的 Verilog 写法」,而不是一门新语言?旧工程怎么渐进迁移?
- logic 的单驱动语义具体是什么?inout、总线、多个 always 驱动同一信号时各该怎么办?
- always_comb 的自动敏感列表和 latch 检查在仿真、综合两个阶段分别如何工作?unique/priority case 解决什么问题?
- enum 的编码控制(默认二进制、one-hot、显式赋值)、类型检查在 FSM 里怎么用最稳?非法状态恢复怎么写?
- typedef、struct、package 在工程里怎么组织,才能既获得类型安全又不踩综合器支持差异?
- interface 和 modport 在什么规模下值得引入?为什么很多 FPGA 团队仍保持传统端口列表?
- 验证侧的 SVA 断言里哪些概念(立即断言、简单并发断言)可以进设计、哪些只属于 testbench?
- Vivado、Quartus、Verilator、Yosys 对 SV 子集的支持现状如何,写代码前如何确认某项特性可用?
读完本文,你应当能把一个 Verilog-2001 工程的核心模块改写成现代 SV 风格,知道每条新语法对应的硬件结构和工具边界,而不是为了语法新而用。
一、logic 统一 wire/reg
logic [7:0] data; // 单驱动: 既能 always 赋值也能 assign
logic valid;
// 多驱动(三态总线)仍用 wire/tri二、always_ff / always_comb 显式意图
always_ff @(posedge clk or negedge rst_n) // 时序
if (!rst_n) q <= 8'd0; else q <= d;
always_comb begin // 组合: 漏赋值会报错(防 latch)
y = sel ? a : b;
end三、enum 状态机(三段式升级版)
typedef enum logic [2:0] {S_IDLE, S_RUN, S_DONE} state_t;
state_t state, next;
always_ff @(posedge clk or negedge rst_n)
if (!rst_n) state <= S_IDLE; else state <= next;
always_comb begin
next = state;
unique case (state)
S_IDLE: if (start) next = S_RUN;
S_RUN : if (done) next = S_DONE;
S_DONE: next = S_IDLE;
endcase
end波形里直接显示状态名字;非法赋值/拼错会被抓。
四、能综合 vs 仅仿真
| 可综合(设计) | 仅 testbench(不可综合) |
|---|---|
| logic、always_ff/comb/latch | class、面向对象 |
| enum、typedef、struct、package | 动态/关联/队列数组、new |
| 增强 parameter、case | 约束随机 randomize、UVM |
| interface(简单,连接用) | 文件 I/O、事件/软件式动态 |
判据:描述静态并行硬件/寄存器的可综合;运行时动态创建、软件式的只在 TB。
五、logic 的精确语义与边界
logic 不是「智能类型」,它只是一个解析为 wire 或 reg 的简写,并附带一条编译期检查:同一个 logic 信号最多一个过程驱动(always 块)或一个连续驱动(assign)。规则带来的直接好处是:声明信号时不用预判它将如何被赋值,模块端口也能统一写 logic 而不必纠结 input/output 配 wire 还是 reg;一旦出现两个 always 驱动同一信号,编译器立即报错,比 Verilog 的多驱动警告更早、更明确。
但要记住它解决不了的问题:真正的多驱动硬件(三态总线)不能用 logic,仍需 wire(语义即 tri),每个驱动点用 assign bus = en ? val : 'z。inout 端口也必须是 wire。另外,inout 内部被多个源驱动在 FPGA 设计里几乎只该出现在管脚级,片内总线靠 MUX 而不是三态实现,这和新旧语言无关。
迁移老代码的做法很机械:把 reg/wire 统一改 logic,编译;凡是报多驱动错误的地方,要么是真 bug(意外两处赋值),要么是真三态,改回 wire。整个迁移通常半小时内能完成一个中等模块,且不改变任何网表。
六、always_ff / always_comb 与 unique/priority
三个新块名本质是「带合同的 always」:
- always_ff 要求块内只有时序语义(沿控制、非阻塞赋值),若不小心写了电平敏感列表或阻塞赋值风格,工具警告甚至报错。它不生成不同电路,但让评审一眼确认「这里是寄存器」。
- always_comb 自动推导敏感列表(不再需要
@(*)的手写版本),并执行完备性检查:任何输入组合可能导致输出未赋值,就报 latch 警告。它还在仿真零时刻自动执行一次,避免初态 X 误导。这正是 锁存器坑 的语法级防线。 - always_latch 留给你确实想要锁存器的极少数场合,显式声明、工具不再报它。
配套的 case 修饰符也值得用:unique case 告诉工具「分支互斥且应覆盖全部取值」,仿真遇到两个分支同时命中或全部不命中会报错,综合可推成并行多路器而非优先链;priority case 表示分支有先后优先级且必须覆盖,推成级联结构并检查完备性。状态机次态逻辑用 unique case 最合适:状态本就互斥,漏写一个状态在仿真立刻暴露。不要把它们当可有可无的装饰——它们把「约定」变成「编译错误」。
实践模板:时序块一律 always_ff 加非阻塞赋值;组合块一律 always_comb,块开头给全部输出默认值,再用 unique case/if 覆盖;复位策略沿用 时钟与复位。
七、enum 状态机工程写法
enum 把「状态集合」做成类型,收益有三层:编码集中管理、赋值类型检查、波形显示名字。工程上推荐的写法细节:
typedef enum logic [2:0] {
S_IDLE = 3'd0,
S_REQ = 3'd1,
S_WAIT = 3'd2,
S_DONE = 3'd3
} state_t;要点:①显式给出底层位宽和每个值,不依赖编译器顺序赋值,综合结果和代码评审都确定;②需要 one-hot 时显式写全值(如 001、010、100)或查工具编码选项,别假设默认;③状态寄存器和次态分开(标准三段式,见 状态机入门),复位到明确的 S_IDLE;④加默认分支把任何非法编码拉回 IDLE(寄存器虽只有几位,配置或单粒子扰动后仍可能进入非法态,见专家 FPGA 可靠性 SEU);⑤输出走组合或专用时序输出段,不要从状态位直接译码关键控制。
枚举类型还能用于非状态机的编码字段(操作码、错误码),凡是「取值集合封闭」的地方都适用,拼错标识符直接编译失败。
八、typedef、struct、package:组织定义而不是堆 include
Verilog 工程里常看到几百行参数 include 文件,SV 给出更好的组织方式:
- typedef 给位宽模式命名,如
typedef logic [31:0] word_t;,总线宽度变更只改一处;配合枚举定义错误码、命令码类型。 - struct 把相关字段打包:
typedef struct {logic valid; logic [7:0] data; logic [3:0] keep;} axis_beat_t;,FIFO/模块端口传一个结构体,接口演进时加字段不用改几十个端口。多数综合器支持「值语义」struct(整体赋值、字段访问);含数组的嵌套结构支持度参差,用前查工具版本。 - package 集中放共享类型、参数、函数,模块
import pkg::*或按需导入,替代脆弱的 include 顺序;综合与仿真都稳定支持。
保守策略:typedef 和 enum 全量用;struct 在团队工具链统一后用于模块间数据载体;package 作为唯一的全局定义入口;避免在可综合设计里用动态数组、字符串、联合类型。每用一项新特性问一句「它对应什么硬件」,答得上就用。
九、interface:什么时候真香,什么时候别用
interface 把一束相关连线(valid、ready、data、keep、last)封成一个容器,模块端口从二十个减到两三个,还能用 modport 定义主从视角、用 clocking block 统一采样。AXI-Stream、AXI-Lite 这类多信号协议用 interface 连接,代码确实短很多。
但 FPGA 综合对 interface 的支持长期是「简单连线稳定、复杂语义看运气」:interface 内嵌 always/函数、参数化 modport、嵌套 interface 在各工具版本表现不一;自动生成的寄存器切片、多主仲裁等高级用法常常只在仿真可靠。工程上常见的折中是:testbench 和仿真平台充分使用 interface;可综合设计先用传统端口列表或「struct 载体 + 独立 valid/ready」;团队确认工具版本后再在局部协议层引入 interface。判断标准永远是综合后的网表与调试可见性,而不是代码行数。
十、SVA 断言与可综合的边界
SVA(SystemVerilog Assertions)是 SV 最有价值的验证设施之一:assert(x) 立即断言检查当前条件;并发断言 assert property (@(posedge clk) req |-> ##[1:3] ack) 描述时序关系,仿真器实时检查、形式工具可做数学证明。对 FPGA 设计者最实用的几条:
- testbench 里大胆用:握手不丢不重、状态机合法跳转、FIFO 不满写不空读,写成断言后回归测试自动报错,不用人肉盯波形(进阶见 高级 Testbench)。
- 设计代码里可放「检查型」断言:部分工具支持把简单并发断言综合进硬件或仅在仿真生效(用编译宏隔离),上板时断言可映射到错误计数器(配合 ILA 的黑匣子思路)。
- class、randomize、covergroup、mailbox、event 等验证设施全部不可综合,它们构建的是软件世界的验证环境,没有对应硬件。
一条不会错的判据:语法是否在描述静态结构和寄存器行为。能画出电路(连线、MUX、FF、RAM、有限状态)的可综合;需要操作系统式动态行为(堆内存、对象、线程调度)的只属于 TB。
十一、工具支持现状与迁移建议
- Vivado:设计子集支持良好(logic/enum/typedef/package/struct/always_*,interface 逐年改善);编译选项里打开 SV 模式(.sv 后缀自动识别)。
- Quartus:同样支持主流设计子集,个别 struct/interface 组合有版本限制,翻 release notes。
- Verilator:SV 设计子集和大量验证语法支持极快,适合 lint 和仿真双用,是开源流程首选检查器(见 开源工具链 与进阶 RTL lint)。
- Yosys:对 Verilog-2001 支持最完整,SV 子集支持在快速进步,复杂特性仍可能不认,面向开源综合的代码保持保守。
迁移建议:新文件直接用「logic + always_ff/comb + enum + typedef/package」四件套;老文件在碰到时顺手改,不搞大重写;团队在 lint 规则里禁用裸 always、强制组合块默认赋值、要求状态用 enum;特性使用以团队最低工具版本为准。SV 子集不是时尚,它把 同步设计原则 里的多条纪律(无 latch、意图显式、单驱动)从口头约定变成了编译器能执行的规则——这才是升级的真正理由。
迁移建议:新文件直接用「logic + always_ff/comb + enum + typedef/package」四件套;老文件在碰到时顺手改,不搞大重写;团队在 lint 规则里禁用裸 always、强制组合块默认赋值、要求状态用 enum;特性使用以团队最低工具版本为准。SV 子集不是时尚,它把 同步设计原则 里的多条纪律(无 latch、意图显式、单驱动)从口头约定变成了编译器能执行的规则——这才是升级的真正理由。
十二、现代 SV 设计风格的完整小例子
把四件套串起来看一个带握手的数据通道骨架,体会风格变化:
package chl_pkg;
typedef enum logic [1:0] {ST_IDLE, ST_BUSY, ST_LAST} chl_st_t;
typedef struct packed {
logic [7:0] data;
logic [3:0] keep;
logic last;
} axis_beat_t;
endpackage
module chl_rx (
input logic clk, rst_n,
input axis_beat_t sin,
input logic sin_valid, sin_ready,
output chl_st_t dbg_state
);
import chl_pkg::*;
chl_st_t state, nstate;
axis_beat_t beat;
always_ff @(posedge clk or negedge rst_n)
if (!rst_n) state <= ST_IDLE;
else state <= nstate;
always_comb begin
nstate = state; // 默认保持,杜绝 latch
unique case (state)
ST_IDLE: if (sin_valid && sin_ready) nstate = ST_BUSY;
ST_BUSY: if (beat.last) nstate = ST_LAST;
ST_LAST: nstate = ST_IDLE;
endcase
end
always_ff @(posedge clk or negedge rst_n)
if (!rst_n) begin
beat <= '0;
end else if (sin_valid && sin_ready) begin
beat <= sin;
end
assign dbg_state = state;
endmodule要点逐行对应前文:package 集中定义;packed struct 做数据载体(packed 保证连续位布局,综合友好);状态用 enum 且三段式;组合块先赋默认值再 unique case;时序块唯一负责寄存器更新;握手规则在寄存器使能里表达。这段代码综合出来就是普通的 FF/MUX/BRAM 候选逻辑,没有任何「SV 特有硬件」——新语法只改变了人写和查错的方式。
十三、迁移路线与团队约定
从 Verilog-2001 迁到 SV 设计子集,推荐低风险顺序:
- 文件后缀与编译选项:.v 改 .sv 或在工程里指定 SV 模式,先不改代码,确认全流程(仿真、综合、lint)通过。
- 类型统一:wire/reg 换 logic,处理多驱动报错(大概率会抓出一两个真 bug)。
- 块名替换:时序 always 换 always_ff、组合换 always_comb,借工具的 latch 检查清一轮历史隐患。
- 状态机枚举化:逐模块替换 localparam,显式编码,默认分支回 IDLE。
- 定义收敛:参数、类型进 package,删掉 include 层级。
- struct 试点:先在新模块的数据通路上用 packed struct,稳定后推广。
- interface 缓行:等团队工具版本和综合结果验证充分后,再在协议层试点。
配套的 lint 规则建议:禁止裸 always(强制 ff/comb/latch);组合块完备性检查;枚举非法值检查;位宽隐式截断告警升错误;模块端口必须声明方向和类型;多驱动零容忍。这些规则 Verilator、SpyGlass、Questa Lint 都能配(lint 体系见进阶 RTL lint)。
最后澄清一个常见误解:用 SV 设计子集不等于在写验证代码、更不等于要学 UVM。设计子集学完也就十来个语法点,一两天就能上手;真正需要数月积累的 class/随机化/覆盖率那套验证体系,写 RTL 的人了解概念即可。反过来,验证工程师也必须懂这些可综合子集,因为他要读 RTL、写断言、定位设计缺陷。SV 的历史使命正是让设计与验证在同一门语言的两端汇合——而 FPGA 设计者站在偏硬件的这一端,取自己需要的那一半就够了。
最后提醒一个团队落地的常见坑:工具链版本不统一时,SV 特性表现可能不同,尤其是综合器对 struct 打包方式、enum 宽度推断、package 编译顺序的处理。工程上要把综合器和仿真器版本固化在仓库文档或容器镜像里,CI 用同一版本跑 lint,避免「我机器上能综合」。另一个建议是保留一份「本项目允许的 SV 特性白名单」,新人照单使用,超出白名单需评审——这比开放全部语法再靠 review 兜底稳得多。语言现代化的目标是更少的缺陷、更清晰的意图,而不是把 testbench 里的花哨构造搬进可综合设计。守住「能画出对应电路」这条线,SV 子集就只会让你的 RTL 更安全。再补充一个常见面试点:很多人会问为什么 FPGA 行业没有像软件那样快速拥抱新语言。答案是工具链成本和存量规模——任何新语法都要经过综合器、仿真器、形式验证、时序签核整条链的验证,行业切换语言以十年计;而 SystemVerilog 的特殊之处在于它是超集,允许逐文件、逐模块渐进采用,不要求一次性重写。这也是本文反复强调「新代码直接用、老代码顺手改」的原因:语言升级的最佳路径从来不是革命,而是让每一行新代码都站在更安全的默认值上。理解这一点,你面对任何未来的 HDL 演进(无论 VHDL 新版本还是现代生成语言)都会有稳定的判断框架:看它是否让设计意图更显式、让错误更早被工具捕获,而不是看它的语法是否时髦。再补充一个面向应届生的建议:很多面试会直接让你在纸上写一个带复位的状态机或一个简单 FIFO,用不用 SV 不是评分点,但如果你能用 enum 定义状态、用 always_ff 和 always_comb 清晰分段、组合块开头赋默认值,面试官立刻知道你接受过现代 RTL 训练。反过来,堆砌 interface、package 但基础的非阻塞赋值都写错,只会暴露华而不实。把四件套用熟、把同步纪律写进每一行,比学多少高级特性都重要。随着工具链成熟,未来的 Verilog-2001 代码会越来越像「带口音的古文」,能读但不必再写;从第一行作业就使用设计子集,就是在为这种迁移积累零成本的习惯。最后说明一个常见疑惑:既然 SV 这么好,为什么本系列代码示例还常保留 Verilog 风格?原因是教学要兼顾不同工具版本和读者基础,Verilog-2001 是所有工具都支持的最大公约数;但理解了本文之后,你应当能在阅读时自动把旧式代码「翻译」成现代写法——wire/reg 对应 logic、always 对应 always_ff 或 always_comb、parameter 常量对应 enum 或 typedef。这种在新旧风格间自由切换、同时看到底层同一块电路的能力,比坚持任何一种写法都重要。最后给一个实践作业:挑你之前写的一个 Verilog 模块(比如 UART 接收或 FIFO 控制),用 SV 设计子集重写一遍——logic 统一类型、always_ff/always_comb 分块、状态用 enum。对比两版代码,统计编译器/仿真器在新版里多报出了哪些警告。这个作业通常只需要半小时,却能让你切实体会「语法替你站岗」是什么感觉,远比读十篇 SV 教程直接。再补充几点工具使用经验:在 Vivado 里把文件类型设为 SystemVerilog 后,旧 Verilog 文件仍按原语法编译,两者可以混在同一工程;仿真器对 SV 的支持通常领先综合器,仿真通过的写法上板前务必确认综合日志没有「unsupported construct」降级为行为模型的警告——这类警告意味着仿真和硅片行为可能不一致。CI 里建议加一条规则:综合日志扫描该关键字并直接失败。
另外,断言是初学者最容易立刻获得回报的 SV 特性。哪怕不用任何高级语法,在 testbench 里写几行 assert,比如「FIFO 满时不允许写」「状态机非法跳转即报错」,回归时就能自动捕获问题,不用每次人工扫波形。先在验证环境里建立断言习惯,再考虑是否把少量检查带进可综合设计,是最稳的进阶路径。最后总结一下本文的判断标准:遇到任何 SV 语法,先问三个问题——它对应什么硬件?综合器和仿真器都支持吗?它让错误更难还是更容易藏?三个问题都有肯定答案,就值得纳入团队的代码风格;任何一个答不上来,就先留在 testbench 里不要进 RTL。用这套标准筛选,SV 设计子集不会变成新的坑,只会成为同步设计纪律的编译器层延伸。很多团队在 SV 迁移中遇到的混乱,根源都不是语言问题,而是没有明确「哪些语法代表什么硬件」的映射约定。本文给出的四件套(logic、always_ff、always_comb、enum)之所以安全,是因为它们各自的硬件含义毫无歧义:logic 是网线或寄存器、always_ff 是时序逻辑、always_comb 是组合逻辑、enum 是带名字的常量编码。而 interface、动态数组、虚方法这些高级特性之所以不建议进 FPGA 设计,恰恰因为它们的硬件映射不唯一或根本需要运行时调度。守住「语法即电路」这条线,SystemVerilog 对你就永远是工具而非玄学。再补一个 package 使用建议:不要把所有定义塞进一个巨大的全局包,那会造成每个文件都依赖整个包、增量编译变慢且接口含糊。更好的组织是按接口或子系统分包,比如 axis_pkg 只放 AXI-Stream 的结构体、宽度常量和函数,模块只 import 自己需要的包。这样依赖关系清晰、接口文档自解释,也便于在其他项目复用同一个协议包。很多工程化收益其实都来自「让物理结构和文件结构对齐」这个简单原则,SV 的组织特性用对了,正好服务这一点。
最后给一个学习检验:不看文档,用 SV 设计子集从零写一个带 almost full/almost empty 的可参数化 FIFO——logic 声明、enum 状态、always_ff 管指针、always_comb 算空满、package 放参数。写完用 lint 跑一遍,确认零 latch 警告、全部有符号/无符号意图明确。这个小作业覆盖了本文 80% 的要点,做通之后再看工程代码里的 SV 写法,基本不会再有语法层面的障碍。最后再强调一次心态:不要试图「学完 SystemVerilog 再写 FPGA」。这门语言的设计子集只有几十个关键字和几条明确的硬件映射规则,边写边查、写完用综合器验证,是效率最高的路径。把本文的四件套用到下一个作业里,你就已经走在现代 RTL 写法的正确轨道上;剩下的高级特性,等遇到真正需要它们的场景(复杂参数化、跨团队接口包、更系统的验证平台)再学不迟。带着「它对应什么电路」这个问题学任何新语法,SystemVerilog 就永远不会变成负担。再补一个常见面试题的回答框架:「SV 相对 Verilog 最大的改进是什么?」高分回答不是罗列语法,而是抓住一条主线——让语言把更多常见错误挡在编译和仿真阶段:logic 消除 reg/wire 混淆、always_ff/always_comb 自动检查 latch 和敏感度、enum 防止非法状态、断言把协议假设变成可执行检查。语言演进的方向始终是「让正确的写法更省事」,理解这条主线,你对任何新语法的判断都不会跑偏。最后把本文浓缩成一句实践准则:新代码默认使用 logic、always_ff、always_comb、enum 四件套,其他特性按项目白名单引入;让编译器和 lint 工具承担尽可能多的机械检查,把人的精力留给架构、时序和验证这些真正需要判断力的工作。这就是现代 RTL 设计风格相对老派 Verilog 最本质的进步——不是语法更花哨,而是工程防线前移了。这正是本文最希望你带走的结论。
十四、新旧写法的逐项替换对照
为了方便在工程里即查即用,把常见 Verilog-2001 模式和推荐 SV 写法做个完整对照。①信号声明:旧写法是区分 reg/wire,新写法一律 logic,遇到真三态再退回 wire。②时序块:旧写法 always @(posedge clk),新写法 always_ff @(posedge clk),语义不变但工具会检查块内赋值风格。③组合块:旧写法 always @(*) 并小心漏赋值,新写法 always_comb 开头给默认输出。④状态编码:旧写法一堆 parameter 加普通寄存器,新写法 typedef enum 显式指定位宽和编码。⑤参数传递:旧写法仅 parameter,新写法配合 package 和 typedef 共享。⑥端口列表:旧写法冗长重复类型,新写法用 ANSI 风格加 logic,一次写清方向类型位宽。⑦复位:两种风格一致,关键是团队统一(见 时钟与复位)。⑧case 分支:加上 unique/priority 修饰,让完备性检查生效。⑨测试断言:旧风格靠 $display 人肉看,新写法用 assert 加 immediate error 自动失败。⑩层次化常量:旧 include 文件改为 package import,编译顺序问题消失。
替换时不要追求一次全部完成,可以按模块合并请求逐个迁,每个模块迁完跑回归仿真和综合对比,确认网表功能等价(资源数字允许小幅变化)。这样的渐进式现代化在任何存量项目里都能安全实施,也符合工程界「让编译器多干活、让 review 少背锅」的总体趋势。SV 设计子集的全部价值可以浓缩成一句话:它把老程序员靠纪律记住的规则,变成了新程序员绕不开的编译器错误。这正和 同步设计原则 一文的精神一脉相承——好语言和好纪律的目标相同,都是让错误的结构无法安静地存在。
常见坑对照
| 坑 | 后果 | 正确做法 |
|---|---|---|
| logic 多驱动 | 编译错误 | 三态总线用 wire/tri |
| always_comb 漏赋值 | 工具直接报错 | 补齐分支(这正是目的) |
| 把 class/randomize 写进设计 | 综合不过 | 验证特性只放 TB |
| enum 编码依赖默认 | one-hot 不符预期 | 需要时显式指定编码 |
| interface 过度复杂 | 综合支持有限 | 简单打包,复杂留仿真 |
相关与延伸
原理:状态机入门(三段式)、意外锁存器。 对比:Verilog vs VHDL;高级验证见进阶 高级 Testbench;工具支持见 开源 FPGA 工具链。
一句话总结
SystemVerilog 是 Verilog 超集,写设计用它的“可综合设计子集”更安全:logic 统一了 wire/reg(单驱动直接用 logic,多驱动三态才用 wire);always_ff/always_comb 显式表达时序/组合意图,always_comb 还会在漏赋值时直接报错、从语法上杜绝意外锁存器;enum 把状态机状态做成类型,非法值/拼错编译期就抓、波形里显示状态名;另有 typedef/struct/package/增强 parameter、interface 打包连线。但 class、约束随机、动态数组、new、UVM 等“运行时动态/软件式”特性只能在 testbench 仿真用、不可综合——判据是“描述静态并行硬件的可综合,动态创建/软件式的只用于验证”。写 FPGA 用设计子集(Vivado/Quartus 普遍支持),写验证环境才放开用全部 SystemVerilog。
上一篇:39 Verilog vs VHDL(语法哲学/选型) | 下一篇:41 开源 FPGA 工具链(Yosys/nextpnr/iverilog) | FPGA 系列目录