3、电源门控技术:电源开关单元、电压降分析、MTCMOS实现、唤醒时间优化

好,咱们接着聊低功耗设计里最硬核的技术之一——电源门控。说白了,就是把芯片里暂时不用的模块,直接把电源给它掐了。这招比动态电压频率调整狠多了,省电效果立竿见影。但代价也不小,实现起来坑特别多。

我最早接触电源门控,是在做一款移动应用处理器的时候。当时为了省电,几乎每个IP核都挂了电源开关。结果第一次流片回来,唤醒时电流冲击太大,直接把供电网络给拉崩了。嗯,从那以后,我对这门技术就多了几分敬畏。

3.1 电源开关单元

电源开关单元,就是那个负责“掐电”和“送电”的开关。它本质上是一个大尺寸的PMOS管或NMOS管。PMOS接在虚拟VDD和真实VDD之间,叫header开关;NMOS接在虚拟VSS和真实VSS之间,叫footer开关。

我个人习惯用header开关。为什么?因为PMOS的衬底可以直接接真实VDD,噪声隔离效果更好。当然,footer开关也有它的优势,比如面积更小,驱动能力更强。具体选哪种,得看你的设计目标和工艺条件。

开关单元的设计有几个关键参数:

  • 导通电阻(Ron):决定了电压降。Ron越小,压降越小,但面积越大。
  • 漏电流(Ioff):决定了关断时的功耗。Ioff越小,省电效果越好。
  • 开关速度:决定了唤醒时间。速度越快,唤醒越快,但冲击电流也越大。

你想想看,这三个参数其实是互相矛盾的。Ron小了,Ioff可能就大了;开关快了,冲击电流就大了。所以设计时得做权衡,没有完美的方案。

重要提示:电源开关单元不是越大越好。我见过有人为了追求极低Ron,把开关尺寸做得特别大,结果漏电流比模块本身的静态功耗还大。这就本末倒置了。

3.2 电压降分析

电压降分析,也叫IR Drop分析。这是电源门控设计里最容易被忽视,但后果最严重的环节。

为什么会这样?因为电源开关本身有电阻,电流流过时会产生压降。如果压降太大,模块的供电电压就会低于正常工作电压,导致时序违例,甚至功能错误。

我在项目中遇到过这样的情况:一个模块在仿真时一切正常,但流片回来后,某些角落的触发器就是采不到正确数据。查了几个月,最后发现是电源开关的IR Drop太大,导致局部电压低了0.1V。0.1V啊,在先进工艺下足以让时序崩盘。

电压降分析通常分两步:

  1. 静态IR Drop分析:计算平均电流下的压降。这个比较简单,用静态功耗分析工具就能做。
  2. 动态IR Drop分析:计算瞬态电流下的压降。这个复杂得多,需要做向量仿真或使用红鹰等专业工具。

我个人建议,在做电源门控设计时,一定要做动态IR Drop分析。因为唤醒瞬间的电流冲击,往往是静态分析的几倍甚至几十倍。静态分析只能告诉你“平均情况”,而动态分析才能暴露“最坏情况”。

警告:千万不要忽略电源开关本身的IR Drop。很多新手只关注电源网络的IR Drop,却忘了开关单元本身也是电阻。我曾经见过一个设计,电源网络IR Drop只有20mV,但开关单元上的压降却高达80mV。加起来100mV,直接超标。

3.3 MTCMOS实现

MTCMOS,全称Multi-Threshold CMOS,是多阈值电压CMOS技术。它是实现电源门控的基础工艺手段。

简单来说,MTCMOS把晶体管分成两类:

  • 高阈值电压(HVT)晶体管:漏电流小,但速度慢。用于电源开关单元。
  • 低阈值电压(LVT)晶体管:速度快,但漏电流大。用于逻辑电路。

你想想看,这个搭配很巧妙。逻辑电路用LVT,保证性能;电源开关用HVT,保证关断时漏电流足够小。两者结合,既省电又不牺牲速度。

MTCMOS的实现方式主要有两种:

实现方式 优点 缺点
Fine-Grain MTCMOS 每个标准单元都带独立的电源开关,控制粒度细 面积开销大,设计复杂度高
Coarse-Grain MTCMOS 整个模块共享一组电源开关,面积开销小 控制粒度粗,唤醒时间长

我个人更倾向于Coarse-Grain MTCMOS。为什么?因为Fine-Grain虽然控制灵活,但面积开销太大了。在先进工艺下,每个标准单元都加一个开关,面积可能增加30%以上。而Coarse-Grain只需要在模块周围加一圈开关,面积增加不到5%。

当然,Coarse-Grain也有它的缺点。比如唤醒时间更长,因为要同时给整个模块充电。但这个问题可以通过优化唤醒时序来解决,后面会讲到。

提示:在做MTCMOS设计时,别忘了考虑“保持寄存器”。当模块断电时,寄存器里的数据会丢失。如果需要在唤醒后恢复状态,就得用保持寄存器。这种寄存器在断电时能通过一个独立的“保持电源”来保存数据。我建议在关键状态寄存器上使用保持寄存器,其他非关键寄存器可以不用。

3.4 唤醒时间优化

唤醒时间,就是从发出唤醒信号到模块完全恢复正常工作的时间。这个时间越短越好,因为唤醒期间模块是不能正常工作的。

但唤醒时间不是想短就能短的。它受几个因素限制:

  • 电源开关的导通速度:开关从关断到完全导通需要时间。
  • 虚拟电源网络的充电时间:模块内部的寄生电容需要充电。
  • 冲击电流的限制:如果充电太快,冲击电流可能超过电源的承受能力。

我曾经在一个项目中,为了追求极短的唤醒时间,把电源开关的尺寸做得很大,同时让所有开关同时导通。结果唤醒瞬间的冲击电流高达10A,直接把芯片的电源焊盘给烧了。嗯,那次教训很深刻。

优化唤醒时间,我总结了几个实用技巧:

  1. 分步唤醒:不要一次性打开所有开关。可以分成几组,依次打开。这样冲击电流被分散了,电源网络的压力小很多。
  2. 电流限制:在唤醒路径上串联一个小电阻,限制峰值电流。虽然会稍微增加唤醒时间,但能保护电源网络。
  3. 预充电:在正式唤醒前,先用一个小电流给虚拟电源网络预充电。这样正式唤醒时,电压已经接近目标值,充电时间大大缩短。
  4. 时钟门控配合:在唤醒期间,保持模块的时钟处于门控状态。等电压稳定后再释放时钟。这样可以避免在电压不稳时触发时序违例。

重要提示:唤醒时间的优化,本质上是在“速度”和“安全”之间找平衡。我个人的经验是,先保证安全,再追求速度。因为一次流片失败的成本,远大于多等几个纳秒的唤醒时间。

最后,给大家一个参考的唤醒时序代码示例。这是一个简单的分步唤醒控制器:

// 分步唤醒控制器示例
module wakeup_controller (
    input  clk,
    input  rst_n,
    input  wakeup_req,
    output reg [3:0] switch_en,  // 4组电源开关使能
    output reg       clk_en       // 时钟使能
);

    reg [2:0] state;
    localparam IDLE     = 3'b000;
    localparam STEP1    = 3'b001;
    localparam STEP2    = 3'b010;
    localparam STEP3    = 3'b011;
    localparam STEP4    = 3'b100;
    localparam DONE     = 3'b101;

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            state <= IDLE;
            switch_en <= 4'b0000;
            clk_en <= 1'b0;
        end else begin
            case (state)
                IDLE: begin
                    if (wakeup_req) begin
                        state <= STEP1;
                        switch_en <= 4'b0001;  // 打开第1组
                    end
                end
                STEP1: begin
                    // 等待电压稳定,假设需要10个时钟周期
                    if (wakeup_timer == 10) begin
                        state <= STEP2;
                        switch_en <= 4'b0011;  // 打开第2组
                    end
                end
                STEP2: begin
                    if (wakeup_timer == 20) begin
                        state <= STEP3;
                        switch_en <= 4'b0111;  // 打开第3组
                    end
                end
                STEP3: begin
                    if (wakeup_timer == 30) begin
                        state <= STEP4;
                        switch_en <= 4'b1111;  // 打开第4组
                    end
                end
                STEP4: begin
                    if (wakeup_timer == 40) begin
                        state <= DONE;
                        clk_en <= 1'b1;        // 释放时钟
                    end
                end
                DONE: begin
                    // 正常工作
                end
            endcase
        end
    end

endmodule

这个例子很简单,但思路是对的。实际项目中,你可能需要根据电源网络的RC时间常数来精确计算每步的等待时间。我建议在仿真时多跑几个corner,确保在最坏情况下也能稳定唤醒。

好了,电源门控技术就讲到这里。下一章咱们聊聊时钟门控,那又是另一个有意思的话题。