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
[i]statements[/i]
is equivalent to -
indexVar := startValue
repeat
[i]statements[/i]
if endValue => startValue
indexVar := indexVar + stepValue
else
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.
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!
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
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 .
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.
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
quickly.
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
repeat
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
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.
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).
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: "...so 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)
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.
pems said...
i got confused by the following though: "...so 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 ...
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.
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.
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
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)
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.
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
[size=2][code]
'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
'---------------------------------------------------------------------
[/code][/size]
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)
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.
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
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.
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...
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.
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
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:
VAR
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.
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.
Comments
The following will not do as it would be expected to do in other programming languages -
The interpreter implementation of -
is equivalent to -
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.
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!
-Phil
It should take them weeks rather than days to debug their programs
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 .
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.
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
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:
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.
-Phil
'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
quickly.
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.
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.
td
...
movd :arg, #arg0
:arg mov t2, t1
...
i am guessing that's a mistake? or am i way off base?
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: "...so 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)
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.
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 ...
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.
I spent hours on this one. Obvious when in isolation here but not when embedded in 3,000 line of code ...
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
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
Michael Park
PS, BTW, and FYI:
To search the forum, use search.parallax.com (do not use the Search button).
Check out the Propeller Wiki: propeller.wikispaces.com/
Post Edited (mpark) : 9/8/2008 6:59:32 AM GMT
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
For me, the past is not over yet.
@ Heater : Entirely contrived.
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
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,
It'd be great to figure out a single instruction for saving both bits, but I don't think it's possible.
-Phil
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
'Still some PropSTICK Kit bare PCBs left!
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 alternative example reverses the position of the c and z flags and keeps them in bits 1 & 0
[/code][/size]
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
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
'Still some PropSTICK Kit bare PCBs left!
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.
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.
-Phil
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
'Still some PropSTICK Kit bare PCBs left!
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
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
'Still some PropSTICK Kit bare PCBs left!
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:
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):
-Phil
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:
Added: The failing case is where the value being passed to isListed is the long return value of another method.
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
--Steve
Post Edited (jazzed) : 1/31/2009 10:57:43 PM GMT
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.
-Phil