赞
踩
有限状态机(Finite State Machine,简写为FSM),用于描述在有限个状态之间按照一定规律转换的时序电路。有限状态机描述电路的结构清晰,特别是对于功能较为复杂的电路,优势更为明显。
最常用的一种分类方式是按照输出分类,按照输出我们可以将将FSM分为moore型和mealy型两类。
moore型:输出仅由当前状态决定
mealy型:输出由当前状态和输入决定
###############注意###########
实现相同功能时,moore型状态机会比mealy型状态机多一个状态,具体看后续举的例子。
对于有限状态机的每一种状态,我们都需要对其进行编码,常用的编码方式有二进制编码、格雷码和一位独热编码(One Hot):
二进制编码:
编码方式:状态寄存器由触发器构成,N个触发器可以构成2N个状态;
特点:是一种压缩编码方式,使用的寄存器少,消耗的资源少;
格雷码:
编码方式:特殊的二进制编码,相邻邻状态跳转只会有一位发生变化;
特点:有二进制编码消耗资源少的优点,同时在状态转换时,因为相邻状态只会有一位发生变化,噪声小,转换速度快;
一位独热编码:
编码方式:1个状态对应一个寄存器,每个状态只有1bit位为1,比如四状态编码0001,0010,0100,1000。
特点:消耗资源多、面积大,但是译码简单。
说明:有限状态机将时序电路的描述分为了三部分,状态转移,状态转移的条件判断,输出赋值(具体看后述三段式)。但是这三部分可以合并,所以,描述方式分为了三种:一段式、两段式和三段式。
(1)准备
首先需要定义状态,根据所实现电路的状态数和编码方式进行定义。以101序列检测器为例,并使用一位独热编码。
例子包含4个状态,0、1、01、101,一位独热编码就需要四个寄存器,状态寄存器需要4位宽:
首先:定义两个状态寄存器,分别表示当前所处的状态和接下来会跳转的状态:
reg [3:0] current_state;
reg [3:0] next_state;
然后,定义状态:
//一位独热编码,共四个状态
parameter IDLE = 4'b0001;
parameter STATE_ONE = 4'b0010;
parameter STATE_TWO = 4'b0100;
parameter STATE_THREE = 4'b1000;
(2)三段式
说明:这里首先说明三段式,因为一段式和两段式是三段式的合并。
//第一段:时序always过程块,格式化的将次态赋值给当前状态
always@(posedge clk or negedge rst_n)
if(!rst)
//IDLE为默认状态
current_state <= IDLE;
else
current_state <= next_state;
//第二段:组合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
//第三段:连续赋值语句或者always过程块,对输出进行赋值
assign out = ...;
或者:
always@(*)begin
case(current_state)
IDLE:
out = ... ;
STATE_ONE:
out = ...;
...
default:
...
endcase
end
或者:
always@(posedge clk or negedge rst_n)
if(!rst)
//IDLE为默认状态
out <= 0;
else
case(next_state)
IDLE:
out <= ... ;
STATE_ONE:
out <= ...;
...
default:
...
endcase
###############注意###########
A 对于输出赋值,如果是组合逻辑电路,条件分支语句的判断条件一般为current_state。而如果是时序逻辑电路,为了避免延迟一拍,条件分支语句的判断条件一般为next_state;
B 三段式的第三段并不只有一块,对于复杂电路,第三段可以由多个连续赋值语句和always过程块组成。
(3)两段式
两段式就是将三段式的第二段和第三段进行了合并。如果输出赋值可以使用组合逻辑电路实现,我们就可以在第二段的状态转移条件判断的同时,进行输出赋值。
//第二段第三段合并为
//第二段:组合always过程块,状态转移条件判断,根据当前状态和输入判断下一个时刻需要跳转到的状态
always@(*)begin\ next_state = IDLE; //初始化 out = 0; case({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
(4)一段式
一段式就是将三段合并为一段,就和一般设计时序逻辑电路的形式一样了,就只需要定义一个状态寄存变量state,而不需要前面的current_state和next_state;
reg [3:0] state;
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)说明
分别用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
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
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型状态机的S1和S3为等价状态。如下图所示,当这两个状态下:输入为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
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
(E)仿真结果
可以看出,当输入变了,next_state会立刻改变,current_state会下一个时钟上升沿到来时才改变,所以输出相较于data_in会延迟一拍。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。