数字调制器输出的符号序列是冲激脉冲,其频谱无限宽。实际信道的带宽有限,直接传输会导致符号间干扰(ISI)。脉冲成型滤波器的作用是:
脉冲成型滤波器h(t)满足无ISI条件的充要条件:
即:在非零采样时刻,滤波器响应恰好为零,不干扰其他符号。
频域等价条件(Nyquist准则):
含义:滤波器频率响应的周期延拓之和为常数。
最常用的脉冲成型滤波器是升余弦(Raised Cosine, RC)滤波器:
其中α为滚降因子(0 < α ≤ 1),控制过渡带宽度:
| 滚降因子α | 带宽 | 频谱效率 | 时域衰减 | 典型应用 |
|---|---|---|---|---|
| 0.0 | 最小(R_s/2) | 最高 | 最慢(1/t衰减) | 理论极限 |
| 0.22 | 0.61R_s | 高 | 快 | 3G WCDMA |
| 0.35 | 0.675R_s | 较高 | 较快 | LTE |
| 0.5 | 0.75R_s | 中等 | 快 | 卫星通信 |
| 1.0 | R_s | 最低 | 最快(1/t³衰减) | 测试/仿真 |
// rrc_filter.v - 根升余弦脉冲成型滤波器
// 第05课:脉冲成型
module rrc_filter #(
parameter DATA_W = 12, // 输入数据位宽
parameter COEFF_W = 16, // 系数位宽
parameter SYM_RATE = 4, // 每符号采样数(上采样因子)
parameter TAP_NUM = 49, // 滤波器抽头数
parameter ROLLOFF = 32 // 滚降因子 α = ROLLOFF/100
)(
input wire clk,
input wire rst_n,
// 符号输入 (符号率)
input wire signed [DATA_W-1:0] sym_in,
input wire sym_valid,
// 采样输出 (采样率 = SYM_RATE × 符号率)
output wire signed [DATA_W-1:0] sample_out,
output wire sample_valid
);
// ============================================================
// RRC系数生成 (α=0.35, 4倍上采样)
// 使用Python生成,此处硬编码
// ============================================================
reg signed [COEFF_W-1:0] coeffs [0:TAP_NUM-1];
initial begin
// 49抽头RRC滤波器 (α=0.35, 4x上采样)
// 对称系数,只列前半部分
coeffs[0] = 16'sd0; coeffs[1] = -16'sd14;
coeffs[2] = -16'sd33; coeffs[3] = -16'sd42;
coeffs[4] = -16'sd28; coeffs[5] = 16'sd18;
coeffs[6] = 16'sd76; coeffs[7] = 16'sd120;
coeffs[8] = 16'sd112; coeffs[9] = 16'sd42;
coeffs[10] = -16'sd80; coeffs[11] = -16'sd210;
coeffs[12] = -16'sd294; coeffs[13] = -16'sd254;
coeffs[14] = -16'sd42; coeffs[15] = 16'sd282;
coeffs[16] = 16'sd650; coeffs[17] = 16'sd954;
coeffs[18] = 16'sd1059; coeffs[19] = 16'sd897;
coeffs[20] = 16'sd485; coeffs[21] = -16'sd61;
coeffs[22] = -16'sd620; coeffs[23] = -16'sd1048;
coeffs[24] = -16'sd1199; // 中心抽头
// 后半部分对称
coeffs[25] = -16'sd1048; coeffs[26] = -16'sd620;
coeffs[27] = -16'sd61; coeffs[28] = 16'sd485;
coeffs[29] = 16'sd897; coeffs[30] = 16'sd1059;
coeffs[31] = 16'sd954; coeffs[32] = 16'sd650;
coeffs[33] = 16'sd282; coeffs[34] = -16'sd42;
coeffs[35] = -16'sd254; coeffs[36] = -16'sd294;
coeffs[37] = -16'sd210; coeffs[38] = -16'sd80;
coeffs[39] = 16'sd42; coeffs[40] = 16'sd112;
coeffs[41] = 16'sd120; coeffs[42] = 16'sd76;
coeffs[43] = 16'sd18; coeffs[44] = -16'sd28;
coeffs[45] = -16'sd42; coeffs[46] = -16'sd33;
coeffs[47] = -16'sd14; coeffs[48] = 16'sd0;
end
// ============================================================
// 上采样 + 延迟线
// ============================================================
reg signed [DATA_W-1:0] shift_reg [0:TAP_NUM-1];
reg [$clog2(SYM_RATE)-1:0] up_cnt;
reg sym_valid_d;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
up_cnt <= 0;
sym_valid_d <= 1'b0;
end else begin
sym_valid_d <= 1'b0;
if (up_cnt == 0 && sym_valid) begin
sym_valid_d <= 1'b1;
end
up_cnt <= up_cnt + 1'b1;
end
end
// 延迟线移位
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
integer i;
for (i = 0; i < TAP_NUM; i = i + 1)
shift_reg[i] <= 0;
end else begin
if (sym_valid_d) begin
shift_reg[0] <= sym_in;
end else begin
shift_reg[0] <= 0; // 零值插入(上采样)
end
integer j;
for (j = 1; j < TAP_NUM; j = j + 1)
shift_reg[j] <= shift_reg[j-1];
end
end
// ============================================================
// MAC (乘累加)
// ============================================================
reg signed [DATA_W+COEFF_W:0] mac_result;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
mac_result <= 0;
end else begin
mac_result = 0;
integer k;
for (k = 0; k < TAP_NUM; k = k + 1)
mac_result = mac_result + shift_reg[k] * coeffs[k];
end
end
assign sample_out = mac_result[DATA_W+COEFF_W-1:COEFF_W];
assign sample_valid = 1'b1; // 每个时钟周期都有输出
endmodule
// ============================================================
// 匹配滤波器(接收端RRC)
// ============================================================
module matched_filter #(
parameter DATA_W = 12,
parameter COEFF_W = 16,
parameter TAP_NUM = 49
)(
input wire clk,
input wire rst_n,
input wire signed [DATA_W-1:0] sample_in, // 采样率输入
input wire sample_valid,
output wire signed [DATA_W-1:0] sym_out, // 符号率输出
output wire sym_valid,
output wire timing_pulse // 最佳采样时刻
);
// 与发送端相同的RRC系数
reg signed [COEFF_W-1:0] coeffs [0:TAP_NUM-1];
initial begin
// 同rrc_filter的系数
coeffs[0] = 16'sd0; coeffs[1] = -16'sd14;
coeffs[2] = -16'sd33; coeffs[3] = -16'sd42;
coeffs[4] = -16'sd28; coeffs[5] = 16'sd18;
coeffs[6] = 16'sd76; coeffs[7] = 16'sd120;
coeffs[8] = 16'sd112; coeffs[9] = 16'sd42;
coeffs[10] = -16'sd80; coeffs[11] = -16'sd210;
coeffs[12] = -16'sd294; coeffs[13] = -16'sd254;
coeffs[14] = -16'sd42; coeffs[15] = 16'sd282;
coeffs[16] = 16'sd650; coeffs[17] = 16'sd954;
coeffs[18] = 16'sd1059; coeffs[19] = 16'sd897;
coeffs[20] = 16'sd485; coeffs[21] = -16'sd61;
coeffs[22] = -16'sd620; coeffs[23] = -16'sd1048;
coeffs[24] = -16'sd1199;
coeffs[25] = -16'sd1048; coeffs[26] = -16'sd620;
coeffs[27] = -16'sd61; coeffs[28] = 16'sd485;
coeffs[29] = 16'sd897; coeffs[30] = 16'sd1059;
coeffs[31] = 16'sd954; coeffs[32] = 16'sd650;
coeffs[33] = 16'sd282; coeffs[34] = -16'sd42;
coeffs[35] = -16'sd254; coeffs[36] = -16'sd294;
coeffs[37] = -16'sd210; coeffs[38] = -16'sd80;
coeffs[39] = 16'sd42; coeffs[40] = 16'sd112;
coeffs[41] = 16'sd120; coeffs[42] = 16'sd76;
coeffs[43] = 16'sd18; coeffs[44] = -16'sd28;
coeffs[45] = -16'sd42; coeffs[46] = -16'sd33;
coeffs[47] = -16'sd14; coeffs[48] = 16'sd0;
end
// 延迟线
reg signed [DATA_W-1:0] shift_reg [0:TAP_NUM-1];
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
integer i;
for (i = 0; i < TAP_NUM; i = i + 1)
shift_reg[i] <= 0;
end else if (sample_valid) begin
shift_reg[0] <= sample_in;
integer j;
for (j = 1; j < TAP_NUM; j = j + 1)
shift_reg[j] <= shift_reg[j-1];
end
end
// MAC
reg signed [DATA_W+COEFF_W:0] mac_result;
always @(posedge clk) begin
if (sample_valid) begin
mac_result = 0;
integer k;
for (k = 0; k < TAP_NUM; k = k + 1)
mac_result = mac_result + shift_reg[k] * coeffs[k];
end
end
// 下采样:每4个采样取1个(最佳采样时刻)
localparam DECIM = 4; // 对应上采样因子
reg [$clog2(DECIM)-1:0] decim_cnt;
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
decim_cnt <= 0;
else if (sample_valid)
decim_cnt <= decim_cnt + 1'b1;
end
assign sym_out = mac_result[DATA_W+COEFF_W-1:COEFF_W];
assign timing_pulse = (decim_cnt == DECIM/2); // 中间采样点
assign sym_valid = sample_valid && timing_pulse;
endmodule
#!/usr/bin/env python3
"""pulse_shaping.py - 脉冲成型仿真
第05课:脉冲成型
演示升余弦滤波器、ISI、眼图
"""
import numpy as np
import matplotlib.pyplot as plt
from scipy import signal
def design_rrc(sps, alpha, num_taps):
"""设计根升余弦滤波器
Args:
sps: 每符号采样数
alpha: 滚降因子
num_taps: 滤波器抽头数(奇数)
Returns:
归一化系数数组
"""
t = np.arange(num_taps) - (num_taps - 1) / 2
t_norm = t / sps # 归一化时间
h = np.zeros(num_taps)
for i in range(num_taps):
t_val = t_norm[i]
if t_val == 0:
h[i] = 1.0 - alpha + 4 * alpha / np.pi
elif abs(abs(t_val) - 1.0 / (4 * alpha)) < 1e-8:
h[i] = (alpha / np.sqrt(2)) * (
(1 + 2 / np.pi) * np.sin(np.pi / (4 * alpha)) +
(1 - 2 / np.pi) * np.cos(np.pi / (4 * alpha))
)
else:
num = np.cos((1 + alpha) * np.pi * t_val) + \
np.sinc((1 - alpha) * t_val) * (1 - alpha) / (4 * alpha)
den = 1 - (4 * alpha * t_val) ** 2
h[i] = num / den if abs(den) > 1e-10 else 0
# 归一化:使脉冲响应峰值=1
h = h / np.sqrt(np.sum(h**2))
return h
def plot_rrc_responses():
"""绘制不同滚降因子的RRC时域和频域响应"""
sps = 8
num_taps = 65
alphas = [0.0, 0.22, 0.35, 0.5, 1.0]
colors = ['#ef4444', '#f59e0b', '#06b6d4', '#10b981', '#8b5cf6']
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 6))
for alpha, color in zip(alphas, colors):
if alpha == 0.0:
# 理想低通(sinc函数)
t = np.arange(num_taps) - (num_taps - 1) / 2
h = np.sinc(t / sps)
h = h / np.sqrt(np.sum(h**2))
else:
h = design_rrc(sps, alpha, num_taps)
t = np.arange(num_taps) - (num_taps - 1) / 2
# 时域
ax1.plot(t / sps, h, color=color, linewidth=1.5,
label=f'α={alpha}', alpha=0.9)
# 频域
w, H = signal.freqz(h, fs=sps)
ax2.plot(w, 20 * np.log10(np.abs(H) + 1e-10),
color=color, linewidth=1.5, label=f'α={alpha}')
ax1.axhline(0, color='white', alpha=0.3)
ax1.axvline(0, color='white', alpha=0.3)
for n in range(-3, 4):
if n != 0:
ax1.axvline(n, color='yellow', alpha=0.15, linestyle='--')
ax1.set_xlabel('时间 (符号周期)', fontsize=12)
ax1.set_ylabel('幅度', fontsize=12)
ax1.set_title('RRC脉冲成型滤波器时域响应', fontsize=13)
ax1.legend(fontsize=9)
ax1.grid(True, alpha=0.3)
ax1.set_xlim(-4, 4)
ax2.axvline(0.5, color='r', linestyle='--', alpha=0.5, label='Nyquist频率')
ax2.set_xlabel('频率 (×1/T_s)', fontsize=12)
ax2.set_ylabel('幅度 (dB)', fontsize=12)
ax2.set_title('RRC滤波器频率响应', fontsize=13)
ax2.legend(fontsize=9)
ax2.grid(True, alpha=0.3)
ax2.set_ylim(-60, 5)
plt.tight_layout()
plt.savefig('/var/www/ttl/digital-comm/rrc_response.png', dpi=100,
facecolor='#0f172a', edgecolor='none')
print("RRC响应图已保存")
def plot_eye_diagram():
"""绘制眼图"""
sps = 8
alpha = 0.35
num_taps = 65
h_rrc = design_rrc(sps, alpha, num_taps)
# 生成随机符号
np.random.seed(42)
num_symbols = 200
symbols = np.random.choice([-1, 1], num_symbols)
# 上采样
upsampled = np.zeros(num_symbols * sps)
upsampled[::sps] = symbols
# 脉冲成型
shaped = np.convolve(upsampled, h_rrc, mode='same')
# 匹配滤波
matched = np.convolve(shaped, h_rrc, mode='same')
# 绘制眼图
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 6))
# 脉冲成型后眼图
eye_len = 2 * sps # 2个符号周期
num_traces = (len(shaped) - eye_len) // sps
for i in range(min(num_traces, 80)):
start = i * sps
end = start + eye_len
if end <= len(shaped):
t_eye = np.linspace(0, 2, eye_len)
ax1.plot(t_eye, shaped[start:end], 'c-', alpha=0.15, linewidth=0.8)
ax1.set_xlabel('时间 (符号周期)', fontsize=12)
ax1.set_ylabel('幅度', fontsize=12)
ax1.set_title(f'脉冲成型后眼图 (α={alpha})', fontsize=13)
ax1.grid(True, alpha=0.3)
# 匹配滤波后眼图
for i in range(min(num_traces, 80)):
start = i * sps
end = start + eye_len
if end <= len(matched):
t_eye = np.linspace(0, 2, eye_len)
ax2.plot(t_eye, matched[start:end], '#10b981', alpha=0.15, linewidth=0.8)
ax2.set_xlabel('时间 (符号周期)', fontsize=12)
ax2.set_ylabel('幅度', fontsize=12)
ax2.set_title(f'匹配滤波后眼图 (α={alpha})', fontsize=13)
ax2.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('/var/www/ttl/digital-comm/eye_diagram.png', dpi=100,
facecolor='#0f172a', edgecolor='none')
print("眼图已保存")
def analyze_isi():
"""分析ISI与滚降因子的关系"""
sps = 8
num_taps = 65
alphas = np.arange(0.1, 1.01, 0.1)
isi_values = []
for alpha in alphas:
h_rrc = design_rrc(sps, alpha, num_taps)
# 整体响应 = RRC × RRC = RC
h_rc = np.convolve(h_rrc, h_rrc, mode='full')
# 找峰值
peak_idx = np.argmax(np.abs(h_rc))
# 计算ISI: 非峰值采样点的能量
isi = 0
for i in range(0, len(h_rc), sps):
if i != peak_idx:
isi += h_rc[i]**2
isi_ratio = isi / h_rc[peak_idx]**2
isi_values.append(isi_ratio)
plt.figure(figsize=(10, 5))
plt.plot(alphas, 10 * np.log10(np.array(isi_values) + 1e-10),
'c-o', markersize=6)
plt.xlabel('滚降因子 α', fontsize=12)
plt.ylabel('ISI功率比 (dB)', fontsize=12)
plt.title('ISI与滚降因子的关系', fontsize=13)
plt.grid(True, alpha=0.3)
plt.savefig('/var/www/ttl/digital-comm/isi_analysis.png', dpi=100,
facecolor='#0f172a', edgecolor='none')
print("ISI分析图已保存")
if __name__ == '__main__':
print("=" * 60)
print("脉冲成型仿真")
print("=" * 60)
plot_rrc_responses()
plot_eye_diagram()
analyze_isi()
# 生成Verilog系数
h = design_rrc(4, 0.35, 49)
h_q15 = np.round(h * 2**15).astype(int)
print("\nVerilog RRC系数 (Q1.15, α=0.35, 4x上采样, 49抽头):")
for i, c in enumerate(h_q15):
print(f" coeffs[{i:2d}] = 16'sd{c};")
print("\n✅ 所有仿真完成!")
练习1:设计α=0.22的RRC滤波器(用于WCDMA),对比α=0.35的眼图差异。
练习2:修改Verilog代码,支持可配置滚降因子(通过寄存器选择)。
练习3:仿真定时偏差对ISI的影响:采样时刻偏移0.1Ts、0.2Ts时BER如何变化?
练习4:实现Gaussian脉冲成型(用于GMSK),比较与RRC的频谱特性。
练习5:在Python中仿真:当RRC滤波器抽头数从17增加到65时,阻带衰减如何改善?
你掌握了控制信号带宽的核心技术!从Nyquist准则到RRC滤波器,从眼图到ISI分析,你已经能让数字信号优雅地通过有限带宽的信道。
下一课预告:第06课学习载波同步——接收端如何恢复与发送端同频同相的载波?