调制解调 · 第4课

第04课:QPSK/QAM调制

🔄 从BPSK到QPSK:双倍频谱效率

BPSK每个符号只携带1比特信息,频谱效率低。QPSK(Quadrature PSK)利用正交的I/Q两路各传1比特,每符号传2比特,频谱效率翻倍!

QPSK的数学表达

s(t) = I(t)·cos(2πf_ct) - Q(t)·sin(2πf_ct)

其中I路和Q路各承载1比特:

输入比特对IQ相位φ
00+1/√2+1/√2π/4
01-1/√2+1/√23π/4
11-1/√2-1/√25π/4
10+1/√2-1/√27π/4
💡 QPSK = 两个正交BPSK:I路cos载波和Q路sin载波正交,互不干扰。QPSK的BER与BPSK相同,但频谱效率翻倍!这就是正交复用的威力。

📐 QPSK的两种变体

1. 绝对QPSK vs π/4-DQPSK

绝对QPSK:星座点固定在{π/4, 3π/4, 5π/4, 7π/4}。

π/4-DQPSK:每个符号的相位是前一个符号相位加上增量Δφ。星座点在两组之间交替,最大相位跳变135°(而非180°),降低频谱扩展。

应用:π/4-DQPSK用于北美IS-136(D-AMPS)和日本PDC系统。

2. OQPSK(Offset QPSK)

将Q路信号延迟半个符号周期,避免I/Q同时跳变导致的180°相位突变。

180°相位跳变会让信号包络过零,对非线性功放非常不友好。OQPSK最大相位跳变90°,包络波动小。

应用:CDMA IS-95上行链路使用OQPSK。

📊 QAM:幅度+相位联合调制

QAM(Quadrature Amplitude Modulation)同时利用幅度和相位两个维度,实现更高的频谱效率。

s(t) = A_I·cos(2πf_ct) - A_Q·sin(2πf_ct)

常见QAM星座图

调制比特/符号星座点数频谱效率最小距离(d_min)典型应用
QPSK242 bps/Hz2√2LTE控制信道
16-QAM4164 bps/Hz2/√10LTE/WiFi5
64-QAM6646 bps/Hz2/√42WiFi5/6
256-QAM82568 bps/Hz2/√170WiFi6
1024-QAM10102410 bps/Hz2/√682WiFi7/5G
⚠️ QAM的代价:阶数越高,星座点越密,对噪声越敏感。256-QAM需要比QPSK高约20dB的SNR才能达到相同BER。实际系统中,高阶QAM只在信道条件好时使用(自适应调制)。

🔧 Verilog实现:QPSK/QAM调制解调器

// 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
✅ Verilator --lint-only 验证通过:QPSK/16-QAM映射解映射器、OQPSK调制器结构正确

🐍 Python仿真:QPSK/QAM星座图与BER

#!/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✅ 所有仿真完成!")
✅ Python仿真验证通过:QPSK/16-QAM/64-QAM星座图和BER曲线均正确
要点回顾:
  1. QPSK利用I/Q正交性,每符号传2比特,频谱效率是BPSK的2倍
  2. OQPSK偏移Q路半个符号,避免180°相位跳变,包络更恒定
  3. QAM联合调制幅度和相位,16-QAM每符号传4比特
  4. Gray编码确保相邻星座点只差1比特,最小化误码扩散
  5. 高阶QAM频谱效率高但对噪声敏感,需要高SNR

📝 课后练习

练习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课将学习脉冲成型——控制信号带宽的关键技术,理解为什么需要升余弦滤波器。