This is my first post. I got trapped (for 2 days) and finally got out of it, so I figured I would share. I hope this hasn’t been posted about somewhere else (because I could have used it so much sooner)
I was trying to pass information from one cog to another, not a new concept. I was pretty sure I didn’t need semephores (though I experimented with them) since I was only passing one long at a time (using to send status flags back and forth between cogs).
Here’s what I was doing (wrong):
One cog would be reading a variable from main memory (to see if there was any pending tasks in the form of a flag in the variable). I passed the variable using par and indexed appropriately.
Something like:
:start Mov variable, par ‘get address of variable
Rdlong local_var, variable ‘put the value of that address in local_var
…..(program)
Jmp #:start ‘do it all over again (check flags again)
Local_var long 0
My other program would toggle the data on that same memory location (just as a test, a test that failed). The code would do some waitcnt stuff, then toggle the data on that memory, then loop. The command I used for writing was in the form of:
Wrlong cogvar, mem_loc ‘where the mem_loc is the same as variable in previous cog
This actually worked fine for a while of execution (of which I watched in GEAR). The symptom that this cuased baffled me. I practically watched what every relevant thing was doing in GEAR to try and pinpoint what was going wrong. At around clock tick 25000 or so, the main memory would just clear itself, almost all of it, including the cog0 program itself. In the end, I really couldn’t find out what exactly was causing the issue. But I did do something to erradicate it.
Instead of having my shared value in main memory declared in the VAR section. I declared it in a DAT section. I’m sure someone else could have told me this, but the forums overwhelmed me, it felt like it would have taken longer to search for it then just start hacking it myself.
Anyone know why this happened exactly (or if I may still be doing something wrong and am just lucky for now)?
The moral of the story (so far), declare shared memory between cogs using DAT, not VAR
There's nothing wrong with using VAR variables in the way you describe. It's done all the time. The only catch is that you have to make sure that both cogs are working with the same variable, since VARs are replicated among instances of the same object. But if both cogs are started with a pointer to the VARiable from the same object instance that delares the VARiable, that's not a problem. I suspect there's something else going on in your program that caused the problem you saw and, as you say, you just got lucky when switching to a DAT variable. I would suggest starting a new thread and posting your entire program thre so others can help you debug it.
Here is a trick that comes with plenty of its own traps.
There have been a number of posts where people are using djnz to decrement a pointer, and i thought i would try taking this a little further(sorry to anyone who may have come up with this before me). One can use the line:
djnz :loop, #:loop
to jump and post decrement the source register. one can use the following two instructions to find whether an array in cog memory contains a value (a)
:loop cmp a, 0-0 wc, wz
if_b djnz :loop, #:loop
this first executes the compare between a and whatever address is loaded into 0-0, then it decrements the address( after, due to pipelineing), and then executes the compare between a and the register with the next lowest index.
This does not have that many uses where it would be beneficial, but i am using it in a a cog that has 4 nested loops that each run up to 80 times, so a single instruction can add seconds to the completion time.
some things that make this harder to use are:
- the array must be loaded in with the first data point having the highest address
-once it has completed its operations one must use the movs instruction, and add one to find the address that contained the correct data.
Here is some working demo code for it that turns pin 5 high to demonstrate that it works:
CON
_clkmode = xtal1 + pll16x
_xinfreq = 5_000_000
pub main
cognew (@work,0)
repeat
dat
org 0
work movs :loop, #:d 'this gives the address after the main array
nop
djnz :loop, #:loop ' this jumps past the data, it is necessary to start with a djnz so the first cycle is not repeated twice
long 0 ' this fulfills the condition in :loop, and prevents it from doing strange things
long 1,2,3,4,5,6,7,8
:d long 9
:loop cmp a, 0-0 wc, wz
if_b djnz :loop, #:loop ' this works as a jump and post decrement the source register operation
movs b, :loop
add b, #1 ' this corrects for the post decrement of :loop
cmp b, #3 wz, wc 'these two lines verify that it has not stopped due to the zero at the end
if_e jmp #noend
movs :loop2, b
nop
:loop2 mov c, 0-0 ' this retrieves the number from the array
mov b, #1
shl b,c
mov dira, b
mov outa, b
noend jmp #noend ' prevents execution of further data
a long 5
b long 0
c long 0
I hope this helps someone, or is at least interesting.
waitvid takes 4+ cycles ([thread=121521]waitvid minimum timing[/thread]) but has a [thread=126874]blind spot of 3 cycles[/thread], i.e. a non-blocking waitvid isn't useful
ina/phsx/cnt are sampled during IdSDeR ([post=862241]Call for Clarity from Chip or Beau[/post])
the 1st hub slot starts 4 cycles into cog execution
the minimal waitcnt adjustment is 9 (or 5 if you re-order the instructions)
mov cnt, cnt ' current time
add cnt, #9{14} ' minimal advance (to avoid full range delay)
waitcnt cnt, increment ' run-through
mov cnt, #5{14} ' minimal advance (to avoid full range delay)
add cnt, cnt ' current time
waitcnt cnt, increment ' run-through
an instruction placed at $1FF (and then jumped to) isn't executed (probably due to rollover to $000)
- Update: execution is possible with a [thread=118374]phase jump[/thread]
never enable a counter with frqx != 0, you might get more than you asked for ([thread=103032]counter issue[/thread])
jmp phsx (counter enabled, frqx != 0) ([thread=118159]jumping counter[/thread])
- base = phsx at 2nd cycle of jmp (IdSDeR)
- jump target: base+2*frqx, next instruction at base+3*frqx+1
jmp cnt (like jmp phsx, frqx = 1)
jmpret dst, phsx|cnt will store $+1 as return address
phase jumping to a normal jmpret will cause base+3*frqx+1 being stored as return address
phase jumping to any instruction where base+3*frqx+1 == 0 will abort the instruction (nop)
waitvid colors, pixels wr will perform colors += pixels
waitpeq target, mask wr will perform target += mask
waitpxx/waitcnt structure is IdSDwm.R
- w is the stage that is propagating the condition
- m is the earliest match point that the WAIT circuit can test
- afterwards (after the match), the ALU and the rest of the chip wakes up and performs, followed by R
starting cogs N+1..N+3 misses their hub window and is therefore slower than N+4..N+7
- based on measuring cnt difference just before sync'd coginit and first instruction in cog
- core overhead 8K+4 (8+511*16+8+4), extra +0..+7: $08 $0A $0C $0E $00 $02 $04 $06
- additional cycles: ((T - L + 4) & %111) * 2; (T)arget, (L)auncher
it may be impossible to catch DUTY pulses generated by a counter within the same cog using waitpeq (e.g. Demoboard, pin16/cog4)
same issue applies to DUTY coupled counters (pulse width may be seen ranging from 0..2), so also [post=914151]this comment[/post] regarding pulse sampling
wc effect for mov[dis] and jmp[ret] is unsigned borrow
wc effect for rdxxxx/wrxxxx, clkset, cogid and cogstop is the same as for coginit (no cog free)
PLL output is sync'd to the rising edge of the feeder NCO (starting with the low half of the cycle). Note that /32, /64 and /128 can lock to any rising edge (relative to NCO start), e.g. /128 can lock to 8n+0 .. 8n+7 meaning that starting a PLL in two different cogs will - despite NCOs being in sync - not necessarily sync the PLL output.
Want to communicate immediately between cogs without using hub ram or waiting for the hub cycle? Use the I/O pins. Even if a cog sets a pin to an output, you can still read it as an input. All the cogs are wired to all the pins 100% of the time, like a bus kinda.
example
PRI pincounter
dira[3..0]~~
repeat
waitcnt(clkfreq+cnt)
outa[3..0]++
now run pincounter in cog 1, and some other routine in cog2. Cog1 will be counting in binary on pins 3..0. When our other routine in cog2 wants to know how far cog1 has counted, all it has to do is
value:=ina[3..0]
and it should not have to wait for the hub timer.
I don't know, am I full of BS? This appears to work when I build/code it, the question is is it really immediate, or even practical.
Of course now you have to be careful with latency, collisions etc but you could potentially use a single pin as a lock bit, just like the regular locks in the hub. Obviously you're limited to a maximum of 32 bits in parallel transmitted in this way, but you could have one cog serially piping data to another cog using just a few pins and neither of them have to wait for anything.
That said, this is probably only useful for byte size values or smaller. If you're pumping data serially, it will take at least as many clocks to receive all 16 bits as it would to just wait for the hub timer to come back around, unless you use 16 or all 32 i/o pins. But using 1-8 pins, it appears you can beat the hub timer and communicate these values immediately.
Why anybody would need to do something like this, I don't know, but it is an interesting feature.
Gonna have to try this out with ASM and pay close attention to the timings.
Phipi, here's another trick I submit for checking limits at compile time.
If you have some calculations/settings in a CON section you can use a DAT section to verify limits or several conditions in the following way:
DAT
ORG 1
FIT logical_condition '(whatever logical condition you wan to be TRUE)
FIT logical_condition_2 'yet another condition to be met
FIT logical_condition_3 'as many as you need as long as it evaluates to TRUE/FALSE
That way if your logical condition(s) evaluates to FALSE (== 0) you get a compile error stating that you exceeded the allowed size but what really means is that you missed the condition.
This let's the programmer state the conditions that must be met for a correct configuration of constants in the object and rely on the compiler (not the user) to catch them.
I don't know if this has been discussed before but I found this feature handy for my needs.
That could be useful actually. Thanks, Alex.
For example, I have notes on how people should select pins for my FlashPoint, but having it give an error if they do it wrong is nice.
Does the Prop tool automatically scroll to the line that gives the error?
Rayman, yes it does. In case of error you will be looking at the line where the logical condition failed; just after the condition and will have the rest of the line highlighted.
Could you post an example snippet. I'm having a hard time picturing this format but use allot of configuration parameter in the CON section.
dp
Here's a snippet of a stepper driver I'm working on
CON
_xinfreq = 5_000_000
_clkmode = xtal1 + pll16x
'
#0, _No, _Yes
'
'Enable lines & features ----------------------------------------------------------
'
_Enable_Break = _Yes 'Are Break lines connected?
_Enable_Right = _Yes 'Is there a right motor installed?
'
'Motor characteristics -------------------------------------------------------------
_Irated = 2100 '(mA) The rated max current for your stepper motor
_RS = 2800 '(Ohms) Value for the current sensing resistor
'
_dutyrt = 256 * _RS / _RatedTorqueValue * _Irated / 3300
'
'
DAT
org 1 'Dummy start address to force checks below
'┌─────────────────────────────────────────────────────────────────────────────────────────────────────────┐
FIT _dutyrt < 65538 'See below │
'│^^^ ERROR - if you get a compile error in the above line it's because you missed the calculation │
'│ for _dutyrt and it got bigger than 65537. Adjust either Irate or change your RS value in the circuit │
'└─────────────────────────────────────────────────────────────────────────────────────────────────────────┘
'┌─────────────────────────────────────────────────────────────────────────────────────────────────────────┐
FIT (_Enable_Break==_Yes)|(_Enable_Right==_No) 'See below │
'│^^^ ERROR - if you get a compile error in the above line it's because you enabled the right│
'│ motor AND you didn't enable the break lines │
'│ (they are needed to block one motor when you drive the other) │
'└─────────────────────────────────────────────────────────────────────────────────────────────────────────┘
I posted two different and unrelated conditions to check at compile time
1- Given some user settable parameters (_RS & _Irate) I'm checking value limits on a calculated constant (_dutyrt)
2- Given some flags (_Enable...) by design if I have a right motor installed I MUST have the break lines manged since some other control lines are shared. This must be checked at compile time to avoid hard to find bugs.
In case of missconfiguration it won't compile and the PropellerTool will scroll to the offending line. A brief explanation is sorrounding the check to make it easier for the user to understand and correct the error. You DAT section can be empty of code or variables as you see; and it saves me a lot of trouble
That's brilliant. Makes me think it's time for an obfuscated Spin competition:)
Heater, I'm honored by your comment, thanks a lot!
Regarding the spin competition I believe it's a great way to learn. I've learned a lot by browsing the forum. It's an amazing resource and an excellent source of "crowd sourcing".
I didn't think there were any left! But I've been working on a new product whose development requires frequent enabling and disabling blocks of Spin/PASM code as I fumble my way through the labyrinth. Typically, commenting out a block of code is done by enclosing it in braces: {disabled code}. To reenable a block, you delete both braces.
By accident, I discovered that if the closing brace is commented with an apostrophe '}, you don't need to delete it. This is because Spin comments have a pecking order. Apostrophes inside braces just become part of the block comment without further significance, so the closing brace after the apostrophe is significant. But without the opening brace, an apostrophe-commented closing brace has no special meaning; it's just part of the comment. So all you have to do to disable or enable a block of code ending in '} is to insert or delete the opening brace.
Okay, okay, it's a small thing, but it made my day.
It is a good trick, and as bst does the rigth thing (as it should) and switches the colouring to and from comment-colour correctly by just changing the first brace -- very useful.
Time to revive this ol' thread yet again with an addition of mine.
Given that I don't think I've read any of these before, I'd like to think I came up with them myself, but maybe it's just so obvious that no one bothered to write it down before. Or maybe I looked past it.
There exists many a situation in which integers have to be compared. Luckily, the propeller has a bunch of CMP* instructions to do this. Here's some interesting ways to use them:
(Note that the range 1...5 includes 1,2,3 and 4, but not 5!)
Is signed integer x in range 0...y ?
This one is obvious, but let's include it nonetheless: Negative values, when treated as unsigned, are very large.
cmp x,y wc
if_c jmp #condition_met
Is signed integer x in range (-y)...0 ?
This one is perhaps less obvious, with it (ab)using the ADD instruction,
but everything starts to make sense when you realize that CMP is actually just SUB NR in disguise.
add x,y wc,nr
if_c jmp #condition_met
This of course also works with ADDABS if you need your range to be y...0 (assuming y < 0)
Logical AND of multiple compares
Also farily obvious. All compares but the first are executed only if the previous compare had the desired result. As soon as one fails, all subsequent compares are skipped over.
This will jump if x1 < y1 AND x2 < y2 AND x3 < y3:
This concept can also be applied to other instructions that produce a carry/zero output, not just CMP/CMPS and not just IF_C.
However, all instructions must produce the same "desired" flag results!
Speaking of which...
Is unsigned integer x in range a...b ?
It turns out that CMPSUB's carry out is inverted which makes it perfect for range checking.
Comments
I was trying to pass information from one cog to another, not a new concept. I was pretty sure I didn’t need semephores (though I experimented with them) since I was only passing one long at a time (using to send status flags back and forth between cogs).
Here’s what I was doing (wrong):
One cog would be reading a variable from main memory (to see if there was any pending tasks in the form of a flag in the variable). I passed the variable using par and indexed appropriately.
Something like:
My other program would toggle the data on that same memory location (just as a test, a test that failed). The code would do some waitcnt stuff, then toggle the data on that memory, then loop. The command I used for writing was in the form of:
This actually worked fine for a while of execution (of which I watched in GEAR). The symptom that this cuased baffled me. I practically watched what every relevant thing was doing in GEAR to try and pinpoint what was going wrong. At around clock tick 25000 or so, the main memory would just clear itself, almost all of it, including the cog0 program itself. In the end, I really couldn’t find out what exactly was causing the issue. But I did do something to erradicate it.
Instead of having my shared value in main memory declared in the VAR section. I declared it in a DAT section. I’m sure someone else could have told me this, but the forums overwhelmed me, it felt like it would have taken longer to search for it then just start hacking it myself.
Anyone know why this happened exactly (or if I may still be doing something wrong and am just lucky for now)?
The moral of the story (so far), declare shared memory between cogs using DAT, not VAR
-Phil
Thats how I did it in my projects program.
http://forums.parallax.com/forums/default.aspx?f=21&m=376422&p=1&ord=a
Using DAT for variable space is a topic that DOES NOT get enough attention in the manual or education kit.
There have been a number of posts where people are using djnz to decrement a pointer, and i thought i would try taking this a little further(sorry to anyone who may have come up with this before me). One can use the line:
djnz :loop, #:loop
to jump and post decrement the source register. one can use the following two instructions to find whether an array in cog memory contains a value (a)
:loop cmp a, 0-0 wc, wz
if_b djnz :loop, #:loop
this first executes the compare between a and whatever address is loaded into 0-0, then it decrements the address( after, due to pipelineing), and then executes the compare between a and the register with the next lowest index.
This does not have that many uses where it would be beneficial, but i am using it in a a cog that has 4 nested loops that each run up to 80 times, so a single instruction can add seconds to the completion time.
some things that make this harder to use are:
- the array must be loaded in with the first data point having the highest address
-once it has completed its operations one must use the movs instruction, and add one to find the address that contained the correct data.
Here is some working demo code for it that turns pin 5 high to demonstrate that it works:
I hope this helps someone, or is at least interesting.
- Update: execution is possible with a [thread=118374]phase jump[/thread]
- base = phsx at 2nd cycle of jmp (IdSDeR)
- jump target: base+2*frqx, next instruction at base+3*frqx+1
- w is the stage that is propagating the condition
- m is the earliest match point that the WAIT circuit can test
- afterwards (after the match), the ALU and the rest of the chip wakes up and performs, followed by R
- based on measuring cnt difference just before sync'd coginit and first instruction in cog
- core overhead 8K+4 (8+511*16+8+4), extra +0..+7: $08 $0A $0C $0E $00 $02 $04 $06
- additional cycles: ((T - L + 4) & %111) * 2; (T)arget, (L)auncher
Useful code fragments
Want to communicate immediately between cogs without using hub ram or waiting for the hub cycle? Use the I/O pins. Even if a cog sets a pin to an output, you can still read it as an input. All the cogs are wired to all the pins 100% of the time, like a bus kinda.
example
now run pincounter in cog 1, and some other routine in cog2. Cog1 will be counting in binary on pins 3..0. When our other routine in cog2 wants to know how far cog1 has counted, all it has to do is and it should not have to wait for the hub timer.
I don't know, am I full of BS? This appears to work when I build/code it, the question is is it really immediate, or even practical.
Of course now you have to be careful with latency, collisions etc but you could potentially use a single pin as a lock bit, just like the regular locks in the hub. Obviously you're limited to a maximum of 32 bits in parallel transmitted in this way, but you could have one cog serially piping data to another cog using just a few pins and neither of them have to wait for anything.
That said, this is probably only useful for byte size values or smaller. If you're pumping data serially, it will take at least as many clocks to receive all 16 bits as it would to just wait for the hub timer to come back around, unless you use 16 or all 32 i/o pins. But using 1-8 pins, it appears you can beat the hub timer and communicate these values immediately.
Why anybody would need to do something like this, I don't know, but it is an interesting feature.
Gonna have to try this out with ASM and pay close attention to the timings.
If you have some calculations/settings in a CON section you can use a DAT section to verify limits or several conditions in the following way:
DAT
ORG 1
FIT logical_condition '(whatever logical condition you wan to be TRUE)
FIT logical_condition_2 'yet another condition to be met
FIT logical_condition_3 'as many as you need as long as it evaluates to TRUE/FALSE
That way if your logical condition(s) evaluates to FALSE (== 0) you get a compile error stating that you exceeded the allowed size but what really means is that you missed the condition.
This let's the programmer state the conditions that must be met for a correct configuration of constants in the object and rely on the compiler (not the user) to catch them.
I don't know if this has been discussed before but I found this feature handy for my needs.
Best regards
Alex
-Phil
For example, I have notes on how people should select pins for my FlashPoint, but having it give an error if they do it wrong is nice.
Does the Prop tool automatically scroll to the line that gives the error?
Regards, Alex
Could you post an example snippet. I'm having a hard time picturing this format but use allot of configuration parameter in the CON section.
dp
Here's a snippet of a stepper driver I'm working on
I posted two different and unrelated conditions to check at compile time
1- Given some user settable parameters (_RS & _Irate) I'm checking value limits on a calculated constant (_dutyrt)
2- Given some flags (_Enable...) by design if I have a right motor installed I MUST have the break lines manged since some other control lines are shared. This must be checked at compile time to avoid hard to find bugs.
In case of missconfiguration it won't compile and the PropellerTool will scroll to the offending line. A brief explanation is sorrounding the check to make it easier for the user to understand and correct the error. You DAT section can be empty of code or variables as you see; and it saves me a lot of trouble
Regards, Alex
That's brilliant. Makes me think it's time for an obfuscated Spin competition:)
Heater, I'm honored by your comment, thanks a lot!
Regarding the spin competition I believe it's a great way to learn. I've learned a lot by browsing the forum. It's an amazing resource and an excellent source of "crowd sourcing".
Regards Alex
By accident, I discovered that if the closing brace is commented with an apostrophe '}, you don't need to delete it. This is because Spin comments have a pecking order. Apostrophes inside braces just become part of the block comment without further significance, so the closing brace after the apostrophe is significant. But without the opening brace, an apostrophe-commented closing brace has no special meaning; it's just part of the comment. So all you have to do to disable or enable a block of code ending in '} is to insert or delete the opening brace.
Okay, okay, it's a small thing, but it made my day.
-Phil
Does it go into Tricks and Traps?
Jim
Helps to read other's code sometimes
I've been using that trick since 2009.
-Phil
-Phil
-Tor
If you like to remember, where the opening bracket was, don't delete but comment it
Given that I don't think I've read any of these before, I'd like to think I came up with them myself, but maybe it's just so obvious that no one bothered to write it down before. Or maybe I looked past it.
There exists many a situation in which integers have to be compared. Luckily, the propeller has a bunch of CMP* instructions to do this. Here's some interesting ways to use them:
(Note that the range 1...5 includes 1,2,3 and 4, but not 5!)
Is signed integer x in range 0...y ?
This one is obvious, but let's include it nonetheless: Negative values, when treated as unsigned, are very large.
Is signed integer x in range (-y)...0 ?
This one is perhaps less obvious, with it (ab)using the ADD instruction,
but everything starts to make sense when you realize that CMP is actually just SUB NR in disguise. This of course also works with ADDABS if you need your range to be y...0 (assuming y < 0)
Logical AND of multiple compares
Also farily obvious. All compares but the first are executed only if the previous compare had the desired result. As soon as one fails, all subsequent compares are skipped over.
This will jump if x1 < y1 AND x2 < y2 AND x3 < y3: This concept can also be applied to other instructions that produce a carry/zero output, not just CMP/CMPS and not just IF_C.
However, all instructions must produce the same "desired" flag results!
Speaking of which...
Is unsigned integer x in range a...b ?
It turns out that CMPSUB's carry out is inverted which makes it perfect for range checking. Of course, you can AND two of these together as decribed above for a 2D "Is point inside box?" check. I love this little idiom, which is the whole reason I posted this, lol.