赞
踩
本文章为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
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。