4. 电源门控技术:内部电源开关设计、唤醒逻辑与状态保持策略

各位做低功耗设计的同行,咱们今天聊一个硬核话题——电源门控。说白了,就是把芯片内部暂时不用的模块彻底断电。这招在CPLD里用好了,功耗能降一个数量级。我早年做一款电池供电的便携设备,就是靠这招把待机电流从mA级压到了μA级。

4.1 内部电源开关设计

CPLD内部的电源开关,不像板级那么粗放。咱们得在芯片内部做精细控制。我习惯把电源开关分成两类:

  • 粗粒度开关:控制整个功能模块,比如一个定时器、一个通信接口
  • 细粒度开关:控制模块内的子电路,比如寄存器阵列、组合逻辑块

实际项目中,我推荐用粗粒度为主。为什么?因为细粒度开关的控制逻辑太复杂,反而容易引入毛刺和时序问题。你想想看,一个模块里几十个开关同时切换,电源网络瞬间抖动,搞不好就把数据搞丢了。

核心设计原则:电源开关必须用PMOS管实现,且要加使能信号同步处理。千万别用NMOS管做高端开关,那会引入额外的压降。

来看一个典型的内部电源开关电路:

// Verilog示例:带使能同步的电源开关控制
module power_switch (
    input  wire clk,          // 系统时钟
    input  wire rst_n,        // 复位
    input  wire en,           // 使能信号(异步)
    output reg  pwr_good,     // 电源稳定标志
    output wire pwr_en        // 电源开关控制
);

reg en_sync1, en_sync2;

// 两级同步器,防止亚稳态
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        en_sync1 <= 1'b0;
        en_sync2 <= 1'b0;
    end else begin
        en_sync1 <= en;
        en_sync2 <= en_sync1;
    end
end

assign pwr_en = en_sync2;

// 电源稳定延时计数器
reg [3:0] delay_cnt;
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        delay_cnt <= 4'd0;
        pwr_good  <= 1'b0;
    end else if (en_sync2 && !pwr_good) begin
        if (delay_cnt < 4'd10)
            delay_cnt <= delay_cnt + 1'b1;
        else
            pwr_good <= 1'b1;
    end else if (!en_sync2) begin
        delay_cnt <= 4'd0;
        pwr_good  <= 1'b0;
    end
end

endmodule

这段代码里,我特意加了10个时钟周期的延时。为什么?因为电源开关打开后,内部电容需要充电时间。我曾经遇到过一开电源就读取数据,结果全是0x00的惨案。嗯,从那以后我养成了加延时的习惯。

4.2 唤醒逻辑设计

唤醒逻辑,说白了就是怎么把睡着的模块叫醒。这里有个关键点:唤醒信号本身不能依赖被唤醒模块的电源。你想想看,模块都断电了,它怎么处理唤醒信号?

我常用的唤醒方案有三种:

方案 原理 适用场景 功耗
边沿唤醒 检测信号上升/下降沿 外部中断、按键 极低
电平唤醒 持续检测电平状态 通信协议握手 较低
定时唤醒 内部定时器到期 周期性任务 中等

我个人最推荐边沿唤醒。为什么?因为电平唤醒需要一直检测,功耗会多出几个μA。别小看这几个μA,在电池供电设备里,这就是几天的待机时间差距。

实战技巧:唤醒逻辑一定要用独立的、永远供电的"常开域"实现。我在某款CPLD里专门划了一个小区域,只放唤醒逻辑和状态保持寄存器,其他模块全部可以断电。

来看一个边沿唤醒的实例:

// 边沿唤醒检测器
module wakeup_detector (
    input  wire clk_always,   // 常开域时钟(32kHz)
    input  wire wake_signal,  // 外部唤醒信号
    output reg  wake_pulse    // 唤醒脉冲
);

reg wake_d1, wake_d2;

always @(posedge clk_always) begin
    wake_d1 <= wake_signal;
    wake_d2 <= wake_d1;
    // 上升沿检测
    wake_pulse <= wake_d1 & ~wake_d2;
end

endmodule

这里我用了32kHz的低速时钟。为什么不用高速时钟?因为唤醒逻辑不需要高响应速度,低速时钟能省不少功耗。我见过有人用100MHz时钟做唤醒检测,那功耗直接翻倍,得不偿失。

4.3 状态保持策略

状态保持,这是电源门控里最头疼的问题。模块断电了,里面的寄存器状态全丢了。再上电时,怎么恢复到断电前的状态?

我总结了三类状态保持策略:

  1. 寄存器备份法:断电前把关键状态复制到常开域的备份寄存器
  2. 非易失存储法:把状态写入CPLD内部的Flash或EEPROM
  3. 状态冻结法:用特殊设计的保持寄存器,断电后靠漏电流维持状态

实际项目中,我90%的情况用寄存器备份法。为什么?因为Flash写入太慢,而且有寿命限制。状态冻结法虽然功耗极低,但保持时间有限,温度高了就掉数据。

注意:寄存器备份法有个坑——备份和恢复的时序必须严格把控。我曾经在恢复时没等电源稳定就加载数据,结果恢复了一半,系统直接跑飞了。后来我加了电源稳定标志(就是前面代码里的pwr_good信号),问题才解决。

来看一个完整的状态保持实现:

// 带状态保持的电源门控模块
module power_gating_with_retention (
    input  wire clk,
    input  wire rst_n,
    input  wire sleep_req,    // 休眠请求
    output wire sleep_ack,    // 休眠确认
    input  wire wakeup,       // 唤醒信号
    // 需要保持的状态
    input  wire [7:0] data_in,
    output wire [7:0] data_out
);

// 常开域寄存器
reg [7:0] retention_reg;
reg sleep_state;

// 休眠状态机
localparam IDLE   = 2'b00;
localparam SAVE   = 2'b01;
localparam SLEEP  = 2'b10;
localparam RESTORE = 2'b11;

reg [1:0] state, next_state;

always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        state <= IDLE;
        retention_reg <= 8'd0;
    end else begin
        state <= next_state;
        // 在SAVE状态保存数据
        if (state == SAVE)
            retention_reg <= data_in;
    end
end

// 状态转移逻辑
always @(*) begin
    next_state = state;
    case (state)
        IDLE:    if (sleep_req) next_state = SAVE;
        SAVE:    next_state = SLEEP;
        SLEEP:   if (wakeup)   next_state = RESTORE;
        RESTORE: next_state = IDLE;
        default: next_state = IDLE;
    endcase
end

// 输出:休眠时用保持数据,唤醒后恢复
assign data_out = (state == SLEEP) ? retention_reg : data_in;
assign sleep_ack = (state == SLEEP);

endmodule

这段代码里,状态保存只花了一个时钟周期。为什么这么快?因为CPLD内部寄存器读取速度极快,不需要像Flash那样等毫秒级的时间。但要注意,保存前一定要确保数据是稳定的。我习惯在保存前加一个时钟周期的数据锁存。

4.4 知识体系总览

说了这么多,咱们用一张图把电源门控的核心逻辑串起来:

电源门控技术知识体系 内部电源开关设计 唤醒逻辑设计 状态保持策略 • PMOS管实现 • 使能信号同步 • 粗粒度为主 • 电源稳定延时 • 边沿唤醒优先 • 常开域实现 • 低速时钟检测 • 独立供电区域 • 寄存器备份法 • 非易失存储法 • 状态冻结法 • 时序严格把控 核心设计流程 模块空闲 → 保存状态 → 关闭电源 → 检测唤醒 → 恢复状态 → 正常工作 关键指标:唤醒时间 < 10μs | 状态保持功耗 < 1μA | 电源切换毛刺 < 5% VDD

这张图把咱们今天讲的三块内容串起来了。你仔细看,电源开关是基础,唤醒逻辑是触发,状态保持是保障。三者缺一不可。

我的经验:刚开始做电源门控时,我总想一次把所有模块都门控掉。后来发现,有些模块频繁唤醒,门控反而浪费功耗。建议先做功耗分析,找出真正空闲时间长的模块再下手。

好了,电源门控技术就讲到这里。记住三个核心:开关要稳、唤醒要快、状态要保。下次咱们聊时钟门控,那个更精细,但也更容易踩坑。

专注资料整理