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

 

posted @ 2023-05-10 15:33  million_yh  阅读(157)  评论(0)    收藏  举报