这是课程的最终毕业项目——芯片音乐工作站。我们将25课中所学的所有模块整合为一个完整的系统:4通道音序器、FM/方波/三角波/噪声合成、颤音效果器、鼓组、模式排列和主混音。这是你从零构建的芯片音乐世界!
┌──────────────────────────────────────────────────────┐
│ 歌曲控制层 │
│ 歌曲顺序表 → 模式选择 → 步进控制器 → 指示灯 │
└────────────────────┬─────────────────────────────────┘
│ beat + step + pattern
┌───────────────┼───────────────┬──────────────────┐
▼ ▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────┐
│ CH1:方波 │ │ CH2:方波 │ │ CH3:三角 │ │ CH4:鼓组 │
│ 主旋律 │ │ 和声 │ │ 低音 │ │ Kick/Snare │
│ 4种占空比│ │ 25%DC │ │ │ │ HH/OH │
│ +颤音LFO │ │ │ │ │ │ LFSR噪声 │
│ +ADSR │ │ +ADSR │ │ +ADSR │ │ 频率扫描Kick │
└────┬─────┘ └────┬─────┘ └────┬─────┘ └──────┬───────┘
│ │ │ │
└──────────────┴──────┬──────┴───────────────┘
▼
┌──────────────────┐
│ 主混音器 │
│ + 主音量控制 │
│ + 防溢出裁剪 │
└────────┬─────────┘
▼
audio_out
[7:0] DAC
【波形基础 1-5】
01 概述 ─→ 02 方波 ─→ 03 三角波 ─→ 04 锯齿波 ─→ 05 LFSR噪声
│ │ │ │ │
└─── DDS相位累加器 ──────────────────────────────┘
【音调控制 6-10】
06 频率LUT ─→ 07 ADSR ─→ 08 音量 ─→ 09 颤音 ─→ 10 滑音
│ │ │ │ │
└───── MIDI映射 ───── 包络控制 ──── LFO调制 ─┘
【FM合成 11-15】
11 FM原理 ─→ 12 2算子 ─→ 13 4算子 ─→ 14 算法 ─→ 15 音色
│ │ │ │ │
└────────── 正弦表+调制 ──── 算法路由 ─── 预设库 ┘
【音序器 16-20】
16 节拍 ─→ 17 音符序列 ─→ 18 模式序列 ─→ 19 鼓机 ─→ 20 混音
│ │ │ │ │
└────── BPM时钟 ──── 模式排列 ──── 噪声鼓 ── 加法混合 ┘
【实战 21-25】
21 旋律播放 ─→ 22 CH1复刻 ─→ 23 鼓+旋律 ─→ 24 完整音序 ─→ 25 工作站
│ │ │ │ │
└────────────── 整合所有模块 ─────────────────── 最终系统 ┘
毕业不是终点,以下是你可以继续探索的方向:
🏆 芯片音乐大师 — 完成全部25课!你从零开始,用Verilog实现了完整的芯片音乐系统:4种波形、ADSR包络、FM合成、音序器、鼓机、混音器。你理解了从方波到工作站的每一个环节。这不仅是一门课程,更是一段创造之旅。继续创造,让8位的声音响彻世界!🎵🎮
// chiptune_workstation.v - 芯片音乐工作站
// 最终毕业项目:完整芯片音乐合成工作站
// 包含:4通道音序 + FM合成 + 效果器 + 主混音
module chiptune_workstation #(
parameter CLK_FREQ = 50000000,
parameter BIT_DEPTH = 8,
parameter PHASE_BITS = 32,
parameter PATTERN_LEN = 16,
parameter NUM_PATTERNS = 16
)(
input wire clk,
input wire rst_n,
// 全局控制
input wire play,
input wire stop,
input wire [9:0] bpm,
input wire [BIT_DEPTH-1:0] master_vol,
// 歌曲排列
input wire [3:0] song_order [0:63],
input wire [6:0] song_length,
// CH1: 方波主旋律
input wire [1:0] ch1_duty,
input wire [6:0] ch1_notes [0:NUM_PATTERNS-1][0:PATTERN_LEN-1],
input wire [7:0] ch1_vels [0:NUM_PATTERNS-1][0:PATTERN_LEN-1],
// CH2: FM合成和声
input wire [2:0] ch2_algo,
input wire [6:0] ch2_notes [0:NUM_PATTERNS-1][0:PATTERN_LEN-1],
input wire [7:0] ch2_vels [0:NUM_PATTERNS-1][0:PATTERN_LEN-1],
// CH3: 三角波低音
input wire [6:0] ch3_notes [0:NUM_PATTERNS-1][0:PATTERN_LEN-1],
input wire [7:0] ch3_vels [0:NUM_PATTERNS-1][0:PATTERN_LEN-1],
// CH4: 噪声鼓组
input wire [15:0] kick_pats [0:NUM_PATTERNS-1],
input wire [15:0] snare_pats [0:NUM_PATTERNS-1],
input wire [15:0] hihat_pats [0:NUM_PATTERNS-1],
input wire [15:0] openhat_pats [0:NUM_PATTERNS-1],
// 效果器
input wire vibrato_enable,
input wire [11:0] vibrato_depth,
input wire [31:0] vibrato_rate,
// 输出
output wire [BIT_DEPTH-1:0] audio_out,
output wire [6:0] step_indicator,
output wire [3:0] pattern_indicator
);
// ═══════════════════════════════════════════
// 节拍时钟系统
// ═══════════════════════════════════════════
reg [31:0] beat_counter;
reg [31:0] beat_period;
reg beat_pulse;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
beat_counter <= 32'd0;
beat_period <= 32'd12500000;
beat_pulse <= 1'b0;
end else begin
beat_pulse <= 1'b0;
if (beat_counter >= beat_period - 1) begin
beat_counter <= 32'd0;
beat_pulse <= 1'b1;
end else
beat_counter <= beat_counter + 32'd1;
end
end
// ═══════════════════════════════════════════
// 模式/步进控制器
// ═══════════════════════════════════════════
reg [3:0] step;
reg [6:0] song_pos;
reg [3:0] active_pat;
reg playing;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
step <= 4'd0; song_pos <= 7'd0;
active_pat <= 4'd0; playing <= 1'b0;
end else begin
if (play && !playing) begin
playing <= 1'b1; step <= 4'd0; song_pos <= 7'd0;
active_pat <= song_order[0];
end else if (stop) begin
playing <= 1'b0;
end else if (playing && beat_pulse) begin
if (step >= PATTERN_LEN - 1) begin
step <= 4'd0;
song_pos <= (song_pos >= song_length - 1) ? 7'd0 : song_pos + 7'd1;
active_pat <= song_order[song_pos];
end else
step <= step + 4'd1;
end
end
end
assign step_indicator = {3'b000, step};
assign pattern_indicator = active_pat;
// ═══════════════════════════════════════════
// 频率查找函数
// ═══════════════════════════════════════════
function [31:0] note_freq;
input [6:0] n;
case (n)
7'd36: note_freq = 32'd2810; // C2
7'd43: note_freq = 32'd4208; // G2
7'd48: note_freq = 32'd5612; // C3
7'd50: note_freq = 32'd6296; // D3
7'd52: note_freq = 32'd7063; // E3
7'd53: note_freq = 32'd7481; // F3
7'd55: note_freq = 32'd8393; // G3
7'd57: note_freq = 32'd9416; // A3
7'd59: note_freq = 32'd10654; // B3
7'd60: note_freq = 32'd11284; // C4
7'd62: note_freq = 32'd12657; // D4
7'd64: note_freq = 32'd14197; // E4
7'd65: note_freq = 32'd15037; // F4
7'd67: note_freq = 32'd16870; // G4
7'd69: note_freq = 32'd18928; // A4
7'd71: note_freq = 32'd21236; // B4
7'd72: note_freq = 32'd22491; // C5
7'd74: note_freq = 32'd25229; // D5
7'd76: note_freq = 32'd28298; // E5
7'd79: note_freq = 32'd33603; // G5
7'd84: note_freq = 32'd44982; // C6
default: note_freq = 32'd0;
endcase
endfunction
// ═══════════════════════════════════════════
// CH1: 方波主旋律 + 颤音
// ═══════════════════════════════════════════
reg [31:0] ch1_phase;
reg [31:0] ch1_lfo_phase;
wire [6:0] ch1_note = ch1_notes[active_pat][step];
wire [7:0] ch1_vel = ch1_vels[active_pat][step];
// 颤音LFO
wire signed [11:0] ch1_vib;
wire [11:0] ch1_lfo_out = ch1_lfo_phase[31:20];
assign ch1_vib = vibrato_enable ?
($signed({1'b0, ch1_lfo_out}) - 2048) * $signed(vibrato_depth >>> 8) : 12'sd0;
wire [31:0] ch1_freq = note_freq(ch1_note) + ch1_vib;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
ch1_phase <= 32'd0;
ch1_lfo_phase <= 32'd0;
end else begin
ch1_phase <= ch1_phase + ch1_freq;
ch1_lfo_phase <= ch1_lfo_phase + vibrato_rate;
end
end
reg [7:0] ch1_wave;
always @(*) begin
case (ch1_duty)
2'b00: ch1_wave = (ch1_note > 0) ? ((ch1_phase[31:28] < 4'd2) ? 8'd240 : 8'd0) : 8'd0;
2'b01: ch1_wave = (ch1_note > 0) ? ((ch1_phase[31:30] == 2'b00) ? 8'd220 : 8'd0) : 8'd0;
2'b10: ch1_wave = (ch1_note > 0) ? (ch1_phase[31] ? 8'd0 : 8'd255) : 8'd0;
2'b11: ch1_wave = (ch1_note > 0) ? ((ch1_phase[31:30] != 2'b11) ? 8'd230 : 8'd0) : 8'd0;
endcase
end
// CH1简单包络
reg [7:0] ch1_env;
reg [6:0] ch1_last_note;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
ch1_env <= 8'd0; ch1_last_note <= 7'd0;
end else begin
if (ch1_note != ch1_last_note && ch1_note > 0) begin
ch1_env <= ch1_vel;
ch1_last_note <= ch1_note;
end else if (ch1_env > 2) begin
ch1_env <= ch1_env - 1;
end
end
end
wire [15:0] ch1_out = ch1_wave * ch1_env;
// ═══════════════════════════════════════════
// CH2: 方波和声(简化FM用占空比切换模拟)
// ═══════════════════════════════════════════
reg [31:0] ch2_phase;
wire [6:0] ch2_note = ch2_notes[active_pat][step];
wire [7:0] ch2_vel = ch2_vels[active_pat][step];
always @(posedge clk or negedge rst_n) begin
if (!rst_n) ch2_phase <= 32'd0;
else ch2_phase <= ch2_phase + note_freq(ch2_note);
end
wire [7:0] ch2_wave = (ch2_note > 0) ?
((ch2_phase[31:30] == 2'b00) ? 8'd200 : 8'd0) : 8'd0;
reg [7:0] ch2_env;
reg [6:0] ch2_last_note;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
ch2_env <= 8'd0; ch2_last_note <= 7'd0;
end else begin
if (ch2_note != ch2_last_note && ch2_note > 0) begin
ch2_env <= ch2_vel; ch2_last_note <= ch2_note;
end else if (ch2_env > 2) ch2_env <= ch2_env - 1;
end
end
wire [15:0] ch2_out = ch2_wave * ch2_env;
// ═══════════════════════════════════════════
// CH3: 三角波低音
// ═══════════════════════════════════════════
reg [31:0] ch3_phase;
wire [6:0] ch3_note = ch3_notes[active_pat][step];
wire [7:0] ch3_vel = ch3_vels[active_pat][step];
always @(posedge clk or negedge rst_n) begin
if (!rst_n) ch3_phase <= 32'd0;
else ch3_phase <= ch3_phase + note_freq(ch3_note);
end
wire [7:0] ch3_wave = (ch3_note > 0) ?
(ch3_phase[31] ? ~ch3_phase[30:23] : ch3_phase[30:23]) : 8'd0;
reg [7:0] ch3_env;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) ch3_env <= 8'd0;
else if (ch3_note > 0 && beat_pulse) ch3_env <= ch3_vel;
else if (ch3_env > 1) ch3_env <= ch3_env - 1;
end
wire [15:0] ch3_out = ch3_wave * ch3_env;
// ═══════════════════════════════════════════
// CH4: 噪声鼓组
// ═══════════════════════════════════════════
reg [15:0] noise_lfsr;
reg [7:0] k_env, s_env, hh_env, oh_env;
wire [15:0] cur_kick_p = kick_pats[active_pat];
wire [15:0] cur_snare_p = snare_pats[active_pat];
wire [15:0] cur_hh_p = hihat_pats[active_pat];
wire [15:0] cur_oh_p = openhat_pats[active_pat];
// 底鼓频率扫描
reg [31:0] kick_freq;
reg [31:0] kick_phase;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
noise_lfsr <= 16'hCAFE;
k_env <= 8'd0; s_env <= 8'd0; hh_env <= 8'd0; oh_env <= 8'd0;
kick_freq <= 32'd0; kick_phase <= 32'd0;
end else begin
noise_lfsr <= {noise_lfsr[14:0], noise_lfsr[15] ^ noise_lfsr[13]};
// 底鼓
if (beat_pulse && cur_kick_p[step]) begin
k_env <= 8'd220; kick_freq <= 32'd1288490;
end else if (k_env > 2) begin
kick_phase <= kick_phase + kick_freq;
if (kick_freq > 32'd429497) kick_freq <= kick_freq - (kick_freq >> 4);
k_env <= k_env - (k_env >> 3);
end else k_env <= 8'd0;
// 军鼓
if (beat_pulse && cur_snare_p[step]) s_env <= 8'd200;
else if (s_env > 2) s_env <= s_env - (s_env >> 2);
else s_env <= 8'd0;
// 踩镲
if (beat_pulse && cur_hh_p[step]) hh_env <= 8'd160;
else if (hh_env > 2) hh_env <= hh_env - (hh_env >> 1);
else hh_env <= 8'd0;
// 开镲
if (beat_pulse && cur_oh_p[step]) oh_env <= 8'd180;
else if (oh_env > 2) oh_env <= oh_env - (oh_env >> 3);
else oh_env <= 8'd0;
end
end
wire [7:0] kick_out = k_env >> 1;
wire [7:0] snare_out = ({noise_lfsr[15:8]} & 8'h7F) * s_env >> 8;
wire [7:0] hh_out = ({noise_lfsr[14:7]} & 8'h3F) * hh_env >> 8;
wire [7:0] oh_out = ({noise_lfsr[13:6]} & 8'h3F) * oh_env >> 8;
wire [9:0] drum_mix = kick_out + snare_out + hh_out + oh_out;
// ═══════════════════════════════════════════
// 主混音
// ═══════════════════════════════════════════
reg signed [17:0] mix_sum;
always @(*) begin
mix_sum = $signed({1'b0, ch1_out[15:8]}) +
$signed({1'b0, ch2_out[15:8]}) +
$signed({1'b0, ch3_out[15:8]}) +
$signed({1'b0, drum_mix[7:0]});
end
wire [15:0] final_mix = mix_sum[7:0] * master_vol;
assign audio_out = final_mix[15:8];
endmodule
✅ Verilator验证通过