一、 前言
时序分析,是所有的FPGA工程师在成长过程中都绕不开的技术,由于在一开始我们学FPGA的时候设计的系统都是低速简单的,所以就使得时序分析看起来好像并没有卵用,我不学我的系统照样可以跑起来啊,于是慢慢忽视了这一部分的学习。但是随着我们的技术的不断提升,我们需要设计一些高频复杂的系统了,结果傻眼了,明明我的代码逻辑没错,我的波形验证也没错,怎么一上板子他就有问题呢?这时就轮到我们的时序分析开始发挥作用了。
很可惜的是,现在网络上大部分关于时序分析和约束的文章都是枯燥且无聊的,几乎每篇文章一上来就是枯燥的概念,什么建立时间、保持时间、余量等等。中文写起来都如此枯燥了,他们还非得用一大堆英文,Tsu、Th等等,并推导出一大堆看似无聊的数学公式。实事求是的说,这些文章讲的内容并没错,但是我们学的是工科,工科的精髓应该在于首先建立感性的认识,然后才有数学公式。就好比物理,他们的公式首先应该具有物理意义,然后我们才能深刻地掌握他们,这也是本文想做的事情,就是帮助大家建立对时序分析的感性认识。
那么没有接触过时序分析的人可能会问,时序分析到底是干什么的呢?他到底能够解决什么问题呢?首先它能够应对我们前面所说的,明明我的代码逻辑没错,我的波形验证也没错,怎么一上板子他就有问题呢这样的问题。其次它提升我们板子所能跑的最高时钟频率,比如说你现在在50M的时钟频率下完成一个功能需要10分钟,那么把你的时钟频率提高到200M的话,那么完成一个功能不就只需要2.5分钟吗?但问题是你的系统能跑这么高的时钟频率吗?这也是时序分析所能解决的问题。
好的,下面让我们开始进入正题。
二、FPGA中的基本结构
在讲时序分析之前,我们首先应该了解FPGA的基本结构。那有些同学就会说为什么我需要了解FPGA的内部结构,我不了解,但我只要会写verilog我的系统照样能跑起来啊。同学你先别急,了解FPGA是我们进行时序分析的必要前提,下面我来简单介绍一下。
相信很多同学在学FPGA的时候都会遇到下面这个图:
这个图中一个蓝色的小方块和一个红色的小方块就组成了FPGA的最基本逻辑单元LUT。如果要简单理解LUT是什么的话,可以认为LUT的蓝色部分可以用于我们的组合逻辑,LUT的红色部分可以用于我们的时序逻辑。那么LUT的蓝色部分为什么可以用于构建组合逻辑呢?不知道在座的各位同学是否知道数据选择器,也就是下面这个东西:
在上图可以看到,4输入数据选择器是4个输入控制1个输出,他的作用就是用于类似于百科全书,比如我写了一条组合逻辑的语句:
assign out = a0 & a1 & a2 & a3;
那么上面的四输入数据选择器,就需要在a0 = a1 = a2 = a3 = 1的时候,让out = 1。而其余情况需要让out = 0。这就让四输入数据选择器像一个百科全书一样,无论输入选择什么值,他都能找到对应的输出取值。学过数电的同学应该也能反应过来,这个模块就是我们所学的真值表,而我们常叫它查找表(look up table)。
讲完了LUT蓝色部分的组合逻辑,那么红色部分的时序逻辑它又是什么呢?其实他就是我们熟悉的D触发器,他的作用就是让上一个周期的输入取值,变成下一个周期的输出取值。
在讲完了FPGA的基本结构之后,我们接下来讲讲我们的verilog代码是怎么和我们上面所讲的结构对应起来的。不知道各位有没有常常听说过这样一句话,“我们在写代码的时候一定要心中有电路”,不知道各位听完这句话有什么感受,反正初学时我听这句话就会感觉,别装了,还心中有电路,这么多代码你给我想个电路看看。但下面我就开始教各位最基础的手上写代码,心中有电路。
在我们所写的verilog代码中,其实最常写的就是三个东西,分别是assign语句,always块,以及case语句。其中assign语句对应的是组合逻辑,那么组合逻辑对应的不就是与或非门的组合嘛,就比如下面这段代码和它对应的电路:
assign out = a0 & a1;
而对于always块,他时常对应的是时序逻辑,也就是对应着reg型变量。那么always块对应的电路是什么样的呢?我们首先写一段代码:
always@(posedge sys_clk)
if(a0 & a1)
out <= 1'd1;
else
out <= 1'd0;
上面这段代码的意义很简单,就是a0 & a1为真时,则out为1,否则out为0。但是他用的是always块,所以out也从上面的组合逻辑,变成了时序逻辑,那么他的电路图就不再只是简单的与门了,而是下面这个样子:
可以看到在加入always块之后,其实就是在与门的后面多加了一个D触发器,使得现在out不是和组合逻辑一样想变就变,而是要受到时钟的管控,只有时钟的上升沿到来之时,Out才可以改变。那么我们再设想一下,假如a0和a1的本身他也是always块,那是不是意味着a0和a1他们都是两个D触发器的输出,那么上面的电路就会变成下面这个样子:
那么为了简化我们的分析,我们认为a1他只是作为组合逻辑的一部分,我们并不需要太关心a1这个D触发器带来的影响,而且由于D触发器还有个重要的时钟没有画出来,所以我们继续将上图化简成下面这个样子:
看到这,恭喜你,你已经有了我们时序分析最重要的基础模型了,也就是上面这个模型,我们所有复杂的时序分析都离不开上面这个模型。在上面的介绍中,我们说过FPGA中常见的其实就是两种电路,一个是assign对应的组合逻辑电路,另一个是always块对应的时序逻辑电路,而对于组合逻辑电路他是不存在所谓的时序问题,因为他连时钟都没有,更不用说什么时序问题了。而我们下面所有的内容展开都离不开上面这个模型图,因为几乎所有的always块他都可以等价于上面这个图。
三、怎么搞时序分析
在有了我们的FPGA基础电路之后,我们对于时序分析还是一脸懵逼,到底时序分析是干什么的呢?下面我们还是以下面这段代码来继续我们的讲解:
always@(posedge sys_clk)
if(a0 & a1)
out <= 1'd1;
else
out <= 1'd0;
假设上面这段代码中a1一直为1,那么此时out的取值取决于a0是1还是0,他们的波形图如下:
在上图中,clk1代表发射D触发器的时钟,clk2代表接受D触发器的时钟,a0代表发射D触发器的输出,in代表接受D触发器的输入,out代表接受D触发器的输出。可以看到在1号时钟上升沿到来之后,此时a0和in的数据都发生了瞬间变化,但是对于out来说,这种由发射D触发器在1号时钟上升沿带来的数据变化,只有在2号时钟上升沿到来之后才会使得out的取值发生变化。这就好比你的甲方在1号上升沿给你一个数据,你作为乙方会在下一个上升沿到来之后将甲方的数据进行处理,然后再输出。
接下来我们就需要引入建立时间和保持时间的概念了,我们还是以甲方与乙方进行举例。你作为一个乙方,将在2号上升沿处理数据,那么你需要关心两件事情。第一个事情就是甲方需要提前2号上升沿一段时间发送来数据,否则的话,你可能干不完活,那么自然就不能在2号上升沿到来的时候将数据进行输出。那么甲方需要提前多少时间将数据送给你呢?这个时间就称为建立时间,也就是Tsu,示意图如下:
那么Tsu就可以理解为FPGA芯片的工作能力,如果FPGA的工作能力越强,那么他处理数据的能力就越强,自然Tsu就可以越短。那么接下来我们来对应下面这张电路图,再深刻理解一下什么是建立时间:
所谓的建立时间在这张图中的含义就是,在我的clk2的2号时钟上升沿到来之前,发射D触发器应该提前Tsu将a0的数据送到in这个地方。那有的人就会困惑了,难道事情不应该是在1号时钟上升沿到来的时候,我的a0马上就会发生变换,然后由于in和a0是组合逻辑的关系,所以a0一变,in也马上更着变,然后我的接受D触发器可以有1个时钟周期的时间来处理in这个数据,怎么会不满足建立时间Tsu呢?
这就是初学FPGA的学习者的理想化模型,但实际上的FPGA并没有那么理想,那么它不理想的地方在哪呢?首先在clk1时钟上升沿到来的时候,a0不会马上发生变化,因为他有一个延迟,使得a0需要经过一段时间才会发生变化,那么这段延迟时间我们常称之为Tco。然后a0到in这段路程也是需要有一定的时间延迟,我们常将这段延迟称之为Tdata。知道了Tco和Tdata之后,那么电路图和时序图应该就变成了下面这个样子:
可以看到in这个信号变量不会在1号上升沿到来的时候就发生变化,而是需要经过Tco+Tdata的时间才会发生变化。那么这和我们的Tsu有什么关系呢,我再画出下面这个图,各位就一目了然了:
上图中就清晰的显示了什么是建立时间。那么我们要做的事情就是让a0这个变量在经过Tco和Tdata的延迟之后,需要在第二个时钟上升沿的Tsu时间之前将数据送给in,这样才能满足建立时间。那么数据从a0到in,实际上就是发射D触发器的输出到接受D触发器的输入,那么这段时间我们称为data arrival time(数据到达时间)。数据到达时间的公式如下:
那么data arrival time,其实是需要小于一个取值的,也就是甲方发送给我数据时间不能大于多少,大于该时间我就无法干活了,这个时间我们称之为data request time(数据需求时间),数据需求时间的公式如下:
其中T代表时钟周期。其实我们要做的事情就是让data arrival time小于data request time即可,这样就可以满足建立时间了。虽然我们已经考虑了Tco和Tdata了,但是这个模型还是比较理想,还是有东西没有考虑进来,这个东西就是时钟的延迟。对于时钟芯片输入进来的时钟信号,他从管脚进来,到发射D触发器和接受D触发器的时钟端,这个过程中时钟难道就没有延迟吗?那么有了延迟之后,dat arrival time和data request time是否又会发生变化呢?这是我们接下来要探讨的问题。
首先我们来考虑一下发射D触发器的时钟延迟。对于发射D触发器来说,如果时钟有延迟,也就意味着甲方会比一开始要慢一点工作,那么甲方就会比原来晚一点发送过来数据,然后数据才会经过Tco和Tdata到达接受触发器。那么数据的波形和电路图应该是下面这个样子:
上图中的clk参数代表从管脚引入的时钟信号,而clk1会比clk信号延迟tclk个时间单位。上图中我们加入了tclk1这个参数之后,此时唯一改变的就是data arrival time。因为甲方自己延误了tclk1,那么整个数据的送达时间也就增加了tclk1,这对于建立时间是一件好事还是坏事呢?那当然是一件不利的事情,因为送达数据的时间变得更晚了,也就更难满足建立时间了。上面这个图中没有标出tco和tdata,各位可以自行看看能不能自己标出。那么Tdata的公式就变成了下面这个:
在引入了发射D触发器的时钟延迟之后,那么接受D触发器自然也需要引入时钟延迟tclk2。首先我们来思考一下对于建立时间来说,引入tclk2是否有益于满足建立时间呢?答案是肯定的。这是因为作为乙方我要干活的时间变晚了,相当于我可以更晚一点开始工作,自然就给了甲方更多的运送数据的时间,自然也就有利于满足建立时间了。那么再加入了tclk2之后,整个数据的波形和电路图应该是下面这个样子:
那么data request time应该就是下面这个式子:
在介绍完数据到达时间和数据需求时间之后,我们来介绍在quartus和vivado软件中常见的一个名词,也就是Tslack(建立时间余量)。其实所谓的建立时间余量指的就是数据送达时间和数据需求时间的差值,再通俗点说就是我要求你在7点钟送达,而你5点就送到了,那么建立时间余量就是2个小时。可以知道的是,余量大于0才行,否则就是不满足建立时间。建立时间余量的公式如下:
如果我们需要满足建立时间,也就是需要满足下面这个式子:
在聊完建立时间之后,我们再来聊聊保持时间。从上面的内容,不知道很多人是否有这样一个感觉,那既然我要满足建立时间,那我就让数据送的越快越好,送的越快,则建立时间余量越大,甚至于有人会想我直接让data arrival time变成0,那就是最理想的情况了。但实际上我们不能这么做,数据送达时间不是越短越好。那么理由是什么呢?假设你想想你作为一个乙方,时钟上升沿到来了之后,你应该输出数据了,结果你上一个时钟周期的数据还没有完全处理完毕,甲方马上就把下一个时钟周期的数据送过来了,然后把你上一个时钟周期的数据给覆盖了,换成你你能不恼火吗。所以从上面这个例子来看,数据不是送的越快越好,而是应该大于某个取值才行,这样我才能安心处理完上一个时钟周期的数据。这个最短送达时间我们称之为保持时间(Th),从上面的描述来看,我们只需要满足下面这个式子就可以满足保持时间:
但细心的朋友在看到上面的式子也会说句不对,为什么呢?因为没有考虑进入Tclk2。那么我们来想想再加入了Tclk2之后对于满足保持时间是有利还是有害呢?答案是有害的。这是因为再引入了Tclk2之后,乙方开始工作的更晚,对于急切想要输送数据的甲方来说这简直就是折磨啊,意味着甲方要更慢地输送数据,那么上面的公式会变成下面这样:
至此我们的建立时间和保持时间全部讲解完毕,可以看到在上面的内容中,我们生动形象的讲解了建立时间和保持时间,并且可以将他们统一到下面这个公式之中。如果用一句话来总结一下时序分析是干什么的话,可以总结成:让数据送到的时间不快也不慢,刚刚好在一个范围内。
四、怎么搞时序约束
在讲完时序分析之后,很多人又会说时序分析我懂了,但是我怎么让数据送到的时间刚刚好呢?下面我来讲讲这个问题。
FPGA内部的时序约束
首先对于如何让数据送达时间刚刚好,其实我们是需要分为在FPGA内部和FPGA外部。对于FPGA内部来说,由于全局时钟树的存在,Tclk1往往和Tcllk2大小差不多相等,而Th往往取值又很小,所以我们常常不考虑保持时间,只考虑建立时间。也就是如何让数据在FPGA的内部传输的越快越好。而对于如何让数据传输的越快,其实我们可以看看,在下面这个公式中我们能控制的变量是什么:
可以看到我们好像能控制的东西只有Tdata了,也就是如何缩短数据从发射D触发器到接受D触发器的时间。而从发射D触发器到接受D触发器的过程中,它主要是需要走过一系列的组合逻辑,所以也就是说在编写verilog的时候我们在always块中编写的逻辑越简单越好,尤其是一定需要少写if else,大量的if条件语句,会使得组合逻辑变得十分复杂。那么如果确实是需要用很复杂的if条件语句呢,那么我可以用flag标志信号。
好吧写到这里我感觉写的内容太多了,给我写累了,感觉自己都在写毕业论文一样了。时序约束这里埋个坑吧,时序分析应该是讲清楚了。时序约束的话主要是要讲清楚input delay max这些东西怎么算,然后还需要讲输入源同步、输出源同步还有非源同步这些模型是什么样子,在这些模型下如何计算input delay max等等。但其实讲清楚并不难,实际上就是告诉quartus软件在外部,我们的tclk1、tdata、tclk2、tco这些参数是多少,并把他们转换为数据到达时间的形式。比如说我quartus不知道外部模型的tclk1、tdata、tclk2、tco,那么我只需要告诉quartus:tclk1+tdata+tco-tclk2的取值就可以,这就是时序约束。
如果各位有疑惑可以在评论区留言,私信的话我可能看不见,见谅各位。然后由于本人水平有限,有问题欢迎提出。