Vivado使用技巧(19):多周期路径

本文深入解析Vivado时序分析中的多周期路径概念,包括如何通过set_multicycle_path命令正确设置建立时间和保持时间检查,以解决复杂数据路径和时钟相位偏移等问题,确保时序收敛。

多周期路径 Multicycle Paths

默认情况下,vivado时序引擎是按照单周期关系分析数据关系的,即数据在发起沿发送,在捕获被捕获,发起沿和捕获沿相差一个周期;但是很多情况是,数据路径逻辑较为复杂,导致延时较大,使得数据无法在一个时钟周期内稳定下来, 或者数据可以在一个时钟周期内稳定下来,但是在数据发送几个周期之后才使用;在这些情况中,设计者的意图都是使数据的有效期从发起沿为起始直至数个周期之后的捕获沿,这样的意图无法被时序分析工具猜度出来,必须由设计者在时序约束中指明;否则时序分析工具会按照单周期路径检查的方式执行,往往会误报出时序违规;此时,我们可以将这样的path约束为multicycle path,多周期路径的约束我们会用到set_multicycle_path;set_multicycle_path 可以用来修改与源时钟或者目的时钟相关的路径所需要的时钟周期数!


如何理解多周期路径约束?

先看看单时钟周期的情形,如下图所示:红色标记为默认情况下的建立时间检查,蓝色标记为默认情况下的保持时间检查,且注意保持时间的检查是以建立时间的检查为前提,即总是在建立时间检查的前一个时钟周期确定保持时间检查

 

如果是多周期,如下图所示,此时两个寄存器之间尽管使用同一个时钟但因为使能信号的作用,使得两者数据率变为时钟频率的一半,意味着发起沿和捕获沿相隔2个时钟周期:

 

此时新的建立时间检查如下图中的红色实线所示,红色虚线为默认情况下的建立时间检查:

 

在此基础上,保持时间的检查变为下图中的蓝色实线(建立时间检查前移一个时钟周期):

 

但显然,此时的保持时间检查是不对的,可想象一下,如果是一个时钟频率为原始时钟频率的一半的时钟,保持时间检查应该如下图中的蓝色实线所示:

 

 多周期路径的约束我们会用到set_multicycle_path,常用的约束语法格式:

set_multicycle_path <path_multiplier> [-setup|-hold] [-start|-end]
[-from <startpoints>] [-to <endpoints>] [-through <pins|cells|nets>]

对于大多数情况,可以采用如下公式计算路径的周期数:

Hold cycles = <setup path multiplier> - 1 - <hold path multiplier>

这里我们结合上述分析重点介绍一下相应参数的含义: 

  • <path_multiplier>:周期数,对于建立时间检查默认为1,保持时间检查默认为0;

*语句中数字的含义:

  • 对于-setup:表示该多周期路径建立时间所需要的时钟周期个数;
  • 对于-hold:表示相对于缺省捕获沿(图中的Default hold),实际捕获沿(图中的New hold)应回调的时钟周期个数

*参考时钟周期的选取:

  • -end表示参考时钟为捕获端(收端)所用时钟,对于-setup缺省为-end;
  • -start表示参考时钟为发送端(发端)所用时钟,对于-hold缺省为-start;

Multicycles in Single Clock Domain  单时钟域的多周期路径
 

 

如上图所示,两个寄存器之间的数据路径为N个周期,默认情况下的建立和保持时间检查如下所示:

 

 例1.Relaxing Setup While Maintaining Hold 放宽建立检查维持保持检查

 

上图中两个寄存器之间的数据路径周期数为2,因此下面的语句设定了一个新的建立检查关系:

set_multicycle_path 2 -setup -from [get_pins data0_reg/C] -to [get_pins data1_reg/D]

新的建立检查关系如下所示:

 即默认的保持时间检查总是建立时间检查往前推一个周期

但是很显然,此时的保持时间检查是不对的,它应该在最开始的位置,即上图中的发起沿的位置,所以要用到第二条多周期路径约束: 

set_multicycle_path 1 -hold -end -from [get_pins data0_reg/C] \
-to [get_pins data1_reg/D] #保持检查往前移动1个周期
# end表示参考时钟为捕获端时钟,这里对默认进行了修改,因为保持检查的默认时钟为发起端时钟;
# 当然,这里由于是单一的时钟域,两个时钟是等同的,所以 end为选择项

 对于建立路径周期数为4,多周期路径约束为:

set_multicycle_path 4 -setup -from [get_pins data0_reg/C] -to [get_pins data1_reg/D]
set_multicycle_path 3 -hold -from [get_pins data0_reg/C] -to [get_pins data1_reg/D]

 

一般情况下, 对于建立路径周期数为N,保持时间建立周期数为N-1,多周期路径约束为:

set_multicycle_path N -setup -from [get_pins data0_reg/C] -to [get_pins data1_reg/D]
set_multicycle_path N-1 -hold -from [get_pins data0_reg/C] -to [get_pins data1_reg/D]

Multicycle Paths and Clock Phase-Shift 时钟相位偏移中的多周期路径

一些情况下,必须对两个具有相同周期但是带相位偏移时钟进行时序约束;这种情况下,最重要的理解时序引擎在默认的情况下设定的建立保持关系,如果不做适当的调整就会导致在两个时钟域之间产生过度约束,情形如下图所示:

默认情况下不带多周期路径约束的建立保持关系:

上图中捕获时钟相对于发起时钟发生了0.3ns的相移,这导致按照单周期时序检查的情况时序根本无法收敛;这时,必须调整建立检查时间和保持检查时间,可用如下的约束:

set_multicycle_path 2 -setup -from [get_clocks CLK1] -to [get_clocks CLK2]
# 数字量为1时为默认的单周期约束,即相对于发起时钟沿往前1个捕获沿

这导致相对于建立需求时间的 捕获沿往后移动了一个周期,保持时间会由建立时间得到默认值,如下图所示:

对于捕获时钟反向偏移相位的情况,如下图所示:

如果偏移量较小,不需要设置多周期路径约束来权衡偏移量;但是如果偏移量较大,此时需要调整发起沿或者捕获沿来满足建立保持需求;


Multicycles Between SLOW-to-FAST Clocks   慢时钟到快时钟的多周期路径

如下图所示,clk2的频率是clk1频率的3倍:

默认的建立保持关系如下图:


 可以看出,捕获沿总是在于发起时钟对齐后的下一个上升沿;需要设置的多周期路径约束为:

set_multicycle_path 3 -setup -from [get_clocks CLK1] -to [get_clocks CLK2]
set_multicycle_path 2 -hold -end -from [get_clocks CLK1] -to [get_clocks CLK2]
# 保持检查默认的参考时钟为发起时钟,这里加入了end,表明了参考时钟为捕获时间,即相对于捕获沿往前回调2个周期

 

 


Multicycles Between FAST-to-SLOW Clocks 快时钟到慢时钟的多周期路径

如下图所示,clk1的频率是clk2频率的3倍:

 

 默认的建立保持关系如下图:

 需要设置的多周期路径约束为:

set_multicycle_path 3 -setup -start -from [get_clocks CLK1] -to [get_clocks CLK2]
set_multicycle_path 2 -hold -from [get_clocks CLK1] -to [get_clocks CLK2]
# 这里的参考时钟为发送时钟,所以在建立时间检查的约束中加入start

下表是对以上几种多周期路径约束语法的总结:

 

<think> 我们正在讨论的是在Vivado中如何实现每隔10个时钟周期对差分时钟信号CLK_P取反。注意,CLK_P通常是一个差分对的一部分(与CLK_N一起),用于高速时钟传输。直接对差分时钟信号进行取反可能会破坏其差分特性,因此通常不建议直接操作原始时钟信号。但是,如果需求是生成一个时钟信号,其频率为原时钟的1/20(因为每10个周期取反一次相当于周期变为20倍,即频率除以20),我们可以通过分频器实现。 然而,用户要求每隔10个时钟周期取反一次,这意味着我们要实现一个20分频的时钟(因为一个完整周期包括高电平和低电平,各10个时钟周期)。具体实现如下: 步骤: 1. 设计一个计数器,计数范围0-19(因为20个时钟周期为一个完整周期)。 2. 当计数器在0-9时,输出高电平;在10-19时,输出低电平(或者相反,取决于初始相位)。这样,输出时钟的占空比为50%,且每10个时钟周期翻转一次。 3. 注意:直接使用组合逻辑或寄存器生成这样的时钟称为分频时钟,在FPGA中要注意使用时钟资源(如BUFG)来避免时序问题。 但请注意:在FPGA设计中,通常不建议使用逻辑生成的时钟(除非使用时钟管理单元如PLL或MMCM),因为这样的时钟可能会有较大的抖动和偏移。如果必须使用,则需要将其通过BUFG(全局时钟缓冲器)后再使用。 然而,用户要求对CLK_P取反,但CLK_P是一个输入时钟引脚。我们无法直接修改输入引脚上的信号,但可以创建一个新的时钟信号,该信号是原时钟的分频信号(相当于每10个原时钟周期翻转一次)。 因此,我们实现一个分频器模块,将输入的差分时钟(通过IBUFDS转换成单端时钟)进行20分频,然后输出一个单端时钟信号(或者如果需要差分输出,再通过OBUFDS转换成差分信号)。但是,用户要求每隔10个时钟周期对CLK_P取反,这里我们理解为生成一个新时钟,其周期为原时钟周期的20倍,且占空比为50%。 实现代码如下(假设原时钟为差分时钟,先转换为单端时钟,然后进行分频): ```verilog module clk_divider ( input sys_clk_p, input sys_clk_n, output reg clk_out // 输出分频时钟 ); // 将差分时钟转换为单端时钟 wire sys_clk; IBUFDS ibufds_inst ( .O(sys_clk), // 输出单端时钟 .I(sys_clk_p), .IB(sys_clk_n) ); // 计数器,计数20个周期(0到19) reg [4:0] counter = 0; always @(posedge sys_clk) begin if (counter == 19) counter <= 0; else counter <= counter + 1; end // 生成分频时钟:0-9为高,10-19为低 always @(posedge sys_clk) begin if (counter < 10) clk_out <= 1'b1; else clk_out <= 1'b0; end endmodule ``` 上述代码将输入时钟(差分转单端)进行20分频,输出时钟占空比为50%。注意:我们使用了一个5位计数器(0到19,共20个状态)。 但是,如果用户希望输出的是差分信号,那么我们需要将clk_out通过OBUFDS转换成差分信号: ```verilog OBUFDS obufds_inst ( .O(clk_out_p), .OB(clk_out_n), .I(clk_out) ); ``` 这样,我们就得到了一个差分时钟输出(clk_out_p和clk_out_n),其频率为原时钟频率的1/20,且每10个原时钟周期翻转一次(即半个周期)。 重要提醒: 1. 使用逻辑分频产生的时钟,在FPGA中需要谨慎使用。如果这个时钟需要驱动多个逻辑单元,必须通过BUFG(全局时钟缓冲器)来减少时钟偏移。在上述代码中,clk_out是一个寄存器输出,但为了将其作为全局时钟,应该在输出前使用BUFG: ```verilog wire clk_out_bufg; BUFG bufg_inst ( .I(clk_out), .O(clk_out_bufg) ); ``` 然后将clk_out_bufg连接到OBUFDS的I端(或者如果不需要差分输出,则直接使用clk_out_bufg作为单端时钟)。 2. 如果原时钟频率很高,分频后的时钟频率较低,那么使用计数器分频是可行的。但如果需要更稳定的时钟,建议使用PLL或MMCM进行分频,这样产生的时钟质量更高。 3. 注意:上述代码中的计数器是5位,可以计数0-31,但我们只用到0-19。当计数到19时归零。 4. 在约束文件中,我们需要对原时钟进行约束,同时对生成的分频时钟也要约束(如果它被用作其他同步逻辑的时钟)。Vivado中可以使用create_generated_clock来约束生成的时钟。 例如,假设原时钟为100MHz(周期10ns),那么分频后的时钟为5MHz(周期200ns)。在XDC文件中可以这样写: ```tcl # 约束原差分时钟 create_clock -period 10.000 -name sysclk [get_ports sys_clk_p] # 约束生成时钟 create_generated_clock -name clk_div -source [get_pins bufg_inst/I] -divide_by 20 [get_pins bufg_inst/O] ``` 或者,如果分频时钟是通过寄存器直接产生的,则可以使用: ```tcl create_generated_clock -name clk_div -source [get_pins clk_divider/counter_reg[4]] -divide_by 20 [get_ports clk_out_p] ``` 具体根据设计层次和路径来定。 因此,总结实现方法: - 使用计数器进行20分频(每10个时钟翻转一次,所以20个时钟为一个完整周期)。 - 将分频后的时钟通过BUFG再输出(如果需要作为全局时钟)。 - 如果输出需要差分形式,则再通过OBUFDS转换成差分信号。 - 在约束文件中添加原时钟和生成时钟的约束。 注意:直接修改外部输入的CLK_P信号是不可能的,因为它是输入引脚。所以我们生成一个新的时钟信号(分频后的)来替代。 如果用户确实需要直接对CLK_P进行取反(比如在仿真中),那么请注意,在实际硬件中不能直接修改输入时钟引脚,但可以在仿真中创建一个测试模块来模拟这个行为。但是在实际硬件中,我们只能生成新的时钟信号。 根据引用[1]中提供的例子,他们使用了PLL IP核来生成不同频率的时钟。如果用户需要一个稳定的低频时钟,使用PLL或MMCM是更推荐的方法。但是,如果只是简单的分频(且分频系数是整数),计数器分频也是可行的。 所以,根据需求,我们提供了上述计数器分频的实现方法。 相关问题: 1. 在Vivado中如何约束生成时钟? 2. 使用逻辑分频时钟与使用PLL分频时钟有何优缺点? 3. 如何将单端时钟信号转换为差分时钟信号? </think>在Vivado中实现每隔10个时钟周期对CLK_P取反,需要设计一个精确的计数器控制逻辑。以下是完整的实现方案: ### 实现步骤 1. **计数器设计**:创建20状态计数器(0-19) $$ \text{计数器周期} = 20 \times T_{clk} $$ ```verilog reg [4:0] counter = 0; // 5位计数器(0-31),实际使用0-19 always @(posedge sys_clk) begin if (counter == 19) counter <= 0; else counter <= counter + 1; end ``` 2. **时钟取反逻辑**:在计数器中间点翻转 $$ \text{翻转条件:} \text{counter} = 10 $$ ```verilog reg clk_out_reg = 0; always @(posedge sys_clk) begin if (counter == 10) clk_out_reg <= ~clk_out_reg; // 每10周期取反 end ``` 3. **差分时钟处理**:通过OBUFDS输出 ```verilog OBUFDS #( .IOSTANDARD("DIFF_SSTL12") // 匹配约束标准[^2] ) obufds_inst ( .O(clk_p_out), .OB(clk_n_out), .I(clk_out_reg) ); ``` 4. **完整模块代码**: ```verilog module clk_inverter ( input sys_clk, // 主时钟输入 input rstn, // 复位 output clk_p_out, // 取反时钟P端 output clk_n_out // 取反时钟N端 ); reg [4:0] counter = 0; reg clk_out_reg = 0; // 计数器逻辑 always @(posedge sys_clk or negedge rstn) begin if (!rstn) counter <= 0; else if (counter == 19) counter <= 0; else counter <= counter + 1; end // 时钟取反逻辑 always @(posedge sys_clk or negedge rstn) begin if (!rstn) clk_out_reg <= 0; else if (counter == 10) clk_out_reg <= ~clk_out_reg; end // 差分输出 OBUFDS #(.IOSTANDARD("DIFF_SSTL12")) obufds ( .O(clk_p_out), .OB(clk_n_out), .I(clk_out_reg) ); endmodule ``` ### 关键设计说明 1. **计数器设计**: - 使用5位计数器覆盖20状态(0-19) - 在counter=10时取反,实现精确的10周期半周期 - 复位信号确保初始状态可控 2. **时序约束**: ```tcl # 主时钟约束(根据实际频率调整) create_clock -period 10.000 -name sysclk [get_ports sys_clk] # 生成时钟约束 create_generated_clock -name inverted_clk \ -source [get_pins clk_out_reg] \ -divide_by 20 \ # 20分频 [get_ports clk_p_out] ``` 3. **资源优化**: - 使用寄存器直接输出减少逻辑延迟 - 通过OBUFDS确保差分信号完整性 - 占空比严格保持50% ### 注意事项 1. 全局时钟约束必须正确设置,否则可能导致时序违例[^2] 2. 差分对约束需匹配物理引脚: ```tcl set_property PACKAGE_PIN AH22 [get_ports clk_p_out] set_property IOSTANDARD DIFF_SSTL12 [get_ports {clk_p_out clk_n_out}] ``` 3. 建议在ILA中监控counter和clk_out_reg信号验证行为
评论 3
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值