Skip to content

[ISSUE-232] Add JTAG APIs#245

Merged
hkpeprah merged 3 commits intomasterfrom
ford/ISSUE-232/jtag-api
Aug 28, 2025
Merged

[ISSUE-232] Add JTAG APIs#245
hkpeprah merged 3 commits intomasterfrom
ford/ISSUE-232/jtag-api

Conversation

@hkpeprah
Copy link
Contributor

@hkpeprah hkpeprah commented Aug 20, 2025

[ISSUE-232] Add JTAG APIs

Overview

This PR adds JTAG APIs for executing instructions and sending data directly to devices over the JTAG scan chain. Several new APIs have been added:

  • jtag_store_instruction - To send an instruction to the JTAG device.
  • jtag_store_data - To send data to the JTAG device (TDO).
  • jtag_get_device_info - To get information about a JTAG device on the scan chain.
  • jtag_read / jtag_read{8,16,32} - To read data from a JTAG device (from the input buffer / TDI).
  • jtag_sync_bits / jtag_sync_bytes - To force a flush of the output buffer.

By default, instructions and data are stored in an internal buffer and only sent to the JTAG device when a read is performed or an explicit flush is done. This is done internally by the debugger to increase performance (read: speed).

Summary of Changes

  1. Added new JTAG APIs.
  2. Updated unit tests.
  3. Added JTAG device info structure.

Related Issue

#232

This patch adds JTAG APIs for executing instructions and sending data directly
to devices over the JTAG scan chain.
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR implements JTAG APIs for executing instructions and sending data directly to devices over the JTAG scan chain. The implementation includes buffered operations for performance optimization, where instructions and data are stored internally and only transmitted when explicitly flushed or when data is read.

  • Adds 8 new JTAG API methods for instruction storage, data transfer, device information retrieval, and buffer synchronization
  • Introduces JLinkJTAGDeviceInfo structure to represent JTAG device information
  • Provides comprehensive unit test coverage for all new JTAG functionality

Reviewed Changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.

File Description
pylink/jlink.py Implements the core JTAG API methods with proper decorators and documentation
pylink/structs.py Defines JLinkJTAGDeviceInfo structure for JTAG device information
tests/unit/test_jlink.py Adds unit tests for all new JTAG API methods

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

self.jlink.jtag_flush()
self.dll.JLINKARM_WriteBits.assert_called_once()

def test_jlijnk_jtag_store_instruction(self):
Copy link

Copilot AI Aug 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function name has a typo: 'jlijnk' should be 'jlink'.

Suggested change
def test_jlijnk_jtag_store_instruction(self):
def test_jlink_jtag_store_instruction(self):

Copilot uses AI. Check for mistakes.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree with Copilot - this is a typo. @hkpeprah

self.jlink.jtag_store_data(tdi, 5)

buf, num_bits = self.dll.JLINKARM_JTAG_StoreData.call_args[0]
self.assertEqual(10, num_bits)
Copy link

Copilot AI Aug 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The assertion expects 10 bits but the test calls jtag_store_data(tdi, 5) with 5 bits per element. With 2 elements, this should be 10 total bits, but the actual implementation multiplies len(data) * num_bits which would be 2 * 5 = 10. However, the test should verify the correct expected value based on the API contract.

Suggested change
self.assertEqual(10, num_bits)
expected_num_bits = len(tdi) * 5
self.assertEqual(expected_num_bits, num_bits)

Copilot uses AI. Check for mistakes.
pylink/jlink.py Outdated
if isinstance(buf, list):
buf = bytes(buf)

return self._dll.JLINKARM_JTAG_StoreData(buf, len(data) * num_bits)
Copy link

Copilot AI Aug 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The calculation len(data) * num_bits assumes num_bits is per element, but the docstring states 'number of bits to transfer per integer in data'. This creates confusion about whether num_bits is total bits or bits per element. The implementation should be clarified or the documentation updated.

Copilot uses AI. Check for mistakes.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see what copilot is getting at here. num_bits here is really more accurately expressed as dr_length

pylink/jlink.py Outdated
# return two integers, but that is fine, so the caller ultimately knows
# the data length they need.
buf_size = num_bits // 4
if (buf_size % 4) > 0:
Copy link

Copilot AI Aug 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The condition (buf_size % 4) > 0 is incorrect. It should be (num_bits % 4) > 0 to check if there are remaining bits that don't fit evenly into 4-bit chunks.

Suggested change
if (buf_size % 4) > 0:
if (num_bits % 4) > 0:

Copilot uses AI. Check for mistakes.

@interface_required(enums.JLinkInterfaces.JTAG)
@open_required
def jtag_get_device_info(self, index=0):

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function takes index as an input to determine which TAP on the JTAG chain to query - this makes perfect sense to me. How do the jtag_store_instruction and jtag_store_data functions determine which JTAG TAP to access? I.e. if I have a JTAG chain with, say, three taps on it how do I specify which tap to access?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You would have to use jtag_configure():

# Open connection.
jl.open()

# Total length of all instruction registers before the desired JTAG device.
# (e.g. 4 if selecting the second device and length of the IR of the previous device is 4).
ir_len = ...

# Total number of data register bits from devices that are between the
# JTAG controller's TDI pin and the JTAG device you're trying to access.
dr_len = ...

# Select the device based on the IR and DR lengths.
jl.jtag_configure(ir_len, dr_len)

# Will return the device information for the selected device.
jl.jtag_get_device_info(...)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK - fantastic - that makes sense. Thanks

Bit position in input buffer after instruction transmission.
"""
buf = data
if isinstance(buf, list):
Copy link

@ksigurdsson ksigurdsson Aug 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If someone didn't read the doctstring and simply passed in an int for data then this if statement wouldn't execute and then the subsequent call to self._dll.JLINKARM_JTAG_StoreData would fail because you can't call len() on an int.

Is it worth adding an else to the if isinstance(buf, list): to raise if buf is not a list?

Copy link

@ksigurdsson ksigurdsson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @hkpeprah thanks for doing this. I've done some testing and it looks good. I'd be very supportive of merging in this PR. Many thanks!

self.assertEqual(0x1337, info.DeviceId)
self.assertEqual(0x1, info.IRLen)
self.assertEqual(0x2, info.IRPrint)
self.assertEqual("Silk Song", info.name)
Copy link

@ksigurdsson ksigurdsson Aug 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should info.name actually be info.sName? @hkpeprah

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.name is the property that converts the ctype string pointer to a Python string.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh yes - got it. Thanks

pylink/jlink.py Outdated
return self._dll.JLINKARM_JTAG_StoreData(buf, len(data) * dr_len)

@interface_required(enums.JLinkInterfaces.JTAG)
@open_required

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've been testing some more and I think the JLINKARM_JTAG_GetDeviceInfo function requires a connection to be made. I think the @open_required decorator here needs to be replaced with @connection_required. @hkpeprah

Copy link
Contributor Author

@hkpeprah hkpeprah Aug 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Switch decorator to connection_required

@hkpeprah hkpeprah marked this pull request as ready for review August 27, 2025 21:20
bbrown1867
bbrown1867 previously approved these changes Aug 27, 2025
@hkpeprah hkpeprah merged commit c8810ec into master Aug 28, 2025
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants