不用PLL!Xilinx FPGA上纯Verilog实现精准50%占空比分频(奇数/偶数通用方案)
不用PLLXilinx FPGA上纯Verilog实现精准50%占空比分频奇数/偶数通用方案在FPGA开发中时钟分频几乎是每个项目都会遇到的基础需求。无论是驱动一个低速的外设接口还是为特定算法模块提供工作时钟我们都需要从系统主时钟衍生出不同频率的时钟信号。一提到时钟管理很多工程师的第一反应就是打开IP Catalog调用那个功能强大的Clock Wizard配置一个PLL或者MMCM。这当然没错这些硬核时钟管理单元性能优异抖动低功能全面。但你想过没有在某些场景下这或许是一种“杀鸡用牛刀”的奢侈甚至是一种不必要的资源浪费。想象一下这些情况你手头的FPGA型号属于低成本的入门系列内部的时钟管理资源CMT本就稀少你的设计已经接近资源使用的极限每一个DSP、每一个BRAM都精打细算或者你仅仅需要一个非常简单的、对抖动要求不高的低频时钟比如从100MHz主时钟分出一个1Hz的LED闪烁信号。在这些资源受限、需求简单的场景下为了一个分频功能而占用一个宝贵的PLL/MMCM就显得不那么经济了。更关键的是纯逻辑实现的分频器其代码简洁、可移植性极强不依赖于特定器件的硬核从Xilinx 7系列到UltraScale甚至到其他厂商的FPGA都能无缝迁移。今天我们就深入探讨如何用纯Verilog代码在Xilinx FPGA上构建一个同时支持奇数与偶数分频、且能保证完美50%占空比的通用分频器。我们将绕过PLL直接与寄存器、计数器和组合逻辑打交道理解其背后的时序原理并重点解决在Vivado环境下如何为这样的设计施加正确的时序约束以及如何优化其资源占用和功耗。这不仅是完成一个功能更是一次对数字电路底层时钟域理解的深化。1. 理解核心挑战为何奇数分频难以实现50%占空比在开始编写代码之前我们必须先厘清一个根本问题为什么偶数分频实现50%占空比轻而易举而奇数分频却需要特殊技巧时钟信号的本质是一个周期性的方波。所谓“分频”即是将输入时钟的周期进行整数倍扩展。N分频意味着输出时钟的周期是输入时钟周期的N倍。偶数分频N为偶数N是一个偶数那么N/2必然是一个整数。要得到50%占空比我们只需要让输出时钟在高电平持续N/2个输入时钟周期在低电平也持续N/2个周期即可。这通过一个简单的计数器就能实现计数器从0计数到N-1在计数值为N/2 - 1时翻转输出时钟。整个过程完全在输入时钟的上升沿控制下完成逻辑清晰直接。奇数分频N为奇数挑战来了。N是奇数N/2不再是整数。你无法找到一个整数个输入时钟周期让高电平和低电平时间绝对相等。例如进行5分频。输出时钟周期是输入时钟的5倍。要占空比50%高电平需要持续2.5个输入时钟周期——这在同步数字电路中是无法直接实现的因为时钟边沿只能发生在整数倍的时钟周期处。那么常见的非50%占空比的奇数分频是如何做的呢它可能会让高电平持续3个周期低电平持续2个周期或反之。但这在很多对时钟对称性有要求的场景下如某些串行通信协议是不可接受的。解决方案的钥匙就在于利用输入时钟的双沿上升沿和下降沿。虽然FPGA内部设计通常推荐使用单一时钟沿以避免时序复杂性但在生成时钟的源头——这个分频模块内部我们可以谨慎地使用下降沿来创造一个新的时间刻度。通过组合两个分别在上升沿和下降沿触发的、具有一定相位差的时钟信号我们可以在宏观上“合成”出一个精确的50%占空比的时钟。这就是我们后续所有代码设计的理论基础。注意在FPGA内部除了时钟管理硬核PLL/MMCM产生的时钟外由普通逻辑LUT、寄存器产生的时钟信号被称为“衍生时钟”或“生成时钟”。对于这类时钟必须通过正确的时序约束告知工具其与源时钟的关系否则静态时序分析STA将无法进行可能导致建立/保持时间违规。2. 通用分频器的Verilog实现一个优雅的解决方案我们不满足于仅仅分别实现奇数和偶数分频。我们的目标是设计一个参数化、通用的模块通过一个参数N分频系数来控制所有行为并且无论N是奇是偶都输出50%占空比的时钟。下面是一个经过精心设计和测试的实现方案。timescale 1ns / 1ps module universal_clk_divider #( parameter integer DIV_COEFF 5 // 分频系数 N必须 2 )( input wire i_clk, // 输入系统时钟 input wire i_rst_n, // 低电平有效异步复位 output wire o_div_clk // 输出分频时钟 (50%占空比) ); // 计算计数器位宽足以计数到 DIV_COEFF-1 localparam integer CNT_WIDTH $clog2(DIV_COEFF); reg [CNT_WIDTH-1:0] cnt 0; // 用于奇数分频的两个中间时钟信号 reg clk_posedge 0; reg clk_negedge 0; // 主计数器逻辑在输入时钟上升沿计数0 到 DIV_COEFF-1 循环 always (posedge i_clk or negedge i_rst_n) begin if (!i_rst_n) begin cnt 0; end else begin if (cnt DIV_COEFF - 1) begin cnt 0; end else begin cnt cnt 1; end end end // 判断是否为偶数分频 wire is_even_div (DIV_COEFF % 2 0); generate if (is_even_div) begin : EVEN_DIVISION // --- 偶数分频路径 (仅使用上升沿) --- reg div_clk_reg 0; always (posedge i_clk or negedge i_rst_n) begin if (!i_rst_n) begin div_clk_reg 0; end else if (cnt (DIV_COEFF/2 - 1)) begin // 在计数值达到半周期-1时翻转 div_clk_reg ~div_clk_reg; end end assign o_div_clk div_clk_reg; end else begin : ODD_DIVISION // --- 奇数分频路径 (使用上升沿和下降沿) --- // 1. 上升沿触发部分在 (N-1)/2 - 1 时翻转 always (posedge i_clk or negedge i_rst_n) begin if (!i_rst_n) begin clk_posedge 0; end else if (cnt ((DIV_COEFF-1)/2 - 1)) begin clk_posedge ~clk_posedge; end end // 2. 下降沿触发部分在 N-1 时翻转 // 注意此处的 cnt 是上升沿更新的值在下降沿时其值已稳定。 always (negedge i_clk or negedge i_rst_n) begin if (!i_rst_n) begin clk_negedge 0; end else if (cnt (DIV_COEFF - 1)) begin clk_negedge ~clk_negedge; end end // 3. 合成最终时钟两个相位差90度的信号进行异或 // 这将产生一个完美的50%占空比时钟 assign o_div_clk clk_posedge ^ clk_negedge; end endgenerate endmodule代码深度解析参数化设计DIV_COEFF参数使得模块高度可复用。$clog2系统函数用于动态计算所需的计数器位宽避免资源浪费。generate块使用generate if根据DIV_COEFF的奇偶性选择不同的硬件结构。这是在综合阶段决定的最终生成的电路要么是偶数分频逻辑要么是奇数分频逻辑不会存在无用的硬件。偶数分频逻辑简洁明了。计数器在DIV_COEFF/2 - 1时翻转输出寄存器产生对称的方波。奇数分频逻辑这是精髓所在。clk_posedge在输入时钟上升沿检查计数器当cnt ((N-1)/2 - 1)时翻转。这个信号本身占空比不是50%。clk_negedge在输入时钟下降沿检查同一个计数器cnt当cnt (N-1)时翻转。由于cnt在上升沿更新在下降沿时其值已经稳定这个操作是安全的。这个信号与clk_posedge有固定的相位差。异或合成将clk_posedge和clk_negedge进行按位异或操作。你可以通过画波形图来验证两个占空比非50%、但相位错开半个输入时钟周期的信号异或后会神奇地得到一个周期为N倍输入周期、占空比为50%的完美时钟信号。为了更直观地理解奇数分频的波形合成过程我们以5分频N5为例分解其产生步骤输入时钟周期cnt值 (上升沿后)clk_posedge 翻转点cnt1?clk_negedge 翻转点cnt4?clk_posedge 波形clk_negedge 波形o_div_clk (异或结果)00否否00011是否10122否否10133否否10144否是1105 (0)0否否1106 (1)1是否011.....................通过上表可以清晰看到clk_posedge每2.5个输入周期翻转一次clk_negedge每5个输入周期翻转一次且与前者有相位差。它们的异或结果o_div_clk高电平持续了2.5个输入周期低电平也持续2.5个输入周期宏观上实现了5分频且50%占空比。3. Vivado中的关键步骤时序约束与时钟定义代码写好了直接扔进Vivado综合实现就能用了吗对于组合逻辑产生的信号或许可以但对于我们用作时钟的o_div_clk绝对不行。缺少正确的约束你的设计可能在硬件上工作不稳定或者根本无法通过时序分析。3.1 创建生成时钟约束我们需要告诉Vivado的时序引擎o_div_clk是由主时钟i_clk派生出来的两者之间存在确定的频率和相位关系。这通过create_generated_clock约束命令实现。在你的XDC约束文件如clk_constraints.xdc中首先定义主时钟然后定义生成时钟# 假设主时钟i_clk来自引脚频率为100MHz create_clock -period 10.000 -name sys_clk [get_ports i_clk] # 对分频器模块的输出网络创建生成时钟约束 # 假设模块例化名为 u_divider create_generated_clock -name clk_div \ -source [get_pins u_divider/i_clk] \ -divide_by 5 \ [get_pins u_divider/o_div_clk]约束命令详解-source [get_pins u_divider/i_clk]指定生成时钟的源头即分频器模块的输入时钟引脚。强烈建议使用get_pins定位到模块的输入引脚而不是get_ports或get_nets这能确保约束在模块内部逻辑发生变化时依然准确。-divide_by 5这里的分频系数5需要与你例化模块时传入的DIV_COEFF参数完全一致。工具会根据这个比值计算clk_div的周期本例中为50ns。[get_pins u_divider/o_div_clk]指定生成时钟的对象即分频器模块的输出引脚。提示对于奇数分频-divide_by参数直接写分频系数N即可如5。Vivado能够理解这个约束并基于源时钟进行正确的时序分析。你不需要手动计算奇数分频下复杂的边沿关系。3.2 约束的自动化与动态参数处理在实际项目中分频系数DIV_COEFF可能作为参数在上层模块中传递。为了约束文件能动态适应我们可以借助Tcl命令来获取参数值。一种更稳健的方法是在RTL代码中使用注释来引导约束或者编写一个Tcl脚本来自动生成约束。例如可以在模块实例化附近添加属性- 在Verilog中无法直接嵌入Tcl但可以在XDC中通过模式匹配来施加约束。对于参数化设计更通用的做法是# 在XDC中可以遍历所有匹配的实例来施加约束 set div_instances [get_cells -hierarchical -filter {REF_NAME universal_clk_divider}] foreach inst $div_instances { # 获取该实例的DIV_COEFF参数值这需要工具支持通常较复杂 # 更简单的方法如果分频比是固定的少数几种可以为每种情况单独写约束。 # 或者在代码中用一个localparam定义分频比并在XDC中引用它需要工具支持。 }对于大多数应用如果分频比在设计中是固定的直接在XDC中写明-divide_by N是最清晰可靠的方式。如果分频比需要动态变化例如通过寄存器配置那么create_generated_clock可能不再适用你需要将其作为普通的时钟使能信号来处理并使用set_clock_groups或set_false_path进行约束这属于更高级的时钟域交叉CDC设计范畴。4. 资源、时序与功耗的深度优化使用纯逻辑分频我们节省了宝贵的时钟管理硬核PLL/MMCM。但逻辑资源LUT和寄存器也是需要精打细算的。此外生成的时钟质量抖动、偏移和功耗也需要我们关注。4.1 资源占用分析与优化我们的通用分频器主要消耗以下资源寄存器用于cnt、clk_posedge、clk_negedge以及偶数分频的div_clk_reg。数量与计数器位宽$clog2(N)相关通常很少N255时也只需8个触发器。查找表LUT用于实现计数器的加1比较逻辑、奇偶判断以及最终的异或门。资源消耗也很低。布线资源生成时钟o_div_clk需要被布线到其他逻辑单元作为时钟输入。这是需要重点关注的地方。优化技巧全局时钟缓冲器BUFG的使用o_div_clk如果需要驱动多个分布在芯片各处的逻辑单元将其直接连接到普通逻辑线上会产生巨大的时钟偏移Skew。强烈建议将生成时钟通过全局时钟网络Global Clock Network进行驱动。在Xilinx FPGA中这通过实例化BUFG原语来实现。// 在顶层模块中 wire div_clk_int; // 分频器输出的内部时钟线 wire div_clk_buf; // 经过BUFG缓冲后的时钟 universal_clk_divider #(.DIV_COEFF(5)) u_divider (... , .o_div_clk(div_clk_int)); BUFG u_bufg (.I(div_clk_int), .O(div_clk_buf)); // 将 div_clk_buf 分配给需要该时钟的模块使用BUFG能显著降低时钟偏移提高时序性能。但请注意芯片上的BUFG数量也是有限的几十个需合理规划。避免过度分频如果需要极低频率的时钟如从100MHz分频到1Hz不要使用单级大系数分频器。这会导致计数器位数过多且生成时钟周期极长任何毛刺都可能被误认为是有效边沿。推荐使用两级分频第一级用较小的系数如1000分频到一个中等频率第二级再用计数器分频。或者更推荐的做法是使用时钟使能Clock Enable代替生成时钟。4.2 更优的方案时钟使能信号设计模式在许多情况下我们并不真的需要一个独立的“时钟”而只是需要让某些逻辑以更低的频率运行。这时使用时钟使能信号是FPGA设计中的最佳实践。原理所有逻辑仍然在原始高速主时钟i_clk的驱动下运行但增加一个使能信号clk_en。该使能信号每N个周期拉高一个周期。只有在clk_en有效时相关的寄存器才采样输入数据并更新状态。module clock_enable_generator #( parameter integer DIV_COEFF 5 )( input wire i_clk, input wire i_rst_n, output wire o_clk_en ); localparam integer CNT_WIDTH $clog2(DIV_COEFF); reg [CNT_WIDTH-1:0] cnt 0; always (posedge i_clk or negedge i_rst_n) begin if (!i_rst_n) begin cnt 0; end else begin if (cnt DIV_COEFF - 1) begin cnt 0; end else begin cnt cnt 1; end end end // 使能信号在计数器为0时拉高一个周期 assign o_clk_en (cnt 0); endmodule // 使用示例一个低速工作的模块 module low_speed_module ( input wire i_clk, input wire i_rst_n, input wire i_clk_en, // 来自 clock_enable_generator input wire [7:0] i_data, output reg [7:0] o_data ); always (posedge i_clk or negedge i_rst_n) begin if (!i_rst_n) begin o_data 8‘h00; end else if (i_clk_en) begin // 仅当时钟使能有效时才更新 o_data i_data; end end endmodule时钟使能模式的巨大优势消除时钟域交叉CDC问题整个设计仍然在单一的主时钟域内避免了复杂的多时钟域同步电路。简化时序约束无需create_generated_clock只需对主时钟进行约束。时序分析简单明了。节省时钟资源无需使用BUFG来布线低频时钟也避免了使用普通逻辑线带来的偏移问题。降低功耗全局时钟网络的功耗相对较高。使用时钟使能低频模块的触发器只在使能时切换动态功耗更低。提高可靠性完全避免了由逻辑产生的时钟可能存在的毛刺风险。因此在设计决策时请务必优先考虑“我真的需要一个新时钟吗还是一个时钟使能信号就够了”对于大多数低速控制、状态机、数据采样等场景时钟使能是远比生成时钟更优的选择。5. 实战测试与调试技巧理论再好也需要经过实践的检验。在Vivado中我们需要进行行为仿真、综合后仿真以及硬件调试。5.1 编写测试平台Testbench一个完善的测试平台应该测试奇数和偶数分频两种情况。timescale 1ns / 1ps module tb_universal_clk_divider; reg clk_100m; reg rst_n; wire div_clk_even; wire div_clk_odd; // 实例化偶数分频 (N8) universal_clk_divider #(.DIV_COEFF(8)) u_even_div ( .i_clk (clk_100m), .i_rst_n (rst_n), .o_div_clk(div_clk_even) ); // 实例化奇数分频 (N5) universal_clk_divider #(.DIV_COEFF(5)) u_odd_div ( .i_clk (clk_100m), .i_rst_n (rst_n), .o_div_clk(div_clk_odd) ); // 生成100MHz时钟 always #5 clk_100m ~clk_100m; // 周期10ns initial begin // 初始化 clk_100m 0; rst_n 0; #100; // 保持复位一段时间 // 释放复位 rst_n 1; #500; // 仿真运行一段时间 // 可以在这里添加自动检查逻辑验证分频比和占空比 // 例如使用$measure测量div_clk_odd的周期和脉宽 $stop; end endmodule在仿真波形中你需要重点观察复位释放后输出时钟是否从确定的初始状态开始。偶数分频如8分频的输出时钟周期是否为80ns高电平持续时间是否为40ns。奇数分频如5分频的输出时钟周期是否为50ns高电平持续时间是否为25ns。检查o_div_clk是否存在任何毛刺特别是奇数分频的异或操作输出端。5.2 硬件调试与ILA的使用将设计下载到FPGA开发板后最直接的调试工具是集成逻辑分析仪ILA。在Vivado中插入ILA IP核抓取i_clk、o_div_clk以及内部计数器cnt等信号。调试关注点时钟稳定性观察o_div_clk的波形是否干净有无毛刺。如果发现毛刺可能需要检查代码的时序逻辑是否严谨或者考虑对输出使用寄存器再打一拍增加输出寄存器来消除毛刺。实际频率使用ILA的测量功能或板载逻辑分析仪测量输出时钟的实际频率与理论值进行对比。资源报告查看Vivado综合和实现后的资源利用率报告确认我们的分频器确实只消耗了极少的LUT和寄存器。最后记得在项目初期就规划好时钟架构。明确哪些模块必须使用独立时钟哪些可以用时钟使能。将本文的纯逻辑分频器作为你工具箱中的一个备用选项在PLL资源紧张或需求简单时它能干净利落地解决问题。在最近的一个传感器数据采集项目中主控FPGA的6个PLL已被高速SerDes和内存控制器占满而板卡上还有三个不同速率比如1MHz 500kHz 125kHz的SPI接口需要驱动。这时用三个轻量级的纯逻辑分频器从同一个100MHz系统时钟产生这三个低频时钟就成为了最简洁高效的解决方案节省下来的PLL资源可以留作他用。

相关新闻

Whisper-large-v3多语种转录效果:斯瓦希里语/豪萨语/孟加拉语等非洲亚洲语种

Whisper-large-v3多语种转录效果:斯瓦希里语/豪萨语/孟加拉语等非洲亚洲语种

Whisper-large-v3多语种转录效果:斯瓦希里语/豪萨语/孟加拉语等非洲亚洲语种 想象一下,你有一段来自非洲偏远地区的斯瓦希里语访谈录音,或者一段南亚的孟加拉语教学视频。传统语音识别工具对这些“小众”语种往往束手无策,要么识…

2026/5/17 7:17:47 阅读更多 →
Mermaid Live Editor完全指南:零基础掌握文本图表绘制的7个实用技巧

Mermaid Live Editor完全指南:零基础掌握文本图表绘制的7个实用技巧

Mermaid Live Editor完全指南:零基础掌握文本图表绘制的7个实用技巧 【免费下载链接】mermaid-live-editor Edit, preview and share mermaid charts/diagrams. New implementation of the live editor. 项目地址: https://gitcode.com/GitHub_Trending/me/mermai…

2026/7/2 19:34:48 阅读更多 →
MedGemma-X数据标注:智能辅助标注工具开发实战

MedGemma-X数据标注:智能辅助标注工具开发实战

MedGemma-X数据标注:智能辅助标注工具开发实战 医学影像标注是AI医疗领域的基础工作,但传统人工标注耗时耗力且容易出错。基于MedGemma-X开发的智能辅助标注工具,能够将标注效率提升3-5倍,同时保证标注质量的一致性。 1. 医学影像…

2026/5/17 7:17:46 阅读更多 →

最新新闻

在GEO优化中,是否应当优先考虑内容的视觉呈现?

在GEO优化中,是否应当优先考虑内容的视觉呈现?

随着生成式AI日益成为信息获取的重要渠道,GEO(生成式引擎优化)正悄然重塑品牌的数字曝光逻辑。在这场以内容质量为核心的角逐中,一个核心矛盾浮出水面:精心雕琢的文字,是否真的需要依赖夺目的视觉元素来“开…

2026/7/3 11:37:50 阅读更多 →
深度学习模型:量化与蒸馏

深度学习模型:量化与蒸馏

模型量化与知识蒸馏是深度学习模型轻量化的两大核心技术,广泛应用于移动端、嵌入式等低资源部署场景。二者核心逻辑完全不同,常搭配使用实现“高精度、低体积、高速度”的落地效果。本文融合理论与实战,精简冗余内容,搭配可直接运…

2026/7/3 11:37:50 阅读更多 →
Si4731与PIC18F4553构建数字收音机系统全解析

Si4731与PIC18F4553构建数字收音机系统全解析

1. Si4731与PIC18F4553的硬件搭档解析Si4731是Silicon Labs推出的一款高性能AM/FM/SW无线电接收芯片,采用数字低中频架构,支持从150kHz到30MHz的调幅广播和76MHz到108MHz的调频广播接收。其核心优势在于:集成完整的射频前端,仅需少…

2026/7/3 11:37:50 阅读更多 →
GTA5线上小助手终极指南:免费开源工具让你的洛圣都冒险更自由

GTA5线上小助手终极指南:免费开源工具让你的洛圣都冒险更自由

GTA5线上小助手终极指南:免费开源工具让你的洛圣都冒险更自由 【免费下载链接】GTA5OnlineTools GTA5线上小助手 项目地址: https://gitcode.com/gh_mirrors/gt/GTA5OnlineTools GTA5线上小助手是一款完全免费的开源游戏辅助工具,专为《侠盗猎车手…

2026/7/3 11:37:50 阅读更多 →
零担货总破损?一文搞懂 ISTA 3B测试包含哪些项目

零担货总破损?一文搞懂 ISTA 3B测试包含哪些项目

做工业设备、大件货物、托盘货的商家,经常遇到零担混运磕碰损坏问题,ISTA 3B 就是 LTL 零担运输专用包装全套检测标准,2017 版为现行通用版本,能完整复刻公路转运全部损伤工况,是工厂、外贸必备包装验证方案。一、哪些…

2026/7/3 11:31:48 阅读更多 →
STM32F1开发文档大全(数据手册/参考手册/标准库/HAL库 全套链接+用途详解)

STM32F1开发文档大全(数据手册/参考手册/标准库/HAL库 全套链接+用途详解)

很多新手学 STM32 最大的痛点:资料太多、不知道看哪个、分不清手册区别、找不到官方原版文档。 本文一次性整理 STM32F1 全套官方权威资料,包含:数据手册、参考手册、标准库、HAL库、固件包、例程、社区资源,附带每个文档的精准用…

2026/7/3 11:27:44 阅读更多 →

日新闻

Nginx防御TLS重协商攻击实战:从原理到配置与监控

Nginx防御TLS重协商攻击实战:从原理到配置与监控

1. 项目概述:为什么TLS重协商攻击至今仍需警惕十多年前的CVE-2011-1473,一个关于TLS/SSL协议重协商机制的漏洞,现在提起来还有必要吗?很多运维和开发朋友可能会觉得,这都老掉牙了,现代服务器和客户端不都默…

2026/7/3 0:03:59 阅读更多 →
华为防火墙双通道远程管理实战:Web与SSH配置详解

华为防火墙双通道远程管理实战:Web与SSH配置详解

1. 项目概述:为什么需要双通道远程管理防火墙?在任何一个稍具规模的企业网络里,防火墙都是那个默默守护在边界的关键角色。作为网络工程师,我们不可能每次都跑到机房,插上console线去配置它。远程管理能力,…

2026/7/3 0:03:59 阅读更多 →
AD74413R与PIC18F65K40的高精度工业数据采集方案

AD74413R与PIC18F65K40的高精度工业数据采集方案

1. 项目概述:AD74413R与PIC18F65K40的协同工作在工业自动化和精密测量领域,同时实现高精度模数转换(ADC)和数模转换(DAC)功能是许多复杂系统的核心需求。AD74413R作为一款四通道可配置模拟输入/输出器件,与PIC18F65K40微控制器的组合&#xf…

2026/7/3 0:05:59 阅读更多 →

周新闻

月新闻