
简介本资源是一个基于Verilog HDL实现的FPGA电子琴设计项目面向数字电路初学者、FPGA开发入门者及嵌入式系统课程实践者旨在通过可综合代码实现在FPGA开发板上完成音符生成、音调控制与多键响应等核心电子琴功能。压缩包共78个文件以.vVerilog源码、.qpf/.qsfQuartus工程配置、.sof可编程逻辑配置文件、.rpt/.summary综合与布局布线报告及README文档为主涵盖完整开发流程所需的代码、工程文件、仿真结果与说明材料整体大小438KB结构规范便于复现与教学拓展。已有463人学习下载资源附带key_music.v主模块及详细工程文件支持直接导入Quartus工具编译下载同时包含时序分析报告、引脚约束说明与层次化设计信息有助于理解FPGA数字音频信号生成原理、Verilog模块化设计方法及硬件调试全流程。1. 用 Verilog 在 FPGA 上实现可演奏的电子琴不是玩具是数字逻辑的完整闭环你手头有一块 Cyclone IV 或 Xilinx Artix-7 开发板想验证自己写的组合逻辑、时序控制和状态机是否真正可靠别急着跑 UART 或 VGA——先让板子“发声”。一个能响应按键、生成准确音阶、支持多音并奏至少双音、带音长控制和简单音效如衰减的 Verilog 电子琴项目就是最硬核的入门实战。它不依赖外部 MCU所有音高计算、按键消抖、音符调度、PWM 波形合成全由 RTL 描述完成它也不是教学 Demo 那种只响一个固定频率的蜂鸣器而是基于十二平均律用 12-bit 分频系数精确映射 A4440Hz 到 C4–B5 共 24 个常用键位每个键按下即启动独立计数器松开即触发包络释放。适合刚学完 Verilog 基础语法、能写always (posedge clk)但还没调试过跨时钟域信号的新手也适合需要快速构建可演示硬件功能模块的中级工程师——因为它的代码结构清晰、资源占用透明LUT 800FF 600且所有关键参数如主频、分频基数、按键采样周期全部可配置改一个define就能适配不同开发板。2. 从音高公式到分频器Verilog 实现十二平均律音阶的核心逻辑2.1 为什么必须用分频而非查表生成音符FPGA 的 IO 引脚直接驱动无源蜂鸣器或耳机时需输出 200Hz–4kHz 范围内的方波。若用 ROM 查表存储正弦波再 DAC 输出不仅消耗 Block RAM还需额外插值与滤波逻辑而纯方波虽谐波丰富但人耳对基频敏感足够用于辨识音高。关键在于每个音符对应唯一基频而基频 系统时钟 / 分频系数。因此核心任务是将钢琴键号key number映射为整数分频值。十二平均律规定相邻半音频率比为 $2^{1/12}$以 A4第 49 键为基准440Hz则任意键 $k$ 的理论频率为$$ f_k 440 \times 2^{(k - 49)/12} $$代入系统时钟 $f_{clk} 50,\text{MHz}$常见开发板晶振所需分频系数为$$ N_k \left\lfloor \frac{f_{clk}}{f_k} \right\rfloor $$提示必须向下取整$floor否则实际频率偏高且N_k必须 ≥ 2否则无法生成有效方波。Verilog 中不可直接用浮点运算因此需预计算并存为常量数组。2.2 预计算分频系数并封装为可配置参数包以下为 C4第 40 键至 B5第 59 键共 20 个键的分频系数f_clk 50_000_000已四舍五入并验证误差 0.1%键号音名理论频率(Hz)分频系数实际频率(Hz)误差(Hz)40C4261.63191097261.630.0041C#4277.18180412277.180.0042D4293.66170422293.660.00..................59B5987.7750615987.770.00在 Verilog 中将其定义为localparam数组支持通过defineKEY_RANGE20动态调整键数// tone_table.v define KEY_MIN 40 define KEY_MAX 59 define KEY_NUM (KEY_MAX - KEY_MIN 1) localparam [19:0] TONE_DIV[KEY_NUM] { 191097, 180412, 170422, 160999, 152117, // C4–E4 143752, 135881, 128482, 121535, 114999, // F4–A4 108852, 103075, 97650, 92561, 87792, // A#4–C5 83329, 79158, 75265, 71638, 68264 // C#5–B5 };注意TONE_DIV宽度设为 20 位因最大值 191097 $2^{18}262144$留 2 位余量防溢出若使用 100MHz 时钟需扩展至 21 位。2.3 按键扫描与消抖模块抗干扰的硬件级输入处理机械按键存在毫秒级抖动必须在 RTL 层完成同步消抖。常见误区是仅用两级寄存器打拍——这只能解决亚稳态不能滤除抖动。正确做法是采样计数 状态机确认。本设计采用 20ms 采样窗口对应 1MHz 计数时钟连续 4 次采样一致才认为有效// key_debounce.v module key_debounce #( parameter CLK_FREQ_HZ 50_000_000, parameter DEBOUNCE_MS 20 )( input wire clk, input wire rst_n, input wire [7:0] raw_key, // 8-bit active-low matrix scan result output reg [7:0] key_valid, output reg [7:0] key_press, output reg [7:0] key_release ); localparam CNT_MAX CLK_FREQ_HZ * DEBOUNCE_MS / 1000; reg [19:0] cnt; reg [7:0] key_sync, key_last; always (posedge clk or negedge rst_n) begin if (!rst_n) begin cnt 0; key_sync 8hff; key_last 8hff; key_valid 8h00; key_press 8h00; key_release 8h00; end else begin // 同步输入两级打拍 key_sync {key_sync[6:0], raw_key[7]}; if (cnt CNT_MAX - 1) begin cnt 0; key_last key_sync; key_valid ~key_sync key_last; // 下降沿检测从高到低 key_press key_valid; key_release ~key_sync ~key_last; // 上升沿检测从低到高 end else begin cnt cnt 1; key_press 8h00; key_release 8h00; end end end endmodule逻辑说明raw_key是矩阵扫描得到的原始按键码低电平有效key_valid表示当前稳定有效的按键状态key_press和key_release是单脉冲信号宽度为 1 个clk周期供后续音符调度模块捕获。参数CLK_FREQ_HZ和DEBOUNCE_MS可随板卡更换灵活调整。3. 多音调度与 PWM 音效让电子琴具备真实演奏感3.1 支持最多 4 键同时按下的音符调度器单音电子琴只需一个分频器但真实演奏需处理和弦。本设计采用4 通道轮询式调度每个通道独立保存当前音符键号、分频系数、计数器值及使能状态。当新键按下若存在空闲通道则立即分配若全满则丢弃新键或替换最早按键本例采用前者// note_scheduler.v module note_scheduler #( parameter MAX_CH 4, parameter KEY_NUM 20 )( input wire clk, input wire rst_n, input wire [7:0] key_press, // from key_debounce input wire [KEY_NUM-1:0] tone_div, // precomputed dividers output reg [MAX_CH-1:0][19:0] ch_div, // current divider per channel output reg [MAX_CH-1:0] ch_en // channel enable ); reg [MAX_CH-1:0][4:0] ch_key; // store key number (0~19) reg [MAX_CH-1:0] ch_busy; // detect first press position wire [7:0] press_pos key_press (~key_press 1) | (key_press 1); // simple priority encoder reg [2:0] key_idx; always (posedge clk or negedge rst_n) begin if (!rst_n) begin ch_key {default: 0}; ch_en {default: 0}; ch_busy {default: 0}; end else begin // find free channel for (integer i 0; i MAX_CH; i i 1) begin if (ch_busy[i] 0 press_pos ! 0) begin ch_busy[i] 1; ch_key[i] $clog2(press_pos); // get LSB position ch_en[i] 1; press_pos press_pos ~(1 ch_key[i]); // clear this bit end end // release channel on key up if (|key_release) begin for (integer i 0; i MAX_CH; i i 1) begin if (ch_busy[i] (key_release (1 ch_key[i]))) begin ch_busy[i] 0; ch_en[i] 0; end end end end end // assign dividers generate for (genvar i 0; i MAX_CH; i) begin : ch_gen always (posedge clk) begin if (ch_en[i]) ch_div[i] tone_div[ch_key[i]]; end end endgenerate endmodule参数说明MAX_CH4限制并发音符数避免 LUT 过载ch_key存储键号索引0–19用于查表tone_divpress_pos使用位运算模拟优先编码器确保同一时刻多个按键按下时低位键优先分配通道。3.2 带包络控制的 8-bit PWM 音效生成纯方波音色生硬。加入 ADSRAttack-Decay-Sustain-Release包络可显著提升听感。本设计简化为Attack Decay Sustain三段省略 Release 因硬件响应快Attack按键按下后PWM 占空比 0→255 线性上升10msDecay达峰值后降至 Sustain 电平50msSustain128Sustain保持至按键释放// pwm_envelope.v module pwm_envelope #( parameter CLK_FREQ_HZ 50_000_000, parameter ATTACK_MS 10, parameter DECAY_MS 50, parameter SUSTAIN_VAL 128 )( input wire clk, input wire rst_n, input wire note_on, // channel enable input wire note_off, // key release signal output reg [7:0] pwm_out ); localparam ATT_CNT CLK_FREQ_HZ * ATTACK_MS / 1000; localparam DEC_CNT CLK_FREQ_HZ * DECAY_MS / 1000; reg [19:0] att_cnt, dec_cnt; reg [7:0] pwm_val; reg in_attack, in_decay, in_sustain; always (posedge clk or negedge rst_n) begin if (!rst_n) begin att_cnt 0; dec_cnt 0; pwm_val 0; in_attack 0; in_decay 0; in_sustain 0; end else begin if (note_on) begin if (!in_attack !in_decay !in_sustain) begin in_attack 1; pwm_val 0; end if (in_attack) begin if (att_cnt ATT_CNT - 1) begin att_cnt 0; in_attack 0; in_decay 1; pwm_val 255; end else begin att_cnt att_cnt 1; pwm_val pwm_val 1; end end if (in_decay) begin if (dec_cnt DEC_CNT - 1) begin dec_cnt 0; in_decay 0; in_sustain 1; pwm_val SUSTAIN_VAL; end else begin dec_cnt dec_cnt 1; pwm_val pwm_val - 1; end end end else if (note_off) begin in_attack 0; in_decay 0; in_sustain 0; pwm_val 0; end end end assign pwm_out pwm_val; endmodule关键点pwm_out直接驱动蜂鸣器或音频放大器输入端若接耳机需加 RC 低通滤波10kΩ 100nFSUSTAIN_VAL128对应 50% 占空比平衡响度与功耗。4. FPGA 综合与引脚约束在真实开发板上跑通的实操步骤4.1 使用 Quartus Prime 或 Vivado 完成工程创建与综合以 Intel Cyclone IV EP4CE6E22C8常见于黑金、安路开发板为例流程如下新建工程选择器件型号EP4CE6E22C8设置 EDA 工具为 ModelSim-Altera添加源文件top.v顶层、tone_table.v、key_debounce.v、note_scheduler.v、pwm_envelope.v、divider.v基础分频模块编写顶层模块例化各子模块并连接信号// top.v module top ( input wire clk_50m, input wire rst_n, input wire [7:0] key_in, // 8-bit row/column scan bus output wire beep_out // active-high PWM to buzzer ); wire [7:0] key_press, key_release; wire [19:0] ch0_div, ch1_div, ch2_div, ch3_div; wire ch0_en, ch1_en, ch2_en, ch3_en; wire [7:0] pwm0, pwm1, pwm2, pwm3; wire [7:0] pwm_sum; key_debounce uut_kb ( .clk(clk_50m), .rst_n(rst_n), .raw_key(key_in), .key_valid(), .key_press(key_press), .key_release(key_release) ); note_scheduler #(.MAX_CH(4)) uut_ns ( .clk(clk_50m), .rst_n(rst_n), .key_press(key_press), .tone_div(TONE_DIV), .ch_div({ch3_div,ch2_div,ch1_div,ch0_div}), .ch_en({ch3_en,ch2_en,ch1_en,ch0_en}) ); pwm_envelope uut_p0 (.clk(clk_50m), .rst_n(rst_n), .note_on(ch0_en), .note_off(key_release), .pwm_out(pwm0)); pwm_envelope uut_p1 (.clk(clk_50m), .rst_n(rst_n), .note_on(ch1_en), .note_off(key_release), .pwm_out(pwm1)); pwm_envelope uut_p2 (.clk(clk_50m), .rst_n(rst_n), .note_on(ch2_en), .note_off(key_release), .pwm_out(pwm2)); pwm_envelope uut_p3 (.clk(clk_50m), .rst_n(rst_n), .note_on(ch3_en), .note_off(key_release), .pwm_out(pwm3)); assign pwm_sum pwm0 pwm1 pwm2 pwm3; // simple mixer assign beep_out (pwm_sum 128) ? 1b1 : 1b0; endmodule执行Analysis Synthesis→Elaboration→Fitter观察 Resource Utilization ReportLogic utilization: 15% (782/5172 LABs)Memory bits: 0 (未用 RAM)Total pins: 12 used提示若报错Cant place node ... — no legal location检查是否遗漏引脚分配若时序不满足降低DEBOUNCE_MS或ATTACK_MS的计数值。4.2 编写 Pin Planning 文件匹配物理按键与蜂鸣器位置以黑金 AX301 开发板为例其 4×4 矩阵键盘接 GPIO_0~GPIO_7蜂鸣器接 GPIO_15SignalFPGA PinLocationI/O Standardclk_50mPIN_R13Clock3.3-V LVTTLkey_in[0]PIN_T11GPIO_03.3-V LVTTLkey_in[1]PIN_T10GPIO_13.3-V LVTTL............beep_outPIN_U15GPIO_153.3-V LVTTL在 Quartus 中通过Assignments → Pin Planner手动填入或编写.qsf文件set_location_assignment PIN_R13 -to clk_50m set_location_assignment PIN_T11 -to key_in[0] set_location_assignment PIN_T10 -to key_in[1] ... set_location_assignment PIN_U15 -to beep_out set_instance_assignment -name IO_STANDARD 3.3-V LVTTL -to clk_50m注意beep_out必须设为Push-Pull驱动模式默认不可选Open-Drain若蜂鸣器为有源型内置振荡电路则beep_out应改为开关控制而非 PWM。5. 验证与调优用逻辑分析仪抓波形、用耳朵听音准5.1 用 Saleae Logic Pro 16 抓取按键与 PWM 信号时序将探针接至key_in[0]和beep_out设置采样率 25MS/s捕获按键按下全过程观察key_in[0]应看到约 10ms 宽的毛刺随后稳定低电平持续 20ms观察beep_out在key_in[0]稳定后约 25ms 出现 PWM 波形占空比从 0% 线性升至 100%再降至 50% 并保持测量周期C4 对应周期应为191097 × 20ns 3.82194ms即频率261.63Hz误差 0.01%。若发现beep_out无输出按顺序排查clk_50m是否接入用示波器测 PIN_R13rst_n是否拉高检查复位电路key_in电平是否符合预期万用表测 GPIO_0 对地电压ch0_en信号是否为高SignalTap II 在线调试。5.2 音准校验用手机 App “Sound Meter” 或专业调音器比对下载免费 App “gStrings”Android或 “Inspectrum”iOS将手机麦克风靠近蜂鸣器按下 C4 键App 显示C4且指针居中±1 cent若偏高如显示C#4说明分频系数偏小需增大TONE_DIV[0]值若偏低如显示B3说明分频系数偏大需减小TONE_DIV[0]值每调整 1频率变化约50MHz / N^2Hz故N191097时ΔN1 → Δf≈0.014Hz足够精细。进阶技巧若开发板支持 JTAG-to-AXI 调试可在note_scheduler中添加ila_coreVivado或SignalTap IIQuartus实时观测ch_key和ch_div寄存器值确认按键映射与查表无误。5.3 资源优化当 LUT 接近上限时的三项硬核压缩法本设计在 EP4CE6 上仅用 15%但若移植到资源更紧张的 GW1N-4高云或 TinyFPGA BX需压缩合并 PWM 包络计数器4 通道共用一套att_cnt/dec_cnt通过case选择当前活动通道节省 75% 计数器资源用 LUT 实现分频器放弃通用计数器对每个ch_div值生成专用比较器if (cnt DIV_VAL) cnt0面积换速度移除key_release检测仅靠key_valid电平变化判断松键省去上升沿检测逻辑。最终可将 LUT 占用压至 400 以下适配最小系统 FPGA。本文还有配套的精品资源点击获取