一、状态机简介
FPGA中的状态机是一种基于状态转移的设计方法,它可以实现复杂的控制逻辑和状态管理。状态机通常由状态寄存器、状态转移逻辑和输出逻辑组成。
状态寄存器用于存储当前状态,状态转移逻辑用于根据输入信号和当前状态计算下一个状态,输出逻辑用于根据当前状态和输入信号计算输出信号。状态机可以实现多种功能,例如序列检测、计数器、状态控制等。
在FPGA中,状态机可以使用Verilog或VHDL等硬件描述语言进行设计和实现。我们需要定义状态寄存器、状态转移逻辑和输出逻辑,并将其综合到FPGA中。状态机的设计需要考虑时序约束、时钟域等因素,以确保正确性和可靠性。
二、分类
状态机可以根据不同的特征进行分类,常见的分类方式包括以下几种:
-
Moore状态机和Mealy状态机:Moore状态机的输出只与当前状态有关,而Mealy状态机的输出不仅与当前状态有关,还与输入信号有关。
-
同步状态机和异步状态机:同步状态机的状态转移和输出逻辑都与时钟信号同步,而异步状态机的状态转移和输出逻辑不与时钟信号同步。
-
有限状态自动机和无限状态自动机:主要是指状态数是否是有限的。
-
顺序状态机和组合状态机:顺序状态机的输出不仅与当前输入有关,还与之前的输入有关,而组合状态机的输出只与当前输入有关。
-
硬件状态机和软件状态机:硬件状态机是在FPGA中实现的,而软件状态机是在软件中实现的。硬件状态机具有高效、低功耗、可重构等优点,而软件状态机具有灵活、易于修改等优点。
三、售货机实验
主要实现功能:除时钟和复位外,定义两种输入——0.5元和1元硬币;售卖机有5种状态——空闲、已投入0.5元到2元4种状态;定义两种输出——找零0.5元和出可乐。
四、代码
选用两段式,一段为时序逻辑描述转移,一段为组合逻辑描述输出。在进行综合时,系统会自动将这种代码识别为状态机。
`timescale 1ns / 1ps //设置时间单位
module cola_fsm2(
input wire clk,
input wire rst_n,
input wire pi_money_one,
input wire pi_money_half,
output wire po_cola,
output wire po_money
);
//定义参数类型
reg [2:0] state;
reg po_cola_p, po_money_p;
wire [1:0] pi_money;
//设置状态参数
parameter
idle=3'b000,half=3'b001,one=3'b010,onehalf=3'b011,
two=3'b100;
//使用拼接符赋值
assign pi_money = {pi_money_one,pi_money_half};
//时序逻辑描述转移
always @(posedge clk or negedge rst_n)
if(rst_n == 1'b0)
state <= idle;
else
case(state)
idle : if(pi_money == 2'b01)
state <= half;
else if(pi_money == 2'b10)
state <= one;
else
state <= idle;
half : if(pi_money == 2'b01)
state <= one;
else if(pi_money== 2'b10)
state <= onehalf;
else
state <= half;
one : if(pi_money == 2'b01)
state <= onehalf;
else if(pi_money== 2'b10)
state <= two;
else
state <= one;
onehalf:if(pi_money == 2'b01)
state <= two;
else if(pi_money == 2'b10)
state <= idle;
else
state <= onehalf;
two : if(pi_money == 2'b01)
state <= idle;
else if(pi_money== 2'b10)
state <= idle;
else
state <= two;
default: state <= idle;
endcase
//组合逻辑描述输出
always @ (posedge clk or negedge rst_n)
if (rst_n == 1'b0)
state <= idle;
else if (((state == two) && (pi_money == 2'b01)) || ((state==onehalf) && (pi_money==2'b10)))
//使用并行块非阻塞赋值
fork
po_cola_p <= 1'b1;
po_money_p <= 1'b0;
join
else if ((state == two )&& (pi_money == 2'b10))
fork
po_cola_p <= 1'b1;
po_money_p <= 1'b1;
join
else
fork
po_cola_p <= 1'b0;
po_money_p <= 1'b0;
join
//将寄存器值分配给输出
assign po_cola=po_cola_p;
assign po_money=po_money_p;
endmodule
五、编写仿真文件
`timescale 1ns / 1ps
module cola_fsm2_tb(
);
reg clk,rst_n,pi_money_one,pi_money_half;
reg random;
wire po_money,po_cola;
always #20 clk = ~clk; //产生一个以40ns为周期的时钟
//在前半个时钟使复位键处于高电平
initial
begin
rst_n <= 1'b0;
clk <= 1'b1;
#20
rst_n <= 1'b1;
end
//将投币面值设置为随机数,
always @(posedge clk or negedge rst_n)
if(rst_n == 1'b0)
random=0;
else
random <= {$random}%2;
always @(posedge clk or negedge rst_n)
if(rst_n == 1'b0)
fork
pi_money_half <= 1'b0;
pi_money_one <= 1'b0;
join
else
fork
pi_money_one <= random;
pi_money_half <= ~random;
join
//将状态和输入硬币与引用模块的成员连接
wire [2:0] state=cola_fsm2_tb.state;
wire [1:0] pi_money=cola_fsm2_tb.pi_money;
initial
begin
$timeformat(-9,0,"ns",6);//设置仿真时间格式,-9表示时间单位为ns、精度为1ns
//监视信号的值并在控制台上显示,监视的信号有变化时自动更新
$monitor("time=%t,state=%b,random=%b,po_money=%b,po_cola=%b",$time,state,random,po_money,po_cola);
end
//引用源模块
cola_fsm2 cola_fsm2_tb(
.clk(clk),
.rst_n(rst_n),
.pi_money_one(pi_money_one),
.pi_money_half(pi_money_half),
.po_cola(po_cola),
.po_money(po_money)
);
endmodule
六、仿真结果分析
观察仿真结果可知:在第2、3个时钟周期分别投入了0.5元,第4、5个时钟周期分别投入了1元,共计3元,此时state值为4(two状态),因为是非阻塞赋值,所以在下一个时钟周期才会吐出可乐和找零0.5元,同时state跳转为0(idle状态)。
声明:此实验为野火FPGA状态机教程之二,基本架构相同,代码有所修改,欢迎交流讨论。