2. 前台校准的偏移校正

任务定义

本案例针对高速ADC中由器件失配、输入直流偏置等因素引入的固定偏移误差,实现一种前台校准的偏移校正方案。要求在每次上电或用户触发时,将ADC输入端短接到模拟地,采集N次样本并计算均值作为偏移估计值,随后在正常转换结果中减去该估计值,完成数字域的偏移校正。校正后的输出应使零输入条件下的平均码值接近理想中点(如12位ADC的理想零输入码值2048)。

实现方案

采用Verilog实现一个带偏移校准功能的ADC数字后端模块。模块包含两个工作阶段:校准时,输入信号被强制置零(模拟前端短接),状态机控制累加器采集M=16个样本并求平均,得到偏移量;正常工作时,输入数据减去该偏移量后输出。校准使能信号cal_en上升沿触发一次校准流程,完成后自动进入正常工作模式。

前台校准状态机 IDLE 等待cal_en CAL_ACCUM 累加16个样本 CAL_DONE 计算偏移量 NORMAL 正常转换 cal_en↑ cnt==15 1clk cal_en=0 且校准完成 数据通路 adc_in[11:0] 减法器 data_out[11:0] offset_reg

Verilog实现

module adc_offset_cal #(
    parameter DATA_WIDTH = 12,
    parameter ACCUM_NUM  = 4  // 累加2^4=16个样本
)(
    input  wire                    clk,
    input  wire                    rst_n,
    input  wire                    cal_en,      // 校准触发(上升沿有效)
    input  wire [DATA_WIDTH-1:0]   adc_in,      // ADC原始输出
    output reg  [DATA_WIDTH-1:0]   data_out,    // 校正后输出
    output reg                     cal_done     // 校准完成标志
);

    // 状态定义
    localparam IDLE       = 2'd0;
    localparam CAL_ACCUM  = 2'd1;
    localparam CAL_DONE   = 2'd2;
    localparam NORMAL     = 2'd3;

    reg [1:0] state, next_state;
    reg [ACCUM_NUM-1:0] sample_cnt;
    reg [DATA_WIDTH+ACCUM_NUM-1:0] accum_sum;
    reg [DATA_WIDTH-1:0] offset_reg;
    reg cal_en_d1, cal_en_rise;

    // 上升沿检测
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            cal_en_d1 <= 1'b0;
        end else begin
            cal_en_d1 <= cal_en;
        end
    end
    assign cal_en_rise = cal_en & ~cal_en_d1;

    // 状态寄存器
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n)
            state <= IDLE;
        else
            state <= next_state;
    end

    // 状态转移
    always @(*) begin
        next_state = state;
        case (state)
            IDLE:      if (cal_en_rise) next_state = CAL_ACCUM;
            CAL_ACCUM: if (sample_cnt == {ACCUM_NUM{1'b1}}) next_state = CAL_DONE;
            CAL_DONE:  next_state = NORMAL;
            NORMAL:    if (cal_en_rise) next_state = CAL_ACCUM;
            default:   next_state = IDLE;
        endcase
    end

    // 校准累加与偏移计算
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            sample_cnt <= 0;
            accum_sum  <= 0;
            offset_reg <= {DATA_WIDTH{1'b0}};
            cal_done   <= 1'b0;
        end else begin
            case (state)
                CAL_ACCUM: begin
                    accum_sum  <= accum_sum + adc_in;
                    sample_cnt <= sample_cnt + 1'b1;
                    cal_done   <= 1'b0;
                end
                CAL_DONE: begin
                    // 右移ACCUM_NUM位求平均
                    offset_reg <= accum_sum[ACCUM_NUM +: DATA_WIDTH];
                    cal_done   <= 1'b1;
                end
                NORMAL: begin
                    cal_done <= 1'b0;
                end
                default: begin
                    accum_sum  <= 0;
                    sample_cnt <= 0;
                end
            endcase
        end
    end

    // 正常模式输出校正
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n)
            data_out <= {DATA_WIDTH{1'b0}};
        else if (state == NORMAL)
            data_out <= adc_in - offset_reg;
        else
            data_out <= adc_in;  // 校准期间直通
    end

endmodule

测试平台与验证

module tb_adc_offset_cal;
    reg clk = 0;
    reg rst_n = 0;
    reg cal_en = 0;
    reg [11:0] adc_in = 0;
    wire [11:0] data_out;
    wire cal_done;

    adc_offset_cal #(.DATA_WIDTH(12), .ACCUM_NUM(4)) dut(
        .clk(clk), .rst_n(rst_n), .cal_en(cal_en),
        .adc_in(adc_in), .data_out(data_out), .cal_done(cal_done)
    );

    always #5 clk = ~clk;

    initial begin
        // 复位
        #10 rst_n = 1;
        // 模拟输入偏移:零输入时ADC输出为2060(理想2048,偏移+12)
        adc_in = 2060;
        // 触发校准
        #10 cal_en = 1;
        #10 cal_en = 0;
        // 等待校准完成(16个时钟周期累加 + 1周期计算)
        wait(cal_done);
        #10;
        // 校准后正常输出应接近2048
        $display("Offset estimate: %0d", dut.offset_reg);
        $display("Calibrated output: %0d", data_out);
        // 验证:偏移估计应为12左右,输出应为2048左右
        if (dut.offset_reg == 12 && data_out == 2048)
            $display("TEST PASSED");
        else
            $display("TEST FAILED");
        #50 $finish;
    end
endmodule

运行与判定标准

运行方式:使用支持Verilog-2001的仿真器(如Vivado XSim、ModelSim、Icarus Verilog)编译并运行上述两个文件。在Vivado中可创建工程添加两个文件后运行行为仿真。

验证方法:观察仿真打印信息。测试平台模拟了零输入时ADC输出为2060(即存在+12 LSB偏移)的场景。校准完成后,offset_reg应等于12(16个2060累加后右移4位得到2060,减去理想中点2048得12),data_out应为2048。

判定标准:仿真输出打印Offset estimate: 12、Calibrated output: 2048且显示TEST PASSED,即证明前台偏移校准功能正确。若偏移估计值或校正输出与预期不符,需检查累加位宽是否足够、状态机时序是否正确。