此文章用来记录笔者学习三段式状态机的一些思考
需求介绍:自动售货机可以投入1元、2元、5元的硬币,一次只能投入一种面额的硬币,获取商品价格为6元,当投入的金额大于6元时,会进行找零
状态划分:
最少的输入次数:5+1/2/5 >=6 2次
最多的输入次数:1+1+1+!+!+! >= 6 6次
输出找零情况:0/1/2/3/4 即输出找零位宽定为3位宽即可
因此状态可以定义为6个,加上上电初始状态和找零状态一共8个状态
状态转移草图
接口分析
输入信号
i_sys_clk:系统工作时钟(50Mhz)
i_sys_rst:复位信号,高电平有效
i_one:投币1元的输入
i_two:投币两元的输入
i_five:投币五元的输入
输出信号
o_thing:输出商品
o_money:输出找零
代码编写
RTL代码
module vm_fsm(
input wire i_sys_clk ,//时钟信号
input wire i_sys_rst ,//复位信号
input wire i_one ,//1元
input wire i_two ,//2元
input wire i_five ,//5元
output reg o_thing ,//输出物品
output reg [2:0] o_money //找零
);
/***************参数**************************/
parameter MONEY_PAY = 'd6;
/***************状态机************************/
parameter IDIE = 8'b0000_0001,
IN1 = 8'b0000_0010,
IN2 = 8'b0000_0100,
IN3 = 8'b0000_1000,
IN4 = 8'b0001_0000,
IN5 = 8'b0010_0000,
IN6 = 8'b0100_0000,
DONE = 8'b1000_0000;
reg [7:0] r_cs_state ;
reg [7:0] r_ns_state ;
/***************寄存器************************/
/***************网表型************************/
/***************组合逻辑**********************/
reg [3:0] r_money_sum ;
/***************时序逻辑**********************/
/
//时序逻辑,锁存状态
always@(posedge i_sys_clk)begin
if(i_sys_rst == 1'b1)
r_cs_state <= IDIE;
else
r_cs_state <= r_ns_state;
end
/
//组合逻辑,状态变迁
always@(*)begin
case(r_cs_state)
IDIE :if((i_one) || (i_two) || (i_five))
r_ns_state = IN1;
else
r_ns_state = IDIE;
IN1 :if((i_one) || (i_two) || (i_five))
r_ns_state = IN2;
else
r_ns_state = IN1;
IN2 : if(r_money_sum >= MONEY_PAY)
r_ns_state = DONE;
else if((i_one) || (i_two) || (i_five))
r_ns_state = IN3;
else
r_ns_state = IN2;
IN3 :if(r_money_sum >= MONEY_PAY)
r_ns_state = DONE;
else if((i_one) || (i_two) || (i_five))
r_ns_state = IN4;
else
r_ns_state = IN3;
IN4 :if(r_money_sum >= MONEY_PAY)
r_ns_state = DONE;
else if((i_one) || (i_two) || (i_five))
r_ns_state = IN5;
else
r_ns_state = IN4;
IN5 :if(r_money_sum >= MONEY_PAY)
r_ns_state = DONE;
else if((i_one) || (i_two) || (i_five))
r_ns_state = IN6;
else
r_ns_state = IN5;
IN6 :if(r_money_sum >= MONEY_PAY)
r_ns_state = DONE;
else if((i_one) || (i_two) || (i_five))
r_ns_state = DONE;
else
r_ns_state = IN6;
DONE :r_ns_state = IDIE;
default:r_ns_state = IDIE;
endcase
end
/
//r_money_sum
always@(posedge i_sys_clk)begin
if(i_sys_rst == 1'b1)
r_money_sum <= 'd0;
else begin
case(r_cs_state)
DONE:r_money_sum <= 'd0;
default:begin
if(i_one)
r_money_sum <= r_money_sum + 1'b1;
else if(i_two)
r_money_sum <= r_money_sum + 'd2;
else if(i_five)
r_money_sum <= r_money_sum + 'd5;
else
r_money_sum <= r_money_sum;
end
endcase
end
end
/
//o_thing
always@(posedge i_sys_clk)begin
if(r_cs_state == DONE)
o_thing <= 1'b1;
else
o_thing <= 1'b0;
end
/
//o_money
always@(posedge i_sys_clk)begin
if(r_cs_state == DONE)
o_money <= r_money_sum - MONEY_PAY;
else
o_money <= 'd0;
end
endmodule
仿真测试代码
`timescale 1ns/1ns
module tb_vlg_design();
reg i_sys_clk ;//时钟信号
reg i_sys_rst ;//复位信号
reg i_one ;//1元
reg i_two ;//2元
reg i_five ;//5元
reg [31:0] state ;//用来观察状态机的变量
wire o_thing ;//输出物品
wire [2:0] o_money ;
//产生时钟和复位
initial
begin
i_sys_clk = 1'b1;
i_sys_rst <= 1'b1;
#100
i_sys_rst <= 1'b0;
end
always #10 i_sys_clk = ~i_sys_clk;
//产生投入硬币输入激励
integer i;
initial
begin
i_one <= 1'b0;
i_two <= 1'b0;
i_five <= 1'b0;
@(negedge i_sys_rst);//等待复位完成
//调用50次任务
@(posedge i_sys_clk);
for(i=0;i<50;i=i+1)begin
input_money();
end
end
//简化输入激励产生
//并且每次只有一种硬币投入
integer random_data;
task input_money();
begin
random_data <= {$random} % 3;
@(posedge i_sys_clk);
begin
if(random_data == 'd0)
i_one <= 1'b1;
else if(random_data == 'd1)
i_two <= 1'b1;
else if(random_data == 'd2)
i_five <= 1'b1;
else ;
end
@(posedge i_sys_clk);
begin
i_one <= 1'b0;
i_two <= 1'b0;
i_five <= 1'b0;
end
end
endtask
//状态机观察
always@(*)
begin
case(vm_fsm_inst.r_cs_state)
8'b0000_0001 :state = "IDIE" ;
8'b0000_0010 :state = "IN1" ;
8'b0000_0100 :state = "IN2" ;
8'b0000_1000 :state = "IN3" ;
8'b0001_0000 :state = "IN4" ;
8'b0010_0000 :state = "IN5" ;
8'b0100_0000 :state = "IN6" ;
8'b1000_0000 :state = "DONE" ;
default:state = "IDIE" ;
endcase
end
//例化
vm_fsm vm_fsm_inst
(
. i_sys_clk (i_sys_clk ),//时钟信号
. i_sys_rst (i_sys_rst ),//复位信号
. i_one (i_one ),//1元
. i_two (i_two ),//2元
. i_five (i_five ),//5元
. o_thing (o_thing ),//输出物品
. o_money (o_money ) //输出找零
);
endmodule
仿真分析
总结
1、FPGA具有并行处理的特性,固使用RTL代码设计的电路都具有并行性。
在实际的电路中,当需要处理“前后顺序的事件时”,就有了状态机的引入
2、绘制状态转移图的三要素
1)输入:决定状态机的状态跳转和输出,(当输入不足以使状态机进行跳转时,可以使用内部的变量来辅助状态机的跳转,这也是工程中最常用的方式)
2)输出:根据"当前时刻的输入和状态“决定,是状态机设计的主要目的
3)状态:根据输入和上一状态决定当前时刻所处的状态,是状态机系统执行的一个稳定的过程
3、状态机的重点
如何将实际问题抽象成对应的状态,是需要不断去练习才能掌握的,也是状态机的精髓。状态是控制流程中的节点,问题的复杂度决定了状态的多少。状态间具有互斥性,在同一时刻,状态机仅可能具有一个状态。
在实际的应用中,不用去刻意区分moore型状态机还是mealy型状态机,使用状态机的目的就是需要利用能处理“前后顺序的事件”的特点来解决实际问题