基于FPGA,实现自动售货机Verilog

前言

        本文章为FPGA设计的实例,基于Verilog,来源于学习FPGA中进行的一项练习,为了能够更快更好的学习FPGA。这也是一项比较全面的一次练习。

设计思路

        平台:Quartus;仿真:Altera_modelsim
        使用状态机进行设计,通过按键进行状态的跳转,电路接口如图所示:

设计一个有A,B,C三种商品的自动售货机,通过按键进行选择,状态跳转,以及投币,找零的流程。
状态机的设计:
IDLE(空闲):数码管上显示 A3b5C1;
PICK(挑选):通过按键1进行选择ABC商品,通过按键2选择购买的数量;
BUY(购买计算):选择好商品以及数量以后,计算出需要的总金额并显示在数码管上,通过按键1,按键2进行投币;
ZL(找零):投币金额减去商品的总金额,将找零的金额显示在数码管上;
CH(出货):当投币金额>=商品总金额后,LED灯闪烁以及数码管显示FFFFFF。

代码部分

        控制模块代码

/**************************************************************
@File    :   ctrl.v
@Time    :   2024/03/26 10:06:48
@Author  :   huangmo777x7 
@EditTool:   VS Code 
@Font    :   UTF-8 
@Function:   自动售货机控制模块
**************************************************************/

module ctrl (
    input                   clk             ,
    input                   rst_n           ,
    input           [2:0]   key_in          ,
    output  reg     [23:0]  display_data    ,
    output  reg     [5:0]   display_point   ,
    output  reg     [5:0]   display_mask    ,
    output  reg     [3:0]   led             
);


//中间信号定义
    localparam  IDLE = 5'b00001,    //空闲状态
                PICK = 5'b00010,    //选择商品以及数量
                BUY  = 5'b00100,    //进行投币,key_in[1]+1  key_in[2]+5
                ZL   = 5'b01000,    //找零    投币-金额=找的钱
                CH   = 5'b10000;    //商品出货
    
    reg     [4:0]       state  ;

    wire    idle2pick   ;     
    wire    pick2buy    ;    
    wire    buy2zl      ;  
    wire    buy2ch      ;  
    wire    zl2ch       ; 
    wire    ch2idle     ;   

    reg     [5:0]   pick_sel;
    reg     [3:0]   a,b,c;

    wire    [6:0]   sum;
    wire    [3:0]   sum_g,sum_s;
    reg     [6:0]   pay_sum;
    wire    [3:0]   pay_sum_g,pay_sum_s;
    wire    [6:0]   diff_sum;
    wire    [3:0]   diff_sum_g,diff_sum_s;

    reg [26:0] cnt_delay;
    wire		add_delay_cnt,end_delay_cnt;
    parameter TIME_2S = 100_000_000;

    reg     [24:0]  cnt_mask;
    wire		    add_mask_cnt,end_mask_cnt;
    parameter       TIME_500MS = 25_000_000;

    wire    [23:0]  display_idle    ;
    wire    [23:0]  display_pick    ;
    wire    [23:0]  display_buy     ;
    wire    [23:0]  display_zl      ;
    wire    [23:0]  display_ch      ;

    reg [22:0] cnt_led;
    wire		add_led_cnt,end_led_cnt ;
    parameter   TIME_100MS = 5_000_000  ;

/******************************************************************
                            状态机设计
*******************************************************************/
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)
            state <= IDLE;
        else case(state)
            IDLE : if(idle2pick) state <= PICK  ;
            PICK : if(pick2buy)  state <= BUY   ;
            BUY  : if(buy2zl)    state <= ZL    ;
                    else if(buy2ch) state <= CH ;
            ZL   : if(zl2ch)     state <= CH    ;
            CH   : if(ch2idle)   state <= IDLE  ;
        endcase
    end

    assign idle2pick = state == IDLE && key_in[0];
    assign pick2buy  = state == PICK && key_in[0];
    assign buy2zl    = state == BUY  && pay_sum > sum;
    assign buy2ch    = state == BUY  && pay_sum == sum;
    assign zl2ch     = state == ZL   && end_delay_cnt ;
    assign ch2idle   = state == CH   && end_delay_cnt ;
    
/******************************************************************
                        各个状态功能
*******************************************************************/
    //通过按键1进行数码管选位
    always@(posedge clk or negedge rst_n)begin
        if(!rst_n)
            pick_sel <= 6'b000001;
        else if(key_in[1] && state == PICK)
            pick_sel <= {pick_sel[3:0],pick_sel[5:4]};
        else if(pick2buy)
            pick_sel <= 6'b000001;
    end

    //通过按键2进行计数
    always@(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            a <= 'b0;
            b <= 'b0;
            c <= 'b0;
        end
        else if(key_in[2] && state == PICK)
            case(pick_sel)
                6'b000001 : if(c == 9) c <= 0; else c <= c + 1 ; 
                6'b000100 : if(b == 9) b <= 0; else b <= b + 1 ;
                6'b010000 : if(a == 9) a <= 0; else a <= a + 1 ;
            endcase
        else if(ch2idle)begin
            a <= 'b0;
            b <= 'b0;
            c <= 'b0;
        end
    end

    //sum   商品的总价
        assign  sum = 3*a + 5*b + 1*c;
        assign  sum_g = sum % 10;
        assign  sum_s = sum / 10;

    //pay_sum   投币的金额
        always@(posedge clk or negedge rst_n)
            if(!rst_n)
                pay_sum <= 'b0;
            else if(key_in[2] && state == BUY)      
                pay_sum <= pay_sum + 5;
            else if(key_in[1] && state == BUY)
                pay_sum <= pay_sum + 1;
            else if(ch2idle)
                pay_sum <= 'b0;

        assign  pay_sum_g = pay_sum % 10;
        assign  pay_sum_s = pay_sum / 10;

    //找零=投币的金额-商品的总价
        assign  diff_sum   = pay_sum - sum;
        assign  diff_sum_g = diff_sum % 10;
        assign  diff_sum_s = diff_sum / 10;
    
    //ZL和CH状态的延时计数器
    always @(posedge clk or negedge rst_n)
        if(!rst_n)
            cnt_delay <= 'd0;
        else if(add_delay_cnt)begin 
            if(end_delay_cnt) 
                cnt_delay <= 'd0;
            else 
                cnt_delay <= cnt_delay + 1'b1;
        end
    assign add_delay_cnt = ((state == ZL) || (state == CH));
    assign end_delay_cnt = add_delay_cnt && cnt_delay == TIME_2S - 1 ;

/******************************************************************
                            显示数据输出
*******************************************************************/
    //各个状态数码管所显示的
    always@(*)
        case(state)
            IDLE    : begin display_data = display_idle ; display_point = 6'b010100; end
            PICK    : begin display_data = display_pick ; display_point = 6'b010100; end
            BUY     : begin display_data = display_buy  ; display_point = 6'b001000; end
            ZL      : begin display_data = display_zl   ; display_point = 6'b000000; end
            CH      : begin display_data = display_ch   ; display_point = 6'b000000; end
            default : begin display_data = display_idle ; display_point = 6'b111111; end
        endcase

    assign display_idle = {4'd10,4'd3,4'd11,4'd5,4'd12,4'd1};
    assign display_pick = {4'd10,a,4'd11,b,4'd12,c};
    assign display_buy  = {4'd13,sum_s,sum_g,4'd5,pay_sum_s,pay_sum_g};
    assign display_zl   = {4'd1,4'd1,4'd1,4'd14,diff_sum_s,diff_sum_g};
    assign display_ch   = {4'd15,4'd15,4'd15,4'd15,4'd15,4'd15};

    //掩码
    always@(posedge clk or negedge rst_n)
        if(!rst_n)
            display_mask <= 6'b111111;
        else case(state)
            PICK    : if(end_mask_cnt)
                case(pick_sel)
                    6'b000001 : begin display_mask <= {5'b11111,~display_mask[0]};      end 
                    6'b000100 : begin display_mask <= {3'b111,~display_mask[2],2'b11};  end
                    6'b010000 : begin display_mask <= {1'b1,~display_mask[4],4'b1111};  end
                    default   : ;
                endcase
            ZL      : display_mask <= 6'b000111;
            default : display_mask <= 6'b111111;
        endcase

    //选择数码管闪烁计数器
    always @(posedge clk or negedge rst_n)
        if(!rst_n)
            cnt_mask <= 'd0;
        else if(add_mask_cnt)begin 
            if(end_mask_cnt) 
                cnt_mask <= 'd0;
            else 
                cnt_mask <= cnt_mask + 1'b1;
        end
    assign add_mask_cnt = 1;
    assign end_mask_cnt = add_mask_cnt && cnt_mask == TIME_500MS - 1 ;

/******************************************************************
                    出货时LED流水闪烁
*******************************************************************/
    always@(posedge clk or negedge rst_n)
        if(!rst_n)
            led <= 'b0;
        else if(state == CH && end_led_cnt)
            if(led == 'b0 || led == 4'b0001)
                led <= 4'b1000;
            else 
                led <= (led >> 1);
        else if(state != CH)
            led <= 'b0;
            
    always @(posedge clk or negedge rst_n)
        if(!rst_n)
            cnt_led <= 'd0;
        else if(add_led_cnt)begin 
            if(end_led_cnt) 
                cnt_led <= 'd0;
            else 
                cnt_led <= cnt_led + 1'b1;
        end
    assign add_led_cnt = 1;
    assign end_led_cnt = add_led_cnt && cnt_led == TIME_100MS - 1 ;

endmodule

仿真代码

/**************************************************************
@File    :   ctrl_tb.v
@Time    :   2024/03/26 15:46:14
@Author  :   haungmo777x7
@EditTool:   VS Code 
@Font    :   UTF-8 
@Function:   仿真ctrl模块文件
**************************************************************/
`timescale 1ns/1ps
module ctrl_tb ();

parameter CLK_CYCLE = 20;
reg	sys_clk,sys_rst_n;

always #(CLK_CYCLE/2) sys_clk = ~sys_clk;
initial begin
    sys_clk = 1'b1;
    sys_rst_n = 1'b0;
    #(CLK_CYCLE*2);
    sys_rst_n = 1'b1;
end

reg     [2:0]   key_in;     // 按键
wire    [3:0]   led;        //LED灯

defparam ctrl.TIME_2S      = 200;   //重定义时间,让仿真快一些
defparam ctrl.TIME_500MS   = 50;    //重定义时间,让仿真快一些
defparam ctrl.TIME_100MS   = 10;    //重定义时间,让仿真快一些
ctrl ctrl(
    /* input                */.clk      (sys_clk)   ,
    /* input                */.rst_n    (sys_rst_n) ,
    /* input       [2:0]    */.key_in   (key_in )   ,
    /* output               */.led      (led)
);

initial begin
    key_in = 0;
    #(CLK_CYCLE*20);

//进入状态(激活售货机)
    my_key(0);

//买三件C
    my_key(2);
    my_key(2);
    my_key(2);   //3

//切换到B商品
    my_key(1);   

//买两件B
    my_key(2);
    my_key(2);   //10

//切换到A
    my_key(1);

//买1件A
    my_key(2);   //3

//结算
    my_key(0);

//故意多给1块  17
    my_key(1);
    my_key(1);
    my_key(2);
    my_key(2);
    my_key(2);

    #2000;
    $stop;
end

task my_key;
    input [1:0] num;
    begin
        key_in[num] = 1;
        #(CLK_CYCLE*1);
        key_in[num] = 0;
        #(CLK_CYCLE*1000);
    end
endtask

endmodule

仿真结果

仿真结果也是调试了很多次代码,结果如图所示,将数码管驱动模块以及按键消抖模块加入即可上板进行验证。

上板验证效果:
 

111

  • 17
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值