做过数字 DFT 的同行,大概都经历过这样一个瞬间:RTL 过了,网表读进来了,扫描链怎么拼也在脑子里过了一遍,结果 DRC 一跑,日志里刷出几十上百条报错——"找不到单元模型""扫描单元无法识别""时钟门控单元被当成黑盒"。卡住你的既不是综合脚本,也不是时序约束,而是一份看起来最不起眼的东西:标准单元库模型。Tessent_StdcellLib 这个题目说的就是这件事,把代工厂交付的标准单元库(standard cell library),整理成 Tessent 系列工具能读懂的库模型,让扫描插入、ATPG、失效诊断、边界扫描这一整套流程真正能跑起来。
我先把话说明白:它不是某个可以直接下载安装的软件,也不是一个新工具,而是一份"字典"。Tessent 需要知道每个单元长什么样、有哪些引脚、引脚之间是什么逻辑关系、哪个脚是时钟、哪个脚是扫描输入、触发器的 D 到 Q 要花多长时间,它才能判断这条扫描链拼得对不对、能不能生成测试向量。库模型没做好,后面所有环节都是在沙子上盖楼,覆盖率上不去、仿真对不上、硅上测出来一堆假失效,回头查半天,根因往往就是库里的一个引脚写反了。
这篇文章适合三类人看:刚接手 DFT 流程、被库文件折磨过的新人;负责维护 DFT 环境、要给团队搭一套可复用库模型的工程师;以及做低功耗、多电压域设计,需要处理门控单元、隔离单元、电平转换单元的资深同学。下面我按"为什么需要它—库里面到底写了什么—怎么从零做出来—出问题怎么查—怎么把它工程化"的顺序,把我踩过的坑和总结的方法完整讲一遍。
1. Tessent_StdcellLib 到底在解决什么问题
1.1 从一次典型的 DFT 报错说起
先还原一个真实场景。你把综合后的网表喂给 Tessent,准备做扫描插入,命令敲下去,工具回你一句类似"cell XXX is not modeled in the library"的提示。很多人第一反应是去翻网表,看是不是综合脚本把某个单元漏了,或者怀疑工艺库路径没指定对。实际上十有八九不是网表的问题,而是库模型里根本没有这个单元的定义。
更隐蔽的一种情况:库里有这个单元,但定义得不完整。比如一个集成时钟门控单元(ICG),引脚、功能都写了,唯独没有把它的时钟属性标出来。Tessent 在做 DRC 的时候就会把它当成普通组合逻辑,门控使能端的可控性分析随之出错,ATPG 要么生成不出向量,要么生成的向量在仿真里对不上。还有更气人的:库里把某个触发器声明成扫描单元了,但扫描输入引脚和扫描使能引脚映射反了,工具照样能跑完,只是拼接出来的扫描链在硅上表现异常,这种问题在仿真阶段几乎发现不了。
这就是为什么我一直跟团队强调,DFT 流程的第一优先级不是覆盖率调优,而是把库模型核对干净。它相当于一本字典,后面所有分析都是"查字典"的结果。字典错一个词条,翻译出来的整句话都是错的。
1.2 标准单元库模型在流程中的位置
要理解 Tessent_StdcellLib 的价值,得先看清它在整个 DFT 流程里的坐标。上游是代工厂交付的工艺套件,里面包含器件模型、版图信息、Liberty 时序库(.lib)、Verilog 行为模型等;下游是 Tessent 这类工具做扫描插入、ATPG、诊断。Liberty 库是为综合和时序分析服务的,它关心的是延时、功耗、面积,对"这个触发器能不能当扫描单元用"这件事并不上心。
Tessent 需要的是另一套视角:功能语义加上测试语义。功能语义来自 Liberty 的逻辑功能和时序弧,测试语义则要额外补充——哪个单元是扫描单元、扫描数据从哪个脚进、从哪个脚出、扫描使能是哪个脚、锁存器是主从结构还是单级、时钟门控单元的门控方式是电平还是脉冲。这些信息 Liberty 里通常没有,或者写得含糊,必须由库模型这一层补齐。
所以从工程角度看,一份合格的 Tessent 标准单元库,等于"Liberty 的功能信息 + 测试专用声明 + 工程侧的命名与规范"三者叠加。缺少任何一块,流程都会在某个环节掉链子。
| 关注点 | Liberty 侧重 | Tessent 库模型侧重 |
|---|---|---|
| 引脚方向 | 有 | 有,且必须与网表一致 |
| 逻辑功能 | 有,供综合使用 | 有,供可控性/可观测性分析 |
| 时序信息 | 完整,含延时表 | 关键路径弧,够分析用 |
| 扫描属性 | 一般缺失 | 必须有,决定扫描链能否拼接 |
| 时钟/门控属性 | 部分有 | 必须显式声明 |
| 电源/地/填充单元 | 有 | 需要,用来避坑和过滤 |
| 单元命名 | 库内命名 | 需与网表实例名严格对应 |
1.3 库模型出错的代价有多高
很多新人会觉得,库模型不就是个配置文件吗,出错改一下不就行了?实际代价比想象中高得多。在扫描插入阶段出错,你会看到扫描链长度对不上、链上单元数比预期多或者少、工具报出莫名其妙的时钟域冲突,这时候你还能靠调试找回来,因为设计还没流片。
到了 ATPG 阶段出错,代价就开始放大。覆盖率曲线卡在某个数字上不去,你花两天调配置、调压缩、调 X 处理,最后发现是某个门控单元没建模导致一片逻辑不可控,这两天就是纯浪费。到了诊断阶段出错更麻烦,因为诊断依赖的是已经生成的向量和库模型的一致性,库不准确会导致诊断结果把故障定位到错误的单元上,良率分析跟着跑偏。
最贵的是硅上出错。扫描链在仿真里全过,硅上测出来某个链的某几颗芯片失效,排查到最后发现是库模型里锁存器的建立保持关系写错了,导致测试向量在某些工艺角下不满足时序。这种问题一旦发生,不是改个文件就能收场的。
2. 走进库内部:标准单元模型的字段与关键属性
2.1 一个可用库文件的骨架长什么样
抛开具体版本差异,一份 DFT 用的库文件大体上是"库头 + 单元集合"的结构。库头声明库名、版本、工艺角、单位等信息;单元集合里每个单元包含引脚定义、功能表达式、时序弧、以及测试相关的声明。下面是一段结构示意,字段名在不同版本里会略有差异,写的时候一定要对着你手头版本的参考示例改,别照抄别家的格式。
library (sc9mc_logic_tt_1v0) { # 库级信息:工艺角、单位、版本 cell (SDFFRQ_X1) { pin (D) { direction: input; } pin (SI) { direction: input; } pin (SE) { direction: input; } pin (CK) { direction: input; clock: true; } pin (RN) { direction: input; } pin (Q) { direction: output; function: "IQ"; } } cell (CLKAND2_X1) { pin (A) { direction: input; } pin (B) { direction: input; clock_gating: true; } pin (Z) { direction: output; function: "A & B"; clock: true; } } }这段代码里最值得说的不是语法,而是它体现的思路:库模型把每个单元当成一个黑盒来描述,描述得越准确,工具的分析就越接近真实。你可以把它类比成给工具画一张"单元身份证",身份证上写清楚了长相、入口、出口和脾气。
2.2 功能建模:function 怎么写才不出错
功能表达式是库模型里最容易出错、也最容易被忽视的部分。一个与非门写成!(A & B)还是~A | ~B,逻辑上等价,但工具内部做可控性分析时的推导路径可能不一样,写错一个括号含义就完全变了。经验做法是:功能表达式尽量写得和 Liberty 里一致,不要自己"优化"。
另一个高频坑是三态单元和双向单元。很多库在建模时把三态输出直接写成普通输出,或者把使能端漏掉,结果 Tessent 在分析总线冲突和总线保持时完全失去判断依据。我的建议是,凡是涉及三态、双向端口、总线保持的单元,全部单独列一张清单逐个核对,不要指望批量转换脚本能处理干净。
还有一个细节:常量单元(Tie High/Tie Low)的处理。有些流程会因为库里没建模常量单元,导致工具把它们识别成未知逻辑,进而影响常量传播。这类单元逻辑极其简单,但漏掉就是漏掉,没有别的办法,只能补上。
2.3 时序单元与时序弧:为什么要给 D 端到 Q 端"讲道理"
触发器是扫描链的骨架,对触发器的建模必须格外严谨。除了引脚功能,还要描述它的时序行为:数据从 D 到 Q 的传播关系、时钟沿的敏感方式、异步复位和置位的作用范围。工具要知道这些,是因为它在拼接扫描链的时候会做"移位路径分析",判断从上一级 Q 到下一级 SI 的路径上是否存在竞争,是否需要插入锁存器(lockup latch)。
如果一个触发器的时钟沿敏感方式写错了,工具很可能判断出错误的移位方向,然后给你报一堆"扫描链时序冲突"。这种报错看起来像是约束问题,实际上是库的问题。排查方法很简单:挑一个最简单的触发器单元,单独写一个小网表跑一遍,看工具识别出来的时钟沿是否和手册一致。
锁存器(Latch)的建模比触发器更麻烦,因为它涉及透明态和保持态。对于主从结构的锁存器扫描单元,必须明确哪个是主、哪个是从、扫描数据进入哪一级、移出从哪一级。这块如果含糊,扫描链在高速移位时会出现数据穿透,仿真不一定能发现,硅上一定会发现。
注意:时钟沿极性、异步复位置位极性这类"极性"信息,是库建模里最容易被复制粘贴搞错的地方。复制一个触发器改成另一个,一定要把极性相关的字段重新核对一遍。
2.4 扫描单元的关键定义:shift 与 capture 语义
扫描单元和普通触发器在功能上没区别,区别在于多了几个专用引脚和一组测试语义声明。典型的一个带扫描的 D 触发器,会有数据输入 D、扫描输入 SI、扫描使能 SE、时钟 CK、输出 Q。库模型需要告诉工具:这个是扫描单元,数据从 SI 进、从 Q 出、SE 高电平时工作在扫描模式。
隐性的要点在于"模式切换"的建模。所谓扫描模式和功能模式,在工具眼里其实是同一套网表的两种激励条件。库模型不需要写两遍功能,但需要保证工具能正确推导出在 SE 为特定值时,SI 到 Q 的传播关系是成立的。如果这个关系推导不出来,扫描链就是断的,工具只能报错。
实际项目里我还遇到过一种情况:某些低功耗库提供了带时钟门控的扫描单元,扫描路径和功能路径共用时钟,但门控使能在扫描模式下必须固定。这类单元在库里需要额外声明,否则工具在移位时会因为时钟被门控而认定路径不连通。这属于典型的"功能上没问题,测试上必须特殊处理"的案例。
2.5 时钟门控、多路选择、tie、填充单元的特殊处理
时钟门控单元是覆盖率杀手,也是库建模里最需要认真对待的一类单元。集成时钟门控单元内部通常含有锁存器和与门,如果只按组合逻辑建模,工具就看不到门控使能的可控性,ATPG 会认为门控后面的所有触发器时钟不可控,覆盖率直接掉一大截。正确的做法是把它作为一个带时钟属性的单元完整建模。
多路选择器在扫描插入阶段非常关键。工具会自动用 MUX 来切换功能时钟和测试时钟,如果库里没有可用的 MUX 单元,或者 MUX 的建模不完整,工具就没法完成时钟切换逻辑的插入,扫描插入会直接失败。这个环节对 MUX 的选择性要求(比如是否可复用现有 MUX)也要在配置里说明。
Tie 单元和填充单元看起来无关紧要,实际上很容易引发 DRC 报错。有些流程会因为网表里出现了未建模的填充单元而报未知单元,虽然不影响功能,但会污染报告,让真正的问题被淹没。处理方式是两种:要么在库里补上这些单元的最小定义,要么在流程里通过配置明确忽略。我的习惯是补定义,因为忽略的口子一旦开了,后面很难收回来。
3. 从 Liberty 到可用的 Tessent 库:实操流程
3.1 环境与工具准备:版本配套比什么都重要
动手之前先把环境理顺。这里必须说一句实在话:工具安装介质一定通过正规渠道获取,网上流传的"tessent安装包"来源不明、版本混乱,装上去轻则命令不存在,重则库解析行为和你同事的环境不一致,debug 到天亮都找不到原因。我见过最离谱的一次是两个人用"同一个版本",实际上是两个不同的补丁版本,同一个库文件解析出来的单元数都不一样。
需要确认的清单大致如下:
| 项目 | 需要确认的内容 |
|---|---|
| 工具版本 | Tessent 主版本与补丁号,团队内保持一致 |
| License | 环境变量配置正确,能覆盖用到的功能模块 |
| 工艺库版本 | Liberty 与版图、网表来自同一版本,避免混用 |
| 网表来源 | 综合脚本产出的网表与库单元命名规则一致 |
| 脚本环境 | Tcl 版本、公共脚本库路径、日志规范 |
| 参考示例 | 手头版本自带的库文件示例,作为格式基准 |
环境准备好之后,先别急着跑全流程,拿一个小模块练手。全芯片跑一次动辄几小时,用错误的环境去跑是纯粹的浪费时间。
3.2 库文件生成与转换的核心步骤
从 Liberty 到 Tessent 可用的库,通常有三种路径。第一种是直接使用工具自带的转换能力,把 Liberty 作为输入编译成工具内部格式,这种方式省事,但生成的模型往往缺少测试语义,需要人工补充扫描单元声明。第二种是工具厂商或代工厂直接提供适配好的库文件,这种最省心,但版本更新经常滞后,新工艺新单元支持不及时。第三种是自己写脚本从 Liberty 提取信息再生成库文件,灵活度最高,维护成本也最高。
我个人的建议是:以第二条路径为主,第三条路径为辅。也就是说,优先用官方提供的库文件,用脚本做增量补充和版本比对。全量自研一套库生成流程,对大多数团队来说投入产出比并不划算。
无论走哪条路,转换完成后一定要做单元数量的核对。做法很简单:把网表里出现的所有单元名提取出来,去重,然后和库文件里定义的单元名做差集。差集为空才说明覆盖完整,有差异就逐个确认是漏了还是命名不一致。
# 从网表提取所有实例的单元名(示意,按实际网表格式调整) grep -oE '^[[:space:]]*[A-Za-z_][A-Za-z0-9_]*' design.v | sort -u > net_cells.txt # 从库文件提取所有定义的单元名 grep -oE 'cell[[:space:]]*\([A-Za-z0-9_]+\)' sc9mc_logic.lib | sort -u > lib_cells.txt # 查看差集:只出现在网表里、库里没有的 comm -23 net_cells.txt lib_cells.txt这段脚本是很粗糙的启发式方法,实际网表有层次结构和参数化实例,需要配合工具导出的实例清单使用,但它能把大部分明显的缺口暴露出来,比人眼翻日志强得多。
3.3 模型抽查:工具自检加人工核对
转换完的库不要直接投入生产,先做两轮抽查。第一轮是工具自检,让工具把库读一遍,输出单元统计和告警信息。常见要看的指标包括:识别的单元总数、识别的扫描单元数、被当作黑盒的单元数、功能表达式解析失败的单元数。这四个数字里任何一个异常,都要先解决再往下走。
第二轮是人工抽查,重点抽查三类单元:使用频率最高的触发器、所有扫描单元、所有时钟相关单元。抽查方法就是挑一个单元,写一个只包含该单元的小网表,跑一遍最简流程,看工具的分析结果是否和手册一致。抽样不需要覆盖全部,但覆盖面要够,我的经验是前两类各抽十到二十个就有代表性。
# 读取库文件并输出统计信息(命令名以手头版本的手册为准) set_context dft -scan read_cell_library ./lib/sc9mc_logic_tt.lib read_cell_library ./lib/sc9mc_scan_tt.lib report_cell_library > ./rpt/cell_library_summary.rpt注意:库文件的读取顺序有时会影响解析结果,特别是当扫描单元定义和基础单元定义分开放在多个文件里时。建议把基础库放在前面、扩展库放在后面,并用报告确认最终生效的定义是你期望的那一份。
3.4 与网表对接:读库、设上下文、建设计的顺序
库读进来之后,接下来是和网表对接。这一步的坑主要出在顺序上。正确的做法是先设定上下文(告诉工具这次是做扫描插入还是做向量生成),再读库,再读网表,最后设定当前设计。顺序颠倒的后果是工具可能用错的分析模型,或者在读网表阶段就把某些单元归到黑盒里,后面再改就麻烦了。
set_context dft -scan read_cell_library ./lib/sc9mc_logic_tt.lib read_verilog ./netlist/top_syn.v set_current_design top # 后续:定义时钟、复位、扫描信号,跑 DRC读完之后立刻跑一次设计规则检查,不要急着做插入。DRC 报告是库模型质量最直接的体检单,前面提到的"单元未建模""扫描单元识别失败""时钟不可控"这些问题,都会在这一步暴露。把 DRC 清干净再往下走,这个习惯能省掉后面大量的返工。
3.5 小规模验证:先跑通一个 Block 再上全芯片
全芯片验证前,一定要拿一个规模适中、结构有代表性的 Block 做端到端验证。所谓端到端,是从读库读到生成向量、再到仿真比对,完整走一遍。这一步的目的不是验证功能,而是验证库模型在不同工具阶段的一致性。
具体做法是:选一个包含触发器、锁存器、门控单元、多路选择器、三态单元的 Block,跑完扫描插入后导出网表,再做一遍 DRC,然后生成向量,把向量灌回仿真器看是否全过。全过说明库模型基本没问题,可以放大到全芯片。如果某个阶段出错,问题范围就被限制在这个 Block 里,排查效率比在全芯片上瞎找高得多。
我一般还会额外加一步:故意在库里改错一个引脚方向,看工具报不报错、报什么错。这个"反向验证"能让你对工具的错误信息建立直觉,以后遇到类似报错能立刻反应过来。听起来有点折腾,但做过一次之后,排查速度会明显不一样。
4. 常见问题与排查技巧实录
4.1 常见报错速查表
下面这张表是我这些年攒下来的,基本覆盖了库模型相关报错里最常见的几类。遇到问题时先按现象定位,再看根因,最后按处理方向走,不要一上来就改脚本。
| 现象 | 常见根因 | 处理方向 |
|---|---|---|
| 报单元未建模 | 库里缺该单元,或命名大小写、参数化后缀不一致 | 比对网表单元名与库单元名,补齐定义 |
| 扫描单元识别不到 | 未声明扫描属性,或 SI/SE/CK/Q 映射错误 | 核对引脚映射,重新声明扫描单元 |
| 覆盖率卡住不动 | 门控单元未建模,或时钟属性缺失 | 补全时钟与门控声明,检查时钟域 |
| DRC 报时钟不可控 | 时钟树上的单元被当黑盒 | 补齐时钟路径上所有单元模型 |
| 移位路径不连通 | 触发器时钟沿极性写错,或锁存器主从标注反了 | 单单元小网表验证极性 |
| 库读入后单元数偏少 | 多个库文件覆盖冲突,读取顺序不对 | 调整读取顺序,核对最终生效定义 |
| 版本升级后大面积报错 | 库文件格式随工具版本变化 | 用新版转换流程重新生成 |
4.2 单元被当黑盒的三种典型原因
第一种是纯漏缺。新工艺库增加了新单元类型,比如某种低功耗隔离单元,而库模型还是上一版,自然识别不了。这种情况最直观,补上就行。
第二种是命名不一致。综合工具在做实例化时可能会给单元名加后缀,或者大小写规范化处理,导致库里叫DFFRQ_X1,网表里成了dffrq_x1。有些工具对大小写敏感,有些不然,这个差异在不同版本间还可能变化。排查方法是直接在网表里搜这个单元名,看实际拼写。
第三种是条件定义。某些库通过参数化生成多种变体,比如同一个功能单元有不同驱动强度、不同阈值电压版本,库模型只覆盖了其中一部分。这种问题最隐蔽,因为报错只出现一次,很容易被当成偶发问题忽略。处理办法是统计网表里每个单元的使用次数,从高频单元开始逐个确认。
4.3 时钟门控导致覆盖率上不去的排查
覆盖率卡住是最让人抓狂的问题,因为它不像报错那样有明确指向。判断是不是库模型导致的,有个简单的自检方法:看覆盖率报告里未被覆盖的故障集中在哪些逻辑锥。如果这些逻辑锥的根都在某个时钟门控单元后面,那基本可以确定是门控建模的问题。
确认之后,处理分两步。先检查库模型里这个门控单元的类型是否正确,是不是被当成了纯组合逻辑;再看它的门控使能引脚是否正确声明了可控性。有些工具提供专门的报告来展示门控单元的状态,用这个报告比翻日志快得多。
还有一个细节容易被忽略:门控单元在扫描模式下通常需要旁路,让时钟在整个移位过程中保持打开。如果库模型没有配合这种旁路机制,工具在移位时会认为路径断开。这类问题在报告里往往表现为"移位失败"而不是覆盖率问题,需要结合两个阶段的报告一起看。
4.4 扫描链拼接异常的定位思路
扫描链长度和预期不一致、链上单元数不对、链顺序错乱,这三类问题属于扫描链拼接异常。定位思路是先固定变量:把设计裁剪到只剩几条链,减少干扰;然后逐步放开,看从哪一步开始出问题。
我常用的排查序列是这样的:先用极简设计验证库模型本身,再用手工指定的链顺序验证插入逻辑,最后放开自动模式。每一步都保存一份报告,对比着看。差异出现的位置就是问题所在。
另外一个经验是,链顺序错乱很多时候不是库的问题,而是时钟域和边沿类型混杂导致的。工具会按照一定规则对触发器排序,如果同一个链里混了上升沿和下降沿触发器,排序结果可能和你的预期不一致。这种情况要么调整链规划,要么接受工具的顺序并在后端配合处理。
4.5 版本与工艺角错配的隐蔽坑
这是我最想强调的一条。库文件一定要和工艺角、网表来源严格对应。典型错误是拿 TT 角的库去做 SS 角网表的测试分析,或者用上一版工艺的库去读新一版综合出来的网表。这类问题不会立刻报错,但分析结果会系统性偏离。
判断是否错配的方法有两个:一是核对库头里的工艺角标识和网表综合时使用的约束是否一致;二是对比库中单元列表和网表单元列表的相似度,如果差异超过一个合理阈值,就要警惕。我个人的习惯是在项目目录里按"工艺—角—版本"三层命名,从源头杜绝拿错文件。
注意:换工艺或者换库版本时,不要只替换库文件然后重跑流程,一定要重新走一遍单元素抽查和 Block 端到端验证。库变了,之前所有的验证结论都作废。
5. 把库维护做成工程化的事
5.1 目录与命名规范先定下来
库文件的混乱往往源于目录和命名的随意。我推荐的结构是按层次分目录:顶层放项目,下面按工艺节点分,再按工艺角分,最后按版本分,每一层都带明确的标识。文件名统一格式,比如"工艺_角_类型_版本",一眼就能看出用途。
做到这一点之后还有两个好处:一是做版本比对的时候容易找到上一版,二是新人接手时不需要问来问去。很多团队把库文件随手扔在一个共享目录里,改一次覆盖一次,出了问题连回滚都做不到,这种情况我在多个项目里见过。
规范的另一部分是变更记录。每次修改库文件都要记一笔:改了什么单元、为什么改、谁验证的、验证结论是什么。这份记录在项目后期排查问题时价值极高,因为很多诡异现象的源头都在几个月前的一次小改动上。
5.2 用脚本把重复劳动干掉
库维护里最枯燥的部分是重复核对:单元列表比对、引脚映射检查、扫描单元数量统计、版本差异对比。这些工作全部可以脚本化。我的做法是写一组小工具,每个工具只干一件事,串成一条流水线,每天或者每次库更新后自动跑一遍,把结论汇总成一份报告。
import re def read_lib_cells(lib_path): """粗略提取库文件中定义的单元名""" cells = set() with open(lib_path, errors="ignore") as f: for line in f: m = re.search(r'\bcell\s*\(\s*([A-Za-z0-9_]+)\s*\)', line) if m: cells.add(m.group(1)) return cells def read_net_cells(net_path): """从实例清单中提取单元名(实例清单一般由工具导出)""" cells = set() with open(net_path, errors="ignore") as f: for line in f: name = line.strip() if name: cells.add(name) return cells net = read_net_cells("net_cells.txt") lib = read_lib_cells("sc9mc_logic.lib") missing = sorted(net - lib) print("网表里存在但库里没有的单元数:", len(missing)) for c in missing[:50]: print(c)脚本不需要写得多漂亮,关键是能稳定跑、结论准确。上面这段只是骨架,实际用的时候要根据网表格式做适配,特别是带参数化实例和多层次设计的情况,用工具导出的实例清单比直接正则匹配网表可靠得多。
5.3 新工艺和新版本的迁移策略
换工艺或者升级工具版本的时候,库模型的迁移是绕不开的。我的建议是把它当成一次小项目来做,而不是顺手改改。具体分四步:先收集新版本的库文件并做单元列表比对,找出新增和废弃的单元;再对新增单元逐个建模并验证;然后拿一个历史 Block 做回归,看结果和旧版本是否一致或者可解释;最后才切换到正式流程。
回归这一步特别重要,因为工具版本升级有时会改变某些分析行为,比如对门控单元的默认处理方式变了,或者对锁存器的排序规则调整了。这些变化不会报错,但会体现在覆盖率数字上。如果不做回归,你会在项目中途突然发现覆盖率数字和之前对不上,然后花大量时间找原因。
5.4 几个不值得踩的坑
最后分享几条我自己的体会,都是吃过亏才记住的。
第一条,不要图省事跳过单元素验证。我见过太多次因为跳过这一步,到最后不得不回头逐个单元重查,时间成本是提前验证的十倍以上。
第二条,不要把库文件和脚本混在一起管理。库文件是数据,脚本是逻辑,两者的变更节奏完全不同。混在一起之后,改脚本会导致库文件的版本号也跟着动,追溯起来一团乱。
第三条,重要节点一定要留存一份已验证的库快照。项目中途因为某个紧急需求改了库,改完之后发现整体结果变差了,这时候如果没有快照,你连退回去的选项都没有。
第四条,团队内维护一份"库问题记录",谁遇到什么现象、怎么解决的,都记下来。这个问题库里积累到几十条之后,新人上手速度会明显提升,因为它比任何文档都贴近真实场景。
第五条,对工具报错保持耐心。库模型相关的报错信息往往不直接指向根因,工具会说"单元未建模",但真正的问题可能是命名不一致或者定义被覆盖了。养成看到报错先验证假设的习惯,而不是直接改配置。
5.5 关于获取工具与资料的渠道
顺带提一句,网上搜"tessent安装包"的人不少,我的态度很明确:工具和工艺库都走正规渠道获取。一方面版本可控、有技术支持,另一方面库文件的格式和语义在不同版本间确实存在差异,非正规渠道拿到的包很可能和团队环境不匹配,排查成本远高于省下的那点事。真正值得花时间的地方是把库模型维护成一套可复用、可追溯、可自动校验的工程资产,而不是到处找包。这件事做扎实了,后面每一个项目的 DFT 流程都会省下大量返工时间,这笔账怎么算都划算。