GCC - Call assembly from C
TomUdale
Posts: 75
Greetings,
I am investigating the propeller chip and am looking at the gcc compiler. One thing I am trying to come to grips with is the ins and outs of calling assembly routines from C. I have several questions that I was hoping someone could shine some light on.
The cleanest example I have seen thus far is the gas_toggle example. I like the combination of the pure C calling into the pure ASM with as few spin or __asm__ type tricks as possible. While I can get that to compile and link I do not understand how the link between the
extern unsigned int* _load_start_coguser0();
declaration in the C file and the
.section .coguser0, "ax"
directive in the S file happens. It seems as if the linker knows magically to add the _load_start_ prefix to the .coguser0 section name.
Is that documented somewhere???
The second and third questions are related: how do you call a general function (i.e. not a cog entry point) and why is that method not used to in the above example?
Let me illustrate what I am asking:
--foo.c
// prototypes for the ASM routines.
extern void cog_function();
extern void someFastFunction();
static void localFunction()
{
someFastFunction();
}
int main()
{
cognew(cog_function,0);
localFunction();
}
--cog_function.s
.cog-mem
_cog_function
'some code
.loop
'more code
jmp #.loop
--zippy_funcs.s
someOtherFastFunction
'some code
ret
_someFastFunction
'some code
call someOtherFastFunction
'some more code
ret
_someOtherOtherFastFunction
'some code
ret
This is pretty much what I expected (possibly with different function prefixes or some attributes) when I first saw the "call ASM from C" bullet in the feature list of gcc. This expectation follows more or less how things work on Windows. I assumed that if I wanted to call an ASM function with parameters I would need to manually manipulate the C stack in the ASM, etc.
I have tried this and not gotten it to link. And I am not seeing any examples to this effect. Is this just not possible (i.e. sections and the extern unsigned int* trick are the only option), or have I just not found the right docs yet?
Any help would be most appreciated.
Best regards,
Tom
I am investigating the propeller chip and am looking at the gcc compiler. One thing I am trying to come to grips with is the ins and outs of calling assembly routines from C. I have several questions that I was hoping someone could shine some light on.
The cleanest example I have seen thus far is the gas_toggle example. I like the combination of the pure C calling into the pure ASM with as few spin or __asm__ type tricks as possible. While I can get that to compile and link I do not understand how the link between the
extern unsigned int* _load_start_coguser0();
declaration in the C file and the
.section .coguser0, "ax"
directive in the S file happens. It seems as if the linker knows magically to add the _load_start_ prefix to the .coguser0 section name.
Is that documented somewhere???
The second and third questions are related: how do you call a general function (i.e. not a cog entry point) and why is that method not used to in the above example?
Let me illustrate what I am asking:
--foo.c
// prototypes for the ASM routines.
extern void cog_function();
extern void someFastFunction();
static void localFunction()
{
someFastFunction();
}
int main()
{
cognew(cog_function,0);
localFunction();
}
--cog_function.s
.cog-mem
_cog_function
'some code
.loop
'more code
jmp #.loop
--zippy_funcs.s
someOtherFastFunction
'some code
ret
_someFastFunction
'some code
call someOtherFastFunction
'some more code
ret
_someOtherOtherFastFunction
'some code
ret
This is pretty much what I expected (possibly with different function prefixes or some attributes) when I first saw the "call ASM from C" bullet in the feature list of gcc. This expectation follows more or less how things work on Windows. I assumed that if I wanted to call an ASM function with parameters I would need to manually manipulate the C stack in the ASM, etc.
I have tried this and not gotten it to link. And I am not seeing any examples to this effect. Is this just not possible (i.e. sections and the extern unsigned int* trick are the only option), or have I just not found the right docs yet?
Any help would be most appreciated.
Best regards,
Tom
Comments
Might this tutorial be of help?
https://sites.google.com/site/propellergcc/documentation/tutorials/inline-asm-basics
Thanks for the welcome and the link. I had indeed seen that tutorial. I am not wild about that kind of inline asm. It is ok for super small accesses to specific assembly instructions, basically making intrinsics, but in my opinion it is an ugly way to code any significant assembly routine. If you have any pretty serious routine, one where you want to have comment blocks etc, the "" business gets kind of messy (again, in my opinion) and for those I feel like the GAS approach is cleaner.
As things stand right now in our little project, there are in fact some pretty big asm routines. It is possible that by switching from spin to C, I can pull some of that code into C. But some pretty large sections of it need to stay in asm. I am trying to do this C evaluation in parallel with my engineers and thus I want to be able to use the ASM blocks as they are by just sticking them into a .S file.
Best regards,
Tom
That's an interesting case: you want COG C code and assembly code, both in separate files, to be compiled and linked into a COG image?
First off, I believe that the only compiler/linker created labels are _load_start* type labels. I don't think you'll get the someFastFunction in C and be able to call it like a function. You'll need to write that function in C then have some inline assembly to call the assembly portion. If you do that then the linker should be able to pull in the contents of the cog_function.S file (make sure to specify it explicitly to GCC).
This should work fine for CMM/LMM type code. The trouble that you might run into is with COG code, since the memory spaces are different. I don't know how to resolve this without playing around some.
The ".section .coguser0" of encoding a cog-mode module is a little obsolete, I'll get back to that in a minute. Basically if you put your code in a section .coguserX where (X is 0 to 7, I believe), it will get linked with other code in other modules that are in the same section, and it will get grouped together in the binary file behind a short fragment of code that initializes the pseudo-registers for C. You can run this code in a new cog, by calling __cognew with a pointer to the C initialization code which is located at _load_start_cogX (where X is 0 to 7 again).
A better way to put all the code that needs to run in one cog together is to save it with a .cogc extension and add it to your SimpleIDE project. The SimpleIDE environment compiles this (more or less as a separate program, it has to have a main() function and everything), and then makes a change to the object file to rename the code segment automatically. After this, it's possible to add the resulting object file to a project that has many other source files (and other .cogc files -- the main( ) symbol won't clash because it's in a different segment after the renaming). The start of each segment generated from a .cogc file is named _load_start_XXXXXXXX_cog where XXXXXXXX is the name of the file without the .cogc extension, with all non-alphanumeric characters translated to underscores so my.smart_functions.cogc has a symbol _load_start_my_smart_functions_cog. This symbol refers to the C startup code that's at the beginning of the cog code and can/must be referred to as an external array of longs, and passed to cognew as startup address.
Unfortunately I think if you use a ".cogc" to put your cog mode code, everything for that cog has to be in that file so if you want to mix C with Assembly you'll have to use inline assembler, you can't use a .S file. If you want to stick with having your code in a separate .S file, you'll have to manually build your code: compile the .cogc file with the -c flag of GCC, compile the .S file with gas, then link the two .o files together to an executable file and rename the .text segment with propeller-elf-objcopy the same way as SimpleIDE does.
If you simply want to call Assembly code from C code and run it in the same cog, you can just mix them in the same way as in other architectures: you create a C module where you declare a function without defining it, and you create a .s file where you declare a public symbol that has the name of your function, prefixed with '_' (this is standard for the C language) and the compiler and linker will do the rest. This is not really different between the Cog memory model and the other memory models, but you'll have to keep the Propeller limitations in mind: there's only enough space in a cog for 2K of code and data, so in Cog mode you run out of space very fast if you want to do a lot of things in the same cog. If you compile in a different memory model (not -mcog) your assembly code gets executed by a kernel that basically loads each instruction from the hub and then executes it. In those non-cog modes you can't do things such as JMP or CALL or CALLRET because the location you're jumping to is not in the cog, it's in the hub. There are special pseudo-instructions to change the LMM/CMM/XMM instruction pointer. Other tricks like self-modifying code are also impossible or unnecessary because they've been replaced with pseudo-instructions.
Another problem is that you have to figure out how the C function passes arguments to the Assembly function, which is not really documented, I think. If you want to know exactly how it works, you'll probably have to analyze the Assembly code that's generated by the C compiler. A simple assembly function that doesn't need to return any value and doesn't need any parameters like yours, can simply be declared in the C code as void SomeFunction(void) and in the .S file as _SomeFunction (which you have to make public with ".public _SomeFunction"). If you do want parameters, it's probably easiest to write a C function that only contains an __asm__ statement, so the C compiler takes care of passing the parameters to the function (and making sure the caller's parameters match the types that the function is expecting; very important!), and it also takes care of setting up the result when you return back to the caller. The __asm__statement takes care of translating the parameter names in the C code into whatever you want for your assembly code. The bulk of your function can still be in a .S file, simply use JMPRET or CALL from inside the __asm__ statement.
I think the intention of the sample was to illustrate how to start a cog that does a separate task at the same time as the other cogs. As mentioned above, it's easy to call a function from a .S file from C code. Make sure the memory model matches. If you want to program the Assembly code in cog mode, make sure the .S file uses ".compress 0" otherwise all the instruction parameters that represent addresses are interpreted as hub addresses instead of cog addresses. This is due to an unfortunate design decision early in the development of the GCC compiler for the Propeller.
If you get linker errors that indicate that addresses are out of reach, you're probably running into the problem that cogs can only hold 2K and all the code that gets loaded into a cog has to be in a 2K hub memory area. The Gnu linker was not originally designed for the Propeller of course, and it doesn't know how to rearrange functions and data to group them all together in the right way, it just gives up in some cases. If you post a zip file with your code to the forum, maybe we can help you get it to build.
As I said, there's much more information in my article. See the link above.
===Jac
Thank you for the pointers. There is quite a bit to chew on there. I will have to read your article Jac, and try a few things to see what I understand and what remains in the dark. I see a few blunders I made already (e.g. .public).
Thanks again and all the best,
Tom
You're on the right track. As Jac mentioned, you have to prefix assembly functions with "_" (so if the C function calls "someFunction" the assembly has to have the label "_someFunction") and you'll have to use a .extern directive to make that label visible globally in the linker.
The details of how to do function call and return depend on the memory model being used. The simplest is the COG memory model, but even there the "call" and "ret" instructions are used only for functions declared with the "native" attribute. This is because the normal PASM call/return is not re-entrant, so would not support recursive functions (which C must support). Instead we do something like: In LMM there's an artificial program counter "pc" used by the LMM interpreter, and there are assembly macros "lcall" and "lret" that do the right thing: The calling convention is pretty similar to the ARM and other RISC processors; the first N arguments (6, I think? I can't remember off-hand, it's more than most functions use) are passed in registers r0-rN, and return values come back in r0 (or the pair r0/r1 for 64 bit return values). If more arguments are needed, or the function takes a variable number of arguments, then they are passed on the stack.The return address goes in the "link register" lr (r15). There's a seperate stack pointer register sp, and non-leaf functions typically save registers that they use, including lr, onto the stack. The C compiler expects r0-r7 to be trashed by function calls, so these need not be preserved.
Eric
Thanks for the info. Indeed my main problem getting past the link stage was the .extern. Jac misremembered .public, but he got me on the right path and I by chance discovered .global (which did not sound right at the time but worked). .extern also works, oddly enough considering that the as.pdf (which I got from the propgcc download directory) says it is ignored. It actually sounds more right to me, but given that .global is prefered in the as.pdf, I am not sure what to think.
At any rate, I can compile and link to empty asm functions from my c function.
I have now started on the even more mundane task of actually toggling some bits from C and I am running into what is no doubt beginner ignorance. The board we have is a board of our own design. The propeller is fed an 80MHz clock from a CPLD that handles some bus address decoding and some other stuff.
The spin code we use to set up the clock is
CON
_clkmode = XINPUT
_clkfreq = 80_000_000
I am trying the following program in the SimpleIDE compiled in the COG memory model:
#include <propeller.h>
#define CLK 0x22 // XINPUT
int main()
{
clkset(CLK, 80000000);
DIRA=0x00FF0000;
while(1)
{
OUTA ^= 0x00FF0000;
}
return 0;
}
I expect to see p16-p23 toggling madly.
I see nothing. I expect that I am not setting up CLK correctly, or otherwise screwing up the clock settings. There _is_ a clock going into XIN. I have the SimpleIDE board selection as GENERIC, thinking that tells the loader "don't assume you know anything" so it will not set the clocks to something else in the startup code.
I tried also CLK as 0x63 (XINPUT+PLL1) and that seemed to have no change.
Any thoughts on my flail here? I cannot find any examples of using clkset in either my SimpleIDE or GCC installation.
Best regards,
Tom
You don't need to use clkset.
Look for Board Types in the SimpleIDE User Guide.
There is also a Loader document here: https://sites.google.com/site/propellergcc/documentation/propeller-loader
I had a feeling that something like that was happening. Looking at the board types, I don't see one that uses XINPUT with a 80MHz clock. Perhaps I am dense, it is late. If there is no existing board type that fits, I would think I need to make my own cfg file.
If so, is there a way to specify that cfg file (or an include path to it) from within SimpleIDE? I don't see a way in the manual.
I would want to check the cfg file into my source control (hg). All the ways I see for the loader to find cfg files use either the output directory (which I was planning on .hgignoring) or places outside the source tree. So ideally I would want to put the cfg file in with my .c files and point to it from within SimpleIDE.
I guess I could use the loader from the command line, but I am lazy and like F10 in SimpleIDE. Any thoughts there?
Thanks for your help and
all the best,
Tom
You will need to specify your own .cfg file since none of the existing ones specify XIN (xinput).
Add your file for example to c:\Program Files (x86)\SimpleIDE\propeller-gcc\propeller-load\xin.cfg
The boards.txt file is a filter ... it will only let listed boards be displayed in the Board Type drop-down box.
Thanks for the tips about the cfg file.
I did not realize that the 80MHz/XINPUT mode was odd. It works fine from what we can tell. It is actually not a big deal to change it - it is generated from a field programmable CPLD. I am fairly sure that the actual XTAL we are using is higher and is divided down to the 80 since we are also using it to generate a clock for a SAR ADC and for the CPLD itself. So I am farily sure I could make it be 5 or 10Mhz reasonably easily (unless of course we are out of room for the expanded divider).
I guess therefore that another possibility is to just change the input clock to something that is supported out of the box although I will try the cfg file first before bothering my VHDL guy.
One last question. What is the purpose of the clkset routine if it does not work as I attempted? It is actually one of the few functions in propeller.h that is not an intrinsic. Someone seemed to think it was important.
Best regards,
Tom
The config file sorted it. I now have a 5Mhz square wave on my scope. Yay! I decided to just put the cfg into the source directory and have the devs copy it manually into their \propeller-load directory as needed.
It would be slightly more robust if one could introduce command line options for the loader via SimpleIDE, specifically -b (or I guess -I), so that the devs would not need to remember to upgrade the cfg file in the event it changed (say we changed the input clock and needed to use the PLL). But that is a small optimization to the tool chain.
Thanks to you and everyone else for the help.
Best regards,
Tom
Glad it works!
I only mentioned "odd" because all the Parallax boards these days use a 5MHz crystal AFAIK. 80MHz from your CPLD is probably better/cheaper than adding a separate 5MHz crystal to your design. Changing your design at this point is likely wasteful.
It works, but there are some restrictions about how it would work. There is a clock restart procedure alluded to in the datasheet - various delays, PLLENA/OSCENA enable/disable, etc.... In your case it may be necessary to start up using rcfast.cfg and then switch to XINPUT - I'm not sure.
Seems like that would do it also. Having these settings be in a config file seems a little cleaner if someone has to change a number of parameters, but that is just my opinion.
Just something so that I (or someone) can set it up once in the IDE and have wholesale changes when you check out new versions from the source control. Rather than checking out from version control, realizing that the config file has changed (the hard part) and then copying it to some other directory on the computer. Just one less thing to have to teach the new guy, or to remember my own dumb self 6 months from now.
All the best,
Tom
I will poke at that clock change thing a little. I hate not knowing how these sorts of things work. I remember that section of the data sheet you are referring to.
At any rate, I am now making pretty good progress. Got my toggle code into a second cog via a .cogc file. Seemed easy enough.
I actually also managed to launch a second cog via cognew() pointing to a local function in my main C file (rather more like SPIN than _load_start_xxx). It worked but interestingly generated slightly different code that resulted in the toggle rate dropping by 1/2. I gather that is not a supported way of doing things.
I am going to poke around at sharing variables between cogs, which I think will be the last main structural thing to understand.
Tom
Ya, the problem with using cogstart on a function rather than cognew with COGC, GAS, or PASM is that cogstart runs the function in CMM/LMM mode. CMM/LMM mode are "interpreted" by the new COG run with cogstart and will always be slower than cognew code (compiled to the metal).
When sharing variables make sure that they are global and declared volatile. Not using volatile can easily byte people because of optimization.
BTW, If it helps, I've verified that users can overload any .cfg file from the propeller-load folder by putting a copy in the SimpleIDE project directory.
Experiment:
1. Choose Board Type Quickstart (quickstart.cfg).
2. Run the Welcome.side demo, and verify "Hello!" output on the terminal.
3. Copy quickstart.cfg to the "SimpleIDE/My Projects" folder.
4. Change the project folder's quickstart.cfg clkmode from XTAL1+PLL16X to XTAL1+PLL8X.
5. Run the Welcome.side demo, and verify something other than "Hello!" output on the terminal.
6. Change the quickstart.cfg clockmode back to normal.
Also using this overload methodology, one can also store settings like device pin numbers or other board specific values in the .cfg file if necessary.
Ha! Indeed you can override any existing cfg file and, it turns out, a non-existing file. If you define a board type in boards.txt, say my_fancy_board.cfg, and select it in SimpleIDE, you can just put my_fancy_board.cfg next to your SIDE file and it loads using that.
Problem solved, multiple ways.
Tom
Parallax boards only.
It is not a propgcc feature. It is in the propside repository spinside branch at the top level.
Some follow ups:
1) Small caveat on my previous "override a non-existant file". Indeed the file must exist. When I exited SimpleIDE and ran it again, it failed. An empty file is fine, but you need something.
2) I did try this again (with success):
#define CLK 0x22 // XINPUT
int main()
{
clkset(CLK, 80000000);
...
}
It does work if you select one of the internal oscillator board types for the loader (e.g. RCFAST). This of course makes total sense in retrospect. I had absent-mindedly chosen one of the XTAL+PLL boards in my earlier attempts and since there was no crystal, the prop was not getting a clock and was never able to get to my clkset call. Duh.
Tom
Ohhh, this is something that I had not thought about. If you modify your local boards.txt and so forth to load custom .cfg files, your changes will get wiped out when you upgrade SimpleIDE. I suppose this makes it better to override a sanctioned Parallax board as Jazzed suggested originally, or to have a -b option as you suggested earlier.
Tom
If you remove it, you will get a huge list of board types based on the contents of the .cfg files in the propeller-load folder with up to 6 or so different variations of the same board in the dropdown menu. Even with boards.txt limiting the types, the list is horrid.
Glad you got clkset to work as expected. Congrats.
I'm not really sure David. I'll be on-line later to chat about some ideas.
I am not sure if you talked about this already, but from my perspective, I think you probably don't need to make major changes to the board config scheme. I would guess that the bulk of the users are using Parallax boards, so keeping the existing propeller-load/*.cfg & board.txt files and the existing drop-down list in SimpleIDE makes sense.
I think all you need is a clean way to do a custom board file for the oddball cases. That can probably be handled simply by allowing -b and -I to be passed to propload.
Maybe something like selecting "Custom" from the board list in SimpleIDE. Then perhaps have a "..." button leading to a dialog box to let you enter the board name and (optionally) a search path. Alternately, have a 4th tab (e.g. "Board Options" ) in that configuration area where you could move the existing "Board Type" drop-down list from the "Project Options" tab and then add the edit fields for the board name and search path (enabled only when they select "Custom").
Whatever way, the user would select "Custom" then enter a board name of thier choice, for example acme_board9. Then they would be able put their custom "acme_board9.cfg" file where they want. Given that the SIDE directory is searched already, I expect that even just the board name would be fine for 99% of uses.
All the best,
Tom
If Parallax doesn't object, which is a serious concern because of resource time, documentation, simplicity, competing priorities, etc ... I could add a Loader tab to the Project manager which could specify different over-loads for the program to use. The list of common attributes is quite small.
There is a workable solution now, so it may be a long time before such an enhancement shows up.