Shop OBEX P1 Docs P2 Docs Learn Events
getting results back from assembly cog — Parallax Forums

getting results back from assembly cog

David BDavid B Posts: 592
edited 2006-08-13 01:04 in Propeller 1
I'd like to have a Spin program kick off an assembly program running in its own cog. The assembly program will do some work, then return a full 32 bits to the main Spin program.

But I'm only able to get up to 9 bits returned.

Here is a working demo of what I've tried so far. It blinks the on-board LED to indicate YES/NO results, and shows that the cog is returning values, but only up to 9 bits. Bits beyond 9 seem to be truncated.

What am I doing wrong? How can I get a full 32 bit result returned back to spin?

thanks,

David


''
'' Propeller "Hello, World!" demo

CON

_clkmode = xinput + pll16x ' external clock source; multiply by 16
_xinfreq = 4_000_000

Led = 27 ' on-board LED


VAR

LONG asmData
LONG asmResult


PUB main

dira[noparse][[/noparse]Led] := 1 ' Make LED pin output

cognew(@asmTest, @asmData)

repeat
if asmResult > $0
blink


PUB blink | t

outa[noparse][[/noparse]Led] := !outa[noparse][[/noparse]Led] ' toggle the pin state
t := cnt + 32_000_000
waitcnt(t)


DAT
ORG

asmTest

' MOV testNumber, #8 ' low numbers returned
MOV testNumber, #$1FF ' up to 9 bits are returned
' MOV testNumber, #$200 ' bits beyond that are cut off

MOV asmResultAddr, par
ADD asmResultAddr, #4
WRLONG testNumber, asmResultAddr


loop JMP #loop


testNumber RES 1
asmResultAddr RES 1

Comments

  • GadgetmanGadgetman Posts: 2,436
    edited 2006-06-01 12:10
    That is because you're working with Immediate mode, and while the instructions ARE 32bits, there's only room for 9bits of data in one instruction.
    You need to use DATA statements and pointers to the data you want to MOVe. smile.gif

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    Don't visit my new website...
  • Paul BakerPaul Baker Posts: 6,351
    edited 2006-06-01 12:50
    Thats the issue with true RISC processors, all instructions must execute in a single instruction. Since the instruction and destination field of the opcode is occupied that only leaves the origin field left for an immediate value, which is 9 bits. The reason this is so is that pipelines don't stall waiting for other entries to come down the pipeline. While the current propeller isn't pipelined, the next one will be so the instruction set was designed with that in mind.
    The work around as Gadgetman said is to declare a constant in the memory space and MOV using the address to the constant. You are actually doing this already, but you are declaring it as a variable then filling it in run time, instead state its value at compile time, ie testNumber·long $00000200.

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    1+1=10
  • JamesxJamesx Posts: 132
    edited 2006-06-01 17:12
    If you get this working, feel free to post the code. This business is still a bit confusing to me.

    Jim C
  • Paul BakerPaul Baker Posts: 6,351
    edited 2006-06-01 17:29
    Not sure why theres that much confusion, but here is code that will work:
    asmTest
     
    ADD testNumber, #$FF             'manipulations to testNumber can be done, just as a variable can be manipulated
    
     
    MOV asmResultAddr, par 
    ADD asmResultAddr, #4
    WRLONG testNumber, asmResultAddr 'contents of testnumber ($2FF) are moved to memory @asmResultAddr
    
    
    loop JMP #loop
    
    
    testNumber long $0000_0200 
    asmResultAddr RES 1
    

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    1+1=10
  • SSteveSSteve Posts: 808
    edited 2006-06-01 17:54
    David: That 9-bit literal trap is becoming a common theme. Chip has mentioned modifying the Propeller Tool to catch that error, but we haven't heard anything more. Perhaps it would be a good idea to send an email to support@parallax.com to request the feature. That way we can be sure they get the requests through an official channel.

    I just found out about the Propeller Tricks & Traps document today (http://forums.parallax.com/forums/default.aspx?f=25&m=114128). That, at least, would have kept us both from falling victim to that trap.

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    OS-X: because making Unix user-friendly was easier than debugging Windows
  • Jim CJim C Posts: 76
    edited 2006-06-01 18:02
    Yah, I basically have code-dislexia when it comes to assembly and literals<>constants<>variables.
  • David BDavid B Posts: 592
    edited 2006-06-01 18:27
    This is my first attempt at running assembly in a separate cog, so my confusion may just be the normal learning process.

    I gather there is a big difference between "buffer RES 1" and "buffer LONG $0" but I'm not clear how they differ. They both allocate a memory location, they both name it, and both allow it to be modified. I thought the only difference was that one has a predefined value and one doesn't, but now I guess there's more to it than that. And it's kind of confusing to call the second form a constant if it can be modified.

    The tips and trap discussion on transferring an array to the hub has been helpful, but its hard for a Propeller assembly newbie to follow the fragments of code. A complete example would be helpful there.

    I'll work on this when I get home tonight, if I have time, and post my results.

    Thanks for all the tips,

    David
  • Paul BakerPaul Baker Posts: 6,351
    edited 2006-06-01 18:48
    No theres no big difference between RES and LONG, one gets assigned a value and the other doesn't. Though through experimentation you'll find RES does get initialized to zero. So your example of "buffer RES 1" and "buffer LONG $0" are identical, its just more clear to anyone looking at your code if you are using the second in the case of declaring a constant "0". Frequently I define constants ZERO and ONES in propeller assembly programs. I also use the trick for run time constants of filling RES blocks with data before the assembly cog is started.

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    1+1=10
  • David BDavid B Posts: 592
    edited 2006-06-01 20:20
    Paul, I don't understand. It looks to me like the only difference between my non-properly working code and your suggested code is that you use "testNumber long $0000_0200" where I used "testNumber RES 1".

    Can you explain why your code should transfer all 32 bits where mine only transfers 9 bits? Are the names processed differently by the code, with one dereferenced where the other isn't or something?
  • Paul BakerPaul Baker Posts: 6,351
    edited 2006-06-01 20:43
    The WRLONG transfers all 32 bits of the specified register to the hub memory. The problem you are having is that the MOV instruction in immediate mode only sets the lowest 9 bits, my code does not use MOV in the immediate mode, instead it sets up the value at compile time so that the constant is already there (all 32 bits of it). Therefore the correct value is in place when it is written.

    There are 4 ways to deal with values larger than $1FF:
    1) define it as a compile time constant as I have shown.
    2) generate it through other operations which operate on all bits (ADD, SUB, SHL,....)
    3) define it as a run time constant, here you use the RES, then (in your spin program) before you start the cog running assembly you load the location in hub memory with the value you want "testNumber = $0000_0200" followed by the cognew.
    4) use four sets of MOV/SHL to load the value 8 bits at a time, but this is akward and wasteful so don't use it.

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    1+1=10
  • SSteveSSteve Posts: 808
    edited 2006-06-01 21:47
    David B said...
    Paul, I don't understand. It looks to me like the only difference between my non-properly working code and your suggested code is that you use "testNumber long $0000_0200" where I used "testNumber RES 1".
    The difference is the source value in the mov dest,source statement. If you use a number as the source (aka a "literal"), the compiler will ignore all but the lower nine bits of the number. e.g. in the statement mov testnumber, #$FFFF_FFFF testnumber will be set to $1FF.

    When you use a register as the source, all 32 bits are moved. e.g.
    mov testnumber,testvalue
    ...
    testvalue long $FFFF_FFFF

    will move the entire value of $FFFF_FFFF into testnumber.

    Do you have a copy of the Propeller Early Documentation? If you look at the binary representation of the assembly instructions on page 4, you can see that the source value is limited to nine bits. That's because each cog has 512 (2^9) bytes of memory. Those nine bits can either be a literal value or an address.

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    OS-X: because making Unix user-friendly was easier than debugging Windows
  • SSteveSSteve Posts: 808
    edited 2006-06-01 21:51
    Paul Baker said...
    Not sure why theres that much confusion, but here is code that will work:

    asmTest 
    
    ADD testNumber, #$FF             'manipulations to testNumber can be done, just as a variable can be manipulated
    

    That will work as long as the literal source value is =< $1FF. If you tried ADD testNumber, #$FFFF testNumber would only be increased by $1FF.

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    OS-X: because making Unix user-friendly was easier than debugging Windows
  • Paul BakerPaul Baker Posts: 6,351
    edited 2006-06-01 22:12
    SSteve said...
    That will work as long as the literal source value is =< $1FF. If you tried ADD testNumber, #$FFFF testNumber would only be increased by $1FF.

    That is correct, I was only trying to show that manipulations can be done to the value even when it is initialized to a value other than 0.

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    1+1=10
  • JamesxJamesx Posts: 132
    edited 2006-06-01 23:31
    Baritone:

    Do you have a suggestion on finding to reference "Propeller Early Documentation"? Tips on assembly programming are always appreciated.

    Jim C
  • David BDavid B Posts: 592
    edited 2006-06-02 04:59
    Here's a working demo of transferring cog data to the main spin program.

    There's an interesting story behind this. I connected my GPS 1 pulse-per-second output to a Propeller input, and wrote a Spin program to capture the system counter and display the count on an LCD display on each rise of the GPS pulse. It worked, but I was surprised to see that the difference between successive counters was missing the low 4 bits. I guess that the time or the processing it takes to interpret the next Spin commands results in this effect, but I wanted better than 4 mHz resolution.

    Thats what led to writing assembly to run in a separate cog to do the same thing but in assembler. It works, and I'm happy to report that this captures every last bit. So now I have a counter that can measure events with 64 mHz resolution and display the counts on an LCD.

    My propeller board is run by a 4 mHz discrete canned crystal oscillator, the style that are commonly used on motherboards. At room temp, this project reported counts ranging from 64,000,513 Hz to 64,000,497 Hz; with about 16 Hz of noise. I held an ice cube against the can and the clock rate immediately dropped, fairly linearly. After a minute the rate was down to 63,999,993 Hz. Removing the cube resulted in the clock rising again.

    I don't know how much noise was due to the crystal and how much to noise on the GPS pulse. It would be nice to temperature stabilize my local oscillator, then use the Propeller to adjust it's rate from the GPS signal, but that's one just more project among many...

    Spin is great to work with. I've never gotten the LCD display to work so fast on any project; I basically pasted in a few lines of code and it just worked! I love how I can change the code, hit F10, and the new code is instantly running. It really helps speed experimenting and development.

    Attached is the Spin archive of this project. There is still a part that isn't working properly - in the assembly part, there is an oddity where I assign one LONG with a value, but that value appears while reading the following LONG. But I imagine that's just some simple mistake I made somewhere.

    David
  • SSteveSSteve Posts: 808
    edited 2006-06-02 06:26
    Jamesx said...
    Do you have a suggestion on finding to reference "Propeller Early Documentation"? Tips on assembly programming are always appreciated.
    Jim:

    It's attached to the first message in this thread: http://forums.parallax.com/showthread.php?p=572669

    -Steve

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    OS-X: because making Unix user-friendly was easier than debugging Windows
  • Jim CJim C Posts: 76
    edited 2006-06-02 11:20
    Steve:

    Thanks for the reference. I've seem most of that info before from a different thread--but it's good to confirm there's not more.


    David:

    Thanks for the GPS code. That's pretty interesting--and tight! Not much code required to get your data.


    Paul:

    After going through it all again (including "Tricks and Traps"), the literal<>source<>destination business is starting to gel.

    I have one more question, though, after reviewing this thread again: when returning a variable result from assembly back to the hub, why do you have to add #4 to the address that is being written into? Using the example below, seems like asmResultAddr is where you want to write to, not four bytes beyond that.

    MOV asmResultAddr, par
    ADD asmResultAddr, #4
    WRLONG testNumber, asmResultAddr


    Checking the Spin.95.1 manual for info on PAR I found a little snippet (below). Why does the example above need the #4 added, but the example below doesn't?


    MOV Mem, PAR 'Retrieve shared memory addr
    :loop <do something>
    WRLONG ValReg, Mem 'Move ValReg value to Shared
    JMP :loop


    Thanks!

    Jim
  • Paul BakerPaul Baker Posts: 6,351
    edited 2006-06-02 13:10
    Jim, glad to hear its starting to gell, you dont have to add a 4 to the par address, I was only making the minimum alteration nessesary to make David's code working as he wanted. I imagine that why he did it is he will eventually write more than one value to hub memory and par holds the address to the beginning of the data block, and he's trying to write to the second long of the data block.

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    1+1=10
  • JamesxJamesx Posts: 132
    edited 2006-06-02 14:05
    Paul:

    Great. That makes sense.

    Thanks.

    Jim
  • David BDavid B Posts: 592
    edited 2006-06-02 21:04
    At first my idea was to have the assembly cog first write the system clock snapshot to the hub, then write another LONG containing a simple incrementing index. The Spin program would monitor the index, and when it changed, would read and process the system clock snapshot. So the assembly cog had two LONGs to transfer to the hub.

    Writing one LONG to the hub worked perfectly. But writing the second would crash the Spin program! It would lock up, and garbage was written to the LCD display. Like Paul suggested, I was adding the #4 to prepare for writing the second LONG.

    So in the code I posted, I just let the system counter snapshot LONG by itself be written to the hub, and it works perfectly. I'd sure like to know what I'm doing wrong with the second write, but I guess that's probably just a matter of gaining more experience with Propeller assembly coding.
  • Paul BakerPaul Baker Posts: 6,351
    edited 2006-06-02 21:12
    David, is the information contained within the index nessesary? Could the spin program keep a copy of the last value of the counter read and compare it with the new copy? I have successfully written assembly programs which write entire arrays to hub memory. I'll have to check the program when I get home to see how I did it.

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    1+1=10
  • David BDavid B Posts: 592
    edited 2006-06-02 22:12
    The working code I posted does what you suggest - the Spin code keeps the last counter value and displays when the count changes.

    I'm not really sure what I want this to do; I'm mostly playing around with it, learning Spin and Propeller assembly and seeing what the Propeller can do.

    But if nothing else, for my own education, I'd like to know what I'm doing wrong in getting multiple LONGs written to the hub, and also what I'm doing wrong in the assembly part where the value of the initialized LONGs shows an odd behavior that I commented on in the code.
  • Paul BakerPaul Baker Posts: 6,351
    edited 2006-06-03 00:20
    Im not sure whats going wrong with you program, it very well may be the spin code thats misbehaving, I'll share my code but it uses a lot of non-standard tricks which would take a while to explain:

    :hptr         mov       :hptr, par                      'use this loc as ptr to hub mem
                  mov       :i, #CogBufSz2                  'reinit counter
                  wrlong    :d_inc, :hptr                   'write non-zero to signal start
                  add       :hptr, #1                       'inc hub pointer
     
    :writeloop    wrlong    fbufstart, :hptr                'write long to main memory
                  add       :writeloop, :d_inc              'inc buffer pointer
                  add       :hptr, #4                       'inc hub pointer 1 long
                  djnz      :i, #:writeloop                 'empty entire buffer              
    
    

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    1+1=10
  • parskoparsko Posts: 501
    edited 2006-08-08 21:06
    I'm bringing back an old post...

    I am having trouble compiling the attached routine. It has something to do with my "ADCval" variable that is supposed to be shared between the ASSM and SPIN code. Specifically, at line 24 in "Shiftin_test_A_001" in the command "test.dec(ADCval)" is says that it is "expecting and expression term".

    Any thoughts?

    Thanks,

    -Parsko
  • Graham StablerGraham Stabler Posts: 2,510
    edited 2006-08-08 21:20
    Try keeping all your RES declarations together and after all other declarations, having ADCval long |<0 at the end may cause a problem, it did when I did something like that

    Graham
  • Mike GreenMike Green Posts: 23,101
    edited 2006-08-08 21:33
    1) All longs have to come before any res statements
    2) This is incomplete (there are no instructions after the last wrlong).
    3) Shiftin_test_A_001 is not in the attached archive
    Try something more like this:
    var long common
    ...
      coginit(@assembly,@common)
    dat
    assembly  rdlong  temp,par
     ... do something with the value in temp
                   wrlong  temp,par
                   jmp     #somewhere
    
    temp      long      0
    
    


    If you need more than one word of data, you can try making common an array (long common ):
    assembly  mov  tempAddr,par
                   rdlong  temp1,tempAddr
                   add     tempAddr,#4
                   rdlong  temp2,tempAddr
                   <do whatever you want with the values>
                   mov  tempAddr,par
                   wrlong  temp1,tempAddr
                   add    tempAddr,#4
                   wrlong  temp2,tempAddr
                   jmp    #somewhere
    
    tempAddr long   0
    temp1     long    0
    temp2     long    0
    
    


    You do have to have a way to tell the SPIN routine that the work is done and the results have been written to the HUB RAM.
    You can use the sign bit of one of the long values. The initial value of common is always positive. The COG sets the sign bit of temp2 to a one. When it writes temp2 into common, that long will have a sign bit of one and both common[noparse][[/noparse]0] and common will have been updated (since the second long is stored last).
  • parskoparsko Posts: 501
    edited 2006-08-12 20:13
    Okay, I still can't get it. I keep getting errors while compiling that point to the ADCval variable.

    I am starting object1. Calling object2 to start and run an assembly program. This program does something with external hardware, then writes the value to main memory. Object1 then writes this value to the TV, then loops back to read in another value. It's a wicked slow loop, but I am only trying to test the high speed shiftin assembly code to see if it works.

    I can't get the writing to main memory to work. I am begoggled! confused.gif The code in the shiftin object might be a bit messy cause I have been trying everything I know/can read.

    Please help.

    -Parsko confused.gifcry.gifconfused.gif
  • Mike GreenMike Green Posts: 23,101
    edited 2006-08-13 01:04
    The problem is that you're defining a shared value improperly. It won't work the way you've done it. First of all, you have ADCval and ADCtemp in the COG's memory and only the program running in that COG has access to it. Second, the address of any SPIN variables is only known at runtime. Third, if you try to use a value in the DAT block, that's not really the value in the COG's memory. It's copied into the COG when the COG is initialized (so you can use it for initial values), but the SPIN code only sees its initialization copy. Also, you don't need a stack for the assembly routine. Assembly doesn't use a stack. Let's say you're trying to get an ADC value and return it to the SPIN program. You pass the address of the shared value in the COGINIT or COGNEW call and it appears in the PAR register. You can use the PAR register directly with the RDLONG and WRLONG instructions like this:
    VAR long ADCval
    PUB start
      coginit(@getADC,@ADCval)
    
    DAT
    getADC      ..... stuff
                     wrlong   finalValue,PAR
                     ..... stuff
    
    


    This writes the "finalValue" into ADCval. You can get an initial value the same way with "RDLONG value,PAR".
Sign In or Register to comment.