LDO LDR Checklist

Low-level design review for an LDO, simulation side · Public Analog Domain & Common Sense · 2026-09-30

Given specs vs design outcomes

Two kinds of numbers get mixed up in LDO reviews. Givens come from the system and do not move with topology or silicon: supply range, load range, temperature range, lifetime, and the output capacitor with its ESR window. Design outcomes are what the review actually signs off: dropout, stability, transient, PSRR, accuracy, Iq and the rest. Every check below measures an outcome against a limit, swept across the givens.

Net and variable names

PlaceholderMeaningUsed in expressions as
$DUTLDO instance name in the testbenchIS("/$DUT/$DUT_OUTPUT") terminal currents
$DUT_POWERInput pin and net (VIN)VS("/$DUT_POWER")
$DUT_GNDGround pin and netNoise reference, IGND
$DUT_OUTPUTOutput pin and net (VOUT)VS, VT, VF, VN of "/$DUT_OUTPUT"
VOUT_NOM, iload, vinDesign variablesSweep variables and spec normalization

PVT corners, shared by all 15 checks

One corner set is defined once per setup and enabled for every test, so a failure in one check can be traced to the same corner in the others. It has five corners, one per process, and each corner carries three supply and three temperature values, so a full run is 5 × 3 × 3 = 45 points plus nominal.

DimensionValuesHow it is set
Process (P)TT, SS, FF, SF, FSModel sections swapped per corner. Core and 3.3 V MOSFETs take tt/ss/ff/sf/fs. Resistors and MIM caps follow SS and FF, and stay typical at SF and FS. Every other section stays nominal.
Voltage (V)VIN typ −10%, typ, +10%Corner variable vin on the $DUT_POWER source
Temperature (T)−40, 25, 125 °CCorner variable temperature

A corner cannot usefully set a variable that the test sweeps: the sweep overrides it, and the three values become identical runs. Dropout and line regulation sweep VIN, and line transient drives VIN with its own stimulus, so they take a P T set. Thermal shutdown sweeps temperature, so it takes a P V set. Offset Monte Carlo runs at the worst PVT point found by the other checks. The PVT corners column in the full table lists the set for each check.

ldo_pvt_corners.il (section 5) adds the set to an ADE XL or ADE Assembler setup and enables it for every test. For the P T or P V checks, call ldoPvtCorners(sdb models ?vary '(P T) ?tests '("LDO_Dropout") ?suffix "_PT") and leave those tests out of the P V T set.

Checks by tier

Tier 1, must not fail · 5 (33%)
Tier 2, fails in the system · 4 (27%)
Tier 3, accuracy and robustness · 5 (33%)
Tier 4, reliability · 1 (7%)

Full table

Every check runs against the shared PVT corner set. The PVT corners column says which of process (P), voltage (V) and temperature (T) apply; a check that sweeps VIN or temperature itself drops that dimension. Sim runs is the rough count for one pass, 45 for the full P V T set. The bar is on a log scale so the Monte Carlo row does not flatten the rest. Click a check to jump to its detail.

#CheckTierAnalysisExample specPVT cornersSim runs

Each check shows the testbench with the driven nets in orange and the measured nets in blue, the characterization charts with the red spec limit, a worst-case table across corners, and the ADE XL output expressions. Curves are illustrative models shaped like a 1.2 V, 300 mA LDO; replace them with your CSV data. For readability the charts show five representative points of the shared PVT set: TT 25 °C, SS and FF at −40 and 125 °C.

Stimulus net
Measured net
Spec limit
μ ± 3σ (Monte Carlo)

DC sweep of the load current from 1 mA to 300 mA at VIN = 1.8 V, across the shared PVT set (5 processes × VIN 1.62, 1.8, 1.98 V × −40, 25, 125 °C = 45 points). The chart and tables show five representative corners. Two limits apply: the static slope (load regulation, mV/A) and the total VOUT error over the whole load range. The numbers below are illustrative, shaped like a real 1.2 V, 300 mA LDO, so the page is ready to take your CSV.

Figure · VOUT vs ILOAD across PVT

1.221.211.201.191.18 0100200300 ILOAD (mA)VOUT (V) +1.5% (1.218 V) −1.5% (1.182 V)
TT 25°C
SS −40°C
SS 125°C
FF −40°C
FF 125°C
Spec

Slope is load regulation; vertical offset is the DC error from offset and divider mismatch. Hot corners sit lowest and slope most because loop gain drops as the pass device heats up.

Worst-case summary

SS 125°C · 16 mV/A (26%)
FF 125°C · 14 mV/A (23%)
TT 25°C · 12 mV/A (20%)
SS −40°C · 10 mV/A (16%)
FF −40°C · 9 mV/A (15%)
RankCornerVOUT at 1 mAVOUT at 300 mALoad reg (spec ≤ 20 mV/A)VOUT error (spec ≤ 1.5%)Result
StatisticLoad reg (mV/A)VOUT error (%)
Typical (TT 25°C)12.00.30
Mean of corners12.20.68
Max16.01.10
Std dev of corners2.80.33
Worst margin to spec4.0 (20%)0.40 (27%)

ADE XL outputs

OutputExpressionSpec
VOUTVS("/$DUT_OUTPUT")plot only
load_reg_mV_per_A1000*(value(VS("/$DUT_OUTPUT") 1m) - value(VS("/$DUT_OUTPUT") 300m))/(300m - 1m)< 20
VOUT_err_pct100*ymax(abs(VS("/$DUT_OUTPUT") - VAR("VOUT_NOM")))/VAR("VOUT_NOM")< 1.5
headroom_Vvalue(VS("/$DUT_POWER") - VS("/$DUT_OUTPUT") 300m)info
  1. Save ldo_loadreg_setup.il and ldo_pvt_corners.il in the same directory.
  2. Edit sections 1 to 3 of ldo_loadreg_setup.il: library, testbench cell, setup view, the four net names, and the operating point. VIN_VAL is also the typical VIN for the ±10% corners.
  3. Close the setup in the GUI, or open it read-only, so the script can open it for editing.
  4. In the CIW, run load("ldo_loadreg_setup.il"). With PVT t it also adds the shared corner set.
  5. Reopen the setup. The new test LDO_LoadReg runs nominal plus the 45 PVT points, with pass/fail against the two specs.
  6. For a setup that already has its tests, such as the other 14 checks, edit section 1 of ldo_pvt_corners.il and load() it on its own. It enables the corners for every test in the setup.
Checked on IC6.1.8 against a 1.8 V capless LDO testbench: both scripts built the setups in batch mode, the nominal test ran in the ADE XL GUI, and all 45 corner points were checked by running Spectre on the netlist ADE generated. ldo_loadreg_setup.il uses the ADE Assembler mae* API, which cannot open a classic ADE XL view (data.sdb, ASSEMBLER-8036). Build the test in an Assembler (maestro) view, or convert it with maestro-to-adexl. ldo_pvt_corners.il uses the axl* corner API and works on both view types. The script prints the DC analysis it ended up with. Check that it shows sweep "Design Variable" and incrType "Linear", because without them ADE netlists a single operating point.
ldo_loadreg_setup.il
;; ====================================================================
;; ldo_loadreg_setup.il
;; Adds an LDO load-regulation test to an existing ADE XL / ADE Assembler
;; (maestro) setup whose testbench schematic already has the LDO hooked up.
;;
;; How to use
;;   1. Edit the values in sections 1 to 3 below.
;;   2. Close the setup view in the GUI (or open it read-only) so this
;;      script can open it for editing.
;;   3. In the CIW:   load("ldo_loadreg_setup.il")
;;
;; Testbench assumptions
;;   - A DC current sink from DUT_OUTPUT to ground, value = ILOAD_VAR
;;   - A DC voltage source on DUT_POWER, value = VIN_VAR
;;   - Output cap and ESR already on the schematic
;;
;; Needs the mae* SKILL API: IC6.1.8 with ADE Assembler, or IC23 / IC25.
;; mae* cannot open a legacy ADE XL (data.sdb) view; build the test in an
;; Assembler (maestro) view. ldo_pvt_corners.il works on both.
;; Not tested on your install; check the printed DC analysis settings.
;; ====================================================================
ldoLoadregDir = let((f) f = get_filename(piport)
                  if(f && rindex(f "/") substring(f 1 strlen(f) - strlen(rindex(f "/"))) "."))
let((
    ;; ---------- 1. Where the setup lives ----------
    (LIB        "myLib")        ; library holding the adexl / maestro view
    (CELL       "tb_ldo")       ; testbench cell
    (VIEW       "adexl")        ; "adexl" or "maestro"
    (TB_VIEW    "schematic")    ; testbench schematic view
    (TEST       "LDO_LoadReg")  ; name of the test this script adds

    ;; ---------- 2. Net and instance names ----------
    (DUT        "I0")           ; $DUT        LDO instance in the testbench
    (DUT_POWER  "VIN")          ; $DUT_POWER  net on the LDO input pin
    (DUT_GND    "GND")          ; $DUT_GND    net on the LDO ground pin
    (DUT_OUTPUT "VOUT")         ; $DUT_OUTPUT net on the LDO output pin
    (ILOAD_VAR  "iload")        ; design variable on the DC load sink
    (VIN_VAR    "vin")          ; design variable on the VIN source

    ;; ---------- 3. Operating point and spec limits ----------
    (VIN_VAL      "1.8")
    (VOUT_NOM     "1.2")
    (ILOAD_MIN    "1m")
    (ILOAD_MAX    "300m")
    (NPTS         "31")         ; number of steps; the sweep has NPTS+1 points
    (SPEC_LOADREG "20")         ; load regulation, mV/A, max
    (SPEC_ERR     "1.5")        ; VOUT error over load, %, max
    (PVT          t)            ; t = add the shared PVT corners (ldo_pvt_corners.il)
    (TEMPS        "-40 25 125") ; corner temperatures; VIN corners are VIN_VAL -10% / +10%
    (RUN          t)            ; t = set up and run, nil = set up only

    sess vs vp dcOpts outs hist ok pvtFile)

  ;; ---------- open the setup ----------
  sess = maeOpenSetup(LIB CELL VIEW ?mode "a")
  unless(sess
    error("ldo_loadreg: cannot open %s/%s/%s for editing. Close it in the GUI and retry. A legacy ADE XL view (data.sdb) cannot be opened by mae* (ASSEMBLER-8036); use an ADE Assembler (maestro) view.\n"
          LIB CELL VIEW))
  when(member(TEST maeGetSetup(?session sess))
    error("ldo_loadreg: test %s already exists. Change TEST or delete that test first.\n" TEST))

  ;; ---------- test and design variables ----------
  maeCreateTest(TEST ?lib LIB ?cell CELL ?view TB_VIEW ?simulator "spectre" ?session sess)
  maeSetVar(VIN_VAR    VIN_VAL   ?session sess)
  maeSetVar("VOUT_NOM" VOUT_NOM  ?session sess)
  maeSetVar(ILOAD_VAR  ILOAD_MIN ?session sess)

  ;; ---------- DC sweep of the load current ----------
  ;; "sweep"/"designVar" make it a design-variable sweep, and "incrType" plus
  ;; "stepTypeLin" make "lin" a step count. Without them ADE stores the keys but
  ;; netlists a bare op point (no param=/start=/stop= on the dc line).
  dcOpts = `(("saveOppoint" t) ("sweep" "Design Variable")
             ("designVar" ,ILOAD_VAR) ("param" ,ILOAD_VAR)
             ("rangeType" "Start-Stop") ("start" ,ILOAD_MIN) ("stop" ,ILOAD_MAX)
             ("incrType" "Linear") ("stepTypeLin" "Number of Steps") ("lin" ,NPTS))
  ;; argument order changed between releases, so try both forms
  ok = errset(maeSetAnalysis(TEST "dc" ?enable t ?options dcOpts ?session sess))
  unless(ok && car(ok)
    ok = errset(maeSetAnalysis("dc" ?enable t ?options dcOpts ?session sess)))
  unless(ok && car(ok)
    printf("ldo_loadreg: could not set the DC sweep. Set it by hand: sweep %s from %s to %s.\n"
           ILOAD_VAR ILOAD_MIN ILOAD_MAX))
  printf("ldo_loadreg: DC analysis now reads %L\n"
         car(errset(maeGetAnalysis(TEST "dc" ?session sess))))

  ;; ---------- outputs and specs ----------
  vs = sprintf(nil "VS(\"/%s\")" DUT_OUTPUT)
  vp = sprintf(nil "VS(\"/%s\")" DUT_POWER)
  outs = list(
    list("VOUT"              vs nil nil)
    list("VOUT_light"        sprintf(nil "value(%s %s)" vs ILOAD_MIN) nil nil)
    list("VOUT_heavy"        sprintf(nil "value(%s %s)" vs ILOAD_MAX) nil nil)
    list("load_reg_mV_per_A" sprintf(nil "1000*(value(%s %s) - value(%s %s))/(%s - %s)"
                                     vs ILOAD_MIN vs ILOAD_MAX ILOAD_MAX ILOAD_MIN)
                             "lt" SPEC_LOADREG)
    list("VOUT_err_pct"      sprintf(nil "100*ymax(abs(%s - VAR(\"VOUT_NOM\")))/VAR(\"VOUT_NOM\")" vs)
                             "lt" SPEC_ERR)
    list("headroom_V"        sprintf(nil "value(%s - %s %s)" vp vs ILOAD_MAX) nil nil))
  foreach(o outs
    maeAddOutput(nth(0 o) TEST ?expr nth(1 o) ?session sess)
    when(nth(2 o)
      maeSetSpec(nth(0 o) TEST ?lt nth(3 o) ?session sess)))

  ;; ---------- shared PVT corners, enabled for every test ----------
  when(PVT
    pvtFile = strcat(ldoLoadregDir "/ldo_pvt_corners.il")
    unless(isFile(pvtFile)
      error("ldo_loadreg: PVT is t but %s is missing. Put ldo_pvt_corners.il beside this file.\n" pvtFile))
    ldoPvtLibOnly = t
    load(pvtFile)
    ldoPvtLibOnly = nil
    ldoPvtCorners(axlGetMainSetupDB(sess)
                  maeGetEnvOption(TEST ?option "modelFiles" ?session sess)
                  ?vinVar VIN_VAR ?vinTyp evalstring(VIN_VAL) ?temps TEMPS))

  ;; ---------- save, then optionally run ----------
  maeSaveSetup(?lib LIB ?cell CELL ?view VIEW ?session sess)
  printf("ldo_loadreg: test %s saved in %s/%s/%s (DUT %s, GND net %s).\n"
         TEST LIB CELL VIEW DUT DUT_GND)
  when(RUN
    hist = maeRunSimulation(?session sess)
    maeWaitUntilDone('All)
    printf("ldo_loadreg: run finished, history %L. Open the view to see pass/fail per corner.\n" hist)
    errset(maeExportOutputView(?session sess
                               ?fileName strcat("./" TEST "_results.csv")
                               ?view "Detail")))
  sess)
ldo_pvt_corners.il
;; ====================================================================
;; ldo_pvt_corners.il
;; Adds the LDO LDR PVT corner set to an ADE XL (adexl) or ADE Assembler
;; (maestro) setup and enables it for every test in that setup, so all
;; 15 checks run against the same corners.
;;
;;   Process  TT SS FF SF FS   (model sections swapped per corner)
;;   Voltage  VIN_TYP x (0.9 1 1.1)
;;   Temp     -40 25 125 C
;;   = 5 corners, 5 x 3 x 3 = 45 points
;;
;; How to use
;;   1. Edit section 1 below.
;;   2. Close the setup view in the GUI so this script can open it.
;;   3. In the CIW:   load("ldo_pvt_corners.il")
;;
;; ldo_loadreg_setup.il calls ldoPvtCorners() itself when PVT is t.
;; Checks that sweep VIN (#2 Dropout, line_reg in #8) or temperature
;; (#13 Thermal shutdown) cannot also take that corner variable: pass
;; ?vary '(P T) or '(P V) and ?tests for those, with a ?suffix so the
;; names do not clash. See the table on the LDR page.
;; ====================================================================

;; ---------- process corners: name, then section swaps ----------
;; Each swap is (nominal section  corner section). Sections not listed
;; stay nominal. This model file has no ss/ff-only resistor or MIM split
;; for SF/FS, so those keep tt_res / tt_mim.
ldoPvtProcess = '(
  ("TT" ("tt" "tt") ("tt_3v" "tt_3v"))
  ("SS" ("tt" "ss") ("tt_3v" "ss_3v") ("tt_res" "ss_res") ("tt_mim" "ss_mim"))
  ("FF" ("tt" "ff") ("tt_3v" "ff_3v") ("tt_res" "ff_res") ("tt_mim" "ff_mim"))
  ("SF" ("tt" "sf") ("tt_3v" "sf_3v"))
  ("FS" ("tt" "fs") ("tt_3v" "fs_3v")))

;; sdb        setup DB handle: axlGetMainSetupDB(session)
;; models     the test's nominal model list, (("file" "section") ...)
;; tests      tests to enable the corners for; nil = every test in sdb
;; vary       which of P V T the corners change
procedure(ldoPvtCorners(sdb models @key (vinVar "vin") (vinTyp 1.8) (vinTol 0.1)
                        (temps "-40 25 125") (vary '(P V T)) (tests nil) (suffix ""))
  let((allTests names c m mf sec swap vinList i)
    allTests = cadr(axlGetTests(sdb))
    unless(tests tests = allTests)
    vinList = sprintf(nil "%g %g %g" vinTyp*(1-vinTol) vinTyp vinTyp*(1+vinTol))
    foreach(p ldoPvtProcess
      c = axlPutCorner(sdb strcat(car(p) suffix))
      axlSetEnabled(c t)
      names = cons(strcat(car(p) suffix) names)
      when(memq('T vary) axlPutVar(c "temperature" temps))
      when(memq('V vary) axlPutVar(c vinVar vinList))
      i = 0
      foreach(fs models
        sec = cadr(fs)
        when(memq('P vary) && (swap = assoc(sec cdr(p))) sec = cadr(swap))
        mf = car(last(parseString(car(fs) "/")))
        m = axlPutModel(c if(i == 0 mf sprintf(nil "%s:%d" mf i)))
        axlSetModelFile(m car(fs))
        axlSetModelSection(m sprintf(nil "\"%s\"" sec))
        axlSetModelTest(m "All")
        axlSetModelBlock(m "Global")
        axlSetEnabled(m t)
        i++)
      foreach(tn allTests axlSetCornerTestEnabled(c tn if(member(tn tests) t nil))))
    printf("ldoPvtCorners: %L, vin %s, temp %s, vary %L, tests %L\n"
           reverse(names) vinList temps vary tests)
    reverse(names)))

;; Legacy ADE XL keeps each test's model list in
;; <view>/test_states/<lib>/<cell>/<sim>/<test>_active/modelSetup
procedure(ldoPvtSubdirs(d)
  setof(x getDirFiles(d) !member(x '("." "..")) && isDir(strcat(d "/" x))))
procedure(ldoPvtStateModels(viewDir test)
  let((root f p models)
    root = strcat(viewDir "/test_states")
    foreach(l ldoPvtSubdirs(root)
      foreach(c ldoPvtSubdirs(strcat(root "/" l))
        foreach(sim ldoPvtSubdirs(strcat(root "/" l "/" c))
          f = strcat(root "/" l "/" c "/" sim "/" test "_active/modelSetup")
          when(!models && isFile(f)
            p = infile(f)  models = car(lineread(p))  close(p)))))
    models))

;; ---------- standalone run ----------
;; Skipped when ldo_loadreg_setup.il loads this file for the procedure.
unless(boundp('ldoPvtLibOnly) && ldoPvtLibOnly
let((
    ;; ---------- 1. Where the setup lives and the operating point ----------
    (LIB      "myLib")
    (CELL     "tb_ldo")
    (VIEW     "adexl")     ; "adexl" or "maestro"
    (VIN_VAR  "vin")       ; design variable on the VIN source
    (VIN_TYP  1.8)         ; typical VIN; corners use -10% / typ / +10%
    (TEMPS    "-40 25 125")
    sess sdb tests models axl)

  axl = (VIEW == "adexl")
  if(axl then
    ;; legacy ADE XL: maeOpenSetup refuses data.sdb (ASSEMBLER-8036)
    sess = axlCreateSession(sprintf(nil "ldoPvt%d" random(100000)))
    sdb = car(errset(axlSetMainSetupDBLCV(sess LIB CELL VIEW)))
  else
    sess = maeOpenSetup(LIB CELL VIEW ?mode "a")
    sdb = sess && axlGetMainSetupDB(sess))
  unless(sdb
    error("ldoPvtCorners: cannot open %s/%s/%s for editing. Close it in the GUI and retry.\n"
          LIB CELL VIEW))
  tests = cadr(axlGetTests(sdb))
  unless(tests error("ldoPvtCorners: %s/%s/%s has no tests.\n" LIB CELL VIEW))
  ;; nominal models come from the first test; every test in one setup
  ;; normally shares the PDK model list
  models = if(axl
    ldoPvtStateModels(ddGetObj(LIB CELL VIEW)~>readPath car(tests))
    maeGetEnvOption(car(tests) ?option "modelFiles" ?session sess))
  unless(models error("ldoPvtCorners: test %s has no model files.\n" car(tests)))
  ldoPvtCorners(sdb models ?vinVar VIN_VAR ?vinTyp VIN_TYP ?temps TEMPS)
  if(axl then
    axlCommitSetupDB(sdb)
    axlCloseSetupDB(sdb)
    axlCloseSession(sess)
  else
    maeSaveSetup(?session sess)
    maeCloseSession(?session sess))
  printf("ldoPvtCorners: saved %s/%s/%s. Reopen it and check the Corners setup.\n" LIB CELL VIEW)
  t))
  1. P. Hazucha et al., "Area-efficient linear regulator with ultra-fast load regulation," IEEE JSSC, vol. 40, no. 4, 2005
    Source of the response-time figure of merit (Hazucha FOM).
  2. TI SSZTA58, LDO Basics: Current Limit
    Brick-wall vs foldback current limit.
  3. TI TLV717P datasheet
    Real foldback curve, 350 mA onset and 40 mA short-circuit current.
  4. TI SBVA097, LDO Current Limit Behavior During Dropout Operation
    Hybrid brick-wall and foldback scheme.
  5. TI E2E, TPS746-Q1 foldback current limit
    Current limit measured at 85% of VOUT nominal.
  6. TI SSZTAD7, LDO Basics: Power Supply Rejection Ratio
    PSRR versus frequency, headroom and COUT.
  7. TI SLAA414, LDO PSRR Measurement Simplified
    Measurement methods for the PSRR check.
  8. TI SLYT202, Analog Applications Journal, PSRR in LDO applications
    Why PSRR drops at heavy load and near dropout.
  9. TI SLAA412, LDO Noise Demystified
    Noise density vs integrated noise, and why bandwidth belongs in the spec.
  10. TI SLYT201, Understanding noise in linear regulators
    Output-referred noise and its main sources.
  11. TI SSZT658, LDO Basics: Preventing Reverse Current
    Body-diode path and blocking methods for the reverse current check.
  12. TI SBVA026, A Topical Index of TI LDO Application Notes
    Index to the rest of the TI LDO app notes.
  13. TI SLVA289B, Feedforward capacitor for internally compensated converters
    Feedback divider and feed-forward zero, from this project's earlier page.
  14. P. E. Allen, CMOS Analog Circuit Design, Lecture 23: Two-Stage Op Amps
    Miller compensation values used for the error-amp model.
  15. Cadence Community, maestro SKILL: maeCreateTest and maeSetAnalysis
    Working example of the calls the script uses.
  16. Cadence Community, building maeAddOutput expressions from variables
    Why the script builds each expression with sprintf.

Chart data on this page is illustrative. Figures and text from the linked documents remain the property of their owners.