Cogs running in parallel - Newbie stuff!
Your typical useless local newbie here again, and I'm looking for information on using the major power of the Propeller2 chip - the multiple cogs running in parallel!
Example code and/or a detailed tutorial would be nice, and some pointers would help too!
For background, all this is supposed to be done in Spin2, tested there (on a P2 Edge w/ breadboard) then compiled to make it faster, but the whole point is to get things done in parallel.
Imagine, if you will, an orchestra. Pseudo-code:
In Cog 0, the Conductor raises the baton, and brings it down! Upon that signal
Cog 1, the violins go scrape
Cog 2, the woodwinds go tweet
Cog 3, the horns go honk
and in Cog 4 the tympani goes BOOM;
Obviously these should all happen in parallel. Each cog needs to share some data (e.g. the string "Beethoven's 5th") but not much (in my application, anyhow).
However, I'm terribly unclear on just how to put code in cogs (put code in the hub?), share data between cogs (put it into the hubram?) and fire off one signal that all the other cogs should pay attention to (cogatn?). If you stop a cog does it keep all its memories for the next start? Can you make them loop endlessly until the signal comes again or is restarting a better way?
I have code in Spin2 for Conductor(), violins(scrape), woodwinds(tweet), &c., but all running in sequence in Cog 0, making for lousy music*. Unfortunately, my search-fu upon the forum isn't coming up with much on how to parallel them - perhaps there's a thread or a project somewhere I've missed?
Anyhow, thanks much for what I have been able to find! I'll keep poking around. S.
- Note: References to music are strictly for illustrative purposes. I'm not actually trying to do MIDI (or even video) here, just embedded parallel processing.
Comments
There are lot of possibilities.
The simplest is:
cogspin(cog#,,violins(),@sviolinstack)
It starts the function violins() in the cog number cog# using a stack space in violinstack() which you have to declare
You can start all your 4 functions in 4 cogs that way.
They will run until you use cogstop (cog#)
The cog can stop itself using cogstop(cogid)
Better don't stop and restart cogs if you don't want to change cog's function. You can always make it sleep and wait for an event.
They can talk to each other using:
This is a Propeller. There is always another method to do something.
A spin cog also stops itself when the function it is started with ends/returns
I don't really want lots of ways! I get enough of that from Windows (16 ways to do 16 different things, but lord help you if you want to do a 17th...) I'd like one way - or at least a comparison job of the multiple ways. Perhaps JonnyMac needs to do another article for Nuts&Volts
This looks excellent, but how do I determine "sviolinstack"? In my real application I don't need much space (a dozen longs, perhaps, not counting subroutine calls), although I'll need some, and I want it to be fast, and I'll eventually need to feed a ring buffer into it (from chips external to the Prop(!)) but baby steps here. I think I can keep track of cog numbers in my head - once determined, they won't change, and there's no need to spontaneously start new ones (unless the Pink Panther invades my orchestra!).
I'm thinking to throw 'flags' around, I'd be interested in the 16 'semaphores', sort of like, in the cog:
10 repeat while !(downbeat)
20 silence
30 Then
40 Play note, and goto 10
That way the cog just keeps running. An interrupt would be even better.
And it leads to all sorts of other fun stuff. In Spin2, how does one say "Put this variable in HubRam?" and how to make a cog play "fetch" (if I may... ) Sometimes the shared data will be the same, so re-fetching it's a waste. Sometimes it'll change, so has to be re-fetched (another flag?). I understand the documentation's a bit of a work in progress - hopefully I can figure it out and maybe writing it down will help. Thanks again, S.
Declare a spin variable in var section. 64 longs should be enough.
If the procedure is started by cogspin, you don't need to do anything to get your variable, simply use it.
from cog1:
i:=1
From cog 2
If you want to use your cog in cog ram mode with asm written code, you can pass the pointer to the cog via PTRA register
There is a lot of examples added to the Propeller Tool and Flexspin: read them.
Awesome! Looks good, I'll try it. I hope I won't need incessant hand-holding all the way, but old dog, new tricks. I'll update and get back to you when it's behaving itself. Thanks! S.
Hm. Some success - I wrote up some cog code, and it runs in its own cog happily enough. But then I spun up three more, that do basically the same thing on different data, and they trample on each other. Running each one separately they each come out with the correct results (I'm feeding them pre-calculated test numbers) but as soon as I spin up two or more, the results are all off.
cogspin(1,Ax0_1(),@stack0)
'cogspin(2,Ax2_3(),@stack2)
'cogspin(3,Ax4_5(),@stack4)
'cogspin(4,Ax6_7(),@stack6)
Each on their own gives the correct results! (and the test data is such that all the results are different), but un-commenting any two (or more) of those lines causes all of them to give incorrect results. They're consistent between runs, and uncommenting different combinations gets you different numbers, but they're all wrong. Changing the stack size (I tried 32, 64, and 128) doesn't seem to make any difference.
Obviously more poking at it is called for. Thanks for the headstart! S.
ETA: Using entire bytes as flags seems to have helped a lot, although the waste bugs me a bit - I like using bits in a byte (or word, or long!) as flags, but I suspect it meant that particular byte got passed around through all the cogs, and sometimes showed up improperly updated. Ah well, seems happier now (although running it through the flexprop compiler cooked up all sorts of garbage. Wow. Not done yet...)
Paste your whole code between three backticks begore and after you code. Then we might be able to help.
As for flags v bytes v longs, best to use bytes or longs. When you get to pasm, remember code and variables in cog and lut ram can only be longs.
You have to remember that if several cogs works on one variable, side effects may occur
If 2 cogs will do
rdlong something, somewhere
add something, 1
wrlong something, somewhere
you may end with added 1 instead of 2.
There are hardware locks available
Every time I try to paste code here it turns into a disaster area, trashing the format something fierce (CR/LF issues, among other things). But I'll keep trying.
Most of the compiler weirdness turned out to be that it seems FlexProp & flexspin does NOT automatically initialize all variables to zero while the Propeller Tool does, so I got to stick in a lot more declarations of zero, and it got happier. Probably a good idea anyhow.
Oddly enough I was getting bigger numbers than I expected - 999 was expected on one channel, and I kept getting things from 1,090 to 1,300-ish - they were all high by about the same percentage. But they're happy now. I considered lots of locks, but that would sort of defeat the parallelism. Lots of bytes worked (Effectively, I've cloned the conductor 16 times, and now there's sixteen near-simultaneous downbeats... )
And for now I'm sorta trying to avoid PASM, not because it's bad in any way (I like assembler - wrote quite a lot of it for AVRs) but it's a learning curve I'll climb later. For now, compiled spin is fast enough (interpreted spin might be fast enough, but it's looking highly marginal. The 3-4x speedup the compiler seems to get me looks like it'll do fine). S.
VAR variables might be 0 after load but not after reset, they keep their current value. DAT variables are set by yourself.
local variables in a method are NOT initialized just return values are set to 0 by default.
This behavior is identical in SPIN1, SPIN2, PropTool, Pnut and FlexProp.
Enjoy!
Mike
VARs are always initialized to zero and there's plenty of code that would not work if that wasn't the case
I assumed that too, but was surprised that a reset (or soft reset via cmd) does not clear the hub RAM, neither on P1 or P2.
It might be that the SPIN interpreter clears the VARs on load of the object, have not tested it, RES in PASM contains whatever is behind the DAT section since a COG always loads the full COG ram regarding of PASM size.
Anyways it is a good thing in programming to initialize vars in general and not assume any given start value.
Enjoy!
Mike
This is readily testable, so I did. Here is the compleat code, three backticks 'n all**:
```' Move tinkering
CON
_clkfreq = 350_000_000 ' Why not run as fast as we can? (350M)
DEBUG_BAUD = 2_000_000
VAR
stack0[32]
stack1[32]
stack2[32]
stack3[32]
BYTE S0
BYTE S1
BYTE S2
BYTE S3
BYTE S4
BYTE S5
BYTE S6
BYTE S7
BYTE B0
BYTE B1
BYTE B2
BYTE B3
BYTE B4
BYTE B5
BYTE B6
BYTE B7
BYTE Z0
BYTE Z1
BYTE Z2
BYTE Z3
BYTE Z4
BYTE Z5
BYTE Z6
BYTE Z7
PUB main()
S0 := 1 ' Setting all the S bytes to a fixed value
S1 := 2 '
S2 := 3 '
S3 := 4 '
S4 := 5 '
S5 := 6 '
S6 := 7 '
S7 := 8 '
' But deliberately NOT setting the B bytes to zero!
' But deliberately NOT setting the Z bytes to zero!
' Edge board LEDs:
pinl(57) ' On program end, these both reappear.
pinl(56)
' Cog 0 is us here now
'cogspin(1,A0(),@stack0)
'cogspin(2,A1(),@stack1)
'cogspin(3,A2(),@stack2)
'cogspin(4,A3(),@stack3)
repeat 500 ' For now, to make it give up eventually
SplusB()
debug(udec_byte(S0), udec_byte(B0), udec_byte(Z0))
debug(udec_byte(S1), udec_byte(B1), udec_byte(Z1))
debug(udec_byte(S2), udec_byte(B2), udec_byte(Z2))
debug(udec_byte(S3), udec_byte(B3), udec_byte(Z3))
debug(udec_byte(S4), udec_byte(B4), udec_byte(Z4))
debug(udec_byte(S5), udec_byte(B5), udec_byte(Z5))
debug(udec_byte(S6), udec_byte(B6), udec_byte(Z6))
debug(udec_byte(S7), udec_byte(B7), udec_byte(Z7))
' More Edge board LEDs
repeat '
pint(56) ' Just leaving it here after we're all done
waitms(1000)
pint(57)
waitms(1000)
PUB SplusB()
Z0 := S0 + B0
Z1 := S1 + B1
Z2 := S2 + B2
Z3 := S3 + B3
Z4 := S4 + B4
Z5 := S5 + B5
Z6 := S6 + B6
Z7 := S7 + B7
```
I left in some cogspins (commented out) because I thought it might make a difference, but it doesn't.
The Propeller Tool (2.5.3) gives the expected results if the variables are zeroed:
When the code is merely cut and pasted into FlexProp (5.2.0), without editing, the results are garbage*:
Turning the Propeller Edge off and on again seemed to make basically no difference. Perhaps I need a newer version of FlexProp. Have fun, all. S.
** changing the tick direction changed not a thing.
Yes, one of the first rules!
Three backticks must be on their own on a line:
'anks. Learn something new every day. S.