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.

RE: AM263P4: How to use shared memory region in PRU ICSS shared RAM

I have been able to determine that utilizing DRAM2 (the 32KB shared memory of the ICSS) has limitations when utilizing the PRU compiler 2.3.3.  The project originally used the following:

Linker snippets

MEMORY:

PAGE 0:
      /* 12 KB PRU Instruction RAM */
      PRU_IMEM          : org = 0x00000000 len = 0x00003000

    /* RAM */

    PAGE 1:
      /* Data RAMs */
      /* 8 KB PRU Data RAM 0 */
      PRU0_DMEM_0       : org = 0x00000000 len = 0x00002000 CREGISTER=24
      /* 8 KB PRU Data RAM 1 */
      PRU0_DMEM_1       : org = 0x00002000 len = 0x00002000 CREGISTER=25

    PAGE 2:
      /* C28 needs to be programmed to point to SHAREDMEM, default is 0 */
      /* 32 KB Shared general purpose memory RAM with ECC, shared between PRU0 and PRU1 */
      PRU_SHAREDMEM     : org = 0x00010000 len = 0x00008000 CREGISTER=28

SECTIONS:
.shared_struct > PRU_SHAREDMEM, PAGE 2

Header file call

extern s_Custom_Struct Struct_Data[NUM_STRUCTS] __attribute__((section(".shared_struct")));

C file
s_Custom_Struct Struct_Data[NUM_STRUCTS] __attribute__((section(".shared_struct")));


The result of this in the .asm file is:
LDI r4, ||Struct_Data||
When viewing the disassembly in debug we see this same line become:
LDI r4, 0
After this, any reference to the Struct_Data uses r4 and an offset when interfacing with RAM.  Therefore, these never actually make it to the shared ram at 0x00010000.
 
  • Hello Nathan,

    This is an interesting question that I have not tried yet myself. However, once we figure out what is going on, I would like to add an example demonstrating the concepts. Please let us know what the "working" code looks like.

    1) if this is a shared memory region between multiple cores, I would suggest defining a fixed offset 

    It is probably fine if you are not placing anything else in Shared RAM, but the linker does not seem guaranteed to always place data regions the same way with different projects. I would suggest specifying the starting address, similar to how _c_int00* is forced to a specific address in these example C linker.cmd files: https://github.com/TexasInstruments/open-pru/blob/main/source/linker_cmd/c_code/am263px/AM263px_PRU0.cmd

    (heads up, I am planning on making one more change to the linker.cmd file labels over the next few weeks to make the labels more descriptive, then I will stop modifying all the linker.cmd files in the OpenPRU repo)

    2) how is the section getting defined in C code?

    I am not sure if __attribute__((section)) is the right format to use here (it could be, perhaps this is a valid use of the LOCATION Pragma in https://www.ti.com/lit/spruhv7 ?)

    Based on reading the C compiler docs, I would start by trying to use the DATA_SECTION pragma to define a data section in C:
    https://www.ti.com/lit/spruhv7

    If you were accessing from assembly, I am not sure if it would be sufficient to define sections, like this:
    .sect ".shared_struct"

    Or if you would also want to use .cstruct & .cunion to make sure that the structure in assembly follows the same layout as the structure in C. You can find more information about those topics in the PRU Assembly Language Tools User's Guide: https://www.ti.com/lit/ug/spruhv6 

    Regards,

    Nick

  • Make sure to configure C28 for SHAREDMEM access

    The other thing to make certain of is that you are actually programming C28 as per the comment in the linker.cmd file you attached:

    PAGE 2:
          /* C28 needs to be programmed to point to SHAREDMEM, default is 0 */
          /* 32 KB Shared general purpose memory RAM with ECC, shared between PRU0 and PRU1 */

    Another thing that might be happening is if your code places ||Struct_Data|| at offset 0x0 within the SHAREDMEM (so maybe r4 is the SHAREDMEM offset), then perhaps the C compiler is assuming that the sharedmem starting address of 0x10000 is already handled.

    In Assembly code 

    You can see an example in the OpenPRU pru_emif project:
    https://github.com/TexasInstruments/open-pru/blob/main/examples/pru_emif/firmware/pru0/main.asm

    ;----------------------------------------------------------------------------
    ;   Constant Table Entries Configuration
    ;   Sample code to configure Constant Table Entries.
    ;----------------------------------------------------------------------------
    ; Configure the Constant Table entry C28 to point to start of shared memory
    ; PRU_ICSSG Shared RAM (local-C28) : 00nn_nn00h, nnnn = c28_pointer[15:0]
    ; By default it is set to 0000_0000h so it will point to DMEM0 address
        ldi     TEMP_REG, 0x0100
        sbco    &TEMP_REG, ICSS_PRU_CTRL_CONST, 0x28, 2

    In C code 

    I don't think we have an example of this. You could directly write to the register PRU_CTRL_CONSTANT_TABLE_PROG_PTR_0.

    It is probably easier for you to just hardcode a direct register write. But for transparency, this is my plan:

    When I add an example for this, I will probably use the C header files. We don't have those yet for AM26x devices. I am checking with the AM26x team if they are planning to add them. AM26x & AM62x should have identical register sets, so you could include this path in your make settings:
    https://github.com/TexasInstruments/open-pru/tree/main/source/include/c_code/am62x 

    and do something like this (I have not tested yet)

    #include <pru_ctrl.h>
    
    ...
    
    /*
     * Configure the Constant Table entry C28 to point to start of shared memory
     * PRU_ICSSG Shared RAM (local-C28) : 00nn_nn00h, nnnn = c28_pointer[15:0]
     * By default it is set to 0000_0000h so it will point to DMEM0 address
     */
    CT_CTRL.CONSTANT_TABLE_PROG_PTR_0_bit.C28_POINTER = 0x100;

    Regards,

    Nick

  • I have added the initialization of C28 into custom code we have developed. We are loading a binary image to the PRU, and the linker reference for 

    .text:_c_int00*  >  0x0, PAGE 0    will resolve to .text:_c_int00_noinit_noargs, which doesn't initialize r2 (stack pointer) and C28. I'd love to hear that this has been fixed since you and I last spoke about it 5 months ago in this E2E post: e2e.ti.com/.../pru-cgt-c-compiler-for-pru-does-not-initialize-stack-pointer
  • We are loading a binary image to the PRU, and the linker reference for 

    .text:_c_int00*  >  0x0, PAGE 0    will resolve to .text:_c_int00_noinit_noargs, which doesn't initialize r2 (stack pointer)

    Can you check the recommendation in this post if you are loading a C program : (+) LP-AM261: Problems when loading C application on the PRU programmatically (stuck in __TI_decompress_lzss) - Arm-based microcontrollers forum - Arm-based microcontrollers - TI E2E support forums

  • Thank you for the reply. We've actually developed better workarounds for both the r2 loading issue, which can be applied to loading any of the registers prior to entering the main() loop when the PRU is loaded from the R5. 

  • Hello Nathan,

    I disagree with Pratheesh's term of "workaround" in the linked response. If it works the way it has been described to me (I have not had bandwidth to test much yet), this is not a "workaround", this will be the standard TI-supported way to initialize PRU C code from an MCU+ core (unless we add an MCU+ driver that can handle ELF binary files).

    I am still working on the documentation and code for the PRU Academy, which will officially document the steps. Here's the current AM64x version of the page, since AM26x PRU Academy is still being written:

    PRU Getting Started Labs > Lab 4: How to Initialize the PRU > Initialize from MCU+ core
    https://dev.ti.com/tirex/explore/node?node=A__AWTM5qdKV.bV6igFiAtgFQ__AM64-ACADEMY__WI1KRXP__LATEST

    Here's my notes, which would go under "Writing MCU+ code to initialize the PRU subsystem"

    Project setup 

    1) Make sure _c_int00* is forced to the beginning of IRAM as you are already doing (future readers, refer to AM263Px linker.cmd file template for C code):
    https://github.com/TexasInstruments/open-pru/blob/main/source/linker_cmd/c_code/am263px/AM263px_PRU0.cmd

    2) This general approach should work with passing both --ram_model and --rom_model in the PRU linker flags. I have been told that rom_model could make sense if you are loading huge amounts of data that can be compressed, but in all of the simple projects that I have dealt with ram_model was a more efficient usage of memory.

    --ram_model means that variables in DRAM are populated during load time by the R5 core, instead of being populated during run time by the PRU core.

    Other differences between ram_model & rom_model:
    * If you build with these models, you can see differences in the data regions in the .map file of your PRU firmware binary
    * with --ram_model, DMEM region .cinit will be uninitialized, but you will see your data regions  like .data are initialized
    * with --rom_model, DMEM region .cinit will be initialized (if you actually have data to place in DRAM), but data regions like .data are uninitialized 

    3) generate the hex array file.

    When you open your hex array file, you should see multiple data arrays, one for each PAGE defined in your linker.cmd file. In my tests, it looks like this goes in page order. So the first array is PAGE 0 (IMEM), and the later ones should be for PAGE 1 (DMEM0/DMEM1 in our current linker.cmd examples) & PAGE 2 (PRU_SHAREDMEM). If you do not have any data getting loaded into a memory region by R5, then an array will not be generated.

    Let's say PRU0 has data to load into DMEM0, and the data array is called PRU0Firmware_1 (while the IMEM array is PRU0Firmware_0).

    4) Load the data with PRUICSS_writeMemory

    Currently, my empty_c example uses PRUICSS_loadFirmware, which does not load the DMEM:
    https://github.com/TexasInstruments/open-pru/blob/main/examples/empty_c/mcuplus/empty_example.c

    So we want to replace that with manual steps, like in the MCU+ SDK docs:
    https://software-dl.ti.com/mcu-plus-sdk/esd/AM64X/latest/exports/docs/api_guide_am64x/DRIVERS_PRUICSS_PAGE.html

        /* Reset and disable PRU core */
        PRUICSS_resetCore(gPruicssHandle, PRUICSS_PRU0);
        PRUICSS_disableCore(gPruicssHandle, PRUICSS_PRU0);
     
        /* Register an Interrupt Handler for an event */
        PRUICSS_registerIrqHandler(gPruicssHandle,
                                   pruEvtoutNum,
                                   intrNum,
                                   eventNum,
                                   waitEnable,
                                   irqHandler);
     
     
        /* API to do Interrupt-Channel-host mapping */
        PRUICSS_intcInit(gPruicssHandle, &pruicssIntcInitData);
     
        /* Load the IRAM in PRU */
        PRUICSS_writeMemory(gPruicssHandle,
                            PRUICSS_IRAM_PRU(0),
                            0,
                            (uint32_t *) pruFirmware,
                            pruFirmwareLength);
        /* Enable PRU */
        PRUICSS_enableCore(gPruicssHandle, PRUICSS_PRU0);

    But we also need to add a second PRUICSS_writeMemory command to load the DMEM. We have the PRU handle, we will pass memory region PRUICSS_DATARAM(0) since we are loading DMEM0. But now you need to know the word offset, and the size of the data array.

    The data array is NOT zero-padded. So you need to know the offset. At this point in time, I am not sure if there is a better way to see this, other than checking the .map file.

    Take this .map file as an example. Let's just look at the data in PAGE 1 / array1

    SECTION ALLOCATION MAP
    
     output                                  attributes/
    section   page    origin      length       input sections
    --------  ----  ----------  ----------   ----------------
    ...
    .bss       1    00000000    000001f0     UNINITIALIZED
                      00000000    000001f0     (.common:payload)
    
    .stack     1    000001f0    00000100     UNINITIALIZED
                      000001f0    00000004     rtspruv3_le.lib : boot.c.obj (.stack)
                      000001f4    000000fc     --HOLE--
    
    .cinit     1    00000000    00000000     UNINITIALIZED
    
    .rodata    1    00000348    0000000c
                      00000348    0000000c     main.obj (.rodata:.string)
    
    .resource_table
    *          1    000002f0    00000058
                      000002f0    00000058     main.obj (.resource_table:retain)
    
    

    So 0x0 - 0x2F0 is uninitialized data (.bss, .stack)
    initialized data starts at 0x2F0, and is size 0x58 + 0xC (.resource_table & .rodata)

    FYI: an unsupported edge case

    If PRU0 uses DMEM0 and PRU1 uses DMEM1, this approach works fine. However, our current linker.cmd file examples do not work well if a PRU core is using both DMEM0 & DMEM1 (for PRU0 the hex array file is larger than needed, for PRU1 I would expect it to not even work). If you need to load data into both DMEM0 & DMEM1, I would suggest splitting into 2 different PAGES in the linker.cmd file, and then using 2 separate calls to PRUICSS_writeMemory to load each of the individual arrays into each of the DMEM instances.

    Outstanding questions for you:

    1) Does --ram_model vs --rom_model make a difference for you in terms of stack pointer initialization?

    2) Do these steps actually work for your project, or are you seeing issues?

    Regards,

    Nick

  • I disagree with Pratheesh's term of "workaround" in the linked response.

    I fully agree  and fixed this in linked post from workaround to method Slight smile

  • 1) --ram_model vs --rom_model didn't make a difference in initialization when we tried this 5 months ago. There was no reliable timeline for when this would get resolved, so fixed it ourselves and have been proceeding with our solution since that time. I am reticent to invoke additional TI functionality into our project based on our daily experience with the resources. 

    2) No TI solutions we have every tried fixed the stack pointer (r2) population at startup, same with populating C28. We forged our own path to work around it.

  • --ram_model vs --rom_model didn't make a difference in initialization

    Yes - this won't

    No TI solutions we have every tried fixed the stack pointer (r2) population at startup

    Sure. There are many ways to solve this but libc init function does setup stack pointer as customer in linked thread acknowledged.

    I am reticent to invoke additional TI functionality into our project based on our daily experience with the resources. 

    Fair but there are no new APIs added to resolve this.