Variable Magic, episode 9

On page 8 of the one-and-only AppNote on the Propeller, in the SPIN portion of the code, there is a line like this:
Later, near the bottom of the DAT section, there is this line:
repeat x from 0 to period
Later, near the bottom of the DAT section, there is this line:
period long 100How does the SPIN interpreter running in one cog have direct access to the RAM of a second cog?
Comments
Years of conventional programming has succeeded in programming me.
All SPIN variables sit in Hub RAM and are shared among all cogs.
-Phil
That's exactly my point about this being a crazy scheme. Where else in all of computerdom does a single variable become two variables in the same listing without the slightest name change? For experienced proppers, this likely seems normal. For me, the quintessential prop outsider, I look at such a piece of code and shake my head.
If I end up using this sample code from AN001 in my application, changing that 'feature' will be the very first thing I do. For my own sanity if nothing else, I would NEVER write a piece of code with two variables parading as one. It's a recipe for debugging hell.
-Phil
Hmm. Is it possible to get 8 instances of the same PASM program running in all 8 cogs?
-Phil
cognew (...) ' start program in next cog (cog 1)
cognew (...) ' start program in next cog (cog 2)
cognew (...) ' start program in next cog (cog 3)
cognew (...) ' start program in next cog (cog 4)
cognew (...) ' start program in next cog (cog 5)
cognew (...) ' start program in next cog (cog 6)
cognew (...) ' start program in next cog (cog 7)
cognew (...) ' starts program in cog 0, overwriting the spin interpeter
Crazy, no. A little confusing, perhaps. A variable has been able to become two or more variables, without a name change, ever since the beginning of time. Well, the invention of UNIX anyway.
UNIX creates processes with the fork() function. Basically if your code executes fork() an entire second copy of your program starts running as a separate process in a separate memory space. That includes the duplication of all variables. From fork() onwards the duplicate variables, with the same names, are updated by each process totally independently.
Here is a simple example:
If you run this you will see the variable "b" is either 1000001 or 1001 depending on which copy we are looking at.
As for the Prop, starting a new PASM COG does a similar thing. All of a sudden all that stuff in a DAT section is now duplicated into a separate memory space, in the COG in this case.
If you look a little at the Prop architecture and what COGINIT COGNEW do then there is no surprise in any of this variable duplication. It's a natural, logical, consequence of the architecture.
P.S. By the way all local variables to functions, procedures, methods in many different languages become new copies of themselves with different values every time the function is called. So the same variable name is a different thing on each invocation.
Actually I'm inclined to agree with you. But perhaps for different reasons.
If a PASM code gets all of it's knowledge of the outside world through PAR parameters. And then communicates with the world via a memory area, "mailbox", whose address was given in a PAR block. Then that PASM code can be used by languages other than Spin. For example Prop BASIC, or C with Catalina or Zog.
If that PASM block has named variables in DAT being set up by Spin code prior to starting it then it is harder to use from non-spin languages as they don't have the "linkage" with it's name in Spin.
I wish all objects, in OBEX say, were written with out the need for this Spin linkage. Sadly they are not and using them with Zog is a pain or other system is a pain.
Everytime I get something useful built with another processor, my next question is how to do the same thing with the Prop. It has been a fascinating, and sometimes frustrating, endeavor. The Prop has such unique solutions, and yet there always seems to be a way to get from here to there. Meanwhile, the counter hardware is no small portion of the power of the Prop, and there are eight complete copies of it! That's pretty impressive.
This is a good point. Are there guidelines on how to write pasm programs that are easy to use with other languages and do those languages have a consistent interface or is each one unique?
As such there are no guidelines other than the general idea that I outlined above. So let's put it like this, a PASM object that is to be easily usable with any language and/or operating system should:
1) Take all of it's required initial configuration parameters via data passed to it via a PAR pointer.
2) Should interact with software external to it only via a shared memory area.
3) The address of that shared memory should be passed to the PASM object as one (or more) parameters passed in via PAR at start up (COGNEW, COGINIT)
It should not:
1) Rely on some Spin code setting up parameters within its DAT area prior to being loaded.
The point is that whilst we can expect any language to support COGINIT/COGNEW and PAR, in some way, we cannot expect them to be able "poke" values into DAT blocks prior to starting the PASM in a COG. Reason being that they have no "linkage" to the locations of those values.
This has repercussions even when working in Spin. If all PASM code was free of any linkage with any Spin object then it could be compiled separately from the Spin. Then it could be included into the complete program at any point in the code via a "file" statement. Imagine, all the PASM you need living in the same known memory area, which could be reused after the COGs have started for, say, video buffer space.
BSTC, and I believe HomeSpun, can already compile Spin objects and extract just the assembled PASM part out of them. So we are part of the way there already. I have made use of this with Zog but most objects seem to need some modification to remove that Spin "linkage". Catalina has been through all this already.
There are perhaps other features of reusable PASM that I have missed, and I know Bill Henning has a even more strict ideas in mind for his Largos operating system.
But you really have to think of the propeller as 8 completely separate microcontrollers that can talk and share things. You really just have to get used to all the nuances that the propeller has in it's parallel processing capabilities. In this example, all SPIN cogs see the DAT value as as single value, but PASM would view it separately in each individual cog.
So maybe, I should change what I said a bit. In PASM it is 8 completely separate parallel microcontrollers, but in SPIN they are 8 linked semi-parallel microcontrollers. But the main difference is only how the two languages reference memory and execute code.
Not so much because it is difficult to do, but rather because there are so many ways it could be done, and most of those ways not optimum for many languages.
A lot of my objects violate this suggestion. For example, I do this a lot:
...where US_001 is a value for the cog I'm about to launch. Do you suggest I load that value into a hub variable and then transfer that via the PAR linkage? While it seems redundant, I do see the value in allowing the object to run under other languages.
1 - Passing initial parameters to pasm programs.
2 - Passing data and parameter changes to pasm programs.
3 - Passing data from pasm programs to "whatever language" program in hub.
4 - Where to put parameters in hub memory (top, bottom, scattered in program, etc.)
There are repercussions for these decisions to both pasm programs and the language compiler.
That's exactly the suggestion. The point is to assume that the PASM/DAT a "black box", a binary blob that you load into a COG, that you don't know the location of any such variables/parameters with it.
This seems like it wastes LONGS in the PASM to get those parameters from PAR at start up. But I have found that such intitialization/setup code space can be reused as variable space when the actual guts of the PASM is running. Zog makes good use of this recycling idea.
kwin:
Easy enough, just pass params in via a PAR block as is commonly done anyway.
Data is easy. Just do it through the shared memory area the address of which was passed in through the PAR block. This is commonly done already as far as I can tell.
Parameter changes is a bit more trick. Normally PASM does not support this (does it) for example to change the baud rate in FullDulexSerial you have to stop it and start it agin with a new baud rate.
If parameter changes on the fly are really needed they can be done through a COMMAND interface in the shared memory area.
Just do it through the shared memory area as normal. For example in Zog the C program knows where the FullDuplexSerial buffers are in memory and their head and tail pointers so it can just add and remove bytes in the same way the FDX Spin code does. The PASM need not know or care what language "out there" is doing that.
In normal Spin/PASM objects so far, the shared memory area is defined in the Spin as a VAR or DAT area and is therfore at some random location in HUB. Better that the "start" method of such objects is give the shared memory area location as parameter by the client object. The start method then passes that on to the PASM through PAR.
Bill Hennings VMCOG shows a good example of doing this.
This way the client code, in what ever language, can decide for itself where the shared memory area is.
P.S. There should probably be a "zeroth rule" for all of this:
0) The PASM code will not read/write any HUB locations, except those whos addresses are passed in through PAR.