第17章:分频器电路设计

本章对应原书第 369~373 页,属于「第四篇 数字电路篇」。

本章在讲什么?(先看这个)

FPGA 板卡的晶振频率是固定的(比如领航者开发板是 50MHz),但工程里经常需要别的频率时钟——这时就要用分频器把时钟"降下来"。分频是 FPGA 设计中最常用的技巧之一:本质上就是用一个计数器数时钟脉冲,数够次数把输出翻转一次,输出频率自然就降低了。本章重点掌握偶数分频的原理与实现,理解奇数分频的思路,并动手设计一个 4 分频电路,最后还会在拓展中给出 7 分频(奇数分频)的参考代码。

核心概念拆解

分频器简介

分频器顾名思义就是对输入时钟进行分频,得到我们需要的时钟。实现分频一般有两种方法:

  1. 直接使用 PLL 进行分频:FPGA 或 ASIC 设计中都可以直接用 PLL 分频。但 PLL 受自身特性限制,比如输入 100MHz 时钟,很多 PLL 都实现不了 1MHz 的分频;
  2. 直接用代码实现分频:本章教大家用 Verilog 代码设计分频器电路。

按照分频比例(分频前频率与分频后频率的比值),分频器分为偶数分频器奇数分频器

偶数分频

偶数分频就是分频前后频率的比值是偶数。比如 50MHz 晶振时钟,二分频后是 50MHz/2 = 25MHz。

实现原理:假设为 N(偶数)分频,只需计数到 N/2-1,然后将时钟翻转、计数器清零,如此循环即可得到 N 分频时钟。

举个例子:晶振是 50MHz,想要 12.5MHz 的时钟,就是 50/12.5 = 4 的四分频设计——计数到 4/2-1 = 1 时,将时钟翻转、计数器清零,就能得到 12.5MHz。

图17.1.1 偶数分频器的波形

从波形看:cnt 在 0 和 1 之间循环,cnt 计到 1 时 out_clk 翻转一次——out_clk 每 2 个输入周期翻转一次、每 4 个输入周期完成一个完整周期,正是 4 分频。

奇数分频

奇数分频就是分频前后频率的比值是奇数,比如 50MHz 三分频后是 50MHz/3 ≈ 16.667MHz。

偶数分频一个简单计数器就够了,而三分频、五分频、七分频等奇数分频时,一个寄存器不够,需要更巧妙的办法。

奇数分频原理:同样假设为 N(奇数)分频,定义一个计数周期为 N 的 cnt:

  • out_clk1(上升沿处理):cnt=0 时在 sys_clk 的上升沿拉低;cnt 计数到 N/2 或 0 时,在上升沿翻转;
  • out_clk2(下降沿处理):cnt=0 时在 sys_clk 的下降沿拉低;cnt 计数到 N/2 或 0 时,在下降沿翻转;
  • 最后将 out_clk1 和 out_clk2 相与,就得到分频后的输出 out_clk。

这样只需要修改 N 的值和计数器位宽,就可以实现其他奇数分频。

图17.1.2 奇数分频器波形图

思路的精髓在于:单一时钟沿上做不出占空比 50% 的奇数分频,所以用上升沿和下降沿各做一路波形,再相与,把两路"半成品"拼成完整的奇数分频时钟。

实验任务

使用 Verilog 语言设计一个 4 分频的分频电路。

程序设计——模块设计

模块命名为 divider_4。输入两个端口:系统时钟和系统复位;输出为分频后的 1 位时钟端口。

图17.3.1 模块框图与端口

信号名 方向 端口说明
sys_clk 输入 系统时钟,50MHz
sys_rst_n 输入 系统复位按键,低电平有效
out_clk 输出 输出时钟信号

分频器内部模块框图

图17.3.2 偶数分频器内部框图

实现 4 分频需要一个能计数到 1(4/2-1)的分频计数器,每当计到 1 就清零计数器、同时对 out_clk 取反。

关于计数器位宽:4 分频计数值只到 1,1bit 就够,但为了模块的通用性,建议位宽设置得稍大一点,比如 2bit 或 3bit(位宽越大通用性越大,一般比所需位宽大 1~2 个 bit 即可)。

编写代码

module divider_4(
    input           sys_clk  , //系统时钟
    input           sys_rst_n, //系统复位

    output  reg     out_clk    //输出时钟
);

//*****************************************************
//**                  main code
//*****************************************************

//reg define
reg  [2:0]  cnt     ;          //3位计数器

//计数器模块
always@(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n)
        cnt <= 3'd0;
    else if(cnt == 3'd1)
        cnt <= 3'd0;
    else
        cnt <= cnt + 3'd1;
end

//cnt计数器计数到1时就对输出时钟out_clk取反一次
always@(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n)
        out_clk <= 1'b0;
    else if(cnt == 3'd1)
        out_clk <= ~out_clk;
end

endmodule

代码就是两个标准的时序 always 块:

  • 计数器:复位清零;计到 1(= 4/2-1)就清零,否则加 1——cnt 在 0、1 之间循环;
  • 翻转器:每当 cnt == 1,out_clk 取反一次。每个输出周期翻转 2 次,对应 2×2 = 4 个输入时钟周期,即 4 分频。

RTL 原理图

在 Flow Navigator → RTL ANALYSIS → Open Elaborated Design → Schematic 打开 RTL 原理图:

图17.3.4 RTL原理图

代码和电路的对应关系非常清晰:RTL_ADD 是加法器,实现计数器累加;RTL_MUX 是选择器,根据比较器的结果选择输出 0 或 cnt 累加结果;RTL_REG_ASYNC 是异步复位的寄存器;RTL_ROM 是存储电路,将 cnt 和 2'b01 对比并输出结果;RTL_INV 是非门,把 out_clk 取反后重新写回寄存器。

提醒:RTL 分析原理图只针对 RTL 层面;想看代码和 FPGA 内部资源的对应关系,需综合后在 SYNTHESIS → Open Synthesized Design → Schematic 查看。

仿真验证——编写 TB 文件

激励只需提供时钟和复位:

`timescale 1ns / 1ns

module tb_divider_4();

//reg define
reg          sys_clk     ;
reg          sys_rst_n   ;

//wire define
wire         out_clk     ;

//信号初始化
initial begin
    sys_clk = 1'b1;
    sys_rst_n <= 1'b0;
    #201
    sys_rst_n <= 1'b1;
end

//生成50MHz系统时钟
always #10 sys_clk <= ~sys_clk;

//例化4分频模块
divider_4    divider_4_inst(
    .sys_clk    (sys_clk    ),
    .sys_rst_n  (sys_rst_n  ),

    .out_clk    (out_clk   )
);

endmodule

仿真验证

打开 Modelsim 运行 10us:

图17.3.5 divider_4模块仿真波形

从波形可知,out_clk 在计数器计到 1 时翻转。在 out_clk 相邻两个上升沿处放置参考线并添加频率显示,可以看到频率为 12.5MHz——正是 50MHz 的 4 分频,与绘制的波形图一致。

本章总结

本章讲解了时序逻辑中最常用的分频电路,重点掌握了偶数分频的实现方法:"计数到 N/2-1 → 翻转输出 → 清零计数器"。

作为数字电路基础篇的最后一章,更希望大家学会一种学习思维:一步步根据需求分析问题,思考如何实现并解决,敢尝试、多尝试、多练习,学过的知识要能用上。对于实用的降频方法,除了手动分频外,后面的 IP 核章节还会讲解通过 PLL(Phase Locked Loop,锁相环)实现任意分频、倍频及相位移动,功能非常强大。

图片对照

本章图片已按讲解位置分散插入各小节,包括偶数/奇数分频波形图、模块框图、RTL 原理图和仿真波形。

初学者容易踩的坑

  • 计数终点算错:N 分频要计到 N/2-1(比如 4 分频计到 1),直接计到 N/2 会让输出周期多一个输入周期。
  • 计数器位宽抠太死:按需 1bit 就够,但通用性差;稍微留大 1~2bit 更利于以后修改分频系数。
  • 以为代码分频的时钟可以随便用:用逻辑分频出来的时钟(如本例的 out_clk)再做时序逻辑时,时钟偏斜和时序分析都会出问题;正式工程中用逻辑分频一般用于低速使能/采样,真正的高质量时钟应使用 PLL。
  • 奇数分频只用单沿做:奇数分频想得到 50% 占空比必须升沿、降沿两路相与,只用一个沿做出来的是占空比不为 50% 的时钟(某些场合够用,但要清楚区别)。
  • 忘加时序约束意识:分频输出频率低了,不代表对时序可以完全不管,输入时钟本身仍需要约束(下一章会专门讲)。

小结 & 下一步

本章掌握了偶数分频"计数-翻转-清零"的标准套路和奇数分频双沿相与的原理,并完成了 4 分频电路的设计与仿真验证。建议动手拓展:根据奇数分频器波形图,设计一个 7 分频的奇数分频器。参考思路:N=7,计数器计到 6 清零;out_clk1 在上升沿、cnt≤N/2-1(即 ≤2)时拉低否则拉高;out_clk2 在下降沿做同样处理;最后两者相与输出。核心代码框架:

module divider_7(
    input           sys_clk     ,           //系统时钟
    input           sys_rst_n   ,           //系统复位

    output          out_clk                 //输出时钟
);

parameter    N = 7;                          //分频系数
parameter    EDGE = N/2-1;                   //取沿数

reg   [2:0]  cnt     ;                       //3位计数器
reg          out_clk1;
reg          out_clk2;

//计数器模块
always@(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n)
        cnt <= 3'd0;
    else if(cnt == N - 1'b1)
        cnt <= 3'd0;
    else
        cnt <= cnt + 'd1;
end

//out_clk1在上升沿进行翻转
always@(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n)
        out_clk1 <= 1'b0;
    else if(cnt <= EDGE)
        out_clk1 <= 1'b0;
    else
        out_clk1 <= 1'b1;
end

//out_clk2在下降沿进行翻转
always@(negedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n)
        out_clk2 <= 1'b0;
    else if(cnt <= EDGE)
        out_clk2 <= 1'b0;
    else
        out_clk2 <= 1'b1;
end

assign out_clk = out_clk1 & out_clk2;

endmodule

动手仿一仿,验证输出是不是约 7.143MHz 的 7 分频时钟。