Updated pasm_toggle example
mindrobots
Posts: 6,506
11-17-11 EDIT
In keeping with the spirit of Steve's series of Toggle examples, I fixed the pasm_toggle to work as the other examples do while still showing how to start a COG with PASM code and pass variables to it via a mailbox. The variable passing wasn't working in the pasm_toggle example as provided with the latest propgcc demos I have. The attached .zip pasm_toggle_updated.zip contains the code to match the other examples. My mailbox code listed in this post will become another example for 2-way C/PASM data exchange.
This thread as it started and was commented on has some valuable information from Eric that will be included in my documentation and future tutorials.
END EDIT
On my way to the library examples, I got distracted by jazzed and potatohead's discussion about using OBEX PASM routines and a "clean" interface between propgcc and PASM. I went to look at the toggle_pasm examples and (sorry Steve) saw that they really didn't show what I believe was intended. So, I rewrote them in a simple verbose style. I've included the source in the thread as well as attached a complete .zip of the demos/toggle/toggle_pasm directory.
This was developed and tested on a QuickStart as the default LMM memory model.
The C code:
/** * @file toggle.c * This program demonstrates starting a PASM COG * and being able to pass parameters from a C program to a PASM program * and from PASM back to C. * * C to PASM Mailbox example * * WARNING: This code makes all IO pins except 30/31 toggle HIGH/LOW. Check if this is OK * for the board you are using. * * * to use: * from directory containing source * make clean * make * propeller-load -pn -t -r toggle.elf (where n is port #) * * Copyright (c) 2011, Steve Denson, Rick Post * MIT Licensed - terms of use below. */ #include <stdio.h> #include <propeller.h> // propeller specific definitions // the STATIC HUB mailbox for communication to PASM routine // static unsigned int delay; // a pointer to this gets passed to the PASM code as the PAR register static unsigned int pins; static int loop_cnt; static int pasm_done; // C stub function to start the PASM routine // need to be able to provide the entry point to the PASM // and a pointer to the STATIC HUB mailbox // the cognew function in the propeller.c library returns the COG # // int start(unsigned int *pinptr) { extern unsigned int binary_toggle_dat_start; cognew(&binary_toggle_dat_start, pinptr); } void usleep(int t) { if(t < 10) // very small t values will cause a hang return; // don't bother function delay is likely enough waitcnt((CLKFREQ/1000000)*t+CNT); } // C main function // LMM model void main (int argc, char* argv[]) { printf("hello, world!\n"); // let the lead LMM COG say hello delay = CLKFREQ>>1; // set the delay rate in the STATIC mailbox // this is actually the duty cycle of the blink 0.5 sec on, 0.5 sec off pins = 0x3fFFffff; // set the PIN mask into the STATIC mailbox // light up all pins except 30 & 31 since we don't know board config loop_cnt = 20; // number of time through the loop (20 toggles, 10 on/off cycles) pasm_done = 0; // make sure it's zero since we'll sit and wait on it to change in a few lines printf ("New COG# %d started.\n",start(&delay)); // start a new COG passing a pointer to the STATIC mailbox structure printf ("waiting for semaphore to be set by PASM code.\n"); while (!pasm_done) { usleep(10); // wait for the PASM code to clear the loop counter } printf("goodbyte, world!\n"); while(1); //let the original COG sit and spin } /* +-------------------------------------------------------------------- TERMS OF USE: MIT License +-------------------------------------------------------------------- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +-------------------------------------------------------------------- */
The PASM
{{ toggle.spin Propgcc - PASM toggle demo Simple PASM routine to demonstrate interaction between PASM subroutines an PROPGCC main program. The code running in the C address space to talk to exchange data values with PASM code running in a COG. The C program has visibility/access to the mailbox variables as normal C variables. The PASM program has visibility/access to the mailbox variables through the PAR register initialized by the COGNEW and hte RDLONG/RDWORD/RDCHAR and WRLONG/WRWORD/WRCHAR instructions. C program starts the PASM routine via cognew() function, passing strat address and PAR register value. PAR register should be the address of a STATIC data area of LONGs in the C program. For this example: PAR -> static unsigned int delay; static unsigned int pins; static unsigned int loop_cnt; static unsigned int pasm_done; The first three variables are used as input to the PASM routine, the last is used to act as a semaphore back to the C routine. This could have been written as more efficient PASM code but for the examples, I was going for maximum clarity at this point. Copyright (c) 2011, Steve Denson, Rick Post MIT Licensed. Terms of use below. }} pub start(pinptr) cognew(@pasm, pinptr) dat org 0 pasm mov mailbox_ptr,par ' save the pointer to the STATIC parameter (HUB) memory area ' the PAR register is initialized by the cognew() function and is a pointer to ' the first STATIC int declared in the C code as the mailbox ' mailbox_ptr will be changed as the code executes. You can reload ' the initial pointer from PAR if you ever need it to point to ' the start of the mailbox again rdlong waitdelay, mailbox_ptr ' read the wait delay from HUB - it is initialized by the C program ' in C program: delay = CLKFREQ>>1; add mailbox_ptr,#4 ' point to the next LONG in HUB rdlong pins,mailbox_ptr ' the caller's PIN mask as initialized in the C program ' in C program: pins = 0x3fffffff; add mailbox_ptr, #4 ' point to the next LONG (4 bytes each) rdlong loopcounter,mailbox_ptr ' set the loop count as provided by the C program ' in C program: loop_cnt = 20; add mailbox_ptr, #4 ' point to the next LONG which is the semaphore we are setting when done mov dira, pins ' set pins provided by C program to OUTPUT mov nextcnt, waitdelay add nextcnt, cnt ' best to add cnt last :loop xor outa, pins ' toggle pins waitcnt nextcnt, waitdelay ' wait for user specified delay djnz loopcounter,#:loop ' loop until the C provided counter hits zero mov done_flag,#1 ' set the semaphore to one wrlong done_flag, mailbox_ptr ' and save it back into hub memory via the ptr provided by the C program ' in C program: while(!pasm_done) to test for update from PASM jmp #$ ' to infinity and BEYOND!! ' these do not need to be in any particular order or have particular names. There is no relationship between these ' local copies of the C variable except when you create via the PAR register and HUB instructions ' there is no address resolution or linkage done by propgcc or the loader ' mailbox_ptr long 0 ' working ptr into the HUB area - reload from PAR if needed pins long 0 ' local copy of the user's PIN mask waitdelay long 0 ' local copy of the user's delay loopcounter long 0 ' local copy of the user's loop counter done_flag long 0 ' local copy of the semaphore to return to the C program nextcnt long 0 ' local variable to save target value from waitcnt {{ MIT Licensed. +-------------------------------------------------------------------- TERMS OF USE: MIT License +-------------------------------------------------------------------- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +-------------------------------------------------------------------- }}
This was really a great learning experience since I haven't really written PASM yet and certainly haven't played with the PAR register. It has lead to a number of questions.
Alpha Tester question: If I remove the usleep(10) from the first while and make it an empty statement, the C program hangs after starting the PASM code and never sees the seamphore change values. As the code currently is, it works fine. Is this a propgcc problem with empty loops? Optimization gone bad? Or am I expecting behavior in an empty loop I shouldn't be getting (programmer error)?
General propgcc questions:
1) Is a STATIC declaration guaranteed to be in HUB on all memory models (except -mcog)? If not, then this needs to be documented and the examples written differently for different memory models.
2) Where does the 'binary_toggle_stat_start' tag come from? I looked lots of places and never saw it get generated but it certainly gets resolved at link time. This needs to be documented for those more ambitious coders with multiple PASM routines.
3) From a coding style perspective, should the mailbox really be a STRUCT? I tried to keep it simple for a SPIN/PASM programmer transitioning to propgcc (I'm not up to talking about propg++ implementations yet).
4) In the toggle.spin program, I'm assuming the two lines of SPIN at the start are just to make the file a valid SPIN program so it will compile.
I'm sure my PASM code can be tightened up but for what it's doing, again, simple was the key (plus it is my first PASM code).
I think this is worth wrapping a tutorial around for our friends transitioning from SPIN/PASM and others learning propgcc.
Sorry it's so long but I thought there might be folks that just wanted to look at the code without having to download and unzip the thing.
Feedback & criticism appreciated - I'm just stumbling along here.
zip
11K
Comments
It's programmer error, but a subtle one. The problem is that the compiler doesn't know that the pasm_done variable can be unexpectedly changed, and so it optimized the loop into an endless loop. What it did would be correct if no other cog was running, or if pasm_done wasn't the cog communication variable. We have to add the C volatile keyword to the declaration of pasm_done to tell it that the variable could be changed by something outside of the C code (the PASM code, in this case).
A static declaration is treated the same as any other variable declaration: in -mcog, -mlmm, and -mxmmc it will go in HUB memory, in -mxmm it will go in external memory. To force it in hub memory in -xmm mode you can add __attribute__((section(".hub"))) to it.
I believe it comes from the OBJCOPY command that created the toggle_firmware.o (in the Makefile). You're right, this should be better documented.
It definitely should be a struct; otherwise the linker might not necessarily keep the variables together in memory. For example, if you add an initialization to one of them that one will end up in the .data section (for initialized data), while the others will be in .bss (for uninitialized data).
Have you looked at the toggle_gas demo? Using the GNU assembler instead of PASM it's even easier to interface the assembly code with C code.
Eric
Thank you for the excellent clarifications. I have massive gaps in my C knowledge, so it's hard for me to identify "Alpha" problems versus "Rick" problems. Hopefully my value will be found in providing well documented, simple examples and explanation for people transitioning to propgcc from other places.
I'll fold your suggestions/corrections into my examples. There's a wealth of information that needs to get captured in the documentation and tutorials.
I did look at toggle-gas and it is much simpler and better integrated. Still, there will be the need for incorporating PASM with "clean" interfaces.
I'll also make my toggle work in a similar fashion to Steve's other toggle programs so from the blinking light standpoint, they look the same and really do just show the differences of getting the same results from several different implementations.
I just finished some testing with VOLATILE.
If I changed the variable as it is in my original C program:
static int pasm_done;
to
static volatile int pasm_done;
it still got optimized away (as best I could tell).
Once I built it into a STRUCT:
then it did not get optimized away and the semaphore works as expected with an empty while loop.
I'm not sure if this is working as you had expected but this is what I've observed in testing. I need to do more testing with a single volatile variable. That should work as you described without having a single variable wrapped inside a structure.
In digging around more and playing with volatile, I stumbled upon signal.h which started me to thinking about standard support for COG to COG signals and LOCK support. Is signal.h being implemented? LOCK support? (I come from an old multi-processor mainframe background, so my mind often happily wanders off to inter-processor communications and data structure locking.)
Today, I'm off to gas_toggle to see what I can screw up there!!
If you put everything in a struct you can just mark the whole struct as volatile, rather than having to put volatile on each member, which is another benefit of structs.
signal.h is implemented, but cogs can't send signals to other cogs. The only way to send a signal is with the raise function, and it just causes the signal to happen on the current cog.
Low level LOCK support is provided by __builtin_propeller_lockset, __builtin_propeller_lockclr, __builtin_propeller_locknew, and __builtin_propeller_lockret, which act like the PASM instructions (except instead of setting the carry they return -1 or 0). We're using one lock (stored in the global __C_LOCK) to provide higher level
inter-cog (or rather inter-thread) communication. See sys/thread.h, and pthread.h in the current repository... I'm not sure if it's made it to the distribution yet.
In LMM mode pthreads will use as many cogs as are available to run threads. In XMM mode only one cog is used for threads (that's a limitation of the current caching model; we may be able to work around this in the future). If there are more threads than cogs, then threads will have to share cogs.
The thread model is cooperative, so threads have to call pthread_yield, pthread_mutex_lock, or sleep to give up the processor.
Eric
I ended up changing the start function to this:
to get rid of two warning about variable type mismatches and "volatile" being discarded on the function call.
You were correct about binary_toggle_dat_start being generated out of objcopy. You can find the name when you dump the symbol table (-t) with objdump. I'll document this so people can find the name on their own PASM code entry points.
Thanks for all the help! You're a wealth of information!
Based on my discussions with Eric and my original intent to provide a working pasm_toggle that performed as Steve's other toggle demos do, I now have the corrected and updated code. The .zip for this is attached to the first post in this thread as pasm_toggle_updated.zip. The code is listed here in case anyone prefers to see the code without having to download and unzip.
The C program:
The PASM:
Thanks for playing along and being tolerant.
If this was easy, even I could do it!!
There is one small bug: the start function is missing a return statement.
Also, it might also be better to declare binary_toggle_data_start as an array of unsigned ints,
since in a way it is really an array of ints (the instructions for the cog) and then you don't have to remember to put the & to take it's address:
Eric