This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

AM2432: AM243x TSN PTP PPS signal (cycle time) behavior

Part Number: AM2432

Hi,

I was trying to change the PPS signal frequnecy (cycle time) on the R5F Core 0 of an AM243x EVM for the PRU ICSSG.

I modified the value of ICSSG_IEP_DFLT_CYCLE_TIME_NSECS from 1000000 to 25000. The PPS signal frequency was set to 40kHz as expected. However, the PTP follower did not sync to the grandmaster clock anymore.

Questions:

  1. What else needs to be modified in order for the PTP follower to sync to the grandmaster clock?
  2. Why is changing PPS signal frequency affects the clock sync between the PTP grandmaster and followers?

Thank you.

  • Hi Matt,

    As mentioned in the previous thread:

    https://e2e.ti.com/support/processors-group/processors/f/processors-forum/1575233/am6442-am64x-or-am243x-tsn-application-questions/6069565?tisearch=e2e-sitesearch&keymatch=PPS%252520AM243x#

    1. Why is changing PPS signal frequency affects the clock sync between the PTP grandmaster and followers?

    I would not recommend modifying ICSSG_IEP_DFLT_CYCLE_TIME_NSECS as it directly impacts our 64-bit timestamp calculations.

    We use IEP0 timer 32-bit shadow mode. The Icssg_convertTs function reconstructs a full 64-bit nanosecond timestamp by counting how many 1ms cycles have elapsed and multiplying by cycleTimeNs (1,000,000 ns), then adding the sub-cycle offset. If you modify cycleTimeNs while the hardware IEP still rolls over at 1ms intervals, the multiplication becomes incorrect. This causes all 64-bit timestamps to be miscalculated, resulting in wrong PTP clock offset and drift measurements—ultimately breaking synchronization between the grandmaster and followers.

    BR
    JC

  • Hi JC,

    Thank you for the explanation.

    Two follow-up questions I have:

    1. I understand you do not recommend changing the cycle time directly. In this case, what is the proper way you recommend to achieve PTP grandmaster and followers to execute commands in sync (based on the grandmaster clock) on a fixed rate, say every 25 micro seconds?

    I would think this is a typical TSN PTP application. There must be a standard approach TI recommends how customers can achieve this.

    2. Can you please further elaborate on what you said "If you modify cycleTimeNs while the hardware IEP still rolls over at 1ms intervals..."?

    Do you mean Icssg_convertTs() is called on a hard-coded fixed rate (1ms), or depending on other PTP endpoint's request (1ms)? And this rate is not changeable on the R5F core 0?

    Or the "IEP hardware" in "the hardware IEP still rolls over at 1ms intervals" means something else?

    Because I did find that in Icssg_convertTs(), cycleTimeNs is a parameter passed by Icssg_Cfg struct. And this value should be preserved when ICSSG_IEP_DFLT_CYCLE_TIME_NSECS is modified.

    uint64_t Icssg_convertTs(EnetPer_Handle hPer,
                             uint64_t ts)
    {
        /* ... */
        uint32_t cycleTimeNs = hIcssg->cycleTimeNs;
        /* ... */
        
        if(hIcssg->clockTypeFw == ICSSG_TIMESYNC_CLKTYPE_SYSTEM_TIME)
        {
            /* ... */
        }
        else
        {
            /* ... */
            ns = ns * cycleTimeNs + iepCntLo;
        }
        
        return ns;
    }

    Thank you very much for your help!

  • Hi Matt,

    1. I understand you do not recommend changing the cycle time directly. In this case, what is the proper way you recommend to achieve PTP grandmaster and followers to execute commands in sync (based on the grandmaster clock) on a fixed rate, say every 25 micro seconds?

    Could you please clarify what you mean by 'execute commands in sync'?

    Can you please further elaborate on what you said "If you modify cycleTimeNs while the hardware IEP still rolls over at 1ms intervals..."?

    Do you mean Icssg_convertTs() is called on a hard-coded fixed rate (1ms), or depending on other PTP endpoint's request (1ms)? And this rate is not changeable on the R5F core 0?

    Or the "IEP hardware" in "the hardware IEP still rolls over at 1ms intervals" means something else?

    To clarify, Icssg_convertTs() is a timestamp conversion function - it converts the hardware timestamp format to a 64-bit timestamp that software can interpret. This is not related to the PTP SYNC interval. The ICSSG IEP counter operates in a relative timestamp mode, where the IEP counter rolls over at a configured interval (default 1 ms). The software requires an absolute 64-bit timestamp for PTP operations. Icssg_convertTs() performs this relative-to-absolute conversion. The cycleTimeNs parameter tells the conversion function what the hardware rollover interval is, so it can correctly calculate the absolute timestamp. If this parameter doesn't match the actual hardware IEP rollover configuration, the converted timestamps will be incorrect.

    This is also the exact reason why we set the hardware timestamp in a relative format using the ICSSG setClockTime API - Note: The PRU firmware uses the same logic for packet timestamping. If you modify the cycle time in the IEP register, packet timestamping will become unreliable in PRU firmware, this is the reason for follower and master goes out of SYNC (as you mentioned above)

    BR
    JC

  • Hi JC,

    For your question "Could you please clarify what you mean by 'execute commands in sync'?",
    For example, we have multiple devices on a TSN network. And we would like to have each of these TSN-enabled device generates a pulse at the same time (within 50ns). This pulse triggers hardware to load a DAC value. Each device generates this pulse every 25 microsecond.
    After the first pulse occurs, each device loads its next DAC value to the hardware, say via SPI. This completes the first round. Then the second pulse occurs that starts the second round. The same process repeats.

    So "execute commands" here means generating periodic pulses and loading DAC values; "in sync" means the periodic pulses generated by all the devices need to happen at the same time (within 50ns). That is why we would like to leverage the TSN sync_out0 to use it as the 25 microsecond periodic pulses.
    Hope this clarifes my #1 question.

    For #2, I think I am still confused.
    I understand the idea of different rollover intervals don't work. But can you please clarify: On what components the rollover interval are configured?
    I know one is IEP0 CMP0 register (cycTimeNs I changed), which is on PRU. You mentioned "If this parameter doesn't match the actual hardware IEP rollover configuration, the converted timestamps will be incorrect.". I thought IEP0 CMP0 is the IEP rollover configuration. Or there is another rollover configuration?


    Also, in Icssg_convertTs(), is this statement to read the hardware timestamp from Ethernet firmware? Does this Ethernet timestamp also rollover every 1ms and not configurable?

    swHi = Icssg_rd32(hIcssg, sharedRam + TIMESYNC_FW_WC_COUNT_HI_SW_OFFSET_OFFSET);


    Thank you for your help!

  • Hi Matt, 


    So "execute commands" here means generating periodic pulses and loading DAC values; "in sync" means the periodic pulses generated by all the devices need to happen at the same time (within 50ns). That is why we would like to leverage the TSN sync_out0 to use it as the 25 microsecond periodic pulses.
    Hope this clarifes my #1 question.

    Thanks this clarified my question. 

    I understand the idea of different rollover intervals don't work. But can you please clarify: On what components the rollover interval are configured?
    I know one is IEP0 CMP0 register (cycTimeNs I changed), which is on PRU. You mentioned "If this parameter doesn't match the actual hardware IEP rollover configuration, the converted timestamps will be incorrect.". I thought IEP0 CMP0 is the IEP rollover configuration. Or there is another rollover configuration?


    Also, in Icssg_convertTs(), is this statement to read the hardware timestamp from Ethernet firmware? Does this Ethernet timestamp also rollover every 1ms and not configurable?

    I will try to help you here in providing more details:

    The IEP timer in shadow mode uses a two-level rollover mechanism to maintain a 64-bit nanosecond timestamp.

    Level 1: IEP Low Register (COUNT_REG0)
    The low register is a 32-bit counter that counts nanoseconds. When it matches the CMP0 compare value (configured for 1ms cycle), the hardware automatically resets it to zero and increments the high register (COUNT_REG1). This is managed entirely by hardware.

    Level 2: IEP High Register (COUNT_REG1)
    The high register counts the number of CMP0 reset events (i.e., milliseconds). Per TRM, this is a 24-bit counter that stops at 0xFF_FFFF and does not wrap. Software must clear it before this threshold is reached. The firmware uses only 23 bits (mask 0x007FFFFF) to detect overflow before hardware saturation. When bit 23 overflows, firmware increments the rollover counter stored at TIMESYNC_FW_WC_HI_ROLLOVER_COUNT_OFFSET and clears COUNT_REG1. 

    Shared Memory Offsets
    TIMESYNC_FW_WC_HI_ROLLOVER_COUNT_OFFSET: Stores the COUNT_REG1 rollover count, incremented each time the 23-bit overflow occurs.
    TIMESYNC_FW_WC_COUNT_HI_SW_OFFSET_OFFSET: Software offset adjustment, applicable only during set clock operations.
    TIMESYNC_CYCLE_EXTN_TIME: Accumulated cycle extension adjustments over time.


    Read Algorithm
    To ensure consistent timestamp reads without race conditions, the code uses a double-read verification pattern. It reads IEP High, rollover count, and IEP Low, then reads IEP High and rollover count again. If both readings match, the timestamp is valid; otherwise, the process repeats. This ensures no rollover occurred between reading the high and low portions.

    Also, in Icssg_convertTs(), is this statement to read the hardware timestamp from Ethernet firmware? Does this Ethernet timestamp also rollover every 1ms and not configurable?

    No, TIMESYNC_FW_WC_COUNT_HI_SW_OFFSET_OFFSET does not rollover. This is not a continuously incrementing counter but rather a saved copy of the IEP COUNT_REG1 value that is captured during set clock operations. During a set clock operation, before the firmware resets IEP COUNT_REG1 to zero, it saves the current COUNT_REG1 value to TIMESYNC_FW_WC_COUNT_HI_SW_OFFSET_OFFSET. This preserves the accumulated time offset so that timestamp continuity is maintained after the reset. The software offset is then added to the current COUNT_REG1 reading during timestamp calculations to maintain accurate timekeeping across set/get clock operations.

    BR
    JC

  • Hi JC,

    Thank you for explaining further details. We know changing the PPS is not feasible.

    In this case, can you please help answer my previous question:

    1. I understand you do not recommend changing the cycle time directly. In this case, what is the proper way you recommend to achieve PTP grandmaster and followers to execute commands in sync (based on the grandmaster clock) on a fixed rate, say every 25 micro seconds?

    I would think this is a typical TSN PTP application. There must be a standard approach TI recommends how customers can achieve this.

    Thank you!

  • 1. I understand you do not recommend changing the cycle time directly. In this case, what is the proper way you recommend to achieve PTP grandmaster and followers to execute commands in sync (based on the grandmaster clock) on a fixed rate, say every 25 micro seconds?

    Happy new year. Apologize for delayed response, I was on vacation last week and need to catch up with my emails this week. 

    For achieving synchronized command execution between PTP grandmaster and followers at a fixed rate (e.g., every 25 microseconds), the recommended approach is to utilize the IEP0 Compare Event mechanism.

    Recommended Implementation:

    • Configure IEP0 CMP Event – Use an available compare register (e.g., CMP8) to generate an event every 25µs, mapped to a host interrupt.
    • Map Host Interrupt to Application Task – The host interrupt triggers your application task for synchronized execution.

    Application Handling:

    • Clear the IEP0 CMP8 status upon interrupt
    • Reprogram CMP8 with the next future timestamp (current + 25µs, then + 50µs, etc.)
    • Continue this pattern until reaching the 1ms boundary
    • Handle IEP0 Rollover – Since IEP0 CMP0 rolls over every 1ms (cycle time), reprogram IEP0 CMP8 register back to the appropriate offset (relative to 0) when crossing the 1ms boundary. CMP8 is unused. 

    This approach leverages the PTP-synchronized IEP timer for precise, clock-aligned execution across all nodes without modifying the underlying cycle time, ensuring deterministic behavior while maintaining standard gPTP operation.

    BR
    JC

  • Happy New Year, JC!  Thank you for the suggestion. This aligns with what I was thinking to try. I will try it out and let you know.

    Thank you.

  • Hi JC,

    I was able to get a roughly correct number of interrupts using CMP8. Now I would like to drive a digital output signal simultaneously (like sync_out0) when the CMP8 compare event occurs. Can you please describe how to do this?

    Thank you for your help!

  • Hi Matt,

    Driving CMP8 to a digital output signal directly via hardware is challenging.

    Would either of these alternatives work for you?

    - Host-driven approach: Route the signal to the host application, which then triggers the GPIO - application latency is a concern here.

    - Low-latency approach: Run a GPIO toggle application on another ICSSG instance on the AM243x.


    Let me know which option suits your requirements, especially if latency is a concern.

    BR
    JC

  • Hi JC,

    The important requirement of our application is all these nodes on the TSN network should generate their digital output signals at exact same time. Latency is allowed if it is not much.

    Questions about the two approaches you mentioned:

    Host-driven approach: Do you mean software controls the GPIO in the interrupt service routine? If not, please explain.

    Low-latency approach: If a GPIO is done on another ICSSG instance, are these digital output signals triggered at the same time? How does this work?

    Please advise.

    Thank you!

  • Host-driven approach: Do you mean software controls the GPIO in the interrupt service routine? If not, please explain.

    Correct, so latency depends on RTOS/HLOS running. So, this will be in the order 1us for the highest priority IRQ with 800MHz R5F, may can optimize down to 500ns or so by heavy customization. GPIO toggle itself has 100-200ns jitter due to interconnect latencies

    Low-latency approach: If a GPIO is done on another ICSSG instance, are these digital output signals triggered at the same time? How does this work?

    Please advise.

    This is by second ICSSG PRU polling CMP status (could be routed to ICSSG INTC) and toggling GPO (low latency 3 ns jitter), so here < 100ns might be possible. 

  • Hi Pratheesh,

    Thank you for the answer.

    For the low-latency approach, "the second ICSSG PRU CMP status polling" means modifying the PRU firmware, not the R5F code, correct?

    Can you please explain this in further details?

    Thank you!

  • Hi Matt, 

    Apologize, your thread got lost from radar.

    That's correct. Polling the IEP CMP status requires custom firmware running on PRU_ICSSGx. By default, the MCU+ SDK example runs PRU Ethernet on ICSSG1, leaving ICSSG0 free for custom use cases.

    A systematic approach for this would be:

    Use ICSSG0 to poll the ICSSG1 IEP0 CMP status registers

    • Monitor CMP8 status:
    • Read IEP_CMP_STATUS_REG and check bit 8 (CMP_STATUS[8])
    • If set, CMP8 event has occurred (counter matched IEP_CMP8_REG0/REG1 value)
    • Clear the status by writing 0x100 (bit 8) to IEP_CMP_STATUS_REG
    • Toggle GPIO

    BR
    JC

  • Hi JC,

    I just had a chance to implement the host-driven approach, using TEST_LED2 GPIO. The GPIO is toggled inside the 25 micro-second interrupt triggered by CMP8 compare event.

    The results were a little confusing:
    I can see the GPIO signal toggles every 25 micro seconds on a oscilloscope. However, there is a brief 1 milli-second envelope that overlays with the 25 micro-second waveform sometimes. It almost looks like the CMP8 event happens every 25 micro seconds most of the time. But sometimes, it happens every 1 milli-second. The jitters between PTP grandmaster and slave are too large to be meaningful in this case.

    With the same code logic, if I change the interrupt interval from 25 to 50 micro-seconds, the envelope seems to disappear. The jitters between PTP grandmaster and slave are in around 10 micro-second range.

    Here is the skeleton of my test code:

    uintptr_t iep0Regs;
    uint32_t cmpValue = 50000;
    EnetPer_Handle hPer = Enet_getPerHandle(gEnetAppCfg.perCtxt[0].hEnet);
    Icssg_Handle hIcssg = (Icssg_Handle)hPer;
    uintptr_t iep0Regs = (uintptr_t)hIcssg->hTimeSync->virtAddr;
    bool gpioSetHigh = false;
    
    SetupIEP0Compare(hIcssg, cmpValue);
    SetupInterrupts(hIcssg->pruss->hPruss);


    The ISR:
    void my_handler()
    {
        if(HWREG(iep0Regs + CSL_ICSS_PR1_IEP0_SLV_CMP_STATUS_REG) & (1 << 8))   /* CMP8 */
        {
            HWREG(iep0Regs + CSL_ICSS_PR1_IEP0_SLV_CMP_STATUS_REG) &= ~(1 << 8);
    
            if(cmpValue != 1000000)     // IEP counter 1ms
            {
                cmpValue += 50000;
            }
            else
            {
                cmpValue = 50000;
            }
            SetupIEP0Compare(hIcssg, cmpValue);
            
            if(gpioSetHigh)
            {
                GPIO_pinWriteHigh(gpioBaseAddr, pinNum);
            }
            else
            {
                GPIO_pinWriteLow(gpioBaseAddr, pinNum);
            }
            
            gpioSetHigh = !gpioSetHigh;
            my_count++;        
        
            PRUICSS_clearEvent(gPruIcssg1Handle, 7);           /* INTC event signal 7: pr1_iep_tim_cap_cmp_pend */                        
        }       
    }
    


    Two functions:

    void SetupIEP0Compare(Icssg_Handle hIcssg, uint32_t sliceTimeNs)
    {
        uintptr_t baseAddr = (uintptr_t)hIcssg->enetPer.virtAddr;
    
        /* Set IEP0 CMP8 register */
        Icssg_wr32(hIcssg, baseAddr + CSL_ICSS_G_PR1_IEP0_SLV_REGS_BASE + CSL_ICSS_G_PR1_IEP0_SLV_CMP8_REG0, (sliceTimeNs - 4));
        Icssg_wr32(hIcssg, baseAddr + CSL_ICSS_G_PR1_IEP0_SLV_REGS_BASE + CSL_ICSS_G_PR1_IEP0_SLV_CMP8_REG1, (sliceTimeNs - 4));
    }
    
    void SetupInterrupts(PRUICSS_Handle hPruss)
    {
        PRUICSS_registerIrqHandler(hPruss,
                                   7,                      /* INTC event signal 7: pr1_iep_tim_cap_cmp_pend */
                                   CSLR_R5FSS0_CORE0_INTR_PRU_ICSSG1_PR1_HOST_INTR_PEND_5,      /* Host interrupt 7 - 2 */
                                   1,                      /*  eventNum     */
                                   1,                      /*  wait_enable  */
                                   my_handler);    
        HwiP_disableInt(CSLR_R5FSS0_CORE0_INTR_PRU_ICSSG1_PR1_HOST_INTR_PEND_5);
        uint32_t status = PRUICSS_intcInit(hPruss, &icss1_intc_initdata);
        
        HwiP_enableInt(CSLR_R5FSS0_CORE0_INTR_PRU_ICSSG1_PR1_HOST_INTR_PEND_5);
    }
    


    Questions:
    1. Does the implementation look correct? If yes, what may cause this 1 micro-second envelope and how to eliminate it?
        Note: cmpValue is the variable to control the INT interval. With a cmpValue of 50000 (50 micro-seconds), the 1 milli-second envelope seems to disappear. 

        (Given the fact the 50 micro-second interval INT seems to be fine, I suspected the interrupt was not fast enough to update the CMP8 registers before the next one sometimes. However, I measured the time spent in the ISR is around 1.2 micro second. Also, this does not explain why the envelope is about 1 milli-second. So I wonder if you have any insights on this...)


    2. If the answer to #1 is no, what was the mistake?


    Thank you for your help!