4. 乘加器(MAC)微架构设计:基本MAC单元、流水线MAC、低功耗MAC设计

乘加器,也就是MAC,是卷积加速器的心脏。说白了,卷积运算就是不停地做“乘”和“加”。一个卷积层里,可能有几百万甚至上亿次这样的操作。所以,MAC单元设计得好不好,直接决定了加速器的性能和功耗。

我个人习惯把MAC设计分成三个层次来看:先搞定最基本的单元,再考虑怎么让它跑得更快,最后琢磨怎么让它更省电。咱们一个一个来聊。

4.1 基本MAC单元:从零开始搭积木

最基本的MAC单元,干的事情很简单:累加值 = 累加值 + 输入A * 输入B。嗯,就这一句话。但在硬件里,它对应着一个乘法器和一个加法器。

结构长什么样?

一个典型的单周期MAC,数据流是这样的:

  • 输入寄存器:先把输入A和输入B锁存住,避免组合逻辑的毛刺传到内部。
  • 乘法器:做A*B的运算。我建议用布斯编码的乘法器,面积和速度比较均衡。
  • 加法器:把乘法结果和上一次的累加值相加。
  • 累加寄存器:存着当前的累加结果,同时反馈给加法器的另一个输入端。

这里有个坑,我刚开始做的时候踩过。累加寄存器的清零信号,一定要处理好。卷积计算每个窗口开始前,累加值必须归零。我曾经用了一个异步清零,结果时序分析老报错。后来改成同步清零,配合数据有效信号,问题就解决了。

核心要点:基本MAC的位宽选择很关键。输入数据是8bit,权重是8bit,乘法结果就是16bit。累加过程中,为了防止溢出,累加器位宽一般要扩展到32bit甚至更宽。具体多宽?得看卷积窗口大小和输入通道数。

// 一个简单的单周期MAC Verilog代码
module mac_single_cycle (
    input  clk,
    input  rst_n,
    input  [7:0] data_in,
    input  [7:0] weight,
    input  clear,          // 累加清零信号
    output reg [31:0] acc_out
);

    wire [15:0] mult_result;
    wire [31:0] add_result;

    // 乘法
    assign mult_result = data_in * weight;

    // 加法 + 累加
    assign add_result = acc_out + mult_result;

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n)
            acc_out <= 32'd0;
        else if (clear)
            acc_out <= 32'd0;
        else
            acc_out <= add_result;
    end

endmodule

你看,代码很简单。但实际项目里,我不会这么写。为什么?因为单周期MAC的时序压力太大了。乘法器加加法器,组合逻辑路径太长,频率根本上不去。这就引出了流水线MAC。

4.2 流水线MAC:让时钟跑得更快

单周期MAC的问题,说白了就是“一口吃不成胖子”。我们把乘法和加法拆开,中间插一级寄存器,这就是流水线。

两级流水线MAC

我一般这么切:

  • 第一级:输入寄存 + 乘法运算。这一级结束,乘法结果被锁存到中间寄存器。
  • 第二级:加法运算 + 累加寄存。这一级把乘法结果和上一次的累加值相加,然后存回去。

这样做的好处很明显:每一级的组合逻辑变短了,时钟频率可以提上去。代价是,结果会晚一个时钟周期出来。不过对于卷积加速器来说,这点延迟完全能接受。

我的经验:流水线级数不是越多越好。对于8bit x 8bit的乘法,两级流水线足够了。如果数据位宽更大,比如16bit x 16bit,我可能会用到三级:第一级做部分积生成,第二级做部分积压缩,第三级做最终加法。

// 两级流水线MAC
module mac_pipeline (
    input  clk,
    input  rst_n,
    input  [7:0] data_in,
    input  [7:0] weight,
    input  clear,
    output reg [31:0] acc_out
);

    reg [15:0] mult_reg;  // 中间寄存器
    wire [15:0] mult_result;
    wire [31:0] add_result;

    // 第一级:乘法
    assign mult_result = data_in * weight;
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n)
            mult_reg <= 16'd0;
        else
            mult_reg <= mult_result;
    end

    // 第二级:加法 + 累加
    assign add_result = acc_out + mult_reg;
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n)
            acc_out <= 32'd0;
        else if (clear)
            acc_out <= 32'd0;
        else
            acc_out <= add_result;
    end

endmodule

你想想看,流水线MAC其实就是用寄存器换频率。面积大了点,但性能提升明显。在卷积加速器里,我们通常会例化几十上百个这样的MAC单元,组成一个脉动阵列。

4.3 低功耗MAC设计:省下来的电就是性能

做芯片的都知道,功耗是大事。尤其是卷积加速器,MAC阵列一跑起来,功耗蹭蹭往上涨。我做过一个项目,芯片回来后一测,MAC阵列功耗占了总功耗的60%以上。从那以后,我对低功耗设计就特别上心。

策略一:操作数隔离

这是最常用的一招。当MAC单元不工作时,把输入数据锁住,不让它们翻转到乘法器里。说白了,就是加一个使能信号。

// 带操作数隔离的MAC
module mac_low_power (
    input  clk,
    input  rst_n,
    input  [7:0] data_in,
    input  [7:0] weight,
    input  en,          // 使能信号
    input  clear,
    output reg [31:0] acc_out
);

    reg [7:0] data_reg, weight_reg;
    wire [15:0] mult_result;
    wire [31:0] add_result;

    // 操作数隔离:只有en有效时才更新输入寄存器
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            data_reg <= 8'd0;
            weight_reg <= 8'd0;
        end else if (en) begin
            data_reg <= data_in;
            weight_reg <= weight_in;
        end
        // 注意:en无效时,data_reg和weight_reg保持不变
    end

    assign mult_result = data_reg * weight_reg;
    // ... 后续累加逻辑同前

endmodule

策略二:门控时钟

这个更狠。当MAC单元空闲时,直接把时钟关掉。不过要注意,门控时钟对后端实现不太友好,容易引入时钟偏斜。我建议只在粗粒度上使用,比如整个MAC阵列的时钟门控,而不是每个MAC单独门控。

策略三:数据格式优化

嗯,这个是我在项目中摸索出来的。如果输入数据有很多0,比如经过ReLU激活函数后,一半以上的数据都是0。这时候,我们可以加一个“零值检测”电路。检测到输入为0时,直接跳过乘法运算,累加值保持不变。

注意:零值检测虽然省功耗,但会引入额外的比较器逻辑。如果数据中0的比例不高,反而得不偿失。我一般会在仿真阶段统计一下数据的稀疏度,再决定要不要加这个功能。

4.4 三种MAC的对比

我把这三种MAC的特点整理了一下,方便你对比选择:

类型 面积 频率 功耗 适用场景
基本MAC 小 低 中 低频、小规模设计
流水线MAC 中 高 中高 高性能卷积加速器
低功耗MAC 中 中 低 移动端、边缘计算

实际项目中,我通常会把流水线MAC和低功耗技术结合起来用。比如,在流水线MAC的基础上,加上操作数隔离和门控时钟。这样既保证了性能,又把功耗控制在合理范围内。

一句话总结:MAC设计没有银弹。你得根据项目的性能目标和功耗预算,在面积、速度、功耗之间做权衡。我个人建议,先从流水线MAC入手,再根据功耗仿真结果,逐步加入低功耗特性。

MAC微架构设计知识体系 MAC微架构设计 基本MAC单元 流水线MAC 低功耗MAC 乘法器 + 加法器 + 累加寄存器 单周期完成乘加运算 位宽扩展防止溢出 两级流水:乘法级 + 加法级 中间寄存器打断组合路径 提升时钟频率,增加吞吐量 操作数隔离 门控时钟 零值检测跳过运算 权衡面积、速度、功耗,选择适合的MAC架构

好了,关于MAC微架构设计,我就聊这么多。从基本单元到流水线,再到低功耗技巧,每一步都有它的道理。你在实际项目中,不妨先从最简单的开始,然后根据仿真结果一步步优化。记住,没有最好的设计,只有最合适的设计。

可可精品资料库