After a printed circuit board has been assembled, someone has to answer a simple question: are the parts on it the right parts, in the right places, and connected correctly? In-circuit test (ICT) is one of the oldest and most widely used answers. It checks individual components and nets on a populated board by making electrical contact with test points and measuring what happens.
For software engineers, ICT is often the first place where hardware and code meet on the factory floor. Test programs, fixture definitions, and limit files are software artifacts, and the quality of that software determines how many good boards get rejected and how many bad boards escape. This article explains what ICT checks, how a test program is structured, and how to calculate pass and fail decisions with a small Java model.
What in-circuit test checks
An ICT system uses a bed-of-nails fixture, a set of spring-loaded probes that contact specific test points on the board. The tester then performs measurements through those probes.
Typical checks include:
Opens and shorts. Two nets that should be isolated are connected, or a connection that should exist is broken. These are the most basic and most valuable checks, because they catch assembly defects early.
Analog component values. Resistors, capacitors, and inductors are measured and compared to limits. A resistor marked 10 kΩ with 1 percent tolerance should measure within 9.9 kΩ and 10.1 kΩ, after accounting for the measurement method.
Digital and mixed-signal device checks. Some ICT systems can drive and sample digital pins to verify that a device is present and functioning at a basic level. This is not a full functional test, but it catches many placement and soldering problems.
Polarity and orientation. Diodes, electrolytic capacitors, and some integrated circuits can be placed backwards. ICT can often detect this by measuring the expected asymmetry.
ICT does not replace functional test. It verifies that the board is assembled correctly and that its components are within expected electrical limits. Functional test then checks that the product behaves correctly as a system.
Limits, guard bands, and the cost of false failures
Every ICT decision depends on limits. A limit that is too tight rejects good boards, which wastes material and labor and creates a backlog of retests. A limit that is too loose lets defective boards pass, which moves the cost to later stages or to the customer.
Two concepts help manage this trade-off.
Measurement uncertainty is the range of error introduced by the tester, fixture, and measurement method. A limit should account for it. If a measurement has an uncertainty of ±0.5 percent, a check against a 1 percent tolerance leaves a narrow margin that may produce false failures.
Guard bands are the deliberate narrowing of a pass window to reduce the chance of accepting a bad part. They are a deliberate trade: a tighter band increases false rejects in exchange for fewer escapes.
Both decisions should be documented and reviewed. A limit that was chosen three years ago for a previous component revision may be wrong for the current one.
Structuring a test program
A good ICT test program is organized so that failures are easy to diagnose. A common structure has three layers.
The first layer is the net and component map: which test points belong to which nets, and which components are connected between them. This map usually comes from the board's design data and should be generated, not typed by hand.
The second layer is the test definition: for each component or net, what is measured, with what stimulus, and with which limits. Each definition should have an identifier that appears in logs and reports.
The third layer is the execution and reporting: the sequence of tests, how results are recorded, and how failures are grouped for the operator.
The following Java model implements the core of the pass or fail decision for an analog component check. It applies a guard band to a nominal value and tolerance, then returns a result with the reason for failure.
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.List;
public class IctLimitCheck {
public enum Verdict { PASS, FAIL_LOW, FAIL_HIGH, FAIL_UNCERTAIN }
public record ComponentSpec(
String refDes,
BigDecimal nominal,
BigDecimal tolerancePct) {
public ComponentSpec {
if (refDes == null || refDes.isBlank()) {
throw new IllegalArgumentException("refDes is required");
}
if (nominal.signum() <= 0) {
throw new IllegalArgumentException("nominal must be positive for " + refDes);
}
if (tolerancePct.signum() < 0) {
throw new IllegalArgumentException("tolerance must not be negative for " + refDes);
}
}
}
public record CheckResult(String refDes, BigDecimal measured, Verdict verdict) {}
/**
* @param guardBandPct the portion of the tolerance window reserved as margin, e.g. 0.20 keeps the outer 20 percent of the window as a reject zone
* @param measurementUncertaintyPct the tester's uncertainty, as a fraction of the nominal value
*/
public static CheckResult check(ComponentSpec spec, BigDecimal measured,
BigDecimal guardBandPct, BigDecimal measurementUncertaintyPct) {
BigDecimal halfWindow = spec.nominal()
.multiply(spec.tolerancePct())
.divide(BigDecimal.valueOf(100), 10, RoundingMode.HALF_UP);
BigDecimal uncertainty = spec.nominal().multiply(measurementUncertaintyPct);
// Shrink the acceptance window by the guard band and by the measurement uncertainty.
BigDecimal usable = halfWindow.multiply(BigDecimal.ONE.subtract(guardBandPct))
.subtract(uncertainty);
BigDecimal low = spec.nominal().subtract(usable);
BigDecimal high = spec.nominal().add(usable);
Verdict verdict;
if (usable.signum() <= 0) {
verdict = Verdict.FAIL_UNCERTAIN;
} else if (measured.compareTo(low) < 0) {
verdict = Verdict.FAIL_LOW;
} else if (measured.compareTo(high) > 0) {
verdict = Verdict.FAIL_HIGH;
} else {
verdict = Verdict.PASS;
}
return new CheckResult(spec.refDes(), measured, verdict);
}
public static void main(String[] args) {
ComponentSpec r1 = new ComponentSpec("R1", new BigDecimal("10000"), new BigDecimal("1"));
BigDecimal guard = new BigDecimal("0.20");
BigDecimal uncertainty = new BigDecimal("0.002");
List<BigDecimal> readings = List.of(
new BigDecimal("10010"),
new BigDecimal("9950"),
new BigDecimal("10090"),
new BigDecimal("10300"));
for (BigDecimal m : readings) {
CheckResult result = check(r1, m, guard, uncertainty);
System.out.printf("%s measured=%s verdict=%s%n", result.refDes(), result.measured(), result.verdict());
}
}
}
Running this model with a 10 kΩ, 1 percent resistor shows the effect of the guard band. The nominal pass window is ±100 Ω, but a 20 percent guard band and a 0.2 percent measurement uncertainty shrink the accepted range. A reading that a simple tolerance check would accept can now fail, and the result tells you why: the reading is inside the nominal tolerance but outside the usable window. That distinction matters when someone asks why a board failed.
Note that the verdict FAIL_UNCERTAIN is important. If the uncertainty consumes the entire tolerance window, no reading can be trusted to pass. A test program should refuse to run in that situation rather than produce random results.
Where software quality matters most
ICT programs are often written under schedule pressure, and their defects are expensive. A few practices pay off quickly.
Version the limits. Store limit files in version control and tie each production run to a specific version. When a yield drops, you need to know whether the limits changed.
Generate what you can. Net maps and component lists should come from design data. Hand-entered test definitions drift away from the actual board.
Make failures explainable. A failure report should name the component, the measured value, the limit, and the verdict. Operators and engineers should not have to decode raw numbers.
Test the test. Use known-good and known-bad boards, sometimes called golden boards, to verify that the test program passes and fails as expected after every change.
Track false fails and escapes separately. A high false-fail rate wastes time and hides real defects. Escapes, meaning defects found later, reveal gaps in coverage. Both should be measured.
Practical guidance for engineering leaders
If your team owns or supports ICT, ask these questions:
- Do we know the measurement uncertainty of each test, and is it included in the limits?
- When did we last review the limits for each component revision?
- Can we trace a failed board to the exact test program version that rejected it?
- Do we track false fails and escapes, or only the overall pass rate?
- Do we verify test programs with golden boards after every change?
The answers usually reveal whether the problem is the board, the fixture, the limits, or the test code, which is the first decision that determines how quickly a yield problem gets solved.
Key takeaways
In-circuit test verifies assembly and component values on populated boards, and its decisions are only as good as its limits. Measurement uncertainty and guard bands define the trade-off between false fails and escapes. Treating test programs and limit files as versioned, tested software is one of the most effective ways for a software team to improve a manufacturing line.
Top comments (0)