简介:ISCAS.ZIP压缩包以ISCAS'89基准测试电路为核心,汇集了从C432到s38417、s38584、s35932等不同规模的数字逻辑测试电路,覆盖简单逻辑门到复杂处理器单元等多级复杂度,适用于集成电路设计、EDA工具验证和算法性能对比。该集合源自国际电路与系统研讨会发布的经典基准,其中SM2/SM3等命名可能对应中小规模或更大规模电路子集,这些基准电路长期用于衡量电路综合、布局布线、时序分析等IC设计工具的性能。压缩包内共533个文件,包含Bench、BLIF、EDF、CIR等电路网表与描述文件,Genlib、LIB等库文件,VHDL、Verilog等设计代码,以及GIF图示、HTML和PDF说明文档,整体约18.58MB。不同格式分别对应网表、逻辑综合中间表示、电路描述等层次,便于不同EDA工具直接读取分析。目前已有310人学习/下载,是硬件研究者、IC设计工程师及电子工程专业学生获取标准评测集合、开展课题实验和工具评测的实用资源。
1. 把 ISCAS'89 放进国密模块验证流,到底在解决什么问题
拿到 ISCAS.ZIP 这个压缩包的人,多半不是第一次碰 SM2/SM3,而是正卡在“功能仿真全过,但一到综合和 DFT 心里就没底”。ISCAS.ZIP 里装的是一整套标准化的门级基准电路,ISCAS'89 系列又是其中最常用的时序电路集合;把它们和 SM2、SM3 摆在一起,不是两条技术线的拼贴,而是把密码模块的面积、时序、可测性放在一个公认的坐标系里做对标。SM3 的压缩函数是典型的深组合逻辑链,SM2 的签名核则带着大位宽模乘和大量状态寄存器,这两类结构都需要一个结构参照物,而 ISCAS'89 正好干这个。这篇文章从解析 bench 网表开始,一路讲到 TestBench、覆盖率、综合约束和 ATPG 向量排序,适合数字前端、后端验证和做密码硬件集成的工程师;做数据库国密适配的人,也能从参考向量的比对方法里拿到可复现的检查手段。
2. ISCAS.ZIP 里到底有什么:bench 格式解析与网表转换
2.1 先分清 .bench、.net、.stil 三种文件在验证链里的位置
解压 ISCAS.ZIP 之后,你会看到好几种后缀。.bench 是 ISCAS'85 开始的纯门级格式,每个文件一个电路,用文本行描述输入、输出和逻辑门,没有任何工艺库信息;到了 ISCAS'89,格式里加入了 D 触发器,文件后缀常见 .net,但很多发行版仍然叫 .bench。.stil 则是测试语言文件,专门给 ATE 或仿真器做测试回放,里面是时钟周期、引脚约束和测试向量,一般不在 RTL 仿真阶段直接读。
理解这几个文件的关系,对后面的做法很关键:.bench/.net 描述的是“结构”,用来做面积、路径深度、DFF 数量和 ATPG 故障分析;.stil 描述的是“激励”,只在做测试向量导入时才用得着。拿密码模块做验证时,我一般不碰 .stil,先把 .bench 转成 Verilog 网表,让它能和自己的 SM2/SM3 设计进同一个仿真或综合流程。下面这段是典型的 ISCAS'89 门表片段:
INPUT(G0) INPUT(G1) OUTPUT(G8) G3 = BUF(G1) G4 = DFF(G3, CLK) G8 = NAND(G0, G4)每一行都由“输出节点 = 门类型(输入节点)”组成,DFF 描述里第二个输入是时钟。这种格式最大的优势是极容易被脚本消费,不像 EDA 网表那样带一堆单元库属性。
2.2 用 Python 把 bench 门表转成 Verilog 网表
把 bench 转成 Verilog 的脚本并不复杂,常见做法是维护一张门类型映射表,再逐行解析。下面是我平时用的转换脚本,处理 AND、OR、NAND、NOR、XOR、XNOR、INV、BUF 和 DFF 已经够覆盖大多数 ISCAS'89 文件:
#!/usr/bin/env python3 # bench2v.py: 将 ISCAS'85/89 bench 门表转为 Verilog 网表 # 用法: python3 bench2v.py s9234.bench > s9234.v import re, sys GATE_MAP = { "AND": "and", "OR": "or", "NAND": "nand", "NOR": "nor", "XOR": "xor", "XNOR": "xnor", "INV": "not", "BUF": "buf", } def parse(path): inputs, outputs, gates = [], [], [] for raw in open(path): line = raw.split("#")[0].strip() m = re.match(r"INPUT\((\w+)\)", line) if m: inputs.append(m.group(1)) continue m = re.match(r"OUTPUT\((\w+)\)", line) if m: outputs.append(m.group(1)) continue m = re.match(r"(\w+)\s*=\s*(\w+)\((.*)\)", line) if m: out, gate = m.group(1), m.group(2) ins = [x.strip() for x in m.group(3).split(",")] gates.append((out, gate, ins)) return inputs, outputs, gates def emit(inputs, outputs, gates): print(f"module iscas_netlist (clk, " + ", ".join(inputs) + ");") print(" input clk;") for s in inputs: print(f" input {s};") for s in outputs: print(f" output {s};") wires = [g[0] for g in gates if g[0] not in outputs] for s in wires: print(f" wire {s};") for name, gate, ins in gates: if gate == "DFF": # 行为级 dff,需另外提供或者使用标准单元库 print(f" dff u_{name} (.d({ins[0]}), .clk(clk), .q({name}));") elif gate in ("INV", "BUF"): print(f" {GATE_MAP[gate]} u_{name} ({name}, {ins[0]});") else: a, b = ins[0], ins[1] if len(ins) > 1 else ins[0] print(f" {GATE_MAP[gate]} u_{name} ({name}, {a}, {b});") print("endmodule") if __name__ == "__main__": i, o, g = parse(sys.argv[1]) emit(i, o, g)这个脚本里有两处值得注意。第一,GATE_MAP 把门类型名映射成 Verilog 原语,遇到多个输入时只用前两个,因为 ISCAS'85/89 的标准门表大多是两输入门;如果你的文件里出现三输入门,把a, b的解析改成递归展开即可,这个坑我在解析部分自定义 .bench 时踩过。第二,DFF 输出使用了没有定义的dff模块名,执行后需要追加一个行为级定义:
module dff (input d, clk, output reg q); always @(posedge clk) q <= d; endmodule转换完成后,把生成的网表和 SM2/SM3 的 RTL 放在同一个编译脚本里,就能做混合仿真。这里提醒一句:bench 转出来的网表没有任何时序信息,综合工具里必须绑定工艺库单元,否则只能做功能级仿真,不能拿它跑 STA。
2.3 按触发器数量和组合深度选电路:密码模块的资源对标
ISCAS'89 的几十个基准电路并不是随便挑一个就能用。密码模块的寄存器数量和组合路径深度差异极大,需要按目的选参照:
| 验证目的 | 推荐电路 | 主要观察指标 |
|---|---|---|
| 快速冒烟 / 流程自检 | s382、s641 | 网表能否正常解析、DFF 时钟路径简单 |
| 面积与单元密度对标 | s9234、s13207 | 单元总数、DFF 占比、扇出集中度 |
| 时序极限 / 最差路径分析 | s35932、s38584 | 组合路径级数、长路径数量 |
| ATPG 覆盖率验证 | s9234、s5378 | 不可测故障比例、扫描链长度 |
选型判断标准只有一个:让 ISCAS'89 电路里代表组合复杂度的部分,和你手上那块密码逻辑处在同一数量级。例如 SM3 核如果采用 64 轮全展开,组合路径深度会非常高,这时拿 s382 这种小电路做参照没有意义,得用 s35932 这类大电路才能暴露出时序收敛问题;如果只是验证脚本流程能跑通,s382 就够了。另外,不要试图把 ISCAS'89 网表和密码模块拼到同一个设计里做综合,它是基准不是 IP,正确的用法是分别在相同约束下综合,再对比报告。
3. 让 SM2/SM3 模块跑在基准测试台上:TestBench、参考向量与覆盖收集
3.1 SM3 hash algorithm block diagram 在 RTL 验证里的人手点
SM3 的结构可以拆成三段:512 位消息填充、64 轮消息扩展、8 个 32 位寄存器的压缩函数。压缩轮里有几个关键路径点,验证时最值得盯:SS1 和 SS2 的计算、两个条件函数 FFj 和 GGj、以及最后的 P0 置换。P0 是X ^ rotl(X,9) ^ rotl(X,17),P1 是X ^ rotl(X,15) ^ rotl(X,23),两者都是线性变换,但实现时很容易把循环移位的宽度写错,导致摘要只在特定输入下匹配。拿“abc”做首轮冒烟测试,是成本最低的入口:
module tb_sm3_core; reg clk = 0, rst_n = 0; reg start = 0; reg [511:0] blk; wire done; wire [255:0] digest; always #5 clk = ~clk; sm3_core u_core ( .clk (clk), .rst_n (rst_n), .start (start), .blk_in (blk), .done (done), .digest (digest) ); initial begin repeat (2) @(posedge clk); rst_n <= 1; // "abc" 填充后的单块数据:0x61626380 || 416 位 0 || 64 位消息长度 0x18 blk <= {32'h61626380, 416'h0, 64'h18}; start <= 1; @(posedge done); // 标准向量,以 GM/T 0004-2012 附录 A 为准 if (digest !== 256'h66c7f0f462eeedd9d1f2d46bdc10e4e24167c4875cf2f7a2297da02b8f4ba8e0) $error("SM3 digest mismatch for 'abc'"); else $info("SM3 'abc' vector passed"); $finish; end endmoduleTB 里最需要注意是 blk 的拼法:0x61626380是填充字节和 0x80 终止位,中间 416 位零补齐到第 447 位,最后 64 位写入原始消息长度 24(二进制就是 0x18)。如果换成长消息,这里需要脚本自动计算长度,不能手拼。跑仿真和覆盖率收集,我一般在 Questa 系工具里用三行命令:
vlog -quiet tb_sm3_core.sv sm3_core.v vsim -c tb_sm3_core -coverage -do "run -all; coverage report -detail; quit"VCS 用户则把-cm line+tgl+cond挂在编译和运行选项上。覆盖率点要多看三处:轮内加法进位链的位翻转、FFj/GGj 条件函数的分支、P0/P1 循环移位后的字节组合。只看最终摘要匹配是不够的,很多实现错误只在覆盖率报告里露出马脚。
3.2 openssl 与 sm3 jar:参考向量从哪来、交叉比对怎么做
RTL 仿真需要独立第三方参考值,不能只拿自己的参考模型互相比。常见做法是先用 OpenSSL 生成标准摘要,再用 Java 的 Bouncy Castle 复算一遍,两个结果一致才写进 TB。生成“abc”的 SM3 摘要命令:
echo -n abc | openssl dgst -sm3旧版本 OpenSSL 编译时未必带 SM3,报错信息一般是 unknown digest,此时改用 Bouncy Castle。Java 侧的包名在org.bouncycastle.crypto.digests.SM3Digest,一段最少代码是:
import org.bouncycastle.crypto.digests.SM3Digest; import java.nio.charset.StandardCharsets; byte[] msg = "abc".getBytes(StandardCharsets.UTF_8); SM3Digest digest = new SM3Digest(); digest.update(msg, 0, msg.length); byte[] out = new byte[32]; digest.doFinal(out, 0);执行完out就是 32 字节摘要。拿到 OpenSSL 和 BC 两边的值之后再和标准文档附录比对,三方一致,才能作为 RTL 的黄金向量。对于 SM2 数字签名,OpenSSL 3.x 已经内置 SM2 支持,但命令行参数会随版本变化,我一般直接用标准算法附录里的示例密钥对,准备 100 条消息覆盖正常签名、签名值篡改、证书 DN 不匹配三组用例,再让 RTL 的验签通路全部回读通过。
3.3 数据库国密测试怎么接:摘要比对与签名正反用例
做数据库国密适配的同事经常问 SM2 怎么测,这里给一条可复现的路径。第一步确认连接层协商结果里出现 SM2 证书和国密传输加密套件,而不是普通 TLS;第二步在业务库表里取一个定长字段,应用侧用 SM3 算出摘要,与数据库视图返回的摘要字段比对;第三步构造 SM2 数字签名三组用例:证书链合法且签名正确时必须通过,篡改任意一个字节必须失败,证书过期必须失败。这套方法不需要专门硬件,纯软件也能把驱动实现是否生效测出来。要注意的是,摘要比对必须用同一个字节串作为输入,数据库侧如果做了隐式类型转换,比对结果会失真,这是数据库国密测试里最常见的误用。
4. 从 RTL 到门级:用 ISCAS'89 参照网表验证面积、时序与可测性
4.1 先定综合约束:周期、IO 延迟与检查报告
密码模块综合前,先把约束写死,否则面积和时序报告没有可比性。Design Compiler 风格的一段约束是这样:
set clk_period 10.0 create_clock -period $clk_period [get_ports clk] set_input_delay 2.0 [remove_from_collection [all_inputs] [get_ports clk]] set_output_delay 2.0 [all_outputs] compile_ultra -timing report_area -hierarchy report_timing -path end -max_paths 20clk_period设 10ns 对应 100MHz,密码模块在这个频率起步是合理的;set_input_delay和set_output_delay各留 2ns 给外部寄存器。compile_ultra需要 license 支持,如果没有,用compile -map_effort medium一样能得到面积报告,只是优化深度有差别。拿到报告后重点看两个东西:worst negative slack 对应的关键路径是哪一段,以及 report_area 里组合逻辑和寄存器面积的比例。把这条关键路径的级数,和第 2 章里 iscas89 转换网表中最长组合路径做对比,就能判断出 SM3 轮函数是否需要再切一级流水。
4.2 SM2 与 SM4/AES-256 的区别:面积和时序敏感点对照
综合约束定了之后,密码算法选型会直接影响面积和时序,这里常被问到的就是 SM2、SM4、AES-256 的区别。它们在验证上的侧重点完全不同:
| 算法 | 类型 | 面积敏感点 | 时序敏感点 | 对应 ISCAS'89 参照 |
|---|---|---|---|---|
| SM2 | 非对称 | 256 位模乘、模逆、点加运算单元 | 模乘器关键路径、射影坐标转换 | s35932 |
| SM4 | 对称分组 | 32 轮轮函数、S-box 面积 | S-box 查表延迟、轮密钥扩展 | s9234 |
| AES-256 | 对称分组 | S-box 与 MixColumns 组合 | 14 轮关键路径、SubBytes 扩散 | s38584 |
SM2 的面积大头在模运算单元,一个 256 位模乘器往往吃掉核心面积的一半,时序收敛点也在它身上。SM4 和 AES-256 则相反,面积集中在 S-box 与轮函数,路径深度相对均衡。所以拿 ISCAS'89 做对标时,SM2 项目重点看长路径深度,SM4/AES 项目重点看单元密度和扇出。综合报告里把两列指标并列后,异常会非常明显,比如 SM2 核的寄存器面积占比远高于参照电路,说明状态机设计过度串行化了。
4.3 扫描链与 ATPG:用 DFF 分布和不可测故障挑电路
DFT 阶段最常翻车的是 SM3 这种带大循环的设计。把综合后的网表做扫描插入时,扫描链配置要先固定下来:
set_scan_configuration -style muxed -chain_count 4 dft_drc insert_dft report_scan_path -verbosechain_count设 4 是常见起步值,它决定扫描链长度和测试时钟频率。重点是 dft_drc 阶段会报出一堆 violation,其中相当一部分来自密码控制逻辑里的门控时钟和伪路径。SM3 的 64 轮如果以状态机形式实现,轮计数器会形成大量约束,导致测试生成时出现不可测故障;而 ISCAS'89 的时序网表大多用普通 DFF 链,故障模型简单。把两者放在同一套 DRC 报告里看,能直观识别出哪些不可测节点是密码逻辑独有的。扫描链插入完成后跑一遍 ATPG,命令形式类似:
add_faults -class all run_atpg -auto_compress如果覆盖率明显低于 iscas89 参照电路,优先检查异步复位和门控时钟的测试模式设置,而不是急着加向量数量。
5. SM3 差分传播验证:1 比特输入差分到底输出多少比特
5.1 用 64 次穷举验证 P0/P1 的每一列
关于“P 置换中有 1 比特输入差分,输出差分有多少比特”这个问题,网上答案从 1 到 3 都有,但正确结论可以用穷举直接验证。P0 和 P1 都是线性变换,但线性变换不等于保持汉明重量,每一列有几个非零项,单比特输入差分就会扩展成几个输出位。直接跑下面这段脚本:
def rotl(x, n): n %= 32 return ((x << n) | (x >> (32 - n))) & 0xFFFFFFFF def p0(x): return x ^ rotl(x, 9) ^ rotl(x, 17) def p1(x): return x ^ rotl(x, 15) ^ rotl(x, 23) for name, f in (("P0", p0), ("P1", p1)): weights = [] for i in range(32): # 穷举全部单比特输入差分 delta_in = 1 << i delta_out = f(delta_in) ^ f(0) # f(0) 为 0,可省略 weights.append(bin(delta_out).count("1")) print(name, sorted(set(weights)))输出结果非常稳定:P0 和 P1 每个单比特输入差分对应的输出差分汉明重量都是 3。原因在于 P0 的表达式里有三个异或项,输入的第 i 位会同时出现在输出第 i、i+9、i+17 位,这三个位置模 32 后互不重合,所以单比特输入差分必然激活三个输出比特。这里的“输出差分多少比特”指的就是输出异或结果中 1 的个数,也就是 3 比特。值得留意的是,这个 3 比特扩散只代表 P 置换本身,完整轮函数里还有模 2^32 加法,进位链会把单比特扰动扩散得更广,最终安全性由 S-box 的差分均匀性和轮数的折叠共同决定。
5.2 把这个结论做成 RTL 自检和向量排序工具
结论可以直接用到验证里。P0/P1 的列权重是固定的 3,那么在检查任何 SM3 核的 RTL 时,可以先对 32 个单比特差分分别跑一遍 P0 和 P1 的输入输出,逐一核对汉明重量是否都为 3;只要出现一次重量不是 3,说明循环移位宽度或异或结构实现有误,根本不用等长包仿真。更进一步,可以把差分活跃度数当成测试向量排序的启发式:在 ISCAS'89 网表的 ATPG 向量集上,统计每个向量使 SM3 中间寄存器翻转的数量,优先把翻转数高的向量排到前面,通常比盲目追加随机种子更快让覆盖率曲线出现变化。这个做法在功能验证和 DFT 向量排序里都能复用,核心就一句话:差分传播分析不只是密码理论,它可以直接换算成寄存器翻转统计,变成覆盖率优化的工具。下次覆盖率卡在某个百分比不动时,先按这个单比特差分权重重排一遍向量,再回头翻 dft_drc 报告,往往比继续加随机种子有效得多。
本文还有配套的精品资源,点击获取