Up to this point, the only contact you are likely to have had with the interrupt and trap mechanisms of the Hawk machine are in the context of error messages generated by the monitor. The most common you are likely to have encountered is "Bus Trap", indicating an illegal memory reference, such as a write to ROM or a read from unimplemented memory. The other message you may have received is "Instruction Trap", indicating an attempt to execute an instruction that the Hawk hardware does not define.
In fact, the trap mechanisms of most modern CPU's are used for far more than detecting errors in user programs. Interrupt handling mechanisms are central to handling asynchronous input/output, and trap mechanisms are central to a class of system features known as virtual machine extensions.
Asynchronous input/output is involved whenever input or output is not synchronous with the request for input or output from the program. (It is worth noting that asynchronous serial communictations, for example, as used with modems and serial printers, is an unrelated use of the same word.) A common example of asynchronous input is with keyboard input. On most computers, input can be typed at any point, and input characters are queued by the software until they are needed by the user program. A second common example is output to a printer. Many computer systems will buffer entire documents, holding them until they can be output while allowing the program that requested the output to go on to other things while the data is transferred to the printer.
Two good examples of virtual machine extensions are software floating point and virtual memory. If your computer has a floating point coprocessor, of course you expect floating point instructions to run at full speed. If this coprocessor is removed, most systems will still operate correctly, but programs that use floating point will run more slowly. In this case, the floating point instructons are not executed by hardware; instead, the hardware traps the floating point instructions and lets the system software execute them.
With systems that have virtual memory, large programs can be run even if the main memory is small, but adding more memory makes the system run faster. What the system does is trap references to nonexistant memory, so that the operating system can copy things to and from disk, rearranging the system's available memory whenever the application program attempts to use a different area of memory.
All of these applications require that the CPU provide a way for the operating system to gain control of the CPU when a user program was running, take some action, and then return control to the user program. In a sense, this is not too different from the effect you would expect if the user program called a subroutine in the system, except that the user program does not include an explicit call instruction.
Instead, when a trap or interrupt conditon is detected, the CPU abandons the normal fetch-execute cycle, saves the state of the current computation, and begins fetching instructions from the appropriate trap or interrupt service routine.
The details of how a CPU saves the state of a computation when a trap or interrupt is encountered vary considerably from one architecture to another, as do the details of how the CPU determines where to transfer control. On machines with a complex instruction set or CISC design philosophy, it is common to find that the state is saved entirely by hardware. On machines with a reduced instruction set or RISC design philosophy, a considerable amount of the state save and restore work is done by software.
The Hawk machine is in the latter category! On the Hawk, only a small amount of state information is saved when an interrupt or trap occurs. The Hawk has a special register, TPC, used only to save the program counter when a trap occurs. Control transfer on interrupt or trap is similarly simplified. There are 16 fixed addresses in low memory used for each of 16 predefined interrupt or trap conditions. Thus, we can describe the hardware action at the time of a trap as follows:
TPC = PC PC = code << 4Thus, given that code is a 4 bit binary number, traps and interrupts will be served by control transfers to addresses 00, 10, 20, 30 and so on up through F0 (hexadecimal). The CPU hardware predefines codes 0 through 3, while codes 4 through F are available for external devices. The Hawk manual gived details of what trap codes are reserved for what purposes.
Most processors were originally built with no clearly thought-out scheme for assigning interrupt or trap codes to external devices. As a result, such codes were typically assigned on a first-come first-served basis.
So long as such a machine is closely held by one manufacturer, and so long as the space for assigning device codes is not exhausted, this poses no problem, but as soon as third party manufacturers enter the scene, or as soon as one code needs to be assigned to two devices, problems arise.
In an ideal world, you ought to be able to buy any device that is compatable with your machine, plug it in, and use it. This is the model people describe with the phrase "plug and play." In the real world, what has happened with most mass produced computers, and most notably, what has happened with the IBM PC, is that multiple devices are on the market with the same code assigned to them.
As a result, when you plug such devices into a maching, you can never be sure that they don't conflict. So, you've got to carefully check the manuals for each device, remember what device codes are assigned to each, and before plugging in a new device, set the jumpers or address switches on that device to an unused device code, then configure the software so it knows what code to associate with what device.
Hardware and software schemes that automatically search out unused device codes and assign them to devices are possible, and it is also possible to design operating system and hardware structures that allow devices to share device codes, but each of these schemes must be thought out in advance.
The basic problems the trap and interrupt service software must face can be cleanly divided between problems specific to a particular trap -- what input/output to perform or what other action to take, and problems of a general nature -- how to save and restore the complete system state, given the partial job done by the CPU.
For any machine, there are many possible ways of handling traps, so we can only give an illustrative example here -- a reasonable solution to the problem, but not one that is mandated by the architecture.
First, we will assume that every trap is associated with a data structure at some fixed memory location in RAM. This data structure is used to save the system state, and it includes a pointer to the code to be executed when that trap occurs, as well as a stack for use by any procedures called by that code.
A reasonable definition for such a data structure is as follows:
; record structure for trap and interrupt service ; register save areas (4 bytes each) R1SV = 0 << 2 R2SV = 1 << 2 R2SV = 2 << 2 R2SV = 3 << 2 ... R13SV = 13 << 2 R14SV = 14 << 2 R15SV = 15 << 2 ; other CPU state save areas (4 bytes each) PSWSV = 16 << 2 PCSV = 17 << 2 MASV = 18 << 2 ; pointer to service routine (4 bytes) SERVER = 19 << 2 ; stack pointer for use by service routine (4 bytes) SERVSP = 20 << 2 ; size of record TRAPSZ = 24 << 2Given this data structure, we can define a macro to generate the code to handle a trap with some particular code as follows:
MACRO HANDLER code,server,size . = code << 4 TSVSET R3 LOAD R3,REF'code JUMP MAINTRAP ALIGN 4 REF'code: W SAVE'code COMMON SAVE'code,TRAPSZ . = SAVE'code + SERVER W server . = SAVE'code + SERVSP W STK'code COMMON STK'code,size ENDMACThis macro initializes the data structures in RAM appropriately, it sets up the contents of ROM, and it relies on a routine called MAINTRAP to do most of the work. Typically, MAINTRAP would look something like this:
; common code for all trap service MAINTRAP: ; R3 points to trap data structure ; TSV holds R3 ; TPC,TMA, PSW and R1-15 must all be saved! STORE R1,R3,R1SV STORE R2,R3,R2SV STORE R4,R3,R4SV STORE R5,R3,R5SV TSVGET R1 TPCGET R2 TMAGET R4 PSWGET R5 STORE R1,R3,R3SV STORE R2,R3,PCSV STORE R4,R3,TMASV STORE R5,R3,PSWSV STORE R6,R3,R6SV STORE R7,R3,R7SV ... STORE R14,R3,R14SV STORE R15,R3,R15SV LOAD R2,R3,SERVSP LOAD R1,R3,SERVER JSRS R1,R1 ; go handle the trap! LOAD R15,R3,R15SV LOAD R14,R3,R15SV LOAD R13,R3,R15SV ... LOAD R5,R3,R5SV LOAD R4,R3,R4SV LOAD R2,R3,PSWSV LOAD R1,R3,PCSV PSWSET R2 TPCSET R1 LOAD R2,R3,R2SV LOAD R1,R3,R1SV LOAD R3,R3,R3SV RTT ; return to user!This bit of code begins and ends on a tricky note, saving just enough of the registers to use those registers to save the special registers PSW, TPC, TMA and TSV, and then, at the end, restoring all the registers but three, then using those to recover the saved PSW and PC before restoring the final registers. The special RTT instruction is used at the very end to load the TPC register into PC, restoring the user's view of the CPU state to exactly what it was at the time of the trap or interrupt.
The trap or interrupt service routine, pointed to by the SERVER field of the data structure, can use R3 as a pointer to the save area data structure if it needs access to any of the saved state information. It may, of course, ignore the saved state.