在真实的CIC滤波器硬件设计中,积分级(Integrator)是整个链路中最消耗资源也最核心的环节。这里的“真实”体现在三个必须直面的工程挑战上:位宽扩展(Bit Growth),必须确保内部累加器位宽充足,以防止非正常的溢出饱和;分时复用存储(State RAM),每个独立通道、每一级积分器都需要自己的“记忆单元”,必须高效地存入RAM进行管理;读-改-写循环(Read-Modify-Write),必须在一个时钟周期内完成“读取旧值 -> 加上新输入 -> 写回新值”的原子操作。
下面的Verilog代码实现了一个3级积分(N=3)、支持6个通道分时复用的积分器模块,它完整地应对了上述挑战。
module real_tdm_cic_int #(
parameter CH_NUM = 6, // 通道数
parameter STAGE_NUM = 3, // 积分级数 (N=3)
parameter IN_W = 32, // 输入位宽
parameter ACC_W = 62 // 累加器位宽 (Hogenauer计算所得)
)(
input wire clk, // 高速时钟
input wire rst_n,
input wire signed [IN_W-1:0] data_in, // ADC输入数据
input wire [2:0] chan_idx, // 当前数据所属通道
input wire vld_in, // 输入有效
output reg signed [ACC_W-1:0] data_out, // 积分完成后的输出
output reg [2:0] out_idx,
output reg vld_out
);
// --- 1. 状态存储:使用 RAM 存储每一级积分器的值 ---
// 总大小 = 通道数 * 级数 = 6 * 3 = 18 个存储单元
reg signed [ACC_W-1:0] int_state_ram [0:CH_NUM*STAGE_NUM-1];
// --- 2. 内部控制信号 ---
reg [1:0] stage_cnt; // 用于在一帧内循环 3 个级数
reg signed [ACC_W-1:0] acc_tmp; // 当前级的累加结果
// 状态机定义
localparam S_IDLE = 2'd0;
localparam S_PROC = 2'd1;
reg [1:0] state;
// --- 3. 核心累加器逻辑 ---
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
state <= S_IDLE;
stage_cnt <= 0;
vld_out <= 0;
// 提示:实际硬件中建议用循环初始化 RAM 为 0
end else begin
case(state)
S_IDLE: begin
vld_out <= 0;
if(vld_in) begin
// 当新采样点进入时,准备开始第 1 级的处理
acc_tmp <= $signed(data_in);
stage_cnt <= 0;
state <= S_PROC;
end
end
S_PROC: begin
// 计算公式:New_State = Old_State + Input_From_Previous_Stage
// 1. 读取该通道、该级的旧值并加上输入
// 2. 将结果存回 RAM,并传递给下一级
acc_tmp <= int_state_ram[chan_idx * STAGE_NUM + stage_cnt] + acc_tmp;
int_state_ram[chan_idx * STAGE_NUM + stage_cnt] <= int_state_ram[chan_idx * STAGE_NUM + stage_cnt] + acc_tmp;
if(stage_cnt == STAGE_NUM - 1) begin
// 3 级积分全部完成
data_out <= int_state_ram[chan_idx * STAGE_NUM + stage_cnt] + acc_tmp;
out_idx <= chan_idx;
vld_out <= 1'b1;
state <= S_IDLE;
end else begin
stage_cnt <= stage_cnt + 1'b1;
end
end
default: state <= S_IDLE;
endcase
end
end
endmodule
设计要点与原理剖析
为什么代码要这样设计?我们逐点拆解背后的工程考量。
① 累加器位宽(ACC_W = 62)
一个常见的误解是:32位输入,累加器用32位就够了。然而,CIC滤波器的积分级是没有反馈抑制的纯累加运算。如果你需要进行数百倍的下采样(例如610倍),根据Hogenauer位宽增长公式,内部位宽会剧烈膨胀。这里的62位就是为了确保在信号达到滤波器理论最大增益时,累加器不会因为位宽不足而产生无法恢复的溢出错误。这种位宽计算是数字信号处理中信号与系统理论在具体硬件实现上的直接体现。
② 状态RAM的布局(State Storage)
如果简单地用 reg [61:0] stage1, stage2, stage3; 来声明,将无法支持多通道分时复用。因此,我们定义了二维数组 int_state_ram。通道0的三级积分值存储在地址0, 1, 2;通道1的值存储在地址3, 4, 5,以此类推。通过 chan_idx * STAGE_NUM + stage_cnt 这种索引方式,我们巧妙地用同一套加法器电路,轮流处理总共18个不同的积分状态,极大地节省了硬件资源。
③ 2的补码卷绕(The "Wrap-Around" Magic)
请注意,代码中没有任何 if (overflow) 的溢出判断逻辑。这是CIC滤波器设计中最精妙的一点:积分级允许自然溢出,只要使用的是有符号补码运算(signed),并且总位宽满足Hogenauer公式的要求,积分级溢出产生的“脏数据”会在后续的梳状级(Comb)通过减法被完美地抵消掉。切记,不要在积分级画蛇添足地添加饱和或截断逻辑,这会破坏整个滤波器的频率响应!
④ 读-改-写(Read-Modify-Write)的时序与性能
在 S_PROC 状态下,一个时钟周期内完成:在时钟上升沿读出RAM中的旧状态;通过组合逻辑计算“旧状态 + 级联输入”;在下一个时钟上升沿将结果写回RAM。这种“单周期完成”的逻辑在50MHz时钟频率下运行绰绰有余。但如果你未来需要将设计推到200MHz甚至更高,就需要将这一步拆分成多级流水线,以满足时序收敛的要求。
在仿真验证时,你可以重点观察几个现象:
- 数据的指数级增长:观察
int_state_ram 中的数值,你会发现它增长极快,迅速超出32位有符号数的表示范围,这正是位宽需要扩展的直观证明。
- 通道隔离性:切换
chan_idx,验证通道0的累加值是否完全不会干扰通道1的值,确保分时复用和存储隔离的正确性。
- 状态机流程:观察当
vld_in 有效后,stage_cnt 是否从0递增到2,并在最后一级完成后正确拉高 vld_out。
通过这样的设计与分析,我们不仅实现了一个功能正确的CIC积分器,更深入理解了其背后的硬件思维与工程权衡。更多关于硬件设计与计算机体系结构的深度讨论,欢迎在云栈社区交流分享。