目录
交通灯和售卖机是常常作为状态机考察的题目,这种题目并不难,需要理清楚状态转移的过程,下面是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元的情况
