Intel FPGA/CPLD设计(高级篇)
上QQ阅读APP看书,第一时间看更新

1.1 可编程逻辑基本设计原则

可编程逻辑设计有许多内在规律可循,总结并掌握这些规律对于较深刻地理解可编程逻辑设计技术非常重要。本章从FPGA/CPLD的基本概念出发,总结出4个基本设计原则,这些指导原则范畴非常广,希望读者不仅仅是学习它们,更重要的是理解它们,并在今后的工作实践中充实、完善它们。

(1)面积和速度的平衡与互换原则。提出了FPGA/CPLD设计的两个基本目标,并探讨了这两个目标对立统一的矛盾关系。

(2)硬件原则。重点在于提醒读者转化软件设计的思路,理解HDL语言设计的本质。

(3)系统原则。希望读者能够通过从全局、整体上把握设计,从而提高设计质量,优化设计效果。

(4)同步设计原则。设计时序稳定的基本要求,也是高速PLD设计的通用法则。

1.1.1 面积和速度的平衡与互换原则

这里的“面积”是指一个设计所消耗FPGA/CPLD的逻辑资源数量:对于FPGA,可以用所消耗的触发器(FF)和查找表(LUT)来衡量;对于CPLD,常用宏单元(MC)衡量。用设计所占用的等价逻辑门数来衡量设计所消耗FPGA/CPLD的逻辑资源数量也是一种常见的衡量方式。“速度”指设计在芯片上稳定运行时所能够达到的最高频率,这个频率由设计的时序状况决定,与设计满足的时钟周期、PAD to PAD Time、Clock Setup Time、Clock Hold Time和Clock-to-Output Delay等众多时序特征量密切相关。面积(Area)和速度(Speed)这两个指标贯穿着FPGA/CPLD设计的始终,是设计质量评价的终极标准。这里我们就讨论一下设计中关于面积和速度的基本原则:面积和速度的平衡与互换。

面积和速度是一对对立统一的矛盾体。要求一个设计同时具备设计面积最小,运行频率最高,这是不现实的。科学的设计目标应该是在满足设计时序要求(包含对设计最高频率的要求)的前提下,占用最小的芯片面积,或者在所规定的面积下,使设计的时序余量更大,频率更高。这两种目标充分体现了面积和速度的平衡思想。关于面积和速度的要求,我们不应该简单地理解为工程师水平的提高和设计完美性的追求,而应该认识到它们是与产品的质量和成本直接相关的。如果设计的时序余量比较大,运行的频率比较高,则意味着设计的健壮性更强,整个系统的质量更有保证;另一方面,设计所消耗的面积更小,则意味着在单位芯片上实现的功能模块更多,需要的芯片数量更少,整个系统的成本也随之大幅度削减。

作为矛盾的两个组成部分,面积和速度的地位是不一样的。相比之下,满足时序、工作频率的要求更重要一些,当两者冲突时,采用速度优先的准则。

面积和速度的互换是FPGA/CPLD设计的一个重要思想。从理论上讲,一个设计如果时序余量较大,所能跑的频率远远高于设计要求,那么就能通过功能模块复用减少整个设计消耗的芯片面积,这就是用速度的优势换面积的节约;反之,如果一个设计的时序要求很高,普通方法达不到设计频率,那么一般可以通过将数据流串并转换,并行复制多个操作模块,对整个设计采取“乒乓操作”和“串并转换”的思想进行处理,在芯片输出模块处再对数据进行“并串转换”。从宏观上看,整个芯片满足了处理速度的要求,这相当于用面积复制换取速度的提高。面积和速度互换的具体操作技巧很多,如“模块复用”“乒乓操作”“串并转换”等,需要大家在日后工作中不断积累。下面举例说明如何使用“速度换面积”和“面积换速度”。

【例1-1】如何使用“速度的优势换取面积的节约”?

在WCDMA(宽带码分多址)系统中,使用到了快速哈达码(FHT)运算,如图1-1所示。

图1-1 FHT原理图

FHT的单步算法如下。

Out[2i]=In[2i]+In[2i+8];i=0-7;

Out[2i+1]=In[2i+1]-In[2i+1+8];i=0-7

考虑流水线式数据处理的要求,最自然的设计方法就是设计不同端口宽度的4个单步FHT,并将这4个单步模块串联起来,从而完成数据流的流水线处理。该FHT实现方式的代码如下。

  //该模块是FHT的顶层,调用4个不同端口宽度的单步FHT模块,完成整个FHT算法
  module
fhtpart(Clk,Reset,FhtStarOne,FhtStarTwo,FhtStarThree,FhtStarFour,
             I0,I1,I2,I3,I4,I5,I6,I7,I8,
             I9,I10,I11,I12,I13,I14,I15,
             Out0,Out1,Out2,Out3,Out4,Out5,Out6,Out7,Out8,
             Out9,Out10,Out11,Out12,Out13,Out14,Out15);
  input Clk;    //设计的主时钟
  input Reset;   //异步复位
  input FhtStarOne,FhtStarTwo,FhtStarThree,FhtStarFour; //4 个单步算法的时
序控制信号
  input [11:0] I0,I1,I2,I3,I4,I5,I6,I7,I8;
  input [11:0] I9,I10,I11,I12,I13,I14,I15;                //FHT的16个输入
  output [15:0] Out0,Out1,Out2,Out3,Out4,Out5,Out6,Out7;
  output [15:0] Out8,Out9,Out10,Out11,Out12,Out13,Out14,Out15;  //FHT的
16个输出
  
  //第1次FHT单步运算的输出
  wire [12:0] m0,m1,m2,m3,m4,m5,m6,m7,m8,m9;
  wire [12:0] m10,m11,m12,m13,m14,m15;
  
  //第2次FHT单步运算的输出
  wire [13:0] mm0,mm1,mm2,mm3,mm4,mm5,mm6,mm7,mm8,mm9;
  wire [13:0] mm10,mm11,mm12,mm13,mm14,mm15;
  
  //第3次FHT单步运算的输出
  wire [14:0] mmm0,mmm1,mmm2,mmm3,mmm4,mmm5,mmm6,mmm7,mmm8,mmm9;
  wire [14:0] mmm10,mmm11,mmm12,mmm13,mmm14,mmm15;
  
  //第4次FHT单步运算的输出
  wire [15:0] Out0,Out1,Out2,Out3,Out4,Out5,Out6,Out7,Out8,Out9;
  wire [15:0] Out10,Out11,Out12,Out13,Out14,Out15;
  //第1次FHT单步运算
  fht_unit1 fht_unit1(Clk,Reset,FhtStarOne,
             I0,I1,I2,I3,I4,I5,I6,I7,I8,
             I9,I10,I11,I12,I13,I14,I15,
             m0,m1,m2,m3,m4,m5,m6,m7,m8,
             m9,m10,m11,m12,m13,m14,m15
             );
  
  //第2次FHT单步运算
  fht_unit2 fht_unit2(Clk,Reset,FhtStarTwo,
             m0,m1,m2,m3,m4,m5,m6,m7,m8,
             m9,m10,m11,m12,m13,m14,m15,
             mm0,mm1,mm2,mm3,mm4,mm5,mm6,mm7,mm8,
             mm9,mm10,mm11,mm12,mm13,mm14,mm15
             );
  //第3次FHT单步运算
  fht_unit3 fht_unit3(Clk,Reset,FhtStarThree,
             mm0,mm1,mm2,mm3,mm4,mm5,mm6,mm7,mm8,
             mm9,mm10,mm11,mm12,mm13,mm14,mm15,
             mmm0,mmm1,mmm2,mmm3,mmm4,mmm5,mmm6,mmm7,mmm8,
             mmm9,mmm10,mmm11,mmm12,mmm13,mmm14,mmm15
             );
  //第4次FHT单步运算
  fht_unit4 fht_unit4(Clk,Reset,FhtStarFour,
             mmm0,mmm1,mmm2,mmm3,mmm4,mmm5,mmm6,mmm7,mmm8,
             mmm9,mmm10,mmm11,mmm12,mmm13,mmm14,mmm15,
             Out0,Out1,Out2,Out3,Out4,Out5,Out6,Out7,Out8,
             Out9,Out10,Out11,Out12,Out13,Out14,Out15
             );
  endmodule

单步FHT运算如下(仅仅举例第4步的模块)。

  module fht_unit4(Clk,Reset,FhtStar,
             In0,In1,In2,In3,In4,In5,In6,In7,In8,
             In9,In10,In11,In12,In13,In14,In15,
             Out0,Out1,Out2,Out3,Out4,Out5,Out6,Out7,Out8,
             Out9,Out10,Out11,Out12,Out13,Out14,Out15
             );
  
  input Clk;          //设计的主时钟
  input Reset;       //异步复位
  input FhtStar;     //单步FHT运算控制信号
  input [14:0] In0,In1,In2,In3,In4,In5,In6,In7,In8,In9;
  input [14:0] In10,In11,In12,In13,In14,In15;           //单步FHT运算输入
  output [15:0] Out0,Out1,Out2,Out3,Out4,Out5,Out6,Out7,Out8,Out9;
  output [15:0] Out10,Out11,Out12,Out13,Out14,Out15;  //单步FHT运算输出
  
  //Single FHT calculation
  reg [15:0] Out0,Out1,Out2,Out3,Out4,Out5;
  reg [15:0] Out6,Out7,Out8,Out9,Out10,Out11;
  reg [15:0] Out12,Out13,Out14,Out15;
  //补码运算
  wire [14:0] In8Co =~In8+1;
  wire [14:0] In9Co =~In9+1;
  wire [14:0] In10Co=~In10+1;
  wire [14:0] In11Co=~In11+1;
  wire [14:0] In12Co=~In12+1;
  wire [14:0] In13Co=~In13+1;
  wire [14:0] In14Co=~In14+1;
  wire [14:0] In15Co=~In15+1;
  
  always @(posedge Clk or negedge Reset)
  begin
    if(!Reset)
    begin
      Out0<=0;Out1<=0;Out2<=0;Out3<=0;
      Out4<=0;Out5<=0;Out6<=0;Out7<=0;
      Out8<=0;Out9<=0;Out10<=0;Out11<=0;
      Out12<=0;Out13<=0;Out14<=0;Out15<=0;
    end
    else
    begin
      if(FhtStar)
      begin
      Out0<={In0[14],In0 }+{In8[14],In8 };
      Out1<={In0[14],In0 }+{In8Co[14],In8Co };
      Out2<={In1[14],In1 }+{In9[14],In9 };
      Out3<={In1[14],In1 }+{In9Co[14],In9Co };
      Out4<={In2[14],In2 }+{In10[14],In10 };
      Out5<={In2[14],In2 }+{In10Co[14],In10Co };
      Out6<={In3[14],In3 }+{In11[14],In11 };
      Out7<={In3[14],In3 }+{In11Co[14],In11Co };
      Out8<={In4[14],In4 }+{In12[14],In12 };
      Out9<={In4[14],In4 }+{In12Co[14],In12Co };
      Out10<={In5[14],In5 }+{In13[14],In13 };
      Out11<={In5[14],In5 }+{In13Co[14],In13Co };
      Out12<={In6[14],In6 }+{In14[14],In14 };
      Out13<={In6[14],In6 }+{In14Co[14],In14Co };
      Out14<={In7[14],In7 }+{In15[14],In15 };
      Out15<={In7[14],In7 }+{In15Co[14],In15Co };
      end
    end
  end
  endmodule

评估一下系统的流水线时间余量后,发现整个流水线有16个时钟周期,而FHT模块的频率很高,加法本身仅仅消耗1个时钟周期,加上数据的选择和分配所消耗的时间,也完全能满足系统频率要求,所以将单步FHT运算复用4次,就能大幅度节约所消耗的资源。这种复用单步算法的FHT实现框图如图1-2所示。它由输入选择寄存、单步FHT运算模块、输出选择寄存和计数器构成。

图1-2 FHT运算复用结构图

代码如下。

  //复用单步算法的FHT运算模块
  module wch_fht(Clk,Reset,
             PreFhtStar,
             In0,In1,In2,In3,In4,In5,In6,In7,
             In8,In9,In10,In11,In12,In13,In14,In15,
             Out0,Out1,Out2,Out3,Out4,Out5,Out6,Out7,Out8,
             Out9,Out10,Out11,Out12,Out13,Out14,Out15
             );
  input Clk;            //设计的主时钟
  input Reset;         //异步复位信号
  input PreFhtStar;   //FHT运算指示信号,和上级模块运算关联
  input [11:0] In0,In1,In2,In3,In4,In5,In6,In7;
  input [11:0] In8,In9,In10,In11,In12,In13,In14,In15;    //FHT的16个输入
  output [15:0] Out0,Out1,Out2,Out3,Out4,Out5,Out6,Out7;
  output [15:0] Out8,Out9,Out10,Out11,Out12,Out13,Out14,Out15; //FHT的
16个输出
  
  //FHT输出寄存信号
  reg [15:0] Out0,Out1,Out2,Out3,Out4,Out5,Out6,Out7;
  reg [15:0] Out8,Out9,Out10,Out11,Out12,Out13,Out14,Out15;
  //FHT的中间结果
  wire [15:0] Temp0,Temp1,Temp2,Temp3,Temp4,Temp5,Temp6,Temp7;
  wire [15:0] Temp8,Temp9,Temp10,Temp11,Temp12,Temp13,Temp14,Temp15;
  
  //FHT运算控制计数器,和前一级流水线模块配合
  reg [2:0] Cnt3;//count from 0 to 4,when Reset Cnt3=7;
  reg FhtEn;//Enable fht culculate
  
  always @(posedge Clk or negedge Reset)
  begin
     if (!Reset)
        Cnt3<= #1 3'b111;
     else
     begin
         if(PreFhtStar)
         Cnt3<= #1 3'b100;
         else
         Cnt3<= #1 Cnt3-1;
     end
  end
  always @(posedge Clk or negedge Reset)
  if (!Reset)
     FhtEn<= #1 0;
  else
  begin
       if (PreFhtStar)
       FhtEn<= #1 1;
       if  (Cnt3==1)
       FhtEn<= #1 0;
  end
  
  //补码运算,复制符号位
          assign Temp0=(Cnt3==4)?{4{In0[11]},In0}:Out0;
          assign Temp1=(Cnt3==4)?{4{In1[11]},In1}:Out1;
          assign Temp2=(Cnt3==4)?{4{In2[11]},In2}:Out2;
          assign Temp3=(Cnt3==4)?{4{In3[11]},In3}:Out3;
          assign Temp4=(Cnt3==4)?{4{In4[11]},In4}:Out4;
          assign Temp5=(Cnt3==4)?{4{In5[11]},In5}:Out5;
          assign Temp6=(Cnt3==4)?{4{In6[11]},In6}:Out6;
          assign Temp7=(Cnt3==4)?{4{In7[11]},In7}:Out7;
          assign Temp8=(Cnt3==4)?{4{In8[11]},In8}:Out8;
          assign Temp9=(Cnt3==4)?{4{In9[11]},In9}:Out9;
          assign Temp10=(Cnt3==4)?{4{In10[11]},In10}:Out10;
          assign Temp11=(Cnt3==4)?{4{In11[11]},In11}:Out11;
          assign Temp12=(Cnt3==4)?{4{In12[11]},In12}:Out12;
          assign Temp13=(Cnt3==4)?{4{In13[11]},In13}:Out13;
          assign Temp14=(Cnt3==4)?{4{In14[11]},In14}:Out14;
          assign Temp15=(Cnt3==4)?{4{In15[11]},In15}:Out15;
  
  always @(posedge Clk or negedge Reset)
  begin
  if (!Reset)
  begin
      Out0<=0;Out1<=0;Out2<=0;Out3<=0;
      Out4<=0;Out5<=0;Out6<=0;Out7<=0;
      Out8<=0;Out9<=0;Out10<=0;Out11<=0;
      Out12<=0;Out13<=0;Out14<=0;Out15<=0;
  end
  else
  begin
    if ((Cnt3<=4) && Cnt3>=0 && FhtEn)
      begin
      Out0[15:0]<= #1 Temp0[15:0]+Temp8[15:0];
      Out1[15:0]<= #1 Temp0[15:0]-Temp8[15:0];
      Out2[15:0]<= #1 Temp1[15:0]+Temp9[15:0];
      Out3[15:0]<= #1 Temp1[15:0]-Temp9[15:0];
      Out4[15:0]<= #1 Temp2[15:0]+Temp10[15:0];
      Out5[15:0]<= #1 Temp2[15:0]-Temp10[15:0];
      Out6[15:0]<= #1 Temp3[15:0]+Temp11[15:0];
      Out7[15:0]<= #1 Temp3[15:0]-Temp11[15:0];
      Out8[15:0]<= #1 Temp4[15:0]+Temp12[15:0];
      Out9[15:0]<= #1 Temp4[15:0]-Temp12[15:0];
      Out10[15:0]<= #1 Temp5[15:0]+Temp13[15:0];
      Out11[15:0]<= #1 Temp5[15:0]-Temp13[15:0];
      Out12[15:0]<= #1 Temp6[15:0]+Temp14[15:0];
      Out13[15:0]<= #1 Temp6[15:0]-Temp14[15:0];
      Out14[15:0]<= #1 Temp7[15:0]+Temp15[15:0];
      Out15[15:0]<= #1 Temp7[15:0]-Temp15[15:0];
      end
  
  end
  end
  endmodule

为了便于对比两种实现方式的资源消耗,在Synplify Pro中对两种实现方法分别做了综合。两次综合选用的参数完全一致,出于仅仅考察设计所消耗的寄存器和逻辑资源,Enable“Disable I/O Insertion”选项,不插入IO,取消Synplify Pro中诸如“FSM Compiler”“FSM Explorer”“Resource Sharing”“Retiming”“Pipelining”等综合优化选项。两次综合的结果如图1-3和图1-4所示。

图1-3 未采用复用方案的“fhtpart”模块综合所消耗的资源

图1-4 采用复用方案的“wch_fht”模块综合所消耗的资源

通过对比可以清晰地观察到,采用复用实现方案所占面积约为原方案的1/4,而得到这个好处的代价是,完成整个FHT运算的周期为原来的4倍。这个例子通过运算周期的加长,换取了消耗芯片面积的减少,是前面所述的用频率换面积的一种体现。本例所述“频率换面积”的前提是FHT模块频率较高,运算周期的余量较大,采用4步复用后,仍然能够满足系统流水线设计的要求。如果流水线时序允许,FHT运算甚至可以采用1bit全串行方案实现,该方案所消耗的芯片面积资源更少。

【例1-2】如何使用“面积复制换速度提高”?

举一个路由器设计的例子。假设输入数据流的速率是450Mbit/s,而FPGA上设计的数据处理模块的处理速度最大为150Mbit/s,由于处理模块的数据吞吐量满足不了要求,看来直接在FPGA上实现是一个“不可能完成的任务”。这种情况下,就应该利用“面积换速度”的思想,至少复制3个处理模块,首先将输入数据进行串并转换,然后利用这3个模块并行处理分配的数据,最后将处理结果“并串转换”,完成数据速率的要求。我们在整个处理模块的两端看,数据速率是450Mbit/s,而在FPGA的内部看,每个子模块处理的数据速率是150Mbit/s,其实整个数据的吞吐量的保障是依赖于3个子模块并行处理完成的,也就是说利用占用更多的芯片面积,实现了高速处理,通过“面积的复制换取处理速度的提高”的思想实现了设计。设计的示意框图如图1-5所示。

图1-5 “面积换速度”示意图

上面仅仅是对“面积换速度”思想的一个简单的举例,其实具体操作过程中还涉及很多的方法和技巧,例如,对高速数据流进行串并转换,采用“乒乓操作”方法提高数据处理速率等,希望读者通过平时的应用进一步积累。

1.1.2 硬件原则

硬件原则主要针对HDL代码编写而言。首先应该明确FPGA/CPLD、ASIC的逻辑设计所采用的硬件描述语言(HDL)同软件语言(如C、C++等)是有本质区别的。以Verilog语言为例,虽然Verilog很多语法规则和C语言相似,但是Verilog作为硬件描述语言,它的本质作用在于描述硬件。应该认识到Verilog是采用了C语言形式的硬件的抽象,它的最终实现结果是芯片内部的实际电路,所以评判一段HDL代码的优劣的最终标准是其描述并实现的硬件电路的性能(包括面积和速度两个方面)。评价一个设计的代码水平较高,仅仅是说这个设计由硬件向HDL代码这种表现形式转换得更流畅、合理。而一个设计的最终性能,在更大程度上取决于设计工程师所构想的硬件实现方案的效率及合理性。

初学者,特别是由软件转行的初学者,片面追求代码的整洁、简短,这是错误的,是与评价HDL的标准背道而驰的。正确的编码方法是,首先要做到对所需实现的硬件电路“胸有成竹”,对该部分硬件的结构与连接十分清楚,然后用适当的HDL语句表达出来即可。

硬件原则的另外一个重要理解是“并行”和“串行”的概念。大家知道,一般来说硬件系统比软件系统速度快、实时性高,其中一个重要原因就是硬件系统中各个单元的运算是独立的,信号流是并行的。而C语言编译后,其机器指令在CPU的高速缓冲队列中基本是顺序执行的,即使有一些并行处理的技术,在一定程度上也是十分有限的。所以在写HDL代码的时候,应该充分理解硬件系统的并行处理特点,合理安排数据流的时序,提高整个设计的效率。

另外,Verilog作为一种HDL语言,对系统行为的建模方式是分层次的。比较重要的层次有系统(System)级、算法(Algorithm)级、寄存器传输(RTL)级、逻辑(Logic)级、门(Gate)级和电路开关(Switch)级等。系统级和算法级与C语言更相似,可用的语法和表现形式也更丰富。自RTL级以后,HDL语言的功能就越来越侧重于硬件电路的描述,可用的语法和表现形式的局限性也越大。相比之下,C语言与系统级和算法级Verilog描述更相近一些,而与RTL级、Gate级、Switch级描述从描述目标和表现形式上都有较大的差异。

【例1-3】举例说明RTL级Verilog描述语法和C语言描述语法的区别。

在C语言的描述中,为了使代码执行效率高,表述简洁,经常用到下面的for循环语句。

  for (i=0; i<16; i++)
      DoSomething();

在实际工作中,除了描述仿真测试激励(Testbench)时使用for循环语句外,极少在RTL级编码中使用for循环。其原因是for循环会被综合器展开为所有变量情况的执行语句,每个变量独立占用寄存器资源,每条执行语句并不能有效地复用硬件逻辑资源,造成巨大的资源浪费。在RTL硬件描述中,遇到类似算法,推荐的方式是先搞清楚设计的时序要求,做一个reg型计数器。在每个时钟沿累加,并在每个时钟沿判断计数器情况,做相应的处理,能复用的处理模块尽量复用,即使所有操作都不能复用,也采用case语句展开处理。代码如下。

  reg [3:0] counter;
  always @ (posedge clk)
  if (syn_rst)
      counter <= 4'b0;
  else
      counter <= counter+1;
  always @ (posedge clk)
      begin
           case (counter)
                 4'b0000:
                 4'b0001:
                 ...    ...
                 default:
           endcase
      end

另外,在C语句描述中有if…else和switch条件判断语句,其语法如下。

  if (flag)  // 表示flag为真
  …
  else
  …

switch语句的基本格式如下。

  switch (variable)
  {
  case value1 :   …
  break;
  case value2 :   …
  break;
   …
   default :   …
  break;
  }

两者之间的区别主要在于switch是多分支选择语句,而if语句只有两个分支可供选择。虽然可以用嵌套的if语句来实现多分支选择,但那样的程序冗长难读。

对应Verilog也有if…else语句和case语句,if语句的语法与C语言相似,case语句的语法如下。

  case (var)
        var_value1:
        var_value1:
        ...    ...
        default:
  endcase

姑且不论casex和casez的作用(这两个语句的应用一定要小心,要注意是否可综合),case语句和if…else嵌套描述结构就有很大的区别。在Verilog语法中,if…else if…else语句是有优先级的,一般来说第一个if的优先级最高,最后一个else的优先级最低。如果描述一个编码器,在有些综合器的参数中就有关于优先级编码器硬件原语的选项Priority Encoder Extraction。而case语句是“平行”的结构,所有的case的条件和执行都没有“优先级”。而建立优先级结构(优先级树)会消耗大量的组合逻辑,所以能够使用case语句的地方尽量用case替换if...else结构。

关于这点简单地引申两点:第一,也可以用if...;if…;的结构描述出不带优先级的“平行”条件判断语;第二,随着现在综合工具的优化能力越来越强,大多数情况下可以将不必要的优先级树优化掉。关于if和case语句的更详细的阐释,见后面关于Coding Style的讨论。

1.1.3 系统原则

系统原则包含两个层次的含义:从更高层面上看,是一个硬件系统,一块单板如何进行模块划分与任务分配,什么样的算法和功能适合放在传统FPGA里面实现,什么样的算法和功能适合放在DSP、CPU里面实现,或者在使用内嵌CPU和DSP Block的FPGA中如何划分软硬件功能,以及FPGA的规模估算数据接口设计等;具体到FPGA设计,就要求对设计的全局有个宏观上的合理安排,比如时钟域、模块复用、约束、面积和速度等问题。要知道在系统上复用模块节省的面积远比在代码上“小打小闹”来的实惠多。

一般来说,实时性要求高、频率快的功能模块适合使用FPGA/CPLD实现。而FPGA和CPLD相比,更适合实现规模较大、频率较高、寄存器资源使用较多的设计。使用FPGA/CPLD设计时,应该对芯片内部的各种底层硬件资源和可用的设计资源有一个较深刻的认识。比如FPGA一般触发器资源比较丰富,而CPLD组合逻辑资源更丰富一些,这一点直接影响着两者使用的编码风格。

FPGA基本由可编程输入/输出单元、基本可编程逻辑单元、嵌入式块RAM、丰富的布线资源、底层嵌入功能单元和内嵌专用硬核6部分组成。CPLD的结构相对比较简单,主要由可编程I/O单元、基本逻辑单元、布线池和其他辅助功能模块构成。把握系统原则就要求设计者根据设计类型与资源评估合理地完成器件选型,然后充分发挥所选器件的各个部分的最大性能,对器件整体上有个优化的组合与配置方案。

(1)存储器资源的使用。

存储器资源使用的基本原则是根据设计中用到多少RAM或ROM,确定所选器件的嵌入式Block RAM的容量,并合理配置每块RAM的深度和宽度。特别值得一提的是,Altera的基于20nm工艺的Arria10 器件和基于28nm工艺的高端器件StratixV包含MLAB(640bit)和M20K(20kbit)RAM结构,而基于28nm工艺的中低端器件ArriaV和CycloneV包含MLAB(640bit)和M10K(10kbit)RAM结构。其中,MLAB适合做一些灵活的小块Buffer、FIFO、DPRAM、SPRAM、ROM等;M20K/M10K适用于一般的需求和做大块数据的缓冲区,如在通信的SDH/SONET传输领域,有一些900kbit的大数据包,用M20K/M10K实现其缓冲结构非常方便。不同大小的RAM结构灵活配置,不仅方便了用户,并可以达到最佳的Block RAM利用效率。对于Xilinx和Lattice器件的RAM应用,除了需要掌握其Block RAM的结构,还需注意Xilinx和Lattice FPGA中的LUT可以灵活配置成小的RAM、ROM、FIFO等存储结构,这种技术被称为分布式RAM(Distributed RAM),分布式RAM在实现多块、小容量存储结构时有一定的优势。

(2)硬核的使用。

《Intel FPGA/CPLD设计(基础篇)》第1章中提到未来FPGA的一个发展趋势是越来越多的FPGA产品将包含DSP或CPU等处理核,FPGA将由传统的硬件设计手段逐步过渡为系统级设计工具。例如,当前Altera的StratixV、ArriaV、CycloneV、Arria10等器件族内部集成了DSP Core,而且ArriaV SX系列和Arria10还集成了双核ARM CortexA9处理器。这就为系统设计和单板设计提供了新的解决方案,传统的软硬件联合设计方案中软件部分在某些适当的情况下可以使用内嵌DSP和CPU Block的FPGA取代通用DSP和FPGA,从而简化了单板设计难点,节约了单板面积,提高了单板可靠性。

必须强调的是,目前这类内嵌在FPGA之中的DSP或CPU处理模块的硬件主要由一些加、乘、快速进位链、Pipelining、Mux等结构组成,加上用逻辑资源和块RAM实现的软核部分并不具备传统DSP和CPU的各种译码机制、复杂的通信总线、灵活的中断和调度机制等硬件结构,所以还不是真正意义上的DSP或CPU。在对这类DSP或CPU Block应用时应该注意其结构特点,扬长避短,注意选择合适的应用场合。这种DSP或CPU Block比较适合应用于运算密集的FIR滤波器、编码解码、FFT(快速傅立叶变换)等操作。对于某些应用,通过在FPGA内部实现多个DSP或CPU运算单元并行运算,其工作效率可以达到传统DSP和CPU的几百倍。

FPGA内部嵌入CPU或DSP等处理器,使FPGA在一定程度上具备了实现软硬件联合系统的能力,FPGA正逐步成为SOPC(System On Programmable Chip)的高效设计平台。

(3)串行收发器的使用。

很多高端FPGA内嵌了SERDES以完成高速串行信号的收发。SERDES是SERializer和DESerializer的英文缩写,即串行收发器,顾名思义,它由两部分构成:发端是串行发送单元SERializer,用高速时钟调制编码数据流;接收端为串行接收单元DESerializer,其主要作用是从数据流中恢复出时钟信号,并解调还原数据,根据其功能,接收单元还有一个名称叫时钟数据恢复器(CDR,Clock and Data Recovery)。

目前三大FPGA生产商Altera、Xilinx、Lattice的高端FPGA产品都包含有高速串行收发器的硬核,提供高达3Gbit/s的传输速率,并提供易于使用的设计软件和IP核,使高速传输电路的设计变得简便、可靠。当前Altera Arria10器件含有丰富的serdes硬核资源,片到片连接可以支持到28.03Gbit/s,而过背板连接可以支持到17.4Gbit/s。

SERDES技术的应用很好地解决了高速系统数据传输的瓶颈,节约了单板面积,提高了系统的稳定性,是高速系统设计的强有力支撑。SERDES在高速系统中的应用请参考后面章节“SERDES与高速系统设计”的论述。

(4)其他结构的使用。

其他常用结构的使用大家应该都非常熟悉了,在此仅简单介绍。对于可编程I/O资源,需要根据系统要求合理配置。通常选择I/O的标准有功耗、传输距离、抗干扰性和EMI等。根据设计的速度要求,要合理选择器件的速度等级,并在设计中正确地分频不同速度等级的布线资源与时钟资源。需要提醒读者的是,选择高等级的器件和改善布线资源分配仅仅是提高芯片工作速度的辅助手段,设计速度主要由电路的整体结构、代码的Coding Style等因素决定。善用芯片内部的PLL或DLL资源完成时钟的分频、倍频、移相等操作不仅简化了设计,并且能有效地提高系统的精度和工作稳定性。

《Intel FPGA/CPLD设计(基础篇)》第1章介绍了完整的FPGA/CPLD通用设计流程,这里我们强调一下FPGA系统的规划流程,FPGA系统规划的简化流程如图1-6所示。

图1-6 系统规划的简化流程

对设计整体意义上的模块复用应该在系统功能定义后就初步考虑,并对模块的划分起指导性作用。模块划分非常重要,除了关系到是否最大程度上发挥项目成员的协同设计能力,而且直接决定着设计的综合、实现效果和相关的操作时间,模块划分的具体方法请参考本章1.3节“Altera推荐的Coding Style”中关于模块划分技巧的论述。

对于系统原则做一点引申,简单谈谈模块化设计方法。模块化设计是系统原则的一个很好的体现,它不仅仅是一种设计工具,它更是一种设计思路、设计方法,它是由顶向下、模块划分、分工协作设计思路的集中体现,是当代大型复杂系统的推荐设计方法。目前很多的EDA厂商都提高了模块化设计工具,如Altera Quartus II内嵌的LogicLock,Xilinx ISE的Modular Design工具等。通过这类工具划分每个模块的设计区域,然后单独设计和优化每个模块,最后将每个模块融合到顶层设计中,从而实现了团队协作、并行设计的模块化设计方法。LogicLock支持模块化设计流程、增量设计流程和团队设计流程等设计方法。合理地使用这些方法,能在最大程度上继承以往设计成果,并行分工协作,有效利用开发资源,缩短开发周期。

【例1-4】在系统层次复用模块。

有些文章介绍利用“可编程匹配滤波器”实现WCDMA基站的方案,其核心是在合理规划系统的基础上,合理划分模块并安排操作时序,提高单元模块的复用率,从而大大降低硬件消耗,其设计思想是系统原则的集中体现。可编程匹配滤波器原理框图如图1-7所示。

图1-7 可编程匹配滤波器原理框图

其设计思想是:利用信道固有特点(如信道pilot导频、信道结构等),应用现代可编程数字信号处理的技术(如DSP、FPGA等),采取反馈与控制匹配滤波方式,实现对某信道的已扩信息的自动解扩、解扰。该可编程MF的主要组成部分为本地码发生器、可编程信号MF(S MF)、帧匹配滤波器(FRAME MF)和控制器。本地码发生器可生成各种所需的扩频、加扰序列,可接收控制器的指示脉冲,产生规定的本地解扩、解扰序列,作为S MF的参考序列;S MF是完成匹配滤波的主体,可接收控制器的指示脉冲,将自己的匹配状态切换到下一匹配状态;F MF完成对导频信号等特殊信号(信息比特待选集有限)的检测,生成指示相关峰,通知控制器将S MF切换到下一匹配状态;控制器统一协调各部分工作。这种可编程滤波器可以在如越区切换、同步、CPCH收发信机等多方面应用,如果适当安排时序流程,可以在较大程度上节约硬件资源。

1.1.4 同步设计原则

同步时序设计是FPGA/CPLD设计的重要原则之一。本节在阐述为什么在PLD设计中要采用同步时序设计的基础上重点论述同步时序设计的要点。

一、异步时序设计与同步时序设计

简单比较一下异步电路和同步电路的异同。

(1)异步电路。

•电路的核心逻辑用组合电路实现,比如异步的FIFO/RAM读写信号、地址译码等电路。

•电路的主要信号、输出信号等并不依赖于任何一个时钟性信号,不是由时钟信号驱动FF产生的。

•异步时序电路的最大缺点是容易产生毛刺,在布局布线后仿真和用高分辨率逻辑分析仪观测实际信号时,这种毛刺尤其明显。

•不利于器件移植,这包括器件族之间的移植和从FPGA向结构化ASIC的移植。

•不利于静态时序分析(STA)、验证设计时序性能。

(2)同步时序电路。

•电路的核心逻辑用各种各样的触发器实现。

•电路的主要信号、输出信号等都是由某个时钟沿驱动触发器产生的。

•同步时序电路可以很好地避免毛刺,布局布线后仿真和用高速逻辑分析仪采样实际工作信号皆无毛刺。

•利于器件移植,这包括器件族之间的移植和从FPGA向结构化ASIC的移植。

•有利于静态时序分析(STA)、验证设计时序性能。

早期PLD设计经常使用行波计数器(Ripple Counters)或异步脉冲生成器等典型的异步逻辑设计方式以节约设计所消耗的面积资源。异步逻辑设计的时序正确性完全依赖于每个逻辑元件和布线的延迟,所以其时序约束相对繁杂而困难,并且极易产生亚稳态、毛刺等,造成设计稳定性下降和设计频率不高。我们在《Intel FPGA/CPLD设计(基础篇)》中提到,今后FPGA/CPLD的一个显著发展趋势是低成本器件大行其道,随着FPGA/CPLD的不断经济化,器件资源已经不再成为设计的主要矛盾,而同步时序电路对全面提高设计的频率和稳定性至关重要,从这个层面上讲,尽量使用同步时序电路更加重要。

另一方面,随着FPGA/CPLD的逻辑规模不断扩大,在FPGA/CPLD中完成复杂且质量优良的异步时序设计过于费时费力,其所需调整的时序路径和需要附加的相关约束相当繁琐,异步时序方法是和可编程设计理念背道而驰的。

随着EDA工具的发展,大规模设计的综合、实现的优化效果越来越强。目前大多数综合、实现等EDA工具都是基于时序驱动(Timing Driven)优化策略的。异步时序电路增加了时序分析的难度,需要确定最佳时序路径所需的计算量超出想像,所需时序约束相当繁琐,而且对于异步电路来说,很多综合、实现工具的编译会带来歧义。而对于同步时序设计则恰恰相反,其时序路径清晰,相关时序约束简单明了,综合、实现优化容易,布局布线计算量小。所以目前的可编程逻辑的EDA工具都推荐使用同步时序设计。

综上所述,现代PLD设计推荐采用同步时序设计方式。

二、同步时序设计的注意事项

同步时序设计的基本原则是使用时钟沿触发所有的操作。如果所有寄存器的时序要求(Setup、Hold时间等指标)都能够满足,则同步时序设计与异步时序设计相比,在不同的PVT条件下能获得更佳的系统稳定性与可靠性。

同步设计中,稳定可靠的数据采样必须遵从以下两个基本原则。

•在有效时钟沿到达前,数据输入至少已经稳定了采样寄存器的Setup时间之久,这条原则简称满足Setup时间原则。

•在有效时钟沿到达后,数据输入至少还将稳定保持采样寄存器的Hold时间之久,这条原则简称满足Hold时间原则。

同步时序设计有以下几个注意事项。

•异步时钟域的数据转换。详见本章1.2.4小节“异步时钟域数据同步”。

•组合逻辑电路的设计方法。详见本章1.3.4小节“组合逻辑的注意事项”。

•同步时序电路的时钟设计。详见本章1.3.5小节“时钟设计的注意事项”。

•同步时序电路的延迟。同步时序设计中电路延迟最常用的设计方法是用分频或倍频的时钟或同步计数器完成所需延迟。换句话说,同步时序电路的延时被当作一个电路逻辑来设计。对于比较大的和特殊定时要求的延时,一般用高速时钟产生一个计数器,根据计数器的计数,控制延时;对于比较小的延时,可以用D触发器打一下,这种做法不仅仅使信号延时了一个时钟周期,而且完成了信号与时钟的初次同步,在输入信号采样和增加时序约束余量中使用。另外许多初学者用行为级(Behavioral Level)方法描述延时,如“#5 a<=4'b0101;”这种行为级描述方法常用于仿真测试激励,但是在电路综合时会被忽略,并不能起到延时作用。

•Verilog定义为reg型,不一定综合成寄存器。这点主要是针对一些Verilog初学者而言的,在Verilog代码中最常用的两种数据类型是wire和reg,一般来说,wire型指定的数据和网线通过组合逻辑实现,而reg型指定的数据不一定就是用寄存器实现的。下面的例子就是一个纯组合逻辑的译码器。请大家注意,代码中将输出信号Dout定义为reg型,但是综合与实现结果却没有使用FF,这个电路是一个纯组合逻辑设计。

  module reg_cmb(Reset,
                 CS,
                 Din,
                 Addr,
                 Dout);
  input Reset;           //Asynchronous reset
  input CS;              //Chip select, low effect
  input [7:0] Din;     //Data in
  input [1:0] Addr;    //Address
  output [1:0] Dout;   //Data out
  reg [1:0] Dout;
  always @(Reset or CS or Addr or Din )
  if (Reset)
       Dout = 0;
   else if (!CS)
       begin
            case (Addr)
                  2'b00: Dout = Din[1:0];
                  2'b01: Dout = Din[3:2];
                  2'b10: Dout = Din[5:4];
                default: Dout = Din[7:6];
            endcase
       end
  else
       Dout = 2'bzz;
  endmodule