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.

MSP430FR2433: Errata CPU40 Impacts GCC depending on first item in data. (Invalid code generated if global variable has some values)

Part Number: MSP430FR2433
Other Parts Discussed in Thread: MSP430-GCC-OPENSOURCE,

There is a seemingly strange behavior with the MSP430-GCC-OPENSOURCE compiler regarding global variables (sometimes).

The example project attached is a simplified example from the codebase I've been working with. In the file "twi.c" there is a global variable called "myvar". When initializing this variable with some values (0x0040, 0x0140, 0x0240, ... 0x0F40) the generated program is "broken".

Broken means (determined using debugger):

  • The program never executes to main
  • The program reaches __crt0_start as expected
  • The program makes it to the call instruction in __crt0_init_bss
  • The program then ends up at address 0x0004 (PC = 0x0004) which is a jump back to itself
  • Between the call and first arrival at 0x0004 there are 516, 095 clock cycles (not instant jump)

Confusingly, the watchdog ISR accessing a global variable (see "wiring.c") is also required for the code to be "broken".

Steps to reproduce (use BugExample.zip)

  • Import BugExample project into CCS
  • Make sure the watchdog isr increments "wdt_overflow_count" in wiring.c
  • Make sure a "broken" value is assigned to "myvar" in twi.c (see comments for broken and working values)
  • Clean & build project
  • Start a debug session
  • Observe that the program never makes it to main

The program will work as intended if "myvar" is not one of the broken values or if the wdt isr does not increment (access?) "wdt_overflow_count".

This does not happen with the same program ported to the TI compiler (also attached as BugExampleTI.zip)

BugExample.zipBugExampleTI.zip

  • Changing the value for an initialized variable will do nothing to the generated code. The call to memcopy() that comes after the call to memset() (to clear the bss) remains the same. Only the data copied changes.

  • My initial description was a little inaccurate. The programs being generated are in fact the same except for the value in the data section (based on a diff of the disassembly with objdump -DS).

    However, when the data section contains one of the "bad" values memset seemingly breaks. The call to memset in __crt0_init_bss never returns. After about 500,000 clock cycles the PC ends up with a value of 4. Seemingly, this is a memset bug.

    It still doesn't make sense why a memset call that should be zeroing things would be impacted by values in the data section, but it seems to be. Even more confusingly, it seems to be specifically values where the upper 4 bits are zeros and the lower 8 bits are 0x40 (bits 8-11 don't seem to matter).

  • To further add to my confusion:

    • I can't replicate this on an FR2355 (issue specific to FR2433?)
    • Adding another variable to twi.c below "myvar" with a "good" value prevents the issue

    Perhaps it is somehow related to what value is at 0x2000 (that's where myvar is in the data section according to the disassembly). Adding a second global pushes myvar down to 0x2002 (which prevents the issue from happening).

    I'm still not sure how the WDT interrupt accessing a global variable is related, but disabling that seems to make everything work regardless of data.

  • Sorry for the rapid replies. Just keep finding something else as I'm messing with this. I've now replicated this in a far simpler project. My current theory is as follows

    On the MSP430FR2433 memset somehow goes wrong in the following set of circumstances when initializing bss

    • There must be something in bss to cause the memset call to occur
    • The first item in data (0x2000) must have the value 0x0_40 where _ can be any digit

    This does not happen with 0x0_40 at 0x2002 instead of 0x2000.

    This does not happen when bss is empty regardless of value at 0x2000 (probably because memset call never happens).

    Given how simple memset should be, I'm wondering if this is perhaps hardware related.

    Edit: Attached simpler project that demonstrates this issue.

    6102.ReducedExample.zip

  • Hi,

    Have you output the .txt or .hex file from the GCC complier and the TI complier and compare those two files?

    Best regards,

    Cash Hao

  • memset is simple:

    0000c404 <__crt0_init_bss>:
        c404:       3c 40 02 20     mov     #8194,  r12     ;#0x2002
    
    0000c408 <.Loc.76.1>:
        c408:       0d 43           clr     r13             ;
    
    0000c40a <.Loc.77.1>:
        c40a:       3e 40 04 00     mov     #4,     r14     ;
    
    0000c40e <.Loc.81.1>:
        c40e:       b0 12 ea c4     call    #-15126 ;#0xc4ea
    
    
    000c4ea <memset>:
        c4ea:       0e 5c           add     r12,    r14     ;
    
    0000c4ec <.LVL2>:
        c4ec:       0f 4c           mov     r12,    r15     ;
    
    0000c4ee <L0^A>:
        c4ee:       0f 9e           cmp     r14,    r15     ;
        c4f0:       01 20           jnz     $+4             ;abs 0xc4f4
    
    0000c4f2 <.Loc.104.1>:
        c4f2:       30 41           ret                     
    
    0000c4f4 <.L3>:
        c4f4:       1f 53           inc     r15             ;
    
    0000c4f6 <.LVL4>:
        c4f6:       cf 4d ff ff     mov.b   r13,    -1(r15) ; 0xffff
        c4fa:       f9 3f           jmp     $-12            ;abs 0xc4ee
    

    and cares not at all about what data is in memory. Note that memset includes a  check for a zero length bss.

  • Looking closer at the linker file (gcc) it appears that data is placed in FRAM directly after text. In this program, memset is placed at the end of text and ends with a jump instruction. The first item in data is thus capable of causing the issue described in Errata CPU40.

    The Errata sheet lists gcc as unaffected, however I believe in this specific scenario (memset at the end of text) the first item in data can cause a problem.

    This doesn't impact the FR2355 since the persistent section is between text and data (though it could still be an issue depending on the first thing in the persistent section).

    The ways I can think of to fix this involve modifying the linker script.

    • Add KEEP (*(.text.end_of_text)) to the text section and add a function to this section that winds up being a "nop" and "ret"
    • Add a section between text and data and place a variable there that is the data for a nop instruction.

    I don't really like either of these solutions (as my codebase eventually will support many targets and maintaining modified linker files seems messy). However, I don't see another solution that ensures there will not be an issue.

  • GCC will insert a nop if it thinks one is required. But it can't deal with the peculiar structure of the library function memset. Which has its return instruction in the middle and a branch at the end. The compiler can't fix that. The linker might, if it were interested.

    This problem requires memset to be the final bit of code in the text section and for the first bit of data in the initializers have a particular pattern. The simple thing to do is to do something about initialized data. You could add something to the linker script (there is a mechanism for including data) but even easier is just to fix the initializers. Altering the order of variables would be simplest. In the worst case add "int foo = 0;" so you don't trigger that errata.

  • I've gone the route of modifying the linker script. The following is added after the text section (0x4303 is a "nop")

    .fix_cpu40 :
    {
      . = ALIGN(2);
      SHORT(0x4303);
    } > FRAM

    Unfortunately, the codebase I'm working with is Arduino based which means there is very minimal control over build system and linking order, so the easy option of fixing the initializers won't work reliably.

    The downsides to this approach (that I can think of) is one word of wasted space in FRAM when these conditions are not met and that I have to use modified linker scripts instead of those distributed with compiler packages, however these tradeoffs are acceptable for my purposes.

  • Something was bugging me about the location of the initialization data but I couldn't figure out what it was. Then when I was looking at code for another processor (fr5969) I noticed that this data was located at the beginning of FRAM. Before the .text sections instead of after.

    I looked over the linker scripts but I am no expert on them and can't see anything that would cause that.

**Attention** This is a public forum