Next come all the arithmetic instructions. It would not be much of a computer if it were unable to do any computations. People not familiar with assembly language would be amazed if they knew how much code relied on arithmetic in real machines.
The first group of instructions add two values together. For example ADR adds the value of a register to the value in the accumulator and stores the result in the accumulator, updating the flags as it goes.
Note that if an addition results in a value which is too large for the accumulator, the carry flag has to be set. Since setflags() does not touch the carry flag, this has to be done specially by the ADR function itself. To determine whether a carry has resulted, a rather strange computation is performed. One looks at the result of the addition and sees if it is SMALLER than one of the values started with. The only way this can happen is if there is a carry, and will always happen if there is.
The remaining addition instructions are just variations on a theme, with instructions like ARC performing an addition of two values and adding an additional 1 if the carry flag is set. It is designed to be executed after an instruction like ADR. We take a little risk here in assuming that MC->C will have the value 1 if the condition checked when assigning the carry flag was true. This is somewhat compiler dependent, but it is usually fairly safe to assume that true is represented by a 1 by most compilers, though there are exceptions. Since our code is written for the Pelles C compiler, we simply make the dangerous assumption.
Next come the subtraction instructions which are very similar to the addition ones, with the carry flag being used to signify a borrow.
It is somewhat annoying having so many versions of the same instruction, however, since we limited ourselves to instructions with single operands, this is necessary if our machine is to have a decent range of functionality.
The next instructions are the multiplication and division instructions. There are numerous difficulties here. Firstly, multiplications and divisions differ depending on whether signed or unsigned quanties are involved. Thus there are two different instructions for each. Fortunately C handles signed and unsigned values separately, so we don't need to be to concerned ourselves, over the implementation of this at the binary level.
The second problem is the fact that if you multiply two 16 bit values, you are liable to get a 32 bit value, and our registers are only 16 bits wide. Thus we have to use two of them to represent the results.
Division is complicated, and the way we have implemented it is not very standard. But we want to avoid overflows, so we only allow a 16 bit value divided by a 16 bit value. This is necessary if we don't want more than one operand, unless we are prepared to set another of our registers apart to store the higher order part of a 32 bit operand. But we have chosen not to allow division of a 32 bit value by a 16 bit value. Note that the way things are set up, if our machine performs a divide by zero, an exception will be raised, and our emulator will crash. This is because an actual division by zero will result in the real world processor, causing the exception. We could get around this by checking for a zero and handling it separately inside our simulated machine. However, we've avoided doing things that way, for simplicity's sake.
As compensation for not offering a 32 bit by 16 bit division, we offer separate instructions for computing the remainder after division. C also offers remainder instructions in this way, which is convenient for us in this case. Actually, offering separate remainder instructions is not very efficient, but if we had wanted complete efficiency all along, we would have used assembly language instead of C for this whole project.
Note that the operation /, is C's integer division operation (if two integer values are divided with it). It simply gives you the integer part of any quotient. The remainder after such a division, is given by C's % operation.