FPGA研发之道(23)-控制(上)

本质上说,FPGA的模块设计就是将输入转化成想要得到的输出结果。而除了某些简单模块,即在当拍内完成,即将输入进行逻辑操作后,再输出。(如简单加法器等)。其余大部分的设计需要通过时序逻辑和组合逻辑混合实现,时序逻辑带来就是延迟起效的问题,举例说,如实现某个信号(start)起效后,接下来五个周期需要分别进行五种操作,分别是op0,op1,op2,op3,op4 等等。如何进行控制,这就是每个工程师要面对的问题。

   对于简单控制,分别可以采用计数和移位寄存器的方式来解决问题。而对于较为复杂的控制,则需要设计状态机来解决。下面将分别介绍

  计数器: 对于上述操作来说,start起效后,可以通过计数实现,设置寄存器count[2:0],有效信号开始时计数自加。 计数的方式带来的问题就是,计数从零开始还是从1开始,假如计数器初始化为0,则从0-4状态可以分别输出op0,op1,op2,op3,op4,但是在无有效信号时,计数会保持0,从而造成op0的输出。 上述举例虽然简单,但是确实很多初学者或者工程师在仿真时会经常会犯的错误。从设计来说,计数需要考虑初始值对于输出的影响。同样计数带来的另一个问题就是,从零开始的计数会导致设计与实际不一致,例如,一个信号9拍后拉低,但从零计数到8时,已经到9拍了(0-8),这种设计会导致命名count==8 9拍存在不一致的现象。当然也可以从1计数到9,这样状态在count==9时触发。这样就会初始化需要复位寄存器为1。当然这个问题大端和小端的争斗一样,没有终点。一个设计中如果多种计数来驱动计数的话,就需要特别小心这个问题计数。当然也可把问题交给仿真器,仿真时根据波形调整,计数的状态。

    移位寄存器:如采用移位寄存器,根据上述例子,则start信号有效后,设计5bit的移位寄存器flag[4:0]分别利用寄存器的某BIT来控制输出,从而在每BIT有效时,分别输出op0,op1,op2,op3,op4。假设此种状态较少,FPGA寄存器资源较为丰富,因此利用移位寄存器是一个不错的注意。

   assign op4 = ( count == 3’b100) ;

   assign op4 = flag[4] 

  比较上述两种输出,则可以看出,通过计数的方式占用输出资源较多,而移位寄存器在此种应用下,占用逻辑就相对简单。(仅针对小规模的计数来说,对于超过16的计数,则使用计数器更优)。另外,通过移位寄存器可以方便的进行时序控制,不用纠结从零开始还是从1开始的问题,在某些简单的处理下能够达到更小的面积和更快的时序。

   对于复杂的控制,则状态机,就是必须的。对于FPGA实现状态机,其实并不需要那么多的设计的方法。主要就是两个要点。(1)独热码。(2)三段式。

   对于第一点来说,独热码,因为FPGA内部寄存器资源较多,另外独热码将会带来额外的面积和时序优化的好处。则以上述例子为例,增加状态转移的触发信号,状态转移图如下所示:

   

 状态独热码(也可以用define  localparam)建议使用parameter或者localparam

 parameter     idle == 6’b000001,

             op0_state == 6’b000010,

             op1_state == 6’b000100,

             op2_state == 6’b001000,

             op3_state == 6’b010000,

             op4_state == 6’b100000;

 三段式结构如下

//1)当前状态

always@(posedge sys_clk or negedge rst_n)

       if(!rst_n)

         cs_state <= idle;

       else

         cs_state <= ns_state;

        

//2)下一状态的赋值

always@(*)

      case(cs_state)     

         idle : if(start)

                  ns_state =  op0_state;

                else

                  ns_state =  idle; 

                   

         op0_state :

                if(op0_over)

                  ns_state =  op1_state;

                else

                  ns_state =  op0_state;

                 

         op1_state :

                if(op1_over)

                  ns_state =  op2_state;

                else

                  ns_state =  op1_state;

         op2_state :

                if(op2_over)

                  ns_state =  op3_state;

                else

                  ns_state =  op2_state;

         op3_state :

                if(op3_over)

                  ns_state =  op4_state;

                else

                  ns_state =  op3_state;

         op4_state :

                if(op4_over)

                  ns_state =  op4_state;

                else

                  ns_state =  idle;

         default ns_state = idle;

        

       endcase

      

//3)输出状态

assign out1 = (cs_state ==  op0_state);

always@(posedge sys_clk or negedge rst_n)

       if(!rst_n)

         out2_reg <= 1'b0;

       else if (cs_state ==  op2_state)

         out2_reg <= 1'b1;

       else

         out2_reg <= 1'b0;

   上述例子,介绍独热码和三段式。三段式的好处不用说,就是逻辑清楚。可以看出out1输出为组合输出。out_2_reg为寄存输出。那么独热码在FPGA内部的优势又有哪些?

1)综合后,逻辑简单

         例如assign out1 = (cs_state ==  op0_state); 综合后的电路等同于

assign out1= cs_state(0) ;//可以看出无逻辑消耗

              out2_reg 的电路等同于 cs_state2)寄存一拍,只需一个寄存器的消耗

     2)时序优化。

          从上述同样得出结论,如果是使用某状态cs_state(n)作为其他信号的输入来说,其本身为寄存器信号,因此关键路径就会减少一级。可能运行较快的频率就会增加。如不是独**,对比这两条语句cs_state = 3 cs_state(3) 一个是组合输出,一个寄存器输出。其不同也就是上述计数与移位寄存器的区别一致。

  那么一般状态机会产生的错误会有哪些那?

    首先;就是状态不全产生LATCH,前文已述,这是FPGA设计的大敌,解决这个问题的方法可以通过所有分支都设定确定状态,如上例中。有没有更简单的方式?

    其此:状态机上述描述,并不直观的显现综合后电路的描述,有没有更直接的rtl的描述,一眼就能看出独热码的特征和好处?

    最后:状态机是一个较为成熟技术,还会有哪些值得关注的地方?

     这些问题,下节再述。

你可能感兴趣的:(FPGA开发)