Propeller Tricks & Traps (Last update 21 June 2007)



  • hippyhippy Posts: 1,981
    edited 2008-03-06 - 02:33:18
    Descending Repeats

    The following will not do as it would be expected to do in other programming languages -

    repeat i from 3 to 1 step -1

    The interpreter implementation of -

    repeat indexVar from startValue to endValue step stepValue

    is equivalent to -

    indexVar := startValue
      if endValue => startValue
        indexVar := indexVar + stepValue
        indexVar := indexVar - stepValue
    while indexVar => startValue and indexVar =< endvalue

    That allows a default step size of 1 to be used when not specified, but subtracting -1 goes the wrong way in a descending repeat and immediately takes the index variable out of range.

    A descending repeat should look like "repeat i from 3 to 1 step 1", and the step can be left out entirely for smaller and quicker code when it is 1.
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 22,785
    edited 2008-03-06 - 03:36:06

    Yup. This is one of those things (like "=<" and "MIN") that makes perfect sense if your mind hasn't already been polluted by other programming languages. Since T&T is designed for the corrupt and uncorrupt alike, your observation deserves inclusion! smile.gif

  • deSilvadeSilva Posts: 2,967
    edited 2008-03-06 - 06:30:04
    It very often helps to read through the manual. I have no longer any mercy with people who say: "What? 4 pages of REPEAT-loop description! I know ALL about REPEAT loops, why should I read that??"
    It should take them weeks rather than days to debug their programs smile.gif

    Those 4 pages can indeed easily be replaced by Hippy's concise algorithm; and they do not expressly address the -1 issue; but p. 297 gives anyone willing to read enough stuff to think about smile.gif.
  • stevenmess2004stevenmess2004 Posts: 1,102
    edited 2008-03-06 - 06:31:56
    Now I understand about the problem in the other threadsmile.gif

    Could also be a trick though if you wanted to do a lot of iterations

    repeat i from 0 to -1 step -1

    would repeat a lot of times and save a small amount of memory.
  • hippyhippy Posts: 1,981
    edited 2008-03-06 - 13:12:10
    @ stevenmess2004 : That's another "special case" ...

    repeat i from 0 to -1 step 1

    This executes twice, with i == 0 then i == -1. Exits with i == -2.

    repeat i from 0 to -1 step -1

    This executes once, with i == 0. Exits with i == 1

    Post Edited (hippy) : 3/6/2008 1:18:30 PM GMT
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 22,785
    edited 2008-06-08 - 22:51:57
    Here's a trap I fell into. 'Took me awhile to figure out what was happening. I wanted to shift a number right by 8, 16, 24, or 32 places, depending on the value of i (0 .. 3). So I tried this:

      number >>= (i + 1) << 3

    Well, that didn't work; and the reason it didn't work is that you can't shift a number by more than 31 places — at least not all at once. What I resorted to was:

      number := (number >> ((i + 1 << 3) - 1)) >> 1

    This shifts the number by the desired number of place minus one, then one more place.

    While, from a Spin standpoint this seems pretty silly, a glance at the assembly description for SHR explains the reason: the number of places to shift uses only the five lower bits of the source operand (i.e. 0 .. 31).

    Now, if I had wanted to rotate the number by 32, rather than shift it, it would've worked fine, since rotating by 32 is the same as rotating by zero.

  • hippyhippy Posts: 1,981
    edited 2008-07-04 - 00:20:17
    Here's a handy trick to shave off some execution speed which can be useful for 'getters' and
    'setters' and other cases where a program has to call down and return back up a long chain
    of routines which do nothing but pass the call on ... use abort instead of return to get back

    This can shave between 8% and 11% off calls. Obviously it depends upon depth of calls and
    how many parameters are used, and don't forget to use the \ to trap the call ! A good
    place for this is in the second level down so it doesn't need to appear in the top-level where
    it's more easily likely to be forgotten.

    PUB Main
      tv.Start( pindefs.GetPinNumber(pindefs.Packed(String("TV_ADC0"))) )
      startTime := CNT
      repeat 100_000
       if [b]\Depth1[/b]( ! $DEAD_D0D0 ) <> $DEAD_D0D0
      endTime := CNT
      tv.Dec( endTime - startTime )
    PRI Depth1( n )
      return Depth2( n )
    PRI Depth2( n )
      return Depth3( n )
    PRI Depth3( n )
      [b]abort[/b] ! n
  • tdtd Posts: 8
    edited 2008-07-16 - 20:21:48
    hippy said...
    To swap the contents of two Cog registers without using a temporary register for storage ...

                        What's left in a         What's left in b
      xor a,b           a ^ b                    b
      xor b,a           a ^ b                    b ^ a ^ b = a
      xor a,b           a ^ b ^ a = b            a

    This works as long as a and b are not the same address in memory. If a and b occupy the same location (like a leaf case in a sort of 1 value) then the value will be reset to 0.

  • pemspems Posts: 70
    edited 2008-08-30 - 02:04:37
    Prop Manual v1.01 page 211, code looks like following:

    movd :arg, #arg0
    :arg mov t2, t1

    i am guessing that's a mistake? or am i way off base?
  • Mike GreenMike Green Posts: 23,013
    edited 2008-08-30 - 02:43:55
    The only mistake is that the ":arg mov" line immediately follows the "movd :arg" line. The first line of code modifies the following instruction's destination field so it contains the address "arg0". The Propeller doesn't have any index registers, so instruction modification is often used for subscripting. The Propeller has a pipeline so the next instruction to be executed is fetched from memory before the current instruction stores its result. The "movd" doesn't finish until after the cog picks up the "mov" so the instruction at ":arg" is always one behind (which may be ok in some circumstances).
  • pemspems Posts: 70
    edited 2008-08-30 - 03:19:47
    Thanks for the explanation Mike, but i guess i wasn't clear enough

    I meant that it is a mistake that the modified instruction follows immediately the modifier instruction in the example code.

    i got confused by the following though: " the instruction at ":arg" is always one behind (which may be ok in some circumstances)." I don't see how it would be ok, since it'll be picking up an instruction with the wrong dest (hence the mistake)
  • Mike GreenMike Green Posts: 23,013
    edited 2008-08-30 - 04:44:53
    The code on page 211, as far as I can tell, is really intended to be an example of the format of assembly code in a DAT section and doesn't even have to do anything useful.

    In any event, the code does what it does. Whether it is a mistake or not depends on the programmer's intention which is nowhere stated.
  • hippyhippy Posts: 1,981
    edited 2008-08-30 - 23:50:36
    pems said...
    i got confused by the following though: " the instruction at ":arg" is always one behind (which may be ok in some circumstances)." I don't see how it would be ok, since it'll be picking up an instruction with the wrong dest (hence the mistake)

    It would 'normally be a mistake' for the reason you say but there are times when the behaviour is useful and what is intended. Here's one example ...

          mov     count,#5 
    [b]     movs    :arg,#"0"
    :arg mov     char,#"X"[/b]
          call    #PrintChar
          add     :arg,#1
          djnz    count,#:arg

    This prints "X12345". Exactly what the code is meant to do. The code takes advantage of the fact that what's executed at :arg isn't what's been placed there by the "mov :arg" which immediately precedes it.
  • hippyhippy Posts: 1,981
    edited 2008-09-08 - 02:05:35

    I spent hours on this one. Obvious when in isolation here but not when embedded in 3,000 line of code ...

    a    res    1
    b    res
    c    res    1

    I expect it was not pressing the key hard enough to get the "1" I wanted and never noticed, or a cut-n-paste and I lost a character. Unfortunately without any operand 'res' defaults to 'res 0' which overlays it onto the next location, so in this case 'b' and 'c' are one and the same.

    And as Sod's Law will have it, most of the time the bug doesn't exhibit itself. This affected a lot of code which had otherwise passed tests but caused a serial output routine to send corrupt data. After a long debugging session to prove the serial I/O itself was working ...

    I'd much prefer it that 'res', 'org' etc demanded explicit operands rather than defaulting to zero. That way silly typo's - and forgetting to go back and add the value - are caught on the first compilation.

    Post Edited (hippy) : 9/8/2008 2:36:37 PM GMT
  • mparkmpark Posts: 1,233
    edited 2008-09-08 - 06:52:20
    Hippy, are you sure about that? The manual says RES without a count reserves one long.

    Michael Park

    PS, BTW, and FYI:
    To search the forum, use (do not use the Search button).
    Check out the Propeller Wiki:

    Post Edited (mpark) : 9/8/2008 6:59:32 AM GMT
  • heaterheater Posts: 3,370
    edited 2008-09-08 - 08:35:41
    Hippy: That print X12345 routine is very sneaky. Is it a "real" piece of code or just contrived to show the point.

    For me, the past is not over yet.
  • hippyhippy Posts: 1,981
    edited 2008-09-08 - 14:36:44
    @ mpark : You're Right ! So why did my code work start to work when I changed from "res" to "res 1" ?!? Back to debugging.

    @ Heater : Entirely contrived.
  • pemspems Posts: 70
    edited 2008-09-13 - 22:08:13
    here is a trap, at least it was for me, but i only start getting my hands dirty with prop's asm

    i was assuming that addresses for local cog memory (registers) are addressed per byte (like the main ram) and not per word, and i would add #4 in a loop to get to next long instead of adding #1 (i use self modifying code).
    So this was overwriting some other stuff. Nothing would work, and it was very hard to debug this
  • Cluso99Cluso99 Posts: 16,954
    edited 2008-09-14 - 15:17:55
    The following methods can be used in PASM to get the c & z flags in one instruction from cog memory if the flags are kept in bits 31·& 30 or 1 & 0. (All other bits must be zero)
    'Set c & z flags 
                            shl     cz_flags,#1   wc,wz,nr  'get c & z flags
    cz_flags                long    0-0                     'c & z flags in b31,30 (c=b31, z=b30)

    'Set c & z flags 
                            shr     cz_flags,#1   wc,wz,nr  'get c & z flags
    cz_flags                long    0-0                     'z & c flags in b1,0 (z=b1, c=b0)
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 22,785
    edited 2008-09-14 - 17:55:21

    Actually it's the "non-zero" condition that gets saved in bit 1. Getting the condition bits in there to begin with is a little more difficult, though. Referring to an earlier post,

            muxnz   save,#2
            muxc     save,#1
            shr        save,#1 wz,wc,nr

    It'd be great to figure out a single instruction for saving both bits, but I don't think it's possible.


    'Still some PropSTICK Kit bare PCBs left!
  • Cluso99Cluso99 Posts: 16,954
    edited 2008-09-15 - 03:31:47
    I'll be a little more specific...

    My method is to be able to check and set c and z flags in one instruction. If, as in the spin interpreter, the bytecodes store the lower 2 bits (bit 1 and 0) and they are tested in the code using 2 instructions, in the getflags "call".

    (Warning... the z flag is set=1 to indicate a zero condition, but it IS tested correctly as "if_z" - you just have to get your mind around this!)

    'the following stores the bytecode...
    op       long  0-0    'bit 1 (0=nocarry, 1=carry), and bit 0 (0=zero, 1=nonzero); other bits vary
                          ' 00 = gives nc and z flags
                          ' 01 = gives nc and nz flags
                          ' 10 = gives c and z flags
                          ' 11 = gives c and nz flags
    cz_flags long  0-0    'bit 31 (0=nocarry, 1=carry), bit 30 (0=zero, 1=nonzero)
                            mov     cz_flags,op             '\ save flags into cz_flags
                            shl     cz_flags,#30            '/ and all other bits become "0"
                            shl     cz_flags,#1   wc,wz,nr  ' get c&z flags <-- single instruction to get flags

    The alternative example reverses the position of the c and z flags and keeps them in bits 1 & 0
    'the following stores only the flags...
    cz_flags long  0-0    'bit 1 (0=zero, 1=nonzero), bit 0 (0=nocarry, 1=carry); all other bits zero
                          ' 00 = gives z and nc flags
                          ' 01 = gives z and c flags
                          ' 10 = gives nz and nc flags
                          ' 11 = gives nz and c flags
                            shr     cz_flags,#1   wc,wz,nr  ' get c&z flags <-- single instruction to get flags

    I hope this clarifies what I am trying to illustrate. Obviously, you can just test for either the z or c flag in the instruction. This means you no longer have to use 2 instructions to test for both flags (or a "call"). Don't forget the "nr" or you will overwrite your variable)
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 22,785
    edited 2008-09-15 - 03:50:00
    I get that, and our single instruction for restoring both flags from cz_flags (save in my case) is the same. What I'm wondering, though, is whether there's a way to save both flags with one instruction instead of the two MUXes in my above example. I don't think there is, but maybe someone's got a trick up his sleeve that I can't see.


    'Still some PropSTICK Kit bare PCBs left!
  • pemspems Posts: 70
    edited 2008-09-21 - 18:21:15
    ok, here is another trap that just got me and took me a while to figure out:

    I had a small state machine where i'd modify the jump location, something like this

    movs jump_label, #some_state_label_to_jump_to


    jump_label jmp, 0-0


    some_state_label_to_jump_to ....

    first of all, one has to use movs to modify jmp location and not movd (using the latter would make sense until one studies documentation [noparse];)[/noparse] )
    second, the code above is WRONG - instead of 0-0, one has to use something like #0 (or #$0), so the instruction code for jmp will be the one which uses an immediate value and not address to a variable. In short, the code above tries to jump to location which is STORED in #some_state_point_to_jump_to (which is of course wrong in thsi case), instead of just jumping to #some_state_point_to_jump_to

    hope this will help somebody.
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 22,785
    edited 2008-09-21 - 19:18:49

    Thanks for that. It's always good to remember that the "#" isn't part of the source field itself — despite its placement in assembly code — but corresponds to a bit elsewhere in the instruction.


    'Still some PropSTICK Kit bare PCBs left!
  • Cluso99Cluso99 Posts: 16,954
    edited 2008-09-22 - 00:36:53
    For jmp instructions, maybe this would be better while still identifying that the source would be changed. Then if a mistake is made and the source is not modified, the jmp would loop on itself instead of jumping to $000... (gives a more predictable result)
    jmp_mod  jmpret j_ret, $-0   ' "$-0"  (inirect) : means self modifying code - defaults to loop here
    jmp_mod2 jmpret j_ret, #$-0  ' "#$-0" (direct)  : means self modifying code - defaults to loop here 

    Trying to chase down this sort of bug is a nightmare. At least if the code loops in the one spot it just hangs. If you jmp to 0 all kinds of weird things happen and you end up looking for a different kind of bug that usually takes a lot longer to find. Just my opinion...
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 22,785
    edited 2008-09-22 - 00:52:53
    Another option would be to jump indirect via a long (named "NextState" perhaps) located after the program and which defaults to a piece of debug code. This long would then receive the address of each next-state routine in turn. It uses extra memory, though, but the indirect jump doesn't add to the execution time. As a bonus, you don't necessarily have to funnel jumps to the next state through a single JMP instruction. Multiple jumps referring to NextState can occur anywhere in the program.


    'Still some PropSTICK Kit bare PCBs left!
  • Cluso99Cluso99 Posts: 16,954
    edited 2008-09-22 - 03:44:39
    Excellent point Phil ! That makes a lot of sense. (I have never written a state machine)
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 22,785
    edited 2009-01-06 - 06:54:54
    Here's a trick you can use for locating the precise time of an input edge, while still being able to time out if it doesn't show up.

    Problem: The Propeller provides WAITPEQ and WAITPNE instructions to provide one-clock granularity for locating input edges. But if the edge never shows up, the instruction will hang. To keep it from hanging, you can poll for the edge in software, but the granularity suddenly changes from one clock to eight. Here's an example that waits for a high-to-low edge and times out if it doesn't show up:

                mov     time,timeout         'Initialize timeout timer.
    :loop       test    pinmask,ina wz       'Test input pin. Is it low?
          if_nz djnz    time,#:loop          '  No:  Go back and check again.
                mov     time,cnt             'Save cnt value. (At this point the z flag will indicate whether we timed out or not.)
    pinmask     long    1 << pinno
    timeout     long    80_000_000 / 10      '1/10 sec. @ 80MHz
    time        res     1

    Solution: By enlisting the help of a counter, the granularity can be reduced back to one. In this example, the counter is set to count up by one every time it sees a low on the pin. By subtracting this count from the time, we get one-clock timing precision (to within a constant):

                mov     ctra,ctra0           'Initialize ctra to count lows on pin.
                mov     frqa,#1              'Make it count up by one.
                mov     time,timeout         'Initialize timeout timer.
                mov     phsa,#0              'Clear counter.
    :loop       test    pinmask,ina wz       'Test input pin. Is it low?
          if_nz djnz    time,#:loop          '  No:  Go back and check again.
                mov     time,cnt             'Save cnt value. (At this point the z flag will indicate whether we timed out or not.)
                sub     time,phsa            'Correct time for number of "lows" before reading cnt.
    pinmask     long    1 << pinno
    timeout     long    80_000_000 / 10      '1/10 sec. @ 80MHz
    ctra0       long    %01100 << 26 | pinno 'Count lows on pinno.
    time        res     1

  • jazzedjazzed Posts: 11,803
    edited 2009-01-31 - 17:43:06
    This may be obvious to some and has been covered·indirectly with Hippy's word/long mixing example,
    but it sure set me back a few hours. Say you want to save some hub memory by creating an array
    of words rather than longs for storing word-wide information, then use a method to·compare a value.
    If you don't explicitly cast the parameter in the body of the method, any comparison may fail as
    the incoming parameter value may contain bits of an adjacent word. Example:

      word mylist[noparse][[/noparse]LISTSIZE]
    PRI isListed(value) | n
      repeat n from 0 to LISTSIZE-1
        if mylist[noparse][[/noparse]n] == word[noparse][[/noparse]@value]  ' <-- Method can fail if you don't cast the argument.
          return true
      return false

    Added: The failing case is where the value being passed to isListed is the long return value of another method.


    Post Edited (jazzed) : 1/31/2009 10:57:43 PM GMT
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 22,785
    edited 2009-01-31 - 22:20:20

    This is true if you are passing data by reference, e.g. in_list := isListed(@valuearray + index). But if valuearray is declared as a word, then passing by value will work without the extra casting effort: in_list := isListed(valuearray[noparse][[/noparse]index]), assuming isListed is rewritten to work directly with a value and not an address.

Sign In or Register to comment.