4、扫描链插入流程:RTL代码准备、综合工具设置、扫描链插入命令、验证扫描链连接

好,咱们进入第四章。扫描链插入,这可以说是DFT工程师的看家本领了。很多刚入行的朋友觉得这步就是跑个脚本完事,其实不然。我做了这么多年,踩过的坑比走过的路还多,今天就把这些经验掰开揉碎了讲给你听。

4.1 RTL代码准备:别让综合工具“看不懂”

RTL代码准备,说白了就是让你的设计能被综合工具识别出哪些是触发器,哪些是组合逻辑。工具要能准确地找到所有需要插入扫描链的寄存器。

核心要求就两条:

  • 时钟和复位要清晰:所有触发器必须由同一个时钟沿(比如上升沿)触发。异步复位信号要统一,别搞什么混合复位。
  • 避免“黑盒”:不要用门级网表例化在RTL里。工具看到门级网表,它没法自动推断出里面的寄存器结构。

我个人习惯:在RTL里,我会把所有触发器的时钟端口统一命名为 clk,复位端口统一命名为 rst_n(低有效)。这样综合脚本里写约束也方便,一眼就能看出来。

举个例子,一段规范的RTL代码长这样:

module my_core (
  input  wire       clk,
  input  wire       rst_n,
  input  wire [7:0] data_in,
  output reg  [7:0] data_out
);

  always @(posedge clk or negedge rst_n) begin
    if (!rst_n)
      data_out <= 8'b0;
    else
      data_out <= data_in;
  end

endmodule

嗯,这里要注意:千万别在 always 块里写组合逻辑和时序逻辑混在一起。比如:

// 错误示范!千万别这么写
always @(posedge clk or negedge rst_n) begin
  if (!rst_n) begin
    data_out <= 8'b0;
    flag     <= 1'b0;
  end else begin
    data_out <= data_in;
    flag     =  some_comb;  // 阻塞赋值混在里面,工具会懵
  end
end

我曾经在一个项目里接手过这样的代码,综合工具死活报寄存器识别错误。查了两天才发现是阻塞赋值和非阻塞赋值混用导致的。从那以后,我要求团队所有RTL代码必须通过lint检查才能进入综合流程。

4.2 综合工具设置:告诉工具你要做什么

工具设置,就是给综合工具(比如Synopsys的DC或Cadence的Genus)下指令,告诉它:“嘿,我要做扫描链插入,你帮我准备好。”

关键设置项:

  • 定义扫描时钟:指定哪个时钟是扫描时钟。通常就是功能时钟,但有时也会用独立的扫描时钟。
  • 定义扫描使能:指定扫描使能信号(scan_enable)。这个信号在测试模式下拉高,让所有触发器进入移位模式。
  • 定义扫描输入/输出:指定芯片的扫描输入引脚(scan_in)和扫描输出引脚(scan_out)。

在DC里,典型的设置脚本长这样:

# 设置扫描链
set_scan_configuration -chain_count 1
set_scan_configuration -clock_mixing no_mix
set_scan_configuration -style multiplexed_flip_flop

# 定义扫描端口
create_port -direction in  scan_in
create_port -direction out scan_out
create_port -direction in  scan_enable

# 设置扫描使能
set_dft_signal -type ScanEnable -port scan_enable -active_state 1
set_dft_signal -type ScanClock  -port clk        -timing {45 55}
set_dft_signal -type ScanDataIn -port scan_in
set_dft_signal -type ScanDataOut -port scan_out

避坑指南:我曾经遇到过一个问题,扫描时钟的时序设置不对。工具默认时钟占空比是50%,但我的PLL输出占空比是45%。结果插入扫描链后,仿真时移位总出错。后来我把 -timing 参数改成了 {45 55},问题就解决了。所以,时钟的占空比一定要跟实际PLL输出匹配。

4.3 扫描链插入命令:一键生成扫描链

设置都做好了,接下来就是执行插入命令。这一步其实很简单,但背后的逻辑很复杂。

核心命令:

  • insert_dft:这是DC里的命令,一键完成扫描链插入。
  • connect_scan_chain:有些工具需要手动连接扫描链,这个命令就是干这个的。

在DC里,执行完 insert_dft 后,工具会做以下几件事:

  1. 把所有触发器替换成带扫描功能的触发器(Muxed-D flip-flop)。
  2. 根据你定义的扫描使能信号,把触发器的数据输入从功能路径切换到扫描路径。
  3. 把触发器串成一条或多条链,从 scan_inscan_out

完整的插入流程:

# 1. 读入设计
read_verilog my_design.v
current_design my_core
link

# 2. 设置DFT
set_scan_configuration -chain_count 4
set_dft_signal -type ScanEnable -port scan_enable
set_dft_signal -type ScanClock  -port clk
set_dft_signal -type ScanDataIn -port {scan_in0 scan_in1 scan_in2 scan_in3}
set_dft_signal -type ScanDataOut -port {scan_out0 scan_out1 scan_out2 scan_out3}

# 3. 插入扫描链
insert_dft

# 4. 保存结果
write -format verilog -hierarchy -output my_design_scan.v
write_test_protocol -output my_design.spf

注意insert_dft 执行后,一定要检查工具的报告。看看有没有寄存器没有被替换成扫描触发器。如果有,那说明你的RTL代码有问题,或者约束没设对。我见过有人直接跳过检查就往下走,结果流片回来测试才发现有寄存器扫不进去,那叫一个惨。

4.4 验证扫描链连接:别让链子断了

扫描链插完了,你以为就完事了?不,验证才是重头戏。你想想看,一条链上几百上千个触发器,只要有一个连接错了,整条链就废了。

验证方法:

  • 形式验证:用Formality或LEC等工具,对比插入前后的网表,确保功能逻辑没被破坏。
  • 仿真验证:写一个简单的测试向量,把一串已知数据(比如 10101010)从 scan_in 扫进去,再从 scan_out 读出来,看是否一致。
  • DRC检查:工具自带的DFT DRC(Design Rule Check)会检查扫描链的完整性。

仿真验证的测试向量示例:

// 扫描链测试向量(简化版)
initial begin
  // 复位
  rst_n = 0;
  #100 rst_n = 1;

  // 进入移位模式
  scan_enable = 1;

  // 扫入数据:10101010
  repeat (8) begin
    scan_in = data_to_shift[7];  // 从高位开始
    @(posedge clk);
    data_to_shift = data_to_shift << 1;
  end

  // 切换到捕获模式
  scan_enable = 0;
  @(posedge clk);

  // 切回移位模式,扫出数据
  scan_enable = 1;
  repeat (8) begin
    @(posedge clk);
    data_out_shift = {data_out_shift[6:0], scan_out};
  end

  // 比较扫入和扫出的数据
  if (data_out_shift == 8'b10101010)
    $display("Scan chain test PASS!");
  else
    $display("Scan chain test FAIL!");
end

我个人经验:验证扫描链连接,我最喜欢用“走马灯”测试。就是扫入一串 00000001,然后看它是不是在每个时钟周期往后移一位。如果某个周期 scan_out 没变化,那肯定就是那个位置断了。这个方法简单粗暴,但非常有效。

另外,别忘了检查扫描链的时序。扫描链上的触发器是串在一起的,移位时钟频率不能太高,否则信号传不过去。我一般会在综合时加一个 set_clock_transition 约束,确保扫描时钟的边沿够缓,不会产生毛刺。

好了,扫描链插入流程就讲到这里。记住,RTL准备是基础,工具设置是关键,插入命令是执行,验证是保障。四步缺一不可。下一章我们聊聊ATPG,那又是另一个有意思的话题了。