SystemVerilog跨时钟域处理:从亚稳态原理到异步FIFO实战

📅 2026/7/29 19:28:51
SystemVerilog跨时钟域处理:从亚稳态原理到异步FIFO实战
1. 项目概述跨时钟域处理的挑战与核心在数字芯片和FPGA设计中跨时钟域处理是一个绕不开的经典话题也是新手工程师最容易踩坑、资深工程师反复打磨的关键环节。我见过太多项目功能仿真一切正常一上板就出现间歇性、难以复现的数据错误追根溯源十有八九是跨时钟域处理不当埋下的“雷”。这个系列文章我们就深入聊聊如何用SystemVerilog来设计和验证跨时钟域电路把CDC这件事掰开揉碎了讲清楚。简单说CDC处理的就是数据从一个时钟域比如时钟A安全、正确地传递到另一个时钟域时钟B的问题。这里的“安全”和“正确”核心在于避免亚稳态的传播。亚稳态可不是逻辑错误它是物理现实——当触发器的建立时间或保持时间被违反时输出会在一个不确定的时间内处于一个非0非1的中间电平。如果这个不确定的状态被后续电路采样就会像病毒一样扩散导致系统行为完全不可预测。因此CDC设计的首要目标不是消除亚稳态这不可能而是隔离和同步它确保亚稳态被限制在局部不会污染整个系统。为什么这件事如此重要且棘手因为现代SoC和复杂FPGA设计动辄包含数十个甚至上百个时钟域。处理器核心、各种外设接口、内存控制器、通信模块都可能有自己独立的时钟源。这些时钟之间通常没有固定的相位或频率关系是真正的异步时钟。在这种环境下任何不经处理的直接信号传递都等同于“裸奔”埋下了随时可能引爆的定时炸弹。我们的工作就是用一套成熟、可靠的方法论和工程实践给这些异步信号“穿上盔甲”让它们能安全地穿越时钟域的边界。2. 跨时钟域处理的核心原理与设计思路要设计可靠的CDC电路必须先理解其背后的物理和时序原理。这不仅仅是写几行RTL代码更是一种对时序的深刻敬畏。2.1 亚稳态的数学模型与失效概率亚稳态的根源是违背了触发器的时序要求。当一个数据信号在时钟有效边沿附近发生变化处于亚稳态窗口内触发器的输出需要一段额外的“决断时间”才能稳定到正确的逻辑电平。这段超出常规传播延迟的时间就是亚稳态的恢复时间。失效概率可以用一个简化的公式来估算MTBF (e^(Tr/τ)) / (Fclk * Fdata * Tw)。这里MTBF是平均无故障时间Tr是留给亚稳态恢复的时间通常是一个同步器链中两个触发器之间的时间间隔τ是触发器的亚稳态时间常数工艺相关Fclk和Fdata分别是采样时钟频率和数据变化频率Tw是数据相对于时钟的亚稳态窗口宽度。这个公式告诉我们几个关键点第一降低时钟频率和数据变化频率能显著提高MTBF但这往往与性能需求相悖。第二增加恢复时间Tr是指数级地提升可靠性这就是为什么多级同步器如两级触发器同步如此有效——它实质上为第一级触发器的输出提供了整整一个时钟周期的恢复时间。第三工艺越先进τ越小触发器的亚稳态特性越好。但即便如此单靠一个触发器进行跨时钟域采样其MTBF可能只有几秒甚至更短对于需要连续运行数年的产品来说是绝对不可接受的。2.2 单比特信号同步两级触发器与边沿检测对于单比特控制信号如使能、复位、请求/应答的跨时钟域最经典、最可靠的方法是使用两级触发器同步器。这是CDC设计的基石。module sync_single_bit ( input logic clk_dst, // 目标时钟域时钟 input logic rst_n, // 异步复位低有效 input logic data_src, // 源时钟域信号 output logic data_sync // 同步到目标时钟域的信号 ); logic sync_reg1, sync_reg2; always_ff (posedge clk_dst or negedge rst_n) begin if (!rst_n) begin sync_reg1 1b0; sync_reg2 1b0; end else begin sync_reg1 data_src; // 第一级采样并进入亚稳态风险区 sync_reg2 sync_reg1; // 第二级对第一级输出进行再采样极大降低亚稳态传播概率 end end assign data_sync sync_reg2; endmodule注意这里的关键在于data_src对于clk_dst是异步的。第一级触发器sync_reg1的输出可能进入亚稳态。但经过一个clk_dst周期后sync_reg1的输出有极高概率已经稳定MTBF达到数千年甚至更长此时sync_reg2采样的就是一个稳定的值。data_sync相对于clk_dst已经是同步信号了。然而两级同步器引入了一个固定的、1到2个目标时钟周期的延迟。如果我们同步的是一个脉冲从0跳变到1再跳变回0这个脉冲在源时钟域可能只有一个周期宽经过同步后在目标时钟域可能被“拉长”也可能被“错过”完全无法预测。因此绝对不要直接用两级同步器去同步一个脉冲。正确的做法是先将脉冲在源时钟域转换为一个电平信号比如用SR触发器锁存同步这个电平信号到目标时钟域然后在目标时钟域用边沿检测电路还原出脉冲。// 源时钟域脉冲转电平 logic pulse_src; // 输入的脉冲 logic level_src; // 转换后的电平 always_ff (posedge clk_src) begin if (pulse_src) level_src 1b1; else if (ack_dst_sync) // 来自目标时钟域的确认信号也需要同步回来 level_src 1b0; end // 同步电平信号到目标时钟域使用sync_single_bit模块 logic level_dst_sync; // 目标时钟域电平检测边沿还原脉冲并生成确认 logic level_dst_sync_dly; logic pulse_dst; logic ack_dst; always_ff (posedge clk_dst) begin level_dst_sync_dly level_dst_sync; pulse_dst level_dst_sync !level_dst_sync_dly; // 检测上升沿还原脉冲 ack_dst pulse_dst; // 生成确认信号需要同步回源时钟域 end这就构成了一个简单的“握手”协议雏形。源端发出请求电平目标端接收并回复确认源端收到确认后撤销请求。这是处理单比特控制信号跨时钟域最稳健的方法之一。2.3 多比特数据总线同步握手与FIFO对于多比特数据总线如32位地址、64位数据的跨时钟域情况要复杂得多。绝对不能将总线的每一位单独用两级触发器同步。因为每一条数据路径的延迟略有不同亚稳态恢复的时间也随机导致同步后的总线在某个时刻可能是一个源时钟域从未出现过的、错误的组合值即“数据歪斜”问题。解决多比特数据同步主要有两种成熟方案握手协议和异步FIFO。握手协议的思想和上面单比特脉冲转换类似但更完整。它通常包含REQ请求、ACK应答、DATA数据和VALID数据有效等信号。源时钟域在数据稳定后拉高REQREQ被同步到目标时钟域后目标端采样DATA并拉高ACKACK再同步回源端源端看到ACK后可以更新数据并准备下一次传输。握手协议可靠但延迟大吞吐量低适合低速、间歇性的数据传输。异步FIFO则是高速、流式数据跨时钟域传输的“瑞士军刀”。其核心是一个双端口存储器通常是寄存器组或RAM写指针在写时钟域递增读指针在读时钟域递增。关键挑战在于写指针需要被读时钟域安全地读取以判断“空”读指针需要被写时钟域安全地读取以判断“满”。直接同步多比特的指针是危险的。业界标准解决方案是使用格雷码。格雷码的特点是相邻两个数值之间只有一位发生变化。将二进制指针转换为格雷码后再进行同步单比特同步就能完美避免多比特同时变化带来的数据歪斜问题。读时钟域同步写指针的格雷码与本地读指针的格雷码比较判断空状态写时钟域同理判断满状态。// 指针转换为格雷码的示例 function logic [ADDR_WIDTH:0] bin2gray (logic [ADDR_WIDTH:0] bin); return (bin 1) ^ bin; // 格雷码转换公式 G B ^ (B 1) endfunction // 同步格雷码指针的模块 module sync_ptr #(parameter WIDTH 4) ( input logic clk, input logic rst_n, input logic [WIDTH:0] ptr_async_gray, // 异步输入的格雷码指针 output logic [WIDTH:0] ptr_sync_gray // 同步后的格雷码指针 ); logic [WIDTH:0] sync_stage1, sync_stage2; always_ff (posedge clk or negedge rst_n) begin if (!rst_n) begin sync_stage1 0; sync_stage2 0; end else begin sync_stage1 ptr_async_gray; sync_stage2 sync_stage1; end end assign ptr_sync_gray sync_stage2; endmodule异步FIFO的设计细节很多包括指针位宽比地址多一位用于区分“满”和“空”当读写指针的格雷码完全相同时为空当写指针比读指针多绕一圈时为满以及空满标志的产生逻辑。这是CDC设计中一个非常重要的专题。3. 基于SystemVerilog的CDC验证方法与技巧设计做好了验证必须跟上。CDC的bug在常规功能仿真中极难暴露因为仿真器是理想的没有亚稳态。我们必须采用专门的CDC验证方法。3.1 静态CDC检查工具的使用与解读静态CDC检查是CDC验证的第一道也是最重要的防线。它通过形式化方法分析整个设计的网表识别出所有潜在的CDC路径并检查这些路径上是否应用了正确的同步结构。常用的工具有Synopsys的Spyglass CDC、Cadence的JasperGold、Mentor的0-In CDC等。使用这些工具我们通常会经历以下几个步骤设置时钟和复位明确告诉工具设计中每个时钟域的主时钟、生成时钟及其关系同步、异步。定义CDC约束对于一些特殊的路径比如复位同步器、静态配置信号上电后不变可以添加约束将其从CDC检查中排除或标记为已处理。运行检查与分析报告工具会生成详尽的报告将问题分为几类无同步器最严重的问题异步信号直接使用。同步器不足比如只用了单级触发器。多比特同步检测到多个信号从一个时钟域到另一个时钟域但没有被聚合到握手或FIFO中。重新汇聚多个同步后的信号在组合逻辑中重新汇聚可能导致因同步延迟不同而产生的逻辑错误。时钟门控CDC时钟门控信号本身的跨时钟域问题。实操心得不要盲目追求“零违规”。静态CDC检查报告往往会有很多“假违例”比如工具无法识别的伪路径、模拟的IO端口等。工程师的核心技能之一是正确解读和豁免这些违例。每一条豁免都必须有充分的理由并记录在案。一个干净的、经过合理解释的CDC报告是设计可靠性的重要证明。3.2 动态仿真中的CDC验证策略静态检查保证了结构正确但无法验证协议逻辑如握手、FIFO指针逻辑的功能正确性。这就需要动态仿真。1. 使用异步时钟激励在Testbench中为不同的时钟域产生真正异步的时钟使用不同的#delay或随机相位差而不是简单的频率倍数关系。这能模拟真实场景下时钟边沿的随机对准。initial begin clk_a 0; clk_b 0; forever begin #(5 $urandom_range(0, 20)/100.0); // 5ns周期加上一个小的随机抖动 clk_a ~clk_a; end end initial begin #1; // 让clk_b的相位完全随机于clk_a forever begin #(7 $urandom_range(0, 20)/100.0); // 7ns周期与clk_a频率不同且相位随机 clk_b ~clk_b; end end2. 注入亚稳态行为在仿真中我们可以通过“作弊”来模拟亚稳态以测试同步电路和后续逻辑的鲁棒性。一种方法是在RTL中插入一个“亚稳态注入”模块在特定的CDC路径上随机地将同步器第一级触发器的输出强制为X未知态观察这个X是否被第二级触发器过滤掉以及是否传播到更远的逻辑。// 一个简单的亚稳态注入模块仅用于仿真 module metastability_injector #(parameter WIDTH1) ( input logic clk, input logic inject_en, output logic [WIDTH-1:0] dout ); logic [WIDTH-1:0] data 0; always_ff (posedge clk) begin data data 1; // 简单的计数器 end always_comb begin if (inject_en $urandom_range(0, 1000) 2) begin // 0.2%的概率注入X dout x; end else begin dout data; end end endmodule3. 协议检查与断言使用SystemVerilog断言来验证CDC协议的正确性。这是最强大的动态验证手段之一。例如对于握手协议我们可以写出如下断言// 在目标时钟域检查当检测到同步后的req上升沿时data必须稳定不变直到ack发出。 property handshake_data_stable; logic [31:0] data_captured; (posedge clk_dst) disable iff (!rst_n) ($rose(req_sync), data_captured data_in) // 采样req上升沿时刻的数据 | ($stable(data_in) throughout (##[0:$] ack_out)); // 在ack_out拉高之前data_in必须保持稳定 endproperty assert_handshake_stable: assert property (handshake_data_stable) else $error(Data changed during handshake!);对于异步FIFO可以断言写满后不能再写读空后不能再读以及读取的数据顺序必须与写入的顺序一致通过对比一个参考队列。3.3 形式验证在CDC中的应用形式验证工具如JasperGold、VC Formal可以穷尽地探索所有可能的输入序列和状态对于验证CDC控制逻辑如FIFO的指针比较和空满标志生成逻辑的绝对正确性非常有效。我们可以将设计抽象为一个黑盒为其接口读写使能、数据、空满标志定义约束和断言然后让形式工具去证明在所有可能的情况下断言都成立。例如证明“当FIFO为空时读使能有效不会导致读指针越过写指针”或“FIFO永远不会在满的情况下被写入数据”。形式验证能发现那些在数十亿次仿真中才可能出现一次的极端角落案例是CDC验证的有力补充。4. 高级CDC技术与常见陷阱掌握了基础我们来看看一些更复杂的场景和工程师常犯的错误。4.1 复位信号的跨时钟域处理复位信号是最特殊、也最关键的全局信号。它通常是异步的来自外部引脚但需要被安全地同步到各个时钟域内部。错误的复位同步是系统启动失败或运行不稳定的主要原因之一。黄金法则异步断言同步释放。这意味着复位信号拉低有效可以是异步的但拉高撤销必须经过目标时钟域的同步。module async_reset_sync_release ( input logic clk, input logic rst_async_n, // 异步输入低有效复位 output logic rst_sync_n // 同步后的复位低有效 ); logic rst_s1; always_ff (posedge clk or negedge rst_async_n) begin if (!rst_async_n) begin // 异步断言只要外部复位有效立即生效 rst_s1 1b0; rst_sync_n 1b0; end else begin // 同步释放外部复位撤销后经过两级同步才撤销内部复位 rst_s1 1b1; rst_sync_n rst_s1; end end endmodule这个电路确保了内部rst_sync_n的撤销边沿是clk的同步事件避免了复位撤销时因亚稳态导致部分触发器退出复位、部分还未退出从而系统状态不一致的问题。绝对不要将异步复位直接连接到触发器的异步复位端然后指望它“自然同步”。4.2 门控时钟与动态频率切换的CDC考量在低功耗设计中门控时钟广泛应用。但门控使能信号本身可能来自另一个时钟域这就形成了一个新的CDC问题。解决方案是使用“锁存器与门”的标准低功耗时钟门控单元并且确保门控使能信号在目标时钟域内被同步和正确处理。通常门控使能信号需要是一个电平信号并在时钟有效边沿处被采样和保持防止在时钟高电平时关闭时钟导致毛刺。动态频率切换也面临类似问题。切换时钟源的选择信号必须被妥善同步到当前活动的时钟域并且在切换过程中可能需要一个短暂的全时钟门控期两个时钟都关闭以避免毛刺。4.3 “数据就绪”信号的同步问题这是一个经典的陷阱。假设一个场景数据在clk_a域生成同时产生一个data_valid_a脉冲。我们希望将数据和这个有效信号同步到clk_b域。新手可能会将data_valid_a单独同步数据总线也单独同步错误。如前所述这会导致数据歪斜。更隐蔽的错误是先同步数据再根据同步后的有效信号来采样数据。// 错误示例 logic [7:0] data_a; logic valid_a; logic [7:0] data_sync; logic valid_sync; sync_single_bit u_sync_valid (.clk_dst(clk_b), .data_src(valid_a), .data_sync(valid_sync)); always_ff (posedge clk_b) begin if (valid_sync) begin // 问题在这里valid_sync可能比数据晚到1-2个周期 data_sync data_a; // 此时采样的data_a可能已经不是valid_a有效时的数据了 end end正确做法是采用“前向”同步策略将valid_a信号作为数据的“载体”或“使能”。一种方法是使用握手协议。另一种更直接的方法是在clk_a域确保valid_a有效期间data_a保持稳定通常是一个时钟周期然后将data_a和valid_a作为一个整体用clk_b去采样。但valid_a本身对clk_b是异步的所以我们需要一个同步机制来安全地采样这对信号。这通常又回到了握手或异步FIFO的范畴。4.4 同步器链的级数选择与物理布局为什么通常是两级触发器而不是一级或三级一级的MTBF太低。三级或更多级可以进一步提高MTBF但收益递减且增加了延迟。对于绝大多数应用时钟频率在1GHz以下数据变化率适中两级同步器提供的MTBF已经远超产品寿命要求达到数千年因此是性价比最高的选择。在极端高频或超高可靠性要求如航空航天的场景下可能会使用三级同步。此外同步器链的物理布局至关重要。在芯片布局布线时必须将构成同步器的两级触发器紧密地放置在一起并且使用高驱动强度的触发器单元。目的是减少第一级触发器输出到第二级触发器输入之间的线延迟和噪声为亚稳态恢复争取更多时间。后端设计人员需要对此施加严格的布局约束。5. 实战一个完整的异步FIFO设计与验证实例让我们把理论付诸实践设计一个深度为8、数据宽度为32位的异步FIFO并为其搭建一个完整的SystemVerilog测试平台。5.1 异步FIFO的RTL设计要点module async_fifo #( parameter DATA_WIDTH 32, parameter ADDR_WIDTH 3 // 深度为 2**ADDR_WIDTH 8 )( // 写端口 input logic wr_clk, input logic wr_rst_n, input logic wr_en, input logic [DATA_WIDTH-1:0] wr_data, output logic full, // 读端口 input logic rd_clk, input logic rd_rst_n, input logic rd_en, output logic [DATA_WIDTH-1:0] rd_data, output logic empty ); // 1. 存储器 logic [DATA_WIDTH-1:0] mem [0:(1ADDR_WIDTH)-1]; // 2. 指针比地址多一位用于区分满和空 logic [ADDR_WIDTH:0] wr_ptr_bin, rd_ptr_bin; logic [ADDR_WIDTH:0] wr_ptr_gray, rd_ptr_gray; logic [ADDR_WIDTH:0] wr_ptr_gray_sync2rd, rd_ptr_gray_sync2wr; // 3. 二进制指针逻辑各自时钟域 always_ff (posedge wr_clk or negedge wr_rst_n) begin if (!wr_rst_n) begin wr_ptr_bin 0; end else if (wr_en !full) begin mem[wr_ptr_bin[ADDR_WIDTH-1:0]] wr_data; // 用二进制指针低位部分寻址 wr_ptr_bin wr_ptr_bin 1; end end // 读指针逻辑类似... always_ff (posedge rd_clk or negedge rd_rst_n) begin if (!rd_rst_n) begin rd_ptr_bin 0; rd_data 0; end else if (rd_en !empty) begin rd_data mem[rd_ptr_bin[ADDR_WIDTH-1:0]]; rd_ptr_bin rd_ptr_bin 1; end end // 4. 二进制转格雷码 assign wr_ptr_gray bin2gray(wr_ptr_bin); assign rd_ptr_gray bin2gray(rd_ptr_bin); // 5. 格雷码指针同步 sync_ptr #(.WIDTH(ADDR_WIDTH)) u_sync_wr2rd ( .clk(rd_clk), .rst_n(rd_rst_n), .ptr_async_gray(wr_ptr_gray), .ptr_sync_gray(wr_ptr_gray_sync2rd) ); sync_ptr #(.WIDTH(ADDR_WIDTH)) u_sync_rd2wr ( .clk(wr_clk), .rst_n(wr_rst_n), .ptr_async_gray(rd_ptr_gray), .ptr_sync_gray(rd_ptr_gray_sync2wr) ); // 6. 空满判断在各自时钟域使用同步后的格雷码指针 // 满写指针比读指针多绕一圈即除了最高位其余位相同且最高位相反 assign full (wr_ptr_gray {~rd_ptr_gray_sync2wr[ADDR_WIDTH], rd_ptr_gray_sync2wr[ADDR_WIDTH-1:0]}); // 空读写指针的格雷码完全相等 assign empty (rd_ptr_gray wr_ptr_gray_sync2rd); endmodule注意事项空满判断的逻辑是异步FIFO设计的精髓。注意比较的是格雷码。判断“满”时比较的是本地的写指针格雷码和同步过来的读指针格雷码。由于格雷码同步可能延迟这个“满”标志是保守的可能提前报满但这是安全的保证了不会溢出。同理“空”标志也是保守的可能提前报空保证了不会读空。5.2 构建SystemVerilog Testbench一个完整的测试平台需要覆盖以下场景正常写满、读空。同时读写FIFO半满状态。写快读慢导致写满。读快写慢导致读空。复位测试。在读写操作中随机插入空闲周期。使用异步时钟并加入随机相位偏移和轻微抖动。module tb_async_fifo; logic wr_clk, rd_clk, wr_rst_n, rd_rst_n; logic wr_en, rd_en, full, empty; logic [31:0] wr_data, rd_data; logic [31:0] ref_queue[$]; // 用于比对数据的参考队列 // 实例化DUT async_fifo #(.DATA_WIDTH(32), .ADDR_WIDTH(3)) dut (.*); // 时钟生成带随机相位 initial begin wr_clk 0; forever #(5 $urandom_range(-10, 10)/100.0) wr_clk ~wr_clk; // 5ns /- 0.1ns抖动 end initial begin rd_clk 0; #1; // 初始相位偏移 forever #(7 $urandom_range(-10, 10)/100.0) rd_clk ~rd_clk; // 7ns /- 0.1ns抖动 end // 复位 initial begin wr_rst_n 0; rd_rst_n 0; #100; wr_rst_n 1; rd_rst_n 1; end // 写数据任务 task automatic write_data(int num_trans); repeat (num_trans) begin (posedge wr_clk); wr_en 0; if (!full) begin wr_en 1; wr_data $urandom(); ref_queue.push_back(wr_data); // 将数据存入参考队列 $display([%0t] WR: data0x%h, ptr%0d, $time, wr_data, dut.wr_ptr_bin); end end wr_en 0; endtask // 读数据任务与断言 task automatic read_and_check(int num_trans); repeat (num_trans) begin (posedge rd_clk); rd_en 0; if (!empty) begin rd_en 1; (negedge rd_clk); // 在时钟后检查数据稳定 if (ref_queue.size() 0) begin $error([%0t] ERROR: Read from empty FIFO!, $time); end else begin logic [31:0] exp_data ref_queue.pop_front(); if (rd_data ! exp_data) begin $error([%0t] ERROR: Data mismatch! Got0x%h, Exp0x%h, $time, rd_data, exp_data); end else begin $display([%0t] RD: data0x%h, ptr%0d, $time, rd_data, dut.rd_ptr_bin); end end end end rd_en 0; endtask // 主测试序列 initial begin wait(wr_rst_n rd_rst_n); #100; fork begin // 写线程 write_data(20); // 尝试写20个数据 end begin // 读线程 #50; // 延迟一段时间开始读模拟写快读慢 read_and_check(20); // 尝试读20个数据 end join // 测试空满标志 #100; if (!empty) $error(FIFO should be empty after all reads!); // 可以继续添加更多并发测试... #100; $display(Test passed!); $finish; end // 监控空满标志 always (posedge wr_clk) if (wr_en full) $warning(Write attempted when FIFO is full!); always (posedge rd_clk) if (rd_en empty) $error(Read attempted when FIFO is empty!); endmodule这个测试平台通过一个ref_queue来预测FIFO中数据的正确顺序实现了数据的自动比对。并发执行的fork...join块模拟了真实的异步读写场景。对空满标志的监控可以及时发现协议违规。5.3 覆盖率收集与结果分析在仿真中我们需要收集功能覆盖率来确保测试的完备性。代码覆盖率确保所有RTL行、分支、条件都被执行到。特别是空满判断的逻辑分支。功能覆盖率定义覆盖组关注以下场景FIFO从空到满的所有状态转移。在空、半满、满状态下的读写操作组合。写指针和读指针的格雷码在环绕从最大值跳回0时的行为。复位在读写过程中发生的情况。covergroup fifo_cg (posedge wr_clk); fifo_state: coverpoint {empty, full} { bins empty_state {2b10}; bins full_state {2b01}; bins partial_state {2b00}; // 非法状态 2b11 应该永远不会发生 illegal_bins illegal {2b11}; } wr_operation: coverpoint wr_en !full; rd_operation: coverpoint rd_en !empty; // 注意需要在rd_clk域另建一个covergroup cross fifo_state, wr_operation, rd_operation; // 交叉覆盖 endgroup通过分析覆盖报告我们可以找到测试的盲点并补充定向测试用例最终达到高覆盖率从而对CDC设计的正确性建立信心。结合之前的静态CDC检查报告一个经过充分验证的CDC模块才能被放心地集成到更大的系统中去。