第2课:方波发生器

阶段1:波形基础

方波是芯片音乐最核心的波形。本课我们将实现一个可调占空比的方波发生器,这是NES 2A03芯片的标志性特性之一。

相位累加器原理

上一课的方波用计数器翻转实现,但那种方法很难精确控制频率。本课引入相位累加器(Phase Accumulator),这是数字频率合成(DDS)的核心技术。

📐 DDS原理详解

相位累加器的工作原理:

  1. 维护一个N位相位寄存器phase,范围0到2^N-1
  2. 每个时钟周期,phase增加一个频率调谐字freq_tune
  3. phase溢出时自动回绕(无符号加法的天然特性),形成一个完整周期
  4. 输出频率 = freq_tune × CLK_FREQ / 2^N

因此:freq_tune = F_target × 2^N / CLK_FREQ

例如,20位相位,50MHz时钟,目标440Hz:

freq_tune = 440 × 2^20 / 50000000 = 440 × 1048576 / 50000000 ≈ 9.22

取整为9,实际输出频率 = 9 × 50000000 / 1048576 ≈ 429.15Hz,误差约2.5%。

增加相位位宽可提高频率精度。24位时freq_tune ≈ 147,实际频率440.01Hz,精度极高。

占空比与音色

方波的占空比(Duty Cycle)定义为高电平占整个周期的比例。它直接影响音色:

占空比谐波特征听感描述NES对应
12.5%基频弱,高次谐波丰富尖锐、薄、像笛子✅ 支持
25%奇偶谐波都有,高次为主明亮、有鼻音✅ 支持
50%只有奇次谐波温暖、饱满、经典✅ 支持
75%与25%互补与25%音色相似但相位相反✅ 支持
NES的2A03芯片在方波通道上支持4种占空比切换。作曲家通过在运行时切换占空比来创造音色变化效果。比如《洛克人》系列大量使用12.5%占空比制造尖锐的领奏音色。

Verilog实现解析

我们的实现采用DDS架构,核心是相位累加器+比较器:

1. 相位累加器

20位相位寄存器每个时钟增加freq_tune。freq_tune值越大,相位跑满一圈越快,输出频率越高。

2. 占空比比较

将phase与duty_threshold比较:phase小于阈值时输出高电平,否则输出低电平。阈值大小决定占空比。

3. 输出位宽

8位输出:高电平=255,低电平=0。这种满幅输出比1位二值输出更方便后续混音处理。

🔬 频率计算实战

假设PHASE_BITS=20, CLK_FREQ=50MHz:

// C4 = 261.63Hz
freq_tune = round(261.63 * 2^20 / 50000000) = round(5.49) = 5
// 实际频率 = 5 * 50000000 / 1048576 = 238.42Hz ❌ 误差太大!

// 改用PHASE_BITS=24
freq_tune = round(261.63 * 2^24 / 50000000) = round(87.76) = 88
// 实际频率 = 88 * 50000000 / 16777216 = 262.26Hz ✅ 误差0.24%

结论:20位相位在低音区精度不足,24位以上才能覆盖完整音域。后续课程将使用32位相位。

仿真验证

我们可以写一个简单的testbench来验证方波输出:

// tb_square_wave.v
module tb_square_wave;
    reg clk = 0;
    reg rst_n = 0;
    reg [19:0] freq_tune = 20'd88;
    reg [1:0] duty_select = 2'b10; // 50%
    wire [7:0] wave_out;
    
    square_wave #(.PHASE_BITS(20)) uut(
        .clk(clk), .rst_n(rst_n),
        .freq_tune(freq_tune),
        .duty_select(duty_select),
        .wave_out(wave_out)
    );
    
    always #10 clk = ~clk; // 50MHz
    
    initial begin
        rst_n = 0;
        #100;
        rst_n = 1;
        #100000;
        $finish;
    end
endmodule
  1. 修改相位位宽为24位,重新计算C4、E4、G4的freq_tune值
  2. 写testbench验证4种占空比的输出波形,用$dumpfile生成VCD波形
  3. 测量实际输出频率:统计N个完整周期所用的时钟周期数
  4. 挑战:实现占空比可连续调节(如用8位duty输入代替2位选择),分析音色变化

方波大师 — 掌握DDS相位累加器原理,实现可调占空比方波发生器,理解占空比对音色的影响!

Verilog 实现

square_wave.v
// square_wave.v - 可调占空比方波发生器
// 支持4种占空比:12.5%, 25%, 50%, 75%
module square_wave #(
    parameter CLK_FREQ = 50000000,
    parameter BIT_DEPTH = 8,        // 输出位宽
    parameter PHASE_BITS = 20       // 相位累加器位宽
)(
    input  wire clk,
    input  wire rst_n,
    input  wire [PHASE_BITS-1:0] freq_tune,  // 频率调谐字
    input  wire [1:0] duty_select,            // 占空比选择
    output wire [BIT_DEPTH-1:0] wave_out
);
    // 相位累加器
    reg [PHASE_BITS-1:0] phase;
    
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n)
            phase <= {PHASE_BITS{1'b0}};
        else
            phase <= phase + freq_tune;
    end
    
    // 占空比阈值查找表
    // 12.5% -> phase < 1/8 全周期
    // 25%   -> phase < 1/4 全周期  
    // 50%   -> phase < 1/2 全周期
    // 75%   -> phase < 3/4 全周期
    reg [PHASE_BITS-1:0] duty_threshold;
    localparam [PHASE_BITS-1:0] FULL = {PHASE_BITS{1'b1}};
    
    always @(*) begin
        case (duty_select)
            2'b00: duty_threshold = FULL >> 3;       // 12.5%
            2'b01: duty_threshold = FULL >> 2;       // 25%
            2'b10: duty_threshold = FULL >> 1;       // 50%
            2'b11: duty_threshold = (FULL >> 2) + (FULL >> 1); // 75%
            default: duty_threshold = FULL >> 1;
        endcase
    end
    
    // 比较相位与阈值输出方波
    assign wave_out = (phase < duty_threshold) ? 
                      {BIT_DEPTH{1'b1}} : {BIT_DEPTH{1'b0}};
endmodule

✅ Verilator验证通过

深入理解占空比的数学

占空比对方波频谱的影响可以用傅里叶级数精确描述:

📐 不同占空比的谐波振幅

方波的傅里叶级数系数(归一化到基频):

占空比 D 的方波谐波 n 的振幅 = sinc(n×D) = sin(πnD)/(πnD)

50%占空比:sin(πn×0.5)/(πn×0.5)
  n=1: sin(0.5π)/0.5π = 1.0      ← 基频
  n=2: sin(1.0π)/1.0π = 0.0      ← 偶次谐波为零!
  n=3: sin(1.5π)/1.5π = -0.333   ← 奇次谐波
  n=4: sin(2.0π)/2.0π = 0.0      ← 偶次为零
  n=5: sin(2.5π)/2.5π = 0.200    ← 奇次

25%占空比:sin(πn×0.25)/(πn×0.25)
  n=1: 0.900
  n=2: 0.637  ← 偶次谐波出现!
  n=3: 0.300
  n=4: 0.000  ← 第4次为零
  n=5: -0.180

关键结论:50%占空比只含奇次谐波,其他占空比包含偶次谐波。这就是为什么50%方波听起来最"饱满",而12.5%方波听起来最"薄"。

NES方波的硬件实现细节

NES 2A03方波通道的实际实现方式:

🔬 DDS vs NES频率方案对比

特征DDS(我们的方案)NES方案
频率编码频率调谐字(值越大频率越高)半周期计数(值越大频率越低)
频率精度均匀分布低频精度高,高频精度低
寄存器位宽32位11位
频率范围0-几MHzA1(55Hz)-B7(3900Hz)
硬件复杂度加法器+比较器减法计数器

方波在音乐中的应用

方波在芯片音乐中的应用远不止"简单波形":

方波的边带频谱与调制

当方波被调制时(无论频率调制还是振幅调制),产生的边带比正弦波更复杂:

📐 方波的AM调制频谱

50%方波被低频正弦波调幅时:

基频fc的奇次谐波: fc, 3fc, 5fc, 7fc...
每个谐波两侧产生边带: fc±fm, 3fc±fm, 5fc±fm...
→ 密集的频谱结构

这就是为什么方波+简单调制就能产生如此丰富的音色变化。芯片音乐中的很多"魔法"都来自这种简单波形+简单调制的组合。

占空比调制的创意应用

快速切换占空比本身就是一种音色效果:

// 占空比切换效果(伪代码)
step 0: duty=2'b10 (50%) → 温暖
step 1: duty=2'b00 (12.5%) → 尖锐
step 2: duty=2'b10 (50%) → 温暖
step 3: duty=2'b01 (25%) → 中等
→ 产生节奏性的音色变化

DDS架构的深入分析

DDS(Direct Digital Synthesis)是本课程波形生成的核心架构,值得深入理解:

📐 DDS系统框图

          freq_tune
              │
              ▼
        ┌──────────────┐
        │  相位累加器   │ ← 32位加法器
        │  phase += dt  │
        └──────┬───────┘
               │ phase[31:0]
               ▼
        ┌──────────────┐
        │  相位-幅度    │ ← 波形映射
        │  转换器      │   (方波/三角/锯齿/LUT)
        └──────┬───────┘
               │ amplitude[7:0]
               ▼
        ┌──────────────┐
        │  包络/音量    │ ← 调制
        │  控制        │
        └──────┬───────┘
               │ audio_out[7:0]
               ▼
             DAC

相位截断与杂散

32位相位只用高8位进行波形映射,低24位被"截断"。这会产生小的杂散频率,但在8位系统中完全可忽略。这是DDS理论的经典问题,但在我们的应用场景中不需要担心。

🔧 相位累加器的Verilog最佳实践

// ✅ 推荐:异步复位 + 同步操作
always @(posedge clk or negedge rst_n) begin
    if (!rst_n)
        phase <= 32'd0;
    else
        phase <= phase + freq_tune;
end

// ❌ 避免:不带复位的累加器
always @(posedge clk)
    phase <= phase + freq_tune;
// 仿真时phase初始值为X,可能导致问题

// ✅ 推荐:参数化设计
module dds #(
    parameter PHASE_BITS = 32
)(
    input  wire [PHASE_BITS-1:0] freq_tune,
    output wire [PHASE_BITS-1:0] phase
);
// 位宽可配置,适应不同精度需求