Is it true that when I want to play an audio 16[url=mailto:16bit@22.05khz]bit@22.05khz[/url]·signal, I only have (20000000/(16*22050))=56.56clock cycles thus I only may use about 14 instructions(most instructions cost 4 cycles)?
The thing that I want to make a synthesizer and·would love to use 16bit 44.1khz to playback my calculations.
But that would really limit me in assembly instructions.
Edit:
I seem to have made a mistake about the calc.
It would have been 20000000/22050=907cycles about 226cycles which would be enough I gess, and then shift the 16bits in the audio dac.
maybe even at 44.1khz...
But for now...
I want to use one cog for wave generation and then put it trought a filter algoritm.
I then have to see if I could sync the 2 cogs.
first cog will generate the first 16bit of the wave, the second will have to wait till the first cog calculated the waveform.
I thought of writing·a bit in the hub mem, "0" would be wait for waveform calc to finish which will be '1" if finished, second cog will make it '0' once it is read.
The first cog sees a '0' and know it has to calc·the next 16bit value of the wave form.
Is this a good approach or not?
Post Edited (darkxceed) : 10/8/2008 5:24:40 PM GMT
For t=1/f, 44.1kHz equates to just over 22675ns, so that's how many nanoseconds you have between each update of the output value. At 80MHz, cycle time is 12.5nS, instruction time 50nS, so 22675/50 = 453 instructions between updates.
PS : It's much better if you start a new topic for questions not related to an existing thread.
darkxceed, the cog operates at 20mips with 80Mhz ( 5Mhz clock * 16PLL ) which is 80Million / 4 clocks per instruction ( with some exceptions hub-ops etc.) = 20Mips [noparse]:)[/noparse]
INA is a register / memory location just like any other. You need to use INA as a source field. Typically, you'd have a bit mask in some location which corresponds to the pin(s) you want, then use a test instruction to set one of the conditional flags (zero or carry) or you'd copy INA to a location and shift and mask the bits you want there. For example:
test bit2mask,INAwc' At this point, the carry flag is set to the state of pin 2
bit2mask long |< 2mov temp,INAshr temp,#16' Shift bit 16 to the position of bit 0and temp,#$FF' Mask off least significant 8 bits
temp long0
yllawwally, making sure you haven't got the bits set in DIRA as output, ie leave them 0's
.
.
.
waitpne PIN2,PIN2 'waits for INA anded with PIN2 to not equal PIN2 ( assuming only one bit is set in PIN2 otherwise a low on any of the input pins in PIN2 would pass this check )
mov datain,INA ' get the data from the port.
shr datain,#16 ' I'm assuming you want the byte, so shift the bits right 16 times
and datain,#255
.
.
.
PIN2 long 1<<2
Hello , i would like to ask if someone changed the code in the library Synth.Spin (which is in Spin language) , to propeller assembly language .
Is it possible or it's very complicated ?
Thank you in advance
DATorg' ' SPI Engine - main loop'
loop rdlong t1,parwz'wait for commandif_zjmp #loop
movd :arg,#arg0 'get 5 arguments ; arg0 to arg4mov t2,t1 ' │mov t3,#5'───┘
:arg rdlong arg0,t2
add :arg,d0
add t2,#4djnz t3,#:arg
mov address,t1 'preserve address location for passing'variables back to Spin language.wrlong zero,par'zero command to signify command receivedror t1,#16+2'lookup command addressadd t1,#jumps
movs :table,t1
rol t1,#2shl t1,#3
:table mov t2,0shr t2,t1
and t2,#$FFjmp t2 'jump to command
jumps byte0'0byte SHIFTOUT_ '1byte SHIFTIN_ '2byte NotUsed_ '3
NotUsed_ jmp #loop
what is the "movd" command doing?
I read the command explanation out of the manual, and I understand movd copies the value in the source location to the destination field (bits [17..9]) of the destination location; but I don't understand what it's doing here. Can you fill the destination field of PASM instructions with a movd operation? and if that is what it's being used for here, why wouldn't you just use arg0, why would you use movd and #arg0?
Also, I understand when you use the # symbol in front of a value, you are indicating that rather than a memory location, you are using a 9 bit literal. But what does it mean when there is a # symbol in front of a memory address?
I've spent a good while trying to figure this out myself, and I don't think I'm going to.
So I really would appreciate an explanation. Thanks.
I read the command explanation out of the manual, and I understand movd copies the value in the source location to the destination field (bits [17..9]) of the destination location; but I don't understand what it's doing here. Can you fill the destination field of PASM instructions with a movd operation? and if that is what it's being used for here, why wouldn't you just use arg0, why would you use movd and #arg0?
In the example above the insn at :arg is modified from two different places and the whole parameter fetch sequence happens to be repeated (new/next command). Hope that's enough to get you down the right path here ...
Also, I understand when you use the # symbol in front of a value, you are indicating that rather than a memory location, you are using a 9 bit literal. But what does it mean when there is a # symbol in front of a memory address?
What are the values ending up in a and b? Then think about what the effect would be on an insn when used with movd in the context of parameter setup.
DATorg0
entry mov a, location ' $000, a = ?mov b, #location ' $001, b = ?waitpeq $, #0' $002
location long42' $003
a res1' $004
b res1' $005
I had a discussion with a friend that I think cleared some of this up for me.
The short answers to my short questions:
Q: Can you modify a later instruction using the "movd" command?
A: Yes. It makes sense, but the only applications for the "movd" command described in the manual are both configuring special registers (either the video or counter modules), and I was having a hard time testing the theory.
Q: What does it mean when you put a literal symbol in front of a label rather than a value?
A: If you put a # symbol in front of a a label for either an initialized or un-initialized variable, you are telling the propeller to use the memory location that label refers to, as a value; rather than the value contained there-in. In other words, if you have a label "Number" at memory location 4, and the value in "Number" is 18, then "#Number" equals 4, rather than 18.
If any of that is wrong please show no mercy.
So to test my understanding I wrote a small block of assembly code, but am not getting the results I expected.
CON_clkmode= xtal1+pll16x_xinfreq= 5_000_000PUBmaincognew(@start, 4)
DATORG0
start mov n1, #0wz'set Z to 1
:read rdlong n1, par'read par into n1add :read, #4'add the literal decimal value 4 into :read
n1Lights muxzdira, n1 'set bits in dira corresponding with the high bits in n1 to Zmuxzouta, n1 'set bits in outa corresponding with the high bits in n1 to Z
Ldelay mov time, cnt'time:=cntadd time, Ldelay 'add delay into timewaitcnt time, #0'wait for count to equal time
_Ldelay long500_000
n1 res1
time res1
I was hoping to see P3 light up (using P0-P31 as a binary display for the value in n1).
I'm using the Gear: Parallax Propeller Emulator (which can be found here) and it's built in dir display,
and this is what I'm getting:
(shows both dira and dirb with dira on top)
which I'm pretty sure reads P0, P1, P2, P3, P5, P6, P9, P10, P11, P13, P20 as high, and everything else as low.
I was hoping to see P3 light up (using P0-P31 as a binary display for the value in n1).
At least dira looks correct ($00102E6F). Why is that? You set par to 4. In the PASM section you do a rdlong from said address (loading a long from hub RAM address #4) which gives you - among other things - the clkmode ($6F) and the binary checksum ($2E). Where did you think you'd get the mask from which would light up P3 (%1000)?
Edit: Just realised that you mentioned dira/dirb rather than dira/outa (still too early here).
For non-special-register movd usage check [post=1108886]this example[/post].
In the propeller manual, it clearly says you can use nine bit literal values in place of a memory location.
Here's an example from the manual:
add X, #25'Add 25 to X add X, Y 'Add Y to X
X long50
Y long10
and the description:
"the result of the first ADD instruction is 75 (i.e.: X + 25 → 50 + 25 = 75) and that value, 75, is stored back in the X register. Similarly, the result of the second ADD instruction is 85
(i.e.: X + Y → 75 + 10 = 85) and so X is set to 85. "
So to answer Kuroneko, I thought I was adding 4+4=8, ie 000000_00000000_00000000_00001000
Really I'm just trying to understand what's going on in this block of code (from Beau Schwabe's SPI engine)
So to answer Kuroneko, I thought I was adding 4+4=8, ie 000000_00000000_00000000_00001000
I assume you mean this bit of code:
start mov n1, #0wz'set Z to 1
:read [COLOR="#FFA500"]rdlong[/COLOR] n1, par'read par into n1add :read, #4'add the literal decimal value 4 into :read
What happens is that - after setting the Z flag - you read from hub RAM location #4 (par == 4) which gives you $00102E6F in n1 (basically the wrong insn here). After that you increase the content of :read by 4 (not n1 or par). Location :read contains an insn (rdlong n1, par) which is encoded as $08BC13F0. Adding 4 to it results in $08BC13F4 (rdlong n1, outa). One way of arriving at 8 would have been:
start mov n1, #0wz'set Z to 1
:read mov n1, par'move par into n1add n1, #4'add the literal decimal value 4 into n1
Doing it like this is OK for a small number of parameters (or [thread=135075]if you want it fast[/thread]). To keep the code size down it's sometimes done in a loop.
read the command (par is the hub address of the command)
if said command is zero try again (until non-zero)
reset parameter fetch insn to rdlong arg0, t2
take copy of command (hub address in lower bits)
initialise loop counter (5 arguments)
read first argument into arg0
modify parameter fetch insn to point to next cog address (arg1)
advance hub address
read next argument (insn at :arg has now been modified/updated)
A note re: point 7, what happens here is that the destination field (insn[17..9]) is incremented by one, IOW starting with rdlong arg0, t2 you'll effectively end up with rdlong arg0+1, t2 which is the same as rdlong arg1, t2 in our case. After 5 loop cycles said insn has been incremented by 5*d0. Because of this we need point 3 which resets the insn's destination field to point to arg0 when the next command is handled.
Thank you very much for your excellent response. I'm going to have to study it as soon as I get some time.
Glancing through it, I basically didn't understand how to do a single thing correctly there. PASM is weird.
I need to re-examine how "values" are passed to PASM sub-routines.
At any rate, again thanks for the response. I've got some work to do.
I thought I'd add to this thread rather than start a new one...
I'm still grappling with some fundamental concepts of ASM. I'm trying to imagine the framework of an ASM routine that shares data with a Spin routine. Here's what I don't get:
I have two bytes and 12 words in hub memory that need to be manipulated by a cog running ASM. How do I get the addresses of said bytes and words into the ASM routine with just one long parameter variable? Or tell Spin where they are if they have to be defined and put in the hub with ASM?
You can only pass one address via par so what most of us do is pass the starting address of a block of longs; within this block you can store the addresses (use @ to populate) of your byte and word arrays.
Note: It is not a good idea to count on placement of variables in a list when you want multiple addresses -- better to manually stuff and pass the addresses as part of a parameters list.
Here's a partial listing:
varlong cog
long param1
long param2
word warray[12]
byte barray[2]
pubstart
stop
param1 := @warray ' get hub address of arrays
param2 := @barray
cog := cognew(@entry, @param1) + 1return cog
pubstopif (cog)
cogstop(cog - 1)
cog := 0datorg0
entry mov t1, par' get address of paramsrdlong wpntr, t1 ' get hub addr of w'sadd t1, #4' next longrdlong bpntr, t1 ' get hub addr of b's
This sets the cog variables wpntr and bpntr to the addresses of the respective arrays. You can use these values (updated with an index) with wrxxxx and rdxxxx to access the arrays.
Here's how you might create cog subroutines to read/write those arrays:
readw mov t1, wpntr ' point to w arrayshl idx, #1' x2 for wordsadd t1, idx ' add indexrdlong wval, t1 ' read warray[idx] into wval
readw_ret ret
writew mov t1, wpntr ' point to w arrayshl idx, #1' x2 for wordsadd t1, idx ' add indexwrlong wval, t1 ' write wval to warray[idx]
writew_ret ret
readb mov t1, bpntr ' point to b arrayadd t1, idx ' add indexrdlong bval, t1 ' read barray[idx] into bval
readb_ret ret
writeb mov t1, bpntr ' point to b arrayadd t1, idx ' add indexwrlong bval, t1 ' write bval to barray[idx]
writeb_ret ret
Remember that the cog sees the hub as a giant array of bytes. What this means, then, is that when you're reading/writing a word, you must multiply the index value by 2 (2 bytes per long word). This is added into the base address of the array which gives you the correct hub address for the word you want to access.
Now I'm trying to replicate the process in PASM.
This is what I have so far:
{PASM 1000th prime number generator ***Time to execute:???}CON_clkmode= xtal1+pll16x_xinfreq= 5_000_000OBJ
pst: "Parallax Serial Terminal"VARlong _Prime
PUBmain
pst.Start(57600)
waitcnt(clkfreq*2+cnt)
cognew(@start, @_Prime)
repeatwhile _Prime==0
pst.str(string(16, "Calculating..."))
pst.str(string(13, "1000th Prime Number == "))
pst.dec(_Prime)
DATorg0
start add candidate, #2'generate "next" candidate (all odd numbers)mov divisor, candidate 'copy candidate to divisor
dd sub divisor, #2'generate "next" divisor (all odd numbers < candidate)sub divisor, #1nr, wz'check if divisor has reached oneif_zjmp #prime 'if divisor==1 (Z==1) then jump to #prime
divide mov checked, check wz'check if (candidate//divisor)==0 if_zjmp #start 'if check==0 (Z==1) then move on to next candidate jmp #dd 'if check<>0 (Z==0) then move on to next divisor
prime add prime_cnt, #1'count candidate as primesub prime_cnt, grand nr, wz'check if prime_cnt==1000if_zjmp #end 'if prime_cnt==1000 (Z==1) then jump to #endjmp #start 'if prime_cnt<>1000 (Z==0) then move on to next candidate
end wrlong candidate, PAR'write candidate (1000th prime number) to address at PAR (_Prime)'************************************************************
candidate long7
prime_cnt long4
check long (candidate//divisor)
grand long1000
divisor res1
quotient res1
checked res1
Unfortunately, instead of the expected 7919, It returns 1999.
My first suspicion is that I don't quite understand how to use the flags properly.
Also, I'm not certain I can have a variable ("check") equal an operation between
two other variables (candidate//divisor) without doing more than just putting candidate//divisor in the value field.
Remember that the cog sees the hub as a giant array of bytes. What this means, then, is that when you're reading/writing a word, you must multiply the index value by 2 (2 bytes per long).
Little slip there - 2 bytes per word is what was meant! And for longs the factor is 4 (or a shift-left of 2 bits).
I guess I'm confused as to how to do that.
In my code I have one variable named check initialized to (candidate//divisor)
and at instruction "divide" I copy the value from check into checked and "wz"
where Z==1 would mean I had evenly divided divisor into candidate, thus candidate is not prime.
In my code I have one variable named check initialized to (candidate//divisor)
This is a compile time value which is calculated as the remainder from dividing the cog address of candidate ($00D) by the cog address of divisor ($011). IOW it will stay $00D for the duration of your run and you will always get a result of 7+(1000-4)*2 = 1999.
Division & Co have to be done the hard way in PASM if you want runtime results. Check appendix B of the manual. You should also terminate your program properly after the wrlong (with an endless loop/wait or cogstop). Otherwise the cog continues to execute what's there.
Bummer. I thought the value was determined when the value was read (not that I can think of how that would happen).
Thanks for the help.
Update:
Integrated the division code from appendix b of the manual (thanks Kuroneko) and it works.
16.5ish seconds to execute; and that's with a 2 second handshake period for the serial terminal. So 14.5ish seconds to generate the prime number. 01_PASM_PrimeNumberGenerator.spin
Update2:
So now I'm trying to pass the starting address of an array of longs like JonnyMac described earlier in the thread.
I think I understand the general goal: to set a variable equal to the starting address of an array of "longs" and then in assembly, read each long into it's own variable by starting with the address in PAR then adding an Index value to that address such that you arrive at the next long (which I think is idx:=4). I have had a hell of a time wrapping my head around the idea of memory addresses, but I feel like I'm close.
{PASM 1000th prime number generator ***Time to execute:???}CON_clkmode= xtal1+pll16x_xinfreq= 5_000_000OBJ
pst: "Parallax Serial Terminal"VARlong array_start
long _PASM_PARS[2]
PUBmain
pst.Start(57600)
waitcnt(clkfreq*2+cnt)
array_start:=@_PASM_PARS
_PASM_PARS[1]:=(cognew(@Pre, @array_start)+1)
pst.str(string(16, "Calculating..."))
repeatwhile _PASM_PARS[0]==0waitcnt(clkfreq/100+cnt)
pst.str(string(13, "1000th Prime Number == "))
pst.dec(_PASM_PARS[0])
DATorg0
Pre mov Larray,PAR'get address of array_startmov array_addr,Larray 'copy address of array_startmov idx,#1'set index to 1shl idx,#2'set index to 4 add array_addr,idx 'add index to array start to get address of next location in arrayrdlong cog_id,array_addr 'read from main memory '*************************************************************************************************************************************
start add candidate,#2'generate "next" candidate (all odd numbers)mov divisor,candidate 'copy candidate to divisor
dd sub divisor,#2'generate "next" divisor (all odd numbers < candidate)sub divisor,#1nr,wz'check if divisor has reached oneif_zjmp #prime 'if divisor==1 (Z==1) then jump to #prime
check jmp #divide 'jmp to #divide
checked mov dend,rem nr,wz'flag z if rem==0if_zjmp #start 'if check==0 (Z==1) then move on to next candidate jmp #dd 'if check<>0 (Z==0) then move on to next divisor
prime add prime_cnt,#1'count candidate as primesub prime_cnt,grand nr, wz'check if prime_cnt==1000if_zjmp #end 'if prime_cnt==1000 (Z==1) then jump to #endjmp #start 'if prime_cnt<>1000 (Z==0) then move on to next candidate
end wrlong candidate,Larray 'write candidate (1000th prime number) to address at PAR (_Prime)'cogstop cog_id 'stop cog(cog_id)
dead nopjmp #dead
'*************************************************************************************************************************************
divide mov dor, divisor 'copy divisor into dormov dend, candidate 'copy den into candidateshl dor,#15'get dor into dor[30..15] cmpsub dend,dor wc'dend =< dor? Subtract it, quotient bit in c rcl dend,#1'rotate c into quotient, shift dividendcmpsub dend,dor wc'rcl dend,#1'repeat x16cmpsub dend,dor wc'rcl dend,#1'cmpsub dend,dor wc'rcl dend,#1'cmpsub dend,dor wc' rcl dend,#1'cmpsub dend,dor wc'rcl dend,#1'cmpsub dend,dor wc' rcl dend,#1'cmpsub dend,dor wc' rcl dend,#1'cmpsub dend,dor wc' rcl dend,#1'cmpsub dend,dor wc'rcl dend,#1'cmpsub dend,dor wc'rcl dend,#1'cmpsub dend,dor wc'rcl dend,#1'cmpsub dend,dor wc' rcl dend,#1' cmpsub dend,dor wc'rcl dend,#1'cmpsub dend,dor wc'rcl dend,#1'cmpsub dend,dor wc' rcl dend,#1''quotient in dend[15..0], remainder in dend[31..16] mov rem,dend 'copy dend to rem shr rem,#15'shift out quotient
ret_checked jmp #checked 'return to checked '*************************************************************************************************************************************
candidate long7
prime_cnt long4
grand long1000
divisor res1
dor res1
dend res1
rem res1
Larray res1
array_addr res1
idx res1
cog_id res1
Other than adding the Pre routine, all I changed was the wrlong from "wrlong candidate,PAR" to "wrlong candidate,Larray" where "Larray" theoretically == @_PASM_PARS[0]
The other variable in the array is the cog_id of the PASM cog so I can use a cogstop command at end; rather than an endless loop. But I'm not using it right now.
My point being I'm probably missing something in the Pre routine.
We were all learning the language at the time and that was my personal preferred method.... now I think OR to set the pin and ANDN to clear the pin is the more accepted method, but it probably depends on the circumstance.
It seems to me that a potential advantage to the MUX method is that it deals with the current pin/s in the mind of the programmer and wont interfere should there be some other pins that other part of the code has set purposely. Why AND or XOR the whole INA or DIRA unless you really want to? It makes no difference on these simple and short code examples, but on more sophisticated pin sets it is better to have code applying only what ought to be applied... n'est pas?
Comments
' lhs == 18-bit value ' rhs == 4-bit value ' acc := lhs * rhs mov acc,#0 shr rhs,#1 WC IF_C add acc,lhs shl lhs,#1 shr rhs,#1 WC IF_C add acc,lhs shl lhs,#1 shr rhs,#1 WC IF_C add acc,lhs shl lhs,#1 shr rhs,#1 WC IF_C add acc,lhs
Is it true that when I want to play an audio 16[url=mailto:16bit@22.05khz]bit@22.05khz[/url]·signal, I only have (20000000/(16*22050))=56.56clock cycles thus I only may use about 14 instructions(most instructions cost 4 cycles)?
The thing that I want to make a synthesizer and·would love to use 16bit 44.1khz to playback my calculations.
But that would really limit me in assembly instructions.
Edit:
I seem to have made a mistake about the calc.
It would have been 20000000/22050=907cycles about 226cycles which would be enough I gess, and then shift the 16bits in the audio dac.
maybe even at 44.1khz...
But for now...
I want to use one cog for wave generation and then put it trought a filter algoritm.
I then have to see if I could sync the 2 cogs.
first cog will generate the first 16bit of the wave, the second will have to wait till the first cog calculated the waveform.
I thought of writing·a bit in the hub mem, "0" would be wait for waveform calc to finish which will be '1" if finished, second cog will make it '0' once it is read.
The first cog sees a '0' and know it has to calc·the next 16bit value of the wave form.
Is this a good approach or not?
Post Edited (darkxceed) : 10/8/2008 5:24:40 PM GMT
PS : It's much better if you start a new topic for questions not related to an existing thread.
But isn't it true that a cog operates at 20Mhz and not 80Mhz?
your calculations is almost the same as me btw 80000000/44100/4=453, so it take one cycle of the 20Mhz clock to execute 1 instruction.
Post Edited (darkxceed) : 10/8/2008 5:57:52 PM GMT
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
http://www.propgfx.co.uk/forum/·home of the PropGFX Lite
·
Well this adds up all well!
thanx
test bit2mask,INA wc ' At this point, the carry flag is set to the state of pin 2 bit2mask long |< 2 mov temp,INA shr temp,#16 ' Shift bit 16 to the position of bit 0 and temp,#$FF ' Mask off least significant 8 bits temp long 0
.
.
.
waitpne PIN2,PIN2 'waits for INA anded with PIN2 to not equal PIN2 ( assuming only one bit is set in PIN2 otherwise a low on any of the input pins in PIN2 would pass this check )
mov datain,INA ' get the data from the port.
shr datain,#16 ' I'm assuming you want the byte, so shift the bits right 16 times
and datain,#255
.
.
.
PIN2 long 1<<2
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
http://www.propgfx.co.uk/forum/·home of the PropGFX Lite
·
Is it possible or it's very complicated ?
Thank you in advance
Assembly Code Examples for the Beginner: SPI Engine Demo
and I have a really simple question.
DAT org' ' SPI Engine - main loop ' loop rdlong t1,par wz 'wait for command if_z jmp #loop movd :arg,#arg0 'get 5 arguments ; arg0 to arg4 mov t2,t1 ' │ mov t3,#5 '───┘ :arg rdlong arg0,t2 add :arg,d0 add t2,#4 djnz t3,#:arg mov address,t1 'preserve address location for passing 'variables back to Spin language. wrlong zero,par 'zero command to signify command received ror t1,#16+2 'lookup command address add t1,#jumps movs :table,t1 rol t1,#2 shl t1,#3 :table mov t2,0 shr t2,t1 and t2,#$FF jmp t2 'jump to command jumps byte 0 '0 byte SHIFTOUT_ '1 byte SHIFTIN_ '2 byte NotUsed_ '3 NotUsed_ jmp #loop
what is the "movd" command doing?
I read the command explanation out of the manual, and I understand movd copies the value in the source location to the destination field (bits [17..9]) of the destination location; but I don't understand what it's doing here. Can you fill the destination field of PASM instructions with a movd operation? and if that is what it's being used for here, why wouldn't you just use arg0, why would you use movd and #arg0?
Also, I understand when you use the # symbol in front of a value, you are indicating that rather than a memory location, you are using a 9 bit literal. But what does it mean when there is a # symbol in front of a memory address?
I've spent a good while trying to figure this out myself, and I don't think I'm going to.
So I really would appreciate an explanation. Thanks.
What are the values ending up in a and b? Then think about what the effect would be on an insn when used with movd in the context of parameter setup.
DAT org 0 entry mov a, location ' $000, a = ? mov b, #location ' $001, b = ? waitpeq $, #0 ' $002 location long 42 ' $003 a res 1 ' $004 b res 1 ' $005
I had a discussion with a friend that I think cleared some of this up for me.
The short answers to my short questions:
Q: Can you modify a later instruction using the "movd" command?
A: Yes. It makes sense, but the only applications for the "movd" command described in the manual are both configuring special registers (either the video or counter modules), and I was having a hard time testing the theory.
Q: What does it mean when you put a literal symbol in front of a label rather than a value?
A: If you put a # symbol in front of a a label for either an initialized or un-initialized variable, you are telling the propeller to use the memory location that label refers to, as a value; rather than the value contained there-in. In other words, if you have a label "Number" at memory location 4, and the value in "Number" is 18, then "#Number" equals 4, rather than 18.
If any of that is wrong please show no mercy.
So to test my understanding I wrote a small block of assembly code, but am not getting the results I expected.
CON _clkmode= xtal1+pll16x _xinfreq= 5_000_000 PUB main cognew(@start, 4) DAT ORG 0 start mov n1, #0 wz 'set Z to 1 :read rdlong n1, par 'read par into n1 add :read, #4 'add the literal decimal value 4 into :read n1Lights muxz dira, n1 'set bits in dira corresponding with the high bits in n1 to Z muxz outa, n1 'set bits in outa corresponding with the high bits in n1 to Z Ldelay mov time, cnt 'time:=cnt add time, Ldelay 'add delay into time waitcnt time, #0 'wait for count to equal time _Ldelay long 500_000 n1 res 1 time res 1
I was hoping to see P3 light up (using P0-P31 as a binary display for the value in n1).
I'm using the Gear: Parallax Propeller Emulator (which can be found here) and it's built in dir display,
and this is what I'm getting:
which I'm pretty sure reads P0, P1, P2, P3, P5, P6, P9, P10, P11, P13, P20 as high, and everything else as low.
So what am I missing here?
Thanks for your help.
Edit: Just realised that you mentioned dira/dirb rather than dira/outa (still too early here).
For non-special-register movd usage check [post=1108886]this example[/post].
In the propeller manual, it clearly says you can use nine bit literal values in place of a memory location.
Here's an example from the manual:
add X, #25 'Add 25 to X add X, Y 'Add Y to X X long 50 Y long 10
and the description:"the result of the first ADD instruction is 75 (i.e.: X + 25 → 50 + 25 = 75) and that value, 75, is stored back in the X register. Similarly, the result of the second ADD instruction is 85
(i.e.: X + Y → 75 + 10 = 85) and so X is set to 85. "
So to answer Kuroneko, I thought I was adding 4+4=8, ie 000000_00000000_00000000_00001000
Really I'm just trying to understand what's going on in this block of code (from Beau Schwabe's SPI engine)
loop rdlong t1,par wz 'wait for command if_z jmp #loop movd :arg,#arg0 'get 5 arguments ; arg0 to arg4 mov t2,t1 ' │ mov t3,#5 '───┘ :arg rdlong arg0,t2 add :arg,d0 add t2,#4 djnz t3,#:arg mov address,t1
start mov n1, #0 wz 'set Z to 1 :read [COLOR="#FFA500"]rdlong[/COLOR] n1, par 'read par into n1 add :read, #4 'add the literal decimal value 4 into :read
What happens is that - after setting the Z flag - you read from hub RAM location #4 (par == 4) which gives you $00102E6F in n1 (basically the wrong insn here). After that you increase the content of :read by 4 (not n1 or par). Location :read contains an insn (rdlong n1, par) which is encoded as $08BC13F0. Adding 4 to it results in $08BC13F4 (rdlong n1, outa). One way of arriving at 8 would have been:start mov n1, #0 wz 'set Z to 1 :read mov n1, par 'move par into n1 add n1, #4 'add the literal decimal value 4 into n1
The objective here is to read 5 parameters from hub RAM. Unrolled it would look like this:
rdlong arg0, t2 add t2, #4 rdlong arg1, t2 add t2, #4 rdlong arg2, t2 add t2, #4 rdlong arg3, t2 add t2, #4 rdlong arg4, t2
Doing it like this is OK for a small number of parameters (or [thread=135075]if you want it fast[/thread]). To keep the code size down it's sometimes done in a loop.{1} loop rdlong t1,par wz 'wait for command {2} if_z jmp #loop {3} movd :arg,#arg0 'get 5 arguments ; arg0 to arg4 {4} mov t2,t1 ' │ {5} mov t3,#5 '───┘ {6} :arg rdlong arg0,t2 {7} add :arg,d0 {8} add t2,#4 {9} djnz t3,#:arg
- read the command (par is the hub address of the command)
- if said command is zero try again (until non-zero)
- reset parameter fetch insn to rdlong arg0, t2
- take copy of command (hub address in lower bits)
- initialise loop counter (5 arguments)
- read first argument into arg0
- modify parameter fetch insn to point to next cog address (arg1)
- advance hub address
- read next argument (insn at :arg has now been modified/updated)
A note re: point 7, what happens here is that the destination field (insn[17..9]) is incremented by one, IOW starting with rdlong arg0, t2 you'll effectively end up with rdlong arg0+1, t2 which is the same as rdlong arg1, t2 in our case. After 5 loop cycles said insn has been incremented by 5*d0. Because of this we need point 3 which resets the insn's destination field to point to arg0 when the next command is handled.Glancing through it, I basically didn't understand how to do a single thing correctly there. PASM is weird.
I need to re-examine how "values" are passed to PASM sub-routines.
At any rate, again thanks for the response. I've got some work to do.
I'm still grappling with some fundamental concepts of ASM. I'm trying to imagine the framework of an ASM routine that shares data with a Spin routine. Here's what I don't get:
I have two bytes and 12 words in hub memory that need to be manipulated by a cog running ASM. How do I get the addresses of said bytes and words into the ASM routine with just one long parameter variable? Or tell Spin where they are if they have to be defined and put in the hub with ASM?
Help me, Obi Wans...
Note: It is not a good idea to count on placement of variables in a list when you want multiple addresses -- better to manually stuff and pass the addresses as part of a parameters list.
Here's a partial listing:
var long cog long param1 long param2 word warray[12] byte barray[2] pub start stop param1 := @warray ' get hub address of arrays param2 := @barray cog := cognew(@entry, @param1) + 1 return cog pub stop if (cog) cogstop(cog - 1) cog := 0 dat org 0 entry mov t1, par ' get address of params rdlong wpntr, t1 ' get hub addr of w's add t1, #4 ' next long rdlong bpntr, t1 ' get hub addr of b's
This sets the cog variables wpntr and bpntr to the addresses of the respective arrays. You can use these values (updated with an index) with wrxxxx and rdxxxx to access the arrays.
Here's how you might create cog subroutines to read/write those arrays:
readw mov t1, wpntr ' point to w array shl idx, #1 ' x2 for words add t1, idx ' add index rdlong wval, t1 ' read warray[idx] into wval readw_ret ret writew mov t1, wpntr ' point to w array shl idx, #1 ' x2 for words add t1, idx ' add index wrlong wval, t1 ' write wval to warray[idx] writew_ret ret readb mov t1, bpntr ' point to b array add t1, idx ' add index rdlong bval, t1 ' read barray[idx] into bval readb_ret ret writeb mov t1, bpntr ' point to b array add t1, idx ' add index wrlong bval, t1 ' write bval to barray[idx] writeb_ret ret
Remember that the cog sees the hub as a giant array of bytes. What this means, then, is that when you're reading/writing a word, you must multiply the index value by 2 (2 bytes per long word). This is added into the base address of the array which gives you the correct hub address for the word you want to access.
First I wrote one in SPIN, (01_SPIN_PrimeNumberGenerator.spin)
It takes 4 minutes and 44 seconds to run.
Now I'm trying to replicate the process in PASM.
This is what I have so far:
{PASM 1000th prime number generator ***Time to execute:???}CON _clkmode= xtal1+pll16x _xinfreq= 5_000_000 OBJ pst: "Parallax Serial Terminal" VAR long _Prime PUB main pst.Start(57600) waitcnt(clkfreq*2+cnt) cognew(@start, @_Prime) repeat while _Prime==0 pst.str(string(16, "Calculating...")) pst.str(string(13, "1000th Prime Number == ")) pst.dec(_Prime) DAT org 0 start add candidate, #2 'generate "next" candidate (all odd numbers) mov divisor, candidate 'copy candidate to divisor dd sub divisor, #2 'generate "next" divisor (all odd numbers < candidate) sub divisor, #1 nr, wz 'check if divisor has reached one if_z jmp #prime 'if divisor==1 (Z==1) then jump to #prime divide mov checked, check wz 'check if (candidate//divisor)==0 if_z jmp #start 'if check==0 (Z==1) then move on to next candidate jmp #dd 'if check<>0 (Z==0) then move on to next divisor prime add prime_cnt, #1 'count candidate as prime sub prime_cnt, grand nr, wz 'check if prime_cnt==1000 if_z jmp #end 'if prime_cnt==1000 (Z==1) then jump to #end jmp #start 'if prime_cnt<>1000 (Z==0) then move on to next candidate end wrlong candidate, PAR 'write candidate (1000th prime number) to address at PAR (_Prime) '************************************************************ candidate long 7 prime_cnt long 4 check long (candidate//divisor) grand long 1000 divisor res 1 quotient res 1 checked res 1
Unfortunately, instead of the expected 7919, It returns 1999.
My first suspicion is that I don't quite understand how to use the flags properly.
Also, I'm not certain I can have a variable ("check") equal an operation between
two other variables (candidate//divisor) without doing more than just putting candidate//divisor in the value field.
As always I would appreciate any advice.
Thanks.
Little slip there - 2 bytes per word is what was meant! And for longs the factor is 4 (or a shift-left of 2 bits).
You haven't implemented the divisibility test so every odd number is believed to succeed, so you stop on the 1000'th odd number.
In my code I have one variable named check initialized to (candidate//divisor)
and at instruction "divide" I copy the value from check into checked and "wz"
where Z==1 would mean I had evenly divided divisor into candidate, thus candidate is not prime.
Division & Co have to be done the hard way in PASM if you want runtime results. Check appendix B of the manual. You should also terminate your program properly after the wrlong (with an endless loop/wait or cogstop). Otherwise the cog continues to execute what's there.
Thanks for the help.
Update:
Integrated the division code from appendix b of the manual (thanks Kuroneko) and it works.
16.5ish seconds to execute; and that's with a 2 second handshake period for the serial terminal. So 14.5ish seconds to generate the prime number.
01_PASM_PrimeNumberGenerator.spin
Update2:
So now I'm trying to pass the starting address of an array of longs like JonnyMac described earlier in the thread.
I think I understand the general goal: to set a variable equal to the starting address of an array of "longs" and then in assembly, read each long into it's own variable by starting with the address in PAR then adding an Index value to that address such that you arrive at the next long (which I think is idx:=4). I have had a hell of a time wrapping my head around the idea of memory addresses, but I feel like I'm close.
{PASM 1000th prime number generator ***Time to execute:???}CON _clkmode= xtal1+pll16x _xinfreq= 5_000_000 OBJ pst: "Parallax Serial Terminal" VAR long array_start long _PASM_PARS[2] PUB main pst.Start(57600) waitcnt(clkfreq*2+cnt) array_start:=@_PASM_PARS _PASM_PARS[1]:=(cognew(@Pre, @array_start)+1) pst.str(string(16, "Calculating...")) repeat while _PASM_PARS[0]==0 waitcnt(clkfreq/100+cnt) pst.str(string(13, "1000th Prime Number == ")) pst.dec(_PASM_PARS[0]) DAT org 0 Pre mov Larray,PAR 'get address of array_start mov array_addr,Larray 'copy address of array_start mov idx,#1 'set index to 1 shl idx,#2 'set index to 4 add array_addr,idx 'add index to array start to get address of next location in array rdlong cog_id,array_addr 'read from main memory '************************************************************************************************************************************* start add candidate,#2 'generate "next" candidate (all odd numbers) mov divisor,candidate 'copy candidate to divisor dd sub divisor,#2 'generate "next" divisor (all odd numbers < candidate) sub divisor,#1 nr,wz 'check if divisor has reached one if_z jmp #prime 'if divisor==1 (Z==1) then jump to #prime check jmp #divide 'jmp to #divide checked mov dend,rem nr,wz 'flag z if rem==0 if_z jmp #start 'if check==0 (Z==1) then move on to next candidate jmp #dd 'if check<>0 (Z==0) then move on to next divisor prime add prime_cnt,#1 'count candidate as prime sub prime_cnt,grand nr, wz 'check if prime_cnt==1000 if_z jmp #end 'if prime_cnt==1000 (Z==1) then jump to #end jmp #start 'if prime_cnt<>1000 (Z==0) then move on to next candidate end wrlong candidate,Larray 'write candidate (1000th prime number) to address at PAR (_Prime) 'cogstop cog_id 'stop cog(cog_id) dead nop jmp #dead '************************************************************************************************************************************* divide mov dor, divisor 'copy divisor into dor mov dend, candidate 'copy den into candidate shl dor,#15 'get dor into dor[30..15] cmpsub dend,dor wc 'dend =< dor? Subtract it, quotient bit in c rcl dend,#1 'rotate c into quotient, shift dividend cmpsub dend,dor wc ' rcl dend,#1 'repeat x16 cmpsub dend,dor wc ' rcl dend,#1 ' cmpsub dend,dor wc ' rcl dend,#1 ' cmpsub dend,dor wc ' rcl dend,#1 ' cmpsub dend,dor wc ' rcl dend,#1 ' cmpsub dend,dor wc ' rcl dend,#1 ' cmpsub dend,dor wc ' rcl dend,#1 ' cmpsub dend,dor wc ' rcl dend,#1 ' cmpsub dend,dor wc ' rcl dend,#1 ' cmpsub dend,dor wc ' rcl dend,#1 ' cmpsub dend,dor wc ' rcl dend,#1 ' cmpsub dend,dor wc ' rcl dend,#1 ' cmpsub dend,dor wc ' rcl dend,#1 ' cmpsub dend,dor wc ' rcl dend,#1 ' cmpsub dend,dor wc ' rcl dend,#1 ' 'quotient in dend[15..0], remainder in dend[31..16] mov rem,dend 'copy dend to rem shr rem,#15 'shift out quotient ret_checked jmp #checked 'return to checked '************************************************************************************************************************************* candidate long 7 prime_cnt long 4 grand long 1000 divisor res 1 dor res 1 dend res 1 rem res 1 Larray res 1 array_addr res 1 idx res 1 cog_id res 1
Other than adding the Pre routine, all I changed was the wrlong from "wrlong candidate,PAR" to "wrlong candidate,Larray" where "Larray" theoretically == @_PASM_PARS[0]The other variable in the array is the cog_id of the PASM cog so I can use a cogstop command at end; rather than an endless loop. But I'm not using it right now.
My point being I'm probably missing something in the Pre routine.
Thanks for your help.
for example:
mov pin_mask, #1
shl pin_mask, pin
or outa, pin_mask 'preset pin high
or dira, pin_mask 'set pin to output