June 1983 © BYTE Publications Inc
Thomas W. Starnes
Motorola Inc., Microprocessor Division
3501 Ed Bluestein Blvd.
Austin, TX 78721
Last month (May BYTE, page 342), I discussed the data-movement, arithmetic, and logic instructions of Motorola's MC68000 family of microprocessors (sometimes referred to as MACSS -- Motorola's Advanced Computer System on Silicon). I examined a useful set of instructions based on a philosophy of orthogonality, which eliminates duplication of effort by similar instructions (thus making the microprocessor easier to understand and use). In this final part of the series, I will discuss branching, jumping, error-trapping, supervisor-mode, and other advanced instructions of the MC68000.
Data-movement, arithmetic, and logic instructions do most of the computational work in programs, but computers would be little more than adding machines without program-control instructions. These instructions give computers the capability to make decisions by executing nonsequential areas of code based on conditions tested at the time of execution. Branch instructions enable the microprocessor to transfer control to portions of code relative to the instruction being executed -- that is, to transfer control to the effective address, which is the sum of the current contents of the program counter and a given offset. You use branch instructions extensively when writing position-independent code. Jump instructions differ from branch instructions in that the jump instructions transfer control to absolute locations in memory, are unconditional, and can use any of the MC68000 addressing modes to specify the destination.
The MC68000 has a flexible conditional branching instruction, referred to as a Bcc instruction, in which the letters cc denote a variety of conditions that can be specified. There are 14 different conditions, including such things as greater than (BGT), less than or equal to (BLE), equal (BEQ), overflow (BVS), and low or same (BLS); a complete list is given in table 1. The BRA instruction is not conditional but always forces the branch to occur.
Table 1: Conditional tests for the Bcc and DBcc groups of instructions. By substituting the letters in the first column for the letters cc, you can construct as many as 16 Bcc (branch on condition) and DBcc (test condition, decrement, and branch) instructions; for example, BHI branches if both the carry and zero bits in the status register are cleared. The third column indicates that the branch will take place if the expression evaluates to "true"; "~" indicates a logical NOT operation, "^" indicates a logical AND operation, while "v" indicates a logical OR operation. The same conditions are available to another instruction group, Scc, which sets or clears all the bits of a given byte based on the condition being evaluated.
Mnemonic Condition Description Flags Tested T true 1 F false 0 HI high ~C^~Z LS low or same C+Z CC carry clear ~C CS carry set C NE not equal ~Z EQ equal Z VC overflow clear ~V VS overflow set V PL plus ~N MI minus N GE greater or equal (N^V)v(~N^~V) LT less than (N^~V)v(~N^V) GT greater than (N^V^~Z)v(~N^~V^~Z) LE less or equal Zv(N^~V)v(~N^V)
You cause branching by the addition of some value to the program counter (PC). Branch instructions include an 8- or 16-bit signed displacement value that you add to the program counter. Because the displacement is a signed number, it can cause either a forward or backward branch. If the condition being tested evaluates to "true," the MC68000 will take the branch; if it is not, it will execute the next instruction in sequence.
Even though all MC68000 instructions are multiples of 16 bits and must be aligned on word boundaries (i.e., start on even addresses), the MC68000 interprets the displacement in all branching operations to be a byte count, not a word count. This is done to give the machine maximum flexibility, while still providing the most opportunity for future growth. Limiting the machine to word offsets would have prevented any future MC68000-family processor from having instructions that could be misaligned (i.e., did not start on a word boundary) or be multiples of 8 bits. Since the flexibility to have misaligned instructions still exists, it makes sense to follow the natural byte-oriented addressing of the MC68000. A 16-bit offset gives an addressing range of -32,768 bytes to +32,767 bytes, not the puny 8-bit computer range of -128 to +127 bytes.
Versions of the jump and branch instructions also exist for subroutine calls. You can branch to a subroutine (BSR) with a displacement value, or you can jump to the subroutine (JSR) by specifying the absolute address. Subroutine calls save the return address (the current value of the program counter) on the system stack before transferring control to the subroutine; the return address is removed from the stack and restored to the program counter when the MC68000 executes the RTS (return-from-subroutine) instruction.
Sometimes you will want to save and restore the condition codes that existed just before the subroutine was called. This is easy enough to do with the MOVE SR,-(A7) instruction, which pushes the status register onto the system stack (pointed to by register A7). You can also save selected registers with a single MOVEM instruction (discussed last month). At the close of the subroutine, you can use a MOVEM instruction to restore the saved registers and then use an RTR instruction (return and restore condition codes) to return and restore the saved condition codes in one operation.
Many times, a backward branch is used to create a programming loop, which is a very important part of programming because it allows operations to be repeated until a desired state or condition is reached. Although the looping constructs that most people are familiar with provide for a loop that ends with a given condition or one that ends after a certain number of iterations, a loop that can end by either means is often very useful. The double condition allows a loop to be performed until a given condition is met while ensuring that the loop does not process invalid data (in the case of, say, a string operation that reaches the end of the data without meeting the condition) or run forever (in the case of a numerical analysis algorithm that never converges to a given minimum tolerance).
The MC68000 has just the instruction for this kind of loop. The decrement-counter-and-branch-conditionally instruction (DBcc) uses any data register as a counter and branches based on both the evaluated condition and the data-register value. A DBcc instruction causes the following sequence of events. First, the MC68000 checks to see if the stated condition is met; if so, execution continues with the next instruction, thus ending the loop. If the condition is not met, the specified register is decremented by 1. If the resulting value is -1, the loop is again ended by having the execution continue with the next intruction; otherwise, the branch to the top of the loop occurs.
Note that the DBcc instruction tests the register for a value of -1. At first, this might seem odd, but there is a very good reason for it. Most looping constructs require extra steps to ensure that the loop can execute zero times when needed and that the loop tests for the desired condition before executing a given iteration. By having the loop entered just before the DBcc instruction (at the end of the loop) and by designing the DBcc instructions so that they end the loop on a value of -1 instead of 0, you create a loop that meets both of the above conditions without being burdened by an explicit second test. As an added bonus, a simple conditional branch instruction (using the same condition as the DBcc instruction) enables you to determine whether the program exited the loop because of the iteration counter or the condition.
The DBcc instruction provides a huge set of string operations, especially in conjunction with the predecrement and postincrement addressing modes. By using the appropriate MOVE instruction, for example:
MOVE Dn,(An)+; MOVE (An)+,(An)+; MOVE -(An),-(An); or MOVE (An)+,-(An)
followed by a DBRA instruction, you can have the MC68000 fill a block of memory, copy strings, and reverse strings. CMPM -(An),-(An) with DBNE compares two strings, while CMP Dn,-(An) with DBEQ searches a string for a pattern match. (See part 2, May BYTE, page 342, for an explanation of this address mode notation.) Multi-instruction loops can make very powerful string operations quite simple.
You can see the real beauty of the DBcc instruction in the assembly-language program of listing 1, which translates a string of characters until a terminating character shows up or the end of the string is reached. Register D3 has the string length in it, while register D4 contains the terminating character you're looking for. Register A1 points to the string, while the translation table (which is found some distance from this code) has its location placed in register A2. The routine in listing 1 runs very quickly and demonstrates the power that results from a combination of versatile instructions and various addressing modes.
Listing 1: A string translation program that uses the DBEQ instruction to end a loop based on either of two conditions: end of string (as determined by the string length, given in register D3) or discovery of a termination character (stored in register D4). This program translates a string character by character according to the character values stored in TABLE. For a given character, its value (stored in register D0) is used as an index into TABLE (pointed to by register A2); the actual translation takes place at LOOP.
MOVEQ #$13,DR load termination character into register MOVE #COUNT,D3 load string length MOVE.L #STRING,A1 load string beginning LEA *+TABLE,A2 offset to conversion table CLR D0 prepare index for word BRA POOL start translation LOOP MOVE.B 0(A2,D0),(A1)+ translate and store result POOL MOVE.B (A1),D0 load next character CMP.B D4,D0 termination character found? DBEQ D3,LOOP if not and not end of string, branch Execution time where n bytes are translated: 72 + (40 * n) clocks = 649 µsec for 128 bytes at 8 MHz
Many high-level languages, such as Pascal, use sophisticated programming concepts that can be enhanced by the use of reentrant and recursive programming and subroutines with local variable areas. The MC68000 has the facilities to support these techniques.
You can enter reentrant code at any time by several execution processes, and it will return correct results to all of them. This is very important for interrupt routines that may interrupt themselves before completion. Only reentrant code will correctly execute the interrupt routine the second time, then return to its interrupted version and correctly execute it. The MC68000 instruction set makes reentrant programming easy.
Recursive programs are those that can call themselves. An example of such a program might draw a straight line between two points by repeatedly plotting the midpoint of the line, then calling itself to operate on the two line segments created by the new point. Recursive programs are created to solve complex algorithms with relatively small amounts of code. Their disadvantages include slow execution and heavy use of the stack (or some other area) for storing each level's set of temporary variables. Of course, the MC68000 designers included special instructions to make this task easier, LINK and UNLK (unlink).
LINK and UNLK allow subroutines to allocate part of the stack for the storage of local variables quickly and easily. Often, a programmer needing to refer to variables associated with a given subroutine call will decrement the stack pointer to reserve an area of memory for such use ("decrement" because stacks usually "grow" downward in memory) and save the address of the top edge of this area as a reference point; this address is called a frame pointer (FP) and is a value that, on the MC68000, is stored in one of the seven address registers A0 through A6. The stack pointer, of course, will move up and down during the execution of a subroutine as stack operations are performed. The stable frame pointer always gives a good reference point to the variables, while the stack pointer would give a wildly varying reference to those same variables. Now let's look at a good method for going into a new routine.
To show how the LINK and UNLK instructions help give the programmer access to local variable areas, let's look at the example of figure 1. (Remember that the frame pointer is actually an address register that is designated by the programmer for this use.) Assume that you are in subroutine A, which has its own local variable area, pointed to by the frame pointer. Before subroutine A calls subroutine B, it first places parameters on top of the stack; see figure 1a. After the subroutine call to B, the return address to A is pushed onto the stack (figure 1b). The LINK instruction contains the name of an address register that is to be taken as the frame register and a displacement that indicates the amount of memory to be saved for local variables. When it is executed, three things happen (figures 1c-1e): the contents of the frame pointer (pointing to a stack location containing the previous frame pointer) are pushed onto the stack, the frame pointer itself is made identical to the stack pointer, and the stack pointer is changed by the displacement given in the instruction. (The displacement is a signed value and must be negative to save local variable space -- if it is positive, you will lose information from the stack.) As shown in figure 1e, the stack pointer points to the top of the stack, and the frame pointer points to one word below the subroutine B local variable area. When the UNLK instruction is executed, the process is reversed (figure 1f), leaving subroutine B ready to execute an RTS instruction and return control to subroutine A.
Figure 1: Use of the LINK and UNLK (unlink) instructions, both of which help the assembly-language programmer manage memory areas to be used for local variables in subroutines. See text for details.
(A) (B) BEFORE AFTER EXECUTION EXECUTION OF JSR B OF JSR B / / / / | | | | | | | | |-----------------| |-----------------| | | | | | | | | | | | | | | | | |-----------------| |-----------------| | | | | | | | | |-----------------| |-----------------| | | SP -> | RETURN ADDRESS | | | | TO SUBROUTINE A | |-----------------| |-----------------| SP -> | PARAMETERS FOR | | | | SUBROUTINE B | | | |-----------------| |-----------------| | SPACE FOR | | | | SUBROUTINE A | | | | LOCAL VARIABLES | | | |-----------------| |-----------------| FP -> | PREVIOUS FP | FP -> | | |-----------------| |-----------------| | | | | | | | | / / / / (C) (D) AFTER AFTER STEP 1 STEP 2 OF LINK OF LINK INSTRUCTION INSTRUCTION / / / / | | | | | | | | |-----------------| |-----------------| | | | | | | | | | | | | | | | | |-----------------| |-----------------| SP -> | POINTER TO | SP -> | POINTER TO | | SUBROUTINE A FP | FP -> | SUBROUTINE A FP | |-----------------| |-----------------| | RETURN ADDRESS | | RETURN ADDRESS | | TO SUBROUTINE A | | TO SUBROUTINE A | |-----------------| |-----------------| | | | | | | | | |-----------------| |-----------------| | | | | | | | | | | | | |-----------------| |-----------------| FP -> | | | | |-----------------| |-----------------| | | | | | | | | / / / / (E) (F) AFTER AFTER STEP 3 UNLK OF LINK INSTRUCTION / / / / | | | | | | | | |-----------------| |-----------------| SP -> | SPACE FOR | | | | SUBROUTINE B | | | | LOCAL | | | | VARIABLES | | | |-----------------| |-----------------| FP -> | POINTER TO | | | | SUBROUTINE A FP | | | |-----------------| |-----------------| | RETURN ADDRESS | SP -> | RETURN ADDRESS | | TO SUBROUTINE A | | TO SUBROUTINE A | |-----------------| |-----------------| | | | | | | | | |-----------------| |-----------------| | | | | | | | | | | | | |-----------------| |-----------------| | | FP -> | | |-----------------| |-----------------| | | | | | | | | / / / /
Most microprocessor operations deal either with data or program control. Most also use memory addresses and, in the case of the MC68000, have some rather sophisticated means of generating those addresses. But the addresses are used by the instruction only to get to the data or program location; the address itself is never available to the programmer and is often lost by the end of the instruction. However, it is sometimes the address itself that you need in your program. The MC68000 has two instructions that help you to get just the address, without using it to fetch any data. By having the MC68000 calculate the address itself (instead of writing a sequence of assembly-language instructions to do the same), you can do the calculation much faster without tying up either memory or registers.
The load-effective-address (LEA) and push-effective-address (PEA) instructions calculate a given effective address and place it either in any address register (LEA) or on the stack (PEA). You can calculate the effective address by using any available addressing mode with any of the appropriate registers. The LEA and PEA instructions can be useful when you are running position-independent code. Sometimes to take advantage of an addressing mode that runs more quickly than, say, the programcounter-relative addressing mode, you may want to calculate the address using LEA and access that area of memory by addressing indirectly through the address register in which the LEA instruction left the calculated address. PEA and LEA are also useful for passing pointers of data to other routines or placing them in memory. Sometimes, it's helpful to verify that an effective address is correct or at least in range. Without these two instructions, it would be extremely difficult to use processor-generated addresses.
Systems with more than one microprocessor running at a time are often designed to share some resources, e.g., memory, buffers, I/O, tasks, and so on. For this to happen, the programs running on the microprocessors must have a secure method of determining which processor has rights to a certain part of memory, a buffer, I/O, or a task. The MC68000 has an instruction, TAS (test and set), that makes such allocation of resources between multiprocessors simple and secure.
The key to this instruction is that it is indivisible, i.e., it can lock out all accesses to the designated addressing location until work on the location is complete. The test-and-set instruction tests a given byte, sets the negative (N) and zero (Z) status register bits accordingly, and then sets the most significant bit of the byte to 1.
In most cases, the microprocessor uses the TAS instruction as follows: It chooses a given byte to represent the status of a shared resource (this byte is often called a semaphore). If the TAS instruction shows the byte to be negative (if its most significant bit is 1), the querying microprocessor knows that the resource is in use. The processor can then either retest the semaphore byte until it shows the resource is available, or it can go about some other task. If the TAS instruction shows the byte to be positive (most significant bit is 0), the microprocessor knows the resource is free. Because the TAS instruction immediately sets the most significant bit to 1 (and because the instruction cannot be interrupted before completion), all the microprocessors with access to the semaphore byte have correct information about the shared resource. The microprocessor that has access to the shared resource has the responsibility of clearing the most significant bit when it is finished.
The only reason this process can work effectively is that the indivisible read-modify-write bus cycle (a special bus cycle) that accompanies the TAS instruction prevents, with hardware signals, any other device from accessing the semaphore byte between the time the TAS reads it and the time it is through setting the bit in it. This means that no two processors can read a semaphore byte and both be told that the resource is available. Thus, a secure way exists for software to determine the availability of shared resources in a multiprocessor MC68000 system.
The MC68000 executes instructions at one of two operating or privilege levels. The upper level, called the supervisor level, provides a protected environment for the operating system to run in, isolating it and its resources from the less trustworthy user code. After a reset operation, the MC68000 begins running in the supervisor mode, in which the operating system and all interrupt routines are also running. The lower level, called the user level, is where most application programs execute and, therefore, where the processor usually spends most of its time.
The only controlled way to get from the supervisor level to the user level is by changing the S/U (supervisor/user) status bit (bit 13) in the processor status register. This is how the operating system switches to begin a user-level program. Should an interrupt be handled in the middle of a user-level routine, the interrupt routine will run at the supervisor level, but upon return to the interrupted routine, the MC68000 will return to the user level.
User-level programs are guaranteed to go to the operating system only through one of the 16 TRAP number n instructions. You can view these instructions as supervisor calls; they immediately transfer control to a specific routine. Upon completion of the TRAP routine, the processor will usually return to the original user-level routine to continue. Thus, there are 16 different supervisor trap instructions, which, along with other types of trap instructions, are listed in table 2.
Table 2: Supervisor trap types and their causes.
Type of Trap Cause address error word or long-word access to an odd address illegal instruction no valid instruction exists for this op code (op codes starting with "1010..." and "1111..." generate other traps; see below) zero divide attempt to divide by zero CHK instruction CHK instruction failed (operand out of bounds) TRAPV instruction overflow has occurred (V bit set) privilege violation attempt to execute a privileged instruction while in the user mode trace an instruction has just ended and the T status register bit is set line 1010 emulator attempt to execute an op code that starts with "1010" line 1111 emulator attempt to execute an op code that starts with "1111" TRAP n instruction Trap n instruction executed (n=0,1,...,15)
Many other means of getting to the supervisor level of execution exist, but they are either conditional (like error traps) or asynchronous (like interrupts). Regardless, all traps are handled similarly by the supervisor. Any trap causes the processor to save the old program counter and status register on the supervisor stack. Then it will go to its external vector table and get a value, appropriate for the cause of the trap, to load into the program counter. This allows each type of trap to have a separate handling routine to correct the problem causing the trap and return to the original program.
Some of these other trap forms are intentionally conditional. Depending upon whether the overflow (V) condition bit is set, one instruction, TRAPV, either does nothing or causes a trap to occur, which forces the MC68000 into the supervisor state. This enables the program to handle all overflow conditions uniformly in a single operating-system-level routine. Another such instruction is the check (CHK) instruction, which verifies that the contents of any data register is greater than 0 but less than a specified bound. If it is within the limits, then nothing happens and the next instruction is run. If it is outside the bounds, then program control jumps indirectly through the vector table to a certain trap routine for handling. This gives the programmer an easy way to check whether an array index is within the proper bounds for that array. In addition, attempts to divide by 0 and access misaligned data (words or long words in memory on odd-byte addresses) will cause trap routines to be executed.
To allow room for future expansion of the MC68000, designers did not use all of the possible bit patterns of the 16-bit op codes. Other microprocessors try to execute undefined op codes, often with disastrous results that cause you to lose control of the computer or even lose valuable work. To assure a completely foolproof system in the face of undefined op codes, the MC68000 refuses to execute any illegal instruction and, instead, executes a specified trap routine for corrective action.
To enable programmers to add whole blocks of new instructions to MC68000 processors, designers left two subgroups of possible op codes unimplemented. Any 16-bit op code beginning with binary 1010 or 1111 was left without definition in the MC68000. Attempts to execute either of these categories of op codes, even though they could be considered illegal instructions, are trapped separately. They cause either a line-1010-emulator or a line-1111-emulator trap routine to execute, enabling the programmer to emulate in software functions that are not implemented in the processor chip of the system. Currently, the 1111 op codes are defined mostly as floating-point instructions and so could be emulated on the MC68000. The 1010 op codes are still reserved for use in processors beyond the MC68020.
Privileged instructions have a special characteristic -- they can be executed only while the processor is running at the supervisor level. Attempts to run them at the user level force privilege-violation traps to occur, allowing the supervisor to take whatever action it thinks suitable.
The privileged instructions are listed in table 3 and are mostly self-explanatory. These instructions are restricted because they modify or control resources or services that must be under the control of the operating system. Many of these instructions modify the upper portion of the status register (SR), which contains the S/U supervisor bit, the interrupt mask, and a trace-mode switch. Such resources are not meant to be in the hands of the users, but maintained by the supervisor; this is why they are restricted.
Table 3: Privileged instructions in the MC68000.
STOP RESET RTE MOVE (when moving a word to the status register) MOVE USP AND, EOR, or OR (when combining an immediate value with the status register)
Another supervisor-privileged resource is the supervisor stack pointer (SSP). This pointer is visible (as address register A7) only when the MC68000 is running at the supervisor level, just as the user stack pointer (USP) is visible (as register A7) only when the MC68000 is running at the user mode. However, when the operating system is ready to pull in a new user-level task, it needs to be able to access the hidden USP to initialize it. This is done with a special, privileged MOVE USP instruction.
The STOP instruction halts processor execution of further instructions, while waiting for an interrupt, a trace exception, or a reset to initiate new activity. The instruction also loads the status register with an immediate 16-bit value, allowing the programmer to enable certain interrupts before stopping the microprocessor. Only the supervisor can initiate this type of operation because, in a user's hands, the operation might throw off all sorts of operating-system timing integrity (such as time-slice clock signals) and generally brings the system to an irrecoverable halt. Also, the instruction must be restricted because it affects the entire status register.
The RESET instruction is a unique and powerful operation. Its execution pulses the reset line on the MC68000 without resetting the processor itself. You use this instruction typically after a catastrophic failure, from which the operating system is trying to recover on its own. It enables the operating system to initialize its external environment (i.e., reset the entire system except for the microprocessor) without forcing itself into a complete restart. Obviously, this instruction's power makes it inappropriate for the user level.
One final privileged instruction is the RTE (return-from-exception) instruction. An exception is anything that causes the microprocessor to perform an operation other than the next normal instruction. Interrupts and traps, then, are exceptions. The RTE is similar to the return-from-interrupt instruction of most microprocessors. It basically reloads both the program counter and the status register with the values from the top of the stack. Because all exceptions force the processor to execution in the supervisor mode, the RTE instruction will be executed only in that mode; this makes it a privileged instruction.
Note, once again, that privileged instructions can be executed from only the supervisor level of operation, where the operating system usually resides. The two different levels of privilege and the restricted use of privileged instructions allow you to build systems that prevent user-level application programs from, inadvertantly or otherwise, running rampant through operating-system code and data.
As you have seen in previous installments of this article, the MC68000 architecture is really designed with the programmer in mind. The MC68000 branch and jump instructions give you complete control over program flow and simplify often-used looping and string-movement constructs. The link and unlink instructions make it easier for you to create modular programs that use local variables. Other instructions carry out complex address calculations quickly, help mediate the use of shared resources, provide for the data integrity of the operating system, and allow recovery from errors under program control. In addition, planners designed the architecture and instruction set with far greater things in mind and made the set easy to expand to more powerful and more comprehensive functions. And all this has been done with a processor for which performance was a primary criterion.
Once you learn a few general concepts of programming the MC68000, coding an application comes easily. Pick up an MC68000 user's manual and a similar guide for any other 16-bit microprocessor. Then spend an hour or two learning each. Code a short program or two, and compare just how easy the MC68000 is to work with. And if you choose to write code for a larger program, you will find your task to be simple regardless of program size.
The MC68000 was designed to be a programmer's instrument through which programmers and system designers could use their creativity to engineer a system to fit the application instead of having to figure out some trick to get the microprocessor to perform the needed task. We at Motorola believe that using the right tool gets a job done faster with fewer mistakes, and the MC68000 is such a tool.
About the Author
Thomas Starnes is an electrical engineer who has spent the last five years helping to plan the direction of the MC68000 family of processor products for Motorola.