Verilog 数字电路课设:饮料贩卖机 3 大核心模块设计与 FPGA 引脚分配实战

📅 2026/7/9 19:19:44
Verilog 数字电路课设:饮料贩卖机 3 大核心模块设计与 FPGA 引脚分配实战
Verilog 数字电路课设饮料贩卖机 3 大核心模块设计与 FPGA 引脚分配实战在数字电路与 FPGA 开发的实践教学中饮料贩卖机是一个经典而富有挑战性的课程设计项目。它不仅涵盖了状态机、时序逻辑、组合逻辑等基础概念还需要学生掌握模块化设计思维和工程实现能力。本文将聚焦三个核心模块——按键防抖、BCD 转换与状态机控制通过完整的代码解析与 FPGA 引脚分配方案带你从零构建一个功能完善的饮料贩卖机系统。1. 系统架构与模块划分一个典型的饮料贩卖机系统通常包含以下功能单元投币模块处理 1 元和 0.5 元硬币的输入商品选择模块记录用户选择的饮料数量计算模块实时计算投币总额与应找零金额显示模块通过数码管显示金额信息状态控制模块管理整个购买流程的状态转换在 Verilog 实现中我们将系统划分为以下可综合的子模块module vending_machine( input clk, // 50MHz 系统时钟 input rst_n, // 低电平复位信号 input yuan_btn, // 1元投币按钮 input jiao_btn, // 0.5元投币按钮 input item_btn, // 商品选择按钮 input buy_btn, // 购买确认按钮 output [7:0] seg, // 数码管段选信号 output [5:0] dig, // 数码管位选信号 output take_led, // 取货指示灯 output change_led, // 找零指示灯 output error_led // 错误指示灯 );1.1 顶层模块连接关系系统的顶层模块主要负责各子模块的信号连接与协调。下图展示了主要模块间的数据流------------- ------------- --------------- | | | | | | | 按键防抖模块 |-----| 金额计算模块 |-----| 显示控制模块 | | | | | | | ------------- ------------- --------------- ↑ ↑ | | | v ------------- --------------- ------------- | | | | | | | 物理按钮输入 | | 状态控制模块 |----| 数码管输出 | | | | | | | ------------- --------------- -------------1.2 关键参数定义在开始具体模块设计前我们需要明确几个关键参数商品单价1.5 元/瓶最大投币金额99.5 元实际应用中可根据需要调整最大购买数量7 瓶由 3 位二进制计数器限制数码管显示格式XX.X十位、个位、小数位各占 1 位数码管2. 核心模块设计与实现2.1 按键防抖模块设计机械按键在闭合和断开时会产生 5-10ms 的抖动这会导致 FPGA 误判多次按键操作。我们的防抖模块采用状态机设计能有效滤除抖动信号。module debounce ( input clk, // 50MHz 时钟 input button_in, // 原始按键输入 input rst_n, // 低电平复位 output reg button_out // 消抖后输出 ); // 20ms 计数器50MHz时钟下需计数1,000,000次 reg [19:0] counter; reg button_sync; // 同步器消除亚稳态 always (posedge clk or negedge rst_n) begin if (!rst_n) begin button_sync 1b1; end else begin button_sync button_in; end end // 防抖状态机 localparam IDLE 2b00; localparam CHECK 2b01; localparam CONFIRM 2b10; reg [1:0] state; always (posedge clk or negedge rst_n) begin if (!rst_n) begin state IDLE; counter 20d0; button_out 1b1; end else begin case (state) IDLE: begin if (button_sync ! button_out) begin state CHECK; counter 20d0; end end CHECK: begin if (counter 20d999_999) begin state CONFIRM; counter 20d0; end else begin counter counter 1b1; if (button_sync button_out) begin state IDLE; end end end CONFIRM: begin button_out button_sync; state IDLE; end end case end end endmodule该模块实现了经典的防抖状态机具有以下特点同步器设计通过两级寄存器消除亚稳态20ms 防抖窗口确保稳定检测按键状态状态机控制清晰划分空闲、检测和确认三个状态提示实际应用中防抖时间可根据具体按键特性调整通常范围在 10-50ms 之间。2.2 BCD 转换与金额计算模块本模块负责将二进制计数值转换为 BCD 码并计算总金额和找零金额。这是系统的核心计算单元。module money_calculator ( input clk, input rst_n, input [2:0] yuan_count, // 1元硬币数量 input [2:0] jiao_count, // 0.5元硬币数量 input [2:0] item_count, // 商品数量 output reg [11:0] total_money, // 总金额BCD码 output reg [11:0] change_money // 找零BCD码 ); // 二进制转BCD码模块 function [3:0] bin2bcd; input [2:0] bin; begin case (bin) 3d0: bin2bcd 4b0000; 3d1: bin2bcd 4b0001; 3d2: bin2bcd 4b0010; 3d3: bin2bcd 4b0011; 3d4: bin2bcd 4b0100; 3d5: bin2bcd 4b0101; 3d6: bin2bcd 4b0110; 3d7: bin2bcd 4b0111; default: bin2bcd 4b0000; endcase end endfunction // 计算总金额和找零 always (posedge clk or negedge rst_n) begin if (!rst_n) begin total_money 12h000; change_money 12h000; end else begin // 计算总金额1元2个0.5元 total_money[3:0] (jiao_count[0]) ? 4b0101 : 4b0000; // 小数位 total_money[7:4] bin2bcd(yuan_count) bin2bcd(jiao_count[2:1]); // 个位 total_money[11:8] (yuan_count jiao_count[2:1] 9) ? 4b0001 : 4b0000; // 十位进位 // 计算找零商品单价1.5元3个0.5元 if ((yuan_count*2 jiao_count) (item_count*3)) begin // 足够支付 change_money[3:0] ((yuan_count*2 jiao_count - item_count*3) % 2) ? 4b0101 : 4b0000; change_money[7:4] bin2bcd((yuan_count*2 jiao_count - item_count*3) 1); change_money[11:8] 4b0000; end else begin // 不足支付找零等于投币金额 change_money total_money; end end end endmodule该模块实现了以下关键功能二进制到BCD转换通过查找表实现3位二进制到4位BCD的转换金额计算将1元和0.5元统一转换为0.5元单位进行计算找零逻辑比较总金额与商品总价计算应找零金额2.3 状态控制与显示模块状态控制模块管理整个购买流程包括投币、选择商品、确认购买等状态转换并控制数码管显示内容。module state_controller ( input clk, input rst_n, input buy_pressed, // 购买按钮按下 input [11:0] total_money, input [11:0] change_money, output reg take_led, output reg change_led, output reg error_led, output reg [7:0] seg, output reg [5:0] dig ); // 状态定义 localparam IDLE 2b00; localparam INPUT 2b01; localparam CONFIRM 2b10; localparam RESULT 2b11; reg [1:0] current_state; reg [1:0] next_state; // 数码管显示内容选择 reg [11:0] display_data; // 数码管段码表 reg [7:0] seg_table [0:9]; initial begin seg_table[0] 8b11111100; // 0 seg_table[1] 8b01100000; // 1 seg_table[2] 8b11011010; // 2 seg_table[3] 8b11110010; // 3 seg_table[4] 8b01100110; // 4 seg_table[5] 8b10110110; // 5 seg_table[6] 8b10111110; // 6 seg_table[7] 8b11100000; // 7 seg_table[8] 8b11111110; // 8 seg_table[9] 8b11110110; // 9 end // 状态寄存器 always (posedge clk or negedge rst_n) begin if (!rst_n) begin current_state IDLE; end else begin current_state next_state; end end // 状态转移逻辑 always (*) begin case (current_state) IDLE: next_state INPUT; INPUT: begin if (buy_pressed) begin next_state CONFIRM; end else begin next_state INPUT; end end CONFIRM: begin next_state RESULT; end RESULT: begin if (!buy_pressed) begin next_state IDLE; end else begin next_state RESULT; end end default: next_state IDLE; endcase end // 输出逻辑 always (posedge clk or negedge rst_n) begin if (!rst_n) begin take_led 1b0; change_led 1b0; error_led 1b0; display_data 12h000; end else begin case (current_state) IDLE: begin take_led 1b0; change_led 1b0; error_led 1b0; display_data 12h000; end INPUT: begin display_data total_money; end CONFIRM: begin if (total_money {item_count*1, 4b0101}) begin // 1.5元/瓶 error_led 1b0; display_data change_money; end else begin error_led 1b1; display_data total_money; end end RESULT: begin if (total_money {item_count*1, 4b0101}) begin take_led 1b1; change_led 1b1; end else begin take_led 1b0; change_led 1b0; end end endcase end end // 数码管扫描显示 reg [2:0] scan_counter; always (posedge clk or negedge rst_n) begin if (!rst_n) begin scan_counter 3d0; seg 8h00; dig 6b000000; end else begin scan_counter scan_counter 1b1; case (scan_counter) 3d0: begin // 显示十位 dig 6b000001; seg seg_table[display_data[11:8]]; end 3d1: begin // 显示个位带小数点 dig 6b000010; seg {seg_table[display_data[7:4]][7:1], 1b1}; end 3d2: begin // 显示小数位 dig 6b000100; seg seg_table[display_data[3:0]]; end default: begin dig 6b000000; seg 8h00; end endcase end end endmodule该模块的主要特点包括四状态状态机清晰划分购买流程的各个阶段动态显示控制根据状态切换数码管显示内容扫描显示技术通过时分复用驱动6位数码管错误处理机制在金额不足时显示错误并退款3. FPGA 实现与引脚分配3.1 时钟与复位电路设计FPGA 实现时需要考虑时钟和复位信号的处理时钟信号使用开发板上的 50MHz 晶振作为系统时钟复位电路采用低电平有效的复位设计可通过按键触发// 时钟分频模块示例 module clock_divider ( input clk_50m, output reg clk_1k ); reg [15:0] counter; always (posedge clk_50m) begin if (counter 16d24999) begin counter 16d0; clk_1k ~clk_1k; end else begin counter counter 1b1; end end endmodule3.2 引脚分配策略合理的引脚分配对系统稳定性和调试便利性至关重要。以下是基于典型 FPGA 开发板的引脚分配建议FPGA 信号开发板资源引脚号备注clk50MHz 晶振PIN_90系统主时钟rst_n复位按键PIN_24低电平有效yuan_btn按键1PIN_421元投币jiao_btn按键2PIN_320.5元投币item_btn按键3PIN_43商品选择buy_btn按键4PIN_44购买确认seg[0]数码管dp段PIN_86seg[1]数码管g段PIN_103seg[2]数码管f段PIN_110seg[3]数码管e段PIN_106seg[4]数码管d段PIN_111seg[5]数码管c段PIN_104seg[6]数码管b段PIN_100seg[7]数码管a段PIN_112dig[1]数码管位选1PIN_126最右侧数码管dig[2]数码管位选2PIN_115dig[3]数码管位选3PIN_125dig[4]数码管位选4PIN_121dig[5]数码管位选5PIN_113dig[6]数码管位选6PIN_120最左侧数码管take_ledLED0PIN_46取货指示灯change_ledLED1PIN_50找零指示灯error_ledLED2PIN_52错误指示灯注意实际引脚分配需根据具体开发板原理图调整上表仅供参考。3.3 时序约束与优化为确保系统稳定工作需要在 FPGA 工具中添加适当的时序约束# 时钟约束 create_clock -name clk -period 20 [get_ports clk] # 输入延迟约束 set_input_delay -clock clk 2 [get_ports {yuan_btn jiao_btn item_btn buy_btn}] # 输出延迟约束 set_output_delay -clock clk 2 [get_ports {seg[*] dig[*] take_led change_led error_led}]4. 调试技巧与常见问题解决4.1 仿真测试方法在硬件实现前建议先进行仿真测试。以下是一个简单的测试平台示例timescale 1ns/1ps module vending_machine_tb; reg clk; reg rst_n; reg yuan_btn; reg jiao_btn; reg item_btn; reg buy_btn; wire [7:0] seg; wire [5:0] dig; wire take_led; wire change_led; wire error_led; vending_machine uut ( .clk(clk), .rst_n(rst_n), .yuan_btn(yuan_btn), .jiao_btn(jiao_btn), .item_btn(item_btn), .buy_btn(buy_btn), .seg(seg), .dig(dig), .take_led(take_led), .change_led(change_led), .error_led(error_led) ); // 时钟生成 initial begin clk 0; forever #10 clk ~clk; end // 测试用例 initial begin // 初始化 rst_n 0; yuan_btn 1; jiao_btn 1; item_btn 1; buy_btn 1; // 复位 #100 rst_n 1; // 模拟投币3元2个1元2个0.5元 #100 yuan_btn 0; #100 yuan_btn 1; #100 yuan_btn 0; #100 yuan_btn 1; #100 jiao_btn 0; #100 jiao_btn 1; #100 jiao_btn 0; #100 jiao_btn 1; // 选择2瓶饮料 #100 item_btn 0; #100 item_btn 1; #100 item_btn 0; #100 item_btn 1; // 确认购买 #100 buy_btn 0; #100 buy_btn 1; // 等待结果 #1000; // 复位准备下一次购买 #100 rst_n 0; #100 rst_n 1; #1000; $finish; end endmodule4.2 常见问题与解决方案按键响应不稳定检查防抖模块参数是否合适确认物理按键接触良好增加防抖时间至 30-50ms数码管显示闪烁或错乱确认扫描频率在 100Hz-1kHz 范围内检查段选和位选信号时序确保共阴/共阳配置正确金额计算错误检查 BCD 转换逻辑确认二进制加法是否考虑了进位仿真验证各中间计算结果状态机卡死检查所有可能的状态转移路径确保复位信号能正确初始化状态机添加超时机制防止死锁4.3 性能优化建议流水线设计将金额计算分为多个时钟周期完成提高系统时钟频率资源共享多个数码管显示复用同一套译码逻辑格雷码编码状态机状态使用格雷码编码减少毛刺时钟门控对不常变化的模块使用时钟使能信号降低功耗5. 扩展功能与进阶设计基础功能实现后可以考虑以下扩展方向5.1 多商品支持修改系统架构支持多种商品选择// 扩展的商品选择接口 input [1:0] item_sel; // 商品选择信号 input item_confirm; // 商品确认按钮 // 商品价格表 reg [7:0] price_table [0:3]; initial begin price_table[0] 8h15; // 1.5元 price_table[1] 8h20; // 2.0元 price_table[2] 8h25; // 2.5元 price_table[3] 8h30; // 3.0元 end5.2 库存管理添加库存管理功能当商品售罄时显示缺货信息// 库存计数器 reg [3:0] stock [0:3]; initial begin stock[0] 4d5; // 商品A库存5个 stock[1] 4d3; // 商品B库存3个 stock[2] 4d7; // 商品C库存7个 stock[3] 4d0; // 商品D已售罄 end // 库存检查逻辑 always (posedge clk) begin if (item_confirm stock[item_sel] 0) begin out_of_stock 1b1; end end5.3 数据持久化使用 FPGA 的 Block RAM 或外部 EEPROM 实现销售数据存储// 销售统计模块 module sales_stats ( input clk, input rst_n, input [1:0] item_sold, output reg [31:0] total_sales ); reg [15:0] item_count [0:3]; reg [15:0] revenue; always (posedge clk or negedge rst_n) begin if (!rst_n) begin item_count[0] 16d0; item_count[1] 16d0; item_count[2] 16d0; item_count[3] 16d0; revenue 16d0; end else if (sale_valid) begin item_count[item_sold] item_count[item_sold] 1b1; revenue revenue price_table[item_sold]; end end endmodule5.4 网络通信接口通过 UART 或 SPI 接口与上位机通信实现远程监控module uart_interface ( input clk, input rst_n, input rx, output tx, input [31:0] sales_data, output reg [7:0] control_cmd ); // UART 收发器实现 // 9600bps, 8N1 配置 // 数据帧格式 // 55 AA [长度] [命令] [数据] [校验和] endmodule