Skip to content
Test Types & Methods

Design for Testability: PCB Best Practices

PCB design for testability (DFT) guidelines covering test point placement, JTAG boundary scan, programming headers, and how DFT enables automated testing.

JJulien Buteau
intermediate12 min readMarch 14, 2026

Good DFT decisions made during PCB layout directly reduce test time, fixture cost, and escapes at ICT, functional test, and in-system programming. A board that's hard to probe is hard to test automatically. This guide covers the mechanical and electrical decisions that make automated testing with OpenHTF and TofuPilot practical.

What DFT Means for PCBAs

Design for Testability (DFT) is the set of layout and schematic decisions that make a PCBA observable and controllable during manufacturing test.

PropertyDefinitionTest impact
ObservabilityCan you measure the signal?Missing test points force blind assumptions
ControllabilityCan you force the circuit into a known state?Missing isolation makes component-level testing impossible
AccessibilityCan the probe physically reach the pad?Covered vias and undersized pads break fixturing

Test Point Placement Guidelines

Test points are the interface between your board and the test fixture. Every net you want to probe at ICT or functional test needs one.

Physical Requirements

ParameterMinimumPreferred
Test point diameter0.9 mm (35 mil)1.27 mm (50 mil)
Pad-to-pad spacing1.27 mm center-to-center2.54 mm (100 mil grid)
Clearance from components0.5 mm1.0 mm
Board edge clearance3.0 mm5.0 mm
Max probe travel (spring pin)3.0 mm1.5 mm nominal

Place test points on a 100 mil grid where possible. Bed-of-nails fixtures are built on that grid.

Which Nets Need Test Points

Cover these at minimum:

  • Power rails (VCC, VBAT, VIO, all regulator outputs)
  • Ground (one per power domain)
  • Digital I/O on microcontrollers and FPGAs
  • Analog signal paths (input and output)
  • Crystal oscillator pins
  • Reset lines
  • Communication buses (UART TX/RX, SPI CS/CLK/MOSI/MISO, I2C SDA/SCL)
  • Programming and debug interfaces (SWDIO, SWDCLK, TDI, TDO, TMS, TCK)

Placement Rules

Put test points on the primary side (component side) when possible. Double-sided fixtures are expensive and slow cycle time. If a net only routes on the back side, add a via to the top with a test point.

Do not cover test point vias with soldermask. Soldermask-covered vias are common for density but kill ICT access.

Common DFT Mistakes and Test Cost Impact

MistakeHow it manifestsCost impact
Test points on bottom side onlyRequires double-sided fixture2x fixture cost, slower cycle time
Test points under connectors or heatsinksUnreachable by spring pinNet is untestable without rework
SMD vias with soldermaskProbe can't make contactFalse failures at ICT, escapes
No test point on power railCan't verify regulation voltagePower faults go undetected
Missing ground reference near analog TPFloating measurementNoise, false limits
No reset test pointCan't force DUT into known stateFunctional test must rely on POR only
0.5 mm or smaller test point padsSpring pins miss or skipIntermittent contact, low fixture yield
Test points at arbitrary coordinatesOff-grid pins, fixture complexityHigher fixture build cost

Boundary Scan (JTAG) Considerations

JTAG boundary scan (IEEE 1149.1) tests interconnect and basic component function without a bed-of-nails fixture.

For JTAG to work in manufacturing:

  • All JTAG-capable devices must be in the scan chain (no unconnected TAP pins)
  • TDO of each device connects to TDI of the next
  • TMS and TCK are bussed across all devices
  • TRST is pulled high with a 10k resistor (active-low)
  • The chain must be accessible through a header or test points

Include a populated 10-pin ARM JTAG or 20-pin JTAG header on EVT boards. Depopulate the connector on production builds but keep the footprint and test points.

Programming Headers and Debug Interfaces

InterfaceHeaderSignals needed
ARM SWD10-pin Cortex DebugSWDIO, SWDCLK, GND, VCC, nRESET
JTAG (ARM)20-pin JTAGTDI, TDO, TMS, TCK, nRESET, VCC, GND
UART bootloader4-pin 2.54 mmTX, RX, GND, VCC
SPI flash6-pinCS, CLK, MOSI, MISO, GND, VCC
FPGA JTAG6-pinTDI, TDO, TMS, TCK, GND, VCC

Many microcontrollers have BOOT0/BOOT1 pins that select the programming mode. These need to be controllable from the test fixture. Add a test point to BOOT0.

How DFT Enables Automated Testing with OpenHTF and TofuPilot

With test points, JTAG access, and a controllable programming interface, you can build a fixture that drives the DUT through a full test sequence without human intervention.

tests/pcba_functional_test.py
import time
import subprocess
import serial
import openhtf as htf
from tofupilot.openhtf import TofuPilot


def program_firmware(test):
    """Flash firmware over SWD using pyocd."""
    result = subprocess.run(
        ["pyocd", "flash", "--target", "stm32g431rb", "firmware.hex"],
        capture_output=True,
        text=True,
        timeout=30,
    )
    test.logger.info(result.stdout)
    if result.returncode != 0:
        test.logger.error(result.stderr)
        return htf.PhaseResult.STOP


@htf.measures(
    htf.Measurement("vcc_3v3")
    .in_range(minimum=3250, maximum=3350),
    htf.Measurement("vbat")
    .in_range(minimum=3500, maximum=4200),
)
def measure_power_rails(test):
    """Probe VCC and VBAT test points with DMM."""
    test.measurements.vcc_3v3 = 3312  # Replace with real DMM read * 1000
    test.measurements.vbat = 3850  # Replace with real DMM read * 1000


@htf.measures(
    htf.Measurement("uart_echo_ok").equals(True),
    htf.Measurement("uart_response_time")
    .in_range(minimum=0, maximum=100),
)
def test_uart_comms(test):
    """Send command over UART, verify echo and response time."""
    with serial.Serial("/dev/ttyUSB0", baudrate=115200, timeout=1) as port:
        port.write(b"PING\r
")
        t0 = time.monotonic()
        response = port.readline().decode().strip()
        elapsed_ms = (time.monotonic() - t0) * 1000

    test.measurements.uart_echo_ok = response == "PONG"
    test.measurements.uart_response_time = round(elapsed_ms, 1)


def main():
    test = htf.Test(
        program_firmware,
        measure_power_rails,
        test_uart_comms,
        test_name="PCBA Functional Test",
    )

    with TofuPilot(test):
        test.execute(test_start=lambda: input("Serial: "))


if __name__ == "__main__":
    main()

Each phase maps to a test point or interface on the board. program_firmware uses SWD. measure_power_rails uses test points on VCC and VBAT. test_uart_comms uses the UART header.

Well-Designed Board vs. Poorly-Designed Board

StepWell-designed DUTPoorly-designed DUT
Fixture contact100% spring pin contact on 100-mil grid40% contact due to off-grid and covered vias
Firmware programmingSWD header, automated with pyocdManual USB cable plug, operator-dependent
Power rail verificationTest point on each rail, DMM probeNo test points, inferred from UART only
UART comms testHeader with TX/RX/GND, 3-second phaseRequires board modification or probing SMD pad
Cycle time45 seconds automated, zero operator steps4 minutes with two operator interventions
Escape rateLow (full coverage)High (partial coverage, manual steps)

DFT Checklist

CategoryCheckPass criteria
Test pointsAll power rails have test pointsOne TP per rail, top side
Test pointsAll ground domains have a ground reference TPWithin 25 mm of critical measurement points
Test pointsKey digital I/O are testableUART, SPI, I2C, GPIO have test points
Test pointsAll TPs are 0.9 mm minimum diameterVerified in DRC
Test pointsAll TPs on 100-mil gridVerify in layout with grid snap
Test pointsNo TPs under connectors, heatsinks, or shieldsManual check
ViasNo soldermask-covered vias on critical netsReview with fab DFM report
JTAGScan chain connected end-to-endTDO_n to TDI_(n+1)
JTAGTRST pulled high at power-on10k resistor to VCC
JTAGJTAG header footprint presentPopulated on EVT
ProgrammingSWD or JTAG header accessible on top sideVerified with fixture clearance
ProgrammingBOOT mode pins accessible as test pointsBOOT0 or equivalent exposed
DebugUART or SWO accessible via header or TPBaud rate documented
MechanicalBoard edge clearance >= 3 mm for all TPsDRC clean
MechanicalComponent clearance >= 0.5 mm around TPsSpring pin travel verified

More Guides

Put this guide into practice