FPGA不同于CPU的一点特点就是CPU是顺序执行的,而FPGA是同步执行(并行)的。那么FPGA如何处理明显具有时间上先后顺序的事件呢?这个时候我们就需要使用到状态机了。
状态机简写为 FSM(Finite State Machine),也称为同步有限状态机,我们一般简称为状态机,之所以说“同步”是因为状态机中所有的状态跳转都是在时钟的作用下进行的,而“有限”则是说状态的个数是有限的。状态机的每一个状态代表一个事件,从执行当前事件到执行另一事件我们称之为状态的跳转或状态的转移,我们需要做的就是执行该事件然后跳转到一下时间,这样我们的系统就“活”了,可以正常的运转起来了。状态机通过控制各个状态的跳转来控制 流程,使得整个代码看上去更加清晰易懂,在控制复杂流程的时候,状态机优势明显。
根据状态机的输出是否与输入条件相关,可将状态机分为两大类,即摩尔(Moore)型状态机和米勒(Mealy) 型状态机。
米勒状态机的模型如下图所示,模型中第一个方框是指产生下一状态的组合逻辑 F,F 是当前状态和输 入信号的函数,状态是否改变、如何改变,取决于组合逻辑 F 的输出;第二框图是指状态寄存器,其由一 组触发器组成,用来记忆状态机当前所处的状态,状态的改变只发生在时钟的跳边沿;第三个框图是指产生输出的组合逻辑 G,状态机的输出是由输出组合逻辑 G 提供的,G 也是当前状态和输入信号的函数。
摩尔状态机的模型如下图所示,对比米勒状态机的模型可以发现,其区别在于米勒状态机的输出由当 前状态和输入条件决定的,而摩尔状态机的输出只取决于当前状态。
根据状态机的实际写法,状态机可以分为一段式、二段式和三段式状态机。
接下来对一个简单的可乐售卖系统使用状态机的思想进行分析。
可乐售卖系统:可乐机每次只能投入 1 枚 1 元硬币,且每瓶可乐卖 3 元钱,即投入 3 个硬币就可以让可乐机出可乐,如果投币不够 3 元想放弃投币需要按复位键,否则之前投入的钱不能退回。
首先分析会有哪些输入、输出信号:
输入信号:
sys_clk_n:既然是同步状态机,那么时钟是肯定少不了的,这里设定时钟是50MHz;
sys_rst_n:一个稳健的系统肯定需要一个复位,这里设定位低电平有效;
money:投币输入,高电平表示投入一元,低电平表示没有投币;
输出信号:
cola:可乐输出,高电平表示掉落一瓶可乐,低电平表示没有可乐掉落;
根据以上输入、输出可以画出状态机模块的示意框图:
接下来需要想一想这个状态机是怎么运作的,也就是要绘制这个系统的状态转移图。前面提到,状态机的状态转移有和输入挂钩的Mealy 状态机,也有和输入无关的Moore 状态机。所以接下来分别用Moore 状态机、Mealy 状态机的思想来绘制状态转移图:
Moore 状态机(输出和输入无关):
根据上面列出的这些状态可以绘制出如下的状态转移图(1/0:前面的1代表输入,后面的0代表输出):
Mealy 状态机(输出和输入相关):
根据上面列出的这些状态可以绘制出如下的状态转移图(1/0:前面的1代表输入,后面的0代表输出):
从上面的分析可以得到以下结论:
接下来就各种写法对状态机进行仿真分析。
一段式状态机是将整个状态机写到一个 always模块里面,在该模块中既描述状态转移,又描述状态的输入和输出。不推荐采用这种状态机,因为从代码风格方面来讲,一般都会要求把组合逻辑和时序逻辑分开;从代 码维护和升级来说,组合逻辑和时序逻辑混合在一起不利于代码维护和修改,也不利于约束。
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 [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执行仿真,仿真出来的波形如下所示:
可以看到:
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 [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执行仿真,仿真出来的波形如下所示:
从仿真结果可以看到:
通过以上,针对一段式状态机可以得出如下小结:
二段式状态机用两个 always 模块来描述状态机,其中一个 always 模块采用同步时序描述状态转移;另一个 模块采用组合逻辑判断状态转移条件,描述状态转移规律以及输出。不同于一段式状态机的是,它需要定 义两个状态,现态和次态,然后通过现态和次态的转换来实现时序逻辑。
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 [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执行仿真,仿真出来的波形如下所示:
从仿真结果可以看到:
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 [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执行仿真,仿真出来的波形如下所示:
从仿真结果可以看到:
通过以上,针对二段式状态机可以得出如下小结:
三段式状态机在两个 always 模块描述方法基础上,使用三个always 模块,一个always 模块采用同步时序描述状态转移,一个 always 采用组合逻辑判断状态转移条件,描述状态转移规律,另一个 always 模块描述状态输出。
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 [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执行仿真,仿真出来的波形如下所示:
从仿真结果可以看到:
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 [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
endmodule
使用ModelSim执行仿真,仿真出来的波形如下所示:
从仿真结果可以看到:
通过以上,针对三段式状态机可以得出如下小结:
状态机的三种描述方法:
一段式:整个状态机写到一个always模块里面,在该模块中既描述状态转移,又描述状态的输入和输出。
二段式:用两个always模块来描述状态机,其中一个always模块采用同步时序描述状态转移;另一个模块采用组合逻辑判断状态转移条件,描述状态转移规律以及输出。
三段式:在两个always模块描述方法基础上,使用三个always模块,一个always模块采用同步时序描述状态转移,一个always采用组合逻辑判断状态转移条件,描述状态转移规律,另一个always模块描述状态输出。
应该选择哪一种状态机 ?
一段式状态机写法不够模块化 ,且过于臃肿不利于维护,及布局布线;
二段式状态机将同步时序和组合逻辑分别放到不同的always模块中实现,这样做的好处不仅仅是便于阅读、理解、维护,更重要的是利于综合器优化代码,利于用户添加合适的时序约束条件,利于布局布线器实现设计。但是其当前状态的输出用组合逻辑实现,组合逻辑很容易产生毛刺,而且不利于约束,不利于综合器和布局布线器实现高性能的设计。
三段式状态机与二段式状态机相比,关键在于根据状态转移规律,在上一状态根据输入条件判断出当前状态的输出,从而在不插入额外时钟节拍的前提下,实现了寄存器输出,解决了毛刺问题。实际应用中三段式状态机使用最多,因为三段式状态机将组合逻辑和时序分开,有利于综合器分析优 化以及程序的维护;并且三段式状态机将状态转移与状态输出分开,使代码看上去更加清晰易懂,提高了代码的可读性,推荐大家使用三段式状态机。
三段式状态机的基本格式:
第一个 always 语句实现同步状态跳转;
第二个 always 语句采用组合逻辑判断状态转移条件;
第三个 always 语句采用时序逻辑描述状态输出。
状态机的编码方式:
独热码:和格雷码相比,虽然独热码多用了触发器,但所用组合电路可以省一些,因而使电路的速度和可靠性有显著提高,而总的单元数并无显著增加。因为独热码只有一位的变化,所以更适用于高速系统。
格雷码:使用了更多的组合逻辑资源,但是比独热码能表示更多的状态。
2进制:使用了更多的组合逻辑资源,但是比独热码能表示更多的状态,稳定性不如格雷码。
三段式状态机的第三段采用next_state还是cur_state:
第三段使用next_state和cur_state的区别在于,当状态跳转时,基于next_state的输出是立刻变化的,而基于cur_state输出会延迟一个周期,其他情况都一样,应该根据自己的时序要求选择。
modelsim显示状态机名称的方法:
1.在Testbench中添加如下语句:实质上就是在测试文件里添加一个变量来代替你要观察的变量;
2.在modelsim的波形仿真见面右击波形,选择Radix--ASSIC。
//------------------------------------------------
//-- 状态机名称查看器
//------------------------------------------------
reg [39:0] state_name_cur; //每字符8位宽,这里最多5个字符40位宽(THREE)
reg [39:0] state_name_next; //每字符8位宽,这里最多5个字符40位宽(THREE)
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 开发指南--正点原子
FPGA Verilog 开发实战指南--基于 Intel Cyclone IV--野火电子
FPGA三段式状态机的思维陷阱