概述
1.1、理论
FPGA不同于CPU的一点特点就是CPU是顺序执行的,而FPGA是同步执行(并行)的。那么FPGA如何处理明显具有时间上先后顺序的事件呢?这个时候我们就需要使用到状态机了。
状态机简写为 FSM(Finite State Machine),也称为同步有限状态机,我们一般简称为状态机,之所以说“同步”是因为状态机中所有的状态跳转都是在时钟的作用下进行的,而“有限”则是说状态的个数是有限的。状态机的每一个状态代表一个事件,从执行当前事件到执行另一事件我们称之为状态的跳转或状态的转移,我们需要做的就是执行该事件然后跳转到一下时间,这样我们的系统就“活”了,可以正常的运转起来了。状态机通过控制各个状态的跳转来控制 流程,使得整个代码看上去更加清晰易懂,在控制复杂流程的时候,状态机优势明显。
1.2、分类
根据状态机的输出是否与输入条件相关,可将状态机分为两大类,即摩尔(Moore)型状态机和米勒(Mealy) 型状态机。
- Mealy 状态机:输出不仅取决于当前状态,还取决于输入状态。
米勒状态机的模型如下图所示,模型中第一个方框是指产生下一状态的组合逻辑 F,F 是当前状态和输 入信号的函数,状态是否改变、如何改变,取决于组合逻辑 F 的输出;第二框图是指状态寄存器,其由一 组触发器组成,用来记忆状态机当前所处的状态,状态的改变只发生在时钟的跳边沿;第三个框图是指产生输出的组合逻辑 G,状态机的输出是由输出组合逻辑 G 提供的,G 也是当前状态和输入信号的函数。
- Moore 状态机:组合逻辑的输出只取决于当前状态,而与输入状态无关。
摩尔状态机的模型如下图所示,对比米勒状态机的模型可以发现,其区别在于米勒状态机的输出由当 前状态和输入条件决定的,而摩尔状态机的输出只取决于当前状态。
1.3、写法
根据状态机的实际写法,状态机可以分为一段式、二段式和三段式状态机。
- 一段式状态机:整个状态机写到一个 always模块里面,在该模块中既描述状态转移,又描述状态的输入和输出。
- 二段式状态机:用两个 always 模块来描述状态机,其中一个 always 模块采用同步时序描述状态转移;另一个模块采用组合逻辑判断状态转移条件,描述状态转移规律以及输出。不同于一段式状态机的是,它需要定义两个状态,现态和次态,然后通过现态和次态的转换来实现时序逻辑。
- 三段式状态机:在两个 always 模块描述方法基础上,使用三个always 模块,一个always 模块采用同步时序描述状态转移,一个 always 采用组合逻辑判断状态转移条件,描述状态转移规律,另一个 always 模块描述状态输出。
2、状态机实例分析
接下来对一个简单的可乐售卖系统使用状态机的思想进行分析。
可乐售卖系统:可乐机每次只能投入 1 枚 1 元硬币,且每瓶可乐卖 3 元钱,即投入 3 个硬币就可以让可乐机出可乐,如果投币不够 3 元想放弃投币需要按复位键,否则之前投入的钱不能退回。
首先分析会有哪些输入、输出信号:
输入信号:
sys_clk_n:既然是同步状态机,那么时钟是肯定少不了的,这里设定时钟是50MHz;
sys_rst_n:一个稳健的系统肯定需要一个复位,这里设定位低电平有效;
money:投币输入,高电平表示投入一元,低电平表示没有投币;
输出信号:
cola:可乐输出,高电平表示掉落一瓶可乐,低电平表示没有可乐掉落;
根据以上输入、输出可以画出状态机模块的示意框图:
接下来需要想一想这个状态机是怎么运作的,也就是要绘制这个系统的状态转移图。前面提到,状态机的状态转移有和输入挂钩的Mealy 状态机,也有和输入无关的Moore 状态机。所以接下来分别用Moore 状态机、Mealy 状态机的思想来绘制状态转移图:
Moore 状态机(输出和输入无关):
- IDLE:首先是系统复位后的默认状态,这个状态下售卖机里没有钱,没有可乐输出;接下来的状态有两种情况:投了1元硬币则跳转状态ONE、没有投硬币则保持IDLE状态;
- ONE:这个状态下售卖机里有1元硬币,所以也没有可乐输出;接下来的状态有两种情况:投了1元硬币则跳转状态TWO、没有投硬币则保持ONE状态;
- TWO:这个状态下售卖机里有2元硬币,所以也没有可乐输出;接下来的状态有两种情况:投了1元硬币则跳转状态THREE、没有投硬币则保持TWO状态;
- THREE:这个状态下售卖机里有3元硬币,但是因为是使用的时序逻辑,所以在这个时钟周期,是不会有可乐输出的,可乐会在状态跳转后(下一个时钟周期输出);接下来的状态有两种情况:投了1元硬币则跳转状态ONE、没有投硬币则跳转状态IDLE状态;而且状态跳转后会输出一瓶可乐(实际上可以理解为THREE状态来自于TWO状态投的一元硬币,也就是这个时钟周期如果输出发生了变化,则输出是和输入有关的了,那就不是Moore 状态机了);
根据上面列出的这些状态可以绘制出如下的状态转移图(1/0:前面的1代表输入,后面的0代表输出):
Mealy 状态机(输出和输入相关):
- IDLE:首先是系统复位后的默认状态,没有可乐输出(分两种情况,投币和不投币);接下来的状态有两种情况:投了1元硬币则跳转状态ONE且没有可乐输出、没有投硬币则保持IDLE状态且没有可乐输出;
- ONE:这个状态下售卖机里有1元硬币;接下来的状态有两种情况:投了1元硬币则跳转状态TWO且没有可乐输出、没有投硬币则保持ONE状态且没有可乐输出;
- TWO:这个状态下售卖机里有2元硬币;接下来的状态有两种情况:投了1元硬币则跳转状态IDLE且输出可乐(根据和输入相关要求,此时输入一元,加上原来的2元,一共有三元,满足输出可乐的条件)、没有投硬币则保持TWO状态且没有可乐输出;
根据上面列出的这些状态可以绘制出如下的状态转移图(1/0:前面的1代表输入,后面的0代表输出):
从上面的分析可以得到以下结论:
- Mealy 状态机比Moore状态机的状态个数要少
- Mealy 状态机比Moore状态机的输出要早一个时钟周期
接下来就各种写法对状态机进行仿真分析。
3、一段式状态机
一段式状态机是将整个状态机写到一个 always模块里面,在该模块中既描述状态转移,又描述状态的输入和输出。不推荐采用这种状态机,因为从代码风格方面来讲,一般都会要求把组合逻辑和时序逻辑分开;从代 码维护和升级来说,组合逻辑和时序逻辑混合在一起不利于代码维护和修改,也不利于约束。
3.1、Moore型(摩尔型)一段式状态机
Verillog代码如下:
-
//==================================================================
-
//-- 1段式状态机(Moore)
-
//==================================================================
-
-
//------------<模块及端口声明>----------------------------------------
-
module
FSM_Moore_1(
-
input sys_clk ,
//输入系统时钟、50M
-
input sys_rst_n ,
//复位信号、低电平有效
-
input money ,
//投币输入,高电平有效
-
-
output reg cola
//可乐输出,高电平有效
-
);
-
-
//------------<状态机参数定义>------------------------------------------
-
//这里使用独热码编码节省组合逻辑资源
-
//此外还可以使用格雷码 、二进制码
-
localparam IDLE =
4
'b0001,
-
ONE =
4
'b0010,
-
TWO =
4
'b0100,
-
THREE =
4
'b1000;
-
-
//------------<reg定义>-------------------------------------------------
-
reg [
3:
0] state;
//定义状态寄存器
-
-
//-----------------------------------------------------------------------
-
//-- 1段式状态机(Moore)
-
//-----------------------------------------------------------------------
-
always@(posedge sys_clk or negedge sys_rst_n)begin
-
if(!sys_rst_n)begin
-
cola <=
1
'b0;
//复位初始状态
-
state <= IDLE;
-
end
-
else
-
case(state)
//根据当前状态、输入进行状态转换判断
-
//根据当前状态进行输出
-
IDLE:begin
-
cola <=
1
'b0;
//初始状态无可乐输出
-
if(money)
-
state <= ONE;
//投币1元则状态跳转到ONE
-
else
-
state <= IDLE;
//否则保持原有状态
-
end
-
ONE:begin
-
cola <=
1
'b0;
//该状态只有1元,无可乐输出
-
if(money)
-
state <= TWO;
//投币1元则状态跳转到TWO
-
else
-
state <= ONE;
//否则保持原有状态
-
end
-
TWO:begin
-
cola <=
1
'b0;
//该状态只有2元,无可乐输出
-
if(money)
-
state <= THREE;
//投币1元则状态跳转到THREE
-
else
-
state <= TWO;
//否则保持原有状态
-
end
-
THREE:begin
-
cola <=
1
'b1;
//该状态有3元,有可乐输出
-
//但是时序逻辑输出会落后一个时钟周期
-
if(money)
-
state <= ONE;
//投币1元则状态跳转到ONE
-
else
-
state <= IDLE;
//否则状态跳转到IDLE
-
end
-
default:begin
//默认状态同IDLE
-
cola <=
1
'b0;
-
if(money)
-
state <= ONE;
-
else
-
state <= IDLE;
-
end
-
endcase
-
end
-
-
endmodule
使用QuartusII编码生成的状态机视图如下:
可以看到,这和我们之前绘制的状态转移图一致。
状态机的编码方式一般有三种,各有优劣,独热码算是用的比较多的:
- 独热码
- 格雷码
- 二进制码
编写Testbench文件进行仿真,文件如下:
-
//-------------------------------------------------------------------
-
//-- 1段式状态机(Moore)
-
//-------------------------------------------------------------------
-
`timescale
1ns/
1ns
-
-
//------------<模块及端口声明>----------------------------------------
-
module
tb_FSM_Moore_1();
-
reg sys_clk;
-
reg sys_rst_n;
-
reg money;
-
wire cola;
-
-
//------------<例化被测试模块>----------------------------------------
-
FSM_Moore_1
FSM_Moore_1_inst(
-
.
sys_clk (sys_clk) ,
-
.
sys_rst_n (sys_rst_n) ,
-
.
money (money) ,
-
-
.
cola (cola)
-
);
-
//------------<设置初始测试条件>----------------------------------------
-
initial begin
-
sys_clk =
1
'b0;
//初始时钟为0
-
sys_rst_n <=
1
'b0;
//初始复位
-
money <=
1
'b0;
//投币初始化为0
-
#
5
//5个时钟周期后
-
sys_rst_n <=
1
'b1;
//拉高复位,系统进入工作状态
-
#
25
//25个时钟周期后
-
money <=
1
'b1;
//拉高投币信号
-
#
40
//40个时钟周期后
-
money <=
1
'b0;
//拉低投币信号
-
#
20
//25个时钟周期后
-
money <=
1
'b1;
//拉高投币信号
-
#
80
//25个时钟周期后
-
money <=
1
'b0;
//拉低投币信号
-
end
-
//------------<设置时钟>----------------------------------------------
-
always #
10 sys_clk = ~sys_clk;
//系统时钟周期20ns
-
-
//------------<状态机名称查看器>----------------------------------------
-
reg [
39:
0] state_name;
//每字符8位宽,这里最多5个字符40位宽
-
-
always @(*) begin
-
case(FSM_Moore_1_inst.state)
-
4
'b0001: state_name =
"IDLE";
-
4
'b0010: state_name =
"ONE";
-
4
'b0100: state_name =
"TWO";
-
4
'b1000: state_name =
"THREE";
-
default: state_name =
"IDLE";
-
endcase
-
end
-
-
endmodule
使用ModelSim执行仿真,仿真出来的波形如下所示:
可以看到:
- 在第2、3、5、6、7、8的6个时钟投入了6个硬币,理论上应该有2个可乐分别输出,实际也有两个可乐输出;
- 第1次输出可乐滞后THREE状态一个时钟周期,且当前的输入为1;第2次输出可乐滞后THREE状态一个时钟周期,且当前的输入为0;这说明输出会之后当前状态一个时钟周期,且与输入无关(输入不管是0还是1都有输出);
- 状态的跳转符合我们绘制的状态转移图。
3.2、Mealy型(米勒型)一段式状态机
Verillog代码如下:
-
//==================================================================
-
//-- 1段式状态机(Mealy)
-
//==================================================================
-
-
//------------<模块及端口声明>----------------------------------------
-
module
FSM_Mealy_1(
-
input sys_clk ,
//输入系统时钟、50M
-
input sys_rst_n ,
//复位信号、低电平有效
-
input money ,
//投币输入,高电平有效
-
-
output reg cola
//可乐输出,高电平有效
-
);
-
-
//------------<状态机参数定义>------------------------------------------
-
//这里使用独热码编码节省组合逻辑资源
-
//此外还可以使用格雷码 、二进制码
-
localparam IDLE =
3
'b001,
-
ONE =
3
'b010,
-
TWO =
3
'b100;
-
-
//------------<reg定义>------------------------------------------------
-
reg [
2:
0] state;
//定义状态寄存器
-
-
//-----------------------------------------------------------------------
-
//-- 1段式状态机(Mealy)
-
//-----------------------------------------------------------------------
-
always@(posedge sys_clk or negedge sys_rst_n)begin
-
if(!sys_rst_n)begin
-
cola <=
1
'b0;
//复位初始状态
-
state <= IDLE;
//复位初始状态
-
end
-
else
-
case(state)
//根据当前状态、输入进行状态转换判断
-
//根据当前状态、输入进行输出
-
IDLE:begin
-
if(money)begin
//投入1元
-
state <= ONE;
//状态跳转到ONE
-
cola <=
1
'b0;
//一共1元 ,没有可乐输出
-
end
-
else begin
//没有投入
-
state <= IDLE;
//保持原有状态
-
cola <=
1
'b0;
//一共0元 ,没有可乐输出
-
end
-
end
-
ONE:begin
-
if(money)begin
//投入1元
-
state <= TWO;
//状态跳转到TWO
-
cola <=
1
'b0;
//一共2元 ,没有可乐输出
-
end
-
else begin
//没有投入
-
state <= ONE;
//保持原有状态
-
cola <=
1
'b0;
//一共1元 ,没有可乐输出
-
end
-
end
-
TWO:begin
-
if(money)begin
//投入1元
-
state <= IDLE;
//状态跳转到IDLE(一共3元了,需要输出可乐)
-
cola <=
1
'b1;
//一共3元 ,输出可乐
-
end
-
else begin
//没有投入
-
state <= TWO;
//保持原有状态
-
cola <=
1
'b0;
//一共2元 ,没有可乐输出
-
end
-
end
-
default:begin
//默认状态同初始状态
-
if(money)begin
-
state <= ONE;
-
cola <=
1
'b0;
-
end
-
else begin
-
state <= IDLE;
-
cola <=
1
'b0;
-
end
-
end
-
endcase
-
end
-
-
endmodule
使用QuartusII编码生成的状态机视图如下:
可以看到,这和我们之前绘制的状态转移图一致。
编写Testbench文件进行仿真,仿真激励设置和Moore型一段式状态机一致,文件如下:
-
//-------------------------------------------------------------------
-
//-- 1段式状态机(Mealy)
-
//-------------------------------------------------------------------
-
`timescale
1ns/
1ns
-
-
//------------<模块及端口声明>----------------------------------------
-
module
tb_FSM_Mealy_1();
-
reg sys_clk;
-
reg sys_rst_n;
-
reg money;
-
wire cola;
-
-
//------------<例化被测试模块>----------------------------------------
-
FSM_Mealy_1
FSM_Mealy_1_inst(
-
.
sys_clk (sys_clk) ,
-
.
sys_rst_n (sys_rst_n) ,
-
.
money (money) ,
-
-
.
cola (cola)
-
);
-
//------------<设置初始测试条件>----------------------------------------
-
initial begin
-
sys_clk =
1
'b0;
//初始时钟为0
-
sys_rst_n <=
1
'b0;
//初始复位
-
money <=
1
'b0;
//投币初始化为0
-
#
5
//5个时钟周期后
-
sys_rst_n <=
1
'b1;
//拉高复位,系统进入工作状态
-
#
25
//25个时钟周期后
-
money <=
1
'b1;
//拉高投币信号
-
#
40
//40个时钟周期后
-
money <=
1
'b0;
//拉低投币信号
-
#
20
//25个时钟周期后
-
money <=
1
'b1;
//拉高投币信号
-
#
80
//25个时钟周期后
-
money <=
1
'b0;
//拉低投币信号
-
end
-
//------------<设置时钟>----------------------------------------------
-
always #
10 sys_clk = ~sys_clk;
//系统时钟周期20ns
-
-
//------------<状态机名称查看器>----------------------------------------
-
reg [
31:
0] state_name;
//每字符8位宽,这里最多4个字符32位宽
-
-
always @(*) begin
-
case(FSM_Mealy_1_inst.state)
-
3
'b001: state_name =
"IDLE";
-
3
'b010: state_name =
"ONE";
-
3
'b100: state_name =
"TWO";
-
default: state_name =
"IDLE";
-
endcase
-
end
-
-
endmodule
使用ModelSim执行仿真,仿真出来的波形如下所示:
从仿真结果可以看到:
- 在第2、3、5、6、7、8的6个时钟投入了6个硬币,理论上应该有2个可乐分别输出,实际也有两个可乐输出;
- 第1次输出可乐滞后TWO状态一个时钟周期,且当前的输入为1;第2次输出可乐滞后TWO状态一个时钟周期,且当前的输入也为1;这说明输出会之后当前状态一个时钟周期,且与输入相关(只有输入为 1才有输出,在第4个时钟是,输入为0,所以没有输出);
- 状态的跳转符合我们绘制的状态转移图。
通过以上,针对一段式状态机可以得出如下小结:
- Moore型状态机输出滞后Mealy型状态机一个时钟周期
- 一段式状态机将所有状态转移与输出全写在一个always块里,如果状态多的话就会看起来十分臃肿,且不利于维护
4、二段式状态机
二段式状态机用两个 always 模块来描述状态机,其中一个 always 模块采用同步时序描述状态转移;另一个 模块采用组合逻辑判断状态转移条件,描述状态转移规律以及输出。不同于一段式状态机的是,它需要定 义两个状态,现态和次态,然后通过现态和次态的转换来实现时序逻辑。
4.1、Moore型(摩尔型)二段式状态机
Verillog代码如下:
-
//==================================================================
-
//-- 2段式状态机(Moore)
-
//==================================================================
-
-
//------------<模块及端口声明>----------------------------------------
-
module
FSM_Moore_2(
-
input sys_clk ,
//输入系统时钟、50M
-
input sys_rst_n ,
//复位信号、低电平有效
-
input money ,
//投币输入,高电平有效
-
-
output reg cola
//可乐输出,高电平有效
-
);
-
-
//------------<状态机参数定义>------------------------------------------
-
localparam IDLE =
4
'b0001,
-
ONE =
4
'b0010,
-
TWO =
4
'b0100,
-
THREE =
4
'b1000;
-
//------------<reg定义>-------------------------------------------------
-
reg [
3:
0] cur_state;
//定义现态寄存器
-
reg [
3:
0] next_state;
//定义次态寄存器
-
-
//-----------------------------------------------------------------------
-
//--状态机第一段:同步时序描述状态转移
-
//-----------------------------------------------------------------------
-
always@(posedge sys_clk or negedge sys_rst_n)begin
-
if(!sys_rst_n)
-
cur_state <= IDLE;
//复位初始状态
-
else
-
cur_state <= next_state;
//次态转移到现态
-
end
-
-
//-----------------------------------------------------------------------
-
//--状态机第二段:组合逻辑判断状态转移条件,描述状态转移规律以及输出
-
//-----------------------------------------------------------------------
-
always@(*)begin
//组合逻辑
-
case(cur_state)
//根据当前状态、输入进行状态转换判断
-
//根据当前状态进行输出
-
IDLE:begin
-
cola =
1
'b0;
//初始状态无可乐输出
-
if(money)
//投币1元
-
next_state = ONE;
//次态(下个状态)为ONE
-
else
-
next_state = IDLE;
//次态为现态
-
end
-
ONE:begin
-
cola =
1
'b0;
//无可乐输出
-
if(money)
//投币1元
-
next_state = TWO;
//次态(下个状态)为TWO
-
else
-
next_state = ONE;
//次态为现态
-
end
-
TWO:begin
-
cola =
1
'b0;
//无可乐输出
-
if(money)
//投币1元
-
next_state = THREE;
//次态(下个状态)为THREE
-
else
-
next_state = TWO;
//次态为现态
-
end
-
THREE:begin
-
cola =
1
'b1;
//输出可乐输出
-
if(money)
//投币1元
-
next_state = ONE;
//次态(下个状态)为ONE
-
else
-
next_state = IDLE;
//次态为IDLE
-
end
-
default:begin
//默认状态同IDLE
-
cola =
1
'b0;
-
if(money)
-
next_state = ONE;
-
else
-
next_state = IDLE;
-
end
-
endcase
-
end
-
-
endmodule
使用QuartusII编码生成的状态机视图如下:
可以看到,这和我们之前绘制的状态转移图一致。
编写Testbench文件进行仿真,仿真激励设置和Moore型一段式状态机一致,文件如下:
-
//------------------------------------------------
-
//-- 2段式状态机(Moore)
-
//------------------------------------------------
-
`timescale
1ns/
1ns
-
-
//------------<模块及端口声明>----------------------------------------
-
module
tb_FSM_Moore_2();
-
-
reg sys_clk;
-
reg sys_rst_n;
-
reg money;
-
-
wire cola;
-
-
//------------<例化被测试模块>----------------------------------------
-
FSM_Moore_2
FSM_Moore_2_inst(
-
.
sys_clk (sys_clk) ,
-
.
sys_rst_n (sys_rst_n) ,
-
.
money (money) ,
-
-
.
cola (cola)
-
);
-
-
//------------<设置初始测试条件>----------------------------------------
-
initial begin
-
sys_clk =
1
'b0;
//初始时钟为0
-
sys_rst_n <=
1
'b0;
//初始复位
-
money <=
1
'b0;
//投币初始化为0
-
#
5
//5个时钟周期后
-
sys_rst_n <=
1
'b1;
//拉高复位,系统进入工作状态
-
#
25
//25个时钟周期后
-
money <=
1
'b1;
//拉高投币信号
-
#
40
//40个时钟周期后
-
money <=
1
'b0;
//拉低投币信号
-
#
20
//25个时钟周期后
-
money <=
1
'b1;
//拉高投币信号
-
#
80
//25个时钟周期后
-
money <=
1
'b0;
//拉低投币信号
-
end
-
//------------<设置时钟>----------------------------------------------
-
always #
10 sys_clk = ~sys_clk;
//系统时钟周期20ns
-
-
//------------------------------------------------
-
//-- 状态机名称查看器
-
//------------------------------------------------
-
reg [
39:
0] state_name_cur;
//每字符8位宽,这里最多5个字符40位宽
-
reg [
39:
0] state_name_next;
//每字符8位宽,这里最多5个字符40位宽
-
-
always @(*) begin
-
case(FSM_Moore_2_inst.cur_state)
-
4
'b0001: state_name_cur =
"IDLE";
-
4
'b0010: state_name_cur =
"ONE";
-
4
'b0100: state_name_cur =
"TWO";
-
4
'b1000: state_name_cur =
"THREE";
-
default: state_name_cur =
"IDLE";
-
endcase
-
end
-
-
always @(*) begin
-
case(FSM_Moore_2_inst.next_state)
-
4
'b0001: state_name_next =
"IDLE";
-
4
'b0010: state_name_next =
"ONE";
-
4
'b0100: state_name_next =
"TWO";
-
4
'b1000: state_name_next =
"THREE";
-
default: state_name_next =
"IDLE";
-
endcase
-
end
-
-
endmodule
使用ModelSim执行仿真,仿真出来的波形如下所示:
从仿真结果可以看到:
- 在第2、3、5、6、7、8的6个时钟投入了6个硬币,理论上应该有2个可乐分别输出,实际也有两个可乐输出;
- 现态落后次态一个时钟周期,这是因为需要用次态去描述现态;
- 与一段式状态机不同,可乐的输出不会滞后一个时钟周期,这是因为采用了组合逻辑来描述输出;
- 第1次输出可乐的输入为1,第2次输出可乐的输入为0;这说明输出与输入无关(输入不管是0还是1都有输出);
- 状态的跳转符合我们绘制的状态转移图;
4.2、Mealy型(米勒型)二段式状态机
Verillog代码如下:
-
//------------------------------------------------
-
//-- 2段式状态机(Mealy )
-
//------------------------------------------------
-
-
//------------<模块及端口声明>----------------------------------------
-
module
FSM_Mealy_2(
-
input sys_clk ,
//输入系统时钟、50M
-
input sys_rst_n ,
//复位信号、低电平有效
-
input money ,
//投币输入,高电平有效
-
-
output reg cola
//可乐输出,高电平有效
-
);
-
-
//------------<状态机参数定义>------------------------------------------
-
localparam
IDLE =
3'b001,
-
ONE =
3'b010,
-
TWO =
3'b100;
-
-
//------------<reg定义>------------------------------------------------
-
reg [
2:
0] cur_state;
//定义现态
-
reg [
2:
0] next_state;
//定义次态
-
-
//-----------------------------------------------------------------------
-
//--状态机第一段:同步时序描述状态转移
-
//-----------------------------------------------------------------------
-
always@(posedge sys_clk or negedge sys_rst_n)begin
-
if(!sys_rst_n)
-
cur_state <=
IDLE;
//复位初始状态
-
else
-
cur_state <= next_state;
//次态转移到现态
-
end
-
-
//-----------------------------------------------------------------------
-
//--状态机第二段:组合逻辑判断状态转移条件,描述状态转移规律以及输出
-
//-----------------------------------------------------------------------
-
always@(*)begin
-
case(cur_state)
//组合逻辑
-
IDLE:begin
//根据当前状态、输入进行状态转换判断
-
//根据当前状态、输入进行输出
-
if(money)begin
//当前输入为1
-
next_state =
ONE;
//次态为ONE
-
cola =
1'b0;
//一共1元 ,没有可乐输出
-
end
-
else begin
//当前输入为0
-
next_state =
IDLE;
//次态为IDLE
-
cola =
1'b0;
//一共0元 ,没有可乐输出
-
end
-
end
-
ONE:begin
-
if(money)begin
//当前输入为1
-
next_state =
TWO;
//次态为TWO
-
cola =
1'b0;
//一共2元 ,没有可乐输出
-
end
-
else begin
//当前输入为0
-
next_state =
ONE;
//次态为ONE
-
cola =
1'b0;
//一共1元 ,没有可乐输出
-
end
-
end
-
TWO:begin
-
if(money)begin
//当前输入为1
-
next_state =
IDLE;
//次态为IDLE
-
cola =
1'b1;
//一共3元 ,输出可乐
-
end
-
else begin
//当前输入为0
-
next_state =
TWO;
//次态为TWO
-
cola =
1'b0;
//一共2元 ,没有可乐输出
-
end
-
end
-
default:begin
//默认状态同初始状态
-
if(money)begin
-
next_state =
ONE;
-
cola =
1'b0;
-
end
-
else begin
-
next_state =
IDLE;
-
cola =
1'b0;
-
end
-
end
-
endcase
-
end
-
-
endmodule
使用QuartusII编码生成的状态机视图如下:
可以看到,这和我们之前绘制的状态转移图一致。
编写Testbench文件进行仿真,仿真激励设置和Moore型一段式状态机一致,文件如下:
-
//------------------------------------------------
-
//-- 2段式状态机(Mealy)
-
//------------------------------------------------
-
`timescale
1ns/
1ns
-
-
//------------<模块及端口声明>----------------------------------------
-
module
tb_FSM_Mealy_2();
-
-
reg sys_clk;
-
reg sys_rst_n;
-
reg money;
-
wire cola;
-
-
//------------<例化被测试模块>----------------------------------------
-
FSM_Mealy_2
FSM_Mealy_2_inst(
-
.
sys_clk (sys_clk),
-
.
sys_rst_n (sys_rst_n),
-
.
money (money),
-
-
.
cola (cola)
-
);
-
-
//------------<设置初始测试条件>----------------------------------------
-
initial begin
-
sys_clk =
1
'b0;
//初始时钟为0
-
sys_rst_n <=
1
'b0;
//初始复位
-
money <=
1
'b0;
//投币初始化为0
-
#
5
//5个时钟周期后
-
sys_rst_n <=
1
'b1;
//拉高复位,系统进入工作状态
-
#
25
//25个时钟周期后
-
money <=
1
'b1;
//拉高投币信号
-
#
40
//40个时钟周期后
-
money <=
1
'b0;
//拉低投币信号
-
#
20
//25个时钟周期后
-
money <=
1
'b1;
//拉高投币信号
-
#
80
//25个时钟周期后
-
money <=
1
'b0;
//拉低投币信号
-
end
-
//------------<设置时钟>----------------------------------------------
-
always #
10 sys_clk = ~sys_clk;
//系统时钟周期20ns
-
-
//------------------------------------------------
-
//-- 状态机名称查看器
-
//------------------------------------------------
-
//1字符8位宽
-
reg [
31:
0] state_name_cur;
//每字符8位宽,这里最多4个字符32位宽
-
reg [
31:
0] state_name_next;
//每字符8位宽,这里最多4个字符32位宽
-
-
always @(*) begin
-
case(FSM_Mealy_2_inst.cur_state)
-
3
'b001: state_name_cur =
"IDLE";
-
3
'b010: state_name_cur =
"ONE";
-
3
'b100: state_name_cur =
"TWO";
-
default: state_name_cur =
"IDLE";
-
endcase
-
end
-
-
always @(*) begin
-
case(FSM_Mealy_2_inst.next_state)
-
3
'b001: state_name_next =
"IDLE";
-
3
'b010: state_name_next =
"ONE";
-
3
'b100: state_name_next =
"TWO";
-
default: state_name_next =
"IDLE";
-
endcase
-
end
-
-
endmodule
使用ModelSim执行仿真,仿真出来的波形如下所示:
从仿真结果可以看到:
- 在第2、3、5、6、7、8的6个时钟投入了6个硬币,理论上应该有2个可乐分别输出,实际也有两个可乐输出;
- 现态落后次态一个时钟周期,这是因为需要用次态去描述现态;
- 与一段式状态机不同,可乐的输出不会滞后一个时钟周期,这是因为采用了组合逻辑来描述输出;
- 第1次输出可乐的输入为1,第2次输出可乐的输入也为1;这说明输出与输入有关;
- 状态的跳转符合我们绘制的状态转移图;
通过以上,针对二段式状态机可以得出如下小结:
- Moore型状态机输出滞后Mealy型状态机一个时钟周期
- 二段式状态机的输出使用组合逻辑输出,而使用组合逻辑则无法避免的会引入“毛刺”问题
5、三段式状态机
三段式状态机在两个 always 模块描述方法基础上,使用三个always 模块,一个always 模块采用同步时序描述状态转移,一个 always 采用组合逻辑判断状态转移条件,描述状态转移规律,另一个 always 模块描述状态输出。
5.1、Moore型(摩尔型)三段式状态机
Verilog代码如下:
-
//==================================================================
-
//-- 3段式状态机(Moore)
-
//==================================================================
-
-
//------------<模块及端口声明>----------------------------------------
-
module
FSM_Moore_3(
-
input sys_clk ,
//输入系统时钟、50M
-
input sys_rst_n ,
//复位信号、低电平有效
-
input money ,
//投币输入,高电平有效
-
-
output reg cola
//可乐输出,高电平有效
-
);
-
-
//------------<状态机参数定义>------------------------------------------
-
localparam
IDLE =
4'b0001,
-
ONE =
4'b0010,
-
TWO =
4'b0100,
-
THREE =
4'b1000;
-
-
//------------<reg定义>-------------------------------------------------
-
reg [
3:
0] cur_state;
//定义现态寄存器
-
reg [
3:
0] next_state;
//定义次态寄存器
-
-
//-----------------------------------------------------------------------
-
//--状态机第一段:同步时序描述状态转移
-
//-----------------------------------------------------------------------
-
always@(posedge sys_clk or negedge sys_rst_n)begin
-
if(!sys_rst_n)
-
cur_state <=
IDLE;
//复位初始状态
-
else
-
cur_state <= next_state;
//次态转移到现态
-
end
-
-
//-----------------------------------------------------------------------
-
//--状态机第二段:组合逻辑判断状态转移条件,描述状态转移规律以及输出
-
//-----------------------------------------------------------------------
-
always@(*)begin
-
case(cur_state)
//组合逻辑
-
//根据当前状态、输入进行状态转换判断
-
IDLE:begin
-
if(money)
-
next_state =
ONE;
//投币1元,则状态转移到ONE
-
else
-
next_state =
IDLE;
//没有投币,则状态保持
-
end
-
ONE:begin
-
if(money)
-
next_state =
TWO;
//投币1元,则状态转移到TWO
-
else
-
next_state =
ONE;
//没有投币,则状态保持
-
end
-
TWO:begin
-
if(money)
-
next_state =
THREE;
//投币1元,则状态转移到THREE
-
else
-
next_state =
TWO;
//没有投币,则状态保持
-
end
-
THREE:begin
-
if(money)
-
next_state =
ONE;
//投币1元,则状态转移到ONE
-
else
-
next_state =
IDLE;
//没有投币,则状态保持
-
end
-
default:begin
//默认状态同IDLE
-
if(money)
-
next_state =
ONE;
-
else
-
next_state =
IDLE;
-
end
-
endcase
-
end
-
-
//-----------------------------------------------------------------------
-
//--状态机第三段:时序逻辑描述输出
-
//-----------------------------------------------------------------------
-
always@(posedge sys_clk or negedge sys_rst_n)begin
-
if(!sys_rst_n)
-
cola <=
1'b0;
//复位、初始状态
-
else
-
case(cur_state)
//根据当前状态进行输出
-
IDLE: cola <=
1'b0;
//无可乐输出
-
ONE: cola <=
1'b0;
//无可乐输出
-
TWO: cola <=
1'b0;
//无可乐输出
-
THREE: cola <=
1'b1;
//输出可乐
-
default:cola <=
1'b0;
//默认无可乐输出
-
endcase
-
end
-
-
endmodule
使用QuartusII编码生成的状态机视图如下:
可以看到,这和我们之前绘制的状态转移图一致。
编写Testbench文件进行仿真,仿真激励设置和Moore型一段式状态机一致,文件如下:
-
//------------------------------------------------
-
//-- 3段式状态机(Moore)
-
//------------------------------------------------
-
`timescale
1ns/
1ns
-
-
//------------<模块及端口声明>----------------------------------------
-
module
tb_FSM_Moore_3();
-
-
reg sys_clk;
-
reg sys_rst_n;
-
reg money;
-
-
wire cola;
-
-
//------------<例化被测试模块>----------------------------------------
-
FSM_Moore_3
FSM_Moore_3_inst(
-
.
sys_clk (sys_clk),
-
.
sys_rst_n (sys_rst_n),
-
.
money (money),
-
-
.
cola (cola)
-
);
-
-
//------------<设置初始测试条件>----------------------------------------
-
initial begin
-
sys_clk =
1
'b0;
//初始时钟为0
-
sys_rst_n <=
1
'b0;
//初始复位
-
money <=
1
'b0;
//投币初始化为0
-
#
5
//5个时钟周期后
-
sys_rst_n <=
1
'b1;
//拉高复位,系统进入工作状态
-
#
25
//25个时钟周期后
-
money <=
1
'b1;
//拉高投币信号
-
#
40
//40个时钟周期后
-
money <=
1
'b0;
//拉低投币信号
-
#
20
//25个时钟周期后
-
money <=
1
'b1;
//拉高投币信号
-
#
80
//25个时钟周期后
-
money <=
1
'b0;
//拉低投币信号
-
end
-
//------------<设置时钟>----------------------------------------------
-
always #
10 sys_clk = ~sys_clk;
//系统时钟周期20ns
-
-
//------------------------------------------------
-
//-- 状态机名称查看器
-
//------------------------------------------------
-
reg [
39:
0] state_name_cur;
//每字符8位宽,这里最多5个字符40位宽
-
reg [
39:
0] state_name_next;
//每字符8位宽,这里最多5个字符40位宽
-
-
always @(*) begin
-
case(FSM_Moore_3_inst.cur_state)
-
4
'b0001: state_name_cur =
"IDLE";
-
4
'b0010: state_name_cur =
"ONE";
-
4
'b0100: state_name_cur =
"TWO";
-
4
'b1000: state_name_cur =
"THREE";
-
default: state_name_cur =
"IDLE";
-
endcase
-
end
-
-
always @(*) begin
-
case(FSM_Moore_3_inst.next_state)
-
4
'b0001: state_name_next =
"IDLE";
-
4
'b0010: state_name_next =
"ONE";
-
4
'b0100: state_name_next =
"TWO";
-
4
'b1000: state_name_next =
"THREE";
-
default: state_name_next =
"IDLE";
-
endcase
-
end
-
-
endmodule
使用ModelSim执行仿真,仿真出来的波形如下所示:
从仿真结果可以看到:
- 在第2、3、5、6、7、8的6个时钟投入了6个硬币,理论上应该有2个可乐分别输出,实际也有两个可乐输出;
- 现态落后次态一个时钟周期,这是因为需要用次态去描述现态;
- 与二段式状态机不同,可乐的输出会滞后一个时钟周期,这是因为采用了时序逻辑来描述输出;
- 第1次输出可乐的输入为1,第2次输出可乐的输入为0;这说明输出与输入无关;
- 状态的跳转符合我们绘制的状态转移图;
- 波形图除了多了一个次态外,其余与一段式 的Moore状态机完全一致;
5.2、Mealy型(米勒型)三段式状态机
Verilog代码如下:
-
//==================================================================
-
//-- 3段式状态机(Mealy)
-
//==================================================================
-
-
//------------<模块及端口声明>----------------------------------------
-
module
FSM_Mealy_3(
-
input sys_clk ,
//输入系统时钟、50M
-
input sys_rst_n ,
//复位信号、低电平有效
-
input money ,
//投币输入,高电平有效
-
-
output reg cola
//可乐输出,高电平有效
-
);
-
-
//------------<状态机参数定义>------------------------------------------
-
localparam
IDLE =
3'b0001,
-
ONE =
3'b0010,
-
TWO =
3'b0100;
-
-
//------------<reg定义>-------------------------------------------------
-
reg [
3:
0] cur_state;
//定义现态寄存器
-
reg [
3:
0] next_state;
//定义次态寄存器
-
-
//-----------------------------------------------------------------------
-
//--状态机第一段:同步时序描述状态转移
-
//-----------------------------------------------------------------------
-
always@(posedge sys_clk or negedge sys_rst_n)begin
-
if(!sys_rst_n)
-
cur_state <=
IDLE;
//复位初始状态
-
else
-
cur_state <= next_state;
//次态转移到现态
-
end
-
-
//-----------------------------------------------------------------------
-
//--状态机第二段:组合逻辑判断状态转移条件,描述状态转移规律以及输出
-
//-----------------------------------------------------------------------
-
always@(*)begin
-
case(cur_state)
//组合逻辑
-
//根据当前状态、输入进行状态转换判断
-
IDLE:begin
-
if(money)
-
next_state =
ONE;
//投币1元,则状态转移到ONE
-
else
-
next_state =
IDLE;
//没有投币,则状态保持
-
end
-
ONE:begin
-
if(money)
-
next_state =
TWO;
//投币1元,则状态转移到TWO
-
else
-
next_state =
ONE;
//没有投币,则状态保持
-
end
-
TWO:begin
-
if(money)
-
next_state =
IDLE;
//投币1元,则状态转移到IDLE
-
else
-
next_state =
TWO;
//没有投币,则状态保持
-
end
-
default:begin
//默认状态同IDLE
-
if(money)
-
next_state =
ONE;
-
else
-
next_state =
IDLE;
-
end
-
endcase
-
end
-
-
//-----------------------------------------------------------------------
-
//--状态机第三段:时序逻辑描述输出
-
//-----------------------------------------------------------------------
-
always@(posedge sys_clk or negedge sys_rst_n)begin
-
if(!sys_rst_n)
-
cola <=
1'b0;
//复位、初始状态
-
else
-
case(cur_state)
//根据当前状态进行输出
-
IDLE: cola <=
1'b0;
//无可乐输出(因为输入不管是0、1都是输出0,所以省略写法)
-
ONE: cola <=
1'b0;
//无可乐输出(因为输入不管是0、1都是输出0,所以省略写法)
-
TWO:begin
-
if(money)
-
cola <=
1'b1;
//如果输入1,则输出可乐
-
else
-
cola <=
1'b0;
//如果输入0,则无可乐输出
-
end
-
default:cola <=
1'b0;
//默认无可乐输出
-
endcase
-
end
-
-
endmodule
使用QuartusII编码生成的状态机视图如下:
可以看到,这和我们之前绘制的状态转移图一致。
编写Testbench文件进行仿真,仿真激励设置和Moore型一段式状态机一致,文件如下:
-
//------------------------------------------------
-
//-- 3段式状态机(Mealy)
-
//------------------------------------------------
-
`timescale
1ns/
1ns
-
-
//------------<模块及端口声明>----------------------------------------
-
module
tb_FSM_Mealy_3();
-
-
reg sys_clk;
-
reg sys_rst_n;
-
reg money;
-
-
wire cola;
-
-
//------------<例化被测试模块>----------------------------------------
-
FSM_Mealy_3
FSM_Mealy_3_inst(
-
.
sys_clk (sys_clk),
-
.
sys_rst_n (sys_rst_n),
-
.
money (money),
-
-
.
cola (cola)
-
);
-
-
//------------<设置初始测试条件>----------------------------------------
-
initial begin
-
sys_clk =
1
'b0;
//初始时钟为0
-
sys_rst_n <=
1
'b0;
//初始复位
-
money <=
1
'b0;
//投币初始化为0
-
#
5
//5个时钟周期后
-
sys_rst_n <=
1
'b1;
//拉高复位,系统进入工作状态
-
#
25
//25个时钟周期后
-
money <=
1
'b1;
//拉高投币信号
-
#
40
//40个时钟周期后
-
money <=
1
'b0;
//拉低投币信号
-
#
20
//25个时钟周期后
-
money <=
1
'b1;
//拉高投币信号
-
#
80
//25个时钟周期后
-
money <=
1
'b0;
//拉低投币信号
-
end
-
//------------<设置时钟>----------------------------------------------
-
always #
10 sys_clk = ~sys_clk;
//系统时钟周期20ns
-
-
//------------------------------------------------
-
//-- 状态机名称查看器
-
//------------------------------------------------
-
reg [
39:
0] state_name_cur;
//每字符8位宽,这里最多5个字符40位宽
-
reg [
39:
0] state_name_next;
//每字符8位宽,这里最多5个字符40位宽
-
-
always @(*) begin
-
case(FSM_Mealy_3_inst.cur_state)
-
4
'b0001: state_name_cur =
"IDLE";
-
4
'b0010: state_name_cur =
"ONE";
-
4
'b0100: state_name_cur =
"TWO";
-
4
'b1000: state_name_cur =
"THREE";
-
default: state_name_cur =
"IDLE";
-
endcase
-
end
-
-
always @(*) begin
-
case(FSM_Mealy_3_inst.next_state)
-
4
'b0001: state_name_next =
"IDLE";
-
4
'b0010: state_name_next =
"ONE";
-
4
'b0100: state_name_next =
"TWO";
-
4
'b1000: state_name_next =
"THREE";
-
default: state_name_next =
"IDLE";
-
endcase
-
end
-
最后
以上就是愤怒美女为你收集整理的FPGA开发学习篇-Moore状态机和Mealy状态机2、状态机实例分析 3、一段式状态机4、二段式状态机5、三段式状态机的全部内容,希望文章能够帮你解决FPGA开发学习篇-Moore状态机和Mealy状态机2、状态机实例分析 3、一段式状态机4、二段式状态机5、三段式状态机所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复