试验三 状态机应用实验

实验原理

状态机设计可以称得上是HDL 设计里面的精华,几乎所有的设计里面都或多或少
地使用了状态机的思想。状态机顾名思义,就是一系列状态组成的一个循环机制,这样
的结构使得编程人员能够更好地使用HDL语言,同时具有特定风格的状态机也能提高
程序的可读性和调试性。
状态机的设计有很多要素,重点的几个如下:
● 状态机的编码。Biary、gray-code编码使用最少的触发器,较多的组合逻辑。而
one-hot 编码反之。由于CPLD更多的提供组合逻辑资源,而FPGA更多的提供
触发器资源,所以CPLD 多使用gray-code,而FPGA 多使用one-hot 编码。另
一方面,对于小型设计使用gray-code和binary编码更有效,而大型状态机使用
one-hot 更高效。
● 关于FSM的编码方法。FSM分两大类:米勒型和摩尔型。组成要素有输入(包
括复位),状态(包括当前状态的操作),状态转移条件,状态的输出条件。
设计FSM 的方法和技巧多种多样,但是总结起来有两大类:第一种,将状
态转移和状态的操作和判断等写到一个模块(process、block)中。另一种是将
状态转移单独写成一个模块,将状态的操作和判断等写到另一个模块中(在
Verilog代码中,相当于使用两个“always” block)。其中较好的方式是后者。其
原因如下。

首先FSM 和其他设计一样,最好使用同步时序方式设计,好处不再累述。
而状态机实现后,状态转移是用寄存器实现的,是同步时序部分。状态的转移
条件的判断是通过组合逻辑判断实现的,之所以第二种比第一种编码方式合理,
就在于第二种编码将同步时序和组合逻辑分别放到不同的程序块(process,
block)中实现。这样做的好处不仅仅是便于阅读、理解、维护,更重要的是利
于综合器优化代码,利于用户添加合适的时序约束条件,利于布局布线器实现
设计。
● 初始化状态和默认状态。
一个完备的状态机(健壮性强)应该具备初始化状态和默认状态。当芯片加
电或者复位后,状态机应该能够自动将所有判断条件复位,并进入初始化状态。
需要注明的一点是,大多数FPGA 有GSR(Global Set/Reset)信号,当FPGA
加电后,GSR 信号拉高,对所有的寄存器,RAM 等单元复位/置位,这是配置
于FPGA的逻辑并未生效,所以不能保证正确的进入初始化状态。所以使用GSR
企图进入FPGA 的初始化状态,常常会产生种种不必一定的麻烦。一般的方法
是采用异步复位信号,当然也可以使用同步复位,但是要注意同步复位的逻辑
设计。解决这个问题的另一种方法是将默认的初始状态的编码设为全零,这样
GSR复位后,状态机自动进入初始状态。
令一方面状态机也应该有一个默认(default)状态,当转移条件不满足,或
者状态发生了突变时,要能保证逻辑不会陷入“死循环”。这是对状态机健壮性
的一个重要要求,也就是常说的要具备“自恢复”功能。对应于编码就是对case,
if-else 语句要特别注意,要写完备的条件判断语句。VHDL中,当使用CASE
语句的时候,要使用“When Others”建立默认状态。使用“IF...THEN...ELSE”语句
的时候,要用在“ELSE”指定默认状态。Verilog 中,使用“case”语句的时候要用
“default”建立默认状态,使用“if...else”语句的注意事项相似。
另外提一个技巧:大多数综合器都支持Verilog 编码状态机的完备状态属性
--“full case”。这个属性用于指定将状态机综合成完备的状态,如Synplicity
的综合工具(Synplify/Synplify Pro,Amplify,etc)支持的命令格式如下:

case (current_state) // synthesis full_case
2’b00 : next_state <= 2’b01;
2’b01 : next_state <= 2’b11;
2’b11 : next_state <= 2’b00;
//这两段代码等效
case (current_state)
2’b00 : next_state <= 2’b01;
2’b01 : next_state <= 2’b11;
2’b11 : next_state <= 2’b00;
default : next_state <= 2bx;
● 状态机的定义可以用parameter定义,但是不推荐使用`define 宏定义的方式,因
为‘define 宏定义在编译时自动替换整个设计中所定义的宏,而parameter仅仅定
义模块内部的参数,定义的参数不会与模块外的其他状态机混淆。
● 在编写状态机的时候最好将状态机的状态转移和每个状态里面要做的工作分开
在两个或者多个always 子块里面编写,这样写比较易读,以后也好修改,具体
写法如下(推荐使用三段式FSM描述方法):
always @ ( posedge clk or negedge rst_n )
if( !rst_n )
state <= 2’b00 ;
else
state <= next_state ;
always @ ( posedge clk or negedge rst_n )
if( !rst_n )
next_state <= 2’b00 ;
else
case ( state )
2’b00:begin if(en) next_state<=2’b01; else next_state<=state; end
2’b01:begin if(en) next_state<=2’b10; else next_state<=state; end
2’b10:begin if(en) next_state<=2’b11; else next_state<=state; end
2’b11:begin if(en) next_state<=2’b00; else next_state<=state; end
default:state<=2’b00;
endcase
always @ ( posedge clk or negedge rst_n )
if( !rst_n )
dout<=4’b0000;
else
case ( state )
2’b00:dout<=4’b0001;
2’b01:dout<=4’b0011;

2’b10:dout<=4’b0111;
2’b11:dout<=4’b1111;
default:dout<=4’b0000;
endcase
上面的状态机描述使用的是三段式描述方法,一个 always 块负责将next_state赋值
给state,一个always 块负责判断触发并产生next_state,另外一个always块里面描述每
个state步骤里面状态机要完成的工作。这样做有很多优点:结构简单,利于时序约束,
无组合逻辑输出,利于控制综合,代码的可靠性和可维护度最高。
ISE 还给用户提供了一种特别的输入方法:状态机输入方法。这种方法由于输入相
对复杂,使用范围十分有限。

module state_machine(clk,rst_n,
		dout0,dout1,dout2,dout3,dout4,dout5,dout6,dout7
					);

	input         clk,rst_n ;
	
	output        dout0,dout1,dout2,dout3,dout4,dout5,dout6,dout7 ;

	parameter     STATE0	 =	 3'b000  ,
					  STATE1  =	 3'b001  ,
					  STATE2	 =	 3'b010  ,
					  STATE3	 =	 3'b011  ,
					  STATE4	 =	 3'b100  ,
					  STATE5	 =	 3'b101  ,
					  STATE6	 =	 3'b110  , 
					  STATE7	 =	 3'b111  ;

	wire          dout0,dout1,dout2,dout3,dout4,dout5,dout6,dout7 ;
	reg   [7 :0 ] data ;
	reg   [23:0 ] cnt ;
	reg   [2 :0 ] state ;

	wire          clk_slow ;

	assign        {dout7,dout6,dout5,dout4,dout3,dout2,dout1,dout0} = data ;
	assign        clk_slow = cnt[23] ;

	always @ ( posedge clk or negedge rst_n )
		if( !rst_n )
			cnt<=24'b0000_0000_0000_0000_0000_0000;
		else
			cnt<=cnt+24'b0000_0000_0000_0000_0000_0001;

	always @ ( posedge clk_slow or negedge rst_n )
		if( !rst_n )
			state<=STATE0;
		else
			case( state )
				STATE0:state<=STATE1;
				STATE1:state<=STATE2;
				STATE2:state<=STATE3;
				STATE3:state<=STATE4;
				STATE4:state<=STATE5;
				STATE5:state<=STATE6;
				STATE6:state<=STATE7;	
				STATE7:state<=STATE0;
				default:state<=STATE0;
			endcase

	always @ ( posedge clk_slow or negedge rst_n )
		if( !rst_n )
			data<=8'b0000_0000;
		else
			case( state )
				STATE0:data<=8'b0000_0001;
				STATE1:data<=8'b0000_0010;
				STATE2:data<=8'b0000_0100;
				STATE3:data<=8'b0000_1000;
				STATE4:data<=8'b0001_0000;
				STATE5:data<=8'b0010_0000;
				STATE6:data<=8'b0100_0000;	
				STATE7:data<=8'b1000_0000;
				default:data<=8'b0000_0000;
			endcase

endmodule

 

你可能感兴趣的:(编程,工作)