The Assembly Language: Part III

Now so far all of our instructions keep the program moving forward one instruction at a time. But such programs can't do very much. Using just these would be like writing C programs with no while loops or if...else constructions. To provide flow in assembly language, we have the control instructions, which are designed to allow a program to branch off to a different location based on a particular decision that is made, or to call a subroutine and return, etc.

Most of the time in assembly language, you want a program to branch off based on how two values compare. For example, we might have a loop in which a register keeps track of which iteration we are on. It might have 1 subtracted from it each time the loop executes and at the end of the loop we want to see whether we should jump back to the start of the loop and execute it again, or whether the loop should finish and the rest of the program execute.

To perform a check such as whether a loop counter has reached zero, we might compare the loop counter with the immediate value 0 and see if they are equal yet or not. Thus we need comparison instructions.

The way a comparison instruction works, is to perform an invisible subtraction of the two values on the side, and update the flags as though that subtraction had actually been performed. If the two values were the same for example, the result of the subtraction would be zero and so the zero flag would be set. If the first value was bigger than the second, the result would be positive, and so the P flag would be set. If the second value was bigger, the result would be negative, and so the carry flag would be set (to signify a borrow).

Now we wish to jump to a particular location in memory, depending on the results of this comparison. This is done by making use of one of the conditional jump instructions, JE, JNE, JA, JNA, JB, JNB, JC or JNC. Each of these instructions just checks the appropriate flags and makes a decision whether to jump or not, based on their values. Therefore, the typical way to make a conditional jump is to have a compare instruction followed by a conditional jump instruction.

If we want the program to just jump unconditionally, we use the JMP instruction, which has no condition associated with it.

Finally to call a subroutine, we use the CALL instruction, followed by the address of the subroutine we want to call. This instruction automatically puts the address of the location we were up to, on the stack, before jumping to the subroutine itself. At the end of the subroutine, the RET instruction is used. It automatically fetches this address back off the stack and jumps back to that location to continue execution of the part of the program where the call instruction was located.

Some final instructions that one might implement (but which aren't at present) are the IN and OUT instructions. These might be used to tell the machine to get input from the keyboard, hard disk, or output data to the screen, or a file or whatever. There is also an instruction called NOP, which purposely does nothing. It can sometimes be useful to have such an instruction, and obviously it takes no work to implement.

The function of the HLT instruction should be fairly obvious. It simply terminates execution of the program.

The machine does one more thing for us. If we write to a memory location with address A000 or above, it writes a character to the screen. Basically one thinks of the memory locations at A000 and above as screen memory.

The screen memory is divided up into many two byte blocks. The first byte in each block represents the attributes (i.e. foreground and background colours) of the text character to be written, and the second byte of each block represents the ASCII value of the character to be written. The machine is set up so that if we write to a character location or an attribute location on its own, nothing happens. We must write to the screen memory, two bytes, or 16 bits at a time, specifying the attribute and the character to be written simultaneously. Of course the memory is laid out so that the first load of locations represent the characters on the first line of text on the screen, in order, then the next lot of locations represent the second line of text, and so on, right down to the memory location which represents the lower right character of text from the last line on the screen.