SpyGlass CDC检查实战:从lint到sgdc约束与回归脚本 我见过太多人把 SpyGlass 当成一个“高级 lint 工具”在用跑一遍规则把 warning 清一清报告归档然后就没有然后了。说实话这样用 SpyGlass 至少浪费了它一半的价值而且是更值钱的那一半。对于做数字前端设计和验证的工程师来说SpyGlass 真正不可替代的地方在于 CDC 检查——也就是跨时钟域静态验证。芯片规模越大时钟域越多这一环就越不能省。我从第一次在项目里完整跑通 SpyGlass 的 lint 和 CDC 流程到现在把它嵌进团队的每日回归中间踩过的坑不少这篇笔记就是把安装、工程配置、sgdc 约束、报告解读和脚本化跑回归的完整路径整理出来给正在上手或者被一堆 violation 淹没的朋友做个参考。1. 先把定位想清楚SpyGlass 到底替你解决了哪几类问题很多刚接触 SpyGlass 的人会问这工具和仿真有什么区别和综合工具里的 lint 有什么区别和后端的 timing sign-off 又有什么关系我的理解是SpyGlass 是一套基于静态分析的 RTL 质量检查和跨时钟域可靠性验证工具它不需要测试向量不需要仿真波形直接把 RTL 代码读进来按内置的规则库去扫描设计结构。这类工具的价值在于“提前量”在仿真还没有铺开、后端还没有开始布局布线的时候就能把一大批结构性问题暴露出来。它主要解决的问题可以分成几个层次。第一层是代码质量与可综合性检查也就是大家熟悉的 lint 功能覆盖位宽不匹配、锁存器意外推断、case 分支不完整、组合逻辑环路、多驱动信号、悬空输入输出等等。第二层是跨时钟域结构检查这是金子所在它会追踪每一条跨时钟域的路径确认数据在进入目标时钟域之前是否有同步处理同步器结构是否符合要求。第三层是跨时钟域协议检查比结构检查更进一步验证握手信号、FIFO 指针、格雷码转换、复位释放等行为级协议是否正确。再往外扩展还有和功耗意图 UPF 的一致性检查、DFT 结构检查等 goal不过日常项目里用的最多的还是 lint 和 cdc 这两大块。这里我想强调一个容易被低估的点SpyGlass 的 lint 检查和综合工具自带的 lint 并不冲突但我个人建议以 SpyGlass 为准。原因很简单它的规则库更细而且 lint 和 CDC 检查是共用同一套工程配置的。你 lint 阶段把约束文件、时钟定义都整理干净了后续做 CDC 检查会省非常多力气。反过来如果 lint 阶段图省事时钟约束一团乱CDC 阶段就会产生海量假违例到时候你会完全分不清哪些是真问题。所以我的习惯是SpyGlass 的工程配置从第一天就好好维护lint 和 CDC 用同一个 prj而不是各建一套。还有一个定位上的误区要纠正SpyGlass 不是用来“证明设计没问题”的它是用来“逼你把设计里说不清楚的部分讲清楚”的。比如一个跨时钟域信号你在代码注释里写“这里是异步的没事”SpyGlass 不认注释它只认你在 sgdc 约束文件里的声明。如果你没有声明它就会报 violation。这个“不讲清楚就报错”的脾气看起来烦人其实是在逼你把设计的时序假设显式化。这一点我觉得是它比仿真更有价值的地方仿真只能验证你想到的场景静态检查能把所有路径都摊开给你看。2. 安装 SpyGlass环境准备与 license 那些坑SpyGlass 现在的官方名字是 VC SpyGlass不过命令行入口还是熟悉的spyglass老工程师也都习惯叫它 SpyGlass。安装本身不复杂解压、配环境变量、license 指对路径基本就能跑起来。但恰恰是 license 环节我见过太多人在那里卡一整天。2.1 Linux 环境的基础依赖SpyGlass 是典型的 Linux 工具官方支持 RedHat 系和 SUSE 系的企业版系统。个人开发机上我建议用 CentOS 7/8、Rocky Linux 或者 Ubuntu LTS 长期支持版64 位环境。解压安装包之后目录里通常有bin、lib、share、scripts这些子目录。最重要的是确认系统里装了几样基础库缺了的话工具启动会报共享库找不到常见的包括libX11、libXext、libncurses、libstdc、libXft这些。在新版系统上还需要确认 32 位兼容库是否装了因为部分工具组件可能还依赖 32 位版本的库。我建议先执行一次ldd $SPYGLASS_HOME/bin/spyglass看看动态库依赖是否满足。如果输出里出现 “not found”就用系统的包管理器把对应库装上。这一步很多人会跳过然后执行 spyglass 命令时得到error while loading shared libraries开始怀疑安装包有问题其实只是缺了库。2.2 环境变量配置的正确姿势安装完成后需要设置三个环境变量我通常会把它们写进用户的.bashrc或者团队公共的setup.sh脚本里export SPYGLASS_HOME/eda/synopsys/spyglass export PATH$SPYGLASS_HOME/bin:$PATH export SNPSLMD_LICENSE_FILE27000license-server这里有个细节值得多说一句Synopsys 家族的 license 变量名比较多老工具认LM_LICENSE_FILE新一些的流程认SNPSLMD_LICENSE_FILE。SpyGlass 的启动脚本通常会同时兼容这两个变量但如果你服务器上同时装了多个 EDA 厂商的工具LM_LICENSE_FILE可能被其他工具的 license 配置占用导致互相干扰。我的建议是 SpyGlass 单独用SNPSLMD_LICENSE_FILE指向 license server避免和别的工具抢同一个变量。反向排查问题的时候这两个变量混用是头号嫌疑。设置完环境变量后用两个命令验证。第一个是spyglass -version能正确打印版本号就说明二进制和环境没问题。第二个是spyglass -gui能弹出图形界面就说明 license 可用。我见过一种情况-version能跑但启动 GUI 或者跑 batch 任务时报 license 错误这是因为部分功能按 feature 授权license 文件里没有包含对应 feature。这种问题只能找流程管理员确认 feature 是否购买齐全自己折腾没有意义。2.3 高发 license 问题排查表故障现象常见原因处理建议启动报 license 超时license server 端口不通或防火墙拦截telnet 测试连通性确认 27000 端口是否放开报 hostname 不匹配license 文件绑定了 hostid服务器主机名改了确认启动 license 时用的主机名和.bashrc设置一致部分功能不可用license feature 不全或过期用lmstat -a查看 feature 状态多个工具互相踢 licenseLM_LICENSE_FILE指向了多个 server单独用SNPSLMD_LICENSE_FILE不要混用我自己踩过最深的一个坑是license server 的 hostname 写在 license 文件里是旧的后来服务器改了主机名结果 license 服务能启动但是客户端连不上提示 “Invalid host”。排查了半天才发现是主机名不匹配把 license 问件里的 hostname 同步修改后就好了。装完第一步就确认主机名能帮你省掉后面一大半的 license 烦恼。3. 跑通第一个 lint 工程从命令行到工程文件安装没问题之后下一步就是建工程跑 lint。SpyGlass 的工程结构说简单也简单说复杂也复杂。简单是因为核心就一个.prj文件复杂是因为这个文件里可以写各种配置指令功能极强。我建议刚开始不要碰 GUI直接用命令行和文本编辑器维护 prj 文件这样可追溯、可版本管理也方便后续接回归脚本。3.1 工程文件的最低可用配置一个最简的 prj 文件需要告诉工具四件事读哪些设计文件、顶层模块是谁、要跑什么检查目标、约束文件在哪。下面这个配置我一直在用核心内容基本没变过# 工程基本配置 set DESIGN_TOP top_module # 读取设计文件支持直接列文件或指定 filelist design_reading_verilog -filelist ./scripts/filelist.f # 指定检查目标 use_configure_analysis -goal lint_rtl # 加载时钟约束文件如需要 current_design top_module constraint_file -file ./constraints/top.sgdc需要注意两点。第一这里的语法在不同版本里略有差异老版本可能写作design_reader或者别的命令名具体以对应版本的 Command Reference 为准。第二use_configure_analysis -goal lint_rtl指定的 goal 决定了跑哪套规则。lint 相关的常用 goal 有lint_rtl、lint_turbo、lint_rtl_script等lint_rtl是覆盖面最均衡的默认选择。配置好 prj 之后跑 lint 就一条命令spyglass -project top.prj -go lint_rtl -batch加上-batch表示无界面模式适合服务器后台执行。执行结束后结果默认写在工程目录下的reports子目录里最直观的是一个网页格式的报告用浏览器打开就能按规则、按模块、按严重级别查看违规列表。3.2 常见 lint violation 与处理逻辑lint 报告里会出现大量规则编号不了解的工程师很容易被吓到。其实回归到本质lint 检查的核心就几类问题我整理了一张表违规类型典型规则示例风险描述处理建议位宽不匹配W528、W529赋值两侧位宽不一致可能截断或意外扩展统一位宽必要时显式 sign extension锁存器推断W525组合逻辑中 case 分支不全导致 latch补 default 或 else 分支组合逻辑环路W459信号经过组合电路回到自身必须消除或加时序切割多驱动W530同一信号被多个 always/assign 驱动检查代码结构只保留一个驱动源信号未定义/悬空W513信号未声明或读取未赋值补声明确认是否漏接处理 lint 结论的顺序有讲究。我的做法是先处理 ERROR 级别的再处理会导致功能异常的 WARNING最后再批量评审那些“建议类”的提示。千万不要一上来就追求“零 violation”很多 lint 规则是过度敏感的比如某些编码风格类规则如果设计团队有统一风格直接在规则配置里关掉或者 waive 掉反而更高效。3.3 一个真实的位宽不匹配例子说一个我印象很深的例子。之前有个模块代码里把一个 16 bit 的信号赋值给一个 8 bit 的信号SpyGlass 报了 W528。写代码的同事觉得功能上没问题因为“高位本来就是 0无所谓”。但看了波形之后发现这个信号在某种配置下高位是有值的结果赋值之后直接被截断导致输出异常。关键是这个问题在仿真里不一定能触发因为要凑齐特定的配置向量。SpyGlass 不需要向量它按结构就能把这种潜在风险全部找出来。所以我的态度是lint 报的位宽问题除非你能逐条解释为什么不会出问题否则老老实实改成匹配的位宽或者加显式截断逻辑别留隐患。4. CDC 检查的核心机制与实战配置lint 跑顺了之后重头戏来了CDC 检查。如果说 lint 是“卫生检查”那 CDC 就是“结构安全审查”。跨时钟域的问题一旦在流片后暴露轻则功能偶发异常重则整颗芯片报废而且这类问题在仿真阶段很难稳定复现因为它是亚稳态行为和温度、电压、时序余量都相关。4.1 跨时钟域问题的本质亚稳态先花一分钟说清楚底层机制。当一个信号在目标时钟域的建立保持时间内发生变化时目标寄存器会进入亚稳态输出既不是 0 也不是 1而是一个不确定的中间电平并且这个不确定状态需要一段时间才能收敛。更麻烦的是这个不确定状态还可能沿着组合逻辑传播出去影响多个下游寄存器。避免亚稳态影响的标准做法是让信号先经过两级同步寄存器给亚稳态一个收敛的时间窗口。这个结构在 RTL 里看起来很简单就是两个串联的触发器但在复杂设计里上百个跨时钟域信号里漏掉任何一个同步器都是致命风险。人眼检查不现实必须靠工具做全量结构扫描。4.2 SpyGlass CDC 检查的层次划分SpyGlass 的 CDC 检查不是一刀切它分成几个层次分别对应不同深度的问题CDC 结构检查Structural CDC识别所有跨时钟域路径检查每一条路径上是否有同步器。这个层次主要发现“漏同步”的问题。CDC 协议检查Protocol CDC验证同步逻辑是否按规范协议工作比如握手信号的四相时序、异步 FIFO 的指针同步、格雷码路径是否正确。CDC DRC 检查一系列设计规则检查比如异步复位释放是否同步、是否存在不安全的多位同步结构、时钟门控是否影响同步器等。跑 CDC 检查的 goal 通常配置为cdc_verify它会把结构检查和协议检查都跑一遍spyglass -project top.prj -go cdc_verify -batch跑完之后报告会列出每一条跨时钟域路径并给每条路径标注 source clock、dest clock、起始信号、终点信号以及经过的路径。你要做的不是看“有没有 violation”而是逐条判断“这个 violation 是真问题还是我没把约束说清楚”。4.3 三个最容易被误报的场景这里我很想说一下误报因为在真实项目里误报比例相当高处理不好会把人折磨疯。第一种标准同步器未被识别。SpyGlass 识别同步器是按结构模板匹配的比如两级触发器串联、中间没有组合逻辑、同一个时钟驱动。如果你的代码风格是“第一级触发器和第二级触发器之间穿了一些其他的逻辑”或者两级触发器不在同一个 always 块里但功能上是同步器工具可能认不出来就会报 CDC violation。处理方式有两个改写代码让结构更标准或者在 sgdc 里显式声明这个信号是同步器。第二种异步 FIFO 的指针路径。异步 FIFO 的读写指针跨时钟域传输时用格雷码编码是基本要求SpyGlass 会检查格雷码编码是否正确、指针比较逻辑是否在正确的时钟域。如果你的 FIFO 实现不是标准模板或者格雷码转换逻辑有问题会报 violation。这类问题不要轻易 waive因为 FIFO 跨时钟域边界出问题的概率真不低。第三种异步复位释放。很多设计的复位信号是异步断言、同步释放的SpyGlass 需要识别到复位同步释放结构否则会报 CDC violation。这个问题经常出现在复位信号由片外直接接入、内部没有做同步释放处理的场景。修复方案是在模块入口加复位同步器同时用 sgdc 声明复位信号。4.4 CDC 检查的完整配置流程我建议的 CDC 工程配置流程是这样第一步在 sgdc 里把所有的时钟信号定义完整、准确第二步声明所有异步信号如异步复位、异步加载信号第三步声明所有你明确知道不需要同步的跨时钟域路径比如只用于测试的旁路信号第四步运行 cdc_verify得到第一版报告第五步根据报告逐条分析把误报的原因归类能通过约束解决的改 sgdc能通过改代码解决的改 RTL第六步重新跑直到剩下的 violation 全部有合理解释或已 waive。这套流程看起来细碎但每一条都是在减少虚假信号、逼近真问题。我自己第一次跑 CDC 的时候报告里 5000 多条 violation其中 95% 以上都是因为时钟定义不全导致工具把同源时钟当成异步时钟在处理。把时钟树理清楚后violation 数量直接掉到 200 条以内。所以如果你的 CDC 报告违例数量异常巨大第一步不是怀疑设计而是先检查 sgdc 的时钟约束。5. .sgdc 约束文件CDC 检查的“说明书”sgdc 是 SpyGlass 的约束文件全称是 SpyGlass Design Constraints。它起什么作用一句话把设计人员脑子里知道的时钟关系、复位关系、异步路径关系用工具能读懂的语法写出来。CDC 检查的质量几乎完全取决于 sgdc 的质量。sgdc 写得好报告精准sgdc 写得烂报告不是洪水就是空白。5.1 sgdc 里到底要写什么一个实用的 sgdc 文件至少包含四类内容我分别说明。时钟定义。告诉工具设计中所有时钟的名字、周期、边沿和时钟源。SpyGlass 会基于这些定义划分时钟域判断哪些路径是跨时钟域的clock -name clk_a -period 10 -edge {0 5} clock -name clk_b -period 8 -edge {0 4} clock -name clk_div2 -period 20 -edge {0 10} -derive复位定义。声明异步复位/置位信号并标明有效电平reset -name rst_n -value 0 reset -name rst_async -value 0 -async跨时钟域路径声明。对于你确认不需要同步的路径显式声明为异步或者 false path避免工具误报false_path -from clk_a -to clk_b cdc_async -from {signal_group_a} -to {signal_group_b}同步器结构声明。对于工具没能自动识别出来的同步器手动指定sync -name sync_u1 -clock clk_b -cells {reg_0_reg reg_1_reg}5.2 约束遗漏导致的典型假 violation我总结过几类最典型的假 violation几乎每类都能在 sgdc 上找到原因。同源时钟被当成异步时钟。两个时钟都由同一个 PLL 分频产生但 sgdc 里没有声明它们的相位关系或父时钟工具按未知关系处理报出大量跨时钟域违例。解法是在时钟定义里加上-derive或者明确-phase关系。组合逻辑跨时钟域。数据从时钟域 A 进入时钟域 B 时中间经过了一级组合逻辑但组合逻辑的延时被忽略工具按“无同步器”处理并报错。这种情况下如果组合逻辑的输入确实是同步过的信号可以在 sgdc 里声明数据路径。测试模式信号。扫描链上的测试控制信号跨时钟域是正常的测试模式下不需要同步器但功能模式下这些路径不生效。如果 sgdc 里没有对测试信号做 exception 声明工具就会报一堆无关紧要的 violation。5.3 sgdc 调试的实用技巧调 sgdc 的过程有点像在跟工具“解释设计”的过程。我常用的调试方法打开 CDC 报告看一条 violation 中标注的 source clock 和 dest clock然后回 sgdc 里查这两个时钟的定义是否正确。如果时钟定义没问题再查这条路径上有没有设置 false path 或 async 声明。80% 的误报都能通过这两步找到原因。还有一个值得养成的习惯sgdc 文件要像 RTL 一样做版本管理和评审。因为它直接控制了检查的严格程度如果约束写得过松会掩盖真实 bug写得过紧又会浪费团队大量时间在假问题上。我见过有些项目为了快速“清零”报告在 sgdc 里加了一堆宽松的 false path结果下游模块出现的真实 CDC bug 被掩盖了等到流片回来才在样片测试中发现。这种错误比漏一个 lint warning 严重得多。6. 报告解读与 waiver 管理别让过量 violation 淹没真问题跑完 SpyGlass 之后你面对的往往是一份几百上千条 violation 的报告。怎么从里面把真问题捞出来这是区分“会用工具”和“用好工具”的分水岭。6.1 报告的结构与阅读顺序SpyGlass 生成的报告主要有两种形式一种是网页格式按照规则分类、模块层次分类展示另一种是纯文本日志适合脚本解析。我建议第一次接触的人先用网页报告因为它能按 hierarchy 逐层下钻交互性更强。打开报告后我的阅读顺序是先看 summary 页掌握总 violation 数量和按 category 的分布心里有个大致比例。直接跳到 CDC 相关的 category优先分析跨时钟域问题。再回到 lint 类别按 ERROR、WARNING、INFO 的优先级逐个看。对每一条 violation点开详细信息查看 source clock、dest clock、起点信号、终点信号、路径详情。不要在报告里毫无目的地滚动。每一条 violation 都值得你回答一个问题这条路径存在设计上是否真的有问题如果回答不了就先保留不要急着 waive。6.2 真违例和假违例的甄别方法甄别真伪是我最想分享的经验。对每一条 violation我通常按下面的顺序做判断第一步确认时钟关系。报这条违例的两个时钟真实关系是什么同源分频、异步关系、还是可配置的时钟切换如果工具理解的时钟关系和实际情况不符大概率是约束问题先修 sgdc。第二步确认同步结构。路径中间有没有同步器同步器的结构是不是标准的“两级触发器”结构如果结构不标准也许是工具没识别而不是设计没有同步。第三步追问数据宽度与协议。单 bit 信号跨时钟域用两级同步器就够了多 bit 信号跨时钟域必须用格雷码、握手或 FIFO。你的设计用的是什么方案如果这个方案本身是对的只是工具没识别协议那就在 sgdc 里声明。第四步检查是否为测试路径或配置路径。跨时钟域信号是不是只在测试模式下有效是不是上电配置阶段才变化的信号如果是这类路径通常可以例外处理。走完这四步一条 violation 到底是真问题还是假报告基本能有个判断。我在项目里常对同事说不要相信报告也不要不信报告报告只是告诉你“这里有条路径需要你解释清楚”。6.3 waiver 管理的规范化先说明我不反对 waive。每个真实项目都有大量经过确认的例外场景不可能全部改成“标准同步器”那样既不经济也不必要。我反对的是“为了清报告而 waive”。这两种做法的区别在于你有没有为这条 waiver 写下理由。推荐的做法是所有的 waiver 都必须带注释注明三个信息谁 waive 的、为什么 waive、在哪个版本下 waive 的。比如在 sgdc 或者工程配置里可以这样写# waiver for sync issue, reviewed by Alice, confirmed with DV, 2024-06 # reason: this signal is static during functional mode, only toggles in DFT mode waive -rule cdc_structural -design -from {dft_mode_signal} -comment DFT mode only这个习惯真正的价值在于三个月后这条 waiver 被翻出来审查时后面的人能通过注释理解当初的判断依据。如果当时只是随手点了个 waive没有留下理由后面所有人都会怀疑这条路径的安全性要么反复分析浪费时间要么永远带着一个隐患。6.4 我的 waiver 底线给一条 violation 加 waiver 之前我会先问自己三个问题这条路径我彻底看明白了吗如果我不 waive真的会对项目进度造成不可接受的负担吗如果流片回来在样片测试中发现问题这条 waiver 能帮我自证清白吗三个问题只要有一个回答不了我就不 waive继续查。这套底线帮我挡掉过至少两回潜在的设计缺陷其中一个是在样片测试阶段就被验证确实出了问题但因为当初没有 waive改起来还来得及。7. 把 SpyGlass 接进日常流程脚本化与回归工具用得再熟如果只在项目启动和收尾阶段各跑一次价值依然有限。真正让 SpyGlass 发挥威力的是把它接进日常开发流程让每一次代码提交都自动触发检查让 violation 数量像 bug 数量一样被追踪收敛。7.1 用 shell 脚本批量跑模块级检查项目到中后期模块数量和代码量都会迅速膨胀手工维护 prj 不现实。我通常的做法是为每个模块建一个独立的 prj 文件统一放在spyglass_prj/目录下然后用一个脚本批量跑#!/bin/bash # run_spyglass.sh 批量执行 lint/cdc for prj in ./spyglass_prj/*.prj; do module$(basename $prj .prj) echo Running SpyGlass on $module spyglass -project $prj -go lint_rtl -batch \ -without_gui 21 | tee ./reports/${module}_lint.log spyglass -project $prj -go cdc_verify -batch \ -without_gui 21 | tee ./reports/${module}_cdc.log done这样一次跑完所有模块日志统一收集后续做解析比对就有了数据源。7.2 在回归流程中比较 violation 次数比“跑一遍”更重要的是“比较变化”。我自己维护了一个简单的文本脚本从日志里提取每类规则的最高严重级别和总数然后和上一次运行结果做 diff。如果新提交的代码引入了新的 ERROR 或者关键 WARNING脚本就直接标红阻塞合入。如果某个模块的 violation 数量明显下降说明重构有效果也可以写进周报里作为质量指标。提取 violation 数量的方式依赖日志格式不同版本格式略有差异但通常可以按规则编号出现次数统计。# 统计 lint 日志中 W528 出现的次数 grep -c W528 ./reports/top_lint.log这种方式比较粗暴但胜在简单稳定。如果你的流程管理平台支持解析网页报告那当然可以做得更精细但对我来说能快速反映“本次改动有没有引入新问题”就够了。7.3 我实际用的一套回归脚本思路这里分享一个我用的相对完整的回归思路不一定适合所有团队但逻辑可以参考每次代码提交后自动 checkout 到最新版本跑模块级 lint 和 CDC。将本次结果与基线版本结果对比生成 violation 增减清单。新增的 violation 自动分配给对应模块 owner要求在下一次提交前处理或给出 waiver 注释。每周汇总一次全部模块的 violation 收敛曲线作为质量周会的输入。这套流程跑起来之后团队对 SpyGlass 报告的反应从“又来了几百条”变成了“增量只有三条归属明确”。对前端质量来说这种“把问题在源头截住”的机制比等到集成阶段再集中清报告要有效得多。8. 我自己实际操作中的一点体会SpyGlass 用了这么多年我最深的体会是它不是一个“用来挑错的工具”而是一个“帮你把设计重新过一遍的工具”。你在写代码的时候脑子里可能会认为某些路径是安全的、某些约束是默认成立的但 RTL 不会把这些想法记录下来。SpyGlass 的价值在于追问每一个细节逼你把“我认为安全”变成“我确认安全并且写进了约束文件”。如果你刚开始接触这个工具我的建议是先别追求把报告清零而是花时间把时钟、复位、异步路径这些基础约束写全写对。约束写对之后报告里留下来的 violation 数量会下降一个量级剩下的每一条都值得认真对待。这个过程从一开始看起来繁琐但做下来之后你对设计的理解深度会明显不一样。最后分享一个小技巧跑 CDC 的时候我都习惯把cdc_verify的报告和 lint 报告一起打开对比着看。因为有些 CDC violation 的根源其实是 RTL 编码问题比如同步器结构写得不够标准改掉编码风格之后 CDC 报告自然干净了大半。先把代码写规矩再让工具替你兜底这才是 SpyGlass 正确的使用方法。