Shop OBEX P1 Docs P2 Docs Learn Events
Multiple PASM Objects Into One PASM Cog — Parallax Forums

Multiple PASM Objects Into One PASM Cog

pjvpjv Posts: 1,903
edited 2011-11-17 09:38 in Propeller 1
Hi All;

I'm still learning Spin here, so I may be not on the best path...

What I'm trying to do is to load several PASM objects into a single PASM cog. The loading (Spin) cog will pull the objects in sequentially as they are listed. Inbetween the loaded objects appear the object header(s) which don't seem to cause much of a problem. What I am not finding an easy solution for is that each object gets compiled with an ORG of 0, and since they are located sequentially into the operating cog, the object's address references are messed up. I'm wanting to avoid placing special ORGs into each object as that would become a nightmare while developing code. Nor do I want to combine all the objects into a single super-object as that will ruin the flexibility I'm looking for.

What I can do, is load the objects via a PASM program and have it re-locate the addresses, but that is also a bit messy, and I was also wanting to keep all this rather Spin friendly........

Does anyone have any tricks or advice ?

Many thanks,

Peter (pjv)

Comments

  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2011-11-16 14:14
    It looks like you've already ruled out the available options. On-the-fly relocation is not really an option, BTW, since the context for immediate operands disappears, once the source has been assembled, viz:
            add     ptr,#bufaddr
    
    vs.
    
            add     ptr,#100
    

    -Phil
  • kuronekokuroneko Posts: 3,623
    edited 2011-11-16 16:13
    FWIW, I played with this a while back. Provided the code fragments are not too long this may work for you. What I do is add 2 bits for each instruction in a long vector before or after the actual code. Each bit corresponds to dst and src field and - if set - means please adjust. The relocate method takes address to code, number of longs, relocation vector address and required offset. Then it's (manually) placed into a buffer (for demonstration) and started (could be done with an overlay loader as well).
    CON
      _clkmode = XTAL1|PLL16X
      _xinfreq = 5_000_000
    
      ofs = 57
      
    VAR
      long  buffer[512]
      
    PUB selftest | lcnt
    
      lcnt := (@tail - @entry) >> 2
      relocate(@entry, lcnt, @tail, ofs)
      longmove(@buffer[ofs], @entry, lcnt)
      
      coginit(cogid, @buffer{0}, 0)
        
    PRI relocate(base, lcnt, info, offset) : n
    
      repeat lcnt
        outb := long[info][n >> 4] >> (n << 1)
        dirb := long[base][n]
        if outb[0]
          dirb[8..0]  += offset
        if outb[1]
          dirb[17..9] += offset
        long[base][n++] := dirb
    
    DAT             org     0
    
    entry           jmpret  outa, [COLOR="#FFA500"]#$+1[/COLOR]
    
                    mov     dira, [COLOR="#FFA500"]mask[/COLOR]
                    rev     outa, #{32-}8
                    waitpeq [COLOR=blue]$[/COLOR], #0
    
                    hubop   [COLOR=blue]$[/COLOR], #%10000_000
                    
    ' initialised data and/or presets
    
    mask            long    $00FF0000
    
    ' uninitialised data and/or temporaries
    
                    fit
    
    '                         FEDC BA98 7654 3210
    tail            long    %%0000_0000_000[COLOR=blue]2[/COLOR]_[COLOR=blue]2[/COLOR]0[COLOR="#FFA500"]11[/COLOR]
    
    DAT
    
  • pjvpjv Posts: 1,903
    edited 2011-11-16 21:39
    Thank you Phil and Marko;

    Marko, what you are describing is almost precisely what I do when I'm doing relocation in assembler, and it works great. Yours uses Spin to do the relocation, but the same principles apply. I had not considered doing that in Spin as it it is much slower (100x) than what I am looking for.... dynamically loading code into a cog while that cog is already running other code. In assembler the concept does operate quite well, loading and relocating small code segments in in the equivalent time of one, or a couple of, Spin instructions. So when Spin triggers driver code to be loaded, it's done by the time the next (few) Spin instructions are executed.

    What I was hoping for was some technique in Spin that was unknown to me (newbie in this realm) that permitted over-riding the ORG directive or some other approach that would result in a contiguous address space among objects at compile time, eliminating the need for Spin-programmed relocation.

    While my ultimate goal is much along the line of what my PASM approach is, I was wanting to mimic similar results in Spin.

    Thank you both for your comments.

    Cheers,

    Peter (pjv)
  • kuronekokuroneko Posts: 3,623
    edited 2011-11-16 21:47
    pjv wrote: »
    What I was hoping for was some technique in Spin that was unknown to me (newbie in this realm) that permitted over-riding the ORG directive or some other approach that would result in a contiguous address space among objects at compile time, eliminating the need for Spin-programmed relocation.
    If you want this done at compile time then org should be able to do exactly what you want. But I'm probably missing an important fact here :)
    DAT             org     0
    
    one             nop
                    jmp     #$-1
    
    DAT '{implied}  org     $
    
    two             nop
                    jmp     #$-1
    
    DAT '{implied}  org     $
    
    three           nop
                    jmp     #$-1
    
    DAT
    
    This gives you three code fragments properly aligned provided they are loaded in that order which one might consider a drawback. That said, the org $ isn't strictly necessary in the above case (it's implied).

    After re-reading your original post, that would work against you wanting to avoid a single object. And IIRC spreading them over separate objects will force you to use specific (hard-wired) orgs anyway. Even if there was a way, the load order would be effectively hard-coded and you wouldn't be able to dynamically re-arrange them (e.g. don't need module A, load C instead) without relocation?! Or does it work like module A is always loaded at offset OA?
  • ericballericball Posts: 774
    edited 2011-11-17 06:14
    pjv wrote: »
    dynamically loading code into a cog while that cog is already running other code.
    Unfortunately, there's no way for one cog to partially load data into another - as the only way for one cog (whether it's running the SPIN interpreter or PASM) to load data into another cog is via COGINIT/COGNEW - which always loads the entire cog. A cog could load code into itself, but I don't know if that is what you are attempting to achieve.
  • pjvpjv Posts: 1,903
    edited 2011-11-17 09:01
    Kuroneko and Eric;

    Thanks for your comments, and I believe I should be clearer in my post.

    There are two things I am working on, the principal one being an assembler based scheduler encompassing a loader, all running in a single cog. The scheduler orchestrates an orderly distribution of cog compute cycles on an as-needed basis to numerous threads running simultaneously in the same cog. Meanwhile, the loader continuously monitors a hub mailbox looking for commands such as loading, dismissing or editing thread parameters in the cog. This is working well, but I still want to clean up some rough edges, and make the external mailbox interface a bit more "Spin friendly" so that numerous Spin programs accessing the programs (mostly drivers) in the cog will not trip over each other.

    The second thing is, while working on this it occurred to me that, to a degree at least, a somewhat similar feature might be possible through loading the scheduler plus multiple assembler objects (like drivers) into a single cog with a COGNEW/COGINIT, but without the dynamic loader concept. And I would look to do that without messing with the internals of the objects.... just string one behind the other in the launcher's OBJ definition section. And here is where I ran into the ORG snag. Each canned object, being its own entity, and not wanting to mess with its internals, conflicts with the ORG of the following objects, and hence simple sequential loading does not appear possible. If Parallax could, or would, modify the compiler so that it does not automatically reset the ORG to zero at the start of each object, but rather lets the programmer explicitly change or keep the ORGs when he wants to. Better yet would be for the compiler to start ORG at zero for the first object, but not re-zero it if it encountered an "ORG $" at the start of any following objects.

    Without that, I'm not sure if I can solve this one, or if it's worth a lot of effort, but I thank you both for your insight and comments.

    Cheers,

    Peter (pjv)
  • ericballericball Posts: 774
    edited 2011-11-17 09:38
    Hi Peter,

    Okay, I think I understand what you want to do. You have a collection of PASM functions F1, F2, F3... and you want to dynamically choose a set of functions, then COGINIT them. Your challenge is PASM is statically addressed, which is a function of the ISA - not the ORG. Let me explain:

    Normally PASM code starts with ORG 0 because the first instruction is always loaded into "register" 0.
    PUB start
      coginit( @cogstart, @cogstart )
    DAT
             ORG 0
    cogstart JMP #cogstart  ' assembled to $000: JMPRET NZ NC NR IF_ALWAYS $000 #$000
    
    Now say, for example, you used ORG $100 instead. Then the code would be assembled to JMPRET NZ NC NR IF_ALWAYS $000 #$100, which would do the same thing if loaded into register $100, but if loaded into any other register would just jump to whatever instruction is stored in register $100. Other ISAs have relative addressing (particularly branch on condition), but PASM doesn't. So you'd need to code "helpers" at fixed locations.

    The usual way to accomplish your task is via overlays where each function uses the same ORG and is always loaded into the same location. Or you could have multiple copies of each routine, each with a different ORG.

    To have position independent code you'd need to first allocate your working variables at fixed registers. Then for jumps (and MOVisd, or other self-modifying code) you'd need a "helper" function at a fixed location, e.g.
    jmphelp     ADD jmphelp_ret,#jmphelp_ret
    jmphelp_ret RET
    
           MOVS jmphelp, #jmpdst-jmpsrc
    jmpsrc CALL #jmphelp
    
    jmpdst opcode
    

    Alternately you could implement a loader which would add an offset to the destination register and the source register if the immediate flag isn't set (except in the case of JMPRET).

    Whatever you do, there will be significant latency on top of the COGINIT delay to create the DAT block, especially since each instruction will need to be copied between HUB RAM locations. It would be somewhat faster if the COG did the job itself.
Sign In or Register to comment.