KiCad PCB Design Guide
Implements modern KiCad 9+ PCB design workflows — from schematic capture through fabrication-ready file export — with emphasis on controlled impedance routing, EMC-aware layout, and robust DRC validation. Acts as a senior hardware design engineer ensuring every board meets signal integrity, manufacturability, and regulatory standards before leaving the desk.
TL;DR Checklist
- Create project with correct units (mm) and grid settings before placing any component
- Assign footprints to all symbols and verify footprint library paths are resolved
- Run ERC after schematic completion — fix all warnings before layout
- Set PCB layer stackup with impedance targets (microstrip/stripline) in pcbnew
- Route differential pairs with length matching within ±5% of pair length tolerance
- Place decoupling capacitors within 2mm of IC power pins; keep return paths uninterrupted
- Run full DRC and fix all errors before generating fabrication output
- Export ODB++ (preferred) or Gerber + drill files; verify with CAM viewer
When to Use
Use this skill when:
- Designing a new PCB from schematic through fabrication files in KiCad 9+
- Reviewing an existing KiCad project for signal integrity, EMC, or manufacturability issues
- Configuring controlled impedance traces (microstrip, stripline) on multi-layer boards
- Setting up differential pair routing with length matching constraints
- Resolving DRC/ERC violations that block fabrication output generation
- Generating Gerber, ODB++, or IPC-2581 fabrication files with correct layer stackup
- Migrating a legacy KiCad 7/8 project to the KiCad 9+ workflow and library system
When NOT to Use
- For simple breadboard or perf-board prototyping where no fabrication is needed
- For pure schematic-only documentation (use a reference skill instead of layout)
- For FPGA fabric design or digital logic synthesis (use an HDL-specific skill)
- When the user needs help with PCB assembly (SMT placement, reflow profiles) — use a manufacturing skill
Core Workflow
Phase 1: Project Setup and Schematic Capture
Initialize Project — Create a new KiCad project file (
*.kicad_pro). Set preferred footprint libraries, board outline dimensions, and default drawing settings. Checkpoint: Confirm layer stackup is defined (at minimum Top copper, Bottom copper, and at least one ground plane) before placing any components.Create or Source Schematic Symbols — Draw new symbols in the Symbol Editor (
kicad-cli pcb symbol editor) or source from libraries like KiCad Official, Bazaar, or manufacturer footprint libraries (Texas Instruments, Analog Devices, Mouser). Checkpoint: Every component must have a symbol with correct pin numbers, electrical types (Passive, Input, Output, Power), and designators prefixed correctly (R,C,U,L,J).Draw the Schematic — Place components, route wires, add power ports, label nets, and place hierarchical sheets for complex designs. Use net labels for global connections rather than long wire runs. Checkpoint: Run ERC (Electrical Rule Check) via
Design Rules → Run ERC. All warnings must be reviewed; critical errors must be resolved before proceeding to layout.Update Netlist — Transfer schematic to PCB with
Tools → Update PCB from Schematic. Checkpoint: Verify footprint assignments resolved correctly — any "unassigned" footprints must be manually linked in the Footprint Assignment Table.
Phase 2: Component Library and Footprint Management
Verify Footprint Libraries — Open the Footprint Manager and verify every footprint has correct 3D model properties, solder mask/paste expansion, and silk screen clearance. Use
Tools → Manage Footprint Librariesto check paths. Checkpoint: No footprints should show "library not found" warnings. Resolve broken library paths or add local libraries.Create Custom Footprints When Needed — For connectors, mechanical parts, or custom packages not in existing libraries. Follow the footprint wizard as a starting point, then refine manually. Checkpoint: Measure the footprint against the datasheet drawing. Verify pad sizes match IPC-7351 recommendations for the target assembly process (0402 minimum pad spacing, for example).
Phase 3: PCB Layout with Impedance and EMC Control
Define Layer Stackup — Open
Preferences → Manage Layer Stack Manager. Set copper thickness (typically 1oz or 0.5oz), prepreg/core dielectric constants (FR-4: Er ≈ 4.2, H ≈ 0.2mm for standard 2-layer), and target impedance for controlled layers. Checkpoint: Record the stackup — it feeds directly into all impedance calculations in Phase 4.Board Outline and Keep-Out Zones — Draw the board edges on the Edge.Cuts layer. Define keep-out zones for mechanical constraints: mounting holes, connector protrusions, antenna clearance areas, thermal relief zones. Checkpoint: Verify board dimensions match the mechanical enclosure drawing or spec sheet.
Place Components Strategically — Group components by functional block (power stage, analog front-end, digital core, I/O). Orient decoupling capacitors closest to IC power pins with the shortest possible return path. Keep high-speed signals away from noisy analog sections and board edges. Checkpoint: Analog-sensitive traces should not cross under switching regulators or high-current paths.
Route with Signal Integrity in Mind — Route critical nets first: clocks, differential pairs, high-speed serial lines (USB, MIPI, Ethernet). Apply length matching rules for parallel buses. Use 45-degree or curved bends; avoid 90-degree corners. Checkpoint: Run Design Rule Check after initial routing pass to catch clearance violations before committing copper.
Phase 4: Validation and Fabrication Output
Run Full DRC — Execute
Design Rules → Run DRC. Review all errors by severity. Common fixable issues: clearance violations, unconnected pins, missing silkscreen text, oversized drill holes. Checkpoint: Zero errors and zero warnings should remain before export. Some non-critical warnings (e.g., "unrouted trace") are acceptable if intentional (test points).Generate Fabrication Files — Export ODB++ (preferred for modern PCB houses) via
File → Export → ODB++. Select appropriate options: include netlist, select all layers, enable assembly drawings and centroid files. Alternative: Gerber + Excellon drill files with NC Drill format. Checkpoint: Open the exported files in a CAM viewer (e.g., KiCad's built-in 3D viewer, ViewBotView, or GC-Prevue) to visually verify layer output matches expectations.Generate BOM and Pick-and-Place — Export Bill of Materials with reference designators, footprints, values, manufacturers, and Mouser/Digikey part numbers. Generate centroid files (X-Y placement) for assembly houses. Checkpoint: Cross-check the BOM against the schematic to ensure every component is accounted for with correct manufacturer part numbers.
Implementation Patterns
Pattern 1: IPC-2152 Trace Width Calculator
Calculate trace width based on IPC-2152 standard for current-carrying capacity. This accounts for copper cross-sectional area, allowable temperature rise, and board layer configuration (internal vs. external).
"""
Trace width calculation based on IPC-2152 standard.
Uses the IPC-2152 empirical model for copper trace current capacity.
External layers: higher cooling → more current per mm² of copper.
Internal layers: insulated by dielectric → lower current per mm².
"""
import math
# IPC-2152 constants (empirically derived from test data)
# These approximate the curve-fit equations from standard Annex D
IPC_2152_A_EXTERNAL = 0.048 # Cross-section area constant for external layers
IPC_2152_B_EXTERNAL = -0.449 # Exponent for external layers
IPC_2152_A_INTERNAL = 0.044 # Cross-section area constant for internal layers
IPC_2152_B_INTERNAL = -0.407 # Exponent for internal layers
# Standard copper thicknesses in oz/ft² (1 oz = 35µm ≈ 1.37 mils)
COPPER_THICKNESS_OZ = {
"0.5oz": 0.5,
"1oz": 1.0,
"2oz": 2.0,
}
def trace_width_from_current(
current_amps: float,
temp_rise_celsius: float = 10.0,
copper_thickness_oz: float = 1.0,
internal_layer: bool = False,
) -> float:
"""Calculate minimum trace width in mm for a given current using IPC-2152.
Args:
current_amps: Maximum current the trace must carry (A).
temp_rise_celsius: Allowable temperature rise above ambient (°C).
10°C is typical for signal traces; 20°C+ may be used for power traces.
copper_thickness_oz: Copper weight in oz/ft² (e.g., 1.0, 2.0).
internal_layer: True if trace is on an internal layer (between two dielectrics).
Internal traces have worse cooling than external traces.
Returns:
Minimum trace width in millimeters.
Raises:
ValueError: If current is negative or copper thickness is unsupported.
"""
if current_amps <= 0:
raise ValueError(f"Current must be positive, got {current_amps} A")
if temp_rise_celsius <= 0:
raise ValueError(f"Temperature rise must be positive, got {temp_rise_celsius} °C")
if copper_thickness_oz not in COPPER_THICKNESS_OZ.values():
raise ValueError(
f"Unsupported copper weight: {copper_thickness_oz} oz/ft². "
f"Use one of: {list(COPPER_THICKNESS_OZ.values())}"
)
# Convert copper thickness to mils (1 oz/ft² ≈ 34.77 µm ≈ 1.37 mils)
copper_mils = copper_thickness_oz * 1.378 # mils
# Select IPC-2152 constants based on layer type
if internal_layer:
a, b = IPC_2152_A_INTERNAL, IPC_2152_B_INTERNAL
else:
a, b = IPC_2152_A_EXTERNAL, IPC_2152_B_EXTERNAL
# Calculate required copper cross-sectional area (mil²)
# I = k * ΔT^b * Area^c → Area = (I / (k * ΔT^b))^(1/c)
# Using the simplified form: Area = (I / a / ΔT^b)
area_mil2 = current_amps / (a * (temp_rise_celsius ** b))
# Width = Area / Thickness
width_mils = area_mil2 / copper_mils
return width_mils * 0.0254 # Convert mils to mm
# --- Example: Design decisions for a typical power stage ---
if __name__ == "__main__":
# Buck converter switch node: 3A, 1oz external copper, 20°C rise
sw_node_width = trace_width_from_current(
current_amps=3.0,
temp_rise_celsius=20.0,
copper_thickness_oz=1.0,
internal_layer=False,
)
print(f"Switch node trace (3A): {sw_node_width:.3f} mm")
# USB D+/D- differential pair: 0.5A, 1oz external, 10°C rise
usb_diff_width = trace_width_from_current(
current_amps=0.5,
temp_rise_celsius=10.0,
copper_thickness_oz=1.0,
internal_layer=False,
)
print(f"USB diff pair power trace (0.5A): {usb_diff_width:.3f} mm")
# Internal ground plane: no width needed, but verify pour connectivity
print("Ground pour: use thermal reliefs with 8+ spokes for thermal mass")
Pattern 2: Controlled Impedance Calculation (Microstrip and Stripline)
Calculate characteristic impedance for microstrip (outer layer) and stripline (inner layer) traces using the IPC-2152/IPC-2223 field-solving approximations. Essential for USB, Ethernet, HDMI, DDR, and high-speed serial interfaces.
"""
Controlled impedance trace calculator for microstrip and stripline configurations.
Uses standard field-solver equations (IPC-2223 / Hammerstad/Jensen models).
Target impedances:
USB 2.0: 90 Ω differential (45 Ω single-ended)
Ethernet RMII: 100 Ω differential (50 Ω single-ended)
HDMI: 100 Ω differential (50 Ω single-ended)
DDR3/4: 50 Ω single-ended, 100 Ω differential (DQS)
UART/SPI: 50 Ω single-ended (optional for short traces < 3")
"""
def microstrip_impedance(
trace_width_mil: float,
dielectric_height_mil: float,
dielectric_constant: float = 4.2,
copper_thickness_mil: float = 1.4,
) -> float:
"""Calculate characteristic impedance of an microstrip trace (outer copper layer).
Uses the Hammerstad and Jensen approximation model for microstrip lines.
Args:
trace_width_mil: Trace width in mils.
dielectric_height_mil: Distance from trace to reference plane (prepreg/core thickness).
dielectric_constant: Relative permittivity (Er) of the dielectric material.
FR-4 typically 4.0–4.5; Rogers RO4350B is 3.48.
copper_thickness_mil: Copper thickness in mils (1oz = 1.37 mil, 0.5oz = 0.69 mil).
Returns:
Characteristic impedance in Ohms.
"""
w = trace_width_mil
h = dielectric_height_mil
# Calculate impedance for W/H <= 1 (narrow traces)
if w / h <= 1.0:
z0 = (84.0 / math.sqrt((dielectric_constant + 1.0) / 2.0)) * math.log(
5.98 * h / (0.8 * w + copper_thickness_mil)
)
else:
# Correction for W/H > 1 (wider traces)
epsilon_eff = (dielectric_constant + 1.0) / 2.0 + (
(dielectric_constant - 1.0) / 2.0 * (1.0 + 12.0 * h / w) ** (-0.5)
)
z0 = (84.0 / math.sqrt(epsilon_eff)) * math.log(
5.98 * h / (0.8 * w + copper_thickness_mil)
)
return z0
def stripline_impedance(
trace_width_mil: float,
dielectric_height_mil: float,
dielectric_constant: float = 4.2,
copper_thickness_mil: float = 1.4,
) -> float:
"""Calculate characteristic impedance of a stripline trace (inner copper layer).
The trace is sandwiched between two reference planes at equal distance.
Args:
trace_width_mil: Trace width in mils.
dielectric_height_mil: Distance from trace to each adjacent reference plane.
For symmetric stripline, this is half the total distance between planes.
dielectric_constant: Relative permittivity of surrounding dielectric.
copper_thickness_mil: Copper thickness in mils.
Returns:
Characteristic impedance in Ohms.
"""
w = trace_width_mil
h = dielectric_height_mil
t = copper_thickness_mil
if w <= 0 or h <= 0:
raise ValueError("Width and height must be positive")
# IPC-2223 stripline equation (symmetric, two reference planes)
z0 = (60.0 / math.sqrt(dielectric_constant)) * math.log(
(4.0 * h) / (0.577 * t + 0.8 * w)
)
return z0
def find_trace_width_for_impedance(
target_z_ohm: float,
trace_type: str = "microstrip",
dielectric_height_mil: float = 6.0,
dielectric_constant: float = 4.2,
copper_thickness_mil: float = 1.378,
tolerance_ohm: float = 5.0,
) -> dict:
"""Find the trace width (mils) that achieves a target impedance within tolerance.
Uses binary search between 2 mils and 100 mils for efficient convergence.
Args:
target_z_ohm: Desired characteristic impedance in Ohms (e.g., 50.0, 90.0).
trace_type: "microstrip" or "stripline".
dielectric_height_mil: Distance to reference plane.
dielectric_constant: Dielectric constant of substrate.
copper_thickness_mil: Copper thickness in mils.
tolerance_ohm: Acceptable impedance deviation from target.
Returns:
Dictionary with trace_width_mil, achieved_impedance, and layer_type.
"""
if trace_type not in ("microstrip", "stripline"):
raise ValueError("trace_type must be 'microstrip' or 'stripline'")
# Binary search for the width that achieves target impedance
lo, hi = 2.0, 100.0 # mils
for _ in range(50): # sufficient iterations for convergence
mid = (lo + hi) / 2.0
if trace_type == "microstrip":
z = microstrip_impedance(mid, dielectric_height_mil, dielectric_constant, copper_thickness_mil)
else:
z = stripline_impedance(mid, dielectric_height_mil, dielectric_constant, copper_thickness_mil)
if abs(z - target_z_ohm) <= tolerance_ohm:
return {
"trace_width_mil": mid,
"achieved_impedance": z,
"layer_type": trace_type,
"dielectric_height_mil": dielectric_height_mil,
"tolerance_ok": True,
}
if z > target_z_ohm:
lo = mid # wider trace → lower impedance
else:
hi = mid # narrower trace → higher impedance
return {
"trace_width_mil": (lo + hi) / 2.0,
"achieved_impedance": z,
"layer_type": trace_type,
"dielectric_height_mil": dielectric_height_mil,
"tolerance_ok": False,
}
if __name__ == "__main__":
# 50Ω microstrip on standard 2-layer FR-4 (top layer to GND plane)
# Typical stackup: 1.6mm board, trace at edge, ~0.8mm to ground pour
result = find_trace_width_for_impedance(
target_z_ohm=50.0,
trace_type="microstrip",
dielectric_height_mil=31.5, # ~0.8mm ≈ 31.5 mils
)
print(f"50Ω microstrip (0.8mm to GND): {result['trace_width_mil']:.1f} mils")
# 90Ω differential pair via single-ended traces on a 4-layer board
# Stackup: Signal1 / GND / PWR / Signal2, GND core ≈ 0.15mm to top layer
result_diff = find_trace_width_for_impedance(
target_z_ohm=45.0, # single-ended for 90Ω diff pair
trace_type="microstrip",
dielectric_height_mil=6.0, # ~0.15mm ≈ 6 mils (standard 4-layer prepreg)
copper_thickness_mil=1.378,
)
print(f"45Ω SE for 90Ω diff pair (0.15mm to GND): {result_diff['trace_width_mil']:.1f} mils")
# Validate with a 2-layer board where trace sits on edge:
result_2layer = find_trace_width_for_impedance(
target_z_ohm=50.0,
trace_type="microstrip",
dielectric_height_mil=47.0, # ~1.2mm for typical 2-layer board edge
copper_thickness_mil=0.689, # 0.5oz copper
)
print(f"50Ω microstrip (2-layer, 1.2mm to GND): {result_2layer['trace_width_mil']:.1f} mils")
Pattern 3: KiCad Python Scripting for Automated Design Rule Validation
Use the pcbnew Python API (available in KiCad 9+) to programmatically verify design rules, check trace clearances, validate differential pair routing, and report violations. This automation catches issues before running the full GUI DRC.
"""
KiCad 9+ Python scripting for automated PCB design validation.
Run via: kicad-cli pcb load project.kicad_pcb --script validate_design.py
Validates:
- Differential pair length matching within spec
- Via stub lengths and via type compliance
- Decoupling capacitor placement relative to IC power pins
- Clearances on high-speed net classes
"""
import sys
import pcbnew
from math import sqrt
def get_net_class(board, net_name):
"""Get the NetClass for a specific net name."""
nets = board.GetNetInfo()
if net_name in nets:
return nets.GetNetClass(net_name)
return None
def measure_trace_length(board, net_name):
"""Measure total trace length of all segments belonging to a net.
Args:
board: The PCBNEW board object.
net_name: Net name string (e.g., "USB_D+" or "SPI_MOSI").
Returns:
Total trace length in mm across all segments on all layers.
"""
total_length = 0.0
net_code = board.FindNet(net_name).GetNet()
for track in board.GetTracks():
if track.GetNetCode() == net_code:
x1, y1 = track.GetStart()
x2, y2 = track.GetEnd()
length = sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2) / 1e6 # convert from nm to mm
total_length += length
return total_length
def check_differential_pair_length_matching(
board,
pair_name_a: str,
pair_name_b: str,
max_mismatch_mm: float = 0.5,
) -> list:
"""Check that a differential pair's traces are length-matched within tolerance.
Args:
board: The PCBNEW board object.
pair_name_a: Net name of the first trace (e.g., "USB_D+").
pair_name_b: Net name of the second trace (e.g., "USB_D-").
max_mismatch_mm: Maximum allowed length difference in millimeters.
Returns:
List of violation dicts if mismatch exceeds tolerance; empty list if OK.
"""
len_a = measure_trace_length(board, pair_name_a)
len_b = measure_trace_length(board, pair_name_b)
mismatch = abs(len_a - len_b)
violations = []
if mismatch > max_mismatch_mm:
violations.append({
"type": "DIFF_PAIR_LENGTH_MISMATCH",
"net_a": pair_name_a,
"net_b": pair_name_b,
"length_a_mm": round(len_a, 3),
"length_b_mm": round(len_b, 3),
"mismatch_mm": round(mismatch, 3),
"max_allowed_mm": max_mismatch_mm,
"severity": "ERROR",
})
return violations
def validate_decoupling_placement(board, max_distance_mm: float = 5.0) -> list:
"""Check that decoupling capacitors are placed within specified distance of IC pins.
Scans all pads belonging to power/ground nets on ICs and verifies nearby caps exist.
Args:
board: The PCBNEW board object.
max_distance_mm: Maximum allowed distance from IC power pin to cap pad (mm).
Returns:
List of violation dicts for uncapped or far-placed decoupling capacitors.
"""
violations = []
max_dist_nm = max_distance_mm * 1e6 # convert mm to nanometers (KiCad internal units)
# Find all IC packages and their power pins
for module in board.GetModules():
if not module.GetReference().startswith("U"):
continue # skip non-IC components
for pad in module.Pads():
net_name = pad.GetNetname()
# Check if this is a power or ground pin
if net_name.upper() in ("VCC", "VDD", "VCC5", "VCC3", "GND", "AVDD", "DVDD"):
pad_pos = pad.GetPosition()
# Search for a nearby decoupling capacitor on the same net
found_cap = False
for cap in board.GetModules():
if not cap.GetReference().startswith("C"):
continue
for cap_pad in cap.Pads():
if cap_pad.GetNetname() == net_name:
cap_pos = cap_pad.GetPosition()
dist = sqrt(
(cap_pos.x - pad_pos.x) ** 2 +
(cap_pos.y - pad_pos.y) ** 2
)
if dist < max_dist_nm:
found_cap = True
break
if found_cap:
break
if not found_cap:
violations.append({
"type": "MISSING_DECAP",
"ic_ref": module.GetReference(),
"pin_name": pad.GetName(),
"net": net_name,
"max_distance_mm": max_distance_mm,
"severity": "WARNING",
})
return violations
def report_via_stubs(board) -> list:
"""Identify all vias and flag through-hole vias that could act as stubs.
In high-speed designs (> 50 MHz), via stubs cause reflections.
Blind/buried vias or back-drilled vias eliminate stubs.
Args:
board: The PCBNEW board object.
Returns:
List of via dicts with layer info for stub analysis.
"""
violations = []
all_layers = board.GetLayersCount()
for via in board.Vias():
start_layer = via.GetLayer()
end_layer = via.GetEndLayer()
# If a via spans from top to bottom (or near-full stackup), it's a potential stub
is_through_hole = (start_layer == 0 and end_layer == all_layers - 1)
if is_through_hole and via.GetHeight() > 1.0: # > 1mm tall on 2-layer board
violations.append({
"type": "VIA_STUB_POTENTIAL",
"via_pos": f"({via.GetX()/1e6:.1f}, {via.GetY()/1e6:.1f}) mm",
"start_layer": start_layer,
"end_layer": end_layer,
"diameter_mm": round(via.GetWidth() / 1e6, 2),
"note": "Consider back-drilling or blind vias for signals > 50 MHz",
})
return violations
def run_validation(board):
"""Run all validation checks and return a consolidated report."""
print("=" * 60)
print("KiCad 9+ Automated PCB Design Validation Report")
print("=" * 60)
all_violations = []
# Check differential pair matching for known high-speed nets
diff_pairs = [
("USB_D+", "USB_D-"),
("ETH_RXD+", "ETH_RXD-"),
("ETH_TXD+", "ETH_TXD-"),
]
for net_a, net_b in diff_pairs:
if board.FindNet(net_a) and board.FindNet(net_b):
v = check_differential_pair_length_matching(board, net_a, net_b, max_mismatch_mm=0.5)
all_violations.extend(v)
# Check decoupling capacitor placement
all_violations.extend(validate_decoupling_placement(board, max_distance_mm=5.0))
# Check via stubs
all_violations.extend(report_via_stubs(board))
# Output results
if not all_violations:
print("✓ All validation checks passed.")
else:
print(f"\nFound {len(all_violations)} issue(s):\n")
for i, v in enumerate(all_violations, 1):
print(f" [{i}] {v['type']} ({v.get('severity', 'UNKNOWN')})")
for key, val in v.items():
if key not in ("type", "severity"):
print(f" {key}: {val}")
return all_violations
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: kicad-cli pcb load project.kicad_pcb --script validate_design.py")
sys.exit(1)
board = pcbnew.LoadBoard(sys.argv[1])
report = run_validation(board)
sys.exit(1 if any(v.get("severity") == "ERROR" for v in report) else 0)
EMC / EMI Design Rules
Ground Plane Strategy
- Use continuous ground planes on inner layers. Never split ground planes under high-speed or mixed-signal circuits. Splitting creates return current discontinuities that radiate.
- Separate analog and digital grounds by routing, not by splitting the plane. Allow all return currents to share a single reference plane; signal returns naturally take the lowest-inductance path back to the source.
- Connect AGND and DGND at a single point under the mixed-signal IC (ADC/DAC) using a narrow bridge or 0Ω link, matching the datasheet recommendation.
Decoupling Best Practices
- Place one decoupling capacitor per power pin where physically possible. Use 0402 or 0603 X7R/X5R ceramic capacitors.
- Critical rule: The path from IC VCC pad → cap → GND plane must form the smallest possible loop. Route cap pads directly to each other using short, wide traces or direct copper pours.
- For multi-pin power domains (MCUs with VDDA and VDDD), use separate caps on each pin even if they share the same net name.
Filtering at I/O Connectors
- Place ESD protection diodes and series resistors as close to connector pins as possible (< 5mm trace).
- For analog inputs, add RC low-pass filters (e.g., 10kΩ + 10nF → 1.6kHz cutoff) before the ADC input.
- Use ferrite beads on power lines feeding noisy subsystems (RF modules, motor drivers).
Signal Integrity Basics
Trace Length Matching
- For parallel buses (SPI, I2C, GPIO), match trace lengths to within ±0.5mm for speeds up to 50MHz.
- For DDR data lines, match all traces in a byte group to within ±0.1mm and inter-byte groups to within ±0.5mm.
- For differential pairs (USB, Ethernet), length mismatch between +/− traces must not exceed 5% of the pair's total electrical length.
Via Management
- Minimize via count on high-speed nets. Each via adds 0.5–1pF of capacitance and creates an impedance discontinuity.
- Use back-drilled vias or blind/buried vias for signals above 50MHz to eliminate antenna stubs.
- Never use vias as a shortcut for routing — every via is a parasitic element (L ≈ 1nH, C ≈ 0.5–1pF).
Crossover Prevention
- On 4+ layer boards: Route horizontal on one signal layer, vertical on the adjacent signal layer, with solid ground planes between them. This provides shielding and reduces crosstalk.
- Keep spacing between parallel traces to at least 3× the trace width (the "3W rule") for controlled impedance lines.
KiCad CLI Automation Recipes
Run DRC from Command Line
# Non-interactive DRC check, exits with error code if violations found
kicad-cli pcb drc board.kicad_pcb --output report.drc.txt
# ERC on schematic
kicad-cli sch ERC schematic.kicad_sch --output report.erc.txt
# Generate Gerber files
kicad-cli pcb export gerber board.kicad_pcb --outdir gerbers \
--layer F.Cu --layer B.Cu --layer F.SilkS --layer B.SilkS \
--layer F.Mask --layer B.Mask --layer Edge.Cuts
# Generate drill files
kicad-cli pcb export ncdrill board.kicad_pcb --outdir gerbers
# Full fabrication export as ODB++
kicad-cli pcb export odbc board.kicad_pcb --outdir odbpp \
--assembly --netlist --centroids
Batch Footprint Library Check
# List all unassigned footprints in a project (pre-layout check)
kicad-cli sch symbol list schematic.kicad_sch | grep "UNASSIGNED"
# Validate all footprint library references
kicad-cli pcb lib verify board.kicad_pcb --output lib_report.txt
Constraints
MUST DO
- Run ERC after every schematic modification and resolve all errors before PCB layout
- Define layer stackup with impedance targets before placing any component
- Route differential pairs as matched pairs in KiCad's track routing constraints panel
- Set length matching rules for all parallel buses and differential pairs in Design Rules → Constraints
- Keep decoupling capacitors within 5mm of IC power pins; route the shortest possible return path
- Verify fabrication output with a CAM viewer before submission to the PCB house
- Document any design deviations from standard rules (e.g., reduced clearances for space constraints)
MUST NOT DO
- Split ground planes under mixed-signal circuits or high-speed digital traces
- Use 90-degree trace corners on high-speed signals — use 45° or curved arcs
- Route clocks parallel to each other on adjacent layers without a ground plane between them
- Place vias directly through mounting holes without annular ring clearance (IPC-7351 minimum)
- Export Gerbers without verifying copper paste mask expansion on BGA pads
- Skip DRC — even "just a few warnings" can hide critical issues like unconnected power nets
- Use default KiCad footprints for BGA/QFN packages without verifying pad pitch against the datasheet
Output Template
When designing or reviewing a KiCad PCB project, produce:
- Stackup Report — Layer count, copper weights, dielectric thicknesses, and calculated impedances
- ERC Summary — All ERC warnings with justification for any accepted warnings
- DRC Summary — Error and warning counts by category; list of any intentional exceptions
- Signal Integrity Notes — Differential pair lengths, mismatch values, via count on critical nets
- EMC Design Summary — Ground strategy description, decoupling coverage (% of power pins covered), I/O filtering locations
- Fabrication Checklist — ODB++/Gerber export verification status, BOM completeness, pick-and-place file generation
Related Skills
| Skill | Purpose |
|---|---|
electrical-engineering-hardware-debugging |
Diagnose PCB-level issues: oscilloscope probing techniques, fault isolation, signal tracing |
electrical-engineering-component-selection |
Choose passive and active components based on specs, tolerance, temperature range, and availability |
electrical-engineering-power-supply-design |
Design linear and switching power supplies with layout considerations for KiCad projects |
electrical-engineering-firmware-hardware-interface |
Define hardware-software interfaces: pin multiplexing, interrupt routing, peripheral configuration |
Live References
Authoritative documentation links for KiCad PCB design. The model follows markdown links at load time to resolve external references and inline content.