当前位置:   article > 正文

【verilog】有限状态机

有限状态机

1. 什么是有限状态机

有限状态机(Finite State Machine,简写为FSM),用于描述在有限个状态之间按照一定规律转换的时序电路。有限状态机描述电路的结构清晰,特别是对于功能较为复杂的电路,优势更为明显。

2.分类

最常用的一种分类方式是按照输出分类,按照输出我们可以将将FSM分为moore型和mealy型两类。
moore型:输出仅由当前状态决定
在这里插入图片描述
mealy型:输出由当前状态和输入决定
在这里插入图片描述
###############注意###########
实现相同功能时,moore型状态机会比mealy型状态机多一个状态,具体看后续举的例子。

3.状态编码

对于有限状态机的每一种状态,我们都需要对其进行编码,常用的编码方式有二进制编码、格雷码和一位独热编码(One Hot):
二进制编码
编码方式:状态寄存器由触发器构成,N个触发器可以构成2N个状态;
特点:是一种压缩编码方式,使用的寄存器少,消耗的资源少;
格雷码
编码方式:特殊的二进制编码,相邻邻状态跳转只会有一位发生变化;
特点:有二进制编码消耗资源少的优点,同时在状态转换时,因为相邻状态只会有一位发生变化,噪声小,转换速度快;
一位独热编码
编码方式:1个状态对应一个寄存器,每个状态只有1bit位为1,比如四状态编码0001,0010,0100,1000。
特点:消耗资源多、面积大,但是译码简单。

4.描述方式

说明:有限状态机将时序电路的描述分为了三部分,状态转移,状态转移的条件判断,输出赋值(具体看后述三段式)。但是这三部分可以合并,所以,描述方式分为了三种:一段式、两段式和三段式。
(1)准备
首先需要定义状态,根据所实现电路的状态数和编码方式进行定义。以101序列检测器为例,并使用一位独热编码。
例子包含4个状态,0、1、01、101,一位独热编码就需要四个寄存器,状态寄存器需要4位宽:
首先:定义两个状态寄存器,分别表示当前所处的状态接下来会跳转的状态

	reg [3:0] current_state;
	reg [3:0] next_state; 
  • 1
  • 2

然后,定义状态:

	//一位独热编码,共四个状态
	parameter IDLE     		= 4'b0001;
	parameter STATE_ONE		= 4'b0010;
	parameter STATE_TWO		= 4'b0100;
	parameter STATE_THREE	= 4'b1000;
  • 1
  • 2
  • 3
  • 4
  • 5

(2)三段式
说明:这里首先说明三段式,因为一段式和两段式是三段式的合并。

//第一段:时序always过程块,格式化的将次态赋值给当前状态

always@(posedge clk or negedge rst_n)
	if(!rst)
		//IDLE为默认状态
		current_state <= IDLE; 
	else 
		current_state <= next_state;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

//第二段:组合always过程块,状态转移条件判断,根据当前状态和输入判断下一个时刻需要跳转到的状态

always@(*)begin\
	next_state = IDLE;		//初始化
	case({current_state,输入})//根据当前状态和输入,判定需要跳转的次态
		情况1:begin
			next_state = STATE_ONE		;
		end
		情况2:begin
			next_state = ...		;
		end
		...
		default:begin
			next_state = STATE_ONE		;					     		 
		end
	endcase
end
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

//第三段:连续赋值语句或者always过程块,对输出进行赋值

	assign out = ...;
  • 1

或者:

always@(*)begin
	case(current_state)
		IDLE:
   			out = ... ;
		STATE_ONE:
   			out = ...;
   		...
		default:
   	    	...
	endcase
end
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

或者:

always@(posedge clk or negedge rst_n)
	if(!rst)
		//IDLE为默认状态
		out <= 0; 
	else
	case(next_state)
		IDLE:
   			out <= ... ;
		STATE_ONE:
   			out <= ...;
   		...
		default:
   	    	...
	endcase
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

###############注意###########
A 对于输出赋值,如果是组合逻辑电路,条件分支语句的判断条件一般为current_state。而如果是时序逻辑电路,为了避免延迟一拍,条件分支语句的判断条件一般为next_state
B 三段式的第三段并不只有一块,对于复杂电路,第三段可以由多个连续赋值语句和always过程块组成。
(3)两段式
两段式就是将三段式的第二段和第三段进行了合并。如果输出赋值可以使用组合逻辑电路实现,我们就可以在第二段的状态转移条件判断的同时,进行输出赋值。
//第二段第三段合并为
//第二段:组合always过程块,状态转移条件判断,根据当前状态和输入判断下一个时刻需要跳转到的状态

always@(*)begin\
	next_state = IDLE;		//初始化
	out = 0case({current_state,输入})//根据当前状态和输入,判定需要跳转的次态
		情况1:begin
			next_state = STATE_ONE		;
			out = ...;
		end
		情况2:begin
			next_state = ...;
			out = ...;
		end
		...
		default:begin
			next_state = IDLE;
			out = 0;
		end
	endcase
end
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

(4)一段式
一段式就是将三段合并为一段,就和一般设计时序逻辑电路的形式一样了,就只需要定义一个状态寄存变量state,而不需要前面的current_statenext_state;

reg [3:0] state;
  • 1
always@(posedge clk or negedge rst_n)
	if(!rst)
		//IDLE为默认状态
		state<= IDLE; 
		out <= 0;
	else
	case(state)
		IDLE:
			state<= ...;
   			out <= ... ;
		STATE_ONE:
			state<= ...;
   			out <= ...;
   		...
		default:
   	    	...
	endcase
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

###############注意###########
一段式结构分析起来比较复杂,且不易于代码维护,所以建议使用两段式或者三段式。

4.举例及细节说明

(1)说明
分别用moore型和mealy型有限状态机实现101序列信号检测器,使用三段式有限状态机实现。
(2)moore型
A)确定输出及输出:
输入:串行序列
输出:1bit序列检测标志信号,检测到101时,拉高标志信号
B)状态及编码
状态:初始状态(IDLE),检测到1(S1),检测到10(S2),检测到101(S3),共4个状态。
编码:一位独热编码,4个状态需要4位宽寄存器
C)状态转移图:
在这里插入图片描述
D)代码
101检测器实现:

module FSM_moore(
    input   sys_clk     ,
    input   sys_rst_n   ,
    input   data_in     ,
    output  flag
    );
    //定义状态
    parameter IDLE  = 4'b0001;
    parameter S1    = 4'b0010;
    parameter S2    = 4'b0100;
    parameter S3    = 4'b1000;
    //定义状态寄存变量
    reg [3:0] current_state;
    reg [3:0] next_state;
    //第一段,时序always过程块,格式化地将次态赋值给当前状态
    always@(posedge sys_clk or negedge sys_rst_n)begin
        if(!sys_rst_n)
            current_state <= IDLE;
        else
            current_state <= next_state; 
    end  
    //第二段,组合always过程块,状态转移条件判断
    always@(*)begin
        next_state = IDLE;   //初始化
        case({current_state,data_in})
            {IDLE,1'b0} : next_state = IDLE; 
            {IDLE,1'b1} : next_state = S1; 
            {S1,1'b0}   : next_state = S2; 
            {S1,1'b1}   : next_state = S1;      
            {S2,1'b0}   : next_state = IDLE; 
            {S2,1'b1}   : next_state = S3;
            {S3,1'b0}   : next_state = S2; 
            {S3,1'b1}   : next_state = S1;   
            default     : next_state = IDLE;               
        endcase
    end
    //第三段,任意形式,赋值输出
    assign flag = (current_state == S3)?1'b1:1'b0;
endmodule
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

testbench:

`timescale 1ns / 1ps
module FSM_moore_tb;
    reg     sys_clk     ;
    reg     sys_rst_n   ;
    reg     data_in     ;
    wire    flag        ;
    parameter T = 20;   //50MHz时钟,周期为20ns
    //初始化模块
    initial begin
        sys_clk = 1'b0;
        sys_rst_n = 1'b0;
        data_in = 1'b0;
        #(T);
        sys_rst_n = 1'b1;
    end
    //时钟产生模块
    always#(T/2) sys_clk = !sys_clk;
    //输入串行序列产生模块
    always@(posedge sys_clk or negedge sys_rst_n)
        if(!sys_rst_n)
            data_in <= 1'b0;
        else
            data_in <= $random % 2;
    //模块例化
    FSM_moore u_FSM_moore(
        .sys_clk        (sys_clk),
        .sys_rst_n      (sys_rst_n),
        .data_in        (data_in),
        .flag           (flag)
    );
endmodule
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

E)仿真结果
可以看出,当输入变了,next_state会立刻改变,current_state会下一个时钟上升沿到来时才改变,所以输出相较于data_in会延迟一拍
在这里插入图片描述
(3)mealy型
A)moore型状态机为mealy型状态机的关系
moore型状态机也可以表示为mealy型(反之不成立):
在这里插入图片描述
###############注意###########
之所以moore型状态机可以表示为mealy型(输出不再仅仅由当前状态决定),是因为moore型状态机,本来就是mealy型状态机的特殊情况。当mealy型状态机,到达同一状态的输出是一致的时候,就是moore型状态机。如下图,转换到IDLE的所有输出均为0(红色),转换到S1状态的所有输出均为0(蓝色),转换到S2状态的所有输出均为0(橙色),转换到S3状态的所有输出均为1(绿色),即:
在这里插入图片描述
B)状态转移图:
前面第2节说到,moore型状态机相较于mealy型状态机,会多一个状态,但是上述的状态机状态是相同的,这是因为当前的mealy状态机并不是最简单的。
###############补充知识###########
等价状态(数电知识):当状态转移图中,两个状态在输入相同时,有相同的输出,且转换到相同的次态,称为等价状态,等价状态可以合并
可以看到mealy型状态机的S1S3等价状态。如下图所示,当这两个状态下:输入为0时,输出均为0,且都转换到次态S2;输入为1时,输出均为0,且都转换到次态S1。
在这里插入图片描述
将等价状态合并得到下图:
合并方法:将连线少的状态删除,原图中输入到被删除状态的线,连接到未删除的等价状态。
在这里插入图片描述
C)状态及编码
状态:初始状态(IDLE),检测到1或101(S1),检测到10(S2),共3个状态。
编码:一位独热编码,3个状态需要3位宽寄存器
D)代码
101序列检测器实现:

module FSM_mealy(
    input   sys_clk     ,
    input   sys_rst_n   ,
    input   data_in     ,
    output  reg flag
    );
    //定义状态
    parameter IDLE  = 3'b001;
    parameter S1    = 3'b010;
    parameter S2    = 3'b100;
    //定义状态暂存变量
    reg [3:0] current_state;
    reg [3:0] next_state;
    //第一段,时序always过程块,格式化地将次态赋值给当前状态
    always@(posedge sys_clk or negedge sys_rst_n)
        if(!sys_rst_n)
            current_state <= IDLE;
        else
            current_state <= next_state;
    //第二段,组合always过程块,状态转移条件判断
    always@(*)begin
        next_state = IDLE;
        case({current_state,data_in})
            {IDLE,1'b0} :   next_state = IDLE   ;
            {IDLE,1'b1} :   next_state = S1     ;
            {S1,1'b0}   :   next_state = S2     ;
            {S1,1'b1}   :   next_state = S1     ;
            {S2,1'b0}   :   next_state = IDLE   ;
            {S2,1'b1}   :   next_state = S1     ;
            default     :   next_state = IDLE   ;
        endcase
    end
    //第三段,输出赋值
    always@(posedge sys_clk or negedge sys_rst_n)
        if(!sys_rst_n)
            flag <= 1'b0;
        else if((current_state == S2)&&(data_in))
            flag <= 1'b1;
        else
            flag <= 1'b0;
endmodule
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41

testbench

`timescale 1ns / 1ps
module FSM_mealy_tb;
    reg     sys_clk     ;
    reg     sys_rst_n   ;
    reg     data_in     ;
    wire    flag        ;
    parameter T = 20;   //50MHz时钟,周期为20ns
    //初始化模块
    initial begin
        sys_clk = 1'b0;
        sys_rst_n = 1'b0;
        data_in = 1'b0;
        #(T);
        sys_rst_n = 1'b1;
    end
    //时钟产生模块
    always#(T/2) sys_clk = !sys_clk;
    //输入串行序列产生模块
    always@(posedge sys_clk or negedge sys_rst_n)
        if(!sys_rst_n)
            data_in <= 1'b0;
        else
            data_in <= $random % 2;
    //模块例化
    FSM_mealy u_FSM_mealy(
        .sys_clk        (sys_clk),
        .sys_rst_n      (sys_rst_n),
        .data_in        (data_in),
        .flag           (flag)
    );
endmodule
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

(E)仿真结果
可以看出,当输入变了,next_state会立刻改变,current_state会下一个时钟上升沿到来时才改变,所以输出相较于data_in会延迟一拍
在这里插入图片描述

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Guff_9hys/article/detail/915503
推荐阅读
相关标签
  

闽ICP备14008679号