勇敢的芯伴你玩转Altera FPGA连载39:Verilog代码风格之提升系统性能的代码风格
0赞勇敢的芯伴你玩转Altera FPGA连载39:Verilog代码风格之提升系统性能的代码风格
特权同学,版权所有
配套例程和更多资料下载链接:
http://pan.baidu.com/s/1i5LMUUD
下面要列举的代码示例是一些能够起到系统性能提升的代码风格。在逻辑电路的设计过程中,同样的功能,可以由多种不同的逻辑电路来可以实现,那么就存在这些电路中孰优孰劣的讨论。因此,带着这样的疑问,我们也一同来探讨一下几种常见的能够提升系统性能的编码技巧。请注意,本知识点所涉及的代码更多的是希望能够授人以“渔”而非授人以“鱼”,大家重点掌握前后不同代码所实现出来的逻辑结构,在不用的应用场合下,可能会有不同的逻辑结构需求,那么大家就要学会灵活应变并写出适合需求的代码。
① 减少关键路径的逻辑等级
在时序设计过程中遇到一些无法收敛(即时序达不到要求)的情况,很多时候只是某一两条关键路径(这些路径在器件内部的走线或逻辑门延时太长)太糟糕。因此,设计者往往只要通过优化这些关键路径就可以改善时序性能。而这些关键路径所经过的逻辑门过多往往是设计者在代码编写时误导综合工具所导致的,那么,举一个简单的例子,看看两段不同的代码,关键路径是如何明显得到改善的。
这个一个简单的例子要实现如下的逻辑运算:
y = ((~a & b & c) | ~d) & ~e;
他们的运算真值表如表5.2所示。
表5.2 运算真值表
输入 |
输出 |
||||
a |
b |
c |
d |
e |
y |
x |
x |
x |
x |
1 |
0 |
x |
x |
X |
0 |
0 |
1 |
1 |
x |
x |
1 |
0 |
0 |
x |
0 |
x |
1 |
0 |
0 |
x |
x |
0 |
1 |
0 |
0 |
0 |
1 |
1 |
1 |
0 |
1 |
注:x表示可以任意取0或1。
按照常规的思路,我们可能会写出如下的代码:
// Verilog例程
module example(a, b, c, d, e, y);
input a,b,c,d,e;
output y;
wire m,n;
assign m = ~a & b & c;
assign n = m | ~d;
assign y = n & ~e;
endmodule
使用Quartus II自带的综合工具,我们可以看到它的RTL视图如图5.10所示,和我们的代码相吻合。
图5.10 未优化前综合结果
而现在假定输入a到输出y的路径是关键路径,影响了整个逻辑的时序性能。那么,下面我们就要想办法从这条路径着手做一些优化的工作。很简单,我们的目标是减少输入a到输出y之间的逻辑等级,目前是3级,我们可以想办法减少到2级甚至1级。
我们来分析公式“y = ((~a & b & c) | ~d) & ~e;”,把~a从最里面的括号往外提取一级就等于减少了一级逻辑。我们简单的分析,当a=0时,y = ((b & c) | ~d) & ~e;当a=1时,y = ~d & ~e。由此我们不难得到“y = ((~a | ~d) & ((b & c) | ~d)) & ~e;”与前式是等价的。我们可以修改前面的代码如下:
// Verilog例程
module example(a, b, c, d, e, y);
input a,b,c,d,e;
output y;
wire m,n;
assign m = ~a | ~d;
assign n = (b & c) | ~d;
assign y = m & n & ~e;
endmodule
经过修改后的代码综合结果如图5.11所示,虽然b和c到y的逻辑等级还是3,但是关键路径a到y的逻辑等级已经优化到了2级。与前面不同的是,优化后的d信号多了一级的负载,也多了一个逻辑门,这其实也是一种“面积换速度”思想的体现。正可谓“鱼和熊掌不可兼得”,在逻辑设计中我们往往需要在“鱼和熊掌”间做抉择。
图5.11 优化后综合结果
上面的这个实例,只是一个也许未必非常恰当的“鱼”的例子。在前面的章节里已经介绍过,在实际工程应用中,类似的逻辑关系可能在映射到最终器件结构时并非以逻辑门的方式来表现,通常是四输入查找表来实现,那么它的优化可能和单纯简单逻辑等级的优化又有些不同,不过希望大家能在这个小例子中学到“渔”的技巧。
②逻辑复制(减少重载信号的散出)与资源共享
逻辑复制是一种通过增加面积来改善时序条件的优化手段。逻辑复制最主要的应用是调整信号的扇出。如果某个信号需要驱动的后级逻辑信号较多,换句话说,也就是其扇出非常大,那么为了增加这个信号的驱动能力,就必须插入很多级的Buffer,这样就在一定程度上增加了这个信号的路径延时。这时可以复制生成这个信号的逻辑,用多路同频同相的信号驱动后续电路,使平均到每路的扇出变低,这样不需要插入Buffer就能满足驱动能力增加的要求,从而节约该信号的路径延时。
资源共享和逻辑复制恰恰是逻辑复制的一个逆过程,它的好处就在于节省面积,同时可能也要以速度的牺牲为代价。
看一个实例,如下:
// Verilog例程
module example(sel, a, b, c, d, sum);
input sel,a,b,c,d;
output[1:0] sum;
wire[1:0] temp1 = {1'b0,a}+{1'b0,b};
wire[1:0] temp2 = {1'b0,c}+{1'b0,d};
assign sum = sel ? temp1:temp2;
endmodule
该代码综合后的视图如图5.12所示,和我们的代码表述的一致,有连个加法器进行运算,结果通过2选1选择器后输出给sum。
5.12 两个加法器的视图
同样实现这个的功能,我们还可以这么编写代码:
// Verilog例程
module example(sel, a, b, c, d, sum);
input sel;
input[7:0] a,b,c,d;
output[7:0] sum;
wire[7:0] temp1 = sel ? a:c;
wire[7:0] temp2 = sel ? b:d;
assign sum = temp1+temp2;
endmodule
综合后的视图如图5.13所示,原先两个加法器我们现在用一个加法器同样可以实现。而原先的一个2选1选择器则需要4选2选择器(可能是两个2选1选择器来实现)替代。如果在设计中加法器资源更宝贵些,那么后面这段代码通过加法器的复用,相比前面一段代码更加节约资源。
图5.13 一个加法器的视图
③ 消除组合逻辑的毛刺
在章节3.2的最后部分对于组合逻辑和时序逻辑的基本概念做了较详细的介绍,并且列举了一个实例说明时序逻辑在大多数设计中更由于组合逻辑。组合逻辑在实际应用中,的确存在很多让设计者头疼的隐患,例如这里要说的毛刺。
任何信号在FPGA器件内部通过连线和逻辑单元时,都有一定的延时,正是我们通常所说的走线延时和门延时。延时的大小与连线的长短和逻辑单元的数目有关,同时还受器件本身的制造工艺、工作电压、温度等条件的影响。信号的高低电平转换也需要一定的上升或下降时间。由于存在这些因素的影响,多个信号的电平值发生变化时,在信号变化的瞬间,组合逻辑的输出并非同时,而是有先有后,因此往往会出现一些不正确的信号,例如一些很小的脉冲尖峰信号,我们称之为“毛刺”。如果一个组合逻辑电路中有毛刺出现,就说明该电路存在“冒险”。
下面我们可以列举一个简单例子来看看毛刺现象是如何产生和消除的。如图5.14所示,这里在图5.10所示实例的基础上对这个组合逻辑的各条走线延时和逻辑门延时做了标记。每个门延时的时间是2ns,而不同的走线延时略有不同。
图5.14 组合逻辑路径的延时标记
在这个实例模型中,我们不难计算出输入信号a、b、c、d、e从输入到输出信号y所经过的延时。通过计算,可以得到a、b、c信号到达输出y的延时是12ns,d到达输出y的延时是9ns,而e到达输出y的延时是7ns。从这些传输延时中,我们可以推断出,在第一个输入信号到达输出端y之前,输出y将保持原来的结果;而在最后一个输入信号到达输出端之后,输出y将获得我们期望的新的结果。从本实例来看,7ns之前输出y保持原结果,12ns之后输出y获得新的结果。那么这里就存在一个问题,在7ns和12ns之间的这5ns时间内,输入y将会是什么状态呢?
如图5.15所示,这里列举一种出现毛刺的情况。假设在0ns以前,输入信号a、b、c、d、e取值均为0,此时输出y=1;在0ns时,b、c、d由0变化为1,输出y=1。在理想情况下,输出y应该一直保持1不变。但从我们的延时模型来看,实际上在9ns到12ns期间,输出y有短暂的低脉冲出现,这不是电路应该的状态,它也就是这个组合逻辑的毛刺。
图5.15 逻辑延时波形
既然我们的多个输入信号的变化前后取值都保持高电平,那么这个低脉冲的毛刺其实不是我们希望看到的,也很可能在后续电路中这个毛刺导致后续的采集出现错误,甚至使得一些功能被误触发。
好了,言归正传,要消除这个毛刺,通常有两个办法,一个办法是硬办法,如果在y信号上并联一个电容,便可轻松的将这类脉冲宽度很小的干扰滤除。但是,我们现在是在FPGA器件内部,还真没有这样的条件和可能性这么处理,那么只能放弃这种方案。另一种办法其实也就是引入时序逻辑,用寄存器多输出信号打一拍,这其实也是时序逻辑明显优于组合逻辑的特性。
如图5.16所示,在原有组合逻辑的基础上,我们添加了一个寄存器用于锁存最终的输出信号y。
图5.16 寄存器锁存组合逻辑输出
如图5.17所示,在引入了寄存器后,新的最终的输出yreg不再随意的改变,而是在每个时钟clk的上升沿锁存当前的输出值。
图5.17 寄存器锁存波形
引入时序逻辑后,并不是说完全就不会产生错误的数据采集或锁存。在时序逻辑中,我们只要遵循一定的规则就可以避免很多问题,如保证时钟clk有效沿前后的数据建立时间和保持时间内待采集的数据是稳定的。