This problem has little to do with assembly language, or at least the lions share of the problem. You should prototype this in C or whatever your favorite language is then simply convert that to another language (assembly in this case).
if you remember in grade school if we want to add 999 + 1
we are going to have a lot of the "carry the one" going on
Now what was perhaps not defined in grade school is "carry in" and "carry out". the thing that you carry the 1 to the next column is the carry out. when you add that thing that is carried over into the current column then that same number is now called carry in, for this column.
b is carry in, a is carry out, the operands are c and d. e is the result, for that column. we did learn in grade school that we could add infinitely large numbers because we only needed to learn to add one column at a time and repeat that infinitely. Note that the original problem above the carry in on the first column is blank, it is an implied zero. zero plus 9 plus 1 equals "0 carry the 1", 0 is the result of 9+1 and 1 is the carry out.
So now do 999 + 1 but put an artificial limit that we can only add two columns at a time, maybe our paper is only wide enough for two numbers, who knows (the processor has register that have a fixed limit in the width, in your case 16 bits, or 16 columns wide).
we know the answer will look like this with four columns,
two columns at a time
00 + 01 +
do the least significant columns first with a zero for carry in. then whatever carries out of that use as the carry in to the next two columns, we can repeat this infinitely for infinitely large numbers.
in assembly language you often (but not always) have an ADD and an ADC instruction of some flavor, add without carry and add with carry, the normal add has an implied zero carry in and ideally has a carry out to a carry flag in some processor status register somewhere. then if there is an add with carry instruction you do the rest of the higher order additions with add with carry, the add with carry uses the carry flag as the carry in, and puts the carry out in the carry flag, so you can cascade the additions.
clear as mud?
subtraction, there is no real subtraction in a computer, there is a reason for the crazy twos complement stuff you learned in beginner programming class. negating a number using twos complement was "invert and add 1". now think about our add operation, an add adds two numbers with a carry in. what if you were to invert the second operand, and then put a 1 in the carry in? that is "invert and add 1", right? if I want to do the math 5 - 1 that is the same as 5 + (-1) which is the same as 5 + invert(1) + 1 right? fits perfectly into the ADD logic.
different instruction sets do different things with the carry out bit for subtract operations, basically on the way into the adder a subtract means invert the second operand and invert the carry in, then do a normal add, on the way out though some processor families invert the carry out bit if the operation was a subtract, some do not. You may or may not have to figure that out depending on what you do with the carry bit after the operation (conditional branches for example jump if carry set, jump if carry not set), in this case as the other poster showed, some instruction sets have a subtract with borrow. Just like add then a series of addc operations to cascade an infinitely large addition, you can use sub and sbb to cascade an infinitely large subtract.
multiply...fortunatly the 8086 makes this a little easier but the overall property is the same anywhere. If you look at the numbers (in binary) if you multiply two numbers that are x bits wide, the result ideally needs to be 2*x wide, so to properly multiply any two 16 bit numbers you need a 32 bit result. if your hardware can only do 16bit * 16bit = 16bit then to easily cascade that multiplier zero the upper 8 bits and pretend to do 8bit * 8bit = 16bit...
we ended up with four numbers that were added together right? I am using hhhh to represent abcd*h and gggg to represent abcd*g, etc.
when we did even two column multiplies
that was broken into four multiply steps
h * d
h * (c*10)
(g*10) * d
(g*10) * (d*10)
which using a math property are
h * d = h*d
h * (c*10) = 10 * (h*c)
(g*10) * d = 10 * (g*d)
(g*10) * (d*10) = 100 * (g*d)
multiplying by 10 in base 10 is just shifting a column, times 100 is two columns and all those items are added up. So if you break up your multiply operands into digestible pieces and keep track of how much you need to shift each item, which basically lands that item in a particular column, using our infinitely wide addition we are already breaking things in to columns or groups of columns. yes it becomes an interative process as above we have four things to add and our cascaded addition only adds two things so you have to do 3 wide additions, 1) the first two operands, 2) the result of 1) plus the third operand 3) the result of 2) plus the last operand
say each of these letters is a 16 bit number
the only multiply pair above that doesnt have some flavor of shift by a multiple of 16 associated with it (think multiply by 10 or 100 or 1000 in decimal) is the h*d.
so the lower 16 bits of the result are the lower 16 bits of h*d. the upper 16 bits of h*d have to be added into other stuff though
the next layer is h*c<<16 and g*d<<16. the lower 16 bits of each of those is added together and also added to the upper 16 bits of d*h. using the two letter combinations to represent their multiplication result
In other words abcd * efgh =
the lower 16 bits of h*d which is represented as hd above is added to just zeros and goes right into the result the upper half of h*d and the lower halves of h*c and g*d are added to become the next 16 bits of the result, and so on.
if you want to multiply two 64 bit numbers using 16*16=32 bit multiply operations and 16*16=16+carry addition operations you can hardcode based on the above.
If you want to limit the result to 64*64=64bits rather than 64*64=128bits you can cut that in half
d000 + (e*d)
I will leave division for you to figure out...
Implement this in a high level language first using basic operations add and subtract, synthesize the carry bit
make an add functoin
unsigned int a,b,c,result,carry;
a = operand1&0xFFFF;
b = operand2&0xFFFF;
c = a+b;
result = c&0xFFFF;
carry = (c>>16)&1;
and an add with carry function
//add with carry
c = a+b+carry_in;
result = c&0xFFFF;
carry = (c>>16)+1;
or if you want to be like the hardware only make the add with carry function and for the add step feed a 0 for carry in, otherwise feed the carry out.
clear as mud?
x86 may for example invert the carry out on a subtract and maybe arm doesnt. Some processors dont have flags at all (mips) and you have to synthesize all the above using smaller than register sized numbers (using 32 bit registers to do 16 bit addition allowing a place to save the 17th bit, the carry out, or maybe do 31 bits at a time, whatever). Just like some processors you have to use half the size of the multiply on the inputs (zero the top half) to get an unclipped answer on the result, and do the above game to properly do the full width multiply.