1. 基于LMS算法的后台校准

基于LMS算法的后台校准 学习卡片

任务定义

本案例实现一个基于LMS(最小均方)自适应算法的后台数字校准模块,用于校正高速ADC中由电容失配、比较器失调等引起的非线性误差。给定一个包含失配误差的12位ADC输出序列,通过LMS算法自适应估计误差权重并在后台实时扣除,使输出信纳比(SINAD)得到提升。案例提供完整的可综合Verilog实现、Testbench及MATLAB验证脚本,形成从算法到RTL的闭环。

算法原理与校准架构

后台校准的核心思想是利用LMS算法在正常转换过程中持续跟踪误差系数,无需中断ADC工作。设ADC理想输出为x[n],实际含误差输出为d[n] = x[n] + e[n],其中e[n]为失配引入的确定性误差。校准器维护一个误差估计y[n] = w[n]·u[n],其中u[n]为误差特征向量(本案例取输出码的低6位构成的温度计码特征),w[n]为自适应权重。LMS更新方程为:

w[n+1] = w[n] + μ·(d[n] − y[n])·u[n]

校准输出为d[n] − y[n]。μ为步长因子,控制收敛速度与稳态失调的折中。本案例采用符号LMS变体以降低硬件复杂度:误差符号与特征符号相乘后仅需移位累加。

Verilog实现

以下RTL代码实现了一个64阶(6位特征)符号LMS后台校准器。输入为12位ADC码,输出为12位校准码。特征向量由输出码的低6位经二进制转温度计码生成,权重为12位有符号数,步长通过右移4位实现(μ=1/16)。

```verilog
module lms_background_cal #(
    parameter ADC_WIDTH = 12,
    parameter FEAT_WIDTH = 6,
    parameter W_WIDTH = 12
)(
    input  wire clk,
    input  wire rst_n,
    input  wire [ADC_WIDTH-1:0] adc_in,      // 含误差的ADC输出
    output wire [ADC_WIDTH-1:0] cal_out      // 校准后输出
);

    // 特征向量:取adc_in低6位,转为温度计码(64位)
    wire [63:0] feat;
    genvar i;
    generate
        for (i = 0; i < 64; i = i + 1) begin : therm_code
            assign feat[i] = (adc_in[FEAT_WIDTH-1:0] > i) ? 1'b1 : 1'b0;
        end
    endgenerate

    // 权重存储(64个12位有符号数)
    reg signed [W_WIDTH-1:0] w [0:63];
    integer j;
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            for (j = 0; j < 64; j = j + 1) w[j] <= 12'sd0;
        end
    end

    // 误差估计:y = sum(w[k] * feat[k]),取高12位截断
    reg signed [W_WIDTH+6:0] y_acc;
    reg signed [W_WIDTH-1:0] y_est;
    reg signed [ADC_WIDTH-1:0] err_signed;
    reg signed [ADC_WIDTH-1:0] adc_signed;
    reg signed [ADC_WIDTH-1:0] cal_reg;
    reg signed [W_WIDTH-1:0] err_sign;
    reg [5:0] idx;

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            y_acc <= 0;
            y_est <= 0;
            cal_reg <= 0;
        end else begin
            // 计算误差估计(组合逻辑展开为顺序累加)
            y_acc = 0;
            for (idx = 0; idx < 64; idx = idx + 1) begin
                if (feat[idx])
                    y_acc = y_acc + w[idx];
            end
            y_est = y_acc[W_WIDTH+5:6];  // 截取高12位

            // 误差计算与符号提取
            adc_signed = $signed(adc_in);
            err_signed = adc_signed - y_est;
            err_sign = (err_signed[ADC_WIDTH-1]) ? -12'sd1 : 12'sd1;

            // 权重更新(符号LMS:w += μ * sign(err) * feat)
            for (idx = 0; idx < 64; idx = idx + 1) begin
                if (feat[idx])
                    w[idx] <= w[idx] + (err_sign >>> 4);  // 右移4位等效乘1/16
            end

            cal_reg <= adc_signed - y_est;
        end
    end

    assign cal_out = cal_reg[ADC_WIDTH-1:0];

endmodule
```

Testbench与验证

以下Testbench生成一个叠加了二次非线性失真的正弦波作为ADC输入,运行校准并计算校准前后的SINAD对比。

```verilog
`timescale 1ns/1ps
module tb_lms_cal;
    reg clk = 0;
    reg rst_n = 0;
    reg [11:0] adc_in;
    wire [11:0] cal_out;

    // 生成含失真的ADC输出:理想正弦 + 二次失真项
    real ideal_sin, distorted;
    integer fd;
    real sinad_before, sinad_after;

    lms_background_cal dut(
        .clk(clk), .rst_n(rst_n),
        .adc_in(adc_in), .cal_out(cal_out)
    );

    always #5 clk = ~clk;

    initial begin
        // 打开记录文件
        fd = $fopen("cal_results.txt", "w");
        rst_n = 0;
        #20 rst_n = 1;

        // 仿真20000个采样点
        for (int n = 0; n < 20000; n++) begin
            #10;
            ideal_sin = 2047.5 + 2000.0 * $sin(2 * 3.14159 * 50.0 * n / 10000.0);
            // 二次失真:理想值的平方项折算到12位范围
            distorted = ideal_sin + 0.0004 * (ideal_sin - 2047.5) * (ideal_sin - 2047.5);
            if (distorted > 4095) distorted = 4095;
            if (distorted < 0) distorted = 0;
            adc_in = $rtoi(distorted);
            // 每100点记录一次校准前后数据
            if (n % 100 == 0)
                $fwrite(fd, "%0d %0d %0d\n", n, adc_in, cal_out);
        end
        $fclose(fd);
        $finish;
    end
endmodule
```

MATLAB离线验证

仿真结束后,使用以下MATLAB脚本读取记录文件,计算校准前后的SINAD,验证校准效果。

```matlab
% 读取仿真数据
data = load('cal_results.txt');
n = data(:,1); adc_raw = data(:,2); adc_cal = data(:,3);

% 去除直流分量后计算SINAD
raw_ac = adc_raw - mean(adc_raw);
cal_ac = adc_cal - mean(adc_cal);

% 计算信号功率(50点周期对应的基频)
N = length(raw_ac);
win = hann(N)';
raw_fft = fft(raw_ac .* win);
cal_fft = fft(cal_ac .* win);

% 基频位置(50点周期对应频率索引)
f_sig = 50;
[~, idx] = max(abs(raw_fft(2:N/2)));
sig_pow_raw = abs(raw_fft(idx))^2;
noise_pow_raw = sum(abs(raw_fft(2:N/2)).^2) - sig_pow_raw;
sinad_raw = 10*log10(sig_pow_raw / noise_pow_raw);

[~, idx2] = max(abs(cal_fft(2:N/2)));
sig_pow_cal = abs(cal_fft(idx2))^2;
noise_pow_cal = sum(abs(cal_fft(2:N/2)).^2) - sig_pow_cal;
sinad_cal = 10*log10(sig_pow_cal / noise_pow_cal);

fprintf('校准前SINAD: %.2f dB\n', sinad_raw);
fprintf('校准后SINAD: %.2f dB\n', sinad_cal);
fprintf('改善量: %.2f dB\n', sinad_cal - sinad_raw);
```

运行与验证方法

步骤1:RTL仿真。在Vivado或ModelSim中编译lms_background_cal.v与tb_lms_cal.v,运行20000个时钟周期仿真。仿真结束后当前目录生成cal_results.txt文件。

步骤2:MATLAB分析。在MATLAB中运行上述脚本,读取cal_results.txt并计算SINAD。

判定标准:校准后SINAD应比校准前提升至少10dB(例如从约45dB提升至55dB以上),且校准输出波形无明显非线性削波。若改善不足,可检查步长因子(右移位数)是否过大导致稳态失调,或仿真点数是否足够收敛。

关键参数与调整说明

参数默认值调整影响
步长右移位数4(μ=1/16)右移位数越小步长越大,收敛快但稳态误差大;反之收敛慢但精度高
特征位数6位(64阶)特征位数需覆盖误差非线性阶数,位数不足会残留未校正误差
权重位宽12位位宽不足会导致权重饱和,需根据误差幅度预留动态范围
LMS后台校准系统架构与数据流 12-bit ADC 含失配误差 特征提取 低6位→温度计码(64位) 权重阵列 w[0:63] 12-bit × 64 误差估计 Σ w[k]·feat[k] d[n] feat[63:0] w[63:0] − y[n] d[n](含误差) 校准输出 d[n] − y[n] LMS权重更新 w[n+1] = w[n] + μ·sign(e)·feat 误差符号 sign(e) feat 信号路径: ADC数据 特征/校准 误差反馈 权重

收敛性与资源说明

本设计权重更新采用符号LMS,硬件仅需比较器、加法器和移位寄存器,无需乘法器。64个12位权重寄存器约占用768个触发器,在主流FPGA上资源开销可忽略。收敛时间约为1/(μ·λ_max)个采样周期,其中λ_max为特征相关矩阵最大特征值;本案例参数下约需5000~8000个采样点达到稳态。若需加快收敛可增大步长(减少右移位数),但需注意稳态失调随之增大。