本质上说,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_state(2)寄存一拍,只需一个寄存器的消耗
(2)时序优化。
从上述同样得出结论,如果是使用某状态cs_state(n)作为其他信号的输入来说,其本身为寄存器信号,因此关键路径就会减少一级。可能运行较快的频率就会增加。如不是独**,对比这两条语句cs_state = 3 与cs_state(3) 一个是组合输出,一个寄存器输出。其不同也就是上述计数与移位寄存器的区别一致。
那么一般状态机会产生的错误会有哪些那?
首先;就是状态不全产生LATCH,前文已述,这是FPGA设计的大敌,解决这个问题的方法可以通过所有分支都设定确定状态,如上例中。有没有更简单的方式?
其此:状态机上述描述,并不直观的显现综合后电路的描述,有没有更直接的rtl的描述,一眼就能看出独热码的特征和好处?
最后:状态机是一个较为成熟技术,还会有哪些值得关注的地方?
这些问题,下节再述。