Electronic – How does the JTAG TAP react to clock cycling, and why am I unable to get an instruction through

bit-bangcjtagstate-machines

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?

Best Answer

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.

Related Topic