3、PLL配置激励编写:寄存器模型、配置序列、频率切换测试、分频比设置

好,咱们直接进入正题。PLL的配置,说白了就是通过寄存器告诉它:「嘿,给我输出这个频率!」。但实际写激励的时候,你会发现事情没那么简单。我刚开始做PLL验证那会儿,就吃过配置顺序不对的亏——PLL死活锁不住,折腾了两天才发现是复位释放的时机搞反了。

这一节,咱们就聊聊PLL配置激励到底该怎么写。我会把寄存器模型、配置序列、频率切换测试和分频比设置这几个核心点串起来讲。

3.1 寄存器模型:你的PLL遥控器

做PLL验证,第一件事就是建寄存器模型。你想想看,PLL通常有十几个寄存器:控制寄存器、分频寄存器、锁定检测寄存器……如果每次都用直接写地址的方式去操作,代码会变得又臭又长。

我个人习惯用UVM的寄存器模型(RAL)。为什么?因为它能让你用名字去访问寄存器,而不是记一堆十六进制地址。比如这样:

// 通过寄存器模型配置PLL
pll_regs.pll_ctrl_reg.write(status, 32'h0000_0001);  // 使能PLL
pll_regs.pll_div_reg.write(status, 32'h0000_000A);   // 设置分频比
pll_regs.pll_lock_reg.read(status, lock_value);       // 读取锁定状态

嗯,这里要注意:寄存器模型里的字段定义一定要和RTL保持一致。我在项目中遇到过,有人把分频比的位宽定义错了,结果仿真跑了一整天,频率全都不对。

核心要点:寄存器模型不是摆设,它是你控制PLL的遥控器。建好了,后面所有测试用例都能复用。

3.2 配置序列:先做什么,后做什么

PLL配置是有严格顺序的。你不能上来就给分频比,得先让PLL上电、复位释放、然后才能配置。我总结了一个标准配置序列:

  1. 上电与复位:先拉高PLL的电源,再释放复位信号。顺序错了,PLL可能进入未知状态。
  2. 配置分频比:设置输入分频、反馈分频、输出分频。这一步决定了最终输出频率。
  3. 使能PLL:让PLL开始工作,进入锁定过程。
  4. 等待锁定:轮询锁定状态寄存器,或者等锁定信号拉高。
  5. 使能输出:锁定之后,再打开输出时钟。

写成Sequence大概是这样:

class pll_config_seq extends uvm_sequence;
  // ...
  task body();
    // 1. 上电与复位
    pll_reset_seq::start(m_sequencer);
    
    // 2. 配置分频比
    pll_regs.pll_div_reg.write(status, div_value);
    
    // 3. 使能PLL
    pll_regs.pll_ctrl_reg.set_field("pll_en", 1);
    pll_regs.pll_ctrl_reg.write(status);
    
    // 4. 等待锁定
    repeat(1000) begin
      pll_regs.pll_lock_reg.read(status, lock_val);
      if(lock_val[0]) break;
      #100ns;
    end
    
    // 5. 使能输出
    pll_regs.pll_ctrl_reg.set_field("out_en", 1);
    pll_regs.pll_ctrl_reg.write(status);
  endtask
endclass

警告:千万不要在PLL未锁定时就使能输出。我曾经见过有人这么干,结果输出时钟频率一直在跳变,后级电路直接崩溃。

3.3 频率切换测试:动态调整的艺术

实际应用中,PLL经常需要在不同频率之间切换。比如芯片在低功耗模式下跑低频,高性能模式下跑高频。这时候,你的激励就要能动态改变分频比。

频率切换测试的核心是:先停输出,再改配置,等重新锁定,再开输出。我建议你这样写:

// 频率切换测试
task freq_switch_test(input int new_div);
  // 关闭输出
  pll_regs.pll_ctrl_reg.set_field("out_en", 0);
  pll_regs.pll_ctrl_reg.write(status);
  
  // 等待输出稳定(通常几个时钟周期)
  repeat(10) @(posedge clk_ref);
  
  // 修改分频比
  pll_regs.pll_div_reg.write(status, new_div);
  
  // 等待重新锁定
  wait(pll_lock_signal == 1);
  
  // 重新使能输出
  pll_regs.pll_ctrl_reg.set_field("out_en", 1);
  pll_regs.pll_ctrl_reg.write(status);
endtask

你可能会问:为什么要先关输出?因为PLL在重新锁定的过程中,输出频率是不确定的。如果后级电路对时钟边沿敏感,可能会采到错误数据。

3.4 分频比设置:算清楚再写

分频比设置是PLL配置中最容易出错的地方。PLL的输出频率公式是:

Fout = Fin × (M / N) / K

其中M是反馈分频比,N是输入分频比,K是输出分频比。举个例子:

参数 值 说明
Fin 50 MHz 输入参考时钟
N 2 输入分频
M 40 反馈分频
K 4 输出分频
Fout 250 MHz 最终输出

写激励的时候,我建议你做一个函数,自动计算分频比:

function automatic int calc_div_value(int fin_mhz, int fout_mhz);
  // 简化版:假设N=1, K=1
  return fout_mhz / fin_mhz;
endfunction

小技巧:分频比寄存器通常有范围限制。比如M不能超过255,N不能超过7。写激励前,先查清楚PLL的规格书,别超限。

3.5 知识体系总览

下面这张图,是我画的PLL配置激励的核心逻辑。你可以把它当作写代码时的路线图:

PLL配置激励核心逻辑 寄存器模型 (RAL) 配置序列 频率切换测试 分频比设置 (M/N/K) 核心原则:先复位 → 再配置 → 等锁定 → 后输出 ⚠ 常见坑:配置顺序错误、未等锁定就输出、分频比超限

嗯,这张图把咱们刚才讲的内容串起来了。你写激励的时候,就照着这个流程走,基本不会出大问题。

最后说一句:PLL配置激励写得好不好,直接决定了你的验证效率。我建议你花点时间把寄存器模型和配置序列封装好,后面所有测试用例都能复用。别像我刚开始那样,每个测试都重新写一遍配置代码——累死不说,还容易出错。