PDA

View Full Version : [RESOLVED] SPIN stub code for learning ASM



Mightor
09-01-2007, 11:11 PM
hey there,

I am trying to learn ASM for the Prop but the one thing I keep running into is how to make use of these bits of ASM code that I create. For example, I copied the Divide algorithm from deSilva's ASM guide:



' Divide x[31..0] by y[15..0] (y[16] must be 0)
' on exit, quotient is in x[15..0] and remainder is in x[31..16]
'
divide shl y,#15 'get divisor into y[30..15]
mov t,#16 'ready for 16 quotient bits
:loop cmpsub x,y wc 'if y =< x then subtract it, set C
rcl x,#1 'rotate c into quotient, shift dividend
djnz t,#:loop 'loop until done
divide_ret ret 'quotient in x[15..0], rem. in x[31..16]



However, I am not sure how I could write some SPIN code that could get the results back from this kind of routine. I have figured out how to pass stuff to it using the cognew and passing an address to an array of longs, for example. This is what I did for my PWM code.
I'd like to be able to use something like FullDuplexSerial to display the results of my little ASM programs so I can see what tweaking this, that or the other does to a result. Right now I have no idea how to do something like that. If some of the gurus could shed some light on this, that would be great! There is no way to call the ASM code (and get the return value) from inside SPIN code without calling cognew, is there?

Gr,
Mightor

▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
| To know recursion, you must first know recursion.
| I'm afraid I might be phobophobic.

Post Edited (Mightor) : 9/9/2007 11:49:56 AM GMT

deSilva
09-01-2007, 11:23 PM
Have a look at example 2 on p. 11: You can see how the COGs access "aCounter"; they can as well write to it.
The simplest way is to store addresses of the variables you want to communicate through into DAT cells (as I did it with "patternAddr").

You can use as many as you like.
The good thing is, that you need not go through incrementing PAR all the time!

There is a special technique for ADVANCED users: you can also use the DAT cells "remaining" in the COG as commucation cells themselves. But wait until the COG is loaded before using them http://forums.parallax.com/images/smilies/smile.gif

See my example for.... (searching...)
Edit: It's here http://forums.parallax.com/showthread.php?p=669873 the 9th posting or so...

Post Edited (deSilva) : 9/1/2007 9:31:37 PM GMT

Mightor
09-02-2007, 01:20 AM
I must be missing something here, because this code is not working at all. The "answer" variable never gets the value 16 as it should.



CON
_clkmode = xtal1 + pll16x
_xinfreq = 5_000_000

VAR
long answer

OBJ
Debug: "FullDuplexSerial"

PUB start
aAddr := @answer
Debug.start(31, 30, 0, 57600)
cognew(assign, 0)
waitcnt(clkfreq + cnt) ' wait a sec for cog to be ready
repeat
Debug.str(string("answer: "))
Debug.dec(answer)
Debug.str(string(10,13))
waitcnt(clkfreq/10 + cnt)


DAT
org
assign mov _t, #16 ' assign the value 16 to _t
wrlong _t, aAddr ' write the value to the HUB variable

assign_ret ret

aAddr long 0
_t long 0




I really kept it as simple as I possibly could. I must be doing something wrong, obviously, or the program would be printing "answer: 16" all the time. Could someone please tell me? I'm quite lost. I am sure the answer to this problem will result in a very loud "DOH!" from my side.

Gr,
Mightor

▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
| To know recursion, you must first know recursion.

Mike Green
09-02-2007, 01:49 AM
Do look at the example deSilva mentioned. You can't do the "wrlong _t,aAddr" because the compiler doesn't know the address of "aAddr" when it assembles the instruction. Use "cognew(@assign,@answer)" to start the assembly routine and use "wrlong _t,PAR" to copy the result back to "answer".

Mightor
09-02-2007, 02:04 AM
Mike,

I did look at it and for the first cog, it passes "0" as the argument and uses RDLONG DIRA, patternAddr (it's in the ex02a section). patternAddr has been assigned the address of aCounter as its value. So why would it work here and not in my example? I am storing the address of the answer variable in aAddr in much the same way. I'm not quite following this.

Gr,
Mightor

This is the code I was looking at:



VAR
LONG aCounter

PUB ex02
patternaddr := @aCounter
COGNEW(@ex02A, 0)
COGNEW(@ex02B, @aCounter)
REPEAT
aCounter++

DAT
ORG 0
ex02A
MOV DIRA, #$FF ' 0..7 for output
:loop
RDLONG OUTA, patternAddr
JMP #:loop
patternAddr LONG 0 ' address of a communication variable
' must be stored here before loading COG
ORG 0
ex02B
MOV DIRA, altPins ' 8..15 for output
:loop
RDLONG r1, PAR
SHL r1, #8
MOV OUTA, r1
JMP #:loop
altPins LONG $FF<<8
r1 LONG 0


▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
| To know recursion, you must first know recursion.

Mike Green
09-02-2007, 02:57 AM
OK, what you are seeing are two different ways to pass information back and forth. ex02A illustrates a technique that initializes the assembly program before it's started by storing data into the program before it gets loaded into the cog. Since the compiler doesn't know where the variables will be stored when the program is compiled, your program has to figure that out when it runs. ex02B shows a different technique where the address of some long word is passed to the cog in the PAR register when it's started. Typically this is the address of a variable or a group of variables in successive memory locations that your program can access.

R Pankau
09-02-2007, 03:23 AM
I'm having similar issues, I read deDilva's tutorial and thought I understood it but when my asm wites back to cog memory it goes to the wrong place, I know because other variables are being over-written. I say wrong, but I'm sure it's my code that's wrong.
I think I agree with Mightor in that in ex02A the variable patternaddr exists in both spin and dat space and is initialized in spin with the address of aCounter, so within asm we should have knowledge of aCounters location and should be able to use wrlong to write to it. does the address of aCounter have to be a multible of 4? how can we ensure that?

my code isn't working so I don't want to argue that I'm right, I just don't see yet where I'm not correct.

▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
I'd rather have a bottle in front of me than a frontal labotomy

R Pankau
09-02-2007, 03:30 AM
I should have said, "asm writes back to HUB memory" in my last post.

▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
I'd rather have a bottle in front of me than a frontal labotomy

R Pankau
09-02-2007, 03:45 AM
along this same line of thinking are there limits to how many successive locations can be written back to HUB memory?
for example say I have a var long buffer[7000] and when a new cog is started, par contains the first location of buffer. In asm will there ever be a problem with writing back to all 7000 locations?

▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
I'd rather have a bottle in front of me than a frontal labotomy

Mike Green
09-02-2007, 04:42 AM
Randy,
Your last situation is never a problem. The assembly code can calculate any address in HUB memory and then can read and write to it. Remember that HUB addresses are byte addresses, so you'd have to multiply your buffer index by 4 (shift it left by 2), then add PAR to it. The result can be used with a RDLONG/WRLONG.

It's been mentioned before that RDWORD/WRWORD ignores the least significant bit of the address and RDLONG/WRLONG ignores the least significant 2 bits of the address.

deSilva
09-02-2007, 04:45 AM
@Mightor:
(1) The COG is not loaded correctly http://forums.parallax.com/images/smilies/smile.gif You have to provide the ADDRESS of "assign" (= @assign) as parameter to COGNEW

(2) The COG just starts at its address 0, there is no "call" or whatever, so there can be no RET! Use either an endless loop, or kill the COG

COGID x
COGSTOP x

deSilva
09-02-2007, 04:50 AM
@Randy: What made you put your question? I mean, It would never enter my mind that there COULD be a limitation of such kind. But I think there is a reason, you would suspect it!?

deSilva
09-02-2007, 04:55 AM
Mike Green said...
Do look at the example deSilva mentioned. You can't do the "wrlong _t,aAddr" because the compiler doesn't know the address of "aAddr" when it assembles the instruction. Use "cognew(@assign,@answer)" to start the assembly routine and use "wrlong _t,PAR" to copy the result back to "answer".


Mike, this is confusing what you were saying. "aAddr" has been set quite correctly by Mightor in the first line of his main routine.

I am not a friend of using PAR, as it creates an additional level of indirection, that can be useful, but in most cases is not

R Pankau
09-02-2007, 05:20 AM
Reason 1, working in the basement too late, with too little coffee
I was running into roadblocks with instructions not taking any more than 9 bits. I should not have been surprised I guess but in the back of my mind I was thinking, "32 bit processor should be able to mov 32 bits", such as the decimal value 7000 from an initialized var in DAT to t1 for example for decrementing and testing at th end of a loop. I ended up splitting it up into two eight bit sections and shifting the MSBs up and adding the lower.

▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
I'd rather have a bottle in front of me than a frontal labotomy

deSilva
09-02-2007, 05:42 AM
@Randy:It was never clearly stated what the issue with your program was, but what you describes as "solution" does not sound very healthy. Shall we dig deeper for it?

As to one of your other questions: When stating something as LONG, whether in DAT or in VAR it will always be aligned correctly. When you are not in real need for space I should not recommend using WORD, as there are too many peculiarities with it. They can be confusing, and in most cases LONG will do fine. There is no penalty for it, as you might expect from your knowledge of other processors.

R Pankau
09-02-2007, 10:17 AM
Well, now that you mention it, I did use a few bytes actually but they were declared after the shared memory. could that have been a reason for a shift?

BTW my code is at another computer and I need to go look at it again with fresh eyes. The main symptom is this (trying to remember from last night). the code is a take off of the IR_kit, In spin I simply sampled the input connected to an IR 38KHz receiver and put ones or zeroes in successive locations in a buffer after the first input pulse was detected.
Then I thouhgt why not convert it to asm and learn something useful. So I ported it to asm and now when I first hit the input with my IR remote it spits out serial data but it should not. The serial dump should not take place until I see a byte in my serial receive variable. The receive variable is what I declared as a byte after the buffer of 7000 longs that I used as a destination for the ir input bits. (one per long) I'm afraid that the asm code is writing to my serial input byte somehow.

▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
I'd rather have a bottle in front of me than a frontal labotomy

R Pankau
09-02-2007, 10:46 AM
Now I'm really confused, but in a good way. I'll never figure out what I did yesterday to make me think that I could not take a 32 bit var and perform a mov with it as the source. I knew that a literal was limited to 9 bits, but I was getting that error or so I thought, with the variable as the source. We'll see what happens after tonight, maybe I was just being stubborn and too tired to catch my mistakes yesterday.

▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
I'd rather have a bottle in front of me than a frontal labotomy

R Pankau
09-02-2007, 11:28 AM
I have it working now. Here are some things I changed.
No longer am I using PAR to find my buffer's address. The method of declaring the var in DAT using SPIN to initialize a pointer works well. don't know why PAR did not work for me.

I had to change my DAT var from a reserved word to a long (the buffer pointer). What is the difference? Looks like the RES is only viewabe in the asm ie cog and the "var_name long value" declaration is available to the whole object. Spin and asm. or at least a copy is sent to the new cog with the address that SPIN put there before spawning the new cog.

The SPIN variable for receiving serial data is still a byte and it is declared before the variables that are used by the asm. All asm vars are declared with one "LONG" and seperated with commas.

Also learned that cnt cannot be used directly as source data with wrlong since it is the address to the cnt register. mov ing it to a local var first then using wrlong does the job.

can you tell I'm new at asm? Since SPIN is not compiled and does run rather slowly I thouht it was a good enough excuse to spend some time learning asm. I did it once in school for the 8088, this seems better than what I remember. I appreciate the enthusiastic help! thanks.

▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
I'd rather have a bottle in front of me than a frontal labotomy

deSilva
09-02-2007, 03:34 PM
Randy P said...
No longer am I using PAR to find my buffer's address. The method of declaring the var in DAT using SPIN to initialize a pointer works well. don't know why PAR did not work for me.

Both should work; consider this:

VAR
LONG hubCell
PUB

' this:
COGNEW(@myAsm, @hubCell)

' .. is equivalent to this:
hubAddr := @hubCell & $FFFC
COGNEW(@myAsm,0)
' Of course you will not do the "&" - it's just a describtion what happens in the machine wrt PAR

DAT
ORG 0
myAsm

hubAddr LONG 0



said...

I had to change my DAT var from a reserved word to a long (the buffer pointer). What is the difference? Looks like the RES is only viewabe in the asm ie cog and the "var_name long value" declaration is available to the whole object. Spin and asm.

RES takes no space in the HUB! When defining it, it is just a bookkeeping remainder for the assembler which cell number (!) to put into an instruction or DAT preset when used. There is no reason why SPIN shall not "know" of this number, but it is NOT a HUB variable, and the simple minded user could be confused...

RES is usefull to save HUB memory, when you use COG vectors of - say - length 256 in 8 different COGs which otherwise would lead to 8kByte absolutly redundant HUB!


said...
or at least a copy is sent to the new cog with the address that SPIN put there before spawning the new cog.

Right!


said...
The SPIN variable for receiving serial data is still a byte and it is declared before the variables that are used by the asm.

Always remember: VAR is re-sorted (LONG, WORD, BYTE) - DAT is padded


said...
All asm vars are declared with one "LONG" and seperated with commas.

This is just fine


said...
Also learned that cnt cannot be used directly as source data with wrlong since it is the address to the cnt register.

No, not exactly! All "special registers" have a "shadow" (=last 16 cells in each COG). Only "dest" is wired to these cells, not "source". This becomes aware to you only in case of CNT, INA, INB, PHSA and PHSB -- just thinking: PAR as well?? I'll check! So the general rule is: Never READ a special register from "dest" position "READING" happens in case of all read-modify-write instructions, which are many. The COG is a Two-Address-Machine and so the name "dest" is a little bit confusing - it is ALSO the destination http://forums.parallax.com/images/smilies/smile.gif

Post Edited (deSilva) : 9/2/2007 9:53:06 AM GMT

Mightor
09-02-2007, 08:26 PM
My stupidity must run deep because I made some changes and it's just not working. I am missing something really fundamental here but I can't figure out what. This is my code now:



CON
_clkmode = xtal1 + pll16x
_xinfreq = 5_000_000

VAR
long answer

OBJ
Debug: "FullDuplexSerial"

PUB start
Debug.start(31, 30, 0, 57600)
cognew(assign, @answer)
waitcnt(clkfreq + cnt) ' wait a sec for cog to be ready
repeat
Debug.str(string("answer: "))
Debug.dec(answer)
Debug.str(string(10,13))
waitcnt(clkfreq/10 + cnt)

DAT
org
assign mov _t, #16 ' assign the value 16 to _t
wrlong _t, par ' write the value to the HUB variable

_t long 1




It's not printing out "16" like it should. What am I missing here?

Gr,
Mightor

▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
| To know recursion, you must first know recursion.

R Pankau
09-02-2007, 08:26 PM
OK so the special registers are only wired to the destination. It is confusing that wrlong for instance has dest as the source data.

As far as the RES vs. declare long in DAT, I was made aware that I was doing something wrong because the compiler complained apparently because Spin was trying to access a RES declaration and it looked like an uninitialized variable error.

▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
I'd rather have a bottle in front of me than a frontal labotomy

Mightor
09-02-2007, 08:32 PM
Randy,

Could you attach some of the code you've written so I can have a look at it?

Thanks,
Mightor

▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
| To know recursion, you must first know recursion.

R Pankau
09-02-2007, 09:04 PM
Sure, I'll try to get it up here this morning.

▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
I'd rather have a bottle in front of me than a frontal labotomy

deSilva
09-02-2007, 09:09 PM
Mightor, why don't you read the answer I gave you 20 hours ago. I did explain your error!!!!!

deSilva
09-02-2007, 09:23 PM
Randy P said...
OK so the special registers are only wired to the destination.

Don't turn my words. I said "dest" is wired to the "cells" $1f0 to $1ff. Whereas "source" is reading (and reading only!) the "real stuff".


said...
It is confusing that wrlong for instance has dest as the source data.

Except MOV nearly all instructions have "dest" as source data http://forums.parallax.com/images/smilies/smile.gif


said...
As far as the RES vs. declare long in DAT, I was made aware that I was doing something wrong because the compiler complained apparently because Spin was trying to access a RES declaration and it looked like an uninitialized variable error.

It says it just does not know this name; that will happen also when you use the name of a local variable of another routine..

Mightor
09-02-2007, 09:55 PM
deSilva said...
Mightor, why don't you read the answer I gave you 20 hours ago. I did explain your error!!!!!

Hehe, you may have explained it, but I probably didn't get it :) I am sure that once the quarter drops I'll have one of those "OMFG, I can't believe I didn't get that" moments, right now though it's more of a slow "Uhhhhhhhhhhhh". Are you referring to the post at about 11:45 PM (GMT +2)? I did initialise the cog with @answer now as its parameter as you state in point 1.

Gr,
Mightor

▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
| To know recursion, you must first know recursion.

deSilva
09-02-2007, 10:04 PM
Mightor! My positive opinion of you is crumbling!
I was referring to the FIRST PARAMETER of COGNEW, which is the address where it loads the COG from. You have to state "@assign" here, not "assign"!
Do I here: "Oh what an idiot I have been"?

Mightor
09-02-2007, 10:10 PM
deSilva said...
Mightor! My positive opinion of you is crumbling!
I was referring to the FIRST PARAMETER of COGNEW, which is the address where it loads the COG from. You have to state "@assign" here, not "assign"!
Do I here: "Oh what an idiot I have been"?

Yeah, I'll just nip out to the back and shoot myself in the head! For some reason by first parameter, I thought you meant the variable that is referenced to by PAR! Stupid me! It should come as no surprise now that my now code works :)

Thanks for your seemingly inexhaustible patience!

Gr,
Mightor

▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
| To know recursion, you must first know recursion.

R Pankau
09-02-2007, 10:47 PM
Mightor,
take a look at your wrlong instruction, it accesses PAR directly and it is not allowed, move Par to a local first and try that, I did the same thing.

▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
I'd rather have a bottle in front of me than a frontal labotomy

deSilva
09-02-2007, 11:07 PM
???

deSilva
09-03-2007, 12:25 AM
Well I won't let my "???" stand alone too long http://forums.parallax.com/images/smilies/smile.gif
PAR behaves as expected as a "typical" shadowed special register
Study this test program ... It's always the same program btw, variants of which I certainly posted 4 or 5 times now...


{
Exploring the shadow registers v02
2007 deSilva
}
CON

_clkmode = xtal1 + pll8x
_xinfreq = 10_000_000

OBJ
pc : "PC_Interface" ' Ariba's PropTerminal

PUB main | parameter
pc.start(31,30)

cntcell := @cntcell
shadow := @shadow
opflag := @opflag

parameter := $1235
cognew(@asm, parameter)


repeat
setpos(0,0)
pc.str(string("Values from shadow cells.. press any key"))
pc.str(string(13, "parameter "))
pc.hex(parameter,8)
pc.str(string(13, "PAR "))
pc.hex(cntcell,8)
pc.str(string(13,"the shadow "))
pc.hex(shadow,8)

if pc.gotKey ' press a key to add to CNT shadow
pc.clearKeys
opflag := 0
waitcnt(clkfreq/20+cnt)


DAT
org 0
asm
mov RA, PAR
wrlong RA, cntcell ' show the real thing
wrlong PAR, shadow ' show the shadow

rdlong RA, opflag WZ
if_Z add PAR, #1 'use shadow
'acknowledge command:
if_Z wrlong opflag, opflag 'write any value as long as it's not 0
jmp #asm

shadow long 0
cntcell long 0
opflag long 0
RA res 1

PRI setpos(px, py)
pc.out(10)
pc.out(px)
pc.out(11)
pc.out(py)

R Pankau
09-03-2007, 12:56 AM
As promised Mightor here is that bit of code



CON
_CLKMODE = XTAL3
_XINFREQ = 20_000_000
samples = 7000

var
long ptr
long time[2]
byte check
byte lockout
long cog,IR_Pin, buffer[7000]

obj serial : "FullDuplexSerial"
pub Main : okay
buf_ptr := @buffer
IR_Pin := 0
ptr := 0
lockout := 0
serial.start(31,30,0,9600)
okay := cog := cognew(@SAMPLE, @IR_PIN)

repeat
check := serial.rxcheck
if check <> 255
ptr := 0
repeat samples
serial.dec(buffer[ptr])
ptr++

dat

SAMPLE org 0

rdlong t1, par
mov IR_pin_mask, #1
shl IR_pin_mask, t1
andn dira, IR_pin_mask

'add t1, #4
'mov buf_ptr, t1

wait mov t2, #0
test IR_pin_mask, ina wc
rcl t2, #1
tjnz t2, #wait
'once data is detected then keep sampling until the buffer is full
mov t3, samp
mov t4, cnt
wrlong t4, buf_ptr

add buf_ptr, #4
look test IR_pin_mask, ina wc
mov t2, #0
rcl t2, #1
xor t2, negate_mask
and t2, #1
wrlong t2, buf_ptr
add buf_ptr, #4

mov Time1, cnt
add Time1, Delay
waitcnt Time1, Delay

djnz t3, #look
mov t4, cnt
wrlong t4, buf_ptr


samp long 6998
negate_mask long $FFFFFFFF
buf_ptr long 0
Delay long 2000
t1 res 1
t2 res 1
t3 res 1
t4 res 1
IR_pin_mask res 1
Time1 RES 1
FIT 496

So is this a professional forum?

This is a hobby for me, no stress, no sarcasm from work, no road-rage from riding my bicycle to work, no yelling at the kids.....Am I in the wrong place?

I guess I should apologize for my slack-jawed-yokel comments about PAR, I was thinking CNT at the time.

▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
I'd rather have a bottle in front of me than a frontal labotomy

Mightor
09-03-2007, 01:07 AM
Thanks Randy :)

▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
| To know recursion, you must first know recursion.

deSilva
09-03-2007, 03:01 AM
There is a difference between biting sarcasm and subtle irony, I think Mightor understands quite well http://forums.parallax.com/images/smilies/smile.gif

Mightor
09-03-2007, 03:56 AM
deSilva said...
There is a difference between biting sarcasm and subtle irony, I think Mightor understands quite well http://forums.parallax.com/images/smilies/smile.gif

I think it is a cultural difference, though. I'm from the Netherlands and you're from Germany (as far as I have managed to ascertain) and we tend to use sarcasm and irony (and even a touch of cynicism) in our every day language. I am not sure if that's the same in the US :)

As for irony :P I found a bug in your Machine Language guide. When specifying multiple flags (like WC and WZ) you need put a "," in between each effect. On page 18, example code ex07[A|B], you forgot these. I came across when I was looking for a way to divide 80_000_000 by 200. The code on page 20 for division won't work for that because the resultant quotient is longer than 16 bits.

Gr,
Mightor

▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
| To know recursion, you must first know recursion.

deSilva
09-03-2007, 04:28 AM
Thank you Mightor - the routines are limited to 16 bit results and unsigned. I copied them from Parallax.
To my knowledge no one has published "true" and "signed" 32-bit routines. I think this has to be done by distinguishing the different cases (<16 bit, >16 bit); signed is simple, just the rules from school.

I have been aware of the "comma" for some time, and I asked myself: "Why does no-one complain?". The error is also in the BNF-Syntax.

The funny thing is that I have never really used a double flag in my whole Propeller life. You need it for every => or =< check with CMPS or even CMP.
But for some reason, the knowledge of > or < seemed always sufficient... Or are there luring bugs in my little programs?

Mightor
09-03-2007, 08:08 PM
K, after much hacking and sawing, this is the code I've come up with to divide a 32bit long by another. One thing kind of puzzles me though and that's these lines:



cmp cpufreq, pwmfreq wc
IF_C add _t, #1



I am not sure why I need it, but when I leave them out, my result is shifted wrongly. Anyway, here's the code. I'd love some feedback as this is basically my first attempt at binary maths from scratch (pretty much from scratch).



{{
Demonstration of binary division in ASM

}}
DAT
propArr Byte "-\|/"

CON
_clkmode = xtal1 + pll16x
_xinfreq = 5_000_000

VAR
long quotient
long freq
long pwm

OBJ
Debug: "SerialMirror"

PUB start
Debug.start(31, 30, 0, 57600)
showWaiting
pwm := 80000
qAddr := @quotient
fAddr := @freq

cognew(@divide, @pwm)
waitcnt(clkfreq + cnt) ' wait a sec for cog to be ready

Debug.str(string("freq: "))
Debug.dec(freq)
Debug.CrLf

Debug.str(string(10,13))

Debug.str(string("quotient: "))
Debug.CrLf
Debug.bin(80_000_000/pwm, 32)
Debug.CrLf
Debug.bin(quotient, 32)
Debug.CrLf
Debug.dec(quotient)
Debug.str(string(10,13))
waitcnt(clkfreq/10 + cnt)

PRI showWaiting | i
repeat i from 0 to 70
Debug.tx(propArr[i//4])
waitcnt(clkfreq / 15 + cnt)
Debug.str(string(27, "[K"))
Debug.str(string(27, "[0;0H"))

DAT
org
divide rdlong pwmfreq, par ' PWM frequency
rdlong cpufreq, #0 ' CPU frequency
wrlong cpufreq, fAddr

:shiftloop
shl pwmfreq, #1 wc, wz ' If the bit 31 was 1, this will be in the C
IF_NZ_AND_NC add _t, #1
IF_NZ_AND_NC jmp #:shiftloop ' If no carry detected or number isn't 0, loop

rcr pwmfreq, #1 ' right shift carry back onto number

cmp cpufreq, pwmfreq wc
IF_C add _t, #1

:divloop cmpsub cpufreq, pwmfreq wc 'if y =< x then subtract it, set C
rcl _q,#1 'rotate c into quotient
shr pwmfreq, #1
djnz _t,#:divloop 'loop until done

wrlong _q, qAddr ' write our quotient to the HUB

:noploop nop ' just loop forever, keeps the cog alive.
jmp #:noploop

_q long 0
qAddr long 0
cpufreq long 0
pwmfreq long 0
_t long 0
fAddr long 0



Please be gentle...

Gr,
Mightor

▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
| To know recursion, you must first know recursion.

Graham Stabler
09-03-2007, 09:30 PM
I love a bit of gentle sarcasm (and some not so gentle) but its not the easiest thing to get across in text, combined with some "unusual wording" it can just come across as rudeness. So I'd just be careful all.

Graham

deSilva
09-04-2007, 12:56 AM
To simplify the debugging of "arithmetic" routines it is generally helpfull to reduce the wordlength to 2. So you have just the numbers 0,1,2, and 3 to occur (unsigned).

Now lets consider the following cases:
A: 2/2
B: 2/3

Both should yield 1 eventually

A:
A1: Shifting the denominator to the left breaks the first loop immediately, leaving pwm = 2 and _t = 1
A2: No _t correction
A3: COMPSUB will leave a carry and the second loop will break, leaving the quotient as 1 -> GOOD!

B:
B1 Shifting the denominator to the left stops he first loop immediately, leaving pwm = 3 and _t = 1
B2: unclear correction of _t as 2<3, leaving _t = 2
B3: COMPSUB will NOT leave a carry in the first loop run, quotient = 0 -> BAD!
B3.1: But you get a second chance:
pwm /= 2
COMPSUB will leave a carry, quotient = 1 -> GOOD!

But this is not an EXPLANATION, just what happens...

Now remember your school math (no sarcasm intended!)
When you divide - say - 12,345 by 13 - how do you start?
NOT devide 12 by 13 - poppycock!
And if you would do that it would require AN ADITIONAL LOOP RUN!
This is exactly the situation you identify by CMP cpu, pwm WC

Normally you would start with 123 by 13.
So to make the algorithm closer to school math I should have coded:



cmp cpufreq, pwmfreq wc
IF_C shr pwmfreq, #1



Incrementing _t however has the same effect.


But this is not yet EXACTLY how you do it in school - you do not blow up the denominator up the highest possible value.
Rather you shift it to a position where it is just somewhat smaller than (or equal to) the counter.

So it would be more transparent (and even faster!) when using a different loop check in the first loop... Something like


cmp cpufreq, pwmfreq wc


perhaps http://forums.parallax.com/images/smilies/wink.gif
Give it a try! But mind the zeroes!

Post Edited (deSilva) : 9/3/2007 6:58:27 PM GMT

Mightor
09-04-2007, 02:19 AM
I went through the Propeller man page and in the section for the CMP command it's a bit unclear what the "WZ" flag does:

Propeller Manual said...

CMP (Compare Unsigned) compares the unsigned values of Value1 and Value2. The Z and C
flags, if written, indicate the relative equal, and greater or lesser relationship between the two.

If the WZ effect is specified, the Z flag is set (1) if Value1 equals Value2. If the WC effect is
specified, the C flag is set (1) if Value1 is less than Value2.


Which is the effect of WZ? Does it set Z when it is "greater or equal" or just when it is equal?

Gr,
Mightor

▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
| To know recursion, you must first know recursion.

Mike Green
09-04-2007, 02:35 AM
Mightor,
Z is set when the two values are equal. CMP does a 2's complement subtraction and Z is set when the result of the subtraction is zero. C is set when there is a carry (borrow) out of the subtraction. If you look at the definition of CMP in the Propeller manual, you'll see that it's a SUB instruction with no result written (NR).

Mightor
09-04-2007, 10:51 AM
Ah I get it now... If you specify WZ and WC and no flags are set after the operation it must've been bigger :) Right?

Gr,
Mightor

▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
| To know recursion, you must first know recursion.

deSilva
09-04-2007, 01:11 PM
There is whole set of mnemonics for it - have a look at the manual p.369 table 5.2



Condition Instruction Synonyms
IF_E if equal IF_Z
IF_NE if not equal IF_NZ
IF_A if above IF_NC_AND_NZ or IF_NZ_AND_NC
IF_B if below IF_C
IF_AE if above or equal IF_NC
IF_BE if below or equal IF_C_OR_Z or IF_Z_OR_C


I am sure I mention this in the tutorial - but I mention so many things, don't I http://forums.parallax.com/images/smilies/wink.gif

Post Edited (deSilva) : 9/4/2007 7:21:50 AM GMT

Mightor
09-04-2007, 01:49 PM
deSilva,

I knew about some of those, they're great. I used them in my code before :) I was just verifying whether I was right wrg to the flags being set/not set and their meaning. I hadn't noticed the "IF_E" and "IF_A" ones though, they're nice and a lot less cryptic looking than IF_Z and IF_NC_AND_NZ. Thanks for pointing them out. I'm at work now but I'll have a look at tweaking that first loop to make use of CMP in a bit :)

Gr,
Mightor

▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
| To know recursion, you must first know recursion.

Mightor
09-05-2007, 01:40 AM
deSilva,

I've been trying to figure out how to setup that shiftloop using your CMP suggestion but I really cannot figure it out. I even took my laptop to work so I could hack it during lunch, all to no avail. I'm afraid I must throw in the towel. Could you please show me what you had in mind?

Thanks,
Mightor

▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
| To know recursion, you must first know recursion.

deSilva
09-05-2007, 03:12 AM
Originally you are shifting to the left up to last possible position.
The new idea is: Just shift the denominator until it is equal or just a little bit below the counter.

When you overdo shifting to the left, your check (CMP) will not work as the important high bit is hiding in the carry! You have to take this also into account

Mightor
09-05-2007, 06:38 PM
deSilva,

I knew what I had to do, it's the execution of the plan that's not going quite as planned :)
This is what I have now:



cmp cpufreq, pwmfreq wc
IF_BE add t_, #1
IF_B shl pwmfreq, #1 wc
IF_B jmp #:shiftloop
IF_C rcr pwmfreq, #1



But this doesn't work because the "add" modifies C and Z, I can't switch around the add and the shl either. Ugh.

Please give me some new hints man, I'm really stuck :P

Gr,
Mightor

▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
| To know recursion, you must first know recursion.
| I'm afraid I might be phobophobic.

Kaio
09-05-2007, 09:51 PM
On the Propeller the flags are only modified if you specify on a instruction to use execution effects. Your add instruction does not use such an effect. So why you are thinking the flags could be changed by add?

Mightor
09-05-2007, 11:12 PM
I didn't know flags could be "inherited" from a previous instruction if the following function could potentially modify the flags too.

Gr,
Mightor

▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
| To know recursion, you must first know recursion.
| I'm afraid I might be phobophobic.

Kaio
09-05-2007, 11:53 PM
A flag will be changed only on an instruction if you use the WC or WZ effect unlike other microcontrollers. This is clearly described in the manual for each instruction.

In your code snippet this occur on the

"cmp cpufreq, pwmfreq wc"

and

"IF_B shl pwmfreq, #1 wc"

instructions.

The "IF_B" on the shl instruction depends on the result of the cmp instruction before. The C flag is only changed by the shl instruction if C flag was set before otherwise the shl instruction is treated as NOP.

Mightor
09-06-2007, 12:26 AM
Kaio,

If I managed to absorb and understand all the information that was in the Propeller Manual I probably wouldn't be on this forum asking my questions. :) There's a serious amount of information in there to digest. For some reason I thought they were just cleared if the instruction was not a NOP. I thought the WZ and WC flags were there to allow them to be set if you chose to.

In any case, I managed to figure it out. This is what I have now:



:shiftloop
cmp pwmfreq, cpufreq wc, wz ' Compare pwmfreq and cpufreq
IF_A jmp #:exitshiftloop
IF_BE add _t, #1
IF_B shl pwmfreq, #1 wc, wz
IF_NZ_AND_NC jmp #:shiftloop

:exitshiftloop
IF_A rcr pwmfreq, #1
wrlong _t, tAddr ' For debugging purposes



Is the IF_A jump right after the cmp something that would be considered and OK way to get out of the loop or is this a little unaesthetic in ASM? I hope I didn't cause some of the more hardcore ASM programmers to get a little nauseous reading my newbie code.

Gr,
Mightor

▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
| To know recursion, you must first know recursion.
| I'm afraid I might be phobophobic.

deSilva
09-06-2007, 12:33 AM
This is what a certain deSilva has written some monthes ago:
said...

..... we have to understand how these two flags are set, reset, or left unchanged. Every experienced assembly programmer knows that this can be a nightmare! A small instruction inserted for some reason in a chain of instructions can destroy a clever and efficient algorithm build on a flag. 8080 instructions are extremely clever, in that 8-bit instructions generally influence flags and 16-bit instructions don’t – with exceptions…

Now there is good news! You can forget all past problems, as a Propeller instruction will only influence a flag, if you tell it so! This is indicated by some “postfix” notation: you write WC (“with carry flag”) or WZ (“with zero flag”) at the end of the instruction.

Now you only have to memorize in what situation an instruction CAN set any flag (if it is allowed to do this). Some basic rules are:

- Moves, arithmetic, and logical instructions change Z whether the result is zero or not.
- Arithmetic instructions change C according to an “overflow”
- Logical instructions set C to form an even parity of all bits.
For the rest of instructions this is more complex http://forums.parallax.com/images/smilies/smile.gif

Mightor
09-06-2007, 12:39 AM
My excuse is that I am neither experienced in ASM nor was I ever an 8080 ASM programmer :) I think you even put that passage in your tutorial, yeah, page 15, just checked. When you pasted it like that I instantly recognised it.

But back to my little piece of new code... Is this what you meant, deSilva?

Gr,
Mightor

▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
| To know recursion, you must first know recursion.
| I'm afraid I might be phobophobic.

Mightor
09-08-2007, 09:57 PM
This is my final test program for long division in ASM. Thanks to deSilva for helping me iron out bugs and everyone else who responded.

Gr,
Mightor




{{
Demonstration of binary division in ASM

}}
DAT
propArr Byte "-\|/"

CON
_clkmode = xtal1 + pll16x
_xinfreq = 5_000_000

VAR
long quotient
long pwm
long t

OBJ
Debug: "SerialMirror"

PUB start
Debug.start(31, 30, 0, 57600)
showWaiting
pwm := 80_000
qAddr := @quotient
tAddr := @t

cognew(@divide, @pwm)
waitcnt(clkfreq + cnt) ' wait a sec for cog to be ready

Debug.str(string("freq: "))
Debug.dec(clkfreq)
Debug.CrLf
Debug.str(string("pwm: "))
Debug.dec(pwm)
Debug.CrLf
Debug.CrLf

Debug.str(string("quotient: "))
Debug.CrLf
Debug.str(string("SPIN: "))
Debug.bin(clkfreq/pwm, 32)
Debug.CrLf
Debug.str(string("ASM: "))
Debug.bin(quotient, 32)
Debug.CrLf
Debug.str(string("SPIN: "))
Debug.dec(clkfreq/pwm)
Debug.CrLf
Debug.str(string("ASM: "))
Debug.dec(quotient)
Debug.CrLf
Debug.str(string("_t: "))
Debug.dec(t)
waitcnt(clkfreq/10 + cnt)

PRI showWaiting | i
repeat i from 0 to 70
Debug.tx(propArr[i//4])
waitcnt(clkfreq / 15 + cnt)
Debug.str(string(27, "[K"))
Debug.str(string(27, "[0;0H"))

DAT
org
divide rdlong pwmfreq, par ' PWM frequency
rdlong cpufreq, #0 ' CPU frequency

cmp pwmfreq, #0 wz ' Check for division by 0
IF_Z mov _q, #0 ' Make quotient 0 in this case
IF_Z jmp #:divret ' Go to exit

cmp pwmfreq, cpufreq wc, wz ' Check pwmfreq > cpufreq
IF_A mov _q, #0 ' Make quotient 0 in this case
IF_A jmp #:divret ' Goto exit

:shiftloop
cmp pwmfreq, cpufreq wc, wz ' Compare pwmfreq and cpufreq
IF_A jmp #:exitshiftloop ' pwmfreq is greater than cpufreq, so exit
IF_BE add _t, #1 ' increment _t counter
IF_B shl pwmfreq, #1 wc, wz ' shift pwmfreq to left, track C and Z
IF_NZ_AND_NC jmp #:shiftloop ' if no carry and pwmfreq != 0, keep going

:exitshiftloop
IF_A rcr pwmfreq, #1 ' If pwmfreq was shifted too much, shift carry back

:divloop cmpsub cpufreq, pwmfreq wc ' if y =< x then subtract it, set C
rcl _q,#1 ' rotate c into quotient
shr pwmfreq, #1 ' Shift pwmfreq to right
djnz _t,#:divloop ' loop until done

:divret mov _t, #255 ' for debugging purposes, to check if we're not
wrlong _t, tAddr ' looping infinitely
wrlong _q, qAddr ' write our quotient to the HUB

:noploop nop ' just loop forever, keeps the cog alive.
jmp #:noploop

_q long 0
qAddr long 0
cpufreq long 0
pwmfreq long 0
_t long 0
tAddr long 0


▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
| To know recursion, you must first know recursion.
| I'm afraid I might be phobophobic.