Yosys ice40 SB_IO 推断失败:顶层三态、内部三态与显式例化排查
更新时间:2026-10-30。长尾排查页,属于 EDA 报错排查系列。遇到 Yosys ice40 SB_IO 推断失败 时,先别急着改三态模板:Yosys 0.33 的
synth_ice40并不会把自动推断的顶层inout直接变成SB_IO。如果验收脚本只看最终 cell 名,正常设计也会被误判。多驱动问题另见 Yosys warning multiple drivers。
首屏答案:顶层双向口自动推断后保留 $_TBUF_,内部三态经 tribuf -logic 转成 LUT 逻辑,显式例化才在 stat 中直接出现 SB_IO。若流程必须在综合网表里看到 SB_IO,就显式例化并核对 PIN_TYPE=6'b101001 与高有效 OUTPUT_ENABLE;若走通常的 nextpnr 流程,$_TBUF_ 本身不是失败证据。
本文要回答的问题
- 顶层
inout没有变成SB_IO,是不是三态写法不合模板? - 内部三态与封装引脚三态为什么得到不同 cell?
- 显式
SB_IO怎样连接双向数据、输入和输出使能? - 低有效
oe_n为什么不能直接接到OUTPUT_ENABLE?
一、完整现象:综合成功,stat 却没有 SB_IO
下面三类写法常被混为一谈,实际结果并不相同。封面卡先给出本篇实测结论,后面逐项展开。
这张卡的关键不是比较 cell 数量大小,而是确认对象:只有显式例化一列要求综合器认识具体的 SB_IO 原语;自动推断保留的是更通用的三态缓冲表达,内部三态则走逻辑转换。
最小顶层双向口如下,oe=1 输出 d,oe=0 释放引脚,q 读回焊盘电平:
module top (
input wire oe,
input wire d,
output wire q,
inout wire pad
);
assign pad = oe ? d : 1'bz;
assign q = pad;
endmodule原始现象先是一条通用三态告警,随后综合正常结束。它没有报告“无法推断 SB_IO”,也没有给出错误码:
# command: yosys -Q -T -p 'read_verilog top_inferred.v; synth_ice40 -top top -noabc -json /tmp/top_inferred.json; check; stat'
1. Executing Verilog-2005 frontend: top_inferred.v
Parsing Verilog input from `top_inferred.v' to AST representation.
Warning: Yosys has only limited support for tri-state logic at the moment. (top_inferred.v:7)
Generating RTLIL representation for module `\top'.
Successfully finished Verilog frontend.这条 limited support 只说明 Yosys 对三态的支持有限,不能单独推出 RTL 错或 IO 推断失败。真正的异常是验收者期待 SB_IO,统计里却只有 $_TBUF_。
先把双向口的行为钉死,后面才不容易被 cell 名带偏。oe=1 时,输出驱动器把 d 送到 pad,同时输入通路仍可把焊盘电平读回 q;oe=0 时,本模块停止驱动,pad 由外部器件或上下拉决定,q 仍然读外部电平。双向并不等于“输入和输出二选一”:这里始终存在输出数据、输出使能、输入采样三条独立路径。所谓三态推断,就是把 z 分支识别为关闭输出驱动器,而不是把数据变成常量 0。
二、工具版本与根因:流程根本没有 map_io 阶段
实验使用 geek-eda:0.2 容器中的 Yosys 0.33 (git sha1 2584903a060)。即使给 synth_ice40 加上 -json,最终结果仍是:
图中要同时读三处:Found and reported 0 problems、唯一的 $_TBUF_、退出码 0。它证明“没有 SB_IO”和“综合失败”不是一回事。查看该版本 help synth_ice40,流程从 begin、flatten 到 map_cells、check、json,没有 iopadmap 或 map_io 阶段;-json 只负责写网表。因此根因是工具流程与验收假设不匹配,不是这个标准顶层三态模板突然失效。
内部三态更不应被例化成 SB_IO。SB_IO 描述的是芯片封装引脚,而内部网线没有 PACKAGE_PIN 可接。本篇让内部总线后接一级反相,避免它直接退化成顶层输出三态:
wire internal_bus;
assign internal_bus = oe ? d : 1'bz;
assign q = ~internal_bus;read_verilog internal_tristate.v; synth_ice40 -top top -noabc; check; stat 最终只有 1 个 SB_LUT4,退出码 0;单独执行到 tribuf -logic 时也只剩 $not。这说明三态结构已被逻辑化,而不是留下一个等待映射到封装 IO 的 TBUF。需要注意,释放状态的原始 z 在普通二值内部逻辑里没有物理对应;若系统依赖“未驱动”语义,应改协议,例如用有效位或确定的默认电平,不能只靠 cell 数判断功能等价。
这里还要警惕一个常见误判:看到内部三态最后只剩 LUT,就认为 Yosys 完整保留了四态仿真语义。综合网表面向的是可制造的二值门电路,z 的“释放驱动”含义不能被 LUT 真值表原样保存。若多个内部模块原来靠轮流输出 z 共享总线,应该明确写出仲裁后的数据选择器和各自的请求、授权信号;谁获得总线、其余周期输出什么值,都要成为普通逻辑。反过来,若三态只存在于最外层焊盘,释放引脚才有明确的电气意义,后续工具才能把它落实到 IO tile。
三、排查清单:先定位三态边界,再决定修法
图中红色是被误判的入口现象,蓝色是两次边界判断,橙色是实际修改动作,绿色才是验收终点。按这条路径逐项核对:
- 确认位置:
z是否最终驱动顶层inout;内部总线不要套封装 IO 修法。 - 确认版本与流程:记录
yosys -V和完整synth_ice40命令,不跨版本照搬 cell 预期。 - 看 TBUF 而非只看警告:顶层保留 TBUF 与内部 TBUF 消失都是本例的正常结果。
- 查多驱动:若多个源驱动同一网线,先解决驱动冲突,不能拿 SB_IO 掩盖。
- 核对使能极性:SB_IO 的
OUTPUT_ENABLE高有效;oe_n必须先反相。 - 把验收写成结构断言:在 CI 中保存同一版本的
stat,分别断言顶层 TBUF 或显式 SB_IO 的预期数量;不要只用“命令返回 0”代表资源映射正确,也不要只搜一条 Warning 就判失败。
四、修复代码:需要具体原语时显式例化
如果网表接口、特殊 IO 配置或团队约束明确要求 SB_IO,使用下面的组合双向配置。PIN_TYPE[5:4]=2'b10 选择由 OUTPUT_ENABLE 控制的输出,PIN_TYPE[3]=1 与 PIN_TYPE[2]=0 选择直接输出 D_OUT_0,PIN_TYPE[0]=1 选择直接输入,所以合起来是 6'b101001。
SB_IO #(
.PIN_TYPE(6'b101001),
.PULLUP(1'b0),
.NEG_TRIGGER(1'b0),
.IO_STANDARD("SB_LVCMOS")
) io (
.PACKAGE_PIN(pad),
.OUTPUT_ENABLE(oe),
.D_OUT_0(d),
.D_IN_0(q)
);本例只使用四个数据端口,其余未用端口不连接;四个参数显式写出,避免依赖取值含义不直观的默认值。若外部信号是 oe_n,先写 wire oe = ~oe_n;,再连接 OUTPUT_ENABLE。实测低有效写法 assign pad = oe_n ? 1'bz : d 会产生 1 个 $_TBUF_ 加 1 个 SB_LUT4,这个 LUT 正是极性转换的网表痕迹。
极性接反往往不会产生任何综合错误:cell 数仍是 1,check 也可能全部通过,但 oe_n=0 时本模块反而驱动总线,与外部发送器发生争用。这个问题不能靠综合统计发现,至少要补方向真值表或仿真,最终还要核对原理图和总线时序。PULLUP=0 也只说明本例没有启用该原语的内部上拉;释放期间焊盘保持什么电平,仍由外部电路与器件配置决定。
| 项目 | 本例配置 | 排错意义 |
|---|---|---|
PACKAGE_PIN | pad | 只能连接真正的封装引脚边界 |
D_OUT_0 / D_IN_0 | d / q | 分开输出数据与输入采样 |
OUTPUT_ENABLE | 高有效 oe | 为 0 时输出高阻 |
PIN_TYPE | 6'b101001 | 组合双向、非寄存模式 |
五、复跑 PASS:stat 与 check 同时验收
显式版本复跑 read_verilog top_explicit.v; synth_ice40 -top top -noabc; check; stat,证据如下:
3. Executing CHECK pass (checking for obvious problems).
Checking module top...
Found and reported 0 problems.
4. Printing statistics.
=== top ===
Number of cells: 1
SB_IO 1
# exit_code: 0这次 PASS 的判定很具体:SB_IO 数量从 0 变为 1,check 仍为 0 个问题,进程退出码为 0。五组对照汇总如下,避免只挑一张统计图下结论:
| 用例 | 最终 cell | 退出码 |
|---|---|---|
| 顶层高有效自动推断 | $_TBUF_ ×1 | 0 |
| 内部三态完整综合 | SB_LUT4 ×1 | 0 |
| 顶层显式例化 | SB_IO ×1 | 0 |
| 顶层低有效自动推断 | $_TBUF_ ×1、SB_LUT4 ×1 | 0 |
内部三态执行到 tribuf -logic | $not ×1 | 0 |
这些结果只证明综合网表结构和基础连接检查符合预期。本次没有运行 nextpnr,没有设置引脚约束,没有做时序分析,也没有上板验证;因此不能把 SB_IO=1 外推成电气配置正确、引脚分配正确或板级双向通信正常。
实验环境
- 机器/系统:
geek-eda:0.2Docker 镜像(Linux 容器)。 - 工具版本:Yosys 0.33 (git sha1 2584903a060)。
- 日期:2026-10-29(UTC)。
- 完整命令:
bash ./run.sh;脚本逐条执行本文五组yosys -Q -T -p命令,合并标准输出与标准错误,并记录各自退出码。
代码获取
四份 Verilog 源码、run.sh 和原始日志位于公开仓 https://github.com/derekzhuo/geek-doc.cn/tree/main/experiments/ts-yosys-ice40-sb-io-inference。在该目录执行 bash ./run.sh,或按 README 的 Docker 命令复跑,即可核对全部 cell 数与退出码。
这一篇在知识体系里的位置
本篇处理 Yosys ice40 SB_IO 推断失败中的“cell 名不符合预期”,重点是三态边界和显式原语。数组资源没有按预期映射可对照 Yosys memory not inferred;同一网线存在多个驱动时看 Yosys warning multiple drivers;更多综合报错从 开源 EDA 报错排查导航 进入。
总结
Yosys ice40 SB_IO 推断失败要先区分顶层与内部三态:Yosys 0.33 的顶层标准 inout 写法保留 $_TBUF_,内部三态被逻辑化,二者都不等于综合失败。只有明确要求具体 IO 原语时才显式例化 SB_IO,用 PIN_TYPE=6'b101001、高有效 OUTPUT_ENABLE 和独立输入输出数据口;复跑以 SB_IO=1、check 0 problems、退出码 0 验收,同时保留布局布线与硬件验证的边界。