Quality & Process Control

What Is Cpl and Cpu (One-Sided Capability)

CPL and CPU measure capability against a single specification limit. How to calculate them, when to use them, and how to record one-sided measurements.

JJulien Buteau
beginner4 min readMarch 28, 2026

Cpk assumes a measurement has both an upper and a lower specification limit. Plenty of real measurements have only one. Leakage current has a maximum and no minimum. Insulation resistance has a minimum and no maximum. CPL and CPU are the one-sided capability indices for exactly these cases.

Definitions

CPU (capability, upper) measures how much room sits between the process and the upper specification limit:

CPU = (USL - mean) / (3σ)

CPL (capability, lower) measures the room above the lower specification limit:

CPL = (mean - LSL) / (3σ)

Cpk is simply the worse of the two when both limits exist:

Cpk = min(CPU, CPL)

So CPL and CPU are not a different tool from Cpk. They are its two halves, and for a one-sided measurement only one half is defined.

Worked Example

A board's quiescent current has a maximum of 45 mA and no meaningful minimum. From 200 production units:

StatisticValue
Mean38.0 mA
Standard deviation1.8 mA
USL45 mA
LSLnone
CPU = (45 - 38.0) / (3 × 1.8) = 7.0 / 5.4 = 1.30

CPU of 1.30 means about 4.3 standard deviations of headroom to the limit. Reporting a Cpk here would be wrong, because there is no lower limit for the calculation to use.

Interpreting the Number

ValueMeaningTypical response
< 1.00Process spread exceeds the available roomEscapes are routine; fix the process or the design
1.00 - 1.33MarginalWatch it; a small drift causes failures
1.33 - 1.67Generally acceptableStandard target for production
1.67 - 2.00ComfortableConsider whether the limit is too loose
> 2.00Very comfortableThe measurement may not be discriminating

A very high CPU deserves a question rather than celebration: either the process is genuinely excellent, or the limit was set so loosely that the test cannot fail, in which case it is consuming cycle time without protecting anything.

Where One-Sided Limits Show Up

MeasurementLimit typeIndex
Leakage currentMaximum onlyCPU
Quiescent currentMaximum onlyCPU
Hipot leakageMaximum onlyCPU
Boot timeMaximum onlyCPU
Harmonic distortionMaximum onlyCPU
BGA void percentageMaximum onlyCPU
Insulation resistanceMinimum onlyCPL
Pull / bond strengthMinimum onlyCPL
Battery capacityMinimum onlyCPL
Transmit powerMinimum onlyCPL
RF sensitivity marginMinimum onlyCPL

Recording a One-Sided Measurement

The index can only be computed if the data says the limit is one-sided. In a procedure, that means declaring a single validator rather than inventing a second bound.

procedure.yaml
33 lines
name: Board Power Testversion: 1.0.0unit:  serial_number:    default_value: "PCBA000001"  part_number:    default_value: "PCBA-100"plugs:  - name: dmm    python: plugs.dmm:Multimetermain:  - name: Quiescent Current    python: phases.quiescent_current    measurements:      - name: Quiescent Current        unit: mA        description: Upper limit only; CPU applies        validators:          - operator: "<="            expected_value: 45.0  - name: Insulation Resistance    python: phases.insulation    measurements:      - name: Insulation Resistance        unit: "MΩ"        description: Lower limit only; CPL applies        validators:          - operator: ">="            expected_value: 100.0
phases/quiescent_current.py
def quiescent_current(measurements, dmm):    measurements.quiescent_current = dmm.measure_dc_current_ma()
phases/insulation.py
def insulation(measurements, dmm):    measurements.insulation_resistance = dmm.measure_insulation_mohm()
plugs/dmm.py
class Multimeter:    def measure_dc_current_ma(self) -> float:        return 38.2  # Replace with a real instrument read    def measure_insulation_mohm(self) -> float:        return 480.0  # Replace with a real instrument read

This is the part teams most often get wrong. Adding a fabricated lower limit to a leakage measurement, so that a Cpk number appears, produces a figure that describes nothing. The honest record has one validator, and the analysis reports CPU.

Calculating It From Your Data

analysis/one_sided.py
21 lines
import statisticsdef cpu(values: list[float], usl: float) -> float:    """Capability against an upper specification limit."""    sigma = statistics.stdev(values)    if sigma == 0:        raise ValueError("Zero variation; check the measurement resolution")    return (usl - statistics.mean(values)) / (3 * sigma)def cpl(values: list[float], lsl: float) -> float:    """Capability against a lower specification limit."""    sigma = statistics.stdev(values)    if sigma == 0:        raise ValueError("Zero variation; check the measurement resolution")    return (statistics.mean(values) - lsl) / (3 * sigma)currents = [38.1, 37.6, 39.0, 38.4, 37.9, 38.8, 38.2]print(f"CPU = {cpu(currents, usl=45.0):.2f}")

Two cautions on the inputs:

Zero standard deviation is a measurement problem, not perfection. It usually means the instrument's resolution is coarser than the real variation, and the index is meaningless.

Capability assumes a stable process. Calculating CPU across a period that includes a process change averages two different distributions into one number that describes neither. Check the control chart first; capability is only meaningful once the process is in control.

Ppl and Ppu

CPL and CPU use short-term, within-subgroup variation. The long-term equivalents, using the overall standard deviation across everything produced, are PPL and PPU. The formulas are identical; only the sigma differs. See Ppl and Ppu and Cpk vs Ppk.

If You Already Run OpenHTF

A one-sided limit is expressed with a single bound, and the same rule applies about not inventing the other one:

power_test.py
import openhtf as htffrom openhtf.util import unitsfrom tofupilot.openhtf import upload@htf.measures(    htf.Measurement("quiescent_current_mA")    .in_range(maximum=45.0)    .with_units(units.MILLIAMPERE)    .doc("Upper limit only; CPU applies"),)def phase_quiescent(test):    test.measurements.quiescent_current_mA = 38.2test = htf.Test(phase_quiescent, part_number="PCBA-100")test.add_output_callbacks(upload())test.execute(test_start=lambda: input("Scan serial: "))

Install with pip install "tofupilot[openhtf]", or skip the callback and run it with tofupilot run.

Common Mistakes

MistakeConsequenceFix
Inventing a second limit to get a CpkA capability number that describes nothingDeclare one validator; report CPU or CPL
Reporting Cpk for a one-sided measurementMisleading, and usually wrong by constructionUse the index that matches the limit
Computing capability on an unstable processThe number averages two distributionsCheck the control chart first
Ignoring a very high CPUA test that cannot fail still costs cycle timeReview whether the limit is meaningful
Sigma of zero treated as excellentThe instrument cannot resolve the variationCheck measurement resolution

More Guides

Put this guide into practice