FSM之三--代码风格

FSM设计之一http://www.cnblogs.com/qiweiwang/archive/2010/11/28/1890244.html

  Moore型状态机与mealy型状态机相比,由于其状态输出仅与当前状态有关,而与输入无关,所以它可以避免由输入信号引起的毛刺,因此建议采用Moore型状态机。但是在实际的应用中,我们只需要对状态输出进行寄存,即在outputs后面加上一级输出寄存,就可以有效地避免毛刺的传播。

  Binary、gray-code编码使用最少的触发器,较多的组合逻辑。而one-hot 编码反之。由于CPLD更多的提供组合逻辑资源,而FPGA更多的提供触发器资源,所以CPLD 多使用gray-code,而FPGA 多使用one-hot编码。 另一方面,对于小型设计使用gray-code和binary编码更有效,而大型状态机使用one-hot更高效。 
   看synplicity的文档,推荐在24个状态以上会用格雷码,在5~24个状态会用独热码,在4个状态以内用二进制码,肯定独热码比二进制码在实现FSM部分会占更多资源,但是译码输出控制简单,所以如果状态不是太多,独热码较好。状态太少译码不会太复杂,二进制就可以了。状态太多,前面独热码所占资源太多,综合考虑就用格雷码了。

编码风格: 
1.  避免生成锁存器 
  一个完备的状态机(健壮性强)应该具备初始化(reset)状态和默认(default)状态。 当芯片加电或者复位后,状态机应该能够自动将所有判断条件复位,并进入初始化状态。需要注
明的一点是,大多数FPGA有GSR(Global Set/Reset)信号,当FPGA加电后,GSR信号拉高,对所有的寄存器,RAM等单元复位/置位,这时配置于FPGA的逻辑并未生效,所以不能保证正确的进入初始化状态。所以使用 GSR进入FPGA的初始化状态,常常会产生种种不必一定的麻烦。一般简单方便的方法是采用异步复位信号,当然也可以使用同步复位,但是要注意同步复位的逻辑设计。 
   状态机也应该有一个默认(default)状态,当转移条件不满足,或者状态发生了突变时,要能保证逻辑不会陷入“死循环”。这是对状态机健壮性的一个重要要求,也就是常说的要具备“自恢复”功能。对应于编码就是对 case,if-else 语句要特别注意,要写完备的条件判断语句。VHDL 中,当使用CASE语句的时候,要使用“When Others”建立默认状态。使用“IF...THEN...ELSE”语句的时候,要用在“ELSE”指定默认状态。 Verilog中, 使用“case”语句的时候要用“default”建立默认状态, 使用“if...else”语句的注意事项相似。 
     另外有一个技巧:大多数综合器都支持 Verilog 编码状态机的完备状态属性--“full case”。

2.  参数定义用 parameter 
  状态的定义用 parameter 定义,不推荐使用`define 宏定义的方式,因为‘define 宏定义在编译时自动替换整个设计中所定义的宏,而 parameter 仅仅定义模块内部的参数,定义的参数不会与模块外的其他状态机混淆。

3.  一定要使用”<=”非阻塞赋值方式 
  采用非阻塞赋值方式消除很多竞争冒险的隐患。

复制代码
module gray
(
input clk,
input rst_n,
input a,
outputreg y
);
//
parameter s0 =2'b00,
s1 =2'b01,
s2 =2'b10,
s3 =2'b11;
//
reg [1:0] current_state;
reg [1:0] next_state;
//状态转移
always @ (posedge clk,negedge rst_n)
begin
if(!rst_n)
current_state <= s0;
else
current_state <= next_state;
end
//状态译码逻辑
always @ (a or current_state)
begin
case(cs)
s0:
if(a==1'b1)
next_state =s1;
else
next_state=s0;
s1:
next_state = s2;
s2:
next_state = s3;
s3:
next_state = s0;
default:
next_state =s0;
endcase
end
//状态寄存输出
always @ (posedge clk,negedge rst_n)
begin
if(!rst_n)
y <=1'b0;
elseif(next_state==s3)//输出与next_state有关
y <=!a;
else
y <=1'b0;
end

endmodule
复制代码

one-hot编码

  one-hot顾名思义,指任何时候一个nbit的寄存器组中有且仅有1bit为1,其他n-1个寄存器值均为0。在状态机编码的范畴中,它就像是“一个萝卜一个坑”,每个状态都占用一个寄存器,该位为1时表示状态机进入该状态,为0时表示状态机不在该状态。因此,每个状态只用1bit即可表示出来。在进入一个新状态时,将原状态对应的寄存器位置0、新状态对应的寄存器位置1,以保证“one-hot”。

  在电路面积和速度方面,one-hot编码方式所占用的DFF资源要比binary编码多一些,n个状态要用n个DFF;但是这种编码方式由在状态译码的时候只需要用1bit参与状态译码,译码逻辑较小,生成的组合逻辑相应的较小。总体来说,在FPGA设计中,采用one-hot编码方式往往消耗的资源要比binary编码少。

  那么为什么one-hot编码方式在译码的时候只需要有1bit参与译码呢?我们知道,需要多少位参与状态译码和状态的表示方式很有关系,也就是说和何种方式去标识一个状态很有关系,如果一个状态用4bit表示,那么它在译码的时候就要用4bit,但是如果只有一个状态用1bit表示,则它参与译码时只需要1bit即可,所以,one-hot这种“一个萝卜一个坑”的结构决定了它在译码时每个状态只需要用一位参与译码。

  one-hot编码方式通常分为两类:一种是非index编码方式;一种是index编码方式。

复制代码
module not_index_fsm
(
input clk,
input rst_n,
input [1:0] a,
output y
);
//
parameter U_DLY =1;
parameter s0 =10'b00_0000_0001,
s1 =10'b00_0000_0010,
s2 =10'b00_0000_0100,
s3 =10'b00_0000_1000,
s4 =10'b00_0001_0000,
s5 =10'b00_0010_0000,
s6 =10'b00_0100_0000,
s7 =10'b00_1000_0000,
s8 =10'b01_0000_0000,
s9 =10'b10_0000_0000;
//
reg [9:0] current_state;
reg [9:0] next_state;
reg [5:0] cnt;
//
always @ (posedge clk,negedge rst_n)
begin
if(!rst_n)
current_state <= s0;
else
current_state <= #U_DLY next_state;
end
//
always @ (a,cnt,current_state)
begin
case(current_state)
s0:
case(a)
2'b00: next_state = s1;
2'b01: next_state = s2;
2'b10: next_state = s3;
2'b11: next_state = s4;
endcase
s1:
next_state = s9;
s2:
next_state = s5;
s3:
next_state = s6;
s4:
next_state = s8;
s5:
next_state = s6;
s6:
next_state = s7;
s7:
begin
if(cnt==6'h2f)
next_state = s9;
else
next_state = s7;
end
s8:
next_state = s9;
s9:
begin
if(cnt ==6'h3f)
next_state = s0;
else
next_state = s9;
end
default:
next_state = s0;
endcase
end
//
always @ (posedge clk,negedge rst_n)
begin
if(!rst_n)
cnt <=6'b0;
elseif(next_state == s9)
cnt <= #U_DLY cnt+1'b1;
else
cnt <= #U_DLY 6'b0;
end

assign y = current_state[8];

endmodule
复制代码

 index编码方式:

复制代码
module index_fsm
(
input clk,
input rst_n,
input [1:0] a,
output y
);
//
parameter U_DLY =1;
parameter s0 =0,
s1 =1,
s2 =2,
s3 =3,
s4 =4,
s5 =5,
s6 =6,
s7 =7,
s8 =8,
s9 =9;
//
reg [9:0] current_state;
reg [9:0] next_state;
reg [5:0] cnt;
//
always @ (posedge clk,negedge rst_n)
begin
if(!rst_n)
state <=10'b0;
        state[s0] <= 1'b1;//because the coding of IDLE is 10'b1
else
current_state <= #U_DLY next_state;
end
//
always @ (a,cnt,current_state)
begin
next_state <=10'b0;//给next_state这样赋默认值综合后不会出现锁存器
case(1'b1)//synthesis parallel_case full_case
current_state[s0]:
case(a)
2'b00: next_state[s1] = 1'b1;
2'b01: next_state[s2] = 1'b1;
2'b10: next_state[s3] = 1'b1;
2'b11: next_state[s4] = 1'b1;
endcase
current_state[s1]:
next_state[s9] =1'b1;
current_state[s2]:
next_state[s5] =1'b1;
current_state[s3]:
next_state[s6] =1'b1;
current_state[s4]:
next_state[s8] =1'b1;
current_state[s5]:
next_state[s6] =1'b1;
current_state[s6]:
next_state[s7] =1'b1;
current_state[s7]:
begin
if(cnt==6'h2f)
next_state[s9] =1'b1;
else
next_state[s7] =1'b1;
end
current_state[s8]:
next_state[s9] =1'b1;
current_state[s9]:
begin
if(cnt ==6'h3f)
next_state[s0] =1'b1;
else
next_state[s9] =1'b1;
end
endcase
end
//
always @ (posedge clk,negedge rst_n)
begin
if(!rst_n)
cnt <=6'b0;
elseif(next_state[s9] ==1'b1)
cnt <= #U_DLY cnt+1'b1;
else
cnt <= #U_DLY 6'b0;
end

assign y = current_state[s8];//在使用one-hot状态机某些情况下,可以将状态信号直接作为输出,从而节约逻辑资源,提供电路速度

endmodule
复制代码

 由上面的例子分析可知,非index编码的状态机在状态译码时使用了10位参与状态译码,index编码时只用了一位参与状态译码。而由前面的分析知道,one-hot编码的本质就是一个状态用一位表示,因此,index编码才能真正地反映出one-hot的本质。

  非index编码的状态机由于译码逻辑过多,在消耗资源级Fmax这两个指标上都不如index编码,因此,在使用one-hot编码时,推荐使用index编码的方式。

原文地址:https://www.cnblogs.com/lueguo/p/3345232.html