1
\$\begingroup\$

I am writing a program for bit-banging a JTAG interface. As far as I understood the JTAG interface, it should act like this:

  • On clock rise, the TDI and TMS inputs are sampled, if Shift-IR or Shift-DR is the current state, the register is shifted with the value of TDI right one place.
  • On clock fall, TDO is set based on the value that fell off the shift register if the current state of the TAP is a Shift state, and then the state of the TAP changes based on the value sampled on TMS.
  • Shifting data in the Shift-IR register only has an effect when navigating to Update-IR, and on Capture-DR the selected register in IR is latched on TDI/TDO and ready for shifting in Shift-DR. If a register has a particular function, the data shifted in Shift-DR will be handled on Update-DR.

So in order to input an instruction, I should navigate the TAP to Shift-IR, then shift my instruction LSB first, then at the same time last bit is shifted in, exit the Shift-IR state. Same for Shift-DR to input or output data.

Here is a sample of the code I'm working with, using a STM32F4 as my bit-banger. jtag_tck() cycles the JTAG clock.

uint8_t jtag_tdi_tms(int32_t tdi_state, uint8_t tms_state)
{
    jtag_set(JTAG_TDI_GPIO_Port, JTAG_TDI_Pin, tdi_state ? GPIO_PIN_SET : GPIO_PIN_RESET);
    jtag_set(JTAG_TMS_GPIO_Port, JTAG_TMS_Pin, tms_state ? GPIO_PIN_SET : GPIO_PIN_RESET);
    jtag_tck();
    return jtag_get(JTAG_TDO_GPIO_Port, JTAG_TDO_Pin);
}
uint32_t idcode()
{
    uint32_t result = 0;
    uint32_t status = 0;

    reset_TAP();

    // Navigate from Test-Logic-Reset to Shift-IR
    jtag_tdi_tms(0, 0);                             // Test-Logic-Reset->Run-Test/Idle

    jtag_tdi_tms(0, 1);                             // Run-Test/Idle->Select-DR Scan
    jtag_tdi_tms(0, 1);                             // Select-DR Scan->Select-IR Scan
    jtag_tdi_tms(0, 0);                             // Select-IR Scan->Capture-IR

    jtag_tdi_tms(0, 0);                             // Capture-IR->Shift-IR (first 1 here, read below)
    // Shift in the instruction: IDCODE = 0x01, LSB first
    status |= jtag_tdi_tms(1, 0)     ;              // Shift-IR->Shift-IR
    status |= jtag_tdi_tms(0, 0) << 1;              // Shift-IR->Shift-IR
    status |= jtag_tdi_tms(0, 0) << 2;              // Shift-IR->Shift-IR
    status |= jtag_tdi_tms(0, 0) << 3;              // Shift-IR->Shift-IR
    status |= jtag_tdi_tms(0, 1) << 4;              // Shift-IR->Exit1-IR
#if DEBUG == 1
    printf("IDCODE IR Status: %lu\n", status);
#endif
    status = 0;
    // End instruction shift

    // Return to Run-Test/Idle
    jtag_tdi_tms(0, 1);                             // Exit1-IR->Update-IR
    jtag_tdi_tms(0, 0);                             // Update-IR->Run-Test/Idle

    // Navigate from Run-Test/Idle to Shift-DR
    jtag_tdi_tms(0, 1);                             // Run-Test/Idle->Select-DR Scan
    jtag_tdi_tms(0, 0);                             // Select-DR Scan->Capture-DR
    jtag_tdi_tms(0, 0);                             // Capture-DR->Shift-DR

    // Shift out the value in the IDCODE register
    for(int i = 0; i < 31; i++)
        result |= (jtag_tdi_tms(0, 0) << i);        // Shift-DR->Shift-DR
    result |= (jtag_tdi_tms(0, 1) << 31);           // Shift-DR->Exit1-DR

    // Return to Run-Test/Idle
    jtag_tdi_tms(0, 1);                             // Exit1-DR->Update-DR
    jtag_tdi_tms(0, 0);                             // Update-DR->Run-Test/Idle

    return result;
}

Here is the result I get on my serial output, which is basically 0x7fffffff:

IDCODE IR Status: 0
2147483647

Beyond this, the documentation for the MCU I'm connected with JTAG on tells me that the output on TDO from shifting in the instruction should look like "p0001", with bits p for "Protected". Except the first 1 comes out as the TAP exits the Capture-IR state and enters the Shift-IR state, and no other 1 is shifted out, which tells me there is no protection issue.

Resetting the TAP to TLR, immediately navigating to Shift-DR and shifting bits out brings the id code as normal.

First: is my understanding of the TAP navigation and data-shifting correct?

Second: Am I missing something on the instruction shifting stage in my code? Or is it that the MCU I'm trying to communicate with is locked somehow?

\$\endgroup\$
3
  • \$\begingroup\$ You'd probably save yourself a lot of trouble comparing to a known good implementation; eg, hack some debug output into openocd or pyocd or run with a cheapie USB logic analyzer watching things... \$\endgroup\$ Commented Nov 12, 2020 at 20:34
  • \$\begingroup\$ Analyzing the signaling should be helpful, I do have a BitScope on hand. I have re-reviewed the documentation and found a timing diagram, and I happened to be right on JTAG timings at least for this device. It also dawned on me that the reason I'm getting all 1s on TDO may be because TDI is always held high for some reason, perhaps from GPIO misconfiguration. I'll investigate that first. \$\endgroup\$ Commented Nov 15, 2020 at 10:51
  • \$\begingroup\$ Turns out, it was a misconfiguration and TDI was held high despite using WritePin to reset it... It was configured as an input instead of an output, and the wrong GPIO port was selected during configuration... I made a mess and posted a useless post. Anyway. I got the idcode function to work. \$\endgroup\$ Commented Nov 16, 2020 at 16:03

1 Answer 1

1
\$\begingroup\$

Turns out, I made a mess in my GPIO configuration. Rectified that, made it work.

Regarding this information I wasn't able to find pretty much anywhere regarding JTAG timings, here's how I decided to run the jtag_tdi_tms instead based on the documentation I have:

uint8_t jtag_tdi_tms(int32_t tdi_state, uint8_t tms_state)
{
    uint8_t result = 0;
    jtag_set(JTAG_TDI_GPIO_Port, JTAG_TDI_Pin, tdi_state);
    jtag_set(JTAG_TMS_GPIO_Port, JTAG_TMS_Pin, tms_state);
    jtag_set(JTAG_TCK_GPIO_Port, JTAG_TCK_Pin, GPIO_PIN_SET);
    result = jtag_get(JTAG_TDO_GPIO_Port, JTAG_TDO_Pin);
    jtag_set(JTAG_TCK_GPIO_Port, JTAG_TCK_Pin, GPIO_PIN_RESET);
    return result;
}

Since TDO changes after a TCK fall, reading TDO during TCK high ensures the result of the previous TCK cycle is in an known state. This coincides with the documentation which says the first 1 on TDO on Shift-IR entry is part of the IR status message.

Regarding shifting bits, the state must be Shift-*R before shifting the first bit in. From Run-Test/Idle to Shift-IR, the sequence on TMS should be [1, 1, 0, 0], then keep TMS low, shift your Instruction in, and right on the last bit, hold TMS high. For instance:

uint32_t idcode()
{
    uint32_t result = 0;
    uint32_t status = 0;

    // Reset the TAP. From this point on, you should just read the
    // idcode from the Shift-DR register, but the point here is to
    // make a working version of a full instruction and
    // data-handling sequence.
    jtag_tdi_tms(0, 1);
    jtag_tdi_tms(0, 1);
    jtag_tdi_tms(0, 1);
    jtag_tdi_tms(0, 1);
    jtag_tdi_tms(0, 1);

    jtag_tdi_tms(0, 0);                             // Test-Logic-Reset->Run-Test/Idle

    // Select the IR Scan path
    jtag_tdi_tms(0, 1);                             // Run-Test/Idle->Select-DR Scan
    jtag_tdi_tms(0, 1);                             // Select-DR Scan->Select-IR Scan
    jtag_tdi_tms(0, 0);                             // Select-IR Scan->Capture-IR
    // In Capture-IR: the IR output value is latched into the shift register
    jtag_tdi_tms(0, 0);                             // Capture-IR->Shift-IR

    // In Shift-IR: The instruction register is shifted by the TCK input
    // Begin shifting instruction: IDCODE = 0x01, LSB first
    status |= jtag_tdi_tms(1, 0);                   // Shift-IR->Shift-IR
    status |= jtag_tdi_tms(0, 0) << 1;              // Shift-IR->Shift-IR
    status |= jtag_tdi_tms(0, 0) << 2;              // Shift-IR->Shift-IR
    status |= jtag_tdi_tms(0, 0) << 3;              // Shift-IR->Shift-IR
    status |= jtag_tdi_tms(0, 1) << 4;              // Shift-IR->Exit1-IR
#if DEBUG == 1
    printf("IDCODE IR Status: %lx\n", status);
    status = 0;
#endif
    // End instruction shift

    // Return to Run-Test/Idle
    jtag_tdi_tms(0, 1);                             // Exit1-IR->Update-IR
    jtag_tdi_tms(0, 0);                             // Update-IR->Run-Test/Idle

    // Select the DR Scan path
    jtag_tdi_tms(0, 1);                             // Run-Test/Idle->Select-DR Scan
    jtag_tdi_tms(1, 0);                             // Select-DR Scan->Capture-DR

    // In Capture-DR: The IDCODE value is latched into the shift register
    jtag_tdi_tms(1, 0);                             // Capture-DR->Shift-DR

    // In Shift-DR: The IDCODE scan chain is shifted by the TCK input.
    for(int i = 0; i < 31; i++)
        result |= (jtag_tdi_tms(i&0x1, 0) << i);    // Shift-DR->Shift-DR
    result |= (jtag_tdi_tms(1, 1) << 31);           // Shift-DR->Exit1-DR

    // Return to Run-Test/Idle
    jtag_tdi_tms(0, 1);                             // Exit1-DR->Update-DR
    jtag_tdi_tms(0, 0);                             // Update-DR->Run-Test/Idle

    return result;
}

Output:

IDCODE IR Status: 1
0x........

where dots are the expected IDCODE.

\$\endgroup\$
0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.