做DFT这些年MBIST的SDC约束是我见过最容易被低估的一环。芯片功能仿真和扫描链测试都过了结果Bringup阶段MBIST一跑就出mismatch或者后端集成时check_timing抛出一大片unconstrained path——最后查下来根因往往不是MBIST RTL写错了而是约束文件里少了一条针对BIST模式的false path或者某个时钟在MBIST模式下没有被正确创建。这类问题隐蔽性强、排查成本高尤其在shared bus架构的MBIST设计里多颗memory共享一个控制器总线选择、数据回读路径一旦约束不到位违例会像雪崩一样涌出来。这篇文章以我自己项目里沉淀的模板为主线把MBIST SDC约束拆开讲清楚先聊为什么MBIST值得单独一套约束视角再给出可复用的SDC文件骨架然后把时钟复位、模式切换、比较器异步路径这几个最容易出问题的点逐个展开最后附上检查和排错的常用手段。适合正在写或维护DFT约束的工程师、做后端集成的朋友以及刚接触DFT想系统理解SDC约束逻辑的入行同学——希望你们少走一些我踩过的弯路。1. MBIST为何需要一套独立的SDC视角一个常见的误解是MBIST逻辑也是同步数字电路功能约束往上叠不就行了实际上MBIST模式下的时钟行为、复位策略、路径活性跟功能模式和扫描模式都有显著差异。它更像是介于“功能测试”和“扫描测试”之间的一种特殊测试形态约束必须单独构建。1.1 功能模式、扫描模式与MBIST模式的约束差异我们先看三种模式在约束层面的核心差异。功能模式下所有逻辑路径都active时钟是正常的门控时钟复位按芯片真实行为走。扫描shift模式下scan_enable为1时钟自由翻转数据在扫描链上逐级搬移这时约束的重点是保证shift路径的建立/保持满足要求。MBIST模式则完全不同。进入BIST后控制器接管memory的地址、数据、控制信号时钟通常被强制旁路掉clock gating变成自由运行的时钟free-running clock。相比scan模式MBIST模式下scan_enable必须为0——因为要保证memory在正常读写下被测试而不是被扫描链逻辑干扰。这个看起来不起眼的差异恰恰是很多约束冲突的来源。约束维度功能模式扫描shift模式MBIST模式scan_enable不关心10时钟行为门控时钟自由翻转自由翻转memory控制功能逻辑扫描逻辑BIST控制器复位策略按需复位通常不复位BIST复位后开始测试路径活性全部activescan路径activeBIST路径active功能路径多被屏蔽所以当我们撰写MBIST SDC时必须先想清楚哪些路径在MBIST模式下真正工作哪些路径虽然存在但已经被模式信号隔离需要在约束里显式声明为false path或case analysis。SDC约束的本质不是“把所有路径都约束出来”而是“告诉工具当前模式下哪些路径是真实存在的”。1.2 MBIST控制器与存储器之间的三类路径从路径类型来看MBIST模式下存在的路径可以粗略分成三类理解这三类路径是写约束的前提。第一类是控制路径。BIST控制器输出地址、数据、写使能、片选等信号到memory的地址端口、数据端口、控制端口。这类路径是同步路径需要正常的建立时间和保持时间约束。它们的时钟源通常就是memory的时钟在约束上要注意控制器和memory是否由同一时钟域驱动。第二类是数据回读路径。memory输出的数据经过可能的mux或隔离逻辑回到BIST控制器的比较器。这里最微妙——memory的读数据在时钟沿之后经过access time才有效而BIST比较器会在下一个时钟沿采样。如果memory输出数据路径上没有额外寄存器那么这条路径是从memory内部寄存器到BIST比较器寄存器的跨单元路径必须保证一个BIST时钟周期内能从memory输出端传播到比较器输入端。第三类是异步/跨时钟路径。比如TCK时钟域内的控制寄存器配置路径与BIST系统时钟域之间的交互又比如BIST结果GO/NO-GO信号从BIST控制器输出经过异步握手逻辑送到JTAG或SoC层面的寄存器。这类路径在SDC里通常要声明为异步时钟组或直接设false path。1.3 时钟与使能信号在BIST模式下的行为变化还有一个很容易被忽略的点BIST模式下的时钟不是简单延续功能时钟就完事。许多设计为了让BIST跑得更可靠会在进入BIST模式时通过一个模式选择mux把memory的时钟切换成BIST控制器输出的时钟或绕开clock gating cell让时钟始终自由运行。这样一来SDC中memory clock的定义就需要随之切换。我的习惯是如果顶层还是用功能时钟作为memory时钟就在约束中为BIST路径单独创建一个generated clock指定source为BIST控制器的内部时钟节点。不能图省事直接用功能时钟的create_clock一带而过。否则综合工具在分析BIST控制器到memory的路径时可能算出完全错误的时钟相位关系。2. MBIST SDC模板的整体骨架我如何组织一个可复用的约束文件前面讲了原理现在直接上我一直在用的模板结构。这套东西我维护了好几个项目基本做到了“新项目拿过来改改端口名就能跑”。它不是一个文件而是一组按职责拆分的约束片段最终在顶层SDC里source进来。2.1 顶层文件划分与可复用设计我把MBIST的SDC拆成四个部分时钟定义、模式设置、时序异常、异步声明。实际项目中我会将这四个部分分别放在独立文件里由顶层统一source。这样做的好处是时钟定义部分跟具体芯片的PLL/时钟树强相关几乎每个项目都要改但mode设置和时序异常部分在架构不变的前提下可以很大程度复用。比如set_case_analysis针对哪几个测试引脚这些是DFT架构阶段就定好的不会因为memory类型变化而频繁修改。模板里我保留了完整的注释和“待修改”标记。团队协作时后接手的工程师能根据文件头部的checklist快速定位需要适配的内容而不必从头到尾读一遍几百行约束。这一点对项目交接尤其重要。2.2 关键SDC命令的职责边界在给代码骨架之前先明确每个常用命令在MBIST约束中的职责边界避免误用。create_clock和create_generated_clock负责建立时钟模型。MBIST模式下我倾向于给BIST controller相关时钟创建明确的generated clock避免工具从功能时钟沿推断出不确定的相位。set_case_analysis负责固定模式信号。test mode、scan enable、mbist enable这类信号在MBIST模式下都有确定值必须用这个命令固定。它告诉工具在这个模式配置下某些引脚是常数。set_false_path负责切断不真实存在的时序路径。比如某个信号在MBIST模式下被mux隔离那么穿过这个mux的路径就应该打false path。set_clock_groups负责划分异步时钟域。MBIST中系统功能时钟与TCK之间几乎永远是异步关系这一条必须写。set_multicycle_path处理多周期路径。比较器采样memory读数据时如果在设计中是隔一个周期才比较一次就需要multicycle path来放松约束。2.3 一套简化模板的代码骨架下面给出一套精简但结构完整的模板骨架去掉项目特定命名保留逻辑主干。实际项目中你只需要把时钟端口、模式端口、BIST控制器实例名替换成自己芯片里的名字即可。# # MBIST SDC Template # Part 1: Clock Definition # # System functional clock create_clock -name func_clk -period 10.0 [get_ports func_clk] # JTAG TCK for MBIST control register access create_clock -name tck -period 100.0 [get_ports tck] # BIST system clock (if separate from func_clk) # create_clock -name bist_clk -period 10.0 [get_ports bist_clk] # Generated clock for memory clock when muxed by BIST logic # create_generated_clock -name mem_clk_bist \ # -source [get_pins u_bist_ctrl/bist_clk_out] \ # -divide_by 1 [get_pins u_mem/clk] # # Part 2: Mode Setup # # Assert test mode, disable scan shift during MBIST set_case_analysis 1 [get_ports test_mode] set_case_analysis 0 [get_ports scan_enable] set_case_analysis 1 [get_ports mbist_enable] # For shared-bus architecture: bank select handled dynamically # set_case_analysis 0 [get_ports bank_sel[*]] ;# 如果有固定测试某bank的需求 # # Part 3: Async Clock Groups # set_clock_groups -asynchronous \ -group {func_clk} \ -group {tck} # # Part 4: Timing Exceptions # # TCK domain never synchronizes to func_clk directly set_false_path -from [get_clocks tck] -to [get_clocks func_clk] set_false_path -from [get_clocks func_clk] -to [get_clocks tck] # BIST compare path: data from memory output to comparator # set_multicycle_path -setup 2 -from [get_pins u_mem/q[*]] -to [get_pins u_bist_ctrl/comp_data[*]] # set_multicycle_path -hold 1 -from [get_pins u_mem/q[*]] -to [get_pins u_bist_ctrl/comp_data[*]]这个骨架不是拿来直接跑的它的作用是建立认知框架。真正落地时每个项目会在Part 3和Part 4里增加大量的路径例外而这些例外才是模板中最值钱的部分。3. 时钟与复位约束BIST时钟、TCK和复位释放时钟永远是SDC里的地基MBIST尤其如此。问题在于BIST模式下的时钟关系比功能模式复杂不少。3.1 BIST时钟与功能时钟的关系很多芯片并不会单独给MBIST一路时钟而是复用功能时钟在BIST模式下让时钟绕过门控。这种情况在SDC里最容易出现的坑是功能时钟路径上的clock gating cell在BIST模式下被旁路而约束文件里还保留着功能时钟经过gating cell的generated clock定义。工具沿着门控时钟路径分析时看到的时钟沿是不确定的导致BIST控制器到memory的路径计算出的时序余量完全失真。我处理这类问题的方法很简单在约束里为BIST模式单独定义一个时钟视图。如果memory clk在BIST模式下由BIST控制器内部的时钟节点驱动那就用create_generated_clock把那个内部节点定义成mem_clk_bist并把source指定为BIST控制器接收的系统时钟或TCK。这样工具在分析BIST路径时会沿着这条独立的时钟路径去计算不会再被功能时钟上的gating cell干扰。3.2 时钟分组与异步时钟域的声明MBIST系统时钟与TCK之间的异步关系我相信几乎所有DFT工程师都不会漏掉set_clock_groups但容易忘的是memory输出到SoC层面的数据通路。Shared bus架构下一颗memory的数据输出往往被回收到总线比较器或者转发到芯片其它模块。这些路径的终点可能在SoC的异步FIFO里也可能在某个与BIST时钟完全无关的同步器后面。如果SDC里没有把这两个时钟域显式声明为异步工具就会按同步路径一步步分析最后报出几十条看起来毫无道理但确实存在的违例。我的习惯是在set_clock_groups里把所有物理上异步的时钟全部声明完哪怕某些时钟域之间根本没有任何逻辑连接。多写一条声明不会带来副作用少写一条则可能让后端同事在ECO阶段对着假违例头疼半天。3.3 复位约束的常用写法与验证MBIST控制器通常有一个独立的复位信号比如bist_rst_n。这个复位信号在进入BIST模式前要被释放释放时机和BIST控制器内部状态机的启动对齐。SDC里需要对这个复位信号做recovery/removal检查吗如果复位是异步断言、同步释放那么检查是必要的。我常用的写法是把复位信号约束为异步复位然后对释放路径设置reset recovery检查set_async_recovery -from [get_ports bist_rst_n] -to [get_clocks func_clk] \ -recovery 0.3不过这里想补充一个实际经验相比在SDC里精细约束复位更重要的是和DFT架构确认BIST控制器在复位释放后是否要等若干个时钟周期才开始测试。如果控制器内部有自带的复位同步逻辑那么在复位释放路径上往往有一个固定的延迟窗口SDC里可以用set_multicycle_path把复位释放到第一个有效时钟沿之间的关系标清楚。否则primetime会在复位释放路径上报一堆无意义的违例。4. Test mode与scan mode共存模式切换的SDC处理很多芯片的MBIST是跟扫描链测试联合使用的——先跑扫描链再跑MBIST两条测试序列共用同一个test mode引脚。不同模式下同一根信号线上的约束要求完全不同SDC必须能区分这些场景。4.1 set_case_analysis如何控制模式选择引脚最常见的做法是引入芯片级别的测试模式引脚例如test_mode它的取值决定了芯片是处于功能模式、扫描模式还是MBIST模式。SDC里通过set_case_analysis固定这些引脚的值。MBIST模式下test_mode通常拉高scan_enable必须为0。因为扫描链移位在MBIST测试中是不允许发生的如果扫描链上还有数据在搬移memory的读数据会被扫描逻辑污染BIST比较器直接误报mismatch。这个错误在仿真阶段往往会被注意到但在SDC里漏掉set_case_analysis 0 [get_ports scan_enable]导致的后果不是功能错误而是工具把扫描路径和BIST路径同时分析产生一堆不现实的时序报告。另一个容易漏掉的引脚是MBIST控制器自己的使能信号比如mbist_enable或bist_mode。没有固定它为1工具会认为MBIST电路处于休眠状态从而把所有从BIST控制器出发的路径当成false path分析等于完全绕过MBIST的时序检查。这样即便BIST路径存在严重违例后端也收不到任何告警直到流片回来跑测试才暴露。4.2 shared bus架构下的bank select与数据总线约束Shared bus DFT架构在全片内核对数大的时候特别常见——一个BIST控制器通过共享地址/数据总线同时连接多个memory的BIST接口通过bank select信号选中当前要测试的memory slice。这种架构下约束的最大难点不是单条路径而是多条路径之间的对称性。BIST控制器到每颗memory的走线长度、负载、扇出都不尽相同SDC如果一刀切地给所有bank设置同样的约束工具大概率会在某些bank上报违例另外一些bank上报过量余量。我的处理思路是对shared bus上的关键路径做分bank约束而不是把所有memory塞到同一个from/to集合里。比如先按bank把每个memory的时钟端、数据端分别定义成不同的collection然后在timing exception里逐bank单独设置multicycle或max_delay。虽然约束代码量增加了但换来的是每一颗memory的时序余量都清晰可见定位问题时不用再猜是哪条bank拖了后腿。4.3 模式切换容易漏掉的MUX与隔离逻辑模式切换在RTL里通常体现为一大片mode mux。进入MBIST模式时memory的地址、数据、控制信号都从功能逻辑切换到BIST控制器。这些mux输入一端是功能逻辑另一端是BIST逻辑输出接到memory端口。如果只固定了test_mode引脚的值工具会推断出mux选择端为常数从而自动把未被选中的那一侧路径屏蔽掉。这本来是正确的但问题出在有些mux不是直接受test_mode控制而是受某个由test_mode组合出来的内部信号控制。比如一个内含反相器的逻辑工具如果不做布尔传播就不会自动把另一侧路径设为false。我在实际项目中就踩过这个坑某个memory的地址在BIST模式下会经过一级反相器mux我顺手设了test_mode1以为地址路径的时序检查已经正确切换到BIST侧结果发现工具仍然在分析功能逻辑到地址端口的路径。最后手动补了一条set_false_path -from [get_pins u_func_logic/*] -to [get_pins u_mem/addr[*]]才算干净。5. 比较器与GO/NO-GO路径异步约束的核心战场MBIST中最容易让约束出问题、也最考验DFT工程师经验的地方就是比较器路径和结果回读路径。5.1 比较器路径的同步/异步判断BIST比较器本身是同步时序逻辑它在BIST时钟沿采样memory读出的数据与内部期望数据比较。这条路径表面上是同步的但有一个隐蔽的时序问题——memory读数据有效的时间窗口和比较器采样沿之间的关系由memory的access time决定而不是由普通的寄存器到寄存器路径决定。实际约束中我通常会给memory输出到比较器输入设一个set_max_delay数值取BIST时钟周期的50%到70%具体取决于memory读时序和余量要求。这样工具在分析时会以这个max_delay为目标而不是完全依赖时钟沿计算。如果比较器内部采用了双沿采样或半周期比较也就是在时钟下降沿或上升沿两次比较数据那就要用set_multicycle_path明确告诉工具数据不是在每一个时钟沿都被采样而是在特定沿才有效。比如下降沿采样时通常设为setup为2、hold为1这样工具会按半个周期来检查建立时间半个周期检查保持时间。5.2 跨时钟与跨模式路径的false path设置MBIST比较结果最终要通过GO/NO-GO信号表达这个信号往往要跨时钟域送到JTAG控制器或芯片外部引脚。GO/NO-GO信号在设计中通常由BIST逻辑锁存后通过两个触发器同步器进入TCK时钟域。如果SDC里没有把BIST时钟域和TCK时钟域设为异步工具会在同步器路径上做一次完整的CDC检查——多半会报出几百条违例。这里的标准做法是先把BIST系统时钟和TCK加入set_clock_groups -asynchronous再对同步器的第一级触发器设置set_false_path -from [get_clocks bist_clk] -to [get_pins u_sync/q0]。这样处理有两个目的一是明确告诉工具这条路径是异步握手路径不需要时序收敛二是把同步器第二级到后级逻辑的路径保留下来让工具检查同步后数据在TCK时钟域是不是真的稳定。这类路径写多了以后我的一个经验是在约束里保持“异步声明”和“false path”分开。set_clock_groups负责建立时钟域之间的边界关系set_false_path只负责切断某一条明确已知的路径。如果把假路径一股脑全塞进set_false_path里而不声明时钟组后级逻辑的异常路径就没法被工具自动排除违例报告里会混入大量噪音。5.3 诊断寄存器与fail数据回读路径的约束现在的MBIST控制器基本都带诊断功能一旦比较失败就把失败地址、期望数据、实际数据锁存进诊断寄存器再通过JTAG口把数据回读出来。这部分路径在SDC中有两个注意点。一个是诊断寄存器本身位于TCK时钟域但它锁存的数据来自BIST时钟域。数据写入时BIST逻辑需要把数据稳定送到TCK域的寄存器输入端这又是一条跨时钟路径。如果控制器内部没有做同步器或异步FIFO单纯依赖设计保证时序SDC就必须对这条路径做严格约束。另一个是诊断数据回读路径。JTAG移位时诊断寄存器的内容要被逐位移到TDO上这个过程中TCK的翻转频率由测试机台决定通常远低于BIST内部时钟频率。SDC只需要保证TCK时钟域内部的路径满足TCK周期即可不需要让BIST数据信号跨到TCK域后还保持BIST时钟频率的时序。做法还是那句声明两个时钟域异步同时对具体的数据锁存路径做multicycle或false path处理。6. 模板落地后的检查清单与常见违例修复约束文件写完不是终点检查它是否真的“约束到了该约束的路径”才是决定流片前能否尽早发现问题的关键。我每次交付MBIST SDC时都会在primetime里做一轮系统检查。6.1 check_timing与check_design必查项check_timing输出里的unconstrained path是我第一个看的项目。MBIST模式下如果出现unconstrained path优先检查是不是某个BIST控制器输出端口没有时钟定义。尤其是当BIST模式时钟跟功能时钟不是同一个时很容易因为端口上没有时钟而报错。check_timing -verbose check_design -type physical第二个必查项是时钟树上的gating check。MBIST模式下时钟要自由运行如果某个BIST控制器的时钟端口还挂在clock gating cell后面check_timing会警告时钟树不完整。这时候就要回到Part 1检查是否漏掉了create_generated_clock。还有一个我近几年越来越依赖的习惯把MBIST模式的时序报告单独跑一遍用report_timing -from加-to把所有关键路径集合列出来人工扫一遍。工具能查出约束缺失但查不出约束是否“符合设计意图”。比如该设multicycle的地方没设工具不会报错只会把违例算在你头上。只有人工去理解路径上每个cell的延迟来源才能真正判断是约束保守了还是实际路径确实有风险。6.2 三类高发违例的根因与修法落地过程中我总结了几类高发违例基本每个项目都会碰到列成表格方便对照。现象根因修法BIST controller到memory的路径大量违例memory时钟在BIST模式下未定义或定义在多路选择器的错误一侧检查Part 1为BIST路径单独创建generated clockTCK时钟域相关路径违例爆炸未声明BIST时钟与TCK异步添加set_clock_groups -asynchronousmemory读数据到比较器路径出现hold违例比较器采样沿与memory输出窗口相位不对用set_multicycle_path或set_max_delay手动校准采样窗口GO/NO-GO信号相关假路径BIST结果同步器路径未设false path对同步器第一级设set_false_path第一类违例最常见也最好修但前提是你能看懂memory时钟在BIST模式下到底从哪来。第二类违例修起来就是加一条声明但要能在铺天盖地的违例报告中定位到它需要一点经验。第三类违例最麻烦因为memory的access time是工艺库和memory compiler给出的不能随便改你只能调整约束来匹配真实时序。6.3 经验从仿真到signoff的SDC迭代流程最后分享一个我坚持了很多项目的流程。MBIST SDC从来不是一次性写完的它至少要经过三轮迭代。第一轮在RTL freeze前后。这时RTL的命名还没有完全稳定但MBIST控制器和memory的接口已经成型。我会先搭建一个“最简约束”——只有时钟、模式引脚、异步时钟组然后在仿真环境里跑一版MBIST的VCS仿真确认pattern能不能跑通。如果仿真在某个bank上卡住多半是约束文件里某条false path切断了本不该切断的路径。第二轮在综合完成后。这时要对照综合网表检查约束。综合工具会做一些优化某些clock gating cell可能被合并或移走如果SDC里指定的source pin在网表中变了位置create_generated_clock就会失效或者warning必须及时更新。第三轮在primetime signoff前。这时做的不是重新写约束而是逐条review时序报告里的异常值确保没有“隐藏的假路径”。我给自己定了一条规矩每条false path、multicycle path都要写注释说明为什么设它作者和时间。这样半年后回来看约束自己还能想起来当初为什么这么做而不是面对一堆不知道谁加的exception满脸问号。MBIST SDC的难点不在SDC语法本身而在于你得同时理解MBIST架构、memory时序和工具行为。走完三轮迭代后模板会越来越贴近项目真实需求新项目里再遇到类似的违例基本都能一眼定位到是第几部分约束该修了。
阅读完成 · 觉得有帮助?