Shop OBEX P1 Docs P2 Docs Learn Events
FlexC and 64 bit integers — Parallax Forums

FlexC and 64 bit integers

For my CNC/motion controller project I need to do some calculations with fixed point math for speed reasons. Most of the values fit in 32 bit but some do not. For example I need an averaging filter where input, output and buffer uses 32 bits but the sum needs 64 bit.

I've noticed that long long aka int64_t has been implemented in FlexC which is very useful. However, in some functions I'd like to use hand-optimized code with inline assembler. So is something like this possible?

uint32_t sqrt64 (uint64_t a)
{
  uint32_t sr;
  __asm{
    qsqrt a.lo,a.hi
    getqx sr
  }
  return sr;
}

Or probably there are already predefined functions for most of what I need, just like __builtin_mulh()? Are the builtin functions documented in "c.pdf" all that exists or are there more of them hidden somewhere?

«1

Comments

  • No, at the moment there's no way to access the components of a C 64 bit variable in inline assembly. However, you could write it in Spin as something like:

    pub sqrt64(alo, ahi) : sr
      asm
        qsqrt alo, ahi
        getqx sr
      endasm
    
  • Hmm, I don't know how your spin example is different from C. If the 64 bit variable is already split into two 32 bit ones I could also do the same in C, right? So I'll try the old union-trick...

  • When trying to manipulate pointers or unions to split the 64 bit value into two 32 bit variables I get error messages like "Variable must be placed in memory (probably due to an @ expression) and hence cannot be accessed in inline assembly". :/
    But this seems to work:

    uint32_t sqrt64 (uint64_t a)
    {
        uint32_t lo= a;
        uint32_t hi= a>>32;
        uint32_t sr;
        __asm{
            qsqrt lo,hi
            getqx sr
        }
        return sr;
    }
    

    ... though it compiles to rather funny ASM code. But optimization is not so important at the moment.We can take care of that later.

  • In the most recent source code (checked into github) you can do:

    unsigned sqrt64 (unsigned long long a)
    {
      unsigned sr;
      __asm{
        qsqrt a+0,a+1
        getqx sr
      }
      return sr;
    }
    
  • Thanks a lot! Now the code is really highly optimized. The function gets inlined automatically and the compiler even places other instructions between the qsqrt and the getqx to make best use of the pipelined execution.

  • @ManAtWork said:
    Thanks a lot! Now the code is really highly optimized. The function gets inlined automatically and the compiler even places other instructions between the qsqrt and the getqx to make best use of the pipelined execution.

    Yes, that was a nice optimization that @Wuerfel_21 added -- she's done a lot to improve the compiler.

  • ManAtWorkManAtWork Posts: 2,176
    edited 2022-11-16 16:09

    I thought

    uint64_t x = 0x80000000;
    printf("%Xll", x+x)
    

    should print out the whole long long but it seems only to print out the least significant 32 bits.

  • @ManAtWork said:
    I thought

    uint64_t x = 0x80000000;
    printf("%Xll", x+x)
    

    should print out the whole long long but it seems only to print out the least significant 32 bits.

    The format should be "%llX", not "%Xll". The format specifier (X in this case) always has to come last, after any length modifiers or width/precision specification. See https://cplusplus.com/reference/cstdio/printf/ for details.

  • ManAtWorkManAtWork Posts: 2,176
    edited 2022-11-16 16:40

    OK, printf doesn't know any 64 bit types, yet. The function call seems to put it on the stack so that...

      uint64_t dist= 0;
      fixp32_t v1= currVel;
      dist+= v1;
      printf ("Put i=%d v1=%08X dist=%08X %08X\n", i, v1, dist);
    

    prints out all 64 bits but in the wrong MSW/LSW order (little endian instead of big endian)

  • Aaarrgg!! Most of the time, the problem is on the other side of the screen.
    I've been debugging my code for hours and it turns out it worked well from the beginning. Only my output was shifted because I pushed more onto the stack than printf popped out. Of course, the compiler can't check this with "..." parameters.

  • What is wrong with

    uint64_t Mul64 (uint32_t a, uint32_t b)
    {
        uint64_t r;
        __asm {
            qmul  a,b
            getqx r+0
            getqy r+1
        }
        return r;
    }
    

    ? The compiler says "error: Operand too complex for inline assembly". But the example in post #5 works.

  • qmul takes 32-bit operands.

  • RaymanRayman Posts: 14,640

    maybe it's uint64_t versus unsigned long long?

  • The error is displayd for the line with getqx, not qmul. "unsigned long long" makes no difference.

  • @ManAtWork : there's a missing case in the inline assembly for r+N, it can deal with parameters but has trouble with local variables. There also seems to be a bug in getqy, so even after I fixed the missing case incorrect code was generated. For now, I suggest doing:

    uint64_t Mul64 (uint32_t a, uint32_t b)
    {
        uint32_t rlo, rhi;
        __asm {
            qmul  a,b
            getqx rlo
            getqy rhi
        }
        return ((uint64_t)rhi << 32) | rlo;
    }
    
  • Ok, the basic calculations seem to work, now. But now I encountered another problem. My rather complex program showed unexplainable results. Because of my stupid problems with printf I started to doubt everything and even ported parts of the code to the PC to be able to use a single step source level debugger. It was kind of a Heisenbug and when observed to close the problem magically vanished...

    It took me more than a day but I finally managed to make a small program that does almost nothing but can still reproduce the bug. It must be some combination of me getting cought in some of the numerous C trapdoors and maybe the compiler not giving correct warnings. I can't tell but the behaviour is really strange. This is what it prints

    Cog0  INIT $0000_0000 $0000_0000 load                                           
    Cog0  INIT $0000_0404 $0000_0000 load                                           
    ClearBuffer() buf1=622C vn=½ÁšE…ÉÑÕÁz-5RþAxisBuffer = 0000622C size = 64      
    buffer[0] vn=F605B666 vm=00000002 dd=00006250_00000A54  
    

    The code should run without modifications on a KISS board. For boards with the standard 20MHz crystal you have to modify/delete the _clkfreq value in the main program.

    void ClearBuffer ()
    {
        currVel= 0;
        wrIndex = 1;
        rdIndex = 0;
        //PutPosDat (nomPos, GetRefNum ());
        buf1= &AxisBuffer[rdIndex];
        buf1->vNom= 0;
        buf1->vMax= maxVel;
        buf2= buf1;
        bufGauge= 1;
        printf ("ClearBuffer() buf1=%X vn=%X", buf1, buf1->vNom);
    }
    

    As I understand it this should display vn=0 if all other variables are initialized correctly and I haven't made a stupid mistake, again. But what's suspicious is that if I turn off optimisation (-O0) the program doesn't compile at all. The compiler complains with "error: Changing hub value for symbol __struct__s_vfs_file_t_putchar".

  • RaymanRayman Posts: 14,640

    Wow, calling C functions from Spin2. Didn't know one could do that...

  • The "changing hub value" error message is one I've seen before in -O0, and related to multiple copies of the struct functions being compiled (even though only one copy is ever used). I've checked in a work-around for now, which is to enable unused function removal even at -O0.

  • Serial corruption seems to be related to switching back and forth between debug() output and printf() output. Chip's debug code seems to make different assumptions from mine, and although I've tried various changes I can't seem to make them work well together. A work-around is to use printf debug (plain -g instead of -gbrk).

  • Thank you very much! It now compiles without errors with both -O0 and -O1 and it doesn't output any scrambled characters anymore.

    Yes, -g makes sense. I haven't thougt that this would cause any (serious) conflicts and just used the defaults. OK, dropped or corrupted characters are logical because the two debug outputs use different buffers but the same smart pins. But why does it overwrite my variables? Never mind.... it's all fine, now.

    @Rayman said:
    Wow, calling C functions from Spin2. Didn't know one could do that...

    Yes, that's a really cool and useful feature. Spin(2) fits the Propeller very well and is good for smaller programs and hardware related drivers. The graphical debugging (unfortunatelly not yet available in FlexProp) is especially useful for realtime debugging. On the other hand, for bigger projects with complex data structures I like C more. It supports real data types and not only bytes, words and longs. And to try out complicated algorithms I could use an IDE on the PC which offers a source level single step debugger where I can watch variables while the code is being executed with just a click instead of having to insert debug() or printf().

  • pik33pik33 Posts: 2,366

    All 4 languages in Flexprop can be mixed in one project, which is very convenient. Now I use Basic for near all things on a P2. Much simpler and much more readable than Spin or C ( C is known as "write only language" ) , structural, and I can use C and Spin code imported as a class.

  • Grrr, bad news, celebrated too early. Debug output now works flawlessly. But my program still outputs false results. I've added a memory hexdump function, see attached code. This is the output:

    ClearBuffer() buf1=634C vn=0                                                    
    AAAAAAAA AAAAAAAA AAAAAAAA AAAAAAAA                                             
    AAAAAAAA AAAAAAAA 00000002 00000878                                             
    0000635C 0000634C 00000000 FDB013F4                                             
    00000003 40000960 00006370 00006370                                             
    Cog0  Startup OK                                                                
    AxisBuffer = 0000634C size = 64                                                 
    buffer[0] vn=00000006 vm=0000082C dd=00004935_0000635C                          
    AAAAAAAA AAAAAAAA AAAAAAAA AAAAAAAA                                             
    00004904 AAAAAAAA FDB013F4 00000003                                             
    40000830 0000635C 0000635C 00000000                                             
    00000002 00006374 00006374 00004967                                             
    

    Opposed to yesterday, vn now equals zero at the end of the ClearBuffer() function which is correct. But after return from ClearBuffer() and after calling Test() vn first equals 00000006 in the DumpBuffer() function and then even gets overwritten with 0635C when DumpMem() is called. And if I change the "24" of memset (buf1, 0xAA, 24); in ClearBuffer() to "32" which means pre-filling the first 8 instead of 6 longwords of AxisBuffer[0] with $AA then execution stops after returning from ClearBuffer() or Init_Axes(). The program crashes although sizeof(AxisBuffEntry)==64.

    This looks like the stack and the memory of AxisBuffer are overlapping. I don't know if there's a classic stack in FlexC. If I look at the assembler code I think it uses the normal call stack for call/return and "objptr" for variables.

    I'm not sure if it has anything to do with the 64 bit types. But it's possible. I've written much bigger programs with much more structures and arrays and the compiler always calculated the adresses correctly. However, things often get screwed up when new features are added... Or am I still doing something wrong?

  • The B2M_memdump.zip file is missing B2M_Axes.h. I tried using an older B2M_Axes.h, but it didn't produce the same output as in your example.

    My suspicion right now is that the problem is in the Spin <-> C interface, that the Spin object declaration is not reserving the necessary space for C variables. A possible work-around for this (for now) would be to declare the variables as "static" in C.

  • @ManAtWork : Using a header file (.h) for an object probably won't work as you'd like. Try using B2M_Axes.c as the file for the object, instead of B2M_Axes.h.

  • Hmm, the file B2M_Axes.h hasn't changed since Nov 15th. So if you use the one from post #17 it should work. As there are probably dangling pointers and corrupted memory there's ahigh chance that the program doesn't output exacly the same for you as for me. It may depend on uninitialized memory. But it should output something different from zero in the AxisBuffer[0].vNom variable after returning from ClearBuffer().

    ClearBuffer() buf1=634C vn=0
    AAAAAAAA AAAAAAAA AAAAAAAA AAAAAAAA
    AAAAAAAA AAAAAAAA 00000002 00000878
    0000635C 0000634C 00000000 FDB013F4
    00000003 40000960 00006370 00006370
    Cog0 Startup OK
    AxisBuffer = 0000634C size = 64
    buffer[0] vn=00000006 vm=0000082C dd=00004935_0000635C
    AAAAAAAA AAAAAAAA AAAAAAAA AAAAAAAA
    00004904 AAAAAAAA FDB013F4 00000003
    40000830 0000635C 0000635C 00000000
    00000002 00006374 00006374 00004967

    The first dump is correct as ClearBuffer() sets it to 0 and the second is different. Do you see at least some differences? The bufffer should theoretically stay the same as no variable assignments are made after calling Clear Buffer(). What results do you get? Did you also test if the program stops or crashes if you change the memset length to "32"?

    @ersmith said:
    @ManAtWork : Using a header file (.h) for an object probably won't work as you'd like. Try using B2M_Axes.c as the file for the object, instead of B2M_Axes.h.

    Ok, I'll try that.

  • @pik33 said:
    All 4 languages in Flexprop can be mixed in one project, which is very convenient. Now I use Basic for near all things on a P2. Much simpler and much more readable than Spin or C ( C is known as "write only language" ) , structural, and I can use C and Spin code imported as a class.

    :+1:

    BASIC ain't going away :smile: If it wasn't for the unfortunate acronym....

    Was just looking at "Structured Text" in the PLC world :lol: ...... It's BASIC! OK, they use := (the colon makes it a real programming language, I guess :lol: ).

  • evanhevanh Posts: 15,915

    It looks more like Pascal than Basic .... or maybe I should say modern Basics have changed to look more like Pascal. The old interactive simplicity has long gone.

  • Ok, replacing the

    OBJ
      axe : "B2M_Axes.h"
    

    with

    OBJ
      axe : "B2M_Axes.c"
    

    helped a lot. The output looks clean and consistent now:

    ClearBuffer() buf1=634C vn=0                                                    
    AAAAAAAA AAAAAAAA AAAAAAAA AAAAAAAA                                             
    AAAAAAAA AAAAAAAA 00000000 00000000                                             
    00000000 00000000 00000000 60000000                                             
    00000000 00000000 00000000 00000000                                             
    Cog0  Startup OK                                                                
    AxisBuffer = 0000634C size = 64                                                 
    buffer[0] vn=00000000 vm=60000000 dd=00000000_00000000                          
    AAAAAAAA AAAAAAAA AAAAAAAA AAAAAAAA                                             
    AAAAAAAA AAAAAAAA 00000000 00000000                                             
    00000000 00000000 00000000 60000000                                             
    00000000 00000000 00000000 00000000                                             
    

    So I guess I was the first person who tried out calling C functions from a Spin main program? Well... kind of my fault (although I don't feel really guilty). As there are no clear instructions of how this is supposed to be done I just tried it out the same way as I did as if I #include external C libraries into other C code, and that means with header files. And C is well known to be susceptible for alle those cases where changing a single character can completely mess up everything...

    I'll have to check if everything else works, now, if I put the 64 bit calculations back in. Thanks for the support, @ersmith !

  • @ManAtWork said:
    So I guess I was the first person who tried out calling C functions from a Spin main program?

    No, but you may be the first one to use a .h file instead of a .c file, or at least a function-only .h file. There's a compiler bug where the Spin object created for a .h file ends up with the wrong size if the variables are not declared in the .h (basically the object size is calculated too early, before all functions are resolved; since in B2M_Axes.h there are no variables, the Spin object is created with 0 bytes reserved for member variables).

    This is actually going to be a tricky bug to fix, but in the current github source code there is at least a check for this and an error is printed.

    Thanks for finding this.

    Regards,
    Eric

  • @pik33 said:
    All 4 languages in Flexprop can be mixed in one project, which is very convenient. Now I use Basic for near all things on a P2. Much simpler and much more readable than Spin or C ( C is known as "write only language" ) , structural, and I can use C and Spin code imported as a class.

    Yeah, I know what you mean! I have hated C my whole life and things like the above problems make me hate it more and more every day. I thought I have to use it for portability reasons. But portability turns out to be nothing but whishful thinking. If we develop embedded systems and need real time performance that means it has to be closely coupled to the hardware and nothing is really portable. I've lost two full days now and finally found out that changing a "h" to a "c" fixes it. I could have re-written my whole program in a different language in those two days. And I think it would have been a lot better for my blood pressure and hair color. :D

    So Basic supports structured data types (classes)? I've seen it also supports 64 bit integers. But no inline assembler. And I think I would miss a source level debugger. I want my Oberon IDE back.

Sign In or Register to comment.