automatically convert spin to PASM
ersmith
Posts: 6,232
After posting my thread about converting spin to C/C++, I noticed a "related thread" about converting spin to PASM. For very simple spin objects that is now possible using a combination of propgcc and spin2cpp. The output isn't very good PASM, of course, but as they say of talking dogs, it's not how well it does it but that it does it at all :-). For quick and dirty projects it may be a good starting point for hand optimization.
For example consider the led toggle program:
The result is, as I said earlier, hardly optimal PASM, but it is valid and works. More complicated sources than LED toggle will probably need some hand massaging to get them to work, though!
For example consider the led toggle program:
CON
_clkmode = xtal1+pll16x
_clkfreq = 80_000_000
pin = 15
VAR
long pause
PUB start
pause := (_clkfreq/2)
DIRA[pin] := 1 ' set as output
OUTA[pin] := 1 ' turn it on
repeat
waitcnt(CNT + pause)
!OUTA[pin]
That can be converted (via the command line) to C with:
spin2cpp --ccode --main led.spinand then the resulting led.c can be converted to PASM (i.e. back to spin!) with:
propeller-elf-gcc -mspin -Os -S -o new.spin led.cThe -mspin option says to produce PASM rather than GAS output, and add a spin wrapper; -Os option says to optimize for size; -S says to produce assembly output; and -o new.spin gives the output file name.
The result is, as I said earlier, hardly optimal PASM, but it is valid and works. More complicated sources than LED toggle will probably need some hand massaging to get them to work, though!
'' spin code automatically generated by gcc
CON
_clkmode = xtal1+pll16x
_clkfreq = 80_000_000
__clkfreq = 0 '' pointer to clock frequency
'' adjust STACKSIZE to how much stack your program needs
STACKSIZE = 256
VAR
long cog '' cog that was started up by start method
long stack[STACKSIZE]
'' add parameters here
long param
'' add any appropriate methods below
PUB start
stop
cog := cognew(@entry, @param) + 1
PUB stop
if cog
cogstop(cog~ - 1)
DAT
org
entry
r0 mov sp,PAR
r1 mov r0,sp
r2 jmp #_main
r3 long 0
r4 long 0
r5 long 0
r6 long 0
r7 long 0
r8 long 0
r9 long 0
r10 long 0
r11 long 0
r12 long 0
r13 long 0
r14 long 0
lr long 0
sp long 0
'.text
long
'global variable _ledSpin_Start
_ledSpin_Start
mov r6, DIRA
mov r7, L_LC2
or r6, r7
mov DIRA, r6
mov r6, OUTA
mov r5, L_LC0
or r6, r7
mov OUTA, r6
mov r4, r7
wrlong L_LC1, r5
L_L2
mov r7, CNT
rdlong r6, r5
add r7, r6
waitcnt r7,#0
mov r6, OUTA
xor r6, r4
mov OUTA, r6
jmp #L_L2
long
L_LC0
long _thisobj
long
L_LC1
long 40000000
long
L_LC2
long 32768
long
'global variable _main
_main
sub sp, #4
wrlong lr, sp
jmpret lr,#_ledSpin_Start
'.data
_thisobj
long 0[2]

Comments
Good question. I timed it and got 16 cycles/toggle in the new version, versus 976 cycles/toggle for the original. So the converted version is about 61x as fast.
(In my original version of this post I quoted an incorrect figure for the new version, because of a silly typo in my timing routine.)
Sounds like a very good idea. Can you include the led.c source, so all the steps are shown ?
I'm not sure I understand the question. All the steps are shown -- led.c is produced automatically from led.spin by spin2cpp. But for completeness, here is led.c:
#include <propeller.h> #include "led.h" #ifdef __GNUC__ #define INLINE__ static inline #else #define INLINE__ static #define waitcnt(n) _waitcnt(n) #define locknew() _locknew() #define lockret(i) _lockret(i) #define lockset(i) _lockset(i) #define lockclr(i) _lockclr(i) #define coginit(id, code, par) _coginit((unsigned)(par)>>2, (unsigned)(code)>>2, id) #define cognew(code, par) coginit(0x8, (code), (par)) #define cogstop(i) _cogstop(i) #endif INLINE__ int32_t PostFunc__(int32_t *x, int32_t y) { int32_t t = *x; *x = y; return t; } #define PostEffect__(X, Y) PostFunc__(&(X), (Y)) static ledSpin thisobj; int32_t ledSpin_Start(void) { int32_t result = 0; thisobj.Pause = (80000000 / 2); DIRA |= (1<<15); OUTA |= (1<<15); while (1) { waitcnt((CNT + thisobj.Pause)); OUTA ^= 0x8000; } return result; } int main() { return ledSpin_Start(); }and here is led.h:#ifndef ledSpin_Class_Defined__ #define ledSpin_Class_Defined__ #include <stdint.h> #define _clkmode (1032) #define _clkfreq (80000000) #define Pin (15) typedef struct ledSpin { int32_t Pause; char dummy__; } ledSpin; int32_t ledSpin_Start(void); #endifThanks, that's good, I know it is an automatic step, but it helps to show everyone each step, makes the flow clearer.
I think spin2cpp is yours ?
I would also suggest adding a comment header, to any generated files, roughly like
that makes project management easier, and anyone looking at just the .c knows how to re-create it.
Thanks, that is a good idea. I'll add that to a future version.
ersmith, thank you, this sure got my attention! Making Spin sixty-one times faster is remarkable. I will be curious to see performance on other programs and how they pan out.
L_LC0 long _thisobj ... _thisobj long 0[2]This may work in GAS but definitely doesn't for normal PASM (in your example L_LC0 contains $29, the cog register index for _thisobj).It's actually the wrlong instruction that uses L_LC0 that's wrong, but good point. The demo does work, but it scribbles on some memory it shouldn't. Ouch! Good catch.
Definitely this method should not be relied on to produce correct code. It's not really the way propeller-elf-gcc was intended to work (if instead of -mspin we used -mcog and compiled to an ELF file then the data would correctly have been placed in hub memory). But it's a neat trick, I thought :-).
It's threads like this (and many more) that make me wonder why PropBASIC is apparently ignored. All the chit-chat about PropForth and the various C/C++ offerings, etc. Unless I am mistaken, PropBASIC is the only high level language that produces native PASM code....no?
Mickster.
Yes, I believe that LMM is a directive if required.
Mickster