前言
在数字系统中,计数器不仅是实现定时、状态机的基础,还经常兼任时钟分频器的角色。本文分享一个结构紧凑的 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 endmodule2.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=1,o_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 闪烁控制、简单时序产生等场景中非常实用。
扩展建议
- 参数化模值:引入
parameter MAX = 99,并将判断条件改为ro_cout == MAX,便于修改计数上限。 - 分频级数可配置:通过参数控制抽取的比特位宽度,实现更多分频级数。
- 加入使能信号:添加计数使能端,在需要时才递增,实现可控分频。
- 消除非 2 次幂模值对分频占空比的影响:若要求严格 50% 占空比,可将计数器改为模 128 (2^7) 或直接使用独立的分频计数器。
希望这篇博客能帮助你快速理解 Verilog 中“计数器+分频”的复用设计。如果你有更巧妙的分频实现方式,欢迎在评论区留言交流!