4. PLL频率覆盖建模:用SystemVerilog定义覆盖点

各位同学,咱们今天聊点实在的。PLL的频率覆盖建模,说白了就是回答一个问题:你的PLL能不能产生所有你需要的时钟频率?

我在做第一个PLL验证项目时,就吃过这个亏。当时只测了几个整数分频点,结果芯片回来发现小数分频模式下有个频点根本锁不住。嗯,从那以后,我对频率覆盖建模就格外上心。

4.1 为什么需要频率覆盖建模?

PLL的输出频率由参考时钟、分频比和反馈路径共同决定。你想想看,一个典型的PLL可能有几十甚至上百个可编程分频器。如果每个都测一遍,仿真时间根本受不了。

所以我们需要功能覆盖点来量化验证的完整性。说白了,就是告诉EDA工具:哪些频率我已经测过了,哪些还没测。

核心思路:用SystemVerilog的covergroup和coverpoint,把PLL的输出频率空间划分成有意义的区间和关键频点。

4.2 整数分频场景的覆盖建模

整数分频相对简单。PLL的输出频率是参考频率的整数倍。比如参考时钟100MHz,分频比N=8,输出就是800MHz。

我个人习惯把整数分频的覆盖点分成两类:

  • 边界频点:PLL能锁定的最低和最高频率
  • 典型频点:设计中实际会用到的频率

来看代码:

// 整数分频覆盖建模
covergroup int_div_freq_cg with function sample(int freq_mhz, int div_ratio);
  // 覆盖输出频率范围
  freq_range: coverpoint freq_mhz {
    bins low_range  = {[100:400]};    // 低频段
    bins mid_range  = {[401:800]};    // 中频段  
    bins high_range = {[801:1600]};   // 高频段
    bins max_freq   = {1600};         // 最大频率点
    bins min_freq   = {100};          // 最小频率点
  }
  
  // 覆盖分频比
  div_ratio_cp: coverpoint div_ratio {
    bins even_div  = {2,4,6,8,10,12,14,16};   // 偶数分频
    bins odd_div   = {3,5,7,9,11,13,15};      // 奇数分频
    bins min_div   = {2};                      // 最小分频比
    bins max_div   = {16};                     // 最大分频比
  }
  
  // 交叉覆盖:频率 × 分频比
  cross_freq_div: cross freq_range, div_ratio_cp;
endgroup

小技巧:我在项目中经常把分频比按奇偶分开。为什么?因为奇数分频的占空比调整电路更容易出问题。我曾经遇到过一个bug,偶数分频全过,奇数分频在某个频点就是锁不住。

4.3 小数分频场景的覆盖建模

小数分频就复杂多了。PLL通过Sigma-Delta调制器来产生小数分频比,输出频率会有抖动和杂散。

小数分频的覆盖点,我建议重点关注:

  • 分数部分的值:0.1, 0.25, 0.5, 0.75 这些关键分数
  • 整数部分+分数部分的组合:比如 8.5, 10.25, 12.75
  • 接近整数的分数:比如 8.01, 7.99,这些边界最容易出问题
// 小数分频覆盖建模
covergroup frac_div_freq_cg with function sample(
  real output_freq, 
  int int_part, 
  real frac_part
);
  // 覆盖输出频率
  freq_cp: coverpoint output_freq {
    bins low  = [100.0:500.0];    // 低频段
    bins mid  = [500.1:1000.0];   // 中频段
    bins high = [1000.1:1600.0];  // 高频段
  }
  
  // 覆盖分数部分
  frac_cp: coverpoint frac_part {
    bins quarter = {0.25, 0.50, 0.75};     // 四分之一系列
    bins tenth   = {0.1, 0.2, 0.3, 0.4, 
                    0.6, 0.7, 0.8, 0.9};   // 十分之一系列
    bins near_int = {0.01, 0.02, 0.98, 0.99}; // 接近整数
    bins zero     = {0.0};                   // 实际上是整数分频
  }
  
  // 覆盖整数部分
  int_cp: coverpoint int_part {
    bins small = {[2:5]};    // 小分频比
    bins med   = {[6:10]};   // 中分频比
    bins large = {[11:16]};  // 大分频比
  }
  
  // 交叉覆盖:分数 × 整数
  cross_frac_int: cross frac_cp, int_cp;
endgroup

注意:小数分频的覆盖点不能只覆盖分数值。我见过有人只覆盖了0.25, 0.5, 0.75,结果漏掉了0.125这个值,偏偏那个频点就是客户要求的。所以一定要结合设计规格来定义覆盖点。

4.4 频段划分与边界覆盖

PLL通常有多个VCO频段(band),每个频段覆盖一定的频率范围。频段切换时的频率覆盖尤其重要。

我建议这样划分频段:

频段 频率范围 (MHz) 典型应用 覆盖优先级
Band 0 100 - 400 低速接口 高
Band 1 400 - 800 DDR内存 高
Band 2 800 - 1200 高速SerDes 中
Band 3 1200 - 1600 射频前端 中
// 频段切换覆盖
covergroup band_switch_cg with function sample(int band_id, real freq);
  // 覆盖每个频段
  band_cp: coverpoint band_id {
    bins bands[] = {[0:3]};  // 4个频段
  }
  
  // 覆盖频段边界
  band_edge_cp: coverpoint freq {
    // 每个频段的上下边界
    bins band0_low  = {100.0};
    bins band0_high = {400.0};
    bins band1_low  = {400.0};
    bins band1_high = {800.0};
    bins band2_low  = {800.0};
    bins band2_high = {1200.0};
    bins band3_low  = {1200.0};
    bins band3_high = {1600.0};
  }
  
  // 交叉覆盖:频段 × 边界
  cross_band_edge: cross band_cp, band_edge_cp;
endgroup

4.5 实战中的覆盖策略

说了这么多理论,来点实际的。我在一个项目中总结了一套频率覆盖策略,分享给大家:

  1. 先覆盖边界:每个频段的最低和最高频率必须测到
  2. 再覆盖典型值:设计规格中列出的所有目标频率
  3. 然后覆盖随机值:用随机化产生中间频率,确保没有盲区
  4. 最后覆盖交叉点:不同分频比和不同频段的组合

我的经验:覆盖点不是越多越好。我曾经在一个项目里定义了200多个覆盖点,结果仿真跑了一周,覆盖率才到60%。后来精简到50个关键覆盖点,两天就达到了95%的覆盖率。所以,精准比全面更重要。

4.6 知识体系总览

下面这张图总结了PLL频率覆盖建模的核心逻辑:

PLL频率覆盖建模知识体系 PLL配置参数 整数分频场景 小数分频场景 边界频点 典型频点 分频比奇偶 分数部分值 整数+分数组合 接近整数边界 频段划分 频段边界 频段切换 交叉覆盖 随机覆盖 边界覆盖 SystemVerilog covergroup / coverpoint 实现 功能覆盖率报告

这张图展示了从PLL配置参数到最终覆盖率报告的完整链路。你可以看到,整数分频和小数分频是两条并行的路径,最终都汇聚到SystemVerilog的covergroup实现中。

4.7 避坑指南

最后,我把自己踩过的坑总结一下:

我曾经犯过的错误:

  • 只覆盖了整数分频,忽略了小数分频的分数部分
  • 覆盖点定义得太细,导致仿真时间爆炸
  • 没有覆盖频段边界,漏掉了频段切换时的频率跳变
  • 交叉覆盖定义得太多,覆盖率收集效率极低

记住一句话:覆盖点是为验证服务的,不是为覆盖而覆盖。每个覆盖点都要能回答一个问题:这个频点如果没测到,会有什么风险?

好了,这一章的内容就到这里。频率覆盖建模是PLL验证的基础,打好这个基础,后面的抖动分析、锁定时间验证才能站得住脚。


可可精品资料库