FPGA巡游之旅——复杂可乐机设计
野火FPGA状态机扩展训练:
前面我们已经学习了很多FPGA相关的知识,包括语法、各种例子,以及相关的波形设计和代码编写方法,这里我们给大家留一个作业,要求以状态机为核心搭建一个小系统。具体要求为:
我们仍以可乐机为背景,一瓶可乐的价格还是2.5元。用按键控制投币(加入按键消抖功能),可以投0.5元硬币和1元硬币,投入0.5元后亮一个灯,投入1元后亮2个灯,投入1.5元后亮3个灯,投入2元后亮4个灯,如果投币后10s不再继续进行投币操作则可乐机回到初始状态。投入2.5元后出可乐不找零,此时led 灯实现单向流水操作,流水10s后自动停止;投入3元后出可乐找零,此时led灯实现双向流水操作,流水10s后自动停止。这里也有复位键,其功能是终止本次投币操作,使可乐机立刻回到初始状态。

测试后无bug
module simple_fsm
#(
parameter CNT_MAX = 20'd999_999, // 20ms按键消抖计数器
parameter CNT_MAX_WATER = 25'd24_999_999, // 0.5s流水灯计数器
parameter CNT_MAX_INIT = 29'd499_999_999 //10s归零计数器
)
(
input wire sys_clk, //系统时钟50MHz
input wire sys_rst_n, //全局复位
input wire pi_money_one, //投币1元
input wire pi_money_half, //投币0.5元
output wire [3:0] led_out // 输出控制led灯
);
//只有6种状态,使用独热码
parameter IDLE = 5'b00001;
parameter HALF = 5'b00010;
parameter ONE = 5'b00100;
parameter ONE_HALF = 5'b01000;
parameter TWO = 5'b10000;
/*10s归零 定义中间参数*/
reg [28:0] cnt_init;
reg cnt_init_flag;
/*流水灯 定义中间参数*/
reg [24:0] cnt ;
reg cnt_flag ;
reg turn_flag;
/*按键消抖 定义中间参数*/
reg [19:0] cnt_20ms; // 按键消抖计数器
reg key_flag;//1:表示消抖后检测到按键被按下;0:表示没有检测到按键被按下
/*状态机 定义中间参数*/
reg [4:0] state;
reg [3:0] led_out_reg;
reg po_money; //1:表示找零;0:表示不找零
reg po_cola; //1:出可乐;0:不出可乐
wire [1:0] pi_money;
/**********************10s归零代码****************************/
//cnt_init:计数器计数10s
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_init <= 29'b0;
else if(pi_money_half == 1'b0 || pi_money_one == 1'b0)//不能用消抖后的pi_money[0] || pi_money[1],否则有问题
cnt_init <= 29'b0;
else if(cnt_init == CNT_MAX_INIT)
cnt_init <= cnt_init;
else
cnt_init <= cnt_init + 1'b1;
//cnt_init_flag:计数器计数满10s标志信号
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_init_flag <= 1'b0;
else if(cnt_init == CNT_MAX_INIT)
cnt_init_flag <= 1'b1;
else
cnt_init_flag <= 1'b0;
/************************************************************************流水灯代码****************************************************************************/
//cnt:计数器计数500ms
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt <= 25'b0;
else if(cnt == CNT_MAX_WATER)
cnt <= 25'b0;
else
cnt <= cnt + 1'b1;
//cnt_flag:计数器计数满500ms标志信号
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_flag <= 1'b0;
else if(cnt == CNT_MAX_WATER - 1)
cnt_flag <= 1'b1;
else
cnt_flag <= 1'b0;
/************************************************************************按键消抖代码****************************************************************************/
//cnt_20ms:如果时钟的上升沿检测到外部按键输入的值为低电平时,计数器开始计数
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_20ms <= 20'b0;
else if(pi_money_half == 1'b1 && pi_money_one == 1'b1)//只要其中一个按键信号拉高 就是噪声,应该重新计数
cnt_20ms <= 20'b0;
else if((cnt_20ms == CNT_MAX) && ((pi_money_half == 1'b0) || (pi_money_one == 1'b0)))//当低电平记满20ms时,若其中之一的按键信号继续拉低,停止计数和清零,一直保持
cnt_20ms <= cnt_20ms;
else
cnt_20ms <= cnt_20ms + 1'b1;
//key_flag:当计数满20ms后产生按键有效标志位
//且key_flag在999_999时拉高,维持一个时钟的高电平
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
key_flag <= 1'b0;
else if(cnt_20ms == CNT_MAX-1)//当记到999_998时就拉高标志位,这样会得到一个时钟脉冲的高电平,若是写999_999,就会变成长电平
key_flag <= 1'b1;
else
key_flag <= 1'b0;
/************************************************************************状态机代码****************************************************************************/
//pi_money:为了减少变量的个数,我们使用位拼接把输入的两个1bit信号拼接成1个2bit信号
//投币方式可以为:不投币(00)、投0.5元(01)、投1元(10),每次只投一个币
assign pi_money = {!pi_money_one&&key_flag,!pi_money_half&&key_flag};//这样的按键消抖写法可以同时对多个按键进行消抖,注意前面20ms计数语句的写法
//第一段状态机,描述当前状态state如何根据输入跳转到下一状态
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_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 if(cnt_init_flag == 1'b1)
state <= IDLE;
else
state <= IDLE;
HALF : if(pi_money == 2'b01)
state <= ONE;
else if(pi_money == 2'b10)
state <= ONE_HALF;
else if(cnt_init_flag == 1'b1)
state <= IDLE;
else
state <= HALF;
ONE : if(pi_money == 2'b01)
state <= ONE_HALF;
else if(pi_money == 2'b10)
state <= TWO;
else if(cnt_init_flag == 1'b1)
state <= IDLE;
else
state <= ONE;
ONE_HALF : if(pi_money == 2'b01)
state <= TWO;
else if(pi_money == 2'b10)
state <= IDLE;
else if(cnt_init_flag == 1'b1)
state <= IDLE;
else
state <= ONE_HALF;
TWO : if(pi_money == 2'b01)
state <= IDLE;
else if(pi_money == 2'b10)
state <= IDLE;
else if(cnt_init_flag == 1'b1)
state <= IDLE;
else
state <= TWO;
default : state <= IDLE;
endcase
//第二段状态机,描述当前状态state和输入pi_money如何影响po_cola输出
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
po_cola <= 1'b0;
else if(cnt_init_flag == 1'b1)
po_cola <= 1'b0;
else if((state == TWO && pi_money == 2'b01)||(state == TWO && pi_money == 2'b10)||(state == ONE_HALF && pi_money == 2'b10)||(state == IDLE && po_cola == 1'b1))
po_cola <= 1'b1;
else
po_cola <= 1'b0;
//第二段状态机,描述当前状态state和输入pi_money如何影响po_money输出
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
po_money <= 1'b0;
else if (cnt_init_flag == 1'b1)
po_money <= 1'b0;
else if ((state == TWO && pi_money == 2'b10) || (state == IDLE && po_money == 1'b1))
po_money <= 1'b1;
else// if ((state == TWO && pi_money == 2'b01) || (state == IDLE && po_money == 1'b0))
po_money <= 1'b0;
//第二段状态机,描述当前状态state和输入pi_money如何影响led_out输出
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
led_out_reg <= 4'b0000;
else if (state == IDLE && po_cola == 1'b0)//IDLE时 但有可乐(找零) 就该单双向流水了,所以得加条件判断
led_out_reg <= 4'b0000;
else if (state == HALF)
led_out_reg <= 4'b0001;
else if (state == ONE)
led_out_reg <= 4'b0011;
else if (state == ONE_HALF)
led_out_reg <= 4'b0111;
else if (state == TWO)
led_out_reg <= 4'b1111;
else if (po_cola == 1'b1 && po_money == 1'b0)
// 单向流水 当led是4‘b1111时,再投0.5,跳变为4’b0001 led是4‘b0111时,再投1 跳变为4‘b0001
begin
if((led_out_reg == 4'b1111 || led_out_reg == 4'b0111) && cnt_flag == 1'b1)//单向流水
led_out_reg <= 4'b0001;
else if(cnt_flag == 1'b1)
led_out_reg <= {led_out_reg[2:0],led_out_reg[3]}; //左移
end
else if (po_cola == 1'b1 && po_money == 1'b1)
// 双向流水
begin
if(led_out_reg == 4'b1111 && cnt_flag == 1'b1 && turn_flag == 1'b0)
led_out_reg <= 4'b0001;//当满足双向流水条件时 先跳转到 4'b0001,然后开始双向流水
else if(cnt_flag == 1'b1 && turn_flag == 1'b0)
led_out_reg <= {led_out_reg[2:0],led_out_reg[3]};//左移 当左移到1000 且亮0.5s时,此时turn_flag为1 开始向右流水 0100 0010 0001
else if(cnt_flag == 1'b1 && turn_flag == 1'b1)//右移1000 0100 0010 0001
led_out_reg <= {led_out_reg[0],led_out_reg[3:1]};
end
//turn_flag:0:左移 1:右移
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
turn_flag <= 1'b0;//复位时默认为左移
else if(led_out_reg == 4'b1000)
turn_flag <= 1'b1;
else if(led_out_reg == 4'b0001)
turn_flag <= 1'b0;
assign led_out = ~led_out_reg;
endmodule
浙公网安备 33010602011771号