状态机初识

什么是状态机?

  根据夏宇闻老师的原文:"状态机是CPU的控制核心,用于产生一系列的控制信号,启动或停止某些部件.CPU何时进行读指令来读写I/O端口及RAM区等操作,都是由状态机来控制的.状态机的当前状态,由变量state(状态)记录,state的值就是当前这个指令周期中已经过的时钟数(从零记起). "

 说的简单些,状态机就是以时钟为触发信号,内含状态判断的机制,例如,我们的日常工作学习流程就可以是一个[状态机]:

  早晨7:30~8:30是起床及工作前的准备.这就是一个状态我们可以用一个标志位来代替,而起床这个状态可以由起床闹钟唤醒,这个闹钟即为此状态的判断(闹钟响[起床],闹钟没响[不起床]).

8:30~21:30是工作学习时间,这是另一个状态,此状态由今天是否为工作日决定(为工作日[工作],为休息日[不工作].

21:30~23:00是睡前准备时间,此为又一状态,此状态以是否加班作为判断(不加班[睡前准备],加班[继续工作]).

23:00~7:30即为睡眠时间,此状态可由是否困倦为判断(困了[睡觉],没有困[继续玩耍]).

以上即为一个含判断,以时钟为触发的状态机,此时我们人作为"CPU".

下面写一个简单的fpga状态机:以时钟为触发,以输入字符串"Hello"作为条件,当满足条件时,控制led灯反转.

代码如下:

module hello(Clk, Rst_n, data, led);
	
	input Clk;	//50M
	input Rst_n;	//低电平有效
	
	input [7:0]data;	//8位位宽数据
	
	output reg led;

	// 多个状态定义
	localparam //本地定义(新语法)
		CHECK_H = 5'b0_0001,
		CHECK_e = 5'b0_0010,
		CHECK_la = 5'b0_0100,
		CHECK_lb = 5'b0_1000,
		CHECK_o = 5'b1_0000;
		
	reg [4:0]state;	//状态寄存器
	
	always @(posedge Clk or negedge Rst_n)
	if(!Rst_n)begin
		led <= 1'b1;	//led为1时熄灭
		state <= CHECK_H;
	end
	else begin
		case(state)
		
			CHECK_H:
				if(data == "H")
					state <= CHECK_e;
				else 
					state <= CHECK_H;
					
			CHECK_e:
				if(data == "e")
					state <= CHECK_la;
				else 
					state <= CHECK_H;
					
			CHECK_la:
				if(data == "l")
					state <= CHECK_lb;
				else 
					state <= CHECK_H;
					
			CHECK_lb:
				if (data == "l")
					state <= CHECK_o;
				else 
					state <= CHECK_H;
					
			CHECK_o:
//				if(data == "o")begin
//					state <= CHECK_H;
//					led = ~led;
//				end
//				else 
//					state <= CHECK_H;  
				//改:
				begin
				state <= CHECK_H;
				if(data == "o")
					led = ~led;
				end
			
			default:
				state <= CHECK_H;
				
		endcase
	end
	
endmodule	

 其中,CHECK_H等为逻辑状态(其实就是一个标志位),变量state为状态记录(作为状态值判断),当状态连续命中而最终确认得到字符串"Hello"时,led灯翻转,否则保持原样.RTL Viewer如下:

状态机初识

testbench如下:

`timescale 1ns/1ns
`define clock_period 20

module hello_tb();
	
	reg Clk;
	reg Rst_n;
	
	reg[7:0] data;
	
	wire led;
	

	hello hello0(
	.Clk(Clk), 
	.Rst_n(Rst_n), 
	.data(data), 
	.led(led)
	);
	
	//产生系统时钟
	initial Clk = 1;
	always #(`clock_period/2) Clk = ~Clk;
	
	initial begin
		Rst_n = 0;
		data = 0;
		#(`clock_period*20) Rst_n = 1;
		#(`clock_period*50)
		forever begin
			data = "i";
			#(`clock_period)
			data = "b";
			#(`clock_period)
			data = "H";
			#(`clock_period)
			data = "e";
			#(`clock_period)
			data = "i";
			#(`clock_period)
			data = "d";
			#(`clock_period)
			data = "i";
			#(`clock_period)
			data = "d";
			#(`clock_period)
			data = "H";
			#(`clock_period)
			data = "E";
			#(`clock_period)
			data = "i";
			#(`clock_period)
			//one Hello
			data = "H";
			#(`clock_period)
			data = "e";
			#(`clock_period)
			data = "l";
			#(`clock_period)
			data = "l";
			#(`clock_period)
			data = "o";			
			#(`clock_period)
			//two Hello
			data = "H";
			#(`clock_period)
			data = "e";
			#(`clock_period)
			data = "l";
			#(`clock_period)
			data = "l";
			#(`clock_period)
			data = "o";
			#(`clock_period)
			
			data = "d";
			#(`clock_period)
			data = "H";
			#(`clock_period)
			data = "E";
			#(`clock_period)
			data = "i";

		end
		
		
		
	end

endmodule

因为随机生成"Hello"的机会渺茫,所以人为定义了两个,让这组data输入一直产生,在仿真设置里设置10ns结束,得到波形如下:

状态机初识

放大一个输出段:

 状态机初识

可以看到一个led翻转发生在第一个"Hello"完成时,第二个翻转为紧跟的第二个"Hello"输入完成时,实现了利用"Hello"输入控制led灯翻转的状态机设计 .

为了便于观察,可在data信号产生前的延迟+1,仿真效果如下:

状态机初识

可以看到led在"hello"中"o"的下一个 时钟沿才发生翻转,即捕获到"o"信号之后发生翻转.

以上即为状态机的内容,作为初识,我认为状态机可以理解为一系列的case,当条件满足时,CPU执行特定的任务:)