4. 时钟域与复位设计:全局时钟网络、PLL/DLL配置、异步复位同步释放、多时钟域同步器设计

时钟和复位,说白了就是FPGA设计的“心跳”和“总开关”。

我见过太多新手,逻辑写得花里胡哨,结果一上板子就出问题。查到最后,十有八九是时钟域没处理好,或者复位信号出了岔子。今天咱们就把这块硬骨头啃下来。

4.1 全局时钟网络:为什么它这么重要?

FPGA内部有专门的全局时钟网络,比如Xilinx的BUFG、Altera的GCLK。这些网络不是普通的走线,它们是“高速公路”。

全局时钟网络的特点是:

  • 低抖动:时钟边沿的偏差极小
  • 低偏斜:到达每个触发器的时延几乎一致
  • 高扇出:可以驱动成千上万个触发器

我个人习惯,所有时钟信号必须走全局时钟网络。千万别图省事,把时钟当普通信号用逻辑门去驱动。我在项目中遇到过一位同事,为了省一个BUFG资源,直接用组合逻辑输出时钟。结果呢?时序收敛不了,整个项目延期两周。

警告:千万不要用组合逻辑输出时钟!比如 assign clk_gated = clk & enable; 这种写法,会产生毛刺,导致寄存器误触发。

4.2 PLL/DLL配置:如何得到你想要的时钟?

千兆以太网需要125MHz的时钟,但板子上通常只有50MHz或100MHz的晶振。这时候就需要PLL(锁相环)或DLL(延迟锁相环)来帮忙。

PLL的核心功能有三个:

  1. 倍频:把输入时钟频率翻倍
  2. 分频:把输入时钟频率降低
  3. 相位调整:让输出时钟相对于输入时钟偏移一定角度

举个例子,我用Xilinx的MMCM(混合模式时钟管理器)来生成125MHz时钟:

// 假设输入时钟为50MHz
// 需要输出125MHz,倍频系数为5,分频系数为2
// 计算公式:Fout = Fin * M / D
// 125 = 50 * 5 / 2

MMCME2_BASE #(
    .BANDWIDTH("OPTIMIZED"),
    .CLKOUT0_DIVIDE_F(2.0),   // 分频系数
    .CLKFBOUT_MULT_F(5.0),    // 倍频系数
    .DIVCLK_DIVIDE(1),        // 输入分频
    .CLKIN1_PERIOD(20.0)      // 输入周期,单位ns
) mmcm_inst (
    .CLKIN1(clk_50m),
    .CLKFBIN(clk_fb),
    .CLKOUT0(clk_125m),
    .CLKFBOUT(clk_fb),
    .LOCKED(pll_locked)
);

这里有个坑:PLL锁定需要时间。我建议在PLL锁定信号有效之前,让整个设计保持在复位状态。否则,时钟还没稳定,逻辑就开始跑了,结果不可预测。

小技巧:PLL的锁定时间通常在几微秒到几十微秒之间。你可以用计数器做个延时,等PLL锁定后再释放复位。

4.3 异步复位同步释放:一个老生常谈的问题

复位信号的处理,是FPGA设计中最容易出问题的地方之一。

为什么?因为复位信号通常是异步的,它可能在任何时刻到来。如果复位信号在时钟边沿附近变化,就会导致寄存器进入亚稳态。

我曾经在一个项目中,因为复位信号没处理好,导致设备在高温下频繁死机。查了整整三天,最后发现是复位信号抖动引起的。

标准的做法是“异步复位,同步释放”。说白了就是:

  • 复位信号可以随时生效(异步复位)
  • 但复位释放时,必须与时钟同步(同步释放)

代码实现很简单:

// 异步复位,同步释放
reg rst_sync1, rst_sync2;

always @(posedge clk or posedge rst_async) begin
    if (rst_async) begin
        rst_sync1 <= 1'b1;
        rst_sync2 <= 1'b1;
    end else begin
        rst_sync1 <= 1'b0;
        rst_sync2 <= rst_sync1;
    end
end

assign rst_sync = rst_sync2;

这个电路用了两级触发器做同步。第一级可能进入亚稳态,但第二级有整整一个时钟周期来稳定。这样,输出的复位信号就是干净的了。

核心要点:异步复位同步释放电路,至少需要两级触发器。如果时钟频率很高,或者对可靠性要求极高,可以用三级。

4.4 多时钟域同步器设计:跨时钟域的“交通规则”

千兆以太网MAC层,天然就有多个时钟域:

  • GMII/RGMII时钟域:125MHz,与PHY接口
  • 用户逻辑时钟域:可能是100MHz、150MHz或其他频率
  • 管理接口时钟域:MDC时钟,通常2.5MHz

数据在这些时钟域之间传递,必须做同步处理。否则,亚稳态会像病毒一样传播。

常用的同步器有三种:

同步器类型 适用场景 延迟
双级触发器同步器 单比特控制信号 2个时钟周期
异步FIFO 多比特数据总线 取决于FIFO深度
握手协议 低速控制信号 可变

对于单比特信号,比如“数据有效”标志,用双级触发器就够了:

// 从clk_a域到clk_b域的同步器
reg sync_ff1, sync_ff2;

always @(posedge clk_b) begin
    sync_ff1 <= signal_from_clk_a;
    sync_ff2 <= sync_ff1;
end

assign signal_to_clk_b = sync_ff2;

对于多比特数据,比如8位的数据总线,绝对不能直接用触发器同步。因为每个比特的走线延迟不同,同步后数据可能错位。

正确的做法是用异步FIFO。我习惯用Xilinx的FIFO Generator IP核,或者自己写一个双口RAM加读写指针。

注意:跨时钟域传递多比特数据时,不要用寄存器打拍的方式。每个比特独立同步,会导致数据不一致。必须用异步FIFO或格雷码。

4.5 知识体系总览

下面这张图,是我画的本章节知识体系。你可以把它当作一个“地图”,随时回来查阅。

时钟域与复位设计知识体系 时钟域与复位设计 全局时钟网络 PLL/DLL配置 异步复位同步释放 多时钟域同步器 低抖动 低偏斜 倍频/分频 相位调整 异步复位 同步释放 双级触发器同步器 异步FIFO 握手协议 核心原则:所有跨时钟域信号必须同步,所有复位信号必须干净

嗯,时钟域和复位设计,说白了就是“规矩”二字。规矩立好了,设计就稳了。我做了这么多年FPGA,最大的体会就是:前期花时间把时钟和复位规划好,后期能省掉80%的调试时间。

你想想看,如果时钟域没处理好,数据传错了,你查逻辑查半天也查不出来。但如果一开始就按规范来,用同步器、用异步FIFO,这些问题根本不会出现。

好了,这一章的内容就到这里。记住我一句话:时钟和复位,是FPGA设计的基石。基石不稳,楼盖得再高也是危楼。

专注资料整理