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.

LAUNCHXL-F28379D: Atomic operation in a C-program

Part Number: LAUNCHXL-F28379D


Hello experts,


as an example I have a C-program.


int8_t var_8;
int16_t var_16;
int32_t var_32;
int64_t var_64;
float32_t var_f32;
float64_t var_f64;

#pragma INTERRUPT(Interrupt_Routine)
void Interrupt_Routine(void)
{
//Variables var_... are modified here

//var_ = var_8, var_16, ....

...
}


main(void)
{
//Variables var_... are used here but not modified
//Use in such commands as
//  ... = var_... + ...;
//  if(var_... >= 0)

...
}


The C compiler translates the C syntaxes into assembler instructions.
As long as var_... stands as a whole in an assembler instruction, the validity of the variable value is guaranteed, because the assembler instruction with 1 CYC cannot be interrupted by interrupt. The assembler instruction is atomic.


My question:
Is the use of the variable var_... in main() always safe? Or can it happen this way that the compiler does not treat a variable as whole.


For example:


C-syntax: tmp1_64 = var_64 + 10LL;
Compiler translates the C-syntax to:
Assembler_Instruction_1: (tmp1_64)_32_bit_Lowpart = (var_64)_32_bit_Lowpart + 10L;
Assembler_Instruction_2: (tmp1_64)_32_bit_Highpart = (var_64)_32_bit_Highpart + Carry_Flag;


Between Assembler_Instruction_1 and Assembler_Instruction_2 an interrupt can come and we then have to deal with an invalid value of var_64.
The example can also be used for other variables like var_32, var_16, var_f64, ... as well as extend for other C syntaxes.

Thanks in advance - Bui



  • The section on "Atomic Access" in the C28x Optimization Guide should answer your question: software-dl.ti.com/.../common_optimization_issues.html

  • Good morning Ajay,


    thank you very much for the quick answer.
    My question is not about writing but about reading a global variable.


    Another example:
    volatile int16_t var_16;
    volatile int32_t var_32;
    volatile float32_t var_f32;
    volatile float64_t var_f64;


    #pragma INTERRUPT(Interrupt_Routine)
    void Interrupt_Routine(void)
    {
       //Variables var_... are modified here
       //var_... = var_16, var_32, ....

    ...
    }


    void main(void)
    {
       int16_t localvar_16;
       int32_t localvar_32;
       float32_t localvar_f32;
       float64_t localvar_f64;

       //Do the local variables have a valid value or not?
       localvar_16 = var_16; //ok or not?
       localvar_32 = var_32; //ok or not?
       localvar_f32 = var_f32; //ok or not?
       localvar_f64 = var_64; //ok or not?

       //Does the same rule apply for reading as for writing?

        
       localvar_16 = var_16; //ok
       localvar_32 = var_32; //ok
       localvar_f32 = var_f32; //ok


       uint16_t val = __disable_interrupts();
       localvar_f64 = var_64; //ok
       if (0U == (val & 0x1))
      __enable_interrupts();

    }

    Regards - Bui

  • Reads of variables 32b or smaller are also atomic.

  • Hello GregM,

    thank you for the link!
    But when I read the answer from Lori, I am really confused. There is a contradiction between the answers of Lori and Ajay
    Lori:

    Ajay and TI documentation

    https://software-dl.ti.com/C2000/docs/optimization_guide/common_optimization_issues.html#atomic-access.

    who is right?

    Regards - Bui

  • Bui, You are correct. The C28 Optimization Guide is being updated.  Lori's link has the correct behavior

  • Hello GregM,

    thank you very much for the quick feedback!
    However, with the best will in the world, I can't imagine that Lori is right. If it were, I would have to lock the interrupt every time I access any global variable in main() (see the example below).

    volatile int16_t var_16;
    volatile int32_t var_32;
    volatile float32_t var_f32;
    volatile float64_t var_f64;
    #pragma INTERRUPT(Interrupt_Routine)
    void Interrupt_Routine(void)
    {
         //Variables var_... are modified here
         //var_... = var_16, var_32, ....

    ...
    }

    void main(void)
    {
         int16_t localvar_16;
         int32_t localvar_32;
         float32_t localvar_f32;
         float64_t localvar_f64;

         //Access to any variable
        uint16_t val = __disable_interrupts();


             localvar_... = var_...; // _... = _16 oder _32 oder oder _f32 oder _f64


        if (0U == (val & 0x1))
        __enable_interrupts();
    }

    It would then be a huge overhead. And compared to microcontrollers and compilers of other manufacturers it is a very big disadvantage. I must emphasize once again. I can't imagine that Lori is right.
    What do you say? Correct me and please excuse me if I am wrong!

    Regards - Bui

  • Hi Bui,

    I have assigned this thread to Lori for further comment. Due to Thanksgiving holiday, please expect response by next week.

    Regards, Santosh

  • Bui,

    From a C28x architecture standpoint:

    • The pipeline is: F1, F2, D1, D2, R1, R2, E, W
    • Once an instruction makes it to D2 (decode2) it will always run to completion. 

    That is, when an interrupt comes in:

    • Any opcode in D2 + continues through the pipeline. 
    • Any opcode in F1, F2, and D1 is dropped and re-fetched after the interrupt returns

    This issue only applies to an access involving multiple opcodes. I discussed with Greg (compiler expert) and we came to this summary:

    1. 16-bit datatypes: load/store are one opcode – no issue.
    2. float32 datatypes: load/stores are one opcode – no issue.
    3. int32/uint32: it is possible this will be broken into two 16-bit accesses by the compiler. It is possible one can be in D2 while the other is in D1 when an interrupt is taken.  

    Regards

    Lori

  • int32/uint32: it is possible this will be broken into two 16-bit accesses by the compiler. It is possible one can be in D2 while the other is in D1 when an interrupt is taken.  

    The Bui question is a really important question, so I also have a follow up question regarding point 3.
    In what scenarios the int32/uint32 accesses can be broken into two 16-bit accesses by the compiler? What measures can be taken to be absolutely sure that those accesses are atomic? In the end I don't want to look at the assembly code every build just to make sure.

    Regards,
    Andy

  • Hi Lori,

    thank for the feedback.

    Exactly the question from AndyP I would also like to ask you and the compiler team.

    Regards - Bui

  • Andy and Bui,

    There are two recommended approaches:

    1. Use an atomic compiler intrinsic if one is available.
      1. These are documented in the compiler user’s guide (www.ti.com/lit/SPRU514). The description will say "in an atomic way".
    2. Disable / enable interrupts around load/stores that must be atomic. There are intrinsics to assist:
      1. __disable_interrupts( );
      2. __enable_interrupts( );

    Additional notes:

    • Atomic accesses within an ISR: By default, accesses within an ISR are atomic. The INTM bit is automatically set (disable interrupts) by the hardware during the context switch. The exception would be if the application re-enables interrupts within the ISR in order to nest interrupts.
    • I discussed creating a list, as you requested, with Greg. This approach would not be practical, or as robust, as the methods listed above.
    • Examining assembly is possible, of course. However it also doesn’t seem practical. The above two methods are recommended. There are a number of E2E posts recommending the same.
    • It may make sense to group atomic accesses together, if possible. Or perhaps create a function to perform the sequence disable/access/enable.

    Best regards,

    Lori & Greg

  • Thanks Lori & Greg for the explanation.

    I've reviewed the compiler intrinsics you mention. I wonder what is the rationale for intrinsics that operates on int variables, like for example

    void __add( int * m , int b );
    void __dec( int * m );

    when you have previously said that the 16-bit datatypes accesses are always atomic. Do we really need an intrisic to do something like this:
    uint16_t var;
    var++;     // <- atomic or not?

    And can you give me a real example where this operation:
    uint32_t var;
    var++;      // <- atomic or not?

    can be broken into non-atomic operation?

    I feel a bit disoriented.

    Regards,
    Andy

  • Andy,

    16-bit reads and writes are atomic.

    For other operations, from C source code, the only way to guarantee using the other atomic C28 instructions (ADD, ADDL, AND, OR, DEC, INC) for all compiler option combinations and all contexts is to use the intrinsics.

    For var16++, with some compiler options and surrounding context, the compiler will generate the INC instruction, however it’s not guaranteed.  

    For var32++, there is no 32-bit equivalent to the atomic 16-bit INC instruction.

    Regards,
    Greg

  • Ok, thank you for explanation and your support.

    Regards,
    Andy

  • Hi Lori & GregM,

    thank for the detailed explanation.

    I hope TI will update  "The C28 Optimization Guide" as soon as possible.

    Have a nice weekend! - Bui