最近在altera FPGA里设计一个外设的驱动模块,模块本身逻辑很简单如下图所示,但是模块和外设之间的时序约束问题搞的很头疼,今天先讲讲总结的一些Timequest下外设约束方法,特别是那毫无用户体验而言的Create Generated Clocks用法。
要让外设正确接收FPGA发出的数据,需要dout和clkout满足外设的建立保持时间,如下图所示。
时序分析是基于源reg的Tco、目的reg的Tsu,源reg到目的reg的Tdelay路径延迟以及到两个reg的clk skew计算出来的,现在Timequest不知道外设接收reg和时钟输入端的延迟参数,无法分析,还需分若干步配置:
1.使用Create Clocks建立系统时钟sysclk
create_clock -name {sysclk} -period 20.000 -waveform { 0.000 10.000 } [get_ports {sysclk}]
Timequset里的所有时钟都需要手动设置,首先设置系统时钟,后面的时钟都要基于这个时钟才能生成。
2.使用Create Generated Clocks建立输出时钟clkout
外设的时钟源于FPGA的输出port clkout,如果不建立时钟,timequest只会把clkout当作一个普通的输出引脚,时序分析器认为目的reg缺少驱动时钟,无法分析。Timequest中将通过倍频、分频或者移相等生成的时钟都归为Generated Clocks,你可以使用Create Clocks创建试一下,不会提示创建失败,但是在最后的时序分析里不会加入clkout的clock network delay,Timequest没有你想象的那么智能,知道clkout是从一个分频模块输出,自动加入模块延迟分析,它不知道这些。所以还是使用Create Generated Clocks来创建吧,先填写源时钟sysclk,再填写生成时钟和源时钟的关系,2分频,最后指定target 生成时钟到clkout引脚,这三个步骤看似没问题,结果却是创建失败,提示找不到sysclk到clkout之间的路径,如下所示。
Warning: No paths exist between clock target "clk_out" of clock "clkout" and its clock source. Assuming zero source clock latency.
但是sysclk到clkout明明是有路径的啊,之间的逻辑也很简单,就是一个二分频关系,bdf图如下所示。
clk_div模块的代码如下:
reg clk_div;
always @(posedge clk or negedge rset_n)
begin
if(!rset_n)
clk_div <= 0;
else
clk_div <= ~clk_div;
end
尝试了很多方法都无效,最终实验成功了一种自认为很别扭的方法,“曲线救国”分两步走:
首先Create Generated Clocks 从引脚sysclk 到 寄存器clk_div
create_generated_clock -name {clk_div_r} -source [get_ports {sysclk}] -divide_by 2 -master_clock {sysclk} [get_registers {clk_div:inst4|clk_div}]
再Create Generated Clocks 从寄存器clk_div到输出引脚clk_out
create_generated_clock -name {clkout} -source [get_registers {clk_div:inst4|clk_div}] -master_clock {clk_div_r} [get_ports {clk_out}]
这才把clk_out设置为和sysclk关联的输出时钟。
3. 用set output delay 设置基于clkout时钟的输出dout延迟。
set_output_delay -add_delay -clock [get_clocks {clkout}] 5.000 [get_ports {dout}],这句话在源reg和目的reg之间搭建起一座桥梁,实现两个作用:
1.告诉分析工具,clkout是目的reg的驱动时钟(但是前提是clkout需要被Timequest认为是个时钟),dout是目的reg的数据源,分析工具才能计算出路径的clk skew(clkout的clock network delay - sysclk的clock network delay);
2.告诉了分析工具外设reg的建立时间Tsu和路径延迟Tdelay,这两个参数之和其实就是输出延迟,分别源于外设数据手册和PCB板的走线延迟。
4.设置set max delay和set min delay
如果不需要更严苛的约束,不设置这两个参数也可以。经过前三步,时序分析器已经知道了源reg和目的reg的时钟频率,系统会默认使用时钟频率分析,既最大延迟为时钟周期Tclk,最小延迟为0。这里的set max delay约束Tco和clk skew为了满足外设的建立时间,使(Tclk + clk skew)-(Tco+Tdelay) > Tsu,set min delay 约束Tco和clk skew满足外设的保持时间,(Tco+Tdelay) - clk skew > Th。
5.设置多周期约束
由于本例的特殊性,源时钟是目的时钟的两倍,需要多时钟约束设置,由篇幅所限,下次再细讲。
最后可以在Report Timing的Registers to Outputs的setup和hold里看到时序余量分析了。
回顾约束的整个过程,最闹心的就是第二步Create Generated Clocks了,可能一直受altera里的pll设置影响,只要设置源时钟,填好输出输入时钟关系,pll就可以用了,到了Timequest里的生成时钟设置,我也想当然的认为,clkout是由sysclk生成变化而来,源里面只要填sysclk,关系填好就肯定没问题了,但是结果并非这么简单。
再回到第二步,再尝试从sysclk直接Create Generated Clocks指定到输出时钟clkout,发现除了找不到sysclk和clkout之间的路径之外还有一个警告
Warning: Node: clk_div:inst|clk_div was determined to be a clock but was found without an associated clock assignment。
警告clk_div被设置为了时钟,但是没有指定关联的源时钟即没指定clk_div是由哪个时钟生成的,从这个警告推理:我设置的Generated 时钟是clkout,而clkout是由clk_div驱动的,所以Timequest也将clk_div认为是一个时钟路径node,但是我只告诉了Timequest clkout的源时钟是sysclk,而没有告诉它clk_div的源时钟是哪个,所以创建失败,虽然从程序显而易见源也是sysclk,看来我高估了TimeQuest的智商,按照上述的“两步走”方案,第一步就是告知clk_div的源时钟是sysclk,第二步再生成基于clk_div的clkout就ok了
推测Timequest中对Creat Generated Clocks分析过程是这样的:从最终的target Clock端口往回分析,寻找这个时钟路径上的net寄存器,看其是否有关联时钟,如果有,看是否已经指定了,如果指定了则继续反向分析直到源时钟,如果没有则提示创建失败。为了验证这个推测,再级联一个分频模块clk_div如下图所示。
此时该如何建立基于sysclk的输出时钟clk_out呢,从clk_out管脚返回分析,clk_out由ints5|clk_div驱动,ints5|clk_div的关联时钟是inst4|clk_div,inst4|clk_div的关联时钟是sysclk,所以要分三步创建:
首先创建基于sysclk的inst4|clk_div。
create_generated_clock -name {clk_div_r} -source [get_ports {sysclk}] -divide_by 2 -master_clock {sysclk} [get_registers {clk_div:inst4|clk_div}]
再创建基于inst4|clk_div的inst5|clk_div。
create_generated_clock -name {clk_div_rr} -source [get_registers {clk_div:inst4|clk_div}] -divide_by 2 -master_clock {clk_div_r} [get_registers {clk_div:inst5|clk_div}]
最后创建基于inst5|clk_div的clk_out。
create_generated_clock -name {clkout} -source [get_registers {clk_div:inst5|clk_div}] -master_clock {clk_div_rr} [get_ports {clk_out}]
验证结果,少了上述任何一步都无法得到正确的输出时钟,比如用Create clk 创建inst4|clk_div 取代第一步,虽然也可以生成clk_out,但最终的输出延迟会减小。
多数工程中,使用分频模块输出时钟的做法并不多,更多的是使用pll,我又尝试了下使用PLL产生一个外部时钟,PLL的源为sysclk,仍然无法直接Create Generated Clocks从sysclk到clk_out,而是首先derive_pll_clocks将所有的PLL输出都设为时钟,再Create Generated Clocks从PLL的时钟输出寄存器到clk_out才可以。