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.

RM48L952: SAFETI: L2 & L3 tests corrupts RAM

Part Number: RM48L952

Hello again,

Any ideas what is the problem, the code jumps to data abort which it can't mask.

In case SRAM ECC test is performed after L2L3 tests, this happen in runtime and boot time. For some reason run time SRAM tests works after start up in case L2L3 tests are only run during start up.

Made simple test case for start up which managed to replicate the problem (before that code I have performed SRAM tests successfully in recommended part of start up routine).

    {
        // Only these tests can be run in priviledged mode
        const SL_SelfTestType aeIconTests[] =
        {
            L3INTERCONNECT_RESERVED_ACCESS,
            L2INTERCONNECT_RESERVED_ACCESS
        };

        for( u32I = 0U; u32I < ELEMENTS( aeIconTests ); u32I++ )
        {
            retVal = SL_SelfTestL2L3Interconnect( aeIconTests[ u32I ], NULL, NULL, 0U); // rest data is for unpriviledge test
            INCREMENT_PASS_FAIL_COUNTER( ST_PASS, retVal, FAILURE_PROCEED );
        }
    }

    {
        SL_SelfTest_Result          failInfoTCMRAM;     /* TCM RAM Failure  information */

        retVal = SL_SelfTest_SRAM(SRAM_ECC_ERROR_FORCING_2BIT, TRUE, &failInfoTCMRAM);
        INCREMENT_PASS_FAIL_COUNTER( failInfoTCMRAM, retVal, FAILURE_PROCEED );
        sl_vimREG->INTREQ0 = 1U; // clear CH: esmHIGH (always FIQ), test enables it
    }

The result during run time and startup is same:
===DATA_ABORT===<CR><LF>
  DFSR: 0x409<CR><LF>
  DFAR: 0x8008170<CR><LF>
  Status: 0x19<CR><LF>
  Read: TRUE<CR><LF>
  AxiDec: TRUE<CR><LF>
====================

where 0x8008170 belongs to SafeTI test buffer
sramEccTestBuff         0x08008160    0x20  Data  Gb  sl_selftest.o [1]


In data abort handler I have copied the error handling for SRAM test from example project + added that variable increasing every time the test is recognized
    if( SL_FLAG_GET((int32)SRAM_ECC_ERROR_FORCING_2BIT) )
    {
        u32Ecc2bit++;

        uint32 u32EccWrEnMask = (uint32)TCRAM_RAMCTRL_ECCWREN;  /*lint !e9033 !e9053 */ // TODO: why lint
        uint32 u32Eec1WrEn = sl_tcram1REG->RAMCTRL & u32EccWrEnMask;
        uint32 u32Eec2WrEn = sl_tcram2REG->RAMCTRL & u32EccWrEnMask;
        if( (u32Eec1WrEn == u32EccWrEnMask)|| (u32Eec2WrEn == u32EccWrEnMask) )
        {   /* So looks like writes to ECC region is enabled, check if the error address is in the test buffer range */
            maskDAbort = TRUE; // TODO: should the address be checked and not just masking error out based on SL_FLAG_GET...
        }
    }

Value of u32Ecc2bit is 3, and that abort is called twice per test meaning that 2nd test managed to handle first data abort but them something happens in the middle of test.

Based on debugger the data abort is called when executing that _SL_Barrier_Data_Access() line
        /* Set the self test flag for a self test to indicate the esm handler that this is done as a part of selftest */
        /* read from location with 2-bit ECC error this will cause a data abort to be generated */
        /*SAFETYMCUSW 446 S MR:10.1 <APPROVED> Comment_11*/
        ramread64 = sramEccTestBuff[2];
        _SL_Barrier_Data_Access();            // unexpected data abort from here
        /* Restore ctrl registers */
        sl_tcram1REG->RAMCTRL &= ~TCRAM_RAMCTRL_ECCWREN;

        /*SAFETYMCUSW 134 S MR: 12.2 <APPROVED> Comment_5*/
        ramRead = sl_tcram2REG->RAMCTRL;
        /* Set the self test flag for a self test to indicate the esm handler that this is done as a part of selftest */
        /*SAFETYMCUSW 446 S MR:10.1 <APPROVED> Comment_11*/
        ramread64 = sramEccTestBuff[3];
        _SL_Barrier_Data_Access();
        /* Restore ctrl registers */
        sl_tcram2REG->RAMCTRL &= ~TCRAM_RAMCTRL_ECCWREN;


Since SRAM_ECC_ERROR_FORCING_2BIT works alone in runtime it indicates that test itself is OK. Since it also works in runtime 1 time after start-up but not in start-up straight after L2L3 test it indicates that something will change somewhere in transtion from start-up to runtime which allows it pass once.

In case I understood correctly next expected data abort should come from here ramread64 = sramEccTestBuff[3];. so something is wrong. Lets check a bit further:

I made this kind of debug trap and set break point to that if
        u32Ecc2bit++;

        if( u32Ecc2bit == 3 )
        {
            temptemp++;
        }

Everything looks to work nicely but the code also passes this IF in same round (and I have there logic which in this case sets maskDAbort = FALSE;)
    /* DAbort due to an SRAM ECC 2Bit self test? */
    if( SL_FLAG_GET((int32)SRAM_ECC_2BIT_FAULT_INJECT) )
    {
    }

So obviously the problem is that test activity array gets some how corrupted.

After startup running this one L3INTERCONNECT_RESERVED_ACCESS the
sl_priv_flag_set[0] looks to have value of 4 (meaning that SRAM_ECC_ERROR_FORCING_1BIT is active but SL_FLAG_SET uses value 0 & 1)
After performing L2INTERCONNECT_RESERVED_ACCESS the  sl_priv_flag_set[3] is 136 (and [0] is now zero) meaning that SRAM_ECC_2BIT_FAULT_INJECT is active.

This perfectly explains why this data abort handler hangs, just wondering what else those L2 & L3 corrupts...

Why this test array gets corrupted, what the L2 & L3 tests do? Are those same kind of destructive test as PBISTs, when those tests should be performed? I execute them nearly in the end of start which doesn't sound safe anymore  in case more RAM is corrupted than just those test activity slots...

This also explains why SRAM tests works one after start up -> before jumping to main() the variables are re-initalized and that corrupted SRAM_ECC_2BIT_FAULT_INJECT is removed...

All ideas are welcome, do I do something wrong in data abort handler?

In case of L3 test the sl_priv_flag_set[0] looks to go to value 4 in STR command just before the IF starts so when data abort has been returned

       0xa6a6: 0x0020         MOVS      R0, R4
       0xa6a8: 0xf001 0xfb24  BL        SL_FLAG_SET             ; 0xbcf4
            _SL_Barrier_Data_Access();
       0xa6ac: 0xf001 0xe826  BLX       _SL_Barrier_Data_Access ; 0xb6fc
            g_L2L3_read_reserved_word = *((uint32*)PCR_RESERVED_LOCATION);
       0xa6b0: 0xf05f 0x407d  MOVS.W    R0, #-50331648          ; 0xfd000000
       0xa6b4: 0x6800         LDR       R0, [R0]
       0xa6b6: 0xf8df 0x1d38  LDR.W     R1, [PC, #0xd38]        ; [0xb3f0] g_L2L3_read_reserved_word // jumps to data abort
       0xa6ba: 0x6008         STR       R0, [R1]    // looks to corrupt here
            if ((0x00000008u == (uint32)(0x00000008u & _SL_Get_DataFault_Status())) &&
                ((uint32)PCR_RESERVED_LOCATION == _SL_Get_DataFault_Address())) {
       0xa6bc: 0xf000 0xef60  BLX       _SL_Get_DataFault_Status ; 0xb580
       0xa6c0: 0x0700         LSLS      R0, R0, #28
       0xa6c2: 0xd505         BPL.N     0xa6d0
       0xa6c4: 0xf000 0xef60  BLX       _SL_Get_DataFault_Address ; 0xb588

And L2 test corrupts data in exactly same point
            _SL_Barrier_Data_Access();
       0xa6e6: 0xf001 0xe80a  BLX       _SL_Barrier_Data_Access ; 0xb6fc
            g_L2L3_read_reserved_word = *((uint32*)SCR_RESERVED_LOCATION);
       0xa6ea: 0xf05f 0x4008  MOVS.W    R0, #-2013265920        ; 0x88000000
       0xa6ee: 0x6800         LDR       R0, [R0]
       0xa6f0: 0xf8df 0x1cfc  LDR.W     R1, [PC, #0xcfc]        ; [0xb3f0] g_L2L3_read_reserved_word // jumps to data abort
       0xa6f4: 0x6008         STR       R0, [R1] // looks to corrupt here
            _SL_Barrier_Data_Access(); /*added to avoid linker alignment issue*/
       0xa6f6: 0xf001 0xe802  BLX       _SL_Barrier_Data_Access ; 0xb6fc
            if ((0x00000008u == (uint32)(0x00000008u & _SL_Get_DataFault_Status())) &&
                ((uint32)SCR_RESERVED_LOCATION == _SL_Get_DataFault_Address())) {
       0xa6fa: 0xf000 0xef42  BLX       _SL_Get_DataFault_Status ; 0xb580
       0xa6fe: 0x0700         LSLS      R0, R0, #28
       0xa700: 0xd505         BPL.N     0xa70e
       0xa702: 0xf000 0xef42  BLX       _SL_Get_DataFault_Address ; 0xb588

In L2 tests the R0 = 0x88000000 and R1 = 0x0800A840

Address of sl_priv_flag_set is 0x0800A840
sl_priv_flag_set        0x0800a840    0x40  Data  Gb  sl_priv.o [1]

So most likely this is not corrupting anything else...

Address of sl_priv_flag_set[3] is 0x0800A843 so this STR command makes 32bit write to the beginning of test array and corrupts it...

Can you guess that it took a bit more than 3 minutes to figure out what is happening...

  • Hello Jarkko,

    I have forwarded your question to the developer of SafeTI library.

    Regards,
    QJ
  • Digged this a bit more and in this code

           0xa6e0: 0x0020         MOVS      R0, R4
           0xa6e2: 0xf001 0xfb07  BL        SL_FLAG_SET             ; 0xbcf4
                _SL_Barrier_Data_Access();
           0xa6e6: 0xf001 0xe80a  BLX       _SL_Barrier_Data_Access ; 0xb6fc
                g_L2L3_read_reserved_word = *((uint32*)SCR_RESERVED_LOCATION);
           0xa6ea: 0xf05f 0x4008  MOVS.W    R0, #-2013265920        ; 0x88000000
           0xa6ee: 0x6800         LDR       R0, [R0]
           0xa6f0: 0xf8df 0x1cfc  LDR.W     R1, [PC, #0xcfc]        ; [0xb3f0] g_L2L3_read_reserved_word
           0xa6f4: 0x6008         STR       R0, [R1]
                _SL_Barrier_Data_Access(); /*added to avoid linker alignment issue*/
           0xa6f6: 0xf001 0xe802  BLX       _SL_Barrier_Data_Access ; 0xb6fc
                if ((0x00000008u == (uint32)(0x00000008u & _SL_Get_DataFault_Status())) &&
                    ((uint32)SCR_RESERVED_LOCATION == _SL_Get_DataFault_Address())) {
     

    The PC is 0xA6EE when jump to abort handlers happens (as expected since trying to read value from 0x88000000 address)
    In data abort handler the LR is 0xA6F6 (points to next "full command" skpping a couple of lines asm)
    abort handler reduces -4 from LR before stackking it so in the stack is 0xA6F2 (tries to go back to previous command?)
    When code returns if goes to 0xA6F4 (skipping the command in 0xA6F0 totally) line since it is next "valid address"...

    R0 and R1 are same before and after data abort handler -> means that value of those registers are stored & restored correctly.

    So the real problem here is that the address reading of g_L2L3_read_reserved_word is skipped so R1 points still to activity array from previous operations and then that STR is executed yeilding to array value corruption... Or alternatively the jumps is made to wrong location and also STR command should be skipped...


    Data abort handler is just like in example code (and I'll guess that exception vectors needs to be in ARM)
    #ifdef __TI_COMPILER_VERSION__
    #pragma INTERRUPT ( _excpt_vec_abort_data, DABT)
    void _excpt_vec_abort_data()
    #endif
    #ifdef __IAR_SYSTEMS_ICC__
    extern __irq __arm void _excpt_vec_abort_data( void ); // for lint
    __irq __arm void _excpt_vec_abort_data( void )
    #endif
    {
    .....
    }

    This results to following kind of asm code
    __irq __arm void _excpt_vec_abort_data( void )
    #endif
    {
    _excpt_vec_abort_data:
          0x207cc: 0xe24ee004     SUB       LR, LR, #4
          0x207d0: 0xe92d50ff     PUSH      {R0-R7, R12, LR}
          0x207d4: 0xeef10a10     VMRS      R0, FPSCR
          0x207d8: 0xe92d0001     STMDB     SP!, {R0}
          0x207dc: 0xe24dd004     SUB       SP, SP, #4
          0x207e0: 0xed2d0b10     VPUSH     {D0-D7}
          0x207e4: 0xe24dd008     SUB       SP, SP, #8
        register uint32 callbkParam1 = 0u, callbkParam2 = 0u, callbkParam3 = 0u;
          0x207e8: 0xe3a01000     MOV       R1, #0


    We are using IAR compiler if that matters. And processor mode is set to thumb in c/c++ compiler "code" configuration tab.

    In case that mode is set to ARM the following kind of code is generated for L2 test

           0xbf9c: 0xeb000835     BL        SL_FLAG_SET             ; 0xe078
                _SL_Barrier_Data_Access();
           0xbfa0: 0xeb000611     BL        _SL_Barrier_Data_Access ; 0xd7ec
                g_L2L3_read_reserved_word = *((uint32*)SCR_RESERVED_LOCATION);
           0xbfa4: 0xe3a00488     MOV       R0, #-2013265920        ; 0x88000000
           0xbfa8: 0xe5900000     LDR       R0, [R0]
           0xbfac: 0xe59f1c2c     LDR       R1, [PC, #0xc2c]        ; [0xcbe0] g_L2L3_read_reserved_word
           0xbfb0: 0xe5810000     STR       R0, [R1]
                _SL_Barrier_Data_Access(); /*added to avoid linker alignment issue*/
           0xbfb4: 0xeb00060c     BL        _SL_Barrier_Data_Access ; 0xd7ec
                if ((0x00000008u == (uint32)(0x00000008u & _SL_Get_DataFault_Status())) &&
                    ((uint32)SCR_RESERVED_LOCATION == _SL_Get_DataFault_Address())) {


    LR in abort handler entry is 0xBFB0 and that -4 is 0xBFAC and now it returns the line where address of g_L2L3_read_reserved_word is read and R1 is updated to correct value so STR writes to correct address.

    In example project the mode looks to be ARM, is this required? Or is this user defined but if thumb is used then example projects abort handler cannot be used as is?

    In Safety manual there are no mentions about thumb or ARM, in chapter 6.2 is said that exception handlers are not provided by the library (but those are on example project).

    With ARM mode selected the whole application looks to work, in case I understood correctly the thumb is subset of ARM in order to reduce code so compiler switching compiler options to ARM is always safe move?

    Current code size looks to have great impact (~50% more in ARM)
    thumb:
      113 657 bytes of readonly  code memory
       34 338 bytes of readonly  data memory
       39 119 bytes of readwrite data memory

    arm:
      169 699 bytes of readonly  code memory
       29 420 bytes of readonly  data memory
       39 119 bytes of readwrite data memory


    It doesn't help if __arm keyword is removed from data abort handler (when cpu mode is thumb)
    #ifdef __IAR_SYSTEMS_ICC__
    extern __irq void _excpt_vec_abort_data( void ); // for lint
    __irq void _excpt_vec_abort_data( void )
    #endif

    So most likely also other tests which jumps data abort may do something "crazy" when thumb is enabled?

    FIQ handler in SafeTI (sl_esm_high_intr_handler) has __fiq keyword and LR is reduced also by 4 in the beginning of it and that looks to works ok even with thumb...

  • Jarkko Silvasti said:
    So most likely also other tests which jumps data abort may do something "crazy" when thumb is enabled?

    Based upon the investigation into LAUNCHXL2-TMS57012: Unable to perform a cold reset when flashing code using XDS debugger from a GCC toolchain I think that the exception handlers require to be compiled for ARM mode, but other functions can be compiled for THUMB mode.

    I don't use the IAR compiler, but can you try compiling the functions which are referenced from exception vectors for ARM mode, and all other functions for THUMB mode?

  • Excuse my poor english what you mean by "referenced"? If case data-abort calls some other functions that those should be compiled as ARM mode also or something else?

    I assume that it needs that __arm keyword in IAR since cannot find anything else from project options which could force the mode for individual functions or files.

    So basically this should be ok for the abort handler itself (this function is set directly to intvec's reset entry: b _excpt_vec_abort_data)

    #ifdef __TI_COMPILER_VERSION__
    #pragma INTERRUPT ( _excpt_vec_abort_data, DABT)
    void _excpt_vec_abort_data()
    #endif
    #ifdef __IAR_SYSTEMS_ICC__
    extern __irq __arm void _excpt_vec_abort_data( void ); // for lint
    __irq __arm void _excpt_vec_abort_data( void )
    #endif
    {
    ...
    }

    I have inside data abort function which checks certain tests (functionality is same as in example project but executed by function, practically replaced 3 lines if conditions by function since there are multiple similar if sentences with just different input values)

    if( bDataAbortAxiDecoder( (int32)L2INTERCONNECT_RESERVED_ACCESS, 0x00000008u, 0x88000000U ) )
    {
    maskDAbort = TRUE;
    }

    This bDataAbortAxiDecoder() function does NOT have __arm keyword, so you suggest that I add it? Just wondering how that could help since the LR value is stacked (and is 'wrong') before that this function is called.

    Then data abort also has ESM_ApplicationCallback() just like the demo app has. This does not have __arm keyword in demo app either. So in case you meant that what I understood then I should also add __arm here and then it differs from demo app.

    And then I have also debug printing call in case abort is not masked (and that has a lot of functions inside it) so it does not sound practical trying to set all called function and functions below that to ARM mode (I could disable printing for test and add __arm's to those 2 functions)...


    I found something interesting, in the end of the data_abort is return address manipulation for TI compiler, why not for IAR? This loooks to increase the stacked LR by 8 in stack
    /* Update the return address, on stack, so that we return to the next instruction */
    #if defined(_TMS570LS31x_) || defined(_TMS570LS12x_) || defined(_RM48x_) || defined(_RM46x_) || defined(_TMS570LC43x_) || defined(_RM57Lx_)
    #ifdef __TI_COMPILER_VERSION__
    __asm(" LDR R0, [SP, #108]");
    __asm(" ADD R0, R0, #8");
    __asm(" STR R0, [SP, #108]");
    #endif
    #endif

    And similar for different CPU models and #if OPTIMISATION_ENABLED

    Is the root cause actually this that stack's LR value is not altered for IAR?

    It also looks like that floating point unit stacking is not conditional, does it with CCS always behave same way, since in IAR you can select whether you are using FPU at all or not (general options -> target tab and there "floating point settings")... I have a feeling that Stack offset should be different in case FPU is not used.
  • I tried to put __arm keywords here and there but no changes as I suspected. Also there were a lot other functions like SL_FLAG_GET and _SL_HoldNClear_nError() which were used in functions called from data abort but really tried to put it every where...

    Also tried to enable "interwork" option in IAR project options, no changes. IAR manual says this about interwork so most likely the settings does not affect anything since hercules is something in range from v6 to v8?
    - "Note: Source code compiled for an ARM architecture v5 or higher, or for AEABI compliance, is interworking by default."

    I have understood that it is only the exception vector which needs to be in ARM mode and with interwork every other function under it could be thumb, but this goes beyond my core competence of MCUs...
  • Forgot to say that in case I put that SafeTI test function to ARM mode then it works (but still doubting that other tests may do something which is not (yet) visible)
    __arm boolean SL_SelfTestL2L3Interconnect(SL_SelfTestType testType, volatile uint32* location, volatile uint32* protsetreg, uint32 protbit)

    Because after that code looks like this which is same for this function as whole code compiled as ARM code (4byte instructions...):

                _SL_Barrier_Data_Access();
           0xa7ac: 0xeb00040a     BL        _SL_Barrier_Data_Access ; 0xb7dc
                g_L2L3_read_reserved_word = *((uint32*)SCR_RESERVED_LOCATION);
           0xa7b0: 0xe3a00488     MOV       R0, #-2013265920        ; 0x88000000
           0xa7b4: 0xe5900000     LDR       R0, [R0]
           0xa7b8: 0xe59f1d10     LDR       R1, [PC, #0xd10]        ; [0xb4d0] g_L2L3_read_reserved_word
           0xa7bc: 0xe5810000     STR       R0, [R1]
                _SL_Barrier_Data_Access(); /*added to avoid linker alignment issue*/
           0xa7c0: 0xeb000405     BL        _SL_Barrier_Data_Access ; 0xb7dc
                if ((0x00000008u == (uint32)(0x00000008u & _SL_Get_DataFault_Status())) &&
                    ((uint32)SCR_RESERVED_LOCATION == _SL_Get_DataFault_Address())) {


    Should there be user definable keyword define in front of every SafeTI function which causes data abort call, that way user could define the behavior of the system without modifying the SafeTI source (and looking all possible functions which causes that data aborts)?

    Currently I have to decide between 2 options, a) put whole SW to ARM mode -> takes a lot of code space b) modify "certified" SafeTI code to put certain functions into ARM mode...

    Any comments?

  • Hi Jarkko,

    Comment 1:

    Some diagnostic tests (in this case the L2/L3 interconnect tests for reserved memory access/SRAM 2 bit ECC errors) result in data aborts. Data abort handler is not part of the safety diagnostic library, but as the safety library requires specific handling of errors created by it, the data abort handler must be tailored for usage with the safety diagnostic library. Part of the tailoring is to modify the return from the abort handler.

    Also, the abort handler provided is a reference implementation. We mention in the user’s guide:

    Exception Handler

    The R4/R5 CPU branches to an exception handler for handling failures at runtime. Following exceptions are handled:

    • Prefetch Aborts (Precise)
    • Data Abort (Precise & Imprecise)
    • Undefined instructions

    These handlers are typically defined by the RTOS/HAL layers and are hence not provided by the library. However, a reference implementation is provided for handling of the errors resulting in CPU exceptions

    Comment 2:

    SafeTI Diagnostics Library was not tested  for Thumb2 mode. This was not captured as part of Initial requirements. There is an active Ticket on this SDOCM00122841, Root cause analysis mentions that thumb2 mode support was not captured as requirement for SafeTI diagnostics library.

    This was not moved as critical work item, since most of Safety Customers were running only in ARM mode.

    Comment 3 :

    I would like to bring to your notice that none of Hercules software released by TI is certified, please go through the licensing terms. We follow TUV certified software development Process, but not the release software. It is customers responsibility to take the software and go for certification, to assist we provide "Compliance Support Package (CSP)" which contains necessary artifacts and tools for customers to take it for certification.

    Coming to your specific question:

    "Currently I have to decide between 2 options, a) put whole SW to ARM mode -> takes a lot of code space b) modify "certified" SafeTI code to put certain functions into ARM mode... "

    If moving to 100% ARM mode is not possible due to memory limitation, you can modify certain SafeTI diagnostics functions and use them, just for that particular function customer cannot use CSP, they have to perform the Static and Dynamic analysis as per their software Quality goal/plan.

  • Thanks,

    1 Question left (see about comment 2)

    about comment1: yes, but in case guide would have mentioned that "provided reference implementation works only in ARM mode for the reason X (LR will not be set correctly for other code modes)" this would have been very clear at the beginning that something extra is needed in between which is not provided by demo application, not just that real fault needs to be handled by own made code.


    about comment2: In case I have understood correctly based on the comments from other source the next SafeTI release might contain abort handler where first stage is made by asm so that LR is always correct despite of the mode (I have seen (draft?) version of that kind of code) - this most likely relates to the ticket number you mentioned.

    Question: In case you have it ready would it be possible to "unofficially" publish the latest assembler code part of the abort handler so that every one does not need to do that part manually from scratch in case they do not use 100% arm mode (and it would not hurt in arm mode either)?


    about comment3: yes, that's why I used "certified" term since I knew that it isn't completely ready package without any work. But now also the modification part is clear for me, thanks. I have had to fix (practically 1 line changes each) following test functions STC, CCMR4F, DMA & register bit acks in sl_esm.c so procedures you mention will be needed there in any case.

    This "__arm modification" can and should be replaced by proper abort asm-handler mentioned above, so no need to worry about that __arm modification anymore, the initial problem was to understand the root cause why something was happening since it wasn't clear to me when starting this thread, discovered it a couple of days later. At least we need to prepare for non 100% arm code since quite big non-safety code package is not our hands yet which in other environment takes ~2MB flash. So better to find those pitfalls earlier than hit them in the end stage when everything should be ready - "just" switch compiler mode to get all code fit into memory...