方波是芯片音乐最核心的波形。本课我们将实现一个可调占空比的方波发生器,这是NES 2A03芯片的标志性特性之一。
上一课的方波用计数器翻转实现,但那种方法很难精确控制频率。本课引入相位累加器(Phase Accumulator),这是数字频率合成(DDS)的核心技术。
相位累加器的工作原理:
phase,范围0到2^N-1freq_tune因此: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%音色相似但相位相反 | ✅ 支持 |
我们的实现采用DDS架构,核心是相位累加器+比较器:
20位相位寄存器每个时钟增加freq_tune。freq_tune值越大,相位跑满一圈越快,输出频率越高。
将phase与duty_threshold比较:phase小于阈值时输出高电平,否则输出低电平。阈值大小决定占空比。
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
方波大师 — 掌握DDS相位累加器原理,实现可调占空比方波发生器,理解占空比对音色的影响!
// 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 2A03方波通道的实际实现方式:
| 特征 | DDS(我们的方案) | NES方案 |
|---|---|---|
| 频率编码 | 频率调谐字(值越大频率越高) | 半周期计数(值越大频率越低) |
| 频率精度 | 均匀分布 | 低频精度高,高频精度低 |
| 寄存器位宽 | 32位 | 11位 |
| 频率范围 | 0-几MHz | A1(55Hz)-B7(3900Hz) |
| 硬件复杂度 | 加法器+比较器 | 减法计数器 |
方波在芯片音乐中的应用远不止"简单波形":
当方波被调制时(无论频率调制还是振幅调制),产生的边带比正弦波更复杂:
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(Direct Digital Synthesis)是本课程波形生成的核心架构,值得深入理解:
freq_tune
│
▼
┌──────────────┐
│ 相位累加器 │ ← 32位加法器
│ phase += dt │
└──────┬───────┘
│ phase[31:0]
▼
┌──────────────┐
│ 相位-幅度 │ ← 波形映射
│ 转换器 │ (方波/三角/锯齿/LUT)
└──────┬───────┘
│ amplitude[7:0]
▼
┌──────────────┐
│ 包络/音量 │ ← 调制
│ 控制 │
└──────┬───────┘
│ audio_out[7:0]
▼
DAC
32位相位只用高8位进行波形映射,低24位被"截断"。这会产生小的杂散频率,但在8位系统中完全可忽略。这是DDS理论的经典问题,但在我们的应用场景中不需要担心。
// ✅ 推荐:异步复位 + 同步操作
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
);
// 位宽可配置,适应不同精度需求