有限状态机

1 状态机原理

Verilog HDL语句块都是并行执行的,若想按照顺序的方式执行语句,则会使用有限状态机,简称为状态机。

状态机的设计分为一段式、二段式和三段式。通常使用两段式状态机和三段式状态机。

1.1 二段式状态机

二段式状态机使用一个时序逻辑进行状态的转移,另一个时序逻辑进行数据的输出,模板如下所示:

//二段式状态机模板
module 模块名(
	端口1,
	端口2,
	...
	端口n
	)
    
    //用独热码描述状态
	localparam 状态1 = 	n'b0000...0001;
	localparam 状态2  = 	n'b0000...0010;
   	...
	localparam 状态n  = 	n'b1000...0000;
	
	reg [位宽]	state;	//当前状态
	
	//时序逻辑描述的状态转移
	always @(posedge clk or negedge rst_n) begin
	 	if (rst_n == 1'b0) begin
	 		state <= 默认状态;
	 	end
        else case(state)
            状态1: begin
                if (状态转移条件) begin
	 					state <= 下一个状态;
	 				end
	 				else begin
						state <= state;
	 			end
            end
            状态2: begin
                ...
            end
            ...
            状态n: begin
                ...
            end
        	default begin
        		state = 默认状态;
        	end
        endcase
	end
    
    //时序逻辑描述的数据输出
    always @(posedge clk or negedge rst_n) begin
        数据输出;
    end
    
endmodule

1.2 三段式状态机

三段式状态机将二段式状态机中进行状态转移的时序逻辑分为两个部分,其中一部分用时序逻辑实现状态的存储,另一部分用组合逻辑实现状态的跳转。与二段式状态机相同的是,都用一个时序逻辑进行数据的输出,模板如下所示:

//三段式状态机模板
module 模块名(
	端口1,
	端口2,
	...
	端口n
	)
	
	//用独热码描述状态
	localparam 状态1 = 	n'b0000...0001;
	localparam 状态2  = 	n'b0000...0010;
   	...
	localparam 状态n  = 	n'b1000...0000;
	
	reg [位宽]	curr_state;	//当前状态
	reg [位宽]	next_state; //接下来的状态

	//时序逻辑描述实现状态curr_state的存储
	always @(posedge clk or posedge rst_n) begin
		if (rst_n == 1'b0) begin
			curr_state <= 默认状态;
		end
		else begin
			curr_state <= next_state;
		end
	end
    
    //组合逻辑实现状态next_state的跳转
    always @(posedge clk or negedge rst_n) begin
        case(curr_state)
        	状态1: begin
                if (状态转移条件) begin
	 					next_state <= 下一个状态;
	 				end
	 				else begin
						next_state <= 状态1;
	 			end
        	end
        	状态2: begin
        		...
        	end
        	...
        	状态n: begin
        		...
        	end
        	default begin
        		next_state = 默认状态;
        	end
        endcase
    end
    
    //时序逻辑描述的数据输出
    always @(posedge clk or negedge rst_n) begin
        数据输出;
    end
    
endmodule

三段式状态机将组合逻辑和时序逻辑分开的好处就是将来在修时序的时候方便插入寄存器。

2 实例:自动售货机

使用状态机描述一个简单的自动售货机,该售货机中的商品3元一件,每次投币只能投入1元。在我们描述状态机之前,一般会先画出对应的状态转移图,该状态转移图如图 1 所示。

image-20240516140535982

图1 状态转移图

根据状态转移图,我们可以得到如下所示的Verilog HDL代码:

2.1 两段式

module fsm(
		input	wire		clk,
		input	wire		rst_n,
		input   wire		pi_money,
		output	reg			po_cola
    );
	//用独热码描述状态
	localparam IDLE = 	3'b001;
	localparam ONE  = 	3'b010;
	localparam TWO  = 	3'b100;
    
//两段式状态机
	//状态寄存器(两段式)
	reg [2:0]	state;

	//该always块描述状态转移:state
	always @(posedge clk or negedge rst_n) begin
		if (rst_n == 1'b0) begin
			state <= IDLE;
		end
		else case(state)
				IDLE: begin
					if (pi_money == 1'b1) begin
						state <= ONE;
					end
					else begin
						state <= state;
					end
				end
				ONE: begin
					if (pi_money == 1'b1) begin
						state <= TWO;
					end
					else begin
						state <= state;
					end
				end
				TWO: begin
					if (pi_money == 1'b1) begin
						state <= IDLE;
					end
					else begin
						state <= state;
					end
				end
				default: begin
					state <= IDLE;
				end
			endcase
	end
    
    //该always快描述数据输出:po_cola
	always @(posedge clk or negedge rst_n) begin
		if (rst_n == 1'b0) begin
			po_cola <= 1'b0;
		end
		else if (state == TWO && pi_money == 1'b1) begin
			po_cola <= 1'b1;
		end
		else begin
			po_cola <= 1'b0;
		end
	end

2.2 三段式

module fsm(
		input	wire		clk,
		input	wire		rst_n,
		input   wire		pi_money,
		output	reg			po_cola
    );
	//用独热码描述状态
	localparam IDLE = 	3'b001;
	localparam ONE  = 	3'b010;
	localparam TWO  = 	3'b100;
    
//三段式寄存器 (将两段式寄存器第一个always块拆分为2个部分,一个部分用时序逻辑,另一个部分用组合逻辑)
	//curr_state用时序逻辑实现
	reg [2:0]	curr_state;
	//next_state用组合逻辑实现
	reg [2:0]	next_state;

	//时序逻辑实现状态的存储
	always @(posedge clk or posedge rst_n) begin
		if (rst_n == 1'b0) begin
			curr_state <= IDLE;
		end
		else begin
			curr_state <= next_state;
		end
	end

	//组合逻辑实现状态的跳转
	always @(*) begin
		case(curr_state)
			IDLE: begin
				if (pi_money == 1'b1) begin
					next_state = ONE;
				end
				else begin
					next_state = IDLE;
				end
			end
			ONE: begin
				if (pi_money == 1'b1) begin
					next_state = TWO;
				end
				else begin
					next_state = ONE;
				end
			end
			TWO: begin
				if (pi_money == 1'b1) begin
					next_state = IDLE;
				end
				else begin
					next_state = TWO;
				end
			end
			default: begin
				next_state = IDLE;
			end
		endcase
	end
    
    //该always快描述数据输出:po_cola
	always @(posedge clk or negedge rst_n) begin
		if (rst_n == 1'b0) begin
			po_cola <= 1'b0;
		end
		else if (curr_state == TWO && pi_money == 1'b1) begin
			po_cola <= 1'b1;
		end
		else begin
			po_cola <= 1'b0;
		end
	end
posted @ 2024-05-16 14:14  Yamada_Ryo  阅读(85)  评论(0编辑  收藏  举报