View Full Version : CMPSUB: a rather odd duck (not so odd, after all -- sorry for the trouble)

Phil Pilgrim (PhiPi)
04-02-2009, 03:17 AM
The cmpsub assembly instruction can be very useful, but there are a couple gotchas that can trap the unwary:

1. Even though it's a subtract instruction, it defaults to nr. You have to specify wr for the subtraction to take place.

2. The sense of the carry flag is the opposite of all the other compare and subtract instructions, in that it's set iff D > S (rather than D < S, as the others do).

Unless there's just an error in the docs, these make cmpsub a bit of an anomaly. I don't want to second-guess Chip, since the instruction set as a whole is so elegant and logical; but it would be interesting to know what prompted these two design decisions.

One thing that #2 makes possible (assuming the docs are correct) is a sure-fire way to set the carry flag without any other side effects:

cmpsub any_register,#0 wc


Addendum: Apparently #1 is the result of an error in the manual. wr is the default. The carry flag behaves as specified. It's this way to keep the division routine as short as possible.

Post Edited (Phil Pilgrim (PhiPi)) : 4/2/2009 4:04:59 AM GMT

04-02-2009, 04:07 AM
Manual says:
CMPSUB compares the unsigned values of Value1 and Value2, and if Value2 is equal to or
greater than Value1 then it is subtracted from Value1 (if the WR effect is specified)

So, it's only subtracting if source is smaller than destination. That's nice for calculations where the result should definitely not be negative.

04-02-2009, 04:14 AM
For example there is a simple but slow algorithm for calculating square roots:

Subtract ever increasing odd numbers, 1, 3, 5, 7... from your input number until you can't subtract any more without going negative. The number of subtractions performed is the square root of your input.

For me, the past is not over yet.

Phil Pilgrim (PhiPi)
04-02-2009, 04:34 AM
Yes, yes: conditional subtraction is what the instruction is all about. It's also useful for managing index wraparound for circular buffers. But the thrust of this thread was meant to point out the default nr and the odd sense of the carry flag, to question whether the documentation is correct on these two issues and, if so, to probe the logic behind this apparent departure from instruction set regularity. That's the part I just don't quite get...


Post Edited (Phil Pilgrim (PhiPi)) : 4/1/2009 9:41:09 PM GMT

04-02-2009, 08:04 AM
Please, please, PLEASE read the Erratta as CMPSUB in the Manual is just plain wrong. (Why the Errata isn't integrated into the Manual is beyond me. Sure, have an Errata for those with printed versions of the manual, but there's no reason not to update the softcopy.)

CMPSUB defaults to WR! C is set if Dest is LESS THAN Source (i.e. same as opposite from SUB).

CMPSUB is really cool for circular counters and division code.

Post Edited (ericball) : 4/12/2009 11:56:06 PM GMT

Phil Pilgrim (PhiPi)
04-02-2009, 08:17 AM
Oh, phew! My faith in the instruction set has been restored! And you're right: I should've scanned the errata before posting. http://forums.parallax.com/images/smilies/sad.gif

····One reason I did not is that I found the same info in two independent sources. (Or maybe they weren't so indpendent after all.)

Thanks for the correction!

Post Edited (Phil Pilgrim (PhiPi)) : 4/2/2009 1:27:07 AM GMT

Phil Pilgrim (PhiPi)
04-02-2009, 11:00 AM
Geez! This thread has become a veritable comedy of errata! What was the lesson from the cold war? Oh, right: trust but verify.

Here's the real dope: wr is, indeed, the default (as shown in the Errata), but carry is set if D > S (not the same as cmp and sub), as the manual correctly states. There's a very good reason for this, too, which I had momentarily forgotten. It helps to keep the divide routine more compact, viz (from "Propeller Guts"):

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, quotient bit into c
rcl x,#1 'rotate c into quotient, shift dividend
djnz t,#:loop 'loop until done
divide_ret ret 'quotient in x[15..0], remainder in x[31..16]

I really do hope this settles it and rather wish I could just delete the whole thread.