Skip to main content

Overview

Signal integrity (SI) analysis helps ensure reliable high-speed digital and analog signal transmission. KiCad provides tools for transmission line modeling, impedance control, and IBIS-based simulation.

Transmission Line Theory

Characteristic Impedance

For a PCB trace, the characteristic impedance Z₀ is determined by:
Z₀ = √(L/C)
Where:
  • L = inductance per unit length
  • C = capacitance per unit length

Microstrip Traces

Surface traces with ground plane beneath:
Z₀ ≈ (87/√(εᵣ+1.41)) × ln(5.98h/(0.8w+t))
Where:
  • εᵣ = dielectric constant
  • h = height above ground plane
  • w = trace width
  • t = trace thickness

Stripline Traces

Internal traces between ground planes:
Z₀ ≈ (60/√εᵣ) × ln(4h/(0.67π(0.8w+t)))

Stackup Configuration

Define board stackup in the board file (.kicad_pcb):
(stackup
  (layer "F.Cu"
    (type "copper")
    (thickness 0.035)
  )
  (layer "dielectric 1"
    (type "prepreg")
    (thickness 0.2104)
    (material "FR4")
    (epsilon_r 4.5)
    (loss_tangent 0.02)
  )
  (layer "In1.Cu"
    (type "copper")
    (thickness 0.0152)
  )
  (layer "dielectric 2"
    (type "core")
    (thickness 1.065)
    (material "FR4")
    (epsilon_r 4.6)
    (loss_tangent 0.02)
  )
  (layer "B.Cu"
    (type "copper")
    (thickness 0.035)
  )
)

Impedance Calculator

Use KiCad’s built-in PCB Calculator for impedance:
import math

def microstrip_impedance(width, height, thickness, er):
    """
    Calculate microstrip impedance
    
    Args:
        width: Trace width (mm)
        height: Dielectric height (mm)
        thickness: Copper thickness (mm)
        er: Relative permittivity
    
    Returns:
        Impedance in ohms
    """
    # Effective width
    if thickness / height < 0.5:
        w_eff = width + (thickness / math.pi) * math.log(2 * height / thickness)
    else:
        w_eff = width + (thickness / math.pi) * math.log(height / thickness + 1)
    
    # Effective permittivity
    er_eff = (er + 1) / 2 + (er - 1) / 2 * math.pow(1 + 12 * height / w_eff, -0.5)
    
    # Impedance
    if w_eff / height < 1:
        z0 = (60 / math.sqrt(er_eff)) * math.log(8 * height / w_eff + w_eff / (4 * height))
    else:
        z0 = (120 * math.pi) / (math.sqrt(er_eff) * (w_eff / height + 1.393 + 0.667 * math.log(w_eff / height + 1.444)))
    
    return z0

# Example: 50Ω trace on FR4
width = microstrip_width_for_impedance(
    target_z0=50,
    height=0.2,      # 0.2mm dielectric
    thickness=0.035, # 1oz copper
    er=4.5          # FR4
)
print(f"Trace width for 50Ω: {width:.3f} mm")

Differential Pairs

Coupling and Impedance

For differential pairs:
Z_diff = 2 × Z_odd
Z_odd = Z₀ × √(1 - k)
Z_even = Z₀ × √(1 + k)
Where k is the coupling coefficient.

Design Rules

Set up differential pair rules in PCB:
import pcbnew

def setup_diff_pair_rules(board, net_prefix, target_z_diff=100):
    """
    Configure differential pair routing rules
    """
    # Get board design settings
    settings = board.GetDesignSettings()
    
    # Set track width for single-ended impedance
    single_z0 = target_z_diff / 2
    track_width = calculate_width_for_impedance(single_z0)
    
    # Set gap between pairs (typically 2-3x trace width)
    gap = track_width * 2.5
    
    # Apply to matching nets
    for net_code in range(board.GetNetCount()):
        net = board.FindNet(net_code)
        if net and net.GetNetname().startswith(net_prefix):
            # Set netclass properties
            netclass = net.GetNetClass()
            netclass.SetTrackWidth(int(track_width * 1e6))  # Convert to nm
            netclass.SetDiffPairWidth(int(track_width * 1e6))
            netclass.SetDiffPairGap(int(gap * 1e6))

# Usage
board = pcbnew.GetBoard()
setup_diff_pair_rules(board, "USB_", 90)  # 90Ω USB differential pair
setup_diff_pair_rules(board, "PCIE_", 100)  # 100Ω PCIe

IBIS Models

KiCad supports IBIS (I/O Buffer Information Specification) models for signal integrity simulation.

IBIS File Structure

[IBIS Ver] 4.2
[File Name] device.ibs
[File Rev] 1.0
[Date] January 15, 2024
[Component] MyDevice
[Manufacturer] Example Corp

[Pin] signal_name model_name R_pin L_pin C_pin
 1     DATA0      Output_3V3 50m   2n    1p
 2     DATA1      Output_3V3 50m   2n    1p
 3     CLK        Clock_3V3  50m   2n    1p

[Model] Output_3V3
Model_type Output
Polarity Non-Inverting
Enable Active-High
Vinl = 0.8V
Vinh = 2.0V
Vmeas = 1.5V
Cref = 50pF
Rref = 50
Vref = 0

[Voltage Range] 3.0V 3.3V 3.6V

[Pulldown]
| Voltage  I(typ)    I(min)    I(max)
  -3.3V    -50m      -45m      -55m
  0.0V     0         0         0
  3.3V     50m       45m       55m

[Pullup]
| Voltage  I(typ)    I(min)    I(max)
  -3.3V    50m       45m       55m
  0.0V     0         0         0
  3.3V     -50m      -45m      -55m

Loading IBIS Models

Programmatic access to IBIS models:
#include "eeschema/sim/kibis/kibis.h"

// Load IBIS file
KIBIS kibis;
if( kibis.Init( "/path/to/device.ibs" ) )
{
    // Get component
    KIBIS_COMPONENT* comp = kibis.GetComponent( "MyDevice" );
    
    // Get pin models
    for( const KIBIS_PIN& pin : comp->GetPins() )
    {
        std::cout << "Pin: " << pin.m_pinNumber 
                  << " Model: " << pin.m_modelName << std::endl;
    }
}

Reflection Analysis

Reflection Coefficient

Γ = (Z_L - Z₀) / (Z_L + Z₀)
Where:
  • Z_L = load impedance
  • Z₀ = transmission line impedance

Return Loss

RL = -20 × log₁₀(|Γ|) dB
Good design targets:
  • RL > 10 dB (acceptable)
  • RL > 20 dB (good)
  • RL > 30 dB (excellent)

Python Analysis

import numpy as np
import matplotlib.pyplot as plt

def reflection_coefficient(z_load, z0=50):
    """Calculate reflection coefficient"""
    return (z_load - z0) / (z_load + z0)

def return_loss(gamma):
    """Calculate return loss in dB"""
    return -20 * np.log10(np.abs(gamma))

def vswr(gamma):
    """Calculate voltage standing wave ratio"""
    return (1 + np.abs(gamma)) / (1 - np.abs(gamma))

# Analyze impedance mismatch
z_loads = np.linspace(25, 75, 100)
gammas = [reflection_coefficient(z) for z in z_loads]
rl = [return_loss(g) for g in gammas]

plt.figure(figsize=(10, 6))
plt.subplot(2, 1, 1)
plt.plot(z_loads, gammas)
plt.xlabel('Load Impedance (Ω)')
plt.ylabel('Reflection Coefficient')
plt.grid(True)

plt.subplot(2, 1, 2)
plt.plot(z_loads, rl)
plt.xlabel('Load Impedance (Ω)')
plt.ylabel('Return Loss (dB)')
plt.axhline(y=10, color='r', linestyle='--', label='10 dB threshold')
plt.grid(True)
plt.legend()
plt.tight_layout()
plt.show()

Crosstalk Analysis

Near-End and Far-End Crosstalk

def crosstalk_coupling(spacing, height, er=4.5):
    """
    Estimate coupling between parallel traces
    
    Args:
        spacing: Edge-to-edge spacing (mm)
        height: Height above ground plane (mm)
        er: Dielectric constant
    
    Returns:
        Coupling coefficient (0-1)
    """
    # Simplified model
    k = np.exp(-np.pi * spacing / height)
    
    # Adjust for dielectric
    k_eff = k / np.sqrt(er)
    
    return k_eff

def near_end_crosstalk(coupling, length, rise_time, velocity=1.5e8):
    """
    Estimate near-end crosstalk
    
    Args:
        coupling: Coupling coefficient
        length: Parallel run length (m)
        rise_time: Signal rise time (s)
        velocity: Propagation velocity (m/s)
    
    Returns:
        NEXT as fraction of aggressor amplitude
    """
    if rise_time <= 0:
        return 0
    
    t_prop = length / velocity
    
    if t_prop < rise_time:
        # Short coupled region
        next_value = coupling * t_prop / rise_time
    else:
        # Long coupled region
        next_value = coupling
    
    return next_value

# Example: 10cm parallel run
spacing = 0.3  # mm
height = 0.2   # mm
length = 0.1   # m (10cm)
rise_time = 1e-9  # 1ns

k = crosstalk_coupling(spacing, height)
next_val = near_end_crosstalk(k, length, rise_time)

print(f"Coupling coefficient: {k:.4f}")
print(f"NEXT: {next_val*100:.2f}% ({20*np.log10(next_val):.1f} dB)")

Propagation Delay

Delay Calculation

def propagation_delay(length, er_eff):
    """
    Calculate signal propagation delay
    
    Args:
        length: Trace length (m)
        er_eff: Effective dielectric constant
    
    Returns:
        Delay in seconds
    """
    c = 3e8  # Speed of light (m/s)
    velocity = c / np.sqrt(er_eff)
    delay = length / velocity
    return delay

def delay_per_inch(er_eff):
    """
    Standard delay metric
    
    Args:
        er_eff: Effective dielectric constant
    
    Returns:
        Delay in ps/inch
    """
    length_inch = 0.0254  # meters
    delay = propagation_delay(length_inch, er_eff)
    return delay * 1e12  # Convert to ps

# FR4 typical
er_eff = 3.5
print(f"Delay: {delay_per_inch(er_eff):.1f} ps/inch")

# Length matching for DDR
clock_length = 100e-3  # 100mm
data_length = 105e-3   # 105mm
skew = abs(propagation_delay(clock_length, er_eff) - 
          propagation_delay(data_length, er_eff))
print(f"Skew: {skew*1e12:.1f} ps")

Design Guidelines

High-Speed Design Rules

ParameterGuidelineTypical Value
Trace spacing3× trace width0.3-0.5 mm
Via stub length< λ/20< 50 mm @ 1 GHz
Diff pair coupling2-3× trace width0.2-0.4 mm
Return pathContinuousNo gaps
Length matching< 0.1 × rise time±5 mm for DDR4

Impedance Tolerances

  • Single-ended: ±10% (45-55Ω for 50Ω target)
  • Differential: ±10% (90-110Ω for 100Ω target)
  • Tight control: ±5% for critical signals

Practical Examples

DDR4 Interface

class DDR4_Design:
    def __init__(self):
        self.target_z_single = 40  # Ω
        self.target_z_diff = 100   # Ω
        self.max_skew = 5e-12      # 5ps
        self.max_length = 0.05     # 50mm
    
    def validate_trace_lengths(self, lengths):
        """Check if trace lengths meet skew requirements"""
        delays = [propagation_delay(l, 3.5) for l in lengths]
        max_skew = max(delays) - min(delays)
        
        if max_skew > self.max_skew:
            print(f"FAIL: Skew {max_skew*1e12:.2f}ps exceeds {self.max_skew*1e12:.2f}ps")
            return False
        
        print(f"PASS: Skew {max_skew*1e12:.2f}ps within spec")
        return True

# Example usage
ddr4 = DDR4_Design()
data_lengths = [0.048, 0.050, 0.049, 0.051]  # meters
ddr4.validate_trace_lengths(data_lengths)

PCIe Gen3

class PCIe_Gen3_Design:
    def __init__(self):
        self.target_z_diff = 85    # Ω (PCIe spec: 85±15%)
        self.max_intra_pair_skew = 1e-12  # 1ps
        self.max_lane_skew = 100e-12      # 100ps
    
    def check_differential_pair(self, p_length, n_length):
        """Validate differential pair matching"""
        skew = abs(propagation_delay(p_length, 3.5) - 
                   propagation_delay(n_length, 3.5))
        
        if skew > self.max_intra_pair_skew:
            print(f"FAIL: Pair skew {skew*1e12:.2f}ps")
            return False
        
        print(f"PASS: Pair skew {skew*1e12:.3f}ps")
        return True

Via Modeling

Via Impedance Discontinuity

def via_impedance(pad_diameter, barrel_diameter, height, er=4.5):
    """
    Estimate via impedance
    
    Args:
        pad_diameter: Via pad diameter (mm)
        barrel_diameter: Via barrel diameter (mm)
        height: Board thickness (mm)
        er: Dielectric constant
    
    Returns:
        Via impedance (Ω)
    """
    # Simplified via model
    z_via = (60 / np.sqrt(er)) * np.log(4 * height / barrel_diameter)
    
    # Capacitance from pad
    c_pad = (er * 8.85e-12 * np.pi * (pad_diameter/2)**2) / height
    
    return z_via, c_pad

# Example
pad_d = 0.6   # mm
barrel_d = 0.3  # mm
board_h = 1.6  # mm

z_via, c_pad = via_impedance(pad_d, barrel_d, board_h)
print(f"Via impedance: {z_via:.1f}Ω")
print(f"Pad capacitance: {c_pad*1e15:.2f}fF")

Best Practices

  1. Minimize via stubs: Use back-drilling for high-speed signals
  2. Control impedance: Maintain ±10% throughout signal path
  3. Match lengths: Keep clock/data skew within specifications
  4. Avoid splits: Don’t cross reference plane gaps
  5. Use ground stitching: Place vias near signal transitions
  6. Simulate critical nets: Verify timing and integrity before fabrication
  7. Document stackup: Specify impedance requirements to fabricator

See Also

Build docs developers (and LLMs) love