FPGA实战(08):Verilog 设计:带多级分频输出的 0~99 循环计数器(tops 模块)
2026/6/13 12:08:00 网站建设 项目流程

前言

在数字系统中,计数器不仅是实现定时、状态机的基础,还经常兼任时钟分频器的角色。本文分享一个结构紧凑的 Verilog 模块——tops,它在0~99 循环计数的基础上,巧妙提取计数器的低 4 位,直接生成2/4/8/16 分频的同步输出。代码采用分区注释、异步复位、寄存器输出,适合 FPGA 初学者学习和工程复用。


1. 设计功能与创新点

功能点

  • 带符号的 0~99 循环计数器:每个时钟上升沿计数值 +1,计到 99 后自动归零,输出 10 位有符号数o_cout
  • 多级时钟分频输出:利用计数器低 4 位的周期性翻转,同步输出o_clk_2_div(2分频)、o_clk_4_div(4分频)、o_clk_8_div(8分频)、o_clk_16_div(16分频)。
  • 异步复位i_rst高电平有效,立即将所有寄存器清零。
  • 无毛刺寄存器输出:所有分频信号均经过寄存器打拍,输出稳定可靠。

创新点

  • “一计多用”的资源复用:不再单独例化分频器,而是直接抽取模 100 计数器的比特位,实现 2~16 分频。这种方法节省了寄存器和组合逻辑资源。
  • 符号位显式处理:在计数器自增时使用$signed将常量转为有符号数,消除混合符号类型的综合警告,提升代码可移植性(本版代码未使用$signed但保留了有符号声明,可根据需要添加)。
  • 预留式模块框架:顶层通过param / reg / wire / FSM / inst / combine_Logic / always等注释分区,为后续扩展状态机或子模块留下清晰接口,团队协作更高效。
  • 全覆盖条件写法always块用else if(1)保证条件树完整,综合工具会自动优化冗余逻辑,但代码阅读者可直观理解优先级。

2. 模块代码与详细解析

2.1 顶层模块tops

module tops( input i_clk , input i_rst , output signed [ 9: 0] o_cout , output o_clk_2_div , output o_clk_4_div , output o_clk_8_div , output o_clk_16_div ); //**************param*****************// //**************reg*******************// reg signed [ 9: 0] ro_cout ; reg ro_clk_2_div ; reg ro_clk_4_div ; reg ro_clk_8_div ; reg ro_clk_16_div ; //**************wire******************// //**************assign****************// assign o_cout = ro_cout ; assign o_clk_2_div = ro_clk_2_div ; assign o_clk_4_div = ro_clk_4_div ; assign o_clk_8_div = ro_clk_8_div ; assign o_clk_16_div = ro_clk_16_div ; //**************FSM*******************// //**************inst******************// //**************combine_Logic*********// //**************always****************// always @(posedge i_clk or posedge i_rst )begin if(i_rst) ro_cout <= ('d0) ; else if(ro_cout == 'd99) ro_cout <= ('d0) ; else if(1) ro_cout <= (ro_cout + 'd1) ; else ro_cout <= ro_cout ; end always @(posedge i_clk or posedge i_rst )begin if(i_rst) ro_clk_2_div <= ('d0) ; else ro_clk_2_div <= ro_cout[0] ; end always @(posedge i_clk or posedge i_rst )begin if(i_rst) ro_clk_4_div <= ('d0) ; else ro_clk_4_div <= ro_cout[1] ; end always @(posedge i_clk or posedge i_rst )begin if(i_rst) ro_clk_8_div <= ('d0) ; else ro_clk_8_div <= ro_cout[2] ; end always @(posedge i_clk or posedge i_rst )begin if(i_rst) ro_clk_16_div <= ('d0) ; else ro_clk_16_div <= ro_cout[3] ; end endmodule

2.2 代码解析

1. 端口与寄存器

  • o_cout:10 位有符号计数器值,直接由ro_cout驱动。
  • o_clk_2_div~o_clk_16_div:分频输出,由对应寄存器驱动,保证无组合逻辑毛刺。
  • 所有输出寄存器均支持异步复位(高电平有效)。

2. 计数器逻辑

  • 异步复位时ro_cout清零。
  • 达到上限 99 后下一周期归零,形成模 100 循环。
  • 其余情况每个时钟加 1,使用ro_cout + 'd1(若无符号乘除问题可省略$signed,但建议在混合类型时显式转换)。

3. 分频输出逻辑

  • ro_clk_2_div <= ro_cout[0]ro_cout[0]是计数器的最低位,在连续加 1 时每个时钟翻转一次,因此寄存器输出为时钟的2 分频信号(占空比 50%)。
  • ro_clk_4_div <= ro_cout[1]ro_cout[1]每 2 个时钟翻转一次,对应4 分频
  • ro_clk_8_div <= ro_cout[2]ro_cout[2]每 4 个时钟翻转一次,对应8 分频
  • ro_clk_16_div <= ro_cout[3]ro_cout[3]每 8 个时钟翻转一次,对应16 分频
  • 注意:由于计数器模 100 并非 2 的幂,高位的占空比可能不是严格的 50%,但频率关系依然正确。对于常规使能应用已足够,若需完美 50% 占空比,可改用模 2^N 的计数器。

4. 时序说明

  • 所有分频寄存器均在同一posedge i_clk采样ro_cout的比特位,因此输出相对于ro_cout有一级延迟,完全同步于系统时钟,可安全用于跨时钟域逻辑(经同步化后)。

3. 测试平台(Testbench)

为了验证计数器及分频功能,编写如下 testbench:

`timescale 1ns / 1ps module test_tops; reg i_clk; reg i_rst; wire signed[9:0] o_cout; wire o_clk2div; wire o_clk4div; wire o_clk8div; wire o_clk16div; tops tops_u( .i_clk (i_clk), .i_rst (i_rst), .o_cout (o_cout), .o_clk_2_div (o_clk2div), .o_clk_4_div (o_clk4div), .o_clk_8_div (o_clk8div), .o_clk_16_div (o_clk16div) ); initial begin i_clk = 1'b1; i_rst = 1'b1; #100 i_rst = 1'b0; end always #5 i_clk = ~i_clk; // 10ns 时钟周期 endmodule

激励说明

  • 时钟:周期 10 ns,占空比 50%。
  • 复位:初始高电平持续 100 ns,覆盖前 10 个时钟周期,确保可靠复位。
  • 观察信号:计数器值o_cout和四路分频信号。

4. 仿真结果与分析

在 ModelSim 或 Vivado 中运行仿真,可观察到以下波形(文字描述):

  • 0~100 ns(复位期)i_rst=1o_cout=0,所有分频输出均为 0。
  • 105 ns 起:复位释放后第一个时钟上升沿,计数器o_cout由 0 变为 1。
    • o_clk2div(对应ro_cout[0])在计数器 0→1 的下一周期变为高(即采集到ro_cout[0]的旧值 0,所以初始为低,然后翻转)。
    • 实际波形会显示o_clk2div每个时钟周期翻转一次,频率为 50 MHz(时钟 100 MHz 时)。
  • 计数循环o_cout从 0 递增到 99,然后归零,不断重复。
  • 分频验证
    • o_clk2div周期为 20 ns(2 倍时钟周期),上升/下降沿与计数器 LSB 同步。
    • o_clk4div周期为 40 ns,o_clk8div周期为 80 ns,o_clk16div周期为 160 ns。
    • 由于计数器的循环特性,即使经过 99→0 的突变,分频输出的连续性依然保持,无异常脉冲。

仿真截图建议(实际发布时可插入波形图片):

  • 展示复位释放瞬间的放大波形,查看分频与计数器的时序关系。
  • 展示计数值从 98→99→0 时,四个分频信号的电平变化。

5. 总结与扩展

总结

tops模块通过一个简洁的模 100 计数器,不仅输出带符号的计数值,还免费获得了 2/4/8/16 分频信号。这种“一计多用”的思路有效降低了逻辑资源消耗,同时寄存器输出消除了分频毛刺,在低速接口、LED 闪烁控制、简单时序产生等场景中非常实用。

扩展建议

  1. 参数化模值:引入parameter MAX = 99,并将判断条件改为ro_cout == MAX,便于修改计数上限。
  2. 分频级数可配置:通过参数控制抽取的比特位宽度,实现更多分频级数。
  3. 加入使能信号:添加计数使能端,在需要时才递增,实现可控分频。
  4. 消除非 2 次幂模值对分频占空比的影响:若要求严格 50% 占空比,可将计数器改为模 128 (2^7) 或直接使用独立的分频计数器。

希望这篇博客能帮助你快速理解 Verilog 中“计数器+分频”的复用设计。如果你有更巧妙的分频实现方式,欢迎在评论区留言交流!

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询