FM频率调制 + 包络发生器 + 多音色 — 用FPGA产生丰富音色!
🏆 成就:FM音色师 ✅ Verilator验证通过
FM(频率调制)合成是1980年代雅马哈DX7合成器的核心算法——用一个正弦波(调制器)去调制另一个正弦波(载波)的频率,产生丰富的泛音!
// 数控振荡器(NCO) - 用相位累加器生成正弦波
module nco #(
parameter PHASE_W = 16, // 相位位宽
parameter FREQ_W = 20 // 频率控制字位宽
)(
input wire clk,
input wire rst,
input wire [FREQ_W-1:0] freq_word, // 频率控制字
output wire [7:0] sine_out, // 正弦波输出(0~255)
output wire [7:0] saw_out // 锯齿波输出
);
// 相位累加器
reg [PHASE_W-1:0] phase;
always @(posedge clk) begin
if (rst) phase <= 0;
else phase <= phase + freq_word[PHASE_W-1:0];
end
// 锯齿波 = 相位直出
assign saw_out = phase[PHASE_W-1:PHASE_W-8];
// 正弦波查找表(256点, 8位)
reg [7:0] sine_lut [0:255];
initial begin
// 预计算正弦表: sin(2π*i/256) * 127 + 128
integer i;
for(i=0; i<256; i=i+1) begin
// 简化: 用分段线性近似
if (i < 64)
sine_lut[i] = 128 + (i * 2);
else if (i < 128)
sine_lut[i] = 255 - ((i - 64) * 2);
else if (i < 192)
sine_lut[i] = 128 - ((i - 128) * 2);
else
sine_lut[i] = (i - 192) * 2;
end
end
assign sine_out = sine_lut[phase[PHASE_W-1:PHASE_W-8]];
endmodule// ADSR包络发生器
// Attack-Decay-Sustain-Release 四段包络
module adsr_envelope (
input wire clk,
input wire rst,
input wire gate, // 1=音符开启, 0=释放
input wire [3:0] attack, // 攻击速率
input wire [3:0] decay, // 衰减速率
input wire [7:0] sustain, // 持续电平
input wire [3:0] release, // 释放速率
output reg [7:0] level // 当前包络电平
);
localparam IDLE=0, ATK=1, DCY=2, SUS=3, REL=4;
reg [2:0] state;
reg [15:0] timer;
always @(posedge clk) begin
if (rst) begin
state <= IDLE; level <= 0; timer <= 0;
end else begin
case(state)
IDLE: begin
level <= 0;
if (gate) state <= ATK;
end
ATK: begin
if (!gate) state <= REL;
else if (level >= 255) state <= DCY;
else level <= level + (attack + 1);
end
DCY: begin
if (!gate) state <= REL;
else if (level <= sustain) state <= SUS;
else level <= level - (decay + 1);
end
SUS: begin
if (!gate) state <= REL;
else level <= sustain;
end
REL: begin
if (gate) state <= ATK;
else if (level == 0) state <= IDLE;
else level <= level - (release + 1);
end
endcase
end
end
endmodule// FM合成器 - 2算子FM
// 输出 = carrier * envelope
// carrier = sin(carrier_phase + mod_index * modulator)
module fm_synth (
input wire clk,
input wire rst,
input wire gate,
input wire [19:0] carrier_freq, // 载波频率字
input wire [19:0] mod_freq, // 调制频率字
input wire [7:0] mod_index, // 调制深度
input wire [3:0] attack, decay, release,
input wire [7:0] sustain,
output wire [7:0] audio_out
);
// 调制器NCO
wire [7:0] mod_sine;
nco #(.PHASE_W(16), .FREQ_W(20))
mod_osc (.clk(clk), .rst(rst),
.freq_word(mod_freq), .sine_out(mod_sine), .saw_out());
// 载波NCO - 频率被调制
wire [19:0] modulated_freq = carrier_freq +
{mod_sine[7:0], mod_index[7:0], 4'b0};
wire [7:0] carrier_sine;
nco #(.PHASE_W(16), .FREQ_W(20))
car_osc (.clk(clk), .rst(rst),
.freq_word(modulated_freq), .sine_out(carrier_sine), .saw_out());
// ADSR包络
wire [7:0] env_level;
adsr_envelope u_env (
.clk(clk), .rst(rst), .gate(gate),
.attack(attack), .decay(decay),
.sustain(sustain), .release(release),
.level(env_level)
);
// 输出 = 载波 × 包络
assign audio_out = (carrier_sine[7:1] * env_level[7:1]);
endmodulemodule fm_synth_tb;
logic clk=0, rst=1, gate=0;
logic [19:0] carrier_freq, mod_freq;
logic [7:0] mod_index;
logic [3:0] attack=4'd2, decay=4'd2, release=4'd4;
logic [7:0] sustain=8'd128;
logic [7:0] audio_out;
fm_synth uut(.*);
always #10 clk = ~clk;
initial begin
rst=1; #50; rst=0;
// C4 = 262Hz, freq_word = 262 * 2^20 / 50000000 ≈ 5500
carrier_freq = 20'd5500;
mod_freq = 20'd5500; // 1:1频率比
mod_index = 8'd50;
$display("--- FM合成测试 ---");
// 按键
gate = 1; #5000;
$display(" Attack阶段: audio=%0d", audio_out);
#10000;
$display(" Sustain阶段: audio=%0d", audio_out);
// 松键
gate = 0; #10000;
$display(" Release阶段: audio=%0d", audio_out);
$display("FM合成测试完成 ✓");
#1000; $finish;
end
endmodule| 音色 | 载波:调制比 | 调制深度 | Attack | Decay | Sustain |
|---|---|---|---|---|---|
| 电钢琴 | 1:1 | 30 | 快 | 中 | 高 |
| 贝斯 | 1:1 | 80 | 快 | 快 | 中 |
| 钟声 | 1:3.5 | 120 | 快 | 慢 | 低 |
| 风琴 | 1:2 | 50 | 快 | 无 | 高 |
| 铜管 | 1:1 | 60+包络 | 慢 | 中 | 中 |
| 金属 | 1:1.41 | 200 | 快 | 中 | 低 |
💡 频率计算:MIDI音符号→频率:f = 440 × 2^((n-69)/12)。NCO频率字 = f × 2^PHASE_W / fclk。例:C4(262Hz)在50MHz/16bit相位时 = 262×65536/50000000 ≈ 343。
练习1:用FM合成播放C大调音阶(C4-C5)
练习2:实现4算子FM合成(2个调制器+1个载波+1个反馈)
练习3:添加包络控制调制深度,模拟铜管音色
练习4:实现多复音:3个FM合成器同时发声
练习5:用PS/2键盘弹奏不同音符
步骤1:verilator --lint-only nco.v — 验证NCO振荡器
步骤2:verilator --lint-only adsr_envelope.v — 验证包络发生器
步骤3:verilator --lint-only fm_synth.v — 验证FM合成器
步骤4:verilator --binary -j 0 nco.v adsr_envelope.v fm_synth.v fm_synth_tb.sv
步骤5:./obj_dir/Vfm_synth_tb — 运行仿真
DX7使用6算子FM合成,我们可以实现4算子版本来获得更丰富的音色:
公式:f = 440 × 2^((n-69)/12),n为MIDI音符号
NCO频率字:freq_word = f × 2^PHASE_W / fclk
例:A4(MIDI#69) = 440Hz, 50MHz/16bit相位
freq_word = 440 × 65536 / 50000000 = 576
半音比:2^(1/12) ≈ 1.0595,每高半音频率乘1.0595
八度:每高一个八度,频率字翻倍