提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


文章目录

  • 前言
  • 一、从一个实例来认识状态机
  • 二、稍微复杂点的状态机



前言

状态机全称是有限状态机(Finite State Machine、FSM),也称同步有限状态机,是表示有限个状态以及在这些状态之间的转移和动作等行为的数学模型。本文对状态机相关概念的学习,并使用FPGA设计的状态机实现特定字符串的检测。


一、从一个实例来认识状态机

有这样一个可乐机,需要投入三枚硬币才能吐出一瓶可乐。

这就涉及到三个部分:1.输入:投入一元硬币;2.输出:可乐机吐出可乐/可乐机不吐出可乐;3.状态:投入0/1/2/3枚一元硬币。

如下图1所示状态转移图,分为3个状态:IDIE,无投入硬币;ONE,投入一枚硬币;TWO,投入两枚硬币。(投入3枚硬币后,吐出可乐,回到无投入硬币状态)。x/x:左边x为1表示投入硬币,0表示无投入硬币;右边x为0表示可乐机没有吐出可乐,为1 表示吐出可乐。

状态机的经典java代码 状态机实例_状态机


图1 可乐机状态转移图 建立可乐机工程。

状态机的经典java代码 状态机实例_sed_02


图2 可乐机示意图

这里给出波形图。

状态机的经典java代码 状态机实例_状态机的经典java代码_03


图3 可乐机波形图 Pi_money高电平表示投入硬币,当Pi_money处于高电平时,在下一个时序state发生改变。 Po_cola高电平表示吐出可乐。

状态机描述方式可分为一段式、二段式和三段式。
一段式:整个状态机写到一个always模块里面。在该模块中既描述状态转移,又描述状态的输入和输出。
二段式:用两个always模块来描述状态机。其中一个always模块采用同步时序描述状态转移,另一个模块采用组合逻辑判断状态转移条件,描述状态转移规律及其输出。
三段式:在两个always模块描述方式基础上,使用三个always模块。一个always模块采用同步时序描述状态转移,另一个模块采用组合逻辑判断判断状态转移条件,描述状态转移规律,另一个always模块描述状态输出(可以用组合电路输出,也可以时序电路输出)。

这里采用二段式进行编程描述。

module	simple_fsm
(
	input	wire		clk		,
	input	wire		rst_n	,
	input	wire		Pi_money,
	
	output	reg			Po_cola	
	);
	
parameter	IDIE	=	3'b001;
parameter	ONE		=	3'b010;
parameter	TWO		=	3'b100;
	
reg		[2:0]	state;

//描述状态转移
always@(posedge clk or negedge rst_n)
	if(rst_n == 0)
		state <= IDIE;
	else	case(state)
		IDIE:	if(Pi_money == 1)
					state	<= ONE;
				else
					state	<= IDIE;
		ONE:	if(Pi_money == 1)
					state	<= TWO;
				else
					state	<= ONE;
		TWO:	if(Pi_money == 1)
					state	<= IDIE;
				else
					state	<= TWO;		
		default:state	<= IDIE;
	endcase
	
//判断状态转移条件
always@(posedge clk or negedge rst_n)
	if(rst_n == 0)
		Po_cola <= 0;
	else	if((state == TWO) && (Pi_money == 1))
		Po_cola <= 1;
	else
		Po_cola <= 0;
	
endmodule

建立仿真验证工程。

`timescale 1ns/1ns
module  simple_fsm_tb();

reg clk;
reg rst_n;
reg Pi_money;

wire Po_cola;

initial
	begin
		clk	= 1;
		rst_n <= 0;
		#20
		rst_n <= 1;
	end
	
always #10 clk = ~clk;

//随机投入硬币
always@(posedge clk or negedge rst_n)
	if(rst_n == 0)
		Pi_money  <= 0;
	else
		Pi_money  <= {$random} % 2;
		
wire  [2:0] state = simple_fsm_inst.state;
		
initial
	begin
		$timeformat(-9,0,"ns",6);//设置时间格式
		$monitor("@time %t:Pi_money = %b,state = %b,Po_cola = %b",$time,Pi_money,state,Po_cola);//打印
		
	end

simple_fsm simple_fsm_inst
(
.clk	(clk),
.rst_n	(rst_n),
.Pi_money(Pi_money),
.Po_cola(Po_cola)
);
endmodule

查看仿真验证。

状态机的经典java代码 状态机实例_状态机的经典java代码_04


在第二个时钟信号时,Pi_money处于高电平,表示投入一枚一元硬币,在下个时钟序列,state发生改变,随后一直投入硬币(Pi_money一直为高电平),当state处于“2”状态,继续投入硬币,在下一个时钟序列可乐机吐出可乐,Po_cola为高电平,验证完毕。

二、稍微复杂点的状态机

现在跟上一章的可乐机增加点难度:可乐机现在需要2.5元一瓶。因此假设人现在可以投币的选项为投入0.5元和1元。设计复杂可乐机工程示意图如下:

状态机的经典java代码 状态机实例_状态机_05


输入:Pi_money_half表示是否投入0.5元硬币;Pi_money_one表示是否投入1元硬币.

输出:Po_cola表示是否吐出可乐;Po_money表示是否找零。

状态:0、0.5、 1、 1.5、 2、 2.5、 3.

输入端有三种情况,以00、 01、 10分别表示不投硬币、投入0.5元硬币、投入1元硬币;输出端也有三种情况,以00、 10、 11分别表示不出可乐/不找零、出可乐/不找零、出可乐/找零。

构造复杂可乐机的状态转移图。

状态机的经典java代码 状态机实例_状态机的经典java代码_06


同样采用二段式进行描述

module	complex_fsm
(
	input	wire		clk		,
	input	wire		rst_n	,
	input	wire		Pi_money_half,
	input	wire		Pi_money_one,
	
	output	reg			Po_money,
	output	reg			Po_cola	
	
	);
//独热码定义状态	
parameter	IDIE		=	5'b00001;
parameter	HALF		=	5'b00010;
parameter	ONE			=	5'b00100;
parameter	ONE_HALF	=	5'b01000;
parameter	TWO			=	5'b10000;
	
wire	[1:0]	Pi_money; //定义组合逻辑
reg		[4:0]	state;

assign	Pi_money = {Pi_money_one, Pi_money_half};

//描述状态转移
always@(posedge clk or negedge rst_n)
	if(rst_n == 0)
		state <= IDIE;
	else	case(state)
		IDIE:	if(Pi_money == 2'b01)
					state	<= HALF;
				else if(Pi_money == 2'b10)
					state	<= ONE;
				else
					state  	<= IDIE;
		HALF:	if(Pi_money == 2'b01)
					state	<= ONE;
				else if(Pi_money == 2'b10)
					state	<= ONE_HALF;
				else
					state  	<= HALF;
		ONE:	if(Pi_money == 2'b01)
					state	<= ONE_HALF;
				else if(Pi_money == 2'b10)
					state	<= TWO;
				else
					state  	<= ONE;	
		ONE_HALF:	if(Pi_money == 2'b01)
					state	<= TWO;
				else if(Pi_money == 2'b10)
					state	<= IDIE;
				else
					state  	<= ONE_HALF;	
		TWO:	if(Pi_money == 2'b01)
					state	<= IDIE;
				else if(Pi_money == 2'b10)
					state	<= IDIE;
				else
					state  	<= TWO;			
		default:state	<= IDIE;
	endcase
	
//判断状态转移条件
always@(posedge clk or negedge rst_n)
	if(rst_n == 0)
		Po_cola <= 0;
	else	if((state == ONE_HALF) && (Pi_money == 2'b10)
				||(state == TWO) && (Pi_money == 2'b01)
				||(state == TWO) && (Pi_money == 2'b10))
		Po_cola <= 1;
	else
		Po_cola <= 0;
		
always@(posedge clk or negedge rst_n)
	if(rst_n == 0)
		Po_money <= 0;
	else	if((state == TWO) && (Pi_money == 2'b10))
		Po_money <= 1;
	else
		Po_money <= 0;
	
endmodule

建立仿真

`timescale 1ns/1ns
module  complex_fsm_tb();

reg clk;
reg rst_n;
reg Pi_money_half;
reg Pi_money_one;
reg	random_data;

wire Po_cola;
wire Po_money;

initial
	begin
		clk	= 1;
		rst_n <= 0;
		#20
		rst_n <= 1;
	end
	
always #10 clk = ~clk;

always@(posedge clk or negedge rst_n)
	if(rst_n == 0)
		random_data <= 0;
	else
		random_data <= {$random} % 2;
	
always@(posedge clk or negedge rst_n)
	if(rst_n == 0)
		Pi_money_half <= 0;
	else
		Pi_money_half <= random_data;

always@(posedge clk or negedge rst_n)
	if(rst_n == 0)
		Pi_money_one <= 0;
	else if(random_data == 1)
		Pi_money_one <= 0;
	else
		Pi_money_one <= {$random} % 2;
		
wire  [1:0] Pi_money = complex_fsm_inst.Pi_money;
wire  [4:0] state = complex_fsm_inst.state;
		
initial
	begin
		$timeformat(-9,0,"ns",6);//设置时间格式
		$monitor("@time %t:Pi_money_half = %b,Pi_money_one = %b,state = %b,Po_cola = %b,Po_money = %b",
		$time,Pi_money_half,Pi_money_one,state,Po_cola,Po_money);//打印
		
	end

complex_fsm complex_fsm_inst
(
.clk	(clk),
.rst_n	(rst_n),
.Pi_money_half(Pi_money_half),
.Pi_money_one(Pi_money_one),
.Po_cola(Po_cola),
.Po_money(Po_money)
);


endmodule

状态机的经典java代码 状态机实例_状态机的经典java代码_07


仿真结果如上所示,在第一个时钟序列,state为00001,即IDIE状态,当投入一元硬币时,在下一个序列,状态变为ONE(00100)。在第4个序列为TWO,再投入一枚0.5元硬币时,在下个序列,Po_cola为高,吐出可乐,状态返回IDIE状态。在Two状态,投入一元硬币,可乐机在下个序列吐出可乐,也找零,如第8个序列到第9个序列。