Shop OBEX P1 Docs P2 Docs Learn Events
Methods with internal state - Inline Pasm - solution found — Parallax Forums

Methods with internal state - Inline Pasm - solution found

bob_g4bbybob_g4bby Posts: 576
edited 2026-03-11 14:22 in PASM2/Spin2 (P2)

If you want to create a method that has internal state, it's easy enough to write a SPIN2 object containing the method. The internal state of the method is defined in the VAR section of the object. Several instantiations of the object can be made and calls to object1.mymethod, object2.mymethod will be successful, with each call having it's own private internal variables whose values persist between calls to mymethod. State is preserved for things like calculating as many rolling averages as your application wants, within compiler limits.

I'd like to do the same with an Inline Assembler method and the following code shows one way (inclong1) that would allow more than one instance if required and one method I suspect that would not (inclong3), because the DAT secction is common to all instantiations, it is not duplicated - yes? I guess the DAT section is really meant to be read-only, alhtough I've found you can write to it.

inclong1 is a bit clunky, because you have to pass the address of mylong into the method as a parameter.

'' File .......... Preserving data between calls.spin2
'' Version........ 1
'' Purpose........ To explore ways of preserving internal state of inline pasm methods
'' Author......... Bob Edwards
'' Email.......... bob.edwards50@yahoo.com
'' Started........ 4/3/2026
'' Latest update.. / /

''=============================================================

CON {timing constants}

    _xinfreq =  20_000_000
    _clkfreq = 288_000_000

VAR {Variable Declarations}

long mylong                     ' Just your usual long

{Public / Private methods}

pub main() | i

    mylong := 0
    repeat
        inclong1(@mylong)
        i := inclong3()
        debug(sdec(mylong))
        debug(udec(i))
        waitms(100)


pub inclong1(varptr) | temp
'increment a long pointed to by varptr

    org
        push ptra
        mov ptra,varptr
        rdlong temp,ptra
        sub temp,#1
        wrlong temp,ptra
        pop ptra
    end


pub inclong3() : mmm | baba, address

    address := @mycounter2

    org
        mov ptra,address
        rdlong baba,ptra
        add baba,#1
        wrlong baba,ptra
        mov mmm,baba
    end

DAT {Symbols and Data}

mycounter2 long 0

In the SPIN2 handbook in the IN-LINE PASM CODE section it says about starting and ending a method call :-

  • Copy the method's first 16 long variables, including any parameters, return values, and local variables, from hub ram to cog registers $1E0 ..$1EF
    blabla...

  • Restore the 16 longs in cog registers $1E0..$1EF back to hub RAM, in order to update any modified method variables.

But elsewhere, in PUB AND PRI BLOCKS it says:-

  • Results and local variables are initialized to zero on method entry.

So are In-line PASM method internal variables preserved from call to call as the words in italics suggests, or do they get zeroed on method entry???

Cheers, puzzled bob

Comments

  • ersmithersmith Posts: 6,274

    @bob_g4bby said:

    In the SPIN2 handbook in the IN-LINE PASM CODE section it says about starting and ending a method call :-

    • Copy the method's first 16 long variables, including any parameters, return values, and local variables, from hub ram to cog registers $1E0 ..$1EF
      blabla...

    • Restore the 16 longs in cog registers $1E0..$1EF back to hub RAM, in order to update any modified method variables.

    That isn't starting and ending a method call, it's starting and ending an inline PASM block. That is, before the inline PASM is executed the local variables are copied to COG RAM, and after the inline PASM finishes they're copied back so that they can still be used in the Spin code for the duration of the current method call. On the next method call they will be re-initialized to 0.

    Your earlier suggestion of a pointer is really the only way to do what you want, the PASM will have to read from the object VAR section in order to get preserved variables.

  • JonnyMacJonnyMac Posts: 9,737

    If you look in the cog RAM map for the interpreter you'll see a 16-long gap ($1E0..$1EF) between the PRx registers and the interrupt jump and return registers.

    When you call a method with inline PASM up to 16 longs from the parameters, results, and local variables are copied to these cog registers -- I assume so that they can be accessed directly instead of through hub access instructions. Using DEBUG you can see this:

    The value in t1 is read from the cog copy of the local variable a -- note that it matches the internal update (725)

    The value in t2 is value of local a read from the hub using the address passed in local b. Notice that while we're in the cog it's still 0. Before passing back hub code the registers in $1E0..$1EF are copied back to the method definition line.

    As mentioned before, unless you mess with them in Spin, the PR0..PR7 registers are persistant and you can probably figure out a way to use them to save state values for an inline PASM method.

  • JonnyMacJonnyMac Posts: 9,737

    This shows that the PRx registers can be accessed inside and outside of the cog and are not modified by the interpreter between calls.

  • Many thanks, both, I get the situation now.

  • @cgracey - Chip, may I put in a suggestion for Pnut. I'd like to be able within an Inline Pasm method to :-

    Load ptra with start address of global variable
    Load register from global pointed at by ptra
    Do calculation
    Save register to global pointed at by ptra

    That would enable inline pasm methods which need to save internal state between calls. For my current project, that might be automatic gain control, iq phase /amplitude correction, i2c based oscillator control.

    Keep up the good work, cheers, bob

  • ersmithersmith Posts: 6,274

    @bob_g4bby You could always do something like that in the Spin code around the inline PASM, e.g.:

    VAR
       long global_a
    
    PUB myfunc() | a
      a := global_a ' load from persistent state
      ORG
         add a, #1  ' or do whatever you want to variable a
      END
      global_a := a  ' update the persistent state
    
  • @ersmith , that would do the job very well - thanks very much. The spin statement fore and aft would add very little time. My dsp in-line assembly methods take between 50-200uS to chew through 1024 iq samples. It pays over and over again to ask the forum - I get locked in to a few ways of doing things, and here comes your simple solution - doh! Myfunc can be given it's own file (object) with only the global variables it needs. Then that object can be instantiated as many times as required - job done.
    Cheers, bob

Sign In or Register to comment.