自动售卖机
描述
请设计状态机电路,实现自动售卖机功能,A饮料5元钱,B饮料10元钱,售卖机可接收投币5元钱和10元钱,每次投币只可买一种饮料,考虑找零的情况。
电路的接口如下图所示。sel信号会先于din信号有效,且在购买一种饮料时值不变。
- sel为选择信号,用来选择购买饮料的种类,sel=0,表示购买A饮料,sel=1,表示购买B饮料;
- din表示投币输入,din=0表示未投币,din=1表示投币5元,din=2表示投币10元,不会出现din=3的情况;
- drinks_out表示饮料输出,drinks_out=0表示没有饮料输出,drinks_out=1表示输出A饮料,drinks_out=2表示输出B饮料,不出现drinks_out =3的情况,输出有效仅保持一个时钟周期;
- change_out表示找零输出,change_out=0表示没有找零,change_out=1表示找零5元,输出有效仅保持一个时钟周期。
接口电路图如下:
输入描述:
input clk ,
input rst_n ,
input sel ,//sel=0,5$dranks,sel=1,10&=$drinks
input [1:0] din ,//din=1,input 5$,din=2,input 10$
输出描述:
output reg [1:0] drinks_out,
output reg change_out
解题思路
有限状态机(FSM)相关知识:
- 有限状态机是时序电路的通用模型,任何时序电路都可以表示为有限状态机;有限状态机从本质上讲是由寄存器与组合逻辑构成的时序电路,各个状态之间的转移总是在时钟的触发下进行的,状态信息存储在寄存器中。因为状态的个数是有限的所以称为有限状态机。
- 同其他时序电路一样,有限状态机也是由两部分组成:存储电路和组合逻辑电路。存储电路,用来生成状态机的状态;组合逻辑电路,用来提供输出以及状态机跳转的条件。
根据输出信号的产生方式,有限状态机可以分为米利型(Mealy)和莫尔型(Moore);
- Mealy型状态机的输出与当前状态和输入有关系;
- Moore型状态机的输出仅依赖当前状态而与输入无关;
在Verilog HDL中,有限状态机的写法较多,常用的有两段式和三段式两种。下面给出三段式的一般格式;
//1.同步时序always模块,格式化描述次态寄存器迁移到现态寄存器
always @(posedge clk or negedge rst_n) //异步复位
if (!rst_n) current_state <= IDLE;
else current_state <= next_state; //注意,使用的是非阻塞赋值
//2.组合逻辑always模块,描述状态转移条件判断
always @(current_state) begin//电平触发
begin
next_state = x ; //要初始化,使得系统复位后能进入正确的状态
case (current_state)
S1: if(...)
next_state = S2; //阻塞赋值
...
endcase
end
end
//3.同步时序always1模块,格式化描述次态寄存器输出
always @(posedge clk or negedge rst_n) begin
...//初始化
case(next_state)
S1:
out1 <= 1'b1 ; //非阻塞赋值
S2:
out2 <= 1'b1;
default: ... //default的作用是免除综合工具综合出锁存器
endcase
end
代码思路
根据售卖机中零钱数设置两个状态:
IDLE:售卖机里0元;
S1:售卖机中已投入5元;
对于找零(change_out)处理:当有饮料弹出的同时,才会退零钱,当没有饮料弹出时,即使售卖机中有钱,也会进入下一状态;
根据题目的要求,我们将一个完整的自动售卖机流程如下所示:
当售卖机里0元时:(即IDLE状态)
sel = 1'b0, din=2'b00(购买A饮料,未投币)时,drinks_out = 2'b00, change_out = 1'b0;(IDLE)
sel = 1'b0, din=2'b01(购买A饮料,投币5元)时,drinks_out = 2'b01, change_out = 1'b0;(IDLE)
sel = 1'b0, din=2'b10(购买A饮料,投币10元)时,drinks_out = 2'b01, change_out = 1'b1;(IDLE)
sel = 1'b1, din=2'b00(购买B饮料,未投币)时,drinks_out = 2'b00, change_out = 1'b0;(IDLE)
sel = 1'b1, din=2'b01(购买B饮料,投币5元)时,drinks_out = 2'b00, change_out = 1'b0;(S1)
sel = 1'b1, din=2'b10(购买B饮料,投币10元)时,drinks_out = 2'b10, change_out = 1'b0;(IDLE)
当售卖机里已投入5元时:(即S1状态)
sel = 1'b0, din=2'b00(购买A饮料,未投币)时,drinks_out = 2'b01, change_out = 1'b0; (IDLE)
sel = 1'b0, din=2'b01(购买A饮料,投币5元)时,drinks_out = 2'b01, change_out = 1'b1;(IDLE)
sel = 1'b0, din=2'b10(购买A饮料,投币10元)时,drinks_out = 2'b01, change_out = 1'b1;(S1)
sel = 1'b1, din=2'b00(购买B饮料,未投币)时,drinks_out = 2'b00, change_out = 1'b0;(S1)
sel = 1'b1, din=2'b01(购买B饮料,投币5元)时,drinks_out = 2'b00, change_out = 1'b0;(IDLE)
sel = 1'b1, din=2'b10(购买B饮料,投币10元)时,drinks_out = 2'b10, change_out = 1'b1;(IDLE)
`timescale 1ns/1ns
module sale(
input clk ,
input rst_n ,
input sel ,//sel=0,5$dranks,sel=1,10&=$drinks
input [1:0] din ,//din=1,input 5$,din=2,input 10$
output reg [1:0] drinks_out,//drinks_out=1,output 5$ drinks,drinks_out=2,output 10$ drinks
output reg change_out
);
parameter IDLE = 2'b01; //当前售卖机里0元
parameter S1 = 2'b10; //当前售卖机已存5元
reg [1:0] current_state, next_state;
//1.
always @(posedge clk or negedge rst_n) begin
if (!rst_n) current_state <= IDLE;
else current_state <= next_state;
end
//2.条件转移判断
wire [2:0] data_in;
assign data_in[2:0] = {sel, din};
always @(*) begin
case(current_state)
IDLE: case (data_in) //当前售卖机里0元
3'b000: begin next_state<= IDLE; end//购买A饮料, 投币0元
3'b001: begin next_state<= IDLE; end//购买A饮料, 投币5元
3'b010: begin next_state<= IDLE; end//购买A饮料, 投币10元
3'b100: begin next_state<= IDLE; end//购买B饮料, 投币0元
3'b101: begin next_state<= S1; end//购买B饮料, 投币5元
3'b110: begin next_state<= IDLE; end//购买B饮料, 投币10元
endcase
S1: case (data_in) 当前售卖机已存5元
3'b000: begin next_state<= IDLE; end//购买A饮料, 投币0元
3'b001: begin next_state<= IDLE; end//购买A饮料, 投币5元
3'b001: begin next_state<= S1 ; end//购买A饮料, 投币10元
3'b100: begin next_state<= S1; end//购买B饮料, 投币0元
3'b101: begin next_state<= IDLE; end//购买B饮料, 投币5元
3'b110: begin next_state<= IDLE; end//购买B饮料, 投币10元
endcase
default: next_state = IDLE;
endcase
end
//3.给出输出
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
drinks_out = 2'b00;
change_out = 1'b0;
end
else begin
case (current_state)
IDLE: case (data_in)
3'b000: begin drinks_out = 2'b00; change_out = 1'b0; end
3'b001: begin drinks_out = 2'b01; change_out = 1'b0; end
3'b010: begin drinks_out = 2'b01; change_out = 1'b1; end
3'b100: begin drinks_out = 2'b00; change_out = 1'b0; end
3'b101: begin drinks_out = 2'b00; change_out = 1'b0; end
3'b110: begin drinks_out = 2'b10; change_out = 1'b0; end
default: begin drinks_out = 2'b00; change_out = 1'b0; end
endcase
S1: case (data_in) //已有5元
3'b000: begin drinks_out = 2'b01; change_out = 1'b0; end
3'b001: begin drinks_out = 2'b01; change_out = 1'b1; end
3'b010: begin drinks_out = 2'b01; change_out = 1'b1; end
3'b100: begin drinks_out = 2'b00; change_out = 1'b0; end
3'b101: begin drinks_out = 2'b10; change_out = 1'b0; end
3'b110: begin drinks_out = 2'b10; change_out = 1'b1; end
default: begin drinks_out = 2'b00; change_out = 1'b0; end
endcase
default: begin
drinks_out = 2'b00; change_out = 1'b0;
end
endcase
end
end
endmodule