cogid behavior

If I am calling a method foo from a main method, and foo starts 5 cogs via cognew, is it guaranteed that the PASM interpreter invoked for the main method will be cogid 0, and each sequentially started cog from foo will be cogid 1, cogid 2, ... , cogid 6? Thank you!
«1

Comments

  • 31 Comments sorted by Date Added Votes
  • That is correct.

    I'd be interested to hear why you need to know that. Generally it is not necessary to know cog ids.
  • escherescher Posts: 83
    edited January 28 Vote Up0Vote Down
    Thanks @Heater. ! And honestly, maybe you can suggest a better solution than the one I was considering with cogid:

    The reason I'm asking is because I need the 5 cogs to each have a unique variable from 0 to 4, and since they share the same memory being passed in, I don't see an easy way to do this (they need to all have the same start memory address passed in, so I can't pass each one a reference to a different memory location containing 0, 1, 2, etc.)

    My thought was using cogid and subtracting one from each to get those unique variables, but that's not pretty.

    I could also perform a waitcnt in my initialization code between each cognew, and increment a shared variable between each one. However I would need to know how long cog initialization takes and to perform the rdbyte, and explicit waits also aren't very pretty.

    Here is my code:
    CON
      ' Graphics system attributes
      numRenderCogs = 5             ' Number of cogs used for rendering
    
    VAR
      ' Graphics system attributes
      long  cog_[numRenderCogs]     ' Array containing IDs of rendering cogs
      ' long cur_ID = 0 ? which would contain the number {0,4} to be read by the currently initializing cog
      long  var_addr_base_          ' Variable for pointer to base address of Main RAM variables
      
    PUB start(varAddrBase) : vidstatus | cIndex                                     ' Function to start renderer with pointer to Main RAM variables
      stop                                                                          ' Stop render cogs if running
    
      ' Instantiate variables
      var_addr_base_ := varAddrBase                                                 ' Assign local base variable address
    
      repeat cIndex from 0 to numRenderCogs - 1
        ifnot cog_[cIndex] := cognew(@render, var_addr_base_) + 1                   ' Initialize cog running "render" routine with reference to start of variables
        ' Here I could perform a waitcnt? 
        ' Here I could increment cur_ID?
    

    This is likely an easily solvable problem, but the restriction of each cog needing the same memory address passed in is a hindrance. Thank you!
  • Heater.Heater. Posts: 20,220
    edited January 28 Vote Up0Vote Down
    The signature for cognew looks like this:

    COGNEW (SpinMethod < (ParameterList) >, StackPointer )

    So why not pass those unique numbers to each new cog as a parameter there. Just change it as you go round that loop starting cogs.

    You need to pass a stack pointer as well.
  • escherescher Posts: 83
    edited January 28 Vote Up0Vote Down
    Isn't that only for calling spin code into a cog running an interpreter? I'm calling PASM code. Sorry, should have made that clearer.
  • Ah yes.

    That is OK. You are passing the address of some memory area to the COG. Just set the required unique number into that area somewhere before starting a COG. The started COG can read it and save it internally.
  • escherescher Posts: 83
    edited January 28 Vote Up0Vote Down
    Ah but the next 4 cogs which are initialized will read that same areas of memory for their own unique value. So let's assume I use a waitcnt between each cognew to give each cog time to read its unique value before it's incremented in hub RAM. How do I know how long to pause between initializations to allow time for each cog to read its unique value? I know the hub has to write 512 longs to a cog's memory, and then execution has to start, but how long does that take? I'm also using a 104 MHz clkfreq, not 80 MHz.
  • Heater.Heater. Posts: 20,220
    edited January 28 Vote Up0Vote Down
    I'm not sure. The time taken to get a COG started is documented somewhere.

    A robust way to do it would be to not use a waitcnt. Rather, have the started COG read the unique id, save it away somewhere, then overwrite that unique id parameter with some invalid value. Meanwhile in the main loop, after starting a COG, add a loop that spins around checking for that invalid value to be written to the unique id space. When it is, exit that loop and proceed to the next COG.


  • Heater. wrote: »
    I'm not sure. The time taken to get a COG started is documented somewhere.

    A robust way to do it would be to not use a waitcnt. Rather, have the started COG read the unique id, save it away somewhere, then overwrite that unique id parameter with some invalid value. Meanwhile the in the main loop, after starting a COG, add a loop that spins around checking for that invalid value to be written to the unique id space. When it is, exit that loop and proceed to the next COG.

    I like that idea, thanks!

  • Hope it works out.

    In general I don't like to see code that requires waits for some magical amount of time to function correctly. Better to have some concrete indication that whatever you are waiting for is done.

    Not always possible mind.
  • YanomaniYanomani Posts: 423
    edited January 28 Vote Up0Vote Down
    Is'nt the problem @escher is seeking a solution for, a very good use case for LOCKs?

    Could'nt COG0 instantiate a single LOCK, by using LOCKNEW, then pass its ID as a parameter to the ones it will be starting afterwards, thru cognew?

    This way, it can start as many COGs as there are needed, limited to seven more, for sure.

    When each started COG begins running, it'll use the passed LOCK ID to do a LOCKSET, and then check the returned value, until successfuly grabbing the LOCK ID to itself.

    Then it will ensure exclusive control over the cur_ID variable, manipulate it as intended, and save its new value, including an internal copy, as its own index, before releasing the LOCK, so the others can now get their chances, one at a time.

    COG0 could continualy supervise the whole process, by following the evolution of the curr_ID variable, until it reaches the intended last value AND LOCKSET does return available to itself (COG0), showing that the last involved COG has finalized any internal curr_ID-related task.

    It (COG0) could also maintain a timer, so the interval between curr_ID changes could be monitored and saved, if intended.

    Then the mean time to start and run the basic setup routine for the other COGs would be known.

    As a final acknowledge of end of the whole process, COG0 would execute a LOCKRET to return the LOCK ID to the pool of free ones.

    Hope this can be implemented and helps a bit.

    Henrique
  • May be. All sounds a bit too complex for me. I have never found a Prop application that actually needs locks.
  • Tracy AllenTracy Allen Posts: 6,175
    edited January 28 Vote Up-1Vote Down
    I see nothing inelegant about using the cogID, provided that the main program does in fact always execute cognew in sequence as expected. If you are worried, use coginit instead. From the manual, for coginit:

    If the third field bit is set (1), the Hub will start the next available (lowest-numbered inactive) cog and return that cog’s ID in Destination (if the WR effect is specified).
    If the third field bit is clear (0), the Hub will start or restart the cog identified by Destination’s fourth field, bits 2:0.

    Cognew is a simple case of coginit; sequential order is guaranteed. For reference, here is a thread that talks about the time taken to start a cog:
    cognew-synchronous-or-asynchronous
    See Mike's comment, 16*512 instructions + fudge factor. Basically it is a number of clock cycles, not absolute time.

  • YanomaniYanomani Posts: 423
    edited January 28 Vote Up0Vote Down
    Heater

    If I would have a chance to choose between relying on some cumbersome timing scheme or ensure exclusive control over a shared space, I'll sure would prefer depending of the last one.

    Perhaps a little joke could help deciding it:

    What do you prefer; a LOCK-shared cabin or a TIME-shared one, at a shopping mall bathroom? :lol:

    Sorry, I couldn't resist.

    Henrique
  • @Tracy,

    The issue with relying on actual COG ids is that it all goes to hell if you don't have control over all the code in your program. The beauty of the Prop is that it is very easy to throw other people's code into ones project and not have to worry about where it runs. But that means cog ids cannot be relied on unless one wants to read and understand every object one uses.

    @Yanomani
    If I would have a chance to choose between relying on some cumbersome timing scheme or ensure exclusive control over a shared space, I'll sure would prefer depending of the last one.
    Quite so. As I said above I don't like to see code that relies on waiting for some magical periods of time.

    I prefer control over that shared space, as you say. That is what my simple solution above does. Without the need for locks.



  • Yanomani,

    I like your analogy of the lock shared vs time shared cabin at the shopping mall bathroom.

    The problem presented here is a bit different though. In my proposed solution to the problem here the next user of the cabin does not and cannot even exist until the previous user has finished with it :)






  • Heater,

    Sure, I've understood your solution and its solidity, but the solely reference of any timing waiting loops had re-glowed some ancient sparks into my brain.

    I have an old case with LOCKs (or their absence) and the many times I've prayed for them, to get implemented in some systems I've worked with.

    Even a single and solid one, at least, would have spared me a lot of thinkering.
  • Oh yes, Yanomani, I also have some ancient sparks in my brain regarding software that relied on waits to function correctly. And multi-processor systems that shared data without locks, or locks that did not work correctly!

    Luckily, when Chip suggested removing locks from the P2 design we persuaded him to keep them. Sometimes they are the only solution to a data sharing problem.

    By the way, I love this new word you have invented "thinkering". It's like "tinkering" except you don't actually need to physically do anything. Rather like Einstein's "Gedankenexperiment" but more down to Earth I think.
  • Heater.Heater. Posts: 20,220
    edited January 29 Vote Up0Vote Down
    Oh wait,

    "‘Thinkering' is a word that the writer Michael Ondaatje coined in his novel The English Patient to express the genesis of concepts in the mind while tinkering with the hands."

    https://www.psychologytoday.com/blog/imagine/200808/thinkering

    The meaning of which is somewhat opposite to what I said above.
  • YanomaniYanomani Posts: 423
    edited January 29 Vote Up0Vote Down
    Sure my bad, Heater.

    It's the cannibal-related X part of my chromosomes, inherited from one of my mother's grandmothers, pure and genuine ones, that tend to mix up known words (think and tinkering) into a someway new (to me) vocabule and meaning.

    Perhaps, also my Xs or Ys could have been influenced by her husband (one of my mother's grandfathers), that was basque.

    The mixing of xokleng language speaking people (brazilian "botocudos" indians) and basque from my mother's inheritage with portuguese, spanish , french (the former two surely with a bit of basque too) and arabic, from my father's, resulted in my someway uniqueness, when it concerns to cut or add language-related concepts.

    Sure, I've been always influenced by a six month long course in german (1978), where I've learned to create new words, simply by adding two or more already known ones, in a chain, clipping out any extra or unused pieces (the old cannibal mantra; go ahead, just in parts). :lol:

    I'ts also a Google translate fault (perhaps not, as per your other post), because I tend to type in english (trying to always improve my poor skills, when it comes to typing english words), into the left panel, while looking at the meaning of the translated phrases in portuguese (thinkering is translated, by Google, as having the same meaning as thinking, thus it don't flaggs an exception into my right panel view).

    Henrique

    P.S. My mother's blood type, O rh-; my father's, A rh+; my own, A rh-, and my sister's (younger than me), O rh+.

    It's a true mixing!
  • Tracy AllenTracy Allen Posts: 6,175
    edited January 29 Vote Up0Vote Down
    Heater. wrote: »
    @Tracy,
    The issue with relying on actual COG ids is that it all goes to hell if you don't have control over all the code in your program. The beauty of the Prop is that it is very easy to throw other people's code into ones project and not have to worry about where it runs. But that means cog ids cannot be relied on unless one wants to read and understand every object one uses.

    Well, escher's question as I understand it was not about random use of other peoples' code, it was specifically about a main program that starts in cog 0 and then starts up 5 new cogs, implying control. It is an application that needs to achieve a specific purpose, not an abstract entity. The cogID facility is there to use and operates in a perfectly deterministic fashion. If the application needs to include another object that starts the 7th cog, the initialization can account for it by thinkering. (I.e. minor forethought may be required!)
  • Well, escher's question as I understand it was not about random use of other peoples' code, it was specifically about a main program that starts in cog 0 and then starts up 5 new cogs, implying control. It is an application that needs to achieve a specific purpose, not an abstract entity. The cogID facility is there to use and operates in a perfectly deterministic fashion. If the application needs to include another object that starts the 7th cog, the initialization can account for it by thinkering. (I.e. minor forethought may be required!)

    I agree with this sentiment entirely, however my issue with using CogID in this fashion is that if someone comes in behind me (or I come back way down the road with a bad memory) and initialize any cogs before those, or make any changes which would disturb the exact cogs and their IDs, then it'll all go to hell.


    Yanomani wrote: »
    Is'nt the problem @escher is seeking a solution for, a very good use case for LOCKs?

    Could'nt COG0 instantiate a single LOCK, by using LOCKNEW, then pass its ID as a parameter to the ones it will be starting afterwards, thru cognew?

    This way, it can start as many COGs as there are needed, limited to seven more, for sure.

    When each started COG begins running, it'll use the passed LOCK ID to do a LOCKSET, and then check the returned value, until successfuly grabbing the LOCK ID to itself.

    Then it will ensure exclusive control over the cur_ID variable, manipulate it as intended, and save its new value, including an internal copy, as its own index, before releasing the LOCK, so the others can now get their chances, one at a time.

    COG0 could continualy supervise the whole process, by following the evolution of the curr_ID variable, until it reaches the intended last value AND LOCKSET does return available to itself (COG0), showing that the last involved COG has finalized any internal curr_ID-related task.

    It (COG0) could also maintain a timer, so the interval between curr_ID changes could be monitored and saved, if intended.

    Then the mean time to start and run the basic setup routine for the other COGs would be known.

    As a final acknowledge of end of the whole process, COG0 would execute a LOCKRET to return the LOCK ID to the pool of free ones.

    Hope this can be implemented and helps a bit.

    Henrique

    The locks ended up being the perfect solution for this problem! I would consider them the best practice for this exact kind of problem. My final code is here, with the locks being in the start method and top of the DAT section.

  • Hi @escher

    Sure, the way you've made use of locks, is a good example of an application where the closest-possible-to-atomic nature of the changes made at the shared variable(s), does ensure the short time each COG will keep the (also shared) lock resource, under its exclusive control.

    Having none internal (to each routine) timing dependencies ("aka" deterministic), during the period that each COG "grabs" the lock to itself, is a good recipe of a successful implementation.

    Since I'm not viewing all the code you've done, I can't confirm that a little concern of mine has not been antecipated and solved, elsewhere in the whole code body.

    The question is: is there any place, into the rest of the code, where the start procedure is called, as many times as needed, until a TRUE status condition is ensured to be received by the caller routine, and also that no other caller point does exists, executed afterwards, where a returned FALSE status could be accepted?

    If so, then there are no concerns at all, and the whole code can be considered to behave the way you intended, based on the use you have made of locks.

    But, if there is a chance that a continuous FALSE return is acceptable, the caller routine must ensure the release of the (cog_sem) lock, returning it to the pool of free ones, before giving-up, perhaps with it's own error status code.

    Since I'm confident you've absorbed the concept of using locks, and sure has made a good use of it, Its almost certain that the above concerns are a function of my ignorance of the rest of your code.

    But, just in case...

    Henrique
  • Why not have each COG add one to the value when it has initialized, and then start the next COG?

    A simple check will let the last one know it's last.

    It sets a flag for your main program to continue knowing all 4 COGS are ready.

    One more memory value can specify number of COGS, if ever needed.

    Your main program only starts one.


    Do not taunt Happy Fun Ball! @opengeekorg ---> Be Excellent To One Another SKYPE = acuity_doug
    Parallax colors simplified: http://forums.parallax.com/showthread.php?123709-Commented-Graphics_Demo.spin<br>
  • ElectrodudeElectrodude Posts: 1,146
    edited January 29 Vote Up0Vote Down
    Why not pass the ID in through par, and poke a pointer to the memory they all need into a register before starting any of them?

    I almost never use par for things that are shared among all instances of a cog. It takes up precious cogram. I try to do all initialization in SPIN before starting the cog.
    VAR
    
      long mem[256]
    
    PUB start | i
    
      mem_ptr := @mem
    
      repeat i from 0 to 4
        cognew(@entry, i << 2) ' shift because bottom two bits of par are cut off
    
    DAT
                    org     0
    entry           mov     id,      par
                    shr     id,      #2
    
    loop            rdlong  x,       mem_ptr   ' do whatever
                    add     mem_ptr, #4
                    jmp     #loop
    
    mem_ptr         long    0-0 ' gets replaced by SPIN initialization code
    
    id              res     1
    x               res     1
    
  • potatohead wrote: »
    Why not have each COG add one to the value when it has initialized, and then start the next COG?

    A simple check will let the last one know it's last.

    It sets a flag for your main program to continue knowing all 4 COGS are ready.

    One more memory value can specify number of COGS, if ever needed.

    Your main program only starts one.


    That's definitely another perfectly reasonable solution! I'm going to stick with the locks as being able to say semaphore and mutex makes my project sound cooler, but I appreciate the suggestion!
  • LMAO. I can be a sucker for that kind of thing too.

    Do not taunt Happy Fun Ball! @opengeekorg ---> Be Excellent To One Another SKYPE = acuity_doug
    Parallax colors simplified: http://forums.parallax.com/showthread.php?123709-Commented-Graphics_Demo.spin<br>
  • Another way to do it is to have a LONG in the DAT PASM image called CogID (or whatever). In the launch loop, before launching each instance set CogID to a new value. That way with no in-cog logic each cog knows its identity no matter what cogs were launched before it. Just be sure to wait 8,000 clocks before changing it again to launch the next cog.
  • localroger wrote: »
    Another way to do it is to have a LONG in the DAT PASM image called CogID (or whatever). In the launch loop, before launching each instance set CogID to a new value. That way with no in-cog logic each cog knows its identity no matter what cogs were launched before it. Just be sure to wait 8,000 clocks before changing it again to launch the next cog.

    Or, do it the other way around - have a LONG in the DAT for the memory pointer, and pass the cog id in through par (but shift it up two bits, since par's bottom two bits don't make it through). That way, you don't need to wait 8,000 clocks between cognews.
  • I guess it depends on what's more important. Using PAR burns several COG longs for each purpose, while the wait for a cog to launch before doing the next one is almost exactly 1 millisecond at 80 MHz. Considering how long it takes the Prop to boot from EEPROM, that's unlikely to be noticed but I've had lots of situations where a few COG longs were critical.
  • The neat thing is, there are so many ways to skin the robotic cat. Nice thread in that respect.
Sign In or Register to comment.