CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-pyftdi

Pure Python FTDI device driver for USB-to-serial/GPIO/SPI/I2C/JTAG bridge devices

Pending
Overview
Eval results
Files

jtag.mddocs/

JTAG Interface

JTAG controller supporting TAP state machine management, instruction and data register access, chain discovery, and basic debugging operations for embedded system development and device programming.

Capabilities

JTAG Controller Setup

Configure and manage JTAG controller with TAP state machine control.

class JtagController:
    def configure(self, url: str, **kwargs):
        """
        Configure JTAG controller with FTDI device.
        
        Parameters:
        - url: FTDI device URL (e.g., 'ftdi:///1')
        - frequency: JTAG clock frequency in Hz (default: 3MHz)
        - debug: Enable debug output
        
        Raises:
        - JtagError: Configuration failed
        - FtdiError: Device access error
        """
    
    def close(self):
        """Close JTAG controller and release device."""
    
    def get_frequency(self) -> float:
        """Get actual configured JTAG frequency."""

TAP State Machine Control

Manage JTAG Test Access Port (TAP) state machine transitions.

def reset(self):
    """Reset JTAG TAP to Test-Logic-Reset state."""

def go_idle(self):
    """Transition TAP to Run-Test-Idle state."""

def capture_ir(self):
    """Transition TAP to Capture-IR state."""

def capture_dr(self):
    """Transition TAP to Capture-DR state."""

def shift_ir(self):
    """Transition TAP to Shift-IR state."""

def shift_dr(self):
    """Transition TAP to Shift-DR state."""

def update_ir(self):
    """Transition TAP to Update-IR state."""

def update_dr(self):
    """Transition TAP to Update-DR state."""

def change_state(self, state: str):
    """
    Change TAP to specific state.
    
    Parameters:
    - state: Target state name ('reset', 'idle', 'capture_ir', etc.)
    """

Instruction Register Operations

Access and manipulate JTAG instruction register.

def write_ir(self, instruction: int, length: int = None) -> int:
    """
    Write instruction to instruction register.
    
    Parameters:
    - instruction: Instruction value to write
    - length: Instruction length in bits (auto-detected if None)
    
    Returns:
    int: Previous instruction register value
    
    Raises:
    - JtagError: Invalid instruction or communication error
    """

def read_ir(self, length: int) -> int:
    """
    Read current instruction register value.
    
    Parameters:
    - length: Instruction length in bits
    
    Returns:
    int: Current instruction register value
    """

Data Register Operations

Access and manipulate JTAG data register.

def write_dr(self, data: int, length: int) -> int:
    """
    Write data to data register.
    
    Parameters:
    - data: Data value to write
    - length: Data length in bits
    
    Returns:
    int: Previous data register value
    """

def read_dr(self, length: int) -> int:
    """
    Read data register value.
    
    Parameters:
    - length: Data length in bits
    
    Returns:
    int: Current data register value
    """

def shift_register(self, out: int, length: int) -> int:
    """
    Shift data through current register (IR or DR).
    
    Parameters:
    - out: Data to shift out
    - length: Number of bits to shift
    
    Returns:
    int: Data shifted in
    """

Chain Management

Discover and manage JTAG device chains.

def detect_register_size(self, register: str = 'ir') -> int:
    """
    Detect instruction or data register size.
    
    Parameters:
    - register: Register type ('ir' or 'dr')
    
    Returns:
    int: Register size in bits
    """

def detect_chain_length(self) -> int:
    """
    Detect JTAG chain length (number of devices).
    
    Returns:
    int: Number of devices in chain
    """

def identify_devices(self) -> list:
    """
    Identify devices in JTAG chain.
    
    Returns:
    list: List of device identification codes
    """

JTAG Engine

Higher-level JTAG operations engine for advanced functionality.

class JtagEngine:
    def __init__(self, controller: JtagController):
        """
        Initialize JTAG engine with controller.
        
        Parameters:
        - controller: Configured JtagController instance
        """
    
    def scan_chain(self) -> dict:
        """
        Scan and analyze JTAG chain.
        
        Returns:
        dict: Chain information with device details
        """
    
    def read_idcodes(self) -> list:
        """
        Read IDCODE from all devices in chain.
        
        Returns:
        list: List of 32-bit IDCODE values
        """
    
    def bypass_test(self) -> bool:
        """
        Perform bypass register test.
        
        Returns:
        bool: True if bypass test passes
        """

JTAG Tool

Command-line style JTAG operations for interactive use.

class JtagTool:
    def __init__(self, device_url: str):
        """
        Initialize JTAG tool with device.
        
        Parameters:
        - device_url: FTDI device URL
        """
    
    def execute_command(self, command: str, args: list = None):
        """
        Execute JTAG command.
        
        Parameters:
        - command: Command name ('reset', 'idcode', 'scan', etc.)
        - args: Command arguments
        """
    
    def interactive_session(self):
        """Start interactive JTAG session."""

Device Support

Pin Assignments

Standard FTDI JTAG pin assignments:

FT232H:

  • TCK: AD0 (JTAG Clock)
  • TDI: AD1 (Test Data In)
  • TDO: AD2 (Test Data Out)
  • TMS: AD3 (Test Mode Select)

FT2232H/FT4232H:

  • Similar assignments per interface
  • Multiple interfaces allow independent JTAG controllers

Clock Speeds

Supported JTAG frequencies:

  • Standard: 1-6 MHz
  • High speed: Up to 30 MHz (FT232H/FT2232H)
  • Adaptive: Automatic speed adjustment for target compatibility

Standard Instructions

Common JTAG instructions:

  • BYPASS (0xFF): Bypass register access
  • IDCODE (0x01): Device identification
  • SAMPLE (0x02): Boundary scan sample
  • PRELOAD (0x02): Boundary scan preload

Usage Examples

Basic JTAG Operations

from pyftdi.jtag import JtagController

# Configure JTAG controller
jtag = JtagController()
jtag.configure('ftdi:///1', frequency=1E6)

# Reset TAP and go to idle
jtag.reset()
jtag.go_idle()

# Read device IDCODE
jtag.write_ir(0x01)  # IDCODE instruction
idcode = jtag.read_dr(32)  # Read 32-bit IDCODE
print(f"Device IDCODE: 0x{idcode:08x}")

# Clean up
jtag.close()

Chain Discovery

from pyftdi.jtag import JtagController, JtagEngine

jtag = JtagController()
jtag.configure('ftdi:///1')

engine = JtagEngine(jtag)

# Scan JTAG chain
chain_info = engine.scan_chain()
print(f"Chain length: {chain_info['length']}")

# Read all device IDCODEs
idcodes = engine.read_idcodes()
for i, idcode in enumerate(idcodes):
    print(f"Device {i}: IDCODE 0x{idcode:08x}")

# Perform bypass test
if engine.bypass_test():
    print("Bypass test passed")
else:
    print("Bypass test failed")

jtag.close()

Boundary Scan Operations

from pyftdi.jtag import JtagController

jtag = JtagController()
jtag.configure('ftdi:///1')

# Enter boundary scan mode
jtag.reset()
jtag.write_ir(0x02)  # SAMPLE/PRELOAD instruction

# Read boundary scan register
boundary_length = 256  # Device-specific
boundary_data = jtag.read_dr(boundary_length)
print(f"Boundary scan data: 0x{boundary_data:x}")

# Write test pattern to boundary scan
test_pattern = 0xAAAA5555  # Alternating pattern
jtag.write_dr(test_pattern, boundary_length)

jtag.close()

Flash Programming via JTAG

from pyftdi.jtag import JtagController
import time

jtag = JtagController()
jtag.configure('ftdi:///1')

def program_flash_word(address, data):
    """Program single word to flash via JTAG."""
    # Device-specific programming sequence
    
    # Set address
    jtag.write_ir(0x10)  # Address instruction
    jtag.write_dr(address, 32)
    
    # Set data
    jtag.write_ir(0x11)  # Data instruction
    jtag.write_dr(data, 32)
    
    # Program command
    jtag.write_ir(0x12)  # Program instruction
    jtag.write_dr(0x01, 8)  # Program enable
    
    # Wait for completion
    time.sleep(0.001)
    
    # Check status
    jtag.write_ir(0x13)  # Status instruction
    status = jtag.read_dr(8)
    return (status & 0x01) == 0  # Success if bit 0 is clear

# Program flash memory
flash_data = [0x12345678, 0x9ABCDEF0, 0x55AA55AA]
base_address = 0x10000000

for i, data in enumerate(flash_data):
    address = base_address + (i * 4)
    if program_flash_word(address, data):
        print(f"Programmed 0x{data:08x} to 0x{address:08x}")
    else:
        print(f"Failed to program address 0x{address:08x}")

jtag.close()

Debug Access Port (DAP)

from pyftdi.jtag import JtagController

class ArmDapJtag:
    """ARM Debug Access Port via JTAG."""
    
    def __init__(self, jtag_controller):
        self.jtag = jtag_controller
        
    def read_dap_register(self, reg_addr):
        """Read DAP register."""
        # Select DAP
        self.jtag.write_ir(0x0A)  # DPACC instruction
        
        # Read register
        request = (1 << 1) | (reg_addr << 2)  # Read + address
        self.jtag.write_dr(request, 35)
        response = self.jtag.read_dr(35)
        
        return (response >> 3) & 0xFFFFFFFF
    
    def write_dap_register(self, reg_addr, value):
        """Write DAP register."""
        self.jtag.write_ir(0x0A)  # DPACC instruction
        
        # Write register  
        request = (0 << 1) | (reg_addr << 2) | (value << 3)
        self.jtag.write_dr(request, 35)

# Usage
jtag = JtagController()
jtag.configure('ftdi:///1')

dap = ArmDapJtag(jtag)

# Read IDCODE via DAP
idcode = dap.read_dap_register(0x00)
print(f"DAP IDCODE: 0x{idcode:08x}")

jtag.close()

Interactive JTAG Tool

from pyftdi.jtag import JtagTool

# Create interactive JTAG tool
tool = JtagTool('ftdi:///1')

# Execute commands
tool.execute_command('reset')
tool.execute_command('idcode')
tool.execute_command('scan')

# Start interactive session
tool.interactive_session()

Exception Handling

from pyftdi.jtag import JtagController, JtagError
from pyftdi.ftdi import FtdiError

try:
    jtag = JtagController()
    jtag.configure('ftdi:///1')
    
    jtag.reset()
    idcode = jtag.read_dr(32)
    
except JtagError as e:
    print(f"JTAG operation error: {e}")
except FtdiError as e:
    print(f"FTDI device error: {e}")
finally:
    if 'jtag' in locals():
        jtag.close()

Types

# Exception types
class JtagError(Exception):
    """JTAG operation error"""

# TAP states
class JtagState:
    """JTAG TAP state definitions."""
    TEST_LOGIC_RESET = 'test_logic_reset'
    RUN_TEST_IDLE = 'run_test_idle'
    SELECT_DR_SCAN = 'select_dr_scan'
    CAPTURE_DR = 'capture_dr'
    SHIFT_DR = 'shift_dr'
    EXIT1_DR = 'exit1_dr'
    PAUSE_DR = 'pause_dr'
    EXIT2_DR = 'exit2_dr'
    UPDATE_DR = 'update_dr'
    SELECT_IR_SCAN = 'select_ir_scan'
    CAPTURE_IR = 'capture_ir'
    SHIFT_IR = 'shift_ir'
    EXIT1_IR = 'exit1_ir'
    PAUSE_IR = 'pause_ir'
    EXIT2_IR = 'exit2_ir'
    UPDATE_IR = 'update_ir'

# Standard JTAG instructions
JTAG_BYPASS = 0xFF
JTAG_IDCODE = 0x01
JTAG_SAMPLE = 0x02
JTAG_PRELOAD = 0x02

Install with Tessl CLI

npx tessl i tessl/pypi-pyftdi

docs

core-ftdi.md

eeprom.md

gpio.md

i2c.md

index.md

jtag.md

serial.md

spi.md

usb-tools.md

tile.json