Test Types & Methods

Design for Testability: PCB Best Practices

Design for testability on PCBs: test points, boundary scan, debug access and firmware hooks that make a board cheap to test in production.

JJulien Buteau
intermediate12 min readMarch 14, 2026

Design for testability is the set of decisions made during layout that determine how much a board costs to test for the rest of its life. Most of them cost nothing at design time and are impossible to add later.

Why It Is a Layout-Time Decision

A board that reaches production without test access leaves three options, all bad: build an expensive fixture that probes awkward places, skip coverage and ship risk, or respin the PCB. The cost curve is steep and one-directional, which is why DFT reviews belong before layout is released, not after the first prototypes fail on the bench.

Test Points

PracticeRecommendation
Diameter0.9-1.0 mm pad, 0.6 mm minimum for fine-pitch probes
Spacing2.54 mm preferred, 1.27 mm minimum between adjacent points
Edge clearanceAt least 3 mm from the board edge for fixture tooling
SideAll on one side if possible; double-sided fixtures roughly double cost
FinishSolderable finish, not solder-masked
LabellingSilkscreen the net name; a bench technician will thank you
Tooling holesTwo non-plated holes, diagonally opposite, for fixture alignment

What to bring out as a test point:

  • Every power rail, on the load side of any filtering
  • Ground, several times, close to the rails they return
  • Reset, boot mode and strap pins
  • I2C/SPI bus lines that a fixture may need to drive or observe
  • Crystal or clock output, if a frequency check is planned
  • Any net the test plan intends to measure

The last line is the actual rule. DFT works when the test plan exists before layout is released, even in draft.

Power Rail Access

Rails are the highest-value test points because nearly every failure mode shows up there first. Bring out each rail separately rather than relying on a single point downstream of all of them, so a measurement can localise the fault.

Consider a way to isolate rails from their loads, such as a zero-ohm link or a solder jumper, to distinguish a shorted regulator from a shorted load. A board that fails with a collapsed rail is otherwise a diagnostic dead end.

Programming and Debug Access

InterfaceWhat to expose
SWD/JTAGClock, data, reset, ground, and target voltage reference
UART consoleTX, RX, ground, at a documented baud rate
Boot modeA strap or test point that can force the bootloader
ResetA test point the fixture can drive, not only a button

Programming at end of line is normal, so the fixture needs to flash firmware, set the boot mode and reset the target without human intervention. If the boot strap is only a physical jumper, someone is moving jumpers on every board.

Boundary Scan

For dense BGA designs where physical probing cannot reach, IEEE 1149.1 boundary scan tests interconnects through the device itself.

RequirementNote
TCK, TMS, TDI, TDO, TRSTBring out to test points or a connector
Chain orderDocument it; the tooling needs it
Non-scan devicesBreak the chain; note what is not covered
Pull-upsPer the standard, so the chain is deterministic at power-up

See boundary scan JTAG test.

Firmware Hooks Are Part of DFT

The most under-used testability feature is firmware that cooperates with the test.

HookValue in production test
Self-test commandExercises peripherals the fixture cannot reach
Version queryConfirms the right image is installed
ADC raw readbackVerifies the analog front end without external stimulus
GPIO set/getEnables loopback testing without extra hardware
Calibration writeStores coefficients computed by the test
Fault injectionVerifies protection circuits deliberately

A selftest command returning structured output turns a whole class of expensive fixture hardware into a serial query. It costs a day of firmware work and saves fixture cost on every station.

Design the output to be machine-readable from the start:

plugs/dut.py
import jsonclass Dut:    """Serial console on the board under test."""    def selftest(self) -> dict:        """Firmware returns JSON, so the test does not parse prose."""        raw = self._query("selftest --json")        return json.loads(raw)    def _query(self, cmd: str) -> str:        return '{"adc_counts": 2048, "gpio_ok": true, "temp_c": 41.2}'
phases/selftest.py
def selftest(measurements, dut):    result = dut.selftest()    measurements.adc_counts = result["adc_counts"]    measurements.gpio_ok = result["gpio_ok"]    measurements.die_temperature = result["temp_c"]
procedure.yaml
19 lines
main:  - name: Selftest    python: phases.selftest    measurements:      - name: ADC Counts        validators:          - operator: ">="            expected_value: 2000          - operator: "<="            expected_value: 2096      - name: GPIO Ok        validators:          - operator: "=="            expected_value: true      - name: Die Temperature        unit: "°C"        validators:          - operator: "<="            expected_value: 85

Structured output matters more than it looks. A firmware self-test that prints human prose forces every test script to parse it, and every firmware wording change breaks production.

Fixture Considerations

PracticeWhy
Keep tall components away from probe areasProbes need clearance
Avoid test points under connectors or shieldsUnreachable once assembled
Provide a ground plane reference near high-speed probesOtherwise the measurement is the fixture
Add a fixture calibration shortLets the test measure its own contact resistance
Standardise tooling hole positions across productsOne fixture base serves many boards

The calibration short is worth singling out. A known short across two pins lets the test measure contact resistance directly, so fixture wear is detected as a trend rather than as mysterious intermittent failures on good boards.

Panel and Multi-Up Considerations

If boards are tested in panel, decide early whether the fixture tests all positions at once. Testing four boards in parallel needs four sets of probes and four instrument paths, or a switching matrix. Both affect the layout, and retrofitting either is expensive.

The DFT Review

Before layout release, walk the test plan against the layout:

  1. Every measurement in the plan has an accessible test point.
  2. Every rail can be measured, and ideally isolated.
  3. Programming and reset are driveable by the fixture.
  4. Boot mode can be forced without human intervention.
  5. Test points are one side, correctly spaced, clear of the edge.
  6. Tooling holes exist and are documented.
  7. Firmware exposes the hooks the plan depends on.
  8. Dense areas have boundary scan or accept documented coverage gaps.

An hour of this review is routinely worth more than a week of fixture engineering later.

If You Already Run OpenHTF

selftest.py
21 lines
import openhtf as htffrom openhtf.util import unitsfrom tofupilot.openhtf import upload@htf.measures(    htf.Measurement("adc_counts").in_range(2000, 2096),    htf.Measurement("gpio_ok").equals(True),    htf.Measurement("die_temperature")    .in_range(maximum=85)    .with_units(units.DEGREE_CELSIUS),)def phase_selftest(test):    test.measurements.adc_counts = 2048    test.measurements.gpio_ok = True    test.measurements.die_temperature = 41.2test = htf.Test(phase_selftest, part_number="PCBA-100")test.add_output_callbacks(upload())test.execute(test_start=lambda: input("Scan serial: "))

Install with pip install "tofupilot[openhtf]", or run it with tofupilot run.

Common Mistakes

MistakeConsequenceFix
Test points added after layoutAwkward fixture, or a respinDFT review before layout release
Test points on both sidesFixture cost roughly doublesConsolidate to one side
Solder-masked test padsProbes cannot make contactSpecify a solderable finish
Boot strap only as a physical jumperManual intervention per boardMake it fixture-driveable
Firmware self-test printing proseEvery wording change breaks productionStructured output, JSON
No fixture calibration shortFixture wear looks like product failureAdd one and trend contact resistance
No tooling holesAlignment by hand, inconsistent contactTwo non-plated holes, diagonal

More Guides

Put this guide into practice