Yosys synth_ice40 流程:RTL 到 iCE40 比特流四步走通
更新时间:2026-11-03。综合是什么 讲的是与工艺无关的通用综合,本篇把镜头对准一个具体器件族:Lattice iCE40。开源世界对 iCE40 的支持最完整,Yosys 直接认识
SB_LUT4、SB_DFFR这些芯片原语,配合 nextpnr-ice40 和 Project IceStorm 的 icepack,可以从一份 RTL 一路走到能烧进 Flash 的.bin。本篇讲正常流程怎么走,报错类问题从 EDA 知识目录 的 ts-yosys-* 各篇进入。
本文要回答的问题
- 一份 RTL 变成 iCE40 比特流,中间要经过哪几个工具、各自吃什么吐什么?
synth_ice40和普通synth差在哪?-json产物里装的是什么?- nextpnr-ice40 的器件型号、封装、PCF 约束参数怎么选,漏写会怎样?
- 综合报告的
SB_LUT4/SB_DFFR和布局布线报告的ICESTORM_LC为什么对不上?
整条链路先压成一张封面卡,后文逐段展开:
读这张卡抓住一条主线:文件形态每过一个工具就下沉一档——行为级 Verilog 到器件原语 JSON,再到带物理坐标的 ASCII 配置,最后是纯二进制比特流。工具之间只靠文件交接,哪一步都不能跳。
一、这一步在数字设计流程里的位置
iCE40 链路位于「功能仿真通过之后、上板烧录之前」。上游给你的是 RTL 和一份引脚约束,下游接收 .bin 的是 iceprog 之类的烧录工具(本篇不涉及)。数据流与各工具的分工如下:
图里蓝色是综合、橙色是布局布线、紫色是打包,灰色是两份中间产物,绿色是最终交付物。注意 PCF 引脚约束是第二步到第三步之间才进入的:Yosys 综合时完全不知道引脚 21 和 99 是谁,它只看到 clk、led[0] 这些端口名;把端口名绑到封装引脚,是 nextpnr 读 PCF 后完成的。这也解释了为什么同一份 JSON 可以给不同封装布线,只要换 --package 和 PCF。
本篇的最小设计是一个 24 位计数器,最高 4 位驱动板载 LED。12 MHz 时钟下 led[0] 翻转频率约 5.7 Hz,肉眼可见;低有效异步复位接排针:
module top (
input wire clk, // 12 MHz 板载晶振
input wire rst_n, // 低有效复位
output wire [3:0] led
);
reg [23:0] q;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) q <= 24'd0;
else q <= q + 24'd1;
end
assign led = q[23:20];
endmodule二、命令逐段解读:三个工具的参数拆给你看
第一步是 Yosys 脚本 synth.ys,只有两行,但第二行是一条包含十几个子过程的宏命令:
read_verilog top.v
synth_ice40 -top top -json top.jsonsynth_ice40 内部按 hierarchy → proc → opt → fsm → opt → alumacc(保留进位链)→ abc(映射 LUT4)→ ice40 原语 techmap → opt_clean → json 的顺序执行,与通用 synth 的区别是收尾映射目标换成了 iCE40 专属原语。常用参数如下:
| 参数 | 作用 | 不写会怎样 |
|---|---|---|
-top <模块名> | 指定顶层 | 多模块设计靠自动猜测,选错整个模块会被优化掉 |
-json <文件> | 写出 nextpnr 使用的 JSON 网表 | 只有屏幕日志,没有可交接的网表文件 |
-blif <文件> | 写 BLIF 网表(给 Arachne-pnr 等旧流程/VPR) | 不影响 JSON 流程,按需二选一 |
-nocarry | 不用 SB_CARRY 进位链,加法全用 LUT 拼 | 算术变慢、占资源,仅排查进位问题时用 |
-nowidelut | 禁止用宽 LUT 模式 | 资源换时序的调试开关,默认不开 |
第二步是布局布线,器件型号、封装、网表、约束、输出五个参数缺一不可:
nextpnr-ice40 --hx1k --package tq144 \
--json top.json --pcf top.pcf --asc top.asc--hx1k:器件族与容量,同类开关还有--lp384/--lp1k/--lp8k/--hx4k/--hx8k/--up5k等。选错容量,资源占用率和可用引脚都会错;先nextpnr-ice40 --help能看到全部型号。--package tq144:封装。同一晶粒有多种封装,引脚号是封装维度的概念——这是本篇实测踩过的坑,详见第四节。--pcf top.pcf:物理约束文件,每行set_io 端口 引脚号。默认要求顶层每个端口都被约束;调试期可用--pcf-allow-unconstrained放行未绑引脚。--json / --asc:输入网表与输出配置流。另有--freq <MHz>声明目标频率,不写时未约束时钟按默认 12 MHz 给时序预算(本篇日志即如此),真实工程应显式声明或写 SDC。
第三步没有任何选项:icepack top.asc top.bin,把文本配置流压成二进制。逆向解包用 iceunpack。
三、输入输出文件:每步产物长什么样
输入两份文本。top.v 是 RTL;top.pcf 把端口绑到 iCEstick(HX1K-TQ144)的真实引脚:
set_io clk 21
set_io rst_n 8
set_io led[0] 99
set_io led[1] 98
set_io led[2] 97
set_io led[3] 96Yosys 产出的 top.json 开头是版本戳与原语黑盒定义,真正的设计在 modules.top 里:三个端口 clk/rst_n/led,71 个 cell,类型已经全部是 iCE40 原语,例如:
{
"creator": "Yosys 0.33 (git sha1 2584903a060)",
"modules": { "top": { "cells": {
"q_SB_CARRY_CI": { "type": "SB_CARRY", ... }
} } }
}JSON 读写回环出问题时可看 Yosys JSON 网表读写回环报错。nextpnr 产出的 top.asc 是 ASCII 配置流,开头声明器件,随后逐 tile 给出 18 位配置位串——它已经是「物理形态」,每行对应芯片上一个 tile:
.comment from next-pnr
.device 1k
.io_tile 1 0
000000000000000000
000000000000000000
...最后的 top.bin 是 32220 字节的裸二进制,HX1K 的配置存储器大小固定,文件大小不随设计规模变化,变的只是其中的配置位内容。三份文件的演进方向一句话概括:JSON 回答「逻辑由哪些原语组成」,ASC 回答「这些原语放在哪、怎么连线」,BIN 只负责让烧录工具把配置位写进芯片。
四、常见错与检查点
| 现象 | 先查什么 | 纠正 |
|---|---|---|
ERROR: package does not have a pin named '98' | 封装与 PCF 引脚号是否匹配。本篇第一次用 --package vq100 跑到 led[1] 即报此错 | iCEstick 是 TQ144 封装,改 --package tq144;引脚号永远以具体封装手册为准 |
Error: port 'led[0]' does not exist 之类 PCF 报错 | PCF 端口名与 RTL 顶层端口是否逐字一致(含位选写法) | 按顶层声明改名,PCF 不做通配匹配 |
| 综合 0 problems,布线后端口悬空 | 顶层模块是否选错、未约束端口是否被工具丢弃 | 显式 -top;确认 nextpnr 日志每个端口都有一行 constrained |
| 时钟时序报告按 12 MHz PASS,上板却不稳定 | 是否漏了 --freq/SDC,复位大扇出有没有被推全局网络 | 显式约束时钟频率;看日志 promoting ... [reset] 是否走了 SB_GB |
贯穿全流程的检查习惯:Yosys 看 stat 的原语数和 check,nextpnr 看六行 constrained、Device utilisation 和末尾 fmax,两端数字要能互相解释(见下节)。
五、真实跑一遍:从 71 个原语到 2% 资源占用
用上面的脚本在容器内执行 make(等价 bash ./run.sh)。Yosys 收尾统计与检查如下:
24 位计数器恰好落成 24 个 SB_DFFR(带复位的触发器,R 对应低有效异步复位经极性处理)、22 个 SB_CARRY(加法器进位链,最低位无进位输入所以是 22 不是 23)、25 个 SB_LUT4(每级加法的数据输入逻辑加高位取数)。进入 nextpnr 后,六个端口先逐一绑到具体 BEL,打包阶段把「LUT+触发器」压进同一个逻辑单元,再把扇出 24 的时钟和复位推上全局网络。布线后的资源账如下:
两份报告要对着读:Yosys 的 71 是原语条数,nextpnr 的 28 个 ICESTORM_LC 才是芯片物理资源。打包日志写明「24 LCs used as LUT4 and DFF + 1 LUT4 only」,再加上进位链合法化用掉的单元,合计 28/1280,占 HX1K 逻辑容量 2%;6 个 SB_IO(clk、rst_n、4 位 LED)占 112 个 IO 的 5%;2 条 SB_GB 全局网络占 8 条的 25%。布线后时序为 clk 域最大频率 194.33 MHz,对默认目标 12 MHz 判定 PASS——余量极大,符合「计数器只占 2% 资源」的直觉。最后 icepack 静默成功,wc -c top.bin 给出 32220 字节,整条链路结束。需要注意边界:PASS 只代表芯片内部布线时序满足预算,引脚电平标准、复位脉冲宽度和真实晶振质量仍要靠板级设计保证。
实验环境
- 机器/系统:macOS(Apple Silicon),全部工具在 Docker 容器内执行
- 工具版本:Yosys 0.33 (git sha1 2584903a060)、nextpnr-ice40 0.6-3build5、Project IceStorm icepack,镜像 geek-eda:0.4-flow(
yosys -V与nextpnr-ice40 --version照抄) - 日期:2026-11-03
- 完整命令:
docker run --rm -v "$PWD":/work -w /work geek-eda:0.4-flow bash ./run.sh(脚本内依次执行yosys -s synth.ys、nextpnr-ice40 --hx1k --package tq144 --json top.json --pcf top.pcf --asc top.asc、icepack top.asc top.bin)
代码获取
完整 RTL、PCF、Yosys 脚本、run.sh 与 Makefile 已同步到公开仓库:geek-doc.cn/experiments/flow-yosys-synth-ice40,克隆后进入该目录执行 make(或 bash ./run.sh)即可从 top.v 一路生成 top.bin,make clean 删除全部中间产物;本机原生装了工具链时用 make DOCKER_RUN="" 运行。
这一篇在知识体系里的位置
- 想先搞懂「综合」本身在干什么:读 综合是什么,再看 Yosys 通用流程的逐命令演示(同簇上下篇);
- 关心 iCE40 双向口为什么没被推断成
SB_IO:那是另一个层面的问题,见 Yosys ice40 SB_IO 推断失败; - JSON 网表读写报错看 Yosys JSON 网表读写回环报错;RTL 文件读不进来的基础错误看 yosys Can't open input file;
- 更多开源 EDA 正常流程与报错页从 EDA 知识目录 进入。
总结
Yosys synth_ice40 流程就是三个工具一条链:Yosys 的 read_verilog + synth_ice40 -top -json 把 RTL 优化并映射成 SB_LUT4/SB_DFFR/SB_CARRY 原语网表(本篇 71 个原语、check 0 problems);nextpnr-ice40 用 --hx1k/--package/--pcf 选定器件、封装和引脚,布局布线后产出带 tile 配置位的 .asc(28/1280 个 LC、6 个 SB_IO、2 条全局网络,fmax 194.33 MHz 对 12 MHz PASS);icepack 再把它压成 32220 字节的 .bin。最容易错的是封装与引脚号不匹配——引脚是封装维度,换器件必须同时核对 --package 与 PCF;读报告时记住 Yosys 数原语、nextpnr 数物理 LC,两份账要通过打包阶段的压缩关系对得上。