I'm glad that's straightened out, but now the example hardly seems to be about indirect JMP at all. Perhaps it would be more compelling if it used an explicit state variable?
Init MOV State,#State1
' NOP <- no longer needed, I guess
JMP State
State1 do state1 stuff
..
Common common post-state code
..
JMP State
State RES 1
Yeah, I thought about that, too, as I was posting the error correction. But I was in such a hurry just to fix it that I didn't follow through with the tighter, more illustrative code. That has now been remedied.
One more thing while I have your attention: The last trick, on sign-extension, says
In Spin, do this:
Value := Value << 24 ~> 24
Wouldn't it be more idiomatic to do this instead:
~Value
I know the trick is about sign-extending from any bit, but using a byte as an example made me half expect that the trick would be the ~ operator. (Of course, for C/C++ guys, this meaning of ~ may be more of a trap than a trick.)
Perhaps the text could mention the two sign-extension operators ~ and ~~, then describe the left/right shifty trick for arbitrary sign bit positions, with an example that uses an oddball shift count rather than 24 or 16. It could also mention that execution time is independent of shift count, assuming that's the case, which I believe it is.
'Another good suggestion, which I've now incorporated.
You're right about the execution time of the shifts. The Propeller uses a barrel shifter, rather than an incremental one. However, since this is neither a trick nor a trap, there wasn't a good place to incorporate such info in the document.
A trick suggestion: I thought the use of jmpret as a task dispatcher in tv_drv was pretty darn clever. Some coroutine action going on there.
A trap suggestion: Square brackets mean array indexing -- except when they don't. After INA, DIRA, etc., they are pin specifiers. I just find that sort of special-case hijinx annoying (probably just me).
mpark said...
....·Square brackets mean array indexing -- except when they don't. ...
A nice list would summarize all the various parentheticals (and where they are used) be they (), [noparse]/noparse or what have you. The compiler sure does (expletive) obscurely when you·get them wrong...
The COGNEW/COGINIT Spin statements allow an arbitrary Spin method call as the first parameter including a call of a method in another object (like foobah.myMethod). This is accepted by the compiler without comment, but will not be executed properly. The method must be one in the same object as the COGNEW/COGINIT call.
values in DAT section byte etc. are not range checked (1.05.5):
DAT
byte $555
will silently fail, and it's equivalent to byte $55.
EDIT: I couldn't replicate the following trap when I tried with simple code...
x := 1 + y
will first write 1 into x, then add y to that.
So if you have an assembly driver polling for that location, it will see it when it's 1. eg. I had hard time debugging the following code:
s_ras_cmd := const#CMD_SAVE_MAP + p_tilemap_index
The following works:
x := const#CMD_SAVE_MAP + p_tilemap_index
s_ras_cmd := x
Your code has a bug, rdlong RA, opflag WZ reads address 0 in hub ram. you should pass a pointer of opflag in PAR etc. EDIT: A post got deleted between my messages.
I wrote some test code, but now I can't even replicate the bug myself... but the temporary variable really solved the problem
Sorry, Jasper, I was unsure for a second.. No, my program is fine But I cannot produce your bug
{
Exploring JASPER's Bug v04
2007 deSilva
}
CON
_clkmode = xtal1 + pll8x
_xinfreq = 10_000_000
OBJ
pc : "PC_Interface" ' Ariba's PropTerminal
PUB main
pc.start(31,30)
alarm := @alarm
opflag := @opflag
cognew(@asm,0)
repeat
pc.out(0)
pc.str(string("checking JASPER's trap ...press any key"))
if alarm
pc.str(string(13, "There's a fly in the ointment"))
waitcnt(clkfreq+cnt)
alarm := 0
if pc.gotKey ' press a key to show the bug (?)
pc.clearKeys
' according to the report, first 0 should be written to opflag
opflag := 0 + takeYourTime
waitcnt(clkfreq/20+cnt)
pri takeYourTime
waitcnt(clkfreq/1000+cnt)
return 1 ' change this to 0 and the programm will trigger...
DAT
org 0
asm
' Wait for opflag ==0
rdlong RA, opflag WZ
if_Z wrlong opflag, alarm 'write any value as long as its not 0
jmp #asm
RA long 0
alarm long 0
opflag long 0
A trick for saving stack space is to use the 'result' variable associated with each method, whether it returns a result or not. A benefit is that unlike other local variables it is zeroed on entry to the method.
If using a variable named 'result' isn't appealing, it can be renamed as the return variable ...
I've corrected an error on page 8, where it stated that bits 31..2 are passed to the PAR register by COGNEW. Actually it's bits 15..2. My thanks to Rayman and BradC for pointing this out!
At some point I need to collect and filter the latest batch of user-contributed material for inclusion. I haven't forgotten, and thanks for all the input!
-Phil
Post Edited (Phil Pilgrim (PhiPi)) : 9/28/2007 7:29:43 PM GMT
I was talking with Chip about the Turboprop and ran into something new.(I think)
My discussion was in “What would you want more of, cogs or RAM?” starting on page 25.
I'm still looking to see if all 512 COG RAM location are indeed filled with HUB data during initialization.
This would be the easiest way to get code/data into the 'Shadow Registers'.
LewisD
Chip said...
Cog RAM is read directly for instructions, not special 'source' registers such as PAR, CNT, etc. So, you could write a JMP instruction into the PAR register ($1F0) and have it execute, not having to consider what PAR would return as a 'source'. In other words, the large-model would work fine by reading eight contiguous hub longs into $1E8-$1EF and then JMPing to $1E8, and then having a JMP back execute from RAM (not PAR) at $1F0.
Chip how many of the registers are like that?(all?)
Yes, all. Some registers like PAR, CNT, INA, etc. have special mux's for reading live states when accessed as source registers. All instructions and destination registers are read from RAM, though - not mux's.
A SPIN REPEAT trap, well known, but sometimes forgotten...
st := 0
REPEAT i FROM st TO 1
' executed twice (i = 0,1)
st := 2
REPEAT i FROM st TO 1
'also executed twice (i = 2,1)
SPIN evaluates both limits in the beginning and decides whether to run the loop forward or backward. This is a great feature, but extremely surprising for experienced programmers.
Excellent suggestion Beau! I think I will use this method in the future. Not only does it eliminate some variables, but it also saves the execution time where other routines do the calculation each time to determine the correct delay length.
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
Timothy D. Swieter tdswieter.com
One little spark of imagination is all it takes for an idea to explode
It would be nicer if the Propeller Tool could (1) create a _CLKFREQ or _XINFREQ based on the other (2) allow both _CLKFREQ and _XINFREQ and produce an error only if the two do not match up using the _CLKMODE setting.
Trap AND Trick: Not sure how well this was known before:
The compiler remembers the size of a label in the DAT section according to the size identifier after this label.
Example:
DAT
lo LONG 10,20,30
by BYTE "ABC",0
However a "standalone label" is always considered as a LONG:
Example:
by
BYTE "ABC",0
is no longer correctly addressable by by[noparse][[/noparse]...]
There is a work around as follows
by BYTE
BYTE "ABC",0
A size identifier without preset does not allocate space but assigns the correct attribute to the label only!
It most likely also aligns for WORD and LONG, but I have not checked it.
Post Edited (deSilva) : 10/27/2007 10:37:32 AM GMT
I shall investigate a little bit deeper.. Alas, I do no longer read the Hydra threads..
But I already can assure you it is not BYTE a standalone label defaults to...
I wasn't disagreeing with you, just adding an additional quirk, but on digging a little deeper it appears that the rules for standalone labels are:
a. The last size identifier preceding a standalone label determines its "type".
b. If there is no preceding size identifier, a standalone label defaults to BYTE.
E.g.:
DAT
long0 ' defaults to BYTE
long $1234, $abcd
PUB Start | ch
tv.start( 24 )
tv.hex( long0, 8 ) ' 00000034
tv.out( 13 )
tv.hex( long0[noparse][[/noparse] 1], 8 ) ' 00000012
tv.out( 13 )
tv.hex( word0, 8 ) ' 20012000
tv.out( 13 )
tv.hex( word0[noparse][[/noparse] 1], 8 ) ' 13121110
tv.out( 13 )
tv.hex( byte0, 8 ) ' 00001110
tv.out( 13 )
tv.hex( byte0[noparse][[/noparse] 1], 8 ) ' 00001312
tv.out( 13 )
tv.hex( long1, 8 ) ' 00000078
tv.out( 13 )
tv.hex( long1[noparse][[/noparse] 1], 8 ) ' 00000056
tv.out( 13 )
DAT
word0 ' LONG because of LONG in previous DAT section
word $2000, $2001
byte0 ' WORD
byte $10, $11, $12, $13
long1 ' BYTE
long $12345678
Here is a cool trick using the muxc command to self modify the instruction bits of another command, thus changing the command entirely....
The example uses this technique to Bit-Bang data to an I/O port with 50nS output transitions (20MHz).
Enjoy!
[b]CON[/b]
[b]_clkmode[/b] = [b]xtal[/b]1 + [b]pll[/b]16x
[b]_xinfreq[/b] = 5_000_000
OutputPin = 0
DataOut = %1000_1111_0000_1010_1010_1001_1001_1011 '<- Data to send
[b]PUB[/b] ASM_FAST_BitBang_DEMO
[b]cognew[/b](@FAST_BitBang,0)
[b]DAT[/b]
FAST_BitBang [b]org[/b] 0
[b]mov[/b] OutputPinMask, #1 '' Create Pin Mask for Output pin
[b]shl[/b] OutputPinMask, #OutputPin
[b]mov[/b] LoopCounts, #32 'Self Modifying Bit-Bang Code
Loop [b]ror[/b] Buff, #1 [b]wc[/b]
BitIndex [b]muxc[/b] Bit0, instr_mux
[b]add[/b] BitIndex, dest_inc
[b]djnz[/b] LoopCounts, #Loop
'This section of code self modifies the code below,
'so that each of the bits can transition in only 50nS.
'It does this by MUXing the "C" flag into the instruction
'location of the muxz commands below.
'By default, the way that the muxz is used, it will make
'output Pin LOW. When the instruction location is changed...
'...from 011110 to 011111 (<-Notice only one bit difference)
'...in this section of the code via the muxc instruction,
'muxz below effectively becomes a muxnz. Cool Huh!?
[b]or[/b] [b]dira[/b], OutputPinMask 'Make Pin an Output
[b]mov[/b] [b]outa[/b], #1 [b]nr[/b],[b]wz[/b] 'Clear Z flag for TRUE data
' [b]mov[/b] [b]outa[/b], #0 [b]nr[/b],[b]wz[/b] 'Set Z flag for INVERTED data
The following MUX commands use the "Z" flag set above
Bit0 [b]muxz[/b] [b]outa[/b], OutputPinMask 'Default LOW Self Modified Code to Bit Bang at 20MHz
Bit1 [b]muxz[/b] [b]outa[/b], OutputPinMask 'Default LOW
Bit2 [b]muxz[/b] [b]outa[/b], OutputPinMask 'Default LOW The lines above that read...
Bit3 [b]muxz[/b] [b]outa[/b], OutputPinMask 'Default LOW
Bit4 [b]muxz[/b] [b]outa[/b], OutputPinMask 'Default LOW muxc {Bit#}, instr_mux
Bit5 [b]muxz[/b] [b]outa[/b], OutputPinMask 'Default LOW
Bit6 [b]muxz[/b] [b]outa[/b], OutputPinMask 'Default LOW ...self modify the instruction bits
Bit7 [b]muxz[/b] [b]outa[/b], OutputPinMask 'Default LOW of the lines that read...
Bit8 [b]muxz[/b] [b]outa[/b], OutputPinMask 'Default LOW
Bit9 [b]muxz[/b] [b]outa[/b], OutputPinMask 'Default LOW {Bit#} muxz outa, OutputPinMask
Bit10 [b]muxz[/b] [b]outa[/b], OutputPinMask 'Default LOW
Bit11 [b]muxz[/b] [b]outa[/b], OutputPinMask 'Default LOW ...so that when the BIT in Buff is
Bit12 [b]muxz[/b] [b]outa[/b], OutputPinMask 'Default LOW a "1", the lines that did read...
Bit13 [b]muxz[/b] [b]outa[/b], OutputPinMask 'Default LOW
Bit14 [b]muxz[/b] [b]outa[/b], OutputPinMask 'Default LOW {Bit#} muxz outa, OutputPinMask
Bit15 [b]muxz[/b] [b]outa[/b], OutputPinMask 'Default LOW
Bit16 [b]muxz[/b] [b]outa[/b], OutputPinMask 'Default LOW ...now function as if they read...
Bit17 [b]muxz[/b] [b]outa[/b], OutputPinMask 'Default LOW
Bit18 [b]muxz[/b] [b]outa[/b], OutputPinMask 'Default LOW {Bit#} muxnz outa, OutputPinMask
Bit19 [b]muxz[/b] [b]outa[/b], OutputPinMask 'Default LOW
Bit20 [b]muxz[/b] [b]outa[/b], OutputPinMask 'Default LOW ...with 'muxz' the output pin is made
Bit21 [b]muxz[/b] [b]outa[/b], OutputPinMask 'Default LOW LOW ... with 'muxnz' the output pin
Bit22 [b]muxz[/b] [b]outa[/b], OutputPinMask 'Default LOW is made HIGH. This is so that a pin
Bit23 [b]muxz[/b] [b]outa[/b], OutputPinMask 'Default LOW can transition in 4 Clocks or 50nS rather
Bit24 [b]muxz[/b] [b]outa[/b], OutputPinMask 'Default LOW than 8 Clocks or 100nS. This makes it twice
Bit25 [b]muxz[/b] [b]outa[/b], OutputPinMask 'Default LOW as fast.
Bit26 [b]muxz[/b] [b]outa[/b], OutputPinMask 'Default LOW
Bit27 [b]muxz[/b] [b]outa[/b], OutputPinMask 'Default LOW
Bit28 [b]muxz[/b] [b]outa[/b], OutputPinMask 'Default LOW
Bit29 [b]muxz[/b] [b]outa[/b], OutputPinMask 'Default LOW
Bit30 [b]muxz[/b] [b]outa[/b], OutputPinMask 'Default LOW
Bit31 [b]muxz[/b] [b]outa[/b], OutputPinMask 'Default LOW
instr_mux [b]long[/b] $0400_0000
dest_inc [b]long[/b] $0000_0200
Buff [b]long[/b] DataOut
OutputPinMask [b]long[/b] 0
LoopCounts [b]long[/b] 0
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ Beau Schwabe
IC Layout Engineer
Parallax, Inc.
Post Edited (Beau Schwabe (Parallax)) : 11/20/2007 6:26:56 PM GMT
Comments
Thanks!
Phil
Post Edited (mpark) : 6/20/2007 2:22:00 PM GMT
Thanks for the prodding!
-Phil
In Spin, do this:
Value := Value << 24 ~> 24
Wouldn't it be more idiomatic to do this instead:
~Value
I know the trick is about sign-extending from any bit, but using a byte as an example made me half expect that the trick would be the ~ operator. (Of course, for C/C++ guys, this meaning of ~ may be more of a trap than a trick.)
Perhaps the text could mention the two sign-extension operators ~ and ~~, then describe the left/right shifty trick for arbitrary sign bit positions, with an example that uses an oddball shift count rather than 24 or 16. It could also mention that execution time is independent of shift count, assuming that's the case, which I believe it is.
You're right about the execution time of the shifts. The Propeller uses a barrel shifter, rather than an incremental one. However, since this is neither a trick nor a trap, there wasn't a good place to incorporate such info in the document.
Thanks,
Phil
A trap suggestion: Square brackets mean array indexing -- except when they don't. After INA, DIRA, etc., they are pin specifiers. I just find that sort of special-case hijinx annoying (probably just me).
values in DAT section byte etc. are not range checked (1.05.5):
will silently fail, and it's equivalent to byte $55.
EDIT: I couldn't replicate the following trap when I tried with simple code...
will first write 1 into x, then add y to that.
So if you have an assembly driver polling for that location, it will see it when it's 1. eg. I had hard time debugging the following code:
The following works:
Post Edited (Jasper_M) : 8/25/2007 5:51:06 PM GMT
I wrote some test code, but now I can't even replicate the bug myself... but the temporary variable really solved the problem
Post Edited (deSilva) : 8/25/2007 11:45:37 PM GMT
Post Edited (Jasper_M) : 8/25/2007 8:34:02 PM GMT
... and I finally found the "bug" in it: It has to be "ointment" not "oinment" - idiot that I am.
Post Edited (deSilva) : 8/25/2007 11:43:10 PM GMT
If using a variable named 'result' isn't appealing, it can be renamed as the return variable ...
At some point I need to collect and filter the latest batch of user-contributed material for inclusion. I haven't forgotten, and thanks for all the input!
-Phil
Post Edited (Phil Pilgrim (PhiPi)) : 9/28/2007 7:29:43 PM GMT
I was talking with Chip about the Turboprop and ran into something new.(I think)
My discussion was in “What would you want more of, cogs or RAM?” starting on page 25.
I'm still looking to see if all 512 COG RAM location are indeed filled with HUB data during initialization.
This would be the easiest way to get code/data into the 'Shadow Registers'.
LewisD
SPIN evaluates both limits in the beginning and decides whether to run the loop forward or backward. This is a great feature, but extremely surprising for experienced programmers.
Post Edited (deSilva) : 10/17/2007 5:39:23 PM GMT
Here is a trick that·allows you to·calculate·_CLKFREQ·within the CON block while·using _XINFREQ...
Likewise, here is how you can calculate·_XINFREQ within the CON block while using _CLKFREQ...
A situation where this might be useful would be to setup timing constants within the CON block without resorting to using any variables.
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
Beau Schwabe
IC Layout Engineer
Parallax, Inc.
Post Edited (Beau Schwabe (Parallax)) : 10/20/2007 8:32:11 PM GMT
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
Timothy D. Swieter
tdswieter.com
One little spark of imagination is all it takes for an idea to explode
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
The more I know, the more I know I don't know.· Is this what they call Wisdom?
The compiler remembers the size of a label in the DAT section according to the size identifier after this label.
Example:
However a "standalone label" is always considered as a LONG:
Example:
is no longer correctly addressable by by[noparse][[/noparse]...]
There is a work around as follows
A size identifier without preset does not allocate space but assigns the correct attribute to the label only!
It most likely also aligns for WORD and LONG, but I have not checked it.
Post Edited (deSilva) : 10/27/2007 10:37:32 AM GMT
But, strangely enough, the compiler complains if you say "standaloneLabel.long[noparse][[/noparse]...]"
(See also http://forums.parallax.com/showthread.php?p=682286.)
But I already can assure you it is not BYTE a standalone label defaults to...
a. The last size identifier preceding a standalone label determines its "type".
b. If there is no preceding size identifier, a standalone label defaults to BYTE.
E.g.:
Post Edited (mpark) : 10/27/2007 4:45:36 PM GMT
-Phil
The example uses this technique to Bit-Bang data to an I/O port with 50nS output transitions (20MHz).
Enjoy!
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
Beau Schwabe
IC Layout Engineer
Parallax, Inc.
Post Edited (Beau Schwabe (Parallax)) : 11/20/2007 6:26:56 PM GMT