3、扫描链设计(Scan Chain)

扫描链,说白了就是给芯片装上一套「体检系统」。

我刚开始做DFT那会儿,总觉得扫描链就是简单地把寄存器串起来。后来踩过坑才明白——这里面的门道,远比想象中多。

3.1 扫描链原理

正常工作时,寄存器各司其职。但测试时,我们需要把它们串成一条链。

为什么会这样?因为芯片内部节点太多,直接测根本测不过来。你想想看,几百万个节点,每个都拉一根测试针?不现实。

所以就有了扫描链的思路:

  • 正常工作模式:每个寄存器独立工作,各管各的
  • 扫描移位模式:所有寄存器串成一条大链,数据像流水一样传下去
  • 捕获模式:让组合逻辑跑一下,把结果抓到寄存器里

我在项目中遇到过一种情况:某次芯片测试覆盖率死活上不去,查了半天发现是扫描链结构设计不合理,导致某些路径根本测不到。嗯,从那以后我设计扫描链时都会先画个拓扑图。

核心要点:扫描链的本质,是把时序电路在测试时「降维」成组合电路。这样就能用ATPG工具自动生成测试向量了。

3.2 扫描触发器

扫描触发器,就是普通触发器加了个「二选一」开关。

结构其实很简单:

// 扫描触发器结构示意
module scan_flipflop (
    input  clk,
    input  rst_n,
    input  d,        // 正常数据输入
    input  si,       // 扫描输入
    input  se,       // 扫描使能
    output q,        // 正常输出
    output so        // 扫描输出(通常就是q)
);

    reg q_reg;
    
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n)
            q_reg <= 1'b0;
        else if (se)
            q_reg <= si;  // 扫描模式
        else
            q_reg <= d;   // 正常模式
    end
    
    assign q  = q_reg;
    assign so = q_reg;  // 扫描输出就是寄存器输出
    
endmodule

这里有个细节:soq 通常是同一个信号。但有些设计会单独拉一个扫描输出缓冲,避免影响正常路径时序。

我个人习惯用带 MUX 的扫描触发器,而不是用独立的MUX加触发器。为什么?因为集成在一起面积更小、时序更好。你想想看,多一个MUX就多一段走线延迟。

经验之谈:选择扫描触发器时,要注意它的建立时间和保持时间。我曾经用过某家工艺的扫描触发器,扫描模式下的建立时间比正常模式多了30%,结果导致扫描频率上不去。

3.3 扫描输入输出

扫描输入输出,就是芯片的「体检入口」和「体检出口」。

信号 方向 说明
scan_in 输入 测试数据从这儿灌进去
scan_out 输出 测试结果从这儿吐出来
scan_enable 输入 控制扫描模式/正常模式
scan_mode 输入 全局扫描模式控制(可选)

设计扫描输入输出时,有几个坑要注意:

  • IO复用:扫描输入输出通常复用功能IO。测试时切过去,正常工作时切回来
  • 电平转换:如果扫描链跨电压域,要加电平转换器
  • ESD保护:扫描IO也要有ESD保护,别省这个

我曾经在一个项目中,因为扫描输出没有加足够的驱动能力,导致测试机台读到的信号全是毛刺。查了两天才发现是驱动强度不够。从那以后,我设计扫描IO时都会留20%的余量。

警告:扫描输入输出不要共用同一个IO。否则你没法同时灌数据和读结果,测试效率会大打折扣。

3.4 扫描使能控制

扫描使能(scan_enable),是整个扫描链的「总开关」。

它的控制逻辑其实不复杂:

// 扫描使能控制示意
assign scan_mode = scan_enable & test_mode;

// 每个扫描触发器的选择信号
// se = 1: 扫描移位模式
// se = 0: 正常/捕获模式

但实际设计中,扫描使能的控制要精细得多:

  • 全局使能:所有扫描链共用一个使能信号。简单,但灵活性差
  • 分组使能:不同扫描链用不同的使能。灵活,但控制复杂
  • 时钟门控:扫描使能要和时钟配合好,避免时序问题

我个人建议用分组使能。为什么?因为不同模块的测试需求不一样。比如CPU核需要高频测试,而外设接口可能只需要低频测试。分组使能可以灵活控制。

这里有个关键点:扫描使能的切换时机

你想想看,如果扫描使能在时钟沿附近变化,会怎么样?

对,会产生毛刺。所以扫描使能通常要经过同步处理:

// 扫描使能同步处理
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        se_sync1 <= 1'b0;
        se_sync2 <= 1'b0;
    end else begin
        se_sync1 <= scan_enable;
        se_sync2 <= se_sync1;
    end
end

assign se = se_sync2;  // 同步后的扫描使能

避坑指南:我曾经在一个项目中,扫描使能没有做同步处理,结果测试时频繁出现数据错误。后来加了三级同步寄存器,问题就解决了。记住:跨时钟域的信号,一定要同步!

3.5 扫描链设计实战要点

说了这么多理论,来点实际的。

设计扫描链时,我通常会按以下步骤来:

  1. 确定扫描链数量:根据芯片规模和测试时间要求来定。一般每条链不超过10000个触发器
  2. 分配扫描触发器:把功能相关的寄存器尽量放在同一条链上
  3. 插入扫描单元:用工具自动替换,但人工检查关键路径
  4. 连接扫描链:注意走线长度,避免跨模块长线
  5. 验证扫描链:跑仿真,确保每条链都能正常工作

嗯,这里要特别强调一下验证。我见过太多人设计完扫描链就直接交出去了,结果测试时发现链根本不通。所以我的习惯是:每设计完一条链,先跑个简单的「链测试」——灌一串0101进去,看能不能原样吐出来。

小技巧:扫描链的测试频率通常比功能频率低。但也不要太低,否则测不出时序问题。我一般用功能频率的1/3到1/2作为扫描频率。

最后说一句:扫描链设计不是一锤子买卖。随着芯片迭代,扫描链也要跟着优化。保持设计文档的更新,比什么都重要。