第25课:毕业项目:芯片音乐工作站

阶段5:实战项目

这是课程的最终毕业项目——芯片音乐工作站。我们将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

25课知识图谱

📚 课程知识体系总结

【波形基础 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位的声音响彻世界!🎵🎮

Verilog 实现

chiptune_workstation.v
// 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验证通过