Can an ISR (presumably in lower cog RAM) call a Spin method somehow?
Or, is there some other way for an interrupt to trigger a Spin method?
Calling a Spin2 method from an interrupt is complicated. You'd need to have some dedicated stack space in VAR, to start. You'd be interrupting the Spin2 program midway through a SKIPF pattern. There would be some things that would need to be carefully saved and restored. I'm sure it's possible, though.
I just posted new version v34n at the top of this thread.
Documentation has been updated to cover all built-in methods.
Built-in methods now require '()', just like user methods: GETCT().
GETSECS() was added to determine seconds since reset/boot. It divides the 64-bit system counter by CLKFREQ to get a 32-bit 'seconds' value (this was JonnyMac's idea). 32 bits can hold 136 years' of seconds. It's something to think how the system counter, running at 297 MHz, goes through those same 32 bits in under 15 seconds.
I rewrote the STRSIZE and STRCOMP methods to be much smaller:
'
'
' STRSIZE(adr) - returns length
' STRCOMP(adra,adrb) - returns true/false
'
strsize_ loc pa,#strsize_buff
skip #%1
strcomp_ loc pa,#strcomp_buff
load_buff setq #7-1 'largest buff program is 7 longs
rdlong buff,pa
jmp #buff
strsize_buff rdfast #0,x
mov x,#0
.loop rfbyte y wz
if_nz ijnz x,#.loop
ret
strcomp_buff popa ptrb
rdfast #0,x
.loop rdbyte x,ptrb++
rfbyte y
cmp x,y wz
if_z tjnz x,#.loop
_ret_ muxz x,_FFFFFFFF
They now load code snippets into BUFF[0..6] and execute them from cog RAM, which is fast and compact.
Also, the xxxxFILL method was shrunk by handling the fill-value replication in the same way STRSIZE/STRCOMP work.
If you can only add one "get time" command, I think getms() (get milliseconds since reset) would be more useful for differential timing. Hopefully, you have space for a few more instructions so you can divide clkfreq by 1000. 32 bits of millisecond is 49 days -- I think that's going to be okay.
If you can only add one "get time" command, I think getms() (get milliseconds since reset) would be more useful for differential timing. Hopefully, you have space for a few more instructions so you can divide clkfreq by 1000. 32 bits of millisecond is 49 days -- I think that's going to be okay.
Thanks for adding that.
We can have both, actually.
Thinking about this, it could be tricky. If we use (CT64 / (CLKFREQ/1000) for milliseconds, it's going to have a potentially large error compared to (CT64 / CLKFREQ) if CLKFREQ's three least-significant digits are not 0. It would be lossy. However, we could just compute (CT64 / CLKFREQ) and then use the remainder to get a correlated milliseconds value. That's how it would have to work for GETSECS() to correlate to GETMS(). We would do (remainder FRAQ CLKFREQ) to get a 32-bit fraction and then scale that unsigned value by 1,000 to get milliseconds. We could also scale by 1,000,000 to get microseconds. This would keep all our timing straight across time units. In fact, we would have those sub-second readings for 2^64 system clock ticks. Maybe there'd be some value in a 'GETSUS() : seconds, microseconds' method, which would always return whole seconds and microseconds, for almost 2,000 years, as a continuous function, at 297MHz.
SKIP can be a quicker alternative to JMP for hub exec, however the more skipped instructions there are the greater the chance of a FIFO reload.
In this Spin2 interpreter code is it worth saving two longs at the cost of 12 extra cycles?
' a: GETSECS()
' b: MULDIV64(m1,m2,d)
'
getsecs_ pusha x 'a
getct z wc 'a
getct y 'a
rdlong x,#@clkfreq_hub 'a
skip #%011111 'a
muldiv64_ setq #2-1 '| b pop m1 and m2
rdlong y,--ptra '| b x=d, y=m1, z=lm2
qmul y,z '| b multiply m1 * m2
getqx y '| b
getqy z '| b
setq z 'a b divide by d
qdiv y,x 'a b
_ret_ getqx x 'a b return quotient
Alternative:
' GETSECS()
'
getsecs_ pusha x
getct z wc
getct y
rdlong x,#@clkfreq_hub
setq z 'divide by d
qdiv y,x
_ret_ getqx x 'return quotient
'
'
' MULDIV64(m1,m2,d)
'
muldiv64_ setq #2-1 'pop m1 and m2
rdlong y,--ptra 'x=d, y=m1, z=lm2
qmul y,z 'multiply m1 * m2
getqx y
getqy z
setq z 'divide by d
qdiv y,x
_ret_ getqx x 'return quotient
EDIT:
A long in another SKIP sequence can be saved.
I think I need that to be so for wflong use with wrapping...
Every object's VAR area starts out long-aligned, but variables get packed into memory in the order they are declared. If a single byte is expressed, you will no longer be long aligned. You can use the ALIGNL keyword to force long alignment before you declare your long array.
Every object's VAR area starts out long-aligned, but variables get packed into memory in the order they are declared.
This is a change from the P1, right? In the P1 aren't variables backed by size; longs, words, then bytes? I like this new feature as it allows one to create a structure that is easily saved and retrieved from a byte-oriented device like an EEPROM.
Code can be a bit quicker and shorter sometimes by deliberately not SKIPping over unwanted instructions.
Spin2 interpreter:
' a: In-line PASM
' b: REGEXEC(hubadr)
' c: REGLOAD(hubadr)
' d: CALL(anyadr)
'
inline setq #16-1 'a load local variables from hub into buff
rdlong buff,dbase 'a
bith pb,#31 'a set flag to restore local variable to hub
mov ptrb,pb 'a get bytecode ptr into ptrb
skip ##%11100100000111 'a x2 begin inline_pasm skip pattern
regexec_ skip ##%1111000000 '| b x2 begin REGEXEC skip pattern
regload_ mov ptrb,x '| b c get hubadr into ptrb
rdword w,ptrb++ 'a b c read start register
rdword y,ptrb++ 'a b c read length of pasm code, minus 1
setq y 'a b c read in code
altd w 'a b c
rdlong 0,ptrb++ 'a b c altd causes ptrb++ to inc by 4, not by (y+1)*4
_ret_ popa x '| | c REGLOAD done, pop stack
shl y,#2 'a | update bytecode ptr for inline_pasm
add y,ptrb 'a |
call_ mov w,x '| | d get CALL address
popa x '| b d pop stack
mov y,pb '| b d save bytecode ptr
mov z,ptra 'a b d save ptra
Alternative that saves one long and two cycles for patterns a and b, but adds four cycles for d:
inline setq #16-1 'a load local variables from hub into buff
rdlong buff,dbase 'a
bith pb,#31 'a set flag to restore local variable to hub
mov ptrb,pb 'a get bytecode ptr into ptrb
skip ##%1001110000011 'a x2 begin inline_pasm skip pattern
regexec_ skip #%11000000 '| b begin REGEXEC skip pattern
regload_ mov ptrb,x '| b c get hubadr into ptrb
rdword w,ptrb++ 'a b c read start register
rdword y,ptrb++ 'a b c read length of pasm code, minus 1
setq y 'a b c read in code
altd w 'a b c
rdlong 0,ptrb++ 'a b c altd causes ptrb++ to inc by 4, not by (y+1)*4
_ret_ popa x '| | c REGLOAD done, pop stack
call_ mov w,x '| | d get CALL address
popa x '| b d pop stack
shl y,#2 'a b d update bytecode ptr for inline_pasm
add y,ptrb 'a b d b: & d: incorrect y is overwritten next
mov y,pb '| b d save bytecode ptr
mov z,ptra 'a b d save ptra
10-bit immediate replaced by 8-bit for second SKIP.
Every object's VAR area starts out long-aligned, but variables get packed into memory in the order they are declared.
This is a change from the P1, right? In the P1 aren't variables backed by size; longs, words, then bytes? I like this new feature as it allows one to create a structure that is easily saved and retrieved from a byte-oriented device like an EEPROM.
Right. This is different than Spin, where all the variables were grouped by size. This way, they all stack in the order they are declared, with no regard for alignment, unless you put an alignment operator in.
TonyB_, awesome! Keep going. There's probably all kinds of stuff in there that can be improved. It's really good when another person looks it over.
I'll keep going until I get it right. There was an undeliberate error due to concentrating on a and b and forgetting about d. Corrected above, however I think this is a lot better (blank lines removed for brevity):
' a: In-line PASM
'
inline setq #16-1 'a load local variables from hub into buff
rdlong buff,dbase 'a
bith pb,#31 'a set flag to restore local variable to hub
mov ptrb,pb 'a get bytecode ptr into ptrb
rdword w,ptrb++ 'a read start register
rdword y,ptrb++ 'a read length of pasm code, minus 1
setq y 'a read in code
altd w 'a
rdlong 0,ptrb++ 'a altd causes ptrb++ to inc by 4, not by (y+1)*4
shl y,#2 'a update bytecode ptr for inline_pasm
add y,ptrb 'a
mov z,ptra 'a save ptra
call w 'a call pasm code (can use pa/pb/ptra/ptrb/stack, C/Z=0)
mov pb,y wc 'a restore bytecode ptr
if_c setq #16-1 'a if inline_pasm, restore local variables to hub
if_c wrlong buff,dbase 'a
_ret_ mov ptra,z 'a restore ptra
'
'
' b: REGEXEC(hubadr)
' c: REGLOAD(hubadr)
' d: CALL(anyadr)
'
regexec_ skip #%11000000 ' b begin REGEXEC skip pattern
regload_ mov ptrb,x ' b c get hubadr into ptrb
rdword w,ptrb++ ' b c read start register
rdword y,ptrb++ ' b c read length of pasm code, minus 1
setq y ' b c read in code
altd w ' b c
rdlong 0,ptrb++ ' b c altd causes ptrb++ to inc by 4, not by (y+1)*4
_ret_ popa x ' | c REGLOAD done, pop stack
call_ mov w,x ' | d get CALL address
popa x ' b d pop stack
mov y,pb ' b d save bytecode ptr
mov z,ptra ' b d save ptra
call w ' b d call pasm code (can use pa/pb/ptra/ptrb/stack, C/Z=0)
mov pb,y wc ' b d restore bytecode ptr
if_c setq #16-1 ' b d if inline_pasm, restore local variables to hub
if_c wrlong buff,dbase ' b d
_ret_ mov ptra,z ' b d restore ptra
TonyB_, awesome! Keep going. There's probably all kinds of stuff in there that can be improved. It's really good when another person looks it over.
I'll keep going until I get it right. There was an undeliberate error due to concentrating on a and b and forgetting about d. Corrected above, however I think this is a lot better (blank lines removed for brevity):
' a: In-line PASM
'
inline setq #16-1 'a load local variables from hub into buff
rdlong buff,dbase 'a
bith pb,#31 'a set flag to restore local variable to hub
mov ptrb,pb 'a get bytecode ptr into ptrb
rdword w,ptrb++ 'a read start register
rdword y,ptrb++ 'a read length of pasm code, minus 1
setq y 'a read in code
altd w 'a
rdlong 0,ptrb++ 'a altd causes ptrb++ to inc by 4, not by (y+1)*4
shl y,#2 'a update bytecode ptr for inline_pasm
add y,ptrb 'a
mov z,ptra 'a save ptra
call w 'a call pasm code (can use pa/pb/ptra/ptrb/stack, C/Z=0)
mov pb,y wc 'a restore bytecode ptr
if_c setq #16-1 'a if inline_pasm, restore local variables to hub
if_c wrlong buff,dbase 'a
_ret_ mov ptra,z 'a restore ptra
'
'
' b: REGEXEC(hubadr)
' c: REGLOAD(hubadr)
' d: CALL(anyadr)
'
regexec_ skip #%11000000 ' b begin REGEXEC skip pattern
regload_ mov ptrb,x ' b c get hubadr into ptrb
rdword w,ptrb++ ' b c read start register
rdword y,ptrb++ ' b c read length of pasm code, minus 1
setq y ' b c read in code
altd w ' b c
rdlong 0,ptrb++ ' b c altd causes ptrb++ to inc by 4, not by (y+1)*4
_ret_ popa x ' | c REGLOAD done, pop stack
call_ mov w,x ' | d get CALL address
popa x ' b d pop stack
mov y,pb ' b d save bytecode ptr
mov z,ptra ' b d save ptra
call w ' b d call pasm code (can use pa/pb/ptra/ptrb/stack, C/Z=0)
mov pb,y wc ' b d restore bytecode ptr
if_c setq #16-1 ' b d if inline_pasm, restore local variables to hub
if_c wrlong buff,dbase ' b d
_ret_ mov ptra,z ' b d restore ptra
Comments
Or, is there some other way for an interrupt to trigger a Spin method?
Calling a Spin2 method from an interrupt is complicated. You'd need to have some dedicated stack space in VAR, to start. You'd be interrupting the Spin2 program midway through a SKIPF pattern. There would be some things that would need to be carefully saved and restored. I'm sure it's possible, though.
So, I just modified my VGA demo program to have 4 instances of the VGA driver and I can use each of them separately:
Can you post more of your code that is giving you trouble?
In Spin2:
-= speed
The prior syntax was cleaner in that case, but there is a consistent pattern to the new syntax.
Look at the operators list in the documentation file I have going.
Documentation has been updated to cover all built-in methods.
Built-in methods now require '()', just like user methods: GETCT().
GETSECS() was added to determine seconds since reset/boot. It divides the 64-bit system counter by CLKFREQ to get a 32-bit 'seconds' value (this was JonnyMac's idea). 32 bits can hold 136 years' of seconds. It's something to think how the system counter, running at 297 MHz, goes through those same 32 bits in under 15 seconds.
I rewrote the STRSIZE and STRCOMP methods to be much smaller:
They now load code snippets into BUFF[0..6] and execute them from cog RAM, which is fast and compact.
Also, the xxxxFILL method was shrunk by handling the fill-value replication in the same way STRSIZE/STRCOMP work.
The interpreter code is now 76 bytes smaller.
Thanks for adding that.
We can have both, actually.
Thinking about this, it could be tricky. If we use (CT64 / (CLKFREQ/1000) for milliseconds, it's going to have a potentially large error compared to (CT64 / CLKFREQ) if CLKFREQ's three least-significant digits are not 0. It would be lossy. However, we could just compute (CT64 / CLKFREQ) and then use the remainder to get a correlated milliseconds value. That's how it would have to work for GETSECS() to correlate to GETMS(). We would do (remainder FRAQ CLKFREQ) to get a 32-bit fraction and then scale that unsigned value by 1,000 to get milliseconds. We could also scale by 1,000,000 to get microseconds. This would keep all our timing straight across time units. In fact, we would have those sub-second readings for 2^64 system clock ticks. Maybe there'd be some value in a 'GETSUS() : seconds, microseconds' method, which would always return whole seconds and microseconds, for almost 2,000 years, as a continuous function, at 297MHz.
A previous request for a new feature was met with the response that it would not fit.
It can get as big as the hub RAM, but I have kept it at 4KB or less. I don't want it to become a pig.
What was that feature? I can't remember.
It was using 64-bit GETCT for WAITMS/WAITUS. About half a dozen posts starting at
http://forums.parallax.com/discussion/comment/1491573/#Comment_1491573
In this Spin2 interpreter code is it worth saving two longs at the cost of 12 extra cycles?
Alternative:
EDIT:
A long in another SKIP sequence can be saved.
How you rewrote the code is how I had first written it.
I think I need that to be so for wflong use with wrapping...
Every object's VAR area starts out long-aligned, but variables get packed into memory in the order they are declared. If a single byte is expressed, you will no longer be long aligned. You can use the ALIGNL keyword to force long alignment before you declare your long array.
VAR ALIGNL LONG Buff[1000]
Spin2 interpreter:
Alternative that saves one long and two cycles for patterns a and b, but adds four cycles for d:
10-bit immediate replaced by 8-bit for second SKIP.
Right. This is different than Spin, where all the variables were grouped by size. This way, they all stack in the order they are declared, with no regard for alignment, unless you put an alignment operator in.
It appears to me that the issue is with assignment statements. Your sample code doesn't appear to have any with results from an object reference.
Is there any documentation regarding new spin2 keywords/methods such as the PINxxx methods?
Yes, there is a documnentation link within the first post in this thread.
A 10-bit immediate is the most annoying thing, ever.
Pick a starting pin, and then add how many above it. Not difficult, just new.
object.constant
instead of the current:
object#constant
The new way IS object.constant.
I'll keep going until I get it right. There was an undeliberate error due to concentrating on a and b and forgetting about d. Corrected above, however I think this is a lot better (blank lines removed for brevity):
+8 longs (34 now, 26 originally)
a: inline -18 cycles (-33%)
b: REGEXEC -6 cycles (-14%)
That's a good time savings, but expensive in terms of longs.