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