BPSK每个符号只携带1比特信息,频谱效率低。QPSK(Quadrature PSK)利用正交的I/Q两路各传1比特,每符号传2比特,频谱效率翻倍!
其中I路和Q路各承载1比特:
| 输入比特对 | I | Q | 相位φ |
|---|---|---|---|
| 00 | +1/√2 | +1/√2 | π/4 |
| 01 | -1/√2 | +1/√2 | 3π/4 |
| 11 | -1/√2 | -1/√2 | 5π/4 |
| 10 | +1/√2 | -1/√2 | 7π/4 |
绝对QPSK:星座点固定在{π/4, 3π/4, 5π/4, 7π/4}。
π/4-DQPSK:每个符号的相位是前一个符号相位加上增量Δφ。星座点在两组之间交替,最大相位跳变135°(而非180°),降低频谱扩展。
应用:π/4-DQPSK用于北美IS-136(D-AMPS)和日本PDC系统。
将Q路信号延迟半个符号周期,避免I/Q同时跳变导致的180°相位突变。
180°相位跳变会让信号包络过零,对非线性功放非常不友好。OQPSK最大相位跳变90°,包络波动小。
应用:CDMA IS-95上行链路使用OQPSK。
QAM(Quadrature Amplitude Modulation)同时利用幅度和相位两个维度,实现更高的频谱效率。
| 调制 | 比特/符号 | 星座点数 | 频谱效率 | 最小距离(d_min) | 典型应用 |
|---|---|---|---|---|---|
| QPSK | 2 | 4 | 2 bps/Hz | 2√2 | LTE控制信道 |
| 16-QAM | 4 | 16 | 4 bps/Hz | 2/√10 | LTE/WiFi5 |
| 64-QAM | 6 | 64 | 6 bps/Hz | 2/√42 | WiFi5/6 |
| 256-QAM | 8 | 256 | 8 bps/Hz | 2/√170 | WiFi6 |
| 1024-QAM | 10 | 1024 | 10 bps/Hz | 2/√682 | WiFi7/5G |
// qpsk_qam_modem.v - QPSK和16-QAM调制解调器
// 第04课:QPSK/QAM调制
// ============================================================
// 串并转换器 (SISO→PISO)
// ============================================================
module serial_to_parallel #(
parameter BIT_W = 2, // 输出位宽 (QPSK=2, 16QAM=4)
parameter DATA_W = 1 // 输入位宽
)(
input wire clk,
input wire rst_n,
input wire din_valid,
input wire din,
output reg [BIT_W-1:0] dout,
output reg dout_valid,
input wire dout_ready
);
reg [$clog2(BIT_W)-1:0] bit_cnt;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
dout <= 0;
dout_valid <= 1'b0;
bit_cnt <= 0;
end else if (din_valid) begin
dout <= {dout[BIT_W-2:0], din};
if (bit_cnt == BIT_W - 1) begin
dout_valid <= 1'b1;
bit_cnt <= 0;
end else begin
bit_cnt <= bit_cnt + 1'b1;
dout_valid <= 1'b0;
end
end else if (dout_ready) begin
dout_valid <= 1'b0;
end
end
endmodule
// ============================================================
// QPSK映射器
// ============================================================
module qpsk_mapper #(
parameter OUT_W = 12
)(
input wire clk,
input wire rst_n,
input wire [1:0] symbol_in, // 2-bit输入
input wire valid_in,
output reg signed [OUT_W-1:0] i_out, // I路输出
output reg signed [OUT_W-1:0] q_out, // Q路输出
output reg valid_out
);
// 归一化映射: ±(2^(OUT_W-2)-1) ≈ ±全幅/√2
localparam signed [OUT_W-1:0] POS_VAL = {(OUT_W-2){1'b1}}; // 正值
localparam signed [OUT_W-1:0] NEG_VAL = ~{(OUT_W-2){1'b1}} + 1'b1; // 负值
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
i_out <= 0;
q_out <= 0;
valid_out <= 1'b0;
end else if (valid_in) begin
valid_out <= 1'b1;
case (symbol_in)
2'b00: begin i_out <= POS_VAL; q_out <= POS_VAL; end // π/4
2'b01: begin i_out <= NEG_VAL; q_out <= POS_VAL; end // 3π/4
2'b11: begin i_out <= NEG_VAL; q_out <= NEG_VAL; end // 5π/4
2'b10: begin i_out <= POS_VAL; q_out <= NEG_VAL; end // 7π/4
default: begin i_out <= 0; q_out <= 0; end
endcase
end else begin
valid_out <= 1'b0;
end
end
endmodule
// ============================================================
// 16-QAM映射器
// ============================================================
module qam16_mapper #(
parameter OUT_W = 12
)(
input wire clk,
input wire rst_n,
input wire [3:0] symbol_in, // 4-bit输入
input wire valid_in,
output reg signed [OUT_W-1:0] i_out,
output reg signed [OUT_W-1:0] q_out,
output reg valid_out
);
// 16-QAM: 4个幅度等级 {-3, -1, +1, +3} × A/√10
// 归一化到OUT_W位宽
localparam signed [OUT_W-1:0] LEVEL_3P = 3 * (2**(OUT_W-3)) - 1; // +3
localparam signed [OUT_W-1:0] LEVEL_1P = 1 * (2**(OUT_W-3)) - 1; // +1
localparam signed [OUT_W-1:0] LEVEL_1N = -1 * (2**(OUT_W-3)); // -1
localparam signed [OUT_W-1:0] LEVEL_3N = -3 * (2**(OUT_W-3)); // -3
// I路映射 (高2位)
function signed [OUT_W-1:0] map_i;
input [1:0] bits;
case (bits)
2'b00: map_i = LEVEL_3N;
2'b01: map_i = LEVEL_1N;
2'b11: map_i = LEVEL_1P;
2'b10: map_i = LEVEL_3P;
default: map_i = 0;
endcase
endfunction
// Q路映射 (低2位)
function signed [OUT_W-1:0] map_q;
input [1:0] bits;
case (bits)
2'b00: map_q = LEVEL_3N;
2'b01: map_q = LEVEL_1N;
2'b11: map_q = LEVEL_1P;
2'b10: map_q = LEVEL_3P;
default: map_q = 0;
endcase
endfunction
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
i_out <= 0;
q_out <= 0;
valid_out <= 1'b0;
end else if (valid_in) begin
i_out <= map_i(symbol_in[3:2]);
q_out <= map_q(symbol_in[1:0]);
valid_out <= 1'b1;
end else begin
valid_out <= 1'b0;
end
end
endmodule
// ============================================================
// QPSK解映射器 (硬判决)
// ============================================================
module qpsk_demapper #(
parameter IN_W = 12
)(
input wire clk,
input wire rst_n,
input wire signed [IN_W-1:0] i_in,
input wire signed [IN_W-1:0] q_in,
input wire valid_in,
output reg [1:0] symbol_out,
output reg valid_out
);
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
symbol_out <= 0;
valid_out <= 1'b0;
end else if (valid_in) begin
valid_out <= 1'b1;
// 根据I/Q正负判决
symbol_out[1] = ~i_in[IN_W-1]; // I>0 → 1
symbol_out[0] = ~q_in[IN_W-1]; // Q>0 → 1
end else begin
valid_out <= 1'b0;
end
end
endmodule
// ============================================================
// 16-QAM解映射器 (硬判决)
// ============================================================
module qam16_demapper #(
parameter IN_W = 12
)(
input wire clk,
input wire rst_n,
input wire signed [IN_W-1:0] i_in,
input wire signed [IN_W-1:0] q_in,
input wire valid_in,
output reg [3:0] symbol_out,
output reg valid_out
);
// 判决门限: 0 和 ±2×scale
localparam signed [IN_W-1:0] THRESH_0 = 0;
localparam signed [IN_W-1:0] THRESH_2 = 2 * (2**(IN_W-3));
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
symbol_out <= 0;
valid_out <= 1'b0;
end else if (valid_in) begin
valid_out <= 1'b1;
// I路判决
if (i_in < -THRESH_2)
symbol_out[3:2] <= 2'b00;
else if (i_in < THRESH_0)
symbol_out[3:2] <= 2'b01;
else if (i_in < THRESH_2)
symbol_out[3:2] <= 2'b11;
else
symbol_out[3:2] <= 2'b10;
// Q路判决
if (q_in < -THRESH_2)
symbol_out[1:0] <= 2'b00;
else if (q_in < THRESH_0)
symbol_out[1:0] <= 2'b01;
else if (q_in < THRESH_2)
symbol_out[1:0] <= 2'b11;
else
symbol_out[1:0] <= 2'b10;
end else begin
valid_out <= 1'b0;
end
end
endmodule
// ============================================================
// OQPSK调制器 (Q路偏移半个符号)
// ============================================================
module oqpsk_modulator #(
parameter OUT_W = 12,
parameter OVERSAMPLE = 4
)(
input wire clk,
input wire rst_n,
input wire [1:0] symbol_in,
input wire valid_in,
output wire signed [OUT_W-1:0] i_out,
output wire signed [OUT_W-1:0] q_out,
output wire valid_out
);
wire signed [OUT_W-1:0] q_mapped;
wire q_mapped_valid;
// I路直接映射
qpsk_mapper #(.OUT_W(OUT_W)) u_i_map (
.clk(clk), .rst_n(rst_n),
.symbol_in(symbol_in), .valid_in(valid_in),
.i_out(i_out), .q_out(), .valid_out(valid_out)
);
// Q路延迟半个符号周期 (OVERSAMPLE/2个时钟)
localparam HALF_SYMBOL = OVERSAMPLE / 2;
reg signed [OUT_W-1:0] q_delay [0:HALF_SYMBOL-1];
reg [1:0] valid_delay [0:HALF_SYMBOL-1];
integer i;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
for (i = 0; i < HALF_SYMBOL; i = i + 1) begin
q_delay[i] <= 0;
valid_delay[i] <= 1'b0;
end
end else begin
q_delay[0] <= q_mapped;
valid_delay[0] <= q_mapped_valid;
for (i = 1; i < HALF_SYMBOL; i = i + 1) begin
q_delay[i] <= q_delay[i-1];
valid_delay[i] <= valid_delay[i-1];
end
end
end
assign q_out = q_delay[HALF_SYMBOL-1];
endmodule
#!/usr/bin/env python3
"""qpsk_qam_simulation.py - QPSK/QAM调制仿真
第04课:QPSK/QAM调制
"""
import numpy as np
import matplotlib.pyplot as plt
from scipy.special import erfc
def qpsk_modulate(bits):
"""QPSK调制:2比特→I/Q符号"""
bits = np.array(bits)
if len(bits) % 2 != 0:
bits = np.append(bits, 0)
symbols_i = 1 - 2 * bits[0::2] # I路: 0→+1, 1→-1
symbols_q = 1 - 2 * bits[1::2] # Q路: 0→+1, 1→-1
return symbols_i / np.sqrt(2), symbols_q / np.sqrt(2)
def qam16_modulate(bits):
"""16-QAM调制:4比特→I/Q符号"""
bits = np.array(bits)
while len(bits) % 4 != 0:
bits = np.append(bits, 0)
n_symbols = len(bits) // 4
i_vals = np.zeros(n_symbols)
q_vals = np.zeros(n_symbols)
# Gray码映射
qam_map = {
(0,0): -3, (0,1): -1, (1,1): +1, (1,0): +3
}
for k in range(n_symbols):
b = bits[4*k:4*k+4]
i_vals[k] = qam_map[(b[0], b[1])]
q_vals[k] = qam_map[(b[2], b[3])]
# 归一化: 平均功率 = 1
norm = np.sqrt(np.mean(i_vals**2 + q_vals**2))
return i_vals / norm, q_vals / norm
def qam64_modulate(bits):
"""64-QAM调制:6比特→I/Q符号"""
bits = np.array(bits)
while len(bits) % 6 != 0:
bits = np.append(bits, 0)
n_symbols = len(bits) // 6
i_vals = np.zeros(n_symbols)
q_vals = np.zeros(n_symbols)
qam_map_8 = {
(0,0,0): -7, (0,0,1): -5, (0,1,1): -3, (0,1,0): -1,
(1,1,0): +1, (1,1,1): +3, (1,0,1): +5, (1,0,0): +7
}
for k in range(n_symbols):
b = bits[6*k:6*k+6]
i_vals[k] = qam_map_8[tuple(b[0:3])]
q_vals[k] = qam_map_8[tuple(b[3:6])]
norm = np.sqrt(np.mean(i_vals**2 + q_vals**2))
return i_vals / norm, q_vals / norm
def simulate_ber_qam(mod_order, snr_db_range, num_bits=200000):
"""仿真QAM在AWGN下的BER"""
np.random.seed(42)
bits = np.random.randint(0, 2, num_bits)
if mod_order == 4: # QPSK
bits_per_sym = 2
i_sym, q_sym = qpsk_modulate(bits)
elif mod_order == 16:
bits_per_sym = 4
i_sym, q_sym = qam16_modulate(bits)
elif mod_order == 64:
bits_per_sym = 6
i_sym, q_sym = qam64_modulate(bits)
ber_sim = []
for snr_db in snr_db_range:
snr_lin = 10 ** (snr_db / 10)
# 每符号维度上的噪声方差
noise_var = 1.0 / (2 * snr_lin)
noise_i = np.sqrt(noise_var) * np.random.randn(len(i_sym))
noise_q = np.sqrt(noise_var) * np.random.randn(len(q_sym))
rx_i = i_sym + noise_i
rx_q = q_sym + noise_q
# 硬判决
if mod_order == 4:
rx_bits_i = (rx_i < 0).astype(int)
rx_bits_q = (rx_q < 0).astype(int)
rx_bits = np.zeros(len(bits))
rx_bits[0::2] = rx_bits_i
rx_bits[1::2] = rx_bits_q
elif mod_order == 16:
# 16-QAM判决(简化)
rx_bits = np.zeros(len(bits))
levels = np.array([-3, -1, 1, 3])
for k in range(len(i_sym)):
# I路
dist_i = np.abs(rx_i[k] * np.sqrt(10) - levels)
dec_i = levels[np.argmin(dist_i)]
# Q路
dist_q = np.abs(rx_q[k] * np.sqrt(10) - levels)
dec_q = levels[np.argmin(dist_q)]
# 反映射 (简化,非Gray)
for b_idx, val in enumerate([-3,-1,1,3]):
if dec_i == val: rx_bits[4*k] = b_idx >> 1; rx_bits[4*k+1] = b_idx & 1
if dec_q == val: rx_bits[4*k+2] = b_idx >> 1; rx_bits[4*k+3] = b_idx & 1
ber = np.sum(bits[:len(rx_bits)] != rx_bits[:len(bits)]) / len(bits)
ber_sim.append(max(ber, 1e-7))
return ber_sim
def plot_constellations():
"""绘制QPSK/16-QAM/64-QAM星座图"""
np.random.seed(42)
fig, axes = plt.subplots(1, 3, figsize=(18, 6))
snr_db = 15
# QPSK
bits = np.random.randint(0, 2, 2000)
i_sym, q_sym = qpsk_modulate(bits)
noise = 0.08 * np.random.randn(len(i_sym))
axes[0].scatter(i_sym + noise, q_sym + noise, c='cyan', s=4, alpha=0.5)
axes[0].set_title('QPSK 星座图', fontsize=14)
axes[0].set_xlabel('I'); axes[0].set_ylabel('Q')
axes[0].grid(True, alpha=0.3); axes[0].axis('equal')
# 16-QAM
bits = np.random.randint(0, 2, 4000)
i_sym, q_sym = qam16_modulate(bits)
noise = 0.04 * np.random.randn(len(i_sym))
axes[1].scatter(i_sym + noise, q_sym + noise, c='#f59e0b', s=4, alpha=0.5)
axes[1].set_title('16-QAM 星座图', fontsize=14)
axes[1].set_xlabel('I'); axes[1].set_ylabel('Q')
axes[1].grid(True, alpha=0.3); axes[1].axis('equal')
# 64-QAM
bits = np.random.randint(0, 2, 6000)
i_sym, q_sym = qam64_modulate(bits)
noise = 0.02 * np.random.randn(len(i_sym))
axes[2].scatter(i_sym + noise, q_sym + noise, c='#10b981', s=3, alpha=0.5)
axes[2].set_title('64-QAM 星座图', fontsize=14)
axes[2].set_xlabel('I'); axes[2].set_ylabel('Q')
axes[2].grid(True, alpha=0.3); axes[2].axis('equal')
plt.tight_layout()
plt.savefig('/var/www/ttl/digital-comm/qam_constellations.png', dpi=100,
facecolor='#0f172a', edgecolor='none')
print("星座图已保存")
def plot_ber_comparison():
"""绘制各QAM阶数的BER对比"""
snr_range = np.arange(0, 25)
ber_qpsk = simulate_ber_qam(4, snr_range)
ber_qam16 = simulate_ber_qam(16, snr_range)
ber_qam64 = simulate_ber_qam(64, snr_range)
plt.figure(figsize=(10, 7))
plt.semilogy(snr_range, ber_qpsk, 'c-o', markersize=4, label='QPSK')
plt.semilogy(snr_range, ber_qam16, '#f59e0b-^', markersize=4, label='16-QAM')
plt.semilogy(snr_range, ber_qam64, '#10b981-s', markersize=4, label='64-QAM')
plt.xlabel('Eb/N0 (dB)', fontsize=12)
plt.ylabel('BER', fontsize=12)
plt.title('QPSK vs 16-QAM vs 64-QAM 误码率对比', fontsize=14)
plt.legend(fontsize=11)
plt.grid(True, alpha=0.3, which='both')
plt.ylim(1e-6, 1)
plt.savefig('/var/www/ttl/digital-comm/qam_ber.png', dpi=100,
facecolor='#0f172a', edgecolor='none')
print("BER对比图已保存")
if __name__ == '__main__':
print("=" * 60)
print("QPSK/QAM 调制仿真")
print("=" * 60)
plot_constellations()
plot_ber_comparison()
print("\n✅ 所有仿真完成!")
练习1:实现64-QAM映射器/解映射器,绘制64-QAM星座图。
练习2:比较QPSK和π/4-DQPSK的相位跳变轨迹,分析对功放的影响。
练习3:实现自适应调制:根据SNR动态选择QPSK/16-QAM/64-QAM。
练习4:用Python仿真:16-QAM在瑞利衰落信道下的BER(与AWGN对比)。
练习5:修改Verilog 16-QAM映射器,使用Gray码映射,分析误码率改善。
你掌握了正交调制的精髓!从QPSK到256-QAM,从Gray编码到硬判决解映射,你已经能在频谱效率和抗噪声之间做出权衡了。
下一课预告:第05课将学习脉冲成型——控制信号带宽的关键技术,理解为什么需要升余弦滤波器。