Shop OBEX P1 Docs P2 Docs Learn Events
Helping each other learn SPIN2 and PASM2 - compiling best practices and language idioms... — Parallax Forums

Helping each other learn SPIN2 and PASM2 - compiling best practices and language idioms...

Stephen MoracoStephen Moraco Posts: 303
edited 2020-11-02 10:55 in Propeller 2
I'm starting this thread with thoughts of seeding a "Best practices in SPIN2 and PASM2 document" for us all to use and to which those of us learning SPIN2 and PASM2 can contribute (hence this thread)

Also, if any language elements come forward from earlier SPIN and PASM but run on the P2 then they're fair game for answer content here too!

We are all learning at various rates. Some of us are much further along than others in these two languages. I'm thinking that it will be counterproductive to ask every one of these questions of Chip directly even tho' he's been amazing when asked. We will always look forward to his answering when he wishes to. If we can help each other by sharing what we have already learned then Chip doesn't have to chime in unless he sees us missing something obvious or we're just going astray.

Please help us keep our discussions in this thread to these two languages only! By contributing to this thread, we are trying to bolster what we know and trying to share best practices while writing SPIN2 and PASM2 code!

Oh, my term "language idiom" I use to mean a tiny snippet of code that we know just works so we commonly use these tiny patterns as a best practice. Additionally, if you find nice a nice short snippet that should be considered for idiomatic use, please post it here for us all to enjoy while we start using it!

-Stephen

Comments

  • Stephen MoracoStephen Moraco Posts: 303
    edited 2020-11-02 10:50
    Here's one I'm trying to sort out. Yes, in both spin and pasm for the P2.

    I have a number in a variable/register. I want to generate a set of 1 bits having the length of the number.

    (e.g., the number is 5 so I want to end up with $00001f (5 one bits.) Does this make sense?

    in other words, what's the simplest way to generate a mask from the number of bits to be set in the mask?

    Maybe the pasm looks something like:
    ' in bitCount, out bitMask
        sub      bitMask, bitMask
        testbn  bitMask, #0   wc    ' set our CY
        rcl        bitMask, bitCount          ' successively move CY into lower bits
    

    which with our new inline assembly makes our spin look like
    PRI getBitMask(bitCount) : newBitMask
         org
         sub      newBitMask, newBitMask
         testbn  newBitMask, #0   wc             ' set our CY
         rcl        newBitMask, bitCount          ' successively move CY into lower bits
         end
    

    NOTE: this will of course need guards. (e.g., don't call this routine with a request for zero bits and we probably should limit it to 32 bits max, etc.!)

    I'm searching through the instruction spreadsheet thinking there may be advanced instructions that can help with this.... While this is pretty good are there better answers?
  • evanhevanh Posts: 15,187
    BMASK is the one you're after. The description is a tad cryptic. :)

  • Cluso99Cluso99 Posts: 18,069
    IIRC encode and decode is what you need.
  • evanhevanh Posts: 15,187
    edited 2020-11-02 10:56
    (e.g., the number is 5 so I want to end up with $00001f (5 one bits.)
    PS: BMASK will give you one more bit than you'd assume. eg: BMASK x, #5 will produce 6 set bits. Slightly cryptic but rational reason for this too. Bit #5 is the sixth bit. It fits in with a group of instructions that use the bit number rather than the number of bits. And the reason why they do this is so that the full 32 bits of a register can be addressed with a 5-bit value.

    PPS: DECOD and ENCOD are part of that group. They suit it though, feels right there.

    PPPS: Cluso has a similar topic that is stickied already, although that's not questions but rather the fruits of the answers - https://forums.parallax.com/discussion/169542/p2-links-for-where-to-obtain-tools-sample-test-code-reference-only/p1

  • I guess the answer to your question on language form and format goes back to how you were introduced to programming. In my case, I learned programming at university, with a very good professor. I started out with FORTRAN, and we had a very specific way of writing the code - setting out a title block, listing variables (i, j, k, l, m, n), were always INTEGERS, Likewise, for FORMAT statements, CALLS, Modules, Subroutines - All were defined with great precision.

    We quickly got into the nitty gritty of coding by doing simple and fun stuff - like Magic Cubes, and Alpha-Numeric "Bubble-up" sorting. New terms like Hollerith / punched paper cards (good riddance ), adders, half-adders, lots of Binary stuff... It served us well. And that was fifty (50 ) years ago.

    Today;s exercise: Magic Cubes in SPIN2 Us old timers know how to do it - Newbies, not so much...
  • JonnyMacJonnyMac Posts: 8,924
    edited 2020-11-03 00:58
    I really like that Spin2 allows us to experiment with inline PASM -- useful for experiments like this. Combining that with using the getct() for timing can be fun and lead to streamlined code.

    Per the suggestions, bmask is helpful, but the you need to make a small adjustment to get a mask with a specific number of ones. Here's my test code:
    pub main() | t, x, y                                                         
                                                                     
      setup()                                                          
                                                                     
      wait_for_terminal(true)
    
      x := 5    
      term.fstr2(string("Bits = %d, Mask = %b\r"), x, bmask (x-1))
      term.fstr2(string("Bits = %d, Mask = %b\r"), x, pasm_test(x))               
                                                                     
      repeat
        waitct(0)
    
    
    pub pasm_test(param) : result
    
      org
                    bmask     result, param
                    shr       result, #1                            ' correct while preserving param
      end
    

    The output is:
    Bits = 5, Mask = 11111
    Bits = 5, Mask = 11111
    
  • kwinnkwinn Posts: 8,697
    JonnyMac wrote: »
    I really like that Spin2 allows us to experiment with inline PASM -- useful for experiments like this. Combining that with using the getct() for timing can be fun and lead to streamlined code.

    I think it's a great idea as well. Best of both worlds. The ease of HLL's along with the speed of assembly where that is needed. Like like eating our cake and having it as well.
  • JonnyMac wrote: »
    I really like that Spin2 allows us to experiment with inline PASM -- useful for experiments like this. Combining that with using the getct() for timing can be fun and lead to streamlined code.

    Jon, you beat me to posting this single-pager test framework. Thanks!

    I agree, I find that as I'm learning how pasm instructions work I end up generating many of these small test files.

    One difference, and accomplishing the same thing as yours, is I end up using Chips' debug() for these little test snippets more than I do serial out. I find that I haven't developed yet, my personal style for which I use when. I'm just soaking it all in for now. ;-)
  • My end purpose for this exercise was to have a rapid generator for PulseWidth bitstreams not really "masking" as my problem statement showed. This allows me to store bit width (or PULSE on times) instead of bit patterns in the output buffers for Matrix panels. Now I can have a user-definable pulse width overall period without needing to adjust my storage technique when the buffer sizes are changed for a specific driver build. Also, in this PWM use, I really do need zero behaving a returning a set of bits with new 1's so you see my "is zero?" test cases return a zero value.

    Just so we have a visual comparison... here's what my snippet looked like when testing the bmask suggestion offered earlier in this thread:
    (This is NOT a competing solution as both work perfectly well. This is just here so that when we speak of these techniques a new reader of this thread will have an idea of what we speaking about.)
    PUB main() | bitCount, newMask
    
        '' run tests - emit debug
    
        repeat bitCount from 0 to 32
            newMask := getBitMask(bitCount)
            debug("- TEST ", ubin_long(newMask), " = getBitMask(", udec(bitCount), ")")
        return
    
    
    PRI getBitMask(bitCount) :newMask
        org
                    sub     newMask, newMask
                    cmp     bitCount, #0    wz
            if_z    ret  
                    sub     bitCount, #1  
                    bmask   newMask, bitCount          ' successively move CY into lower bits
        end
    
    PRI getBitMaskOld(bitCount) :newMask
        org
                    sub     newMask, newMask
                    cmp     bitCount, #0    wz
            if_z    ret  
                    testbn  newMask, #1         wc      ' set our CY
                    rcl     newMask, bitCount            ' successively move CY into lower bits
        end
    

    Our point in sharing these things in this thread is to over-time show code examples that help us develop SPIN2 and PASM2 more quickly and with more already tested code (these idioms) which we develop and then reuse these in our code wherever applicable.
  • bambino69bambino69 Posts: 126
    edited 2020-11-11 10:14
    One of Chips code fragments he offered me was the sine wave generator. In it was a line:
    repeat 
        repeat f from 50 to 70
          freq := (f frac clkfreq) >> 1
          waitms(1000)
    


    What is "frac", It Wasn't listed anywhere else in the file!
    Not understanding what is going on inside the parenthesis.
  • From the context, it looks like it is the SPIN2 way of using the CORDIC QFRAC instruction to divide f by clkfreq
  • Just found it.
    It is unsigned fraction binary operator.
  • evanhevanh Posts: 15,187
    edited 2020-11-11 18:56
    With dividend and quotient of 1.0 represented by 2^32 (4 G) in a 32.32 fixed point format. Divisor is 32-bit integer.

    EDIT: Err: Quotient is meant to be 32r32 format, same as QDIV. Not sure how that works in practice.

  • JonnyMacJonnyMac Posts: 8,924
    edited 2020-11-11 20:51
    My end purpose for this exercise was to have a rapid generator for PulseWidth bitstreams not really "masking" as my problem statement showed. This allows me to store bit width (or PULSE on times) instead of bit patterns in the output buffers for Matrix panels. Now I can have a user-definable pulse width overall period without needing to adjust my storage technique when the buffer sizes are changed for a specific driver build. Also, in this PWM use, I really do need zero behaving a returning a set of bits with new 1's so you see my "is zero?" test cases return a zero value.

    Just so we have a visual comparison... here's what my snippet looked like when testing the bmask suggestion offered earlier in this thread:
    (This is NOT a competing solution as both work perfectly well. This is just here so that when we speak of these techniques a new reader of this thread will have an idea of what we speaking about.)
    PUB main() | bitCount, newMask
    
        '' run tests - emit debug
    
        repeat bitCount from 0 to 32
            newMask := getBitMask(bitCount)
            debug("- TEST ", ubin_long(newMask), " = getBitMask(", udec(bitCount), ")")
        return
    
    
    PRI getBitMask(bitCount) :newMask
        org
                    sub     newMask, newMask
                    cmp     bitCount, #0    wz
            if_z    ret  
                    sub     bitCount, #1  
                    bmask   newMask, bitCount          ' successively move CY into lower bits
        end
    
    PRI getBitMaskOld(bitCount) :newMask
        org
                    sub     newMask, newMask
                    cmp     bitCount, #0    wz
            if_z    ret  
                    testbn  newMask, #1         wc      ' set our CY
                    rcl     newMask, bitCount            ' successively move CY into lower bits
        end
    

    Our point in sharing these things in this thread is to over-time show code examples that help us develop SPIN2 and PASM2 more quickly and with more already tested code (these idioms) which we develop and then reuse these in our code wherever applicable.

    You don't need ...
        sub newMask, newMask
    
    ... because the interpreter clears the result variable before executing any of the method code. It's not hurting, it's just redundant.

    If the value for bmask is 0, it returns 0, hence this 2-line version works.
    pub pasm_test(param) : result
    
      org
                    bmask     result, param
                    shr       result, #1                            ' correct while preserving param
      end
    

    A few minutes later...

    Darn, that doesn't work with 32 bits, which is a valid mask. Need some lunch.
Sign In or Register to comment.