Sharing Locks Between Spin and PASM

coryco2coryco2 Posts: 107
edited May 2014 in Propeller 1 Vote Up0Vote Down
After working with Spin for a while, I recently decided to start delving into Propeller assembly, and am a bit confused on how locks (semaphores) can be implemented so Spin and PASM running in separate cogs can access global variables in a mutually exclusive way.

Here is a quick demo I wrote up to test my understanding:
CON

  _clkmode = xtal1 + pll16x                      ' System clock settings
  _xinfreq = 5_000_000

  HOME   = 1                                     ' Parallax Serial Terminal constants 
  CRSRXY = 2
  CR     = 13
  CLREOL = 11
  CLS    = 16

OBJ
  debug : "FullDuplexSerial"                     ' Declare FullDuplexSerial object

VAR
  '(4 contiguous longs)  
  long  variable[3]       
  long  SemID    'ID# of lock checked out by PASM

PUB Start

  cognew(@entry, @variable[0])  'Start cog and pass the address of variable[0] from Spin to PASM cog

  SpinLoop    

PUB SpinLoop

  debug.start(31, 30, 0, 57600)                  ' Start serial connection for debug terminal
                                                        
  waitcnt(clkfreq + cnt)                         ' Wait 1 s before starting

  repeat    
    IF lockset(SemID) <> true   'If this locks the resource by not getting back TRUE as the previous state
      debug.dec(variable[0]) 
      debug.str(string(CR))
      debug.dec(variable[1]) 
      debug.str(string(CR))
      debug.dec(variable[2]) 
      debug.str(string(CR, CR))
      lockclr(SemID)            'unlock the variables to allow PASM to update them    
      
    waitcnt(clkfreq + cnt)

DAT 'Assembly language code
                        org                               
' Entry
entry                   mov     l,par                   'par value here is the variable[0] address passed from Spin in the Start method
                        mov     v1, #0
                        mov     v2, #0
                        mov     v3, #0
                                                        
                        locknew SemIDa                 'check out a lock, store ID in register SemIDa
                        add     l,#3*4                 'Calculate address in main memory 3 four-byte longs from variable[0]  (i.e. Spin variable SemID)
                        wrlong  SemIDa,l               'copy lock number from PASM variable to Spin variable to give both access to the same lock

' Update values

update                  lockset SemIDa         wc      ' Set lock, set c flag to previous state of the lock 

        if_nc           jmp    #$+3                    'if c indicates previous state of lock was clear, jump ahead three instructions        
        if_c            lockset SemIDa         wc      'if lock previously set, set lock and loop back three instructions to test previous state again 
                        jmp    #$-3                          

        if_nc           add     v1, #1                'Increment variable values
                        add     v2, #1 
                        add     v3, #1

                        mov     p,par
                        wrlong  v1,p
                        add     p, #4
                        wrlong  v2,p
                        add     p, #4
                        wrlong  v3,p
                        
                        lockclr SemIDa

                        jmp     update

'
' Data
'
p                      res     1
l                      res     1 

v1                     res     1      
v2                     res     1      
v3                     res     1      
SemIDa                 res     1      'lock ID#

The PASM is supposed to increment the values of three variables from zero up by steps of one, waiting for the Spin in another cog to display the values on the terminal once per second. As is, the code will now will display a set of values only once and stall, and they are 1428597 and not 1 as I would expect. If I remove the delay, it does increment and display more values, but in much larger jumps than one.

What am I getting wrong here, please?

Comments

  • 6 Comments sorted by Date Added Votes
  • kuronekokuroneko Posts: 3,623
    edited May 2014 Vote Up0Vote Down
    Why do you lock twice in PASM land? The second may actually deadlock everything (SPIN releases after the first, PASM locks again but throws the current state away). Anyway, it looks more like that PASM grabs the lock too fast (only one hubwindow between lock/clr). For SPIN to get in there takes a miracle. I added a pause and get what you probably wanted.
    DAT 'Assembly language code
                            org                               
    ' Entry
    entry                   mov     l,par                   ' par value here is the variable[0] address passed from Spin in the Start method
                            mov     v1, #0
                            mov     v2, #0
                            mov     v3, #0
                                                            
                            locknew SemIDa                  ' check out a lock, store ID in register SemIDa
                            add     l,#3*4                  ' Calculate address in main memory 3 four-byte longs from variable[0]  (i.e. Spin variable SemID)
                            wrlong  SemIDa,l                ' copy lock number from PASM variable to Spin variable to give both access to the same lock
    
    ' Update values
    
    update                  lockset SemIDa wc               ' Set lock, set c flag to previous state of the lock 
            if_c            jmp     #$-1
    
                            add     v1, #1                  ' Increment variable values
                            add     v2, #1 
                            add     v3, #1
    
                            mov     p,par
                            wrlong  v1,p
                            add     p, #4
                            wrlong  v2,p
                            add     p, #4
                            wrlong  v3,p
                            
                            lockclr SemIDa
    
                            [COLOR="#FF8C00"]rdlong  cnt, #0
                            add     cnt, cnt
                            waitcnt cnt, #0[/COLOR]
    
                            jmp     [COLOR="#FF0000"]#[/COLOR]update
    
    '
    ' Data
    '
    p                      res     1
    l                      res     1 
    
    v1                     res     1      
    v2                     res     1      
    v3                     res     1      
    SemIDa                 res     1      'lock ID#
    
  • coryco2coryco2 Posts: 107
    edited May 2014 Vote Up0Vote Down
    Thanks for the response.

    Could you please explain how your pause works?

    rdlong cnt, #0
    add cnt, cnt
    waitcnt cnt, #0

    I thought "cnt" was a read-only register containing the current system counter value, but it appears the rdlong instruction writes to it?
  • kuronekokuroneko Posts: 3,623
    edited May 2014 Vote Up0Vote Down
    When cnt is used in the destination slot of an insn its shadow companion is used (normal register). The above is equivalent to
    rdlong  temp, #0
    add     temp, cnt
    waitcnt temp, #0
    
  • I'm having a similar issue with sharing a lock between PASM and Spin. There are several threads on the subject, but none seem to address what I'm experiencing. Technically, the code works alright without a lock, but there is a fairly high probability of "glitches" caused by reading partially updated values!

    Basically, one cog sets "the" lock to indicate that it is reading about 8 bytes of data via I2C from a touchscreen controller (to hub RAM); the lock is cleared after it's finished. In the event that the lock is already checked out, it simply skips reading the touchscreen controller; the main loop will come back around before long.
                        'From here onwards, we "while" away.  First, scan the FT5206 touchscreen
                        'controller, and dump all of the touch registers into Propeller RAM.
                        '
                        'Update touch registers ONLY if the "lock" is clear
    mlCheckTouch        lockset lock_touch      wc
                if_c    jmp #TouchEnd                               'PASM is using the lock, so don't overwrite the registers
                        
                        mov temp1, #5                               '#of registers to read - 1
                        mov touchPtr, par
                        sub touchPtr, #4                            'reach before the RA8875 "mailbox" and get the FT5206's memory pointer
                        rdlong touchPtr, touchPtr                   'reset the pointer address
                        
                       'Initiate an I2C Read from the FT5206.
                        call #startfunc                             '	start();
                        mov i2cTransfer, #FT5206_Address
                        call #writebytefunc
                        mov i2cTransfer, #1                         'address #1 = "Gesture ID"
                        call #writebytefunc
                        '#stopfunc call NOT in example code.  Should be a Repeated Start.
                        call #stopfunc
                        call #startfunc                             '	start();
                        mov i2cTransfer, #FT5206_Address | 1        'READ
                        call #writebytefunc                         'will return Z = 0 (nonzero) from the '5206's ACK
    
    mlTouchLoop         mov i2cAck,  #0                             'ACK
                        call #readbytefunc
                        wrbyte i2cTransfer, touchPtr                'write the returned value
                        'Keep track of where we are
                        add touchPtr, #1                            'writing bytes, not words or longs
                        djnz temp1, #mlTouchLoop
                        
                        'Done.  Read the last register with NACK, and release the bus.
                        mov i2cAck,  #1                             'NACK
                        call #readbytefunc
                        wrbyte i2cTransfer, touchPtr                'write the returned value
                        call #stopfunc
                        'Done writing HUB RAM, so release the lock...
                        lockret lock_touch                          'PASM can now grab it...
                        
                        'If the screen is touched, reseet the "display timer" and/or turn the display back on (if it's off)
                        sub touchPtr, #4                            'from $06 to $02 - TD_STATUS
                        rdbyte temp1, touchPtr      wz
                if_z    jmp #mlTouchEnd                             'It's NOT touched.
                        'Display touched...reset the "off" timer
                        mov tmrDisplay, #0                          'DISPLAY TOUCHED...reset timeout timer
                        'Screen off?
                        test bits, #1               wz
                if_z    jmp #DisplayOn                              'Yes, the display is off.  Turn it back on.
    mlTouchEnd          '===============================================================
    

    Meanwhile, the Spin code that reads the values from hub RAM, waits to check out the lock, reads several registers, and then releases the lock.
    PUB CheckTouch
        repeat until not lockset(lock_touch)
        px := (800- (((FT5206Regs[2] & $F) << 8) | FT5206Regs[3]))
        py := (480- (((FT5206Regs[4] & $F) << 8) | FT5206Regs[5]))     'DEBUG...SCREEN FLIPPED
        tch := FT5206Regs[1]
        lockclr(lock_touch)
    

    If I use the code as written above, the Spin side immediately locks up (pardon the pun), and as the lock is checked out, the PASM side doesn't read the touch controller, either.

    I use locks elsewhere, but only in Spin code, so multiple subroutines accessing the same shared resource have to wait on each other; they work just fine. What is going on here when introducing PASM to the mix?
  • Use lockclr in your PASM routine instead of lockret. The latter returns the lock to the available lock pool. Instead, you just want to clear it so the Spin program can set it.

    -Phil
    “Perfection is achieved not when there is nothing more to add, but when there is nothing left to take away. -Antoine de Saint-Exupery
  • Thanks again...why didn't I catch that?! Now it works as expected.
Sign In or Register to comment.