Let us look at the completed instruction listing for our project after all
that work we just did in lessons 1 and 2.
Wow! we now have well over 90% of the instructions coded. So is this a
microprocessor yet? Frankly, no. At best we have a logic sequencer that can
only execute code. It has no way to react to events.
In this lesson we will study how to react to events, both external and
internal. The ability to react to events is one of the major capabilities
that set processors apart from other sequential logic entities.
An event is something that is singular in nature, and usually occurs outside the scope of current operational control. External events are generated by peripheral hardware devices and may occur at predictable intervals, such as timer clicks, or irregularly, like keyboard strokes. Simple sequential devices may identify external events by a software polling process. Even more advanced devices still use polling for certain hardware devices; in fact USB hubs, controllers, and endpoints are polled devices.
Event polling has certain advantages. First of all, polling occurs at known intervals, and is therefore, well controlled. Polling also has a built in priority structure, unless the program adds an event queue to the polling process. However, polling is time consuming by nature, and can miss certain events if the polling interval is not fine enough.
Processors contain a feature whereby the external device or internal software (to be discussed later) can signal an event and request special event processing. This request mechanism is variously called interrupts, restarts, or exceptions. For purposes of this lesson we will use the term interrupt to indicate an event originated signal that requests this type of special handling.
External interrupt signals may assume one of three forms:
The 8085x architecture hosts five different hardware interrupt input signals:
In some architectures, software can invoke an event for exceptional handling or shared processing via special instructions. Operating system calls, or calls to UEFI (in earlier systems called BIOS) are usually internal events. Software generated events share some of the key aspects of hardware interrupt reaction and will sometimes share many of the same operations, such as switching to protected (or supervisor) mode automatically, using the same interrupt vector table, or saving the return address for the currently executing process.
External interrupts normally have a pre-determined priority, enforced by hardware, to determine their order of recognition. For internal events, order of recognition is normally determined by software. Interrupt priority is usually dictated by system design based upon two factors:
In our architecture, the priority of the five external interrupts and the software interrupt instructions is as follows:
Since interrupts disrupt the currently executing process, processors require some measure of control over interrupt recognition. They normally contain instructions to enable and disable all interrupt recognition, and frequently contain instructions to enable or disable specific interrupts. This ability to enable and disable specific interrupts is referred to as an interrupt mask. Interrupts that can be specifically enabled or disabled are referred to as maskable interrupts.
Even when an interrupt is present, it cannot interfere with a currently executing instruction or DMA operation. Interrupt presence is only detected at the beginning of an instruction fetch cycle, or when the processor is idle.
Many processors have a non-maskable interrupt (NMI), i.e. an interrupt that cannot be disabled. NMIs are normally used to provide service for catastrophic events that will cause the processor to cease operation; like emergency power off warnings, access to non-existent memory, or divide by zero. NMIs cannot be disabled specifically and, in most cases, generally.
The interrupt enable for general interrupts is also specialized in many architectures. Interrupts frequently push the return address of the interrupted process on to the stack. In the event of a 'babbling' interrupt or an interrupt 'storm', these returns could get backed up if the interrupt enable executed immediately. Since each return consumes a number of processor cycles to complete, this backup could escalate with each interrupt; leading to an "I/O bound" state (a state where the processor can never exit interrupt service before the next interrupt occurs; thereby 'freezing' the processor (from the user perspective) or a stack overflow (which generally will 'crash' the processor). To avoid these issues many processors, including ours, hold off generalized interrupt enables until the next instruction. This allows interrupt service routines to return to the interrupted process before another interrupt can be recognized.
|8085x Interrupt Control|
The 8085x instruction set contains two instructions to control interrupts in general. EI enables all interrupts that are not otherwise masked. DI disables all interrupts that can be disabled. It also contains an instruction, SIM, that allows you to mask interrupts from RESTART 7.5, RESTART 6.5 and RESTART 5.5, reset the interrupt from RESTART 7.5, and control the state of serial output data. The SIM control data is place in the accumulator register before the SIM instruction is executed.
The state of the SOD output pin is changed to reflect the state of the SOD bit during the SIM instruction only when SOD ENA bit is set. Otherwise the SOD bit is ignored. The interrupt mask bits are set to the value of MASK during the SIM instruction only if MASK ENA is set. Otherwise they, too, will be ignored. The interrupt associated with each mask bit will be selectively disabled while the mask bit is set, regardless of the state of inte. The restart 7.5 interrupt can be reset by setting RST 7.5 and executing a SIM instruction.
In order to effectively control the general and specific interrupt enables, it is useful to know the current status of the interrupt disable, the interrupt mask, and the interrupt signals themselves. The 8085x architecture provides the RIM instruction to read this data, and the state of the serial input data. When the RIM instruction is executed, this data is placed in the accumulator.
There is one caveat to the inte state returned by the RIM instruction. TRAP, unlike all other eternal interrupts, is a non-maskable interrupt. This means that it can occur whether interrupts are enabled or disabled. Therefore the first RIM instruction after a TRAP will return the state of inte before the trap,; rather than the current state of inte, unless there is an intervening EI or DI instruction.
The 8085x architecture also contains, like some other processors, a low power mode. Since the bulk of the power that a processor consumes is used for driving output pins, and transitioning internal registers, the 8085x processors will place all output and bi-directional pins into a high impedance state and idling much of the logic after executing a HALT instruction. A processor reset or a pending interrupt will cause the processor to reactivate these pins and logic.
Implementing the RIM and SIM instructions do require further changes to our parameter file. In order to immunize or microcode expander from these changes, it has been changed to ignore the subfields embedded within the opcode field. However, the compiler may still be able to optimize the affected signals to reduce the number of logic levels, due to the overall design of the opcodes.
I also discovered, while researching this project, that the undocumented instructions and flags that we activated in the 8085B versions and beyond were also present in the 8085A. Therefore, we can remove a lot of conditional code both in the parameter file, and the RTL code for the processor itself.
As promised, I also applied fixes to ensure that the saved flags are loaded during the POPPSW instruction. This required changes to the flag generation logic itself, since all flags are affected, and to the flag enables.
We could simulate these changes at this point. However, the next section is very closely coupled, and the changes required to the test bench are both extensive, and applicable to both sections. Therefore, I will delay simulation until after the next section. You have enough information at this point to simulate this section yourself, if you prefer.
|Interrupt and RESTART Translation|
When INTR is recognized the address of the interrupt service routine is obtained from the external hardware only if the first interrupt acknowledge cycle returns a 'CALL' instruction. For all other interrupts, when the INTR is recognized, but the first interrupt acknowledge cycle returns a RESTART instruction, or when program execution encounters a RESTART instruction; the hardware must determine the interrupt service routine address (called interrupt vector) from the interrupt that has been recognized or from the contents of the instruction latch. RESTART instructions in the instruction latch, and the hardware interrupts themselves (excluding INTR) form regular patterns (except for the RSTV instruction) in the lower six bits of the interrupt vector, and therefore use minimal logic to decode. The RSTV instruction can only be encountered during ordinary program execution, and can be translated into a six-bit vector address using simple hardware. The translation hardware zero fills the upper ten bits of the vector.
In order to use a hardware generated interrupt vector, a special opcode field is needed in the microcode state machine and interpreted by the microcode expander, which places this address on to the source bus, for use by the bus state machine. This opcode field has been already incorporated into the previous version of the parameter file.
Interrupt recognition can only occur when the processor is idle, or just prior to an instruction fetch. Interrupt recognition of any hardware interrupt will immediately set the global interrupt enable state to disabled.
To recognize the INTR interrupt a bus cycle, similar to an instruction fetch cycle from address FFFF (except that the iom_n signal is driven high and the inta_n signal, instead of rd_n, is driven low) is executed. External hardware places either a CALL or a RESTART instruction on the data bus while inta_n is low. The instruction is placed into the instruction latch. This first interrupt acknowledge cycle is always at least 6 cycles long. While the bus cycle is executing, the current contents of the program counter are placed into the TEMP register.
If the first interrupt acknowledge bus cycle returned a CALL instruction, two more bus cycles are required to read the interrupt services address. The first cycle returns the lower eight bits of the address and the second cycle returns the upper eight bits of the address. These cycles are similar to an I/O read cycle from device address FF, except that S0 is forced high and inta_n, rather than rd_n, is driven low to read the data. In the absence of HOLD or the READY signal driven low, these cycles are three system clocks long. The data from these bus cycles is placed into the program counter.
If the first interrupt acknowledge cycle returned a RESTART instruction however; internal hardware must derive the interrupt vector from the instruction itself and place it into the program counter before the bus cycle completes.
TRAP, RESTART 7.5, RESTART 6.5, and RESTART 5.5 acknowledge the interrupt differently. A bus cycle that is at least six system clocks in length with iom_n, s[1:0], rd_n, wr_n iom_n, ad[7:0], and a[16:8] are all driven to high impedance is initiated. While this bus cycle is executing, the current contents of the program counter are transferred to the TEMP register and the interrupt vector associated with this particular interrupt, and generated by the internal hardware, is placed into the program counter.
After these cycles occur, the contents of TEMP must be 'pushed' on to the stack. This saved data is referred to as the interrupt return, To avoid adding even more states to the microcode state machine, the common code is used for these bus cycles. However, since the common code utilizes the instruction latch (which is unchanged when acknowledging TRAP, RESTART 7.5, RESTART 6.5, and RESTART 5.5) to determine state transitions; some indication of these interrupts is required.
In order to resolve this issue, a D type register has been added to the microcode expander. This register is set whenever the special bus cycle required for TRAP, RESTART 7.5, RESTART 6.5, and RESTART 5.5 is initiated. It is cleared on a global reset or when an ordinary instruction fetch bus cycle is requested. The output of this register, avec_cycle, is used by the microcode state machine to determine whether or not to evaluate the contents of the instruction latch for the next state transition.
Interrupt recognition requires that the state of interrupt pending (intp) before any instruction fetch bus cycle is requested. If interrupt pending is active, we must also determine whether the interrupt being recognized originated from the intr signal or not, to select the appropriate bus cycle. We must also decide whether to fetch another instruction or go to a low power (halt) condition.
During single cycle instructions this scenario presents no problem, since there are three internal clock cycles available to complete the current instruction, poll the required signals, and request the next bus cycle. During multi-cycle instructions, however, there is only one internal clock cycle available left to request the next bus cycle. Therefore the polling cannot be a separate state, which would consume an internal clock cycle. Instead, the polling requires a 4x8-bit mux to select the appropriate state without using an additional clock.
There are a variety of ways to code this mux, most of which will add more complexity to our simple microcode state machine. The Verilog language, fortunately, provides a simple way of describing some forms of common logic. This method is known as a 'function'.
Functions provide a convenient method for generating shared logic, but there are some basic rules associated with them:
For obvious reasons, we will need a much more complex test bench in order to test all these changes we just finished. For one thing, most of our original program code will have to move, since those lower addresses are needed for interrupt and restart vectors. In the real world, this is known as page zero addressing. Page zero addressing is the reason why user programs start at address 100H in the x86 architecture. We also need a primitive interrupt generator, and a programmable interrupt controller (PIC). Therefore, we will use the following test bench for this simulation:
I would like to say that the simulation worked perfectly the first time out, but you and I both know that my nose would start to grow. We are now up to 15,320 lines of code (adding RTL code, test bench, parameter file, and definition file). If it had worked perfectly the first time, we could put the simulation companies out of business. So, using the techniques explained in (OMG another link) Module 3, Lesson 1 I discovered an error with the ASLOP op code decode. Rather than adding extra logic to our project to overcome this issue, we modified our parameter file (again) to help resolve this issue. Point to remember; when you are simulating. do not confine your debug efforts to just your RTL, but include your test bench, parameter file, and your definition file as well. Anyhow, I came up with this new parameter file for us to use:
You may have guessed already, but there are myriad changes to the RTL code itself. For one thing, I eliminated the propagation of the xint signal, which indicates the intr signal was recognized by the priority encoder to simplify logic. There was a simulator bug, which I encountered once before, cause one register to unexpectedly change state when only the data input changed. I worked around this by changing the dreg_r module to blocking assignments. This has no impact on physical implementation, but corrected the simulation error. Other simulators may cause you to change this back to non-blocking assignments. I also tracked down and corrected un-connected signals, which IVERILOG does not call out. There were also some corrections in the interrupt control block and a few other typographical errors. Some existing parameter definitionsi n the microcode state machine had to be updated, particularly the compare operations which were loading the accumulator, but overall our architecture remained intact. We now have the following (debugged) RTL code:
This is a long simulation, so I only captured a few screens for you to look at. Use your waveform viewer to look at anything else.
GTKWAVE Display of Trap Interrupt Processing
GTKWAVE Display of RIM Instructions Following a Trap Interrupt
GTKWAVE Display of Interrupt Acknowledge Returning a RST20 Instruction
GTKWAVE Display of Interrupt Acknowledge Returning a CALL 00C0 Instruction