目录
交通灯和售卖机是常常作为状态机考察的题目,这种题目并不难,需要理清楚状态转移的过程,下面是2道例题
交通灯
要求实现一个交通红绿灯,具有红黄绿三个小指示灯和一个行人按钮,正常情况下,机动车道指示灯按照60时钟周期绿灯,5个时钟周期黄灯,10个时钟周期红灯循环。当行人按钮按下,如果剩余绿灯时间大于10个时钟,则缩短为10个时钟,小于10个时钟则保持不变。
注:机动车道的指示灯和人行道指示灯应该是配对的,当机动车道的灯为绿或者黄时,人行道的灯为红;当机动车道的灯为红时,人行道的灯为绿,为简便起见,只考虑机动车道的指示灯。
模块的信号接口图如下:
输入描述:
clk:系统时钟信号
rst_n:复位信号,低电平有效
pass_request:行人按钮信号,当该信号为1,表示按钮按下,如果剩余绿灯时间大于10个时钟,则缩短为10个时钟,小于10个时钟则保持不变。
输出描述:
clock:交通灯倒计时读数
red:该信号为1,表示红灯亮,为0表示红灯不亮
yellow:该信号为1,表示黄灯亮,为0表示黄灯不亮
green:该信号为1,表示黄灯亮,为0表示黄灯不亮
题解思路
首先考虑实现正常情况下的功能:机动车道指示灯按照60时钟周期绿灯,5个时钟周期黄灯,10个时钟周期红灯循环
首先设置一个倒计时的计数器,每个时钟计数器输出减一,当计数器计数到0时,切换切换显示灯颜色,同时重置计数器为相应颜色的持续时间。
三个颜色的指示灯之间的转化可以使用一个简单的状态机实现。在红灯状态下,红灯亮,其余两个灯灭。而且当cnt==0,即倒计时结束时切换到黄灯,否则停留在红灯状态。在黄灯灯状态下,黄灯亮,其余两个灯灭。而且当cnt==0,即倒计时结束时切换到绿灯,否则停留在黄灯状态。在绿灯状态下,绿灯亮,其余两个灯灭。而且当cnt==0,即倒计时结束时切换到红灯,否则停留在绿灯状态。
再考虑实现当行人按钮按下的情况:如果剩余绿灯时间大于10个时钟,则缩短为10个时钟,小于10个时钟则保持不变。
因此首先判断当前是否是绿灯,绿灯倒计时是否大于10,行人按钮是否被按下,如果这三个条件同时满足,则把倒计时置为10。
分析完成之后,可以画出状态转移图如下:
代码如下:
module traffic(
input clk,
input reset,
input pass_request,//行人按钮信号
output wire [7:0]clock,//交通灯倒计时读数
output reg red_out ,//该信号为1,表示红灯亮,为0表示红灯不亮
output reg yellow_out ,//该信号为1,表示黄灯亮,为0表示黄灯不亮
output reg green_out //该信号为1,表示黄灯亮,为0表示黄灯不亮
);
reg [7:0] cnt;//计数秒数
reg [2:0] current_state;
reg [2:0] next_state;
parameter IDLE =3'd0;
parameter GREEN =3'd1;
parameter YELLOW=3'd2;
parameter RED =3'd3;
parameter PASS =3'd4;
always@(posedge clk)begin
if(reset)begin
current_state<=3'd0;
end
else begin
current_state<=next_state;
end
end
always@(*)begin
if(reset)begin
next_state=IDLE;
end
else begin
case(current_state)
IDLE :begin
next_state=GREEN;
end
GREEN :begin
if(cnt>9&&pass_request==1)begin
next_state=PASS;
end
else if (cnt==0)begin
next_state=YELLOW;
end
end
YELLOW:begin
if (cnt==0)begin
next_state=RED;
end
end
RED :begin
if (cnt==0)begin
next_state=GREEN;
end
end
PASS :begin
if (cnt==0)begin
next_state=YELLOW;
end
end
endcase
end
end
always@(posedge clk)begin
if(reset)begin
cnt<=8'd0;
red_out <=1'd0;
yellow_out<=1'd0;
green_out <=1'd0;
end
else begin
case(current_state)
IDLE:begin
cnt<=8'd60;
end
GREEN :begin
green_out <=1'd1;//绿灯亮
if(cnt==8'd0)begin//跳转黄灯
green_out <=1'd0;
yellow_out<=1'd1;
cnt <=8'd5;
end
else if(cnt>8'd9&&pass_request==1) begin
cnt<=8'd9;
end
else begin
cnt<=cnt-8'd1;
end
end
YELLOW:begin
if(cnt==8'd0)begin//跳转红灯
green_out <=1'd0;
yellow_out<=1'd0;
red_out <=1'd1;
cnt <=8'd10;
end
else begin
cnt<=cnt-8'd1;
end
end
RED :begin
if(cnt==8'd0)begin//跳转绿灯
green_out <=1'd1;
yellow_out<=1'd0;
red_out <=1'd0;
cnt <=8'd60;
end
else begin
cnt<=cnt-8'd1;
end
end
PASS:begin
if(cnt==8'd0)begin//跳转黄灯
green_out <=1'd0;
yellow_out<=1'd1;
red_out <=1'd0;
cnt <=8'd5;
end
else begin
cnt<=cnt-8'd1;
end
end
endcase
end
end
assign clock=cnt;
endmodule
没有行人按下时: 有行人按下时:
自动售卖机
描述
请设计状态机电路,实现自动售卖机功能,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
按照题目的描述,自动售卖机的运行流程是:选择饮料——给钱——找零——出饮料——初始界面
我们也可以按照这个流程来写状态机:
状态机的难点在于找零操作上,解决方法是:在select(选择饮料)状态根据sel信号赋值给cnt0;在pay状态根据din信号赋值给cnt1,如果cnt1大于cnt0,意味着需要找钱,零钱=cnt1-cnt0
代码如下:
module sale(
input clk ,
input rst ,
input sel ,//sel=0,5$dranks,sel=1,10&=$drinks
input [1:0] din ,//din=1,input 5$,din=2,input 10$
output reg [2:0] drinks_out,//drinks_out=1,output 5$ drinks,drinks_out=2,output 10$ drinks
output reg [3:0] change_out
);
reg[3:0]cnt0;//饮料价格
reg[3:0]cnt1;//给的钱
reg [2:0] state;
parameter idle =3'd0;
parameter select=3'd1;
parameter pay =3'd2;
parameter money =3'd3;
parameter drink =3'd4;
parameter calculate=3'd5;
always@(posedge clk)begin
if(rst)begin
cnt0 <=4'd0;
cnt1 <=4'd0;
drinks_out<=1'd0;
change_out<=3'd0;
state<=idle;
end
else begin
case(state)
idle :begin
cnt0 <=4'd0;
cnt1 <=4'd0;
drinks_out<=1'd0;
change_out<=3'd0;
state<=select;
end
select:begin
if(sel==1'd0)begin//选择A饮料
cnt0 <=4'd10;
end
else if(sel==1'd1) begin//选择B饮料
cnt0 <=4'd5;
end
state<=pay;
end
pay:begin
if(din==2'd2)begin//投币10元
cnt1 <=4'd10;
end
else if(din==2'd1) begin//投币5元
cnt1 <=4'd5;
end
else if(din==2'd0)begin//投币0元
cnt1 <=4'd0;
end
state<=calculate;
end
calculate:begin
if(cnt1>cnt0)begin
state<=money;
end
else begin
state<=drink;
end
end
money:begin
change_out<=cnt1-cnt0;
state<=drink;
end
drink:begin
if(sel==1'd0)begin//输出A饮料
drinks_out<=3'd1;
end
else if(sel==1'd1) begin//输出B饮料
drinks_out<=3'd2;
end
state<=idle;
end
endcase
end
end
endmodule
买B饮料,给10元的情况