Skip to content
DenseChip
Learn
PracticeCode labInterview

DenseChip

Navigate

Menu

LearnPracticeCode labInterview

Tools

All toolsSV editorCode repoResume
DenseChip

Practice, courses, and chip-design tools in one focused workspace.

LearnPracticeCode labInterviewToolsLearning paths

© 2026 DenseChip

AboutRoadmapCareersInternshipsFAQContactPrivacyTerms
Skip to lesson

Course contents

  • How to Use This Course
  • The NanoSoC Tour
  • Reading Path
  • SoC Glossary10
  • SoC Metrics Reference
  • SoC Cheatsheet
  • What SoC Integration Is12
  • Architecture and Budgets21
  • Memory Maps and Address Decode26
  • Interconnect and Bus Fabrics26
  • Clock and Reset Architecture24
  • Power Architecture21
    • Power Domains and States
    • Isolation and Retention Integration
    • PMU and Power Sequencer
      • The Sequencer State Machine
      • Handshakes with the Reset Controller
      • Timeouts, Watchdogs and Refusals
    • Cold-Start Power-Up Sequence
    • Supply Sequencing and PMIC
    • Thermal and Power Envelope
    • DVFS Integration
  • Interrupts, DMA and Peripherals15
  • Memories, Boot and Security21
  • IP Integration and Handoff21
  • Top-Level Verification and Bring-up21
  • Package, Pads and I/O18
  • Chip Signoff and Tapeout18
  • SoC Interview Prep16
  • Capstone: Integrate NanoSoC14
Loading lesson

←PMU and Power Sequencer
  1. Topics
  2. SoC Design
  3. Power Architecture
  4. PMU and Power Sequencer
  5. The Sequencer State Machine
On this page

    Part 6 of 14, lesson 7 of 21Part 6 of 14/Power Architecture/Lesson 7 of 21All levels

    The Sequencer State Machine

    The NanoSoC power FSM state by state: what each state waits for, what it drives, and what moves it on.

    Why it matters

    A power controller described in words drifts: one reviewer's isolate-then-reset is another's reset-then-isolate, and the chip implements whichever the RTL writer guessed. Written as a state machine with named states, named waits and named timeouts, the NanoSoC sequencer leaves nothing to guess, and this lesson is that machine with the NanoSoC counts on the arcs.

    The core idea

    The machine idles in RUN, watching SW_OFF_REQ. The driver guarantees the quiesce contract: it writes the request only after DMA STATUS and SBR STATUS read idle, so the machine never samples live busy levels from the switchable domain. On the request it steps through ISOLATE, asserting iso_sw_en over 2 peripheral clocks; GATE_CLK for 2; ASSERT_RST for 2; SWITCH_OFF with the 64-clock ramp; and SLEEP, where it waits for a wake interrupt. Wake steps through SWITCH_ON with the same 64-clock ramp, WAIT_PGOOD for 8 clocks past power-good, RELOCK for the 256-clock PLL lock, CLK_OPEN for 2 clocks, ISO_RELEASE 4 system clocks later, and RST_RELEASE for the 18-plus-8 reset staging, landing back in RUN with RST_CAUSE bit 3 set. The table after the code gives each state its wait; the code omits the down-counters for space. Every wait state has a timeout arc to a safe state; the timeouts lesson names them.

    nano_pmu_seq.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    // NanoSoC teaching values: power sequencer FSM, complete module.
    // Waits live in the table in the text; counters are omitted here for space.
    module nano_pmu_seq (
      input  logic clk_per,
      input  logic rst_per_n,
      input  logic sw_off_req,
      input  logic wake_irq,
      input  logic pgood,
      input  logic pll_lock,
      output logic iso_sw_en,
      output logic clk_gate,
      output logic rst_sw_n,
      output logic sw_open,
      output logic seq_done
    );
      typedef enum logic [3:0] {
        S_RUN, S_QUIESCE, S_ISOLATE, S_GATE_CLK, S_ASSERT_RST,
        S_SWITCH_OFF, S_SLEEP, S_SWITCH_ON, S_WAIT_PGOOD, S_RELOCK,
        S_CLK_OPEN, S_ISO_REL, S_RST_REL
      } state_t;
      state_t state, nxt;
    
      always_ff @(posedge clk_per or negedge rst_per_n) begin
        if (!rst_per_n) state <= S_RUN;
        else state <= nxt;
      end
    
      always_comb begin
        nxt = state;
        case (state)
          S_RUN:       if (sw_off_req) nxt = S_QUIESCE;
          S_QUIESCE:   nxt = S_ISOLATE;
          S_ISOLATE:   nxt = S_GATE_CLK;
          S_GATE_CLK:  nxt = S_ASSERT_RST;
          S_ASSERT_RST:nxt = S_SWITCH_OFF;
          S_SWITCH_OFF:nxt = S_SLEEP;
          S_SLEEP:     if (wake_irq) nxt = S_SWITCH_ON;
          S_SWITCH_ON: if (pgood) nxt = S_WAIT_PGOOD;
          S_WAIT_PGOOD:nxt = S_RELOCK;
          S_RELOCK:    if (pll_lock) nxt = S_CLK_OPEN;
          S_CLK_OPEN:  nxt = S_ISO_REL;
          S_ISO_REL:   nxt = S_RST_REL;
          S_RST_REL:   nxt = S_RUN;
          default:     nxt = S_RUN;
        endcase
      end
    
      assign iso_sw_en = (state == S_ISOLATE) || (state == S_GATE_CLK) ||
                         (state == S_ASSERT_RST) || (state == S_SWITCH_OFF) ||
                         (state == S_SLEEP) || (state == S_SWITCH_ON) ||
                         (state == S_WAIT_PGOOD) || (state == S_RELOCK) ||
                         (state == S_CLK_OPEN);
      // Clocks are gated from GATE_CLK until CLK_OPEN opens them again.
      assign clk_gate = (state == S_GATE_CLK) || (state == S_ASSERT_RST) ||
                        (state == S_SWITCH_OFF) || (state == S_SLEEP) ||
                        (state == S_SWITCH_ON) || (state == S_WAIT_PGOOD) ||
                        (state == S_RELOCK);
      // rst_sys_n and rst_cpu_n stay asserted (low) from ASSERT_RST until RST_REL.
      assign rst_sw_n = !((state == S_ASSERT_RST) || (state == S_SWITCH_OFF) ||
                          (state == S_SLEEP) || (state == S_SWITCH_ON) ||
                          (state == S_WAIT_PGOOD) || (state == S_RELOCK) ||
                          (state == S_CLK_OPEN) || (state == S_ISO_REL));
      assign sw_open = (state == S_SWITCH_OFF) || (state == S_SLEEP);
      assign seq_done = (state == S_RUN);
    endmodule

    State

    Wait

    Drives / does

    RUN

    Until SW_OFF_REQ

    Normal running; seq_done high

    QUIESCE

    Request accepted

    Driver contract check point

    ISOLATE

    2 clk_per

    iso_sw_en asserted; PD_SW outputs clamped to 0

    GATE_CLK

    2 clk_per

    Clocks gated

    ASSERT_RST

    2 clk_per

    rst_sys_n and rst_cpu_n asserted

    SWITCH_OFF

    64 clk_per ramp

    Switch opened; rush current limited by staged turn-off/on

    SLEEP

    Until wake interrupt

    PD_SW off; isolation held

    SWITCH_ON

    64 clk_per ramp

    Switch closed; rails charge

    WAIT_PGOOD

    8 clk_per past pgood

    Settling margin

    RELOCK

    256 clk_per

    PLL relock; wait on pll_lock

    CLK_OPEN

    2 clk_per

    Clock gates open, resets still asserted

    ISO_RELEASE

    4 clk_sys later

    Isolation released while resets are still held

    RST_RELEASE

    18 clk_sys, then 8 clk_sys + 2 clk_cpu

    rst_sys_n, then rst_cpu_n; back to RUN

    The waits add up to the NanoSoC totals. Down: 2 + 2 + 2 + 64 = 70 clk_per, 2.917 µs. Up: (64 + 8 + 256 + 2) clk_per at 41.667 ns is 13,750 ns, plus (4 + 18 + 8) clk_sys at 10.417 ns is 312.5 ns, plus 2 clk_cpu at 5.208 ns is 10.42 ns: 14,072.9 ns. The script checks both sums and fails if any state's wait is changed without the total being revisited.

    example.py
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    # Wait budget of the sequencer table, NanoSoC teaching values (NanoSoC wake sequence).
    T_PER, T_SYS, T_CPU = 1e3 / 24, 1e3 / 96, 1e3 / 192   # ns
    DOWN = {"ISOLATE": 2, "GATE_CLK": 2, "ASSERT_RST": 2, "SWITCH_OFF": 64}
    UP_PER = {"SWITCH_ON": 64, "WAIT_PGOOD": 8, "RELOCK": 256, "CLK_OPEN": 2}
    UP_SYS = {"ISO_RELEASE": 4, "RST_RELEASE_SYS": 18, "RST_RELEASE_CPU": 8}
    UP_CPU = {"RST_RELEASE_CPU": 2}
    down_clk = sum(DOWN.values())
    up_ns = (sum(UP_PER.values()) * T_PER + sum(UP_SYS.values()) * T_SYS
             + sum(UP_CPU.values()) * T_CPU)
    assert down_clk == 70, down_clk
    assert abs(down_clk * T_PER - 2916.7) < 0.1
    assert abs(up_ns - 14072.9) < 0.1, up_ns
    # The wake order is isolation release (4 clk_sys) before rst_sys_n (18): the
    # resets are still asserted when isolation is released.
    assert UP_SYS["ISO_RELEASE"] < UP_SYS["RST_RELEASE_SYS"]
    # Injected bug: a RELOCK table entry of 4 + 256 would break the total.
    bad_ns = up_ns + 4 * T_PER
    assert abs(bad_ns - 14072.9) > 100.0
    print("PASS: down 70 clk_per = 2.917 us, up 14,072.9 ns; a stray +4 clk_per is caught")

    What the review looks for

    • The wait table matches the state list one for one, and the down counts add to 70 clk_per down and 14,072.9 ns up.

    • Quiesce is a software contract, not a sampled level: SW_OFF_REQ is written only after the driver reads idle in DMA and SBR STATUS; the machine itself trusts the request.

    • Isolation stays asserted across every state from ISOLATE to CLK_OPEN inclusive; the output equation above is checked, not eyeballed.

    Verify and debug

    Drive the FSM through both paths: a request with the driver-quiesce contract honoured must reach SLEEP, a wake must reach RUN with the cause bit set. Cover every arc, including the wake arriving in the same cycle as SLEEP entry. If the machine sticks in a wait state, the wait input (pgood, pll_lock, or the counter itself) is the suspect, which is the handshake lesson's territory.

    Power-gating controller sequence errors: retention saved at the wrong time, isolation mistimed, or the safe order interleaved. The FSM above fixes the order structurally; the timeout lesson fixes the waits that words like soon would otherwise leave open.

    If asked to design a power FSM, draw these states with the NanoSoC waits, then offer the two simplifications you refused: merging isolate with reset, and skipping the power-good wait. Say what each merge would risk.

    Check yourself SW_OFF_REQ is written while a DMA job is mid-transfer. Who is at fault, and what is the guard?

    The driver: quiesce is a software contract, so the request must follow idle reads in DMA STATUS and SBR STATUS. The machine trusts the request; the guard is the driver plus a power-aware test that requests sleep mid-job and checks nothing powers down.

    Related topics

    • Next: Handshakes with the Reset Controller

    • Low Power: Power Domain Basics

    • Protocol First Principles: Low-Power Handshakes

    Previous

    121 / 289

    Next: Handshakes with the Reset Controller

    Up next

    Up nextHandshakes with the Reset Controller

    Sign in to save progress

    Practice this lessonPrevious lessonReport an error

    See also

    • Timeouts, Watchdogs and Refusals