Please notice that my use of a HUB memory register to exchange information between COGS in my last program posting was disapproved of with some emphasis. (Though how to do just that has just been discussed).
No it has not "just been discussed".
It has been discussed in over a dozen posts in this thread because the idea is a core concept.
Heater:
Please notice that my use of a HUB memory register to exchange information between COGS in my last program posting was disapproved of with some emphasis.
I didn't take any of the above posts as disapproval to use HUB memory to share information between cogs. In fact, according of one of the other threads that's pretty much the main way of doing it.
What I understood disapproval was aimed at was the arbitrary choice of a HUB memory address when there is a mechanism to do this via PAR.
I saw someone call PAR a register only and that over-writing it doesn't work because it's "read-only" or something.
I thought that PAR was a compiler symbol that was evaluated at the time of assembly? I don't see any reason why anything other than the compiler would need to even know about the symbol.
I thought that PAR was a compiler symbol that was evaluated at the time of assembly? I don't see any reason why anything other than the compiler would need to even know about the symbol.
Consider the following fragment:
PUBstart(parameter)
returncognew(@entry, parameter)
DATorg0entryrdlong:tmp, par
...
:tmpres1
Ah yes, the value of parameter... which is only known at runtime.
I'm guessing you'd use this kind of idiom for PASM's version of a dispatch table?
If I'm completely 100% wrong btw, I'll continue to point out that I am still a newbie :-) I have exactly 170 lines of code (combined SPIN and ASM to my name).
It's quite simple really. par is one (the first, $1F0) of the 16 special purpose register living in each cog. The parameter passed to coginit/cognew has its bits 15..2 extracted (we only have space for 14bit) and reappears in par[15..2]. All other bits in par are cleared. Whatever you pass down to the cog is up to you. It can be an address and it can be anything else provided it fit's into 14bit (it's basically down to interpretation). For example, you could pass a pin number by reference (rdlong pin, par) or pass the pin number itself shifted by 2 and extract it inside the cog with mov pin, par followed by shr pin, #2.
If par is used in the source slot of an instruction you'll always get the same value for the lifetime of the cog (that's the read-only bit). Using it in the destination slot will access the shadow register which you are free to modify.
So, check my logic for me here - I'm making assumptions.
So, you said that the two least significant bits are always zero'd when you write to PAR. Given the use-case that PAR is frequently used to pass HUB memory references and HUB memory is in longs I'm wanting to jump to a conclusion...
You see where my brain is going? By zeroing the two least significant bits PASM is forcing the value of PAR to always align with addresses which are multiples of 4 - ie longs.
You see where my brain is going? By zeroing the two least significant bits PASM is forcing the value of PAR to always align with addresses which are multiples of 4 - ie longs.
You are correct about the forced 4n alignment. This is also a source of unexplained errors, e.g. if you pass the address of a(n unaligned) byte variable (4n+3) it silently gets clamped to 4n. As for concidence, maybe, maybe not. It seems like a good compromise in that you can cover the whole 64K address range (hub RAM/ROM). And for other alignments you can always rebuild your address (+1/+2/+3) from 4n.
So, check my logic for me here - I'm making assumptions.
...
Coincidence?
Excellent, you got it in only a few posts. Your cargo net is pretty good.
Any long in the 32KB address space is definitely intentional.
Look at the PASM COGINIT instruction in the documentation for more enlightenment.
Table 3-1: Destination Register Fields
Bits Meaning
31:1814-bit Long address for PAR Register
17:414-bit Long address of code to load
3 Start a New COG
2:0 Cog ID (for reusing a specific COG)
The COGNEW variant is best to use unless you have a very good reason for specifying a COG ID.
Excellent, you got it in only a few posts. Your cargo net is pretty good.
Thanks, I appreciate the vote of confidence. My confidence has been kinda dented as I'm stalled on my propeller project. I moved to the hardware for 12 weeks and now I'm back to the software I've forgotten almost everything I've learned.
I am working on a detailed discussion of the use of PAR with extended code samples. I need the feedback of the experts on this one so I get it right for the book. Should be done in a day or two.
'----------------------------------------------' This is a demonstration of using shared variables' between SPIN and PASM.'con_clkmode = xtal1 + pll16x_clkfreq = 80_000_000obj
fdx : "FullDuplexSerial"' include fdx object for terminalvarlong command ' this is the command variablelong share[4] ' these are shared variablespubstart | n
fdx.start(31,30,0,115200) ' start serial portcognew(@pasm, @command) ' start pasm demowaitcnt(clkfreq+cnt) ' wait for fdx and pasm startrepeat
command := -1' send non-zero as the "go" commandrepeatwhile command ' wait until the command is done
fdx.tx($a) ' print a newline
fdx.tx($d)
fdx.str(string("shared values:"))
repeat n from0to3' print the values
fdx.tx(" ")
fdx.hex(share[n],8)
waitcnt(clkfreq+cnt) ' don't print too fast'----------------------------------------------' this code increments shared array variables' every time command is non-zero'datorg0
pasm
mov tmp, par' par is used as the command pointeradd tmp, #4' shared array starts 4 bytes after command addressmov ptr1, tmp ' save shared[0] addressadd tmp, #4' next addressmov ptr2, tmp ' save shared[1] addressadd tmp, #4' next addressmov ptr3, tmp ' save shared[2] addressadd tmp, #4' next addressmov ptr4, tmp ' save shared[3] addressmov tmp, #0
:loop
rdlong cmd, par' wait for user commandtjz cmd, #:loop ' if no command, jump to #:looprdlong dat1, ptr1 ' read shared datardlong dat2, ptr2
rdlong dat3, ptr3
rdlong dat4, ptr4
add dat1, #1' increment dataadd dat2, #1add dat3, #1add dat4, #1wrlong dat1, ptr1 ' write shared datawrlong dat2, ptr2
wrlong dat3, ptr3
wrlong dat4, ptr4
wrlong tmp, par' let user know we're donejmp #:loop
cmd res1' the command register
dat1 res1' data temporary registers
dat2 res1
dat3 res1
dat4 res1
ptr1 res1' pointers to shared array
ptr2 res1
ptr3 res1
ptr4 res1
tmp res1' temporary pointerfit$1ef' don't let PASM grow beyond $1ef'' end of file'----------------------------------------------
Just for enlightenment and to demonstrate what I meant by "more complicated" here's a more generic "array version" you asked about .... It adds the value of the djnz "len" variable value to the data. If nothing else it shows how to "index" registers in the cog.
I'm sure you'll find a simpler way to present this information at some point. It may be too much for a beginner, but like it or not the concept is a core idea in propeller PASM.
'----------------------------------------------' This is a demonstration of using shared variables' between SPIN and PASM.' This version uses self-modifying code with an array'con_clkmode = xtal1 + pll16x_clkfreq = 80_000_000
ALEN = 32
ASNT = ALEN-1obj
fdx : "FullDuplexSerial"' include fdx object for terminalvarlong command ' this is the command variablelong share[ALEN] ' these are shared variablespubstart | n
fdx.start(31,30,0,115200) ' start serial portcognew(@pasm, @command) ' start pasm demowaitcnt(clkfreq+cnt) ' wait for fdx and pasm startrepeat
command := -1' send non-zero as the "go" commandrepeatwhile command ' wait until the command is donerepeat n from0to ASNT ' print the values (0 based end = ALEN-1)ifnot n & 7
fdx.tx($a) ' print a newline
fdx.tx($d)
fdx.str(string("shared values:"))
fdx.tx(" ")
fdx.hex(share[n],8)
waitcnt(clkfreq+cnt) ' don't print too fast'----------------------------------------------' this code changes shared array variables' every time command is non-zero'datorg0
pasm
long0 [8]
mov tmp, par' par is used as the command pointeradd tmp, #4' shared array starts 4 bytes after command addressmov ptr, tmp ' save shared[0] base address
:loop
rdlong cmd, par' wait for user commandtjz cmd, #:loop ' if no command, jump to #:loopmov tmp, ptr ' set base address for read from HUBmovd :read, #dat1 ' set start address for write to dat1 arraymov len,#ALEN ' number of items to read from hub
:read
rdlong0-0, tmp ' read shared data to dat1 arrayadd tmp, #4' next pointer addressadd :read, x200 ' next data addressdjnz len, #:read ' read the next pointer to dat1 arraymovd :incr, #dat1 ' set start address for write to dat1 arraymov len,#ALEN ' number of items to increment
:incr
add0-0, len ' increment data by len'add 0-0, #1 ' increment data by 1add :incr, x200 ' next data addressdjnz len, #:incr ' read the next pointer to dat1 arraymov tmp, ptr ' set base address for write to HUBmovd :write,#dat1 ' set start address for read from dat1 arraymov len,#ALEN ' number of items to write back to hub
:write
wrlong0-0, tmp ' read shared data to dat1 arrayadd tmp, #4' next pointer addressadd :write,x200 ' next data addressdjnz len, #:write ' read the next pointer to dat1 arraywrlong zero,par' let user know we're donejmp #:loop
x200 long$200' used to increment destination field
zero long0
cmd res1' the command register
tmp res1' temporary pointer
ndx res1' increment index to set upper bits of dat array
len res1' array length register
ptr res1' shared array base pointer
dat1 res ALEN ' data temporary registers of size ALENfit$1ef' don't let PASM grow beyond $1ef'' end of file'----------------------------------------------
Wait till I get my progression on line and then give me a critique. I'm working on it pretty much full time so it should be done by tomorrow. Also want to see what other might suggest. Want to explain it so beginners are comfortable with this very necessary PASM/SPIN protocol.
H
When you want to pass an array of numbers with PAR, (lets say an array of 10 variables is needed)
Do you have to set up 10 spaces in the RES area thus
Array res 10
or can this allocation to be the last assignment as
Array res 1
and therefore does it have to be the last assignment in the Cog.
res N doesn't take up space in hub memory. So if you need N longs reserve them. Imagine you put the res 1 at the end of your PASM program and it ends up at location $1EC (4 longs left). How do you fit the other 6 longs in there? Your program will then happily scramble the first 6 SPRs (depending on access mode).
Can you be a bit more specific? What I was trying to say was (in case the question was addressed to me) if res 10 is what you need then use res 10. DON'T do something like res-1-at-the-end and hope it works.
The propeller manuals are not available in Kindle format but you can load the pdfs onto an iPad and then save them as iBooks. They can then be searched very effectively. Very useful for a beginner like me. Particularly so because relevent information is often scattered throught the manual. The search provides phrased results making it even more powerful.
Parallax reported that Kindle format was currently not in the pipeline.
Mine does. But if one wants to make a nice eBook for the Kindle, there is a free tool called Mobipocket Creator that is very easy to use. I am presently translating my acting coach's book, Acting is Living, to Kindle (and other eReaders) using this tool. For Mobipocket your material is a narrow subset of HTML with a few special codes; it's really easy if you've done any HTML coding at all.
I do not have access to a Kindle but I do have the Kindle App on my ipad2.
The app does not allow opening anything other than what has first been downloaded as books from the internet.
Maybe a pdf can be seen as a book somehow. The iPad has a button to convert a pdf to an iBook.
There may be some other tricks I am unaware of.
I have limited computer skills, in some departments, none.
Did not mean to mislead anyone. Apologies.
H
This will be added to, polished and edited before it is published. It will depend on what I learn in the interim. This is the general idea for now.
Chapter XXX
Using the PAR register to share variables between two or more Cogs
Sharing information between Cogs from within the PASM environment is best done with the PAR command. The register is designed specifically for this purpose and is covered on page 283 in the Propeller Manual. This chapter discussed the use of this register in detail.
A single variable shared
If you want to share a variable in the spin environment, it is a simple matter of declaring the variable under the VAR section of the listing, and it will become available to all eight Cogs. This is not true for the PASM environment in that there is no way to declare a global variable directly in this environment. There are two ways to make it possible to create a variable in one Cog and read it another Cog.
One way of doing this is to store the variable at a high memory location in the Hub from any one Cog and to read from that location from any other Cog either in Spin or in PASM. This method will NOT be demonstrated and is not recommended. There are too many problems with doing it this way. I will not even provide a sample listing of how to do this to completely discourage you from using this method. Since it is simple enough to use the PAR register to do the transfer, there is never a good reason to use this method.
The recommended way is to use the PAR register to share one or more variables or constants between the Cogs. PAR is one of the 16 special purpose, dedicated registers in each Cog. When you start a PASM Cog, you can pass it a variable that is available in both the PASM environment of the started Cog and in the general SPIN environment if is it declared under the VAR section. If the passed variable is given the same name in more than one PASM Cog, it will be able to be shared between these PASM cogs as well. The sharing can be for one variable or an array depending on how the shared registers are described.
First let us consider sharing just one value to start with. On startup, the PAR register contains a memory location that points to the location of the shared variable. We store information in the PAR register by putting the information in the memory location.
Consider these few lines of PASM code and comments
VAR
long SharedValue 'Shared variable is designated to be a long
….
….
PUB methodName 'name the method that runs the console display
Cognew(@start,@SharedValue) 'start second Cog at SecondCog in DAT memory
….
….
DAT
….org 0
….
mov mem Par.. 'move the memory location in PAR into MEM
wrlong Value, mem 'Store the value in the memory location
After the last line has been executed in the PASM Cog, the number in “value” will be available in “SharedValue” in all the SPIN Cogs. If another cogs has been started and the same @SharedValue variable passed in them, then all these Cogs could read the variable too.
You can also write-to and read-from the PAR register directly with
wrlong variable PAR'write variable to PAR register.
and
rdlong variable PAR'write variable to PAR register.
With the writing and reading taking place in the same cog or separate cogs.
Here we are using the PAR register directly, we are not going through its memory location though that can be done as was demonstrated earlier.
So sharing just one variable is not complicated. The steps we outlines above were as follows
1. Declare the variable global in the SPIN environment
2. Pass the same variable to all necessary PASM methods
3. Write and read longs to the shared variable in SPIN
4. Write and read longs to the PAR variable in the PASM methods.
Let us combine all the above information into a typical listing that you can refer to from time to time to refresh your memory. Consider a program that increments a variable by 1every time through the loops and makes this variable available to all Cogs whether they are written in SPIN or PASM. The initializing, incrementing and writing is done in the PASM cog and the reading and displaying is done in the SPIN Cog.
{{ Program 016 PASM minimum PAR.spin
This program is a minimal implementation of the use of the PAR register.
There are two Cogs in this program, one in SPIN and one in PASM
Cog 1 displays a counter on the console
Cog 2 first sets the variable to 0
it them finds the location of PAR and moves it to MEM
It then stores the variable in that location.
The loop adds 1 to the variable and stores
it back in the location of PAR (as was read into MEM.)
}}
VARlong sharedVariable
OBJ
fds: "FullDuplexSerial"PUBFirstCog'displays values on console
fds.start(31,30,0,115200) 'start console at 115200 for debug outputcognew(@SecondCog, @SharedVariable) 'start the second Cog in PASMwaitcnt(clkfreq/4+cnt) 'wait 1/4 for everything to stabilize.repeat'loop
fds.tx($1) 'home to 0,0
fds.dec(SharedVariable) 'display written value
fds.tx(" ") 'spacewaitcnt(clkfreq/60+cnt) 'flicker free waitDATorg0'start at 0 location in the cog
SecondCog 'start point identificationmov variable, zero 'initialize variable to 0mov MEM, PAR'find location of PAR, put in MEM
re_do wrlong variable, MEM 'write variable to location MEMadd MEM , #1'add one to the MEM locationjmp #re_do 'jump back, do it again
zero long0'a zero to initialize variable
variable res1'variable storage space
MEM res1'MEM storage space
Two variables
Next we need to expand the program to transfer two variables between a Cog written in PASM and one written in SPIN. Once we master dealing with two variables, we can extend it to as many variables as we need, RAM space availability will be our only constraint.
As a general rule it is best if we as beginners assume that all our variables are longs. This is particularly so because PASM does not allow us to designate the length of its variables (though it does allow us to write bytes and words). Longs are, by definition, 4 bytes long so they require storage locations that are 4 bytes apart. If we store our first variable starting at location MEM, the next variable will be stored in location MEM+4. Every time we need an address for a fresh variable, we have to add 4 to the last location that we stored a variable in. In this particular case the two locations that we will use will be MEM and MEM+4.
If only a few variables are to be assigned, we can assign individual names and storage locations to them. We do not need an indexed array. In the last exercise we will implement an indexed array but for now we will simply assign two locations for the two variables that we will be using.
The first program needs to be modified to increase the various routines to handle two variables instead of one. Lets take a look at the display routine first. We need to make the following changes:
1. Assign space for two variables in VAR
2. Print out two variables on the console instead of one
VARlong sharedVariable[2]
OBJ
fds: "FullDuplexSerial"PUBFirstCog'displays values on console
fds.start(31,30,0,115200) 'start console at 115200 for debug outputcognew(@SecondCog, @SharedVariable) 'start the second Cog in PASMwaitcnt(clkfreq/4+cnt) 'wait 1/4 for everything to stabilize.repeat'loop
fds.tx($1) 'home to 0,0
fds.dec(SharedVariable) 'display first value
fds.tx(" ") 'space
fds.dec(SharedVariable[2]) 'display second valuewaitcnt(clkfreq/60+cnt) 'flicker free wait
The PASM code in the second Cog requires a little more attention to handle two variables. Here instead of adding one to the variable each time through the loop we need to call the second variable into active use. We also need to specify two real values for the two variables that we will be manipulating so that we have something to look at to confirm a successful program. Let us select 1111 and 2222 as our two values and define them as longs. These variables are a part of the PASM cog and we want to be able to read them and display them in the SPIN cog. Accordingly we will name the two variables var_one and vat_two and provide each of them with a storage location in the PASM cog. If we run the program and can read them in the SPIN cog we will have succeeded.
Here is the code for the PASM cog.
DATorg0'start at 0 location in the cog
SecondCog 'start point identificationmov MEM1, PAR'find location of PAR, put in MEM1mov MEM2, MEM1 'make MEM2=MEM1add MEM2, #4'add 4 to MEM2 for next long
re_do wrlong var_one, MEM1 'write variable to location MEM1wrlong var_two, MEM2 'write variable to location MEM2jmp #re_do 'jump back, do it again
var_one long1111'variable declaration
var_two long2222'variable declaration
MEM1 res1'first address storage space
MEM2 res1'second address storage space
All put together the program is.
{{ Program 017 PASM minimum PAR.spin
This program is a minimal implementation for two variables and PAR.
There are two Cogs in this program, one in SPIN and one in PASM
Cog 1 in SPIN displays the variables on the console
Cog 2 in PASM sets the variables to 1111 and 2222
Finds the location of PAR and moves it to mem1
Then mem2 is set to mem1 +4.
The two variables are transferred to mem1 and mem2
and become available to the SPIN environment
Loop.
}}
VARlong sharedVariable
OBJ
fds: "FullDuplexSerial"PUBFirstCog'displays values on console
fds.start(31,30,0,115200) 'start console at 115200 for debug outputcognew(@SecondCog, @SharedVariable) 'start the second Cog in PASMwaitcnt(clkfreq/60+cnt) 'wait 1/4 for everything to stabilize.repeat'loop
fds.tx($1) 'home to 0,0
fds.dec(SharedVariable) 'display first value
fds.tx($d) 'space
fds.dec(SharedVariable[1]) 'display second valuewaitcnt(clkfreq/6+cnt) 'flicker free waitDATorg0'start at 0 location in the cog
SecondCog 'start point identificationmov mem1, PAR'find location of PAR, put in MEM1mov mem2, mem1 'make MEM2=MEM1add mem2, #4'add 4 to MEME2
re_do wrlong var_one, mem1 'write variable to location MEM1wrlong var_two, mem2 'write variable to location MEM2jmp #re_do 'jump back, do it again
var_one long1111'variable declaration
var_two long2222'variable declaration
mem1 res1'first address storage space
mem2 res1'second address storage space
Indefinite number of variables
Next we need to understand how to share an indefinite number of variables between a SPIN Cog and a PASM Cog. In order to do this we will need to define an indexing variable and decide on a way to tell the system how many variables are to be shared. An indexing scheme that allows us to access the variables serially will have to be devised also.
In order for the program to be universal let us agree that we need to be able to share between 1 and 10 variables (but the upper limit is determined by how much memory we have left in the Cog after the program is read in).
In the following code, the first cog written in SPIN, displays the 10 variables that are being transferred. The second cog, written in PASM, first find the location of PAR and stores it in mem. It then starts storing the 10 variables in 10 memory locations that are 4 bytes apart. The various variables are incremented and decremented as necessary.
In all these program I tried to make the code as compact and simple as possible as my present expertise allowed.
{{ Program 018 Array from PAR.spin
This program is a minimal implementation for an array and PAR.
There are two Cogs in this program, one in SPIN and one in PASM
Cog 1 in SPIN displays the variables on the console
Cog 2 in PASM sets the variables to 0 and 9
Finds the location of PAR and moves it to mem
Then storage array is set with offsets of +4.
The array variables are transferred to mem array
and become available to the SPIN environment
Loop.
}}
VARlong shared[12], array, n
OBJ
fds: "FullDuplexSerial"PUBFirstCog'displays values on console
fds.start(31,30,0,115200) 'start console at 115200 for debug outputcognew(@SecondCog, @Shared) 'start the second Cog in PASMwaitcnt(clkfreq/4+cnt) 'wait 1/4 for everything to stabilize.
array:=10
n:=0repeat'loop
fds.tx($1) 'home to 0,0repeat'loop
fds.dec(n) 'display first value
fds.tx(" ") 'space
fds.tx(" ") 'space
fds.dec(Shared[n]) 'display first value
fds.tx(" ") 'space
fds.tx(" ") 'space
fds.tx($d) 'new line
n:=n+1'counter variableif n=>array 'decision to reset
n:=0'reset n
fds.tx($1) 'home
fds.tx($3) 'clear screenwaitcnt(clkfreq/60+cnt) 'flicker free waitDATorg0'start at 0 location in the cog
SecondCog 'start point identification
:reset mov mem, PAR'find location of PAR, put in memmov cnter, #10'set counter for 10 variablesmov var_one, #0'set first variable to 0
:re_do wrlong var_one, mem 'write variable to location (mem+4....)add var_one, #5'increment variable value by 5add mem, #4'increment memory location by 4 for longssub cnter, #1wz'subtract from counter and set 0 flagif_nzjmp #:re_do 'jmp and redo if 0 flag NOT setjmp #:reset 'jump back, do it again from beginning
cnter res1'counter for variables
mem res10'ten address storage space
var_one res1'variable content
{{ Program 018Arrayfrom PAR.spin
This program is a minimal implementation for an array and PAR.
...
mem res 10'ten address storage space
var_one res 1 'variable content
In program 18, you don't need "mem res 10" ... "mem res 1" is sufficient.
Your code stores only one COG variable's values to a number of hub addresses.
Here we are using the PAR register directly, we are not going through its memory location though that can be done as was demonstrated earlier.
What does that even mean?
You also use Finds the location of PAR ... a lot. To me par only ever refers to register $1F0 in cog memory. So there is really nothing to find, it's built-in. Unless your PAR means something else. In this case please clarify exactly what you mean.
{{ Program 016 PASM minimum PAR.spin
..
It then stores the variable in that location.
[COLOR="red"]The loop adds 1 to the variable and stores[/COLOR]
}}
...
org0'start at 0 location in the cog
SecondCog 'start point identificationmov variable, zero 'initialize variable to 0mov MEM, PAR'find location of PAR, put in MEM
re_do wrlong variable, MEM 'write variable to location MEM
[COLOR="red"] add MEM , #1'add one to the MEM location
[/COLOR] jmp #re_do 'jump back, do it again
zero long0'a zero to initialize variable
variable res1'variable storage space
MEM res1'MEM storage space
You also use Finds the location of PAR ... a lot. To me par only ever refers to register $1F0 in cog memory. So there is really nothing to find, it's built-in. Unless your PAR means something else. In this case please clarify exactly what you mean.
I must be getting confused here. I have thought that the cog architecture was what we called in the bad old core days, memory to memory. daSilva calls them cells, special ones are referred to as registers, but still in all I understand them ALL to be memory.
To b@stardize a line out of Dune, the register is the memory and the memory is the register. Or am I lost in space????
Comments
It has been discussed in over a dozen posts in this thread because the idea is a core concept.
I didn't take any of the above posts as disapproval to use HUB memory to share information between cogs. In fact, according of one of the other threads that's pretty much the main way of doing it.
What I understood disapproval was aimed at was the arbitrary choice of a HUB memory address when there is a mechanism to do this via PAR.
I saw someone call PAR a register only and that over-writing it doesn't work because it's "read-only" or something.
I thought that PAR was a compiler symbol that was evaluated at the time of assembly? I don't see any reason why anything other than the compiler would need to even know about the symbol.
Am I missing something clever here?
PUB start(parameter) return cognew(@entry, parameter) DAT org 0 entry rdlong :tmp, par ... :tmp res 1
Which value would you give par at assembly time?I'm guessing you'd use this kind of idiom for PASM's version of a dispatch table?
If I'm completely 100% wrong btw, I'll continue to point out that I am still a newbie :-) I have exactly 170 lines of code (combined SPIN and ASM to my name).
If par is used in the source slot of an instruction you'll always get the same value for the lifetime of the cog (that's the read-only bit). Using it in the destination slot will access the shadow register which you are free to modify.
So, you said that the two least significant bits are always zero'd when you write to PAR. Given the use-case that PAR is frequently used to pass HUB memory references and HUB memory is in longs I'm wanting to jump to a conclusion...
You see where my brain is going? By zeroing the two least significant bits PASM is forcing the value of PAR to always align with addresses which are multiples of 4 - ie longs.
Coincidence?
Any long in the 32KB address space is definitely intentional.
Look at the PASM COGINIT instruction in the documentation for more enlightenment.
Table 3-1: Destination Register Fields Bits Meaning 31:18 14-bit Long address for PAR Register 17:4 14-bit Long address of code to load 3 Start a New COG 2:0 Cog ID (for reusing a specific COG)
The COGNEW variant is best to use unless you have a very good reason for specifying a COG ID.We had one for a while. The creator of it invested a ton into a nice bot too. Could pull up lots of good info with just a keyword. Gone now.
Anyway, welcome and please just jump in! This forum almost never sleeps. Not quite IRC, but... it's active, and that's better than nothing.
Thanks, I appreciate the vote of confidence. My confidence has been kinda dented as I'm stalled on my propeller project. I moved to the hardware for 12 weeks and now I'm back to the software I've forgotten almost everything I've learned.
Re irc:
I love the infobots on irc - so underrated and underused. You know, there is no reason why we couldn't resurect it :-D
I'll even run an infobot for the service :-)
Thanks guys,
Red
H
Basically, what is the right way to do this?
When you want to pass an array of numbers with PAR, (lets say an array of 10 variables is needed)
Do you have to set up 10 spaces in the RES area thus
Array res 10
or can this allocation to be the last assignment as
Array res 1
and therefore does it have to be the last assignment in the Cog.
I understand that in the SPIN program we would use
VAR
long sharedVariable[10]
to accommodate the data
H
If you say "array res 10", it will be much more complicated to use.
My very first example was one way that saves space ...
obviously it would be better for you and others to present it differently.
Below is a different example to study.
Output of the program on the terminal looks like this:
shared values: 00000001 00000001 00000001 00000001 shared values: 00000002 00000002 00000002 00000002 shared values: 00000003 00000003 00000003 00000003 shared values: 00000004 00000004 00000004 00000004 shared values: 00000005 00000005 00000005 00000005 shared values: 00000006 00000006 00000006 00000006
'---------------------------------------------- ' This is a demonstration of using shared variables ' between SPIN and PASM. ' con _clkmode = xtal1 + pll16x _clkfreq = 80_000_000 obj fdx : "FullDuplexSerial" ' include fdx object for terminal var long command ' this is the command variable long share[4] ' these are shared variables pub start | n fdx.start(31,30,0,115200) ' start serial port cognew(@pasm, @command) ' start pasm demo waitcnt(clkfreq+cnt) ' wait for fdx and pasm start repeat command := -1 ' send non-zero as the "go" command repeat while command ' wait until the command is done fdx.tx($a) ' print a newline fdx.tx($d) fdx.str(string("shared values:")) repeat n from 0 to 3 ' print the values fdx.tx(" ") fdx.hex(share[n],8) waitcnt(clkfreq+cnt) ' don't print too fast '---------------------------------------------- ' this code increments shared array variables ' every time command is non-zero ' dat org 0 pasm mov tmp, par ' par is used as the command pointer add tmp, #4 ' shared array starts 4 bytes after command address mov ptr1, tmp ' save shared[0] address add tmp, #4 ' next address mov ptr2, tmp ' save shared[1] address add tmp, #4 ' next address mov ptr3, tmp ' save shared[2] address add tmp, #4 ' next address mov ptr4, tmp ' save shared[3] address mov tmp, #0 :loop rdlong cmd, par ' wait for user command tjz cmd, #:loop ' if no command, jump to #:loop rdlong dat1, ptr1 ' read shared data rdlong dat2, ptr2 rdlong dat3, ptr3 rdlong dat4, ptr4 add dat1, #1 ' increment data add dat2, #1 add dat3, #1 add dat4, #1 wrlong dat1, ptr1 ' write shared data wrlong dat2, ptr2 wrlong dat3, ptr3 wrlong dat4, ptr4 wrlong tmp, par ' let user know we're done jmp #:loop cmd res 1 ' the command register dat1 res 1 ' data temporary registers dat2 res 1 dat3 res 1 dat4 res 1 ptr1 res 1 ' pointers to shared array ptr2 res 1 ptr3 res 1 ptr4 res 1 tmp res 1 ' temporary pointer fit $1ef ' don't let PASM grow beyond $1ef ' ' end of file '----------------------------------------------
I'm sure you'll find a simpler way to present this information at some point. It may be too much for a beginner, but like it or not the concept is a core idea in propeller PASM.
'---------------------------------------------- ' This is a demonstration of using shared variables ' between SPIN and PASM. ' This version uses self-modifying code with an array ' con _clkmode = xtal1 + pll16x _clkfreq = 80_000_000 ALEN = 32 ASNT = ALEN-1 obj fdx : "FullDuplexSerial" ' include fdx object for terminal var long command ' this is the command variable long share[ALEN] ' these are shared variables pub start | n fdx.start(31,30,0,115200) ' start serial port cognew(@pasm, @command) ' start pasm demo waitcnt(clkfreq+cnt) ' wait for fdx and pasm start repeat command := -1 ' send non-zero as the "go" command repeat while command ' wait until the command is done repeat n from 0 to ASNT ' print the values (0 based end = ALEN-1) ifnot n & 7 fdx.tx($a) ' print a newline fdx.tx($d) fdx.str(string("shared values:")) fdx.tx(" ") fdx.hex(share[n],8) waitcnt(clkfreq+cnt) ' don't print too fast '---------------------------------------------- ' this code changes shared array variables ' every time command is non-zero ' dat org 0 pasm long 0 [8] mov tmp, par ' par is used as the command pointer add tmp, #4 ' shared array starts 4 bytes after command address mov ptr, tmp ' save shared[0] base address :loop rdlong cmd, par ' wait for user command tjz cmd, #:loop ' if no command, jump to #:loop mov tmp, ptr ' set base address for read from HUB movd :read, #dat1 ' set start address for write to dat1 array mov len,#ALEN ' number of items to read from hub :read rdlong 0-0, tmp ' read shared data to dat1 array add tmp, #4 ' next pointer address add :read, x200 ' next data address djnz len, #:read ' read the next pointer to dat1 array movd :incr, #dat1 ' set start address for write to dat1 array mov len,#ALEN ' number of items to increment :incr add 0-0, len ' increment data by len 'add 0-0, #1 ' increment data by 1 add :incr, x200 ' next data address djnz len, #:incr ' read the next pointer to dat1 array mov tmp, ptr ' set base address for write to HUB movd :write,#dat1 ' set start address for read from dat1 array mov len,#ALEN ' number of items to write back to hub :write wrlong 0-0, tmp ' read shared data to dat1 array add tmp, #4 ' next pointer address add :write,x200 ' next data address djnz len, #:write ' read the next pointer to dat1 array wrlong zero,par ' let user know we're done jmp #:loop x200 long $200 ' used to increment destination field zero long 0 cmd res 1 ' the command register tmp res 1 ' temporary pointer ndx res 1 ' increment index to set upper bits of dat array len res 1 ' array length register ptr res 1 ' shared array base pointer dat1 res ALEN ' data temporary registers of size ALEN fit $1ef ' don't let PASM grow beyond $1ef ' ' end of file '----------------------------------------------
Program output:
shared values: 00000020 0000001F 0000001E 0000001D 0000001C 0000001B 0000001A 00000019 shared values: 00000018 00000017 00000016 00000015 00000014 00000013 00000012 00000011 shared values: 00000010 0000000F 0000000E 0000000D 0000000C 0000000B 0000000A 00000009 shared values: 00000008 00000007 00000006 00000005 00000004 00000003 00000002 00000001 shared values: 00000040 0000003E 0000003C 0000003A 00000038 00000036 00000034 00000032 shared values: 00000030 0000002E 0000002C 0000002A 00000028 00000026 00000024 00000022 shared values: 00000020 0000001E 0000001C 0000001A 00000018 00000016 00000014 00000012 shared values: 00000010 0000000E 0000000C 0000000A 00000008 00000006 00000004 00000002 shared values: 00000060 0000005D 0000005A 00000057 00000054 00000051 0000004E 0000004B shared values: 00000048 00000045 00000042 0000003F 0000003C 00000039 00000036 00000033 shared values: 00000030 0000002D 0000002A 00000027 00000024 00000021 0000001E 0000001B shared values: 00000018 00000015 00000012 0000000F 0000000C 00000009 00000006 00000003
H
H
My mistake
I thought you indicated that the use of
Array Res 10
Was incorrect
H
The propeller manuals are not available in Kindle format but you can load the pdfs onto an iPad and then save them as iBooks. They can then be searched very effectively. Very useful for a beginner like me. Particularly so because relevent information is often scattered throught the manual. The search provides phrased results making it even more powerful.
Parallax reported that Kindle format was currently not in the pipeline.
Harprit.
Mine does. But if one wants to make a nice eBook for the Kindle, there is a free tool called Mobipocket Creator that is very easy to use. I am presently translating my acting coach's book, Acting is Living, to Kindle (and other eReaders) using this tool. For Mobipocket your material is a narrow subset of HTML with a few special codes; it's really easy if you've done any HTML coding at all.
The app does not allow opening anything other than what has first been downloaded as books from the internet.
Maybe a pdf can be seen as a book somehow. The iPad has a button to convert a pdf to an iBook.
There may be some other tricks I am unaware of.
I have limited computer skills, in some departments, none.
Did not mean to mislead anyone. Apologies.
H
Chapter XXX
Using the PAR register to share variables between two or more Cogs
Sharing information between Cogs from within the PASM environment is best done with the PAR command. The register is designed specifically for this purpose and is covered on page 283 in the Propeller Manual. This chapter discussed the use of this register in detail.
A single variable shared
If you want to share a variable in the spin environment, it is a simple matter of declaring the variable under the VAR section of the listing, and it will become available to all eight Cogs. This is not true for the PASM environment in that there is no way to declare a global variable directly in this environment. There are two ways to make it possible to create a variable in one Cog and read it another Cog.
One way of doing this is to store the variable at a high memory location in the Hub from any one Cog and to read from that location from any other Cog either in Spin or in PASM. This method will NOT be demonstrated and is not recommended. There are too many problems with doing it this way. I will not even provide a sample listing of how to do this to completely discourage you from using this method. Since it is simple enough to use the PAR register to do the transfer, there is never a good reason to use this method.
The recommended way is to use the PAR register to share one or more variables or constants between the Cogs. PAR is one of the 16 special purpose, dedicated registers in each Cog. When you start a PASM Cog, you can pass it a variable that is available in both the PASM environment of the started Cog and in the general SPIN environment if is it declared under the VAR section. If the passed variable is given the same name in more than one PASM Cog, it will be able to be shared between these PASM cogs as well. The sharing can be for one variable or an array depending on how the shared registers are described.
First let us consider sharing just one value to start with. On startup, the PAR register contains a memory location that points to the location of the shared variable. We store information in the PAR register by putting the information in the memory location.
Consider these few lines of PASM code and comments
VAR long SharedValue 'Shared variable is designated to be a long …. …. PUB methodName 'name the method that runs the console display Cognew(@start,@SharedValue) 'start second Cog at SecondCog in DAT memory …. …. DAT ….org 0 …. mov mem Par.. 'move the memory location in PAR into MEM wrlong Value, mem 'Store the value in the memory location
After the last line has been executed in the PASM Cog, the number in “value” will be available in “SharedValue” in all the SPIN Cogs. If another cogs has been started and the same @SharedValue variable passed in them, then all these Cogs could read the variable too.
You can also write-to and read-from the PAR register directly with
wrlong variable PAR 'write variable to PAR register.
andrdlong variable PAR 'write variable to PAR register.
With the writing and reading taking place in the same cog or separate cogs.Here we are using the PAR register directly, we are not going through its memory location though that can be done as was demonstrated earlier.
So sharing just one variable is not complicated. The steps we outlines above were as follows
1. Declare the variable global in the SPIN environment
2. Pass the same variable to all necessary PASM methods
3. Write and read longs to the shared variable in SPIN
4. Write and read longs to the PAR variable in the PASM methods.
Let us combine all the above information into a typical listing that you can refer to from time to time to refresh your memory. Consider a program that increments a variable by 1every time through the loops and makes this variable available to all Cogs whether they are written in SPIN or PASM. The initializing, incrementing and writing is done in the PASM cog and the reading and displaying is done in the SPIN Cog.
{{ Program 016 PASM minimum PAR.spin This program is a minimal implementation of the use of the PAR register. There are two Cogs in this program, one in SPIN and one in PASM Cog 1 displays a counter on the console Cog 2 first sets the variable to 0 it them finds the location of PAR and moves it to MEM It then stores the variable in that location. The loop adds 1 to the variable and stores it back in the location of PAR (as was read into MEM.) }} VAR long sharedVariable OBJ fds: "FullDuplexSerial" PUB FirstCog 'displays values on console fds.start(31,30,0,115200) 'start console at 115200 for debug output cognew(@SecondCog, @SharedVariable) 'start the second Cog in PASM waitcnt(clkfreq/4+cnt) 'wait 1/4 for everything to stabilize. repeat 'loop fds.tx($1) 'home to 0,0 fds.dec(SharedVariable) 'display written value fds.tx(" ") 'space waitcnt(clkfreq/60+cnt) 'flicker free wait DAT org 0 'start at 0 location in the cog SecondCog 'start point identification mov variable, zero 'initialize variable to 0 mov MEM, PAR 'find location of PAR, put in MEM re_do wrlong variable, MEM 'write variable to location MEM add MEM , #1 'add one to the MEM location jmp #re_do 'jump back, do it again zero long 0 'a zero to initialize variable variable res 1 'variable storage space MEM res 1 'MEM storage space
Two variables
Next we need to expand the program to transfer two variables between a Cog written in PASM and one written in SPIN. Once we master dealing with two variables, we can extend it to as many variables as we need, RAM space availability will be our only constraint.
As a general rule it is best if we as beginners assume that all our variables are longs. This is particularly so because PASM does not allow us to designate the length of its variables (though it does allow us to write bytes and words). Longs are, by definition, 4 bytes long so they require storage locations that are 4 bytes apart. If we store our first variable starting at location MEM, the next variable will be stored in location MEM+4. Every time we need an address for a fresh variable, we have to add 4 to the last location that we stored a variable in. In this particular case the two locations that we will use will be MEM and MEM+4.
If only a few variables are to be assigned, we can assign individual names and storage locations to them. We do not need an indexed array. In the last exercise we will implement an indexed array but for now we will simply assign two locations for the two variables that we will be using.
The first program needs to be modified to increase the various routines to handle two variables instead of one. Lets take a look at the display routine first. We need to make the following changes:
1. Assign space for two variables in VAR
2. Print out two variables on the console instead of one
VAR long sharedVariable[2] OBJ fds: "FullDuplexSerial" PUB FirstCog 'displays values on console fds.start(31,30,0,115200) 'start console at 115200 for debug output cognew(@SecondCog, @SharedVariable) 'start the second Cog in PASM waitcnt(clkfreq/4+cnt) 'wait 1/4 for everything to stabilize. repeat 'loop fds.tx($1) 'home to 0,0 fds.dec(SharedVariable) 'display first value fds.tx(" ") 'space fds.dec(SharedVariable[2]) 'display second value waitcnt(clkfreq/60+cnt) 'flicker free wait
The PASM code in the second Cog requires a little more attention to handle two variables. Here instead of adding one to the variable each time through the loop we need to call the second variable into active use. We also need to specify two real values for the two variables that we will be manipulating so that we have something to look at to confirm a successful program. Let us select 1111 and 2222 as our two values and define them as longs. These variables are a part of the PASM cog and we want to be able to read them and display them in the SPIN cog. Accordingly we will name the two variables var_one and vat_two and provide each of them with a storage location in the PASM cog. If we run the program and can read them in the SPIN cog we will have succeeded.
Here is the code for the PASM cog.
DAT org 0 'start at 0 location in the cog SecondCog 'start point identification mov MEM1, PAR 'find location of PAR, put in MEM1 mov MEM2, MEM1 'make MEM2=MEM1 add MEM2, #4 'add 4 to MEM2 for next long re_do wrlong var_one, MEM1 'write variable to location MEM1 wrlong var_two, MEM2 'write variable to location MEM2 jmp #re_do 'jump back, do it again var_one long 1111 'variable declaration var_two long 2222 'variable declaration MEM1 res 1 'first address storage space MEM2 res 1 'second address storage space
All put together the program is.
{{ Program 017 PASM minimum PAR.spin This program is a minimal implementation for two variables and PAR. There are two Cogs in this program, one in SPIN and one in PASM Cog 1 in SPIN displays the variables on the console Cog 2 in PASM sets the variables to 1111 and 2222 Finds the location of PAR and moves it to mem1 Then mem2 is set to mem1 +4. The two variables are transferred to mem1 and mem2 and become available to the SPIN environment Loop. }} VAR long sharedVariable OBJ fds: "FullDuplexSerial" PUB FirstCog 'displays values on console fds.start(31,30,0,115200) 'start console at 115200 for debug output cognew(@SecondCog, @SharedVariable) 'start the second Cog in PASM waitcnt(clkfreq/60+cnt) 'wait 1/4 for everything to stabilize. repeat 'loop fds.tx($1) 'home to 0,0 fds.dec(SharedVariable) 'display first value fds.tx($d) 'space fds.dec(SharedVariable[1]) 'display second value waitcnt(clkfreq/6+cnt) 'flicker free wait DAT org 0 'start at 0 location in the cog SecondCog 'start point identification mov mem1, PAR 'find location of PAR, put in MEM1 mov mem2, mem1 'make MEM2=MEM1 add mem2, #4 'add 4 to MEME2 re_do wrlong var_one, mem1 'write variable to location MEM1 wrlong var_two, mem2 'write variable to location MEM2 jmp #re_do 'jump back, do it again var_one long 1111 'variable declaration var_two long 2222 'variable declaration mem1 res 1 'first address storage space mem2 res 1 'second address storage space
Indefinite number of variables
Next we need to understand how to share an indefinite number of variables between a SPIN Cog and a PASM Cog. In order to do this we will need to define an indexing variable and decide on a way to tell the system how many variables are to be shared. An indexing scheme that allows us to access the variables serially will have to be devised also.
In order for the program to be universal let us agree that we need to be able to share between 1 and 10 variables (but the upper limit is determined by how much memory we have left in the Cog after the program is read in).
In the following code, the first cog written in SPIN, displays the 10 variables that are being transferred. The second cog, written in PASM, first find the location of PAR and stores it in mem. It then starts storing the 10 variables in 10 memory locations that are 4 bytes apart. The various variables are incremented and decremented as necessary.
In all these program I tried to make the code as compact and simple as possible as my present expertise allowed.
{{ Program 018 Array from PAR.spin This program is a minimal implementation for an array and PAR. There are two Cogs in this program, one in SPIN and one in PASM Cog 1 in SPIN displays the variables on the console Cog 2 in PASM sets the variables to 0 and 9 Finds the location of PAR and moves it to mem Then storage array is set with offsets of +4. The array variables are transferred to mem array and become available to the SPIN environment Loop. }} VAR long shared[12], array, n OBJ fds: "FullDuplexSerial" PUB FirstCog 'displays values on console fds.start(31,30,0,115200) 'start console at 115200 for debug output cognew(@SecondCog, @Shared) 'start the second Cog in PASM waitcnt(clkfreq/4+cnt) 'wait 1/4 for everything to stabilize. array:=10 n:=0 repeat 'loop fds.tx($1) 'home to 0,0 repeat 'loop fds.dec(n) 'display first value fds.tx(" ") 'space fds.tx(" ") 'space fds.dec(Shared[n]) 'display first value fds.tx(" ") 'space fds.tx(" ") 'space fds.tx($d) 'new line n:=n+1 'counter variable if n=>array 'decision to reset n:=0 'reset n fds.tx($1) 'home fds.tx($3) 'clear screen waitcnt(clkfreq/60+cnt) 'flicker free wait DAT org 0 'start at 0 location in the cog SecondCog 'start point identification :reset mov mem, PAR 'find location of PAR, put in mem mov cnter, #10 'set counter for 10 variables mov var_one, #0 'set first variable to 0 :re_do wrlong var_one, mem 'write variable to location (mem+4....) add var_one, #5 'increment variable value by 5 add mem, #4 'increment memory location by 4 for longs sub cnter, #1 wz 'subtract from counter and set 0 flag if_nz jmp #:re_do 'jmp and redo if 0 flag NOT set jmp #:reset 'jump back, do it again from beginning cnter res 1 'counter for variables mem res 10 'ten address storage space var_one res 1 'variable content
Harprit
I don't have one either but it prompted me to order a refurbished one ($99.00, no shipping)
Harprit
Your code stores only one COG variable's values to a number of hub addresses.
You also use Finds the location of PAR ... a lot. To me par only ever refers to register $1F0 in cog memory. So there is really nothing to find, it's built-in. Unless your PAR means something else. In this case please clarify exactly what you mean.
I must be getting confused here. I have thought that the cog architecture was what we called in the bad old core days, memory to memory. daSilva calls them cells, special ones are referred to as registers, but still in all I understand them ALL to be memory.
To b@stardize a line out of Dune, the register is the memory and the memory is the register. Or am I lost in space????
Frank