//****************************************************************************// //********************** Interrupts - May 29th, 2018 ************************// //**************************************************************************// - Rainy post-Memorial day, a scarily significant portion of the class absent...ah, another week of school in summer - Finally caught up on the reading, only, I suppose, to now fall behind for another week ----------------------------------------------- - So, today, we'll walk through some chapter 4 stuff on interrupts/exceptions - "This is a VERY important concept, but since you won't be assigned a project on it, we won't be taking multiple lectures to cover it; still, reading Chapter 4 is a good idea" - First, though, we have to finish up Chapter 3 from last week - So, in order to handle branching, we have to use the Z-register as another input to the state machine so we know when to branch, and we need the 4-bit opcode as another input (from the decode stage) so we know what instruction to execute - All of the microstates to execute each of these stages/instructions are stored in the ROM; these tell us what to run on the datapath to execute the current instruction AND where to go after the instruction is finished - To make our lives easy for branching, we can do what the textbook calls "cloning an instruction:" we have 2 addresses, 001000 and 101000 - At the first one, we'll put a copy of ifetch1 - At the second one, we'll put the "branching" instruction of BEQ (i.e. beq4) - "By choosing these 2 addresses that ONLY differ by 1 bit, we can use Z as an input to choose which branch we want to take by just appending it to the address; this is fast, and it's easy to do" - Similarly, for DECODE, this gives us a way of branching between the different possible instructions by appending the opcode to the front - This "appending" is what the modifier circuitry in the textbook is doing - To support this, we add 2 more outputs "M" and "T" that say to include/ignore the opcode/Z-register - So, if AND has an opcode of 0000, its address in ROM will be "opcode + z + normal address" = 0000 + 1 + 1000 = 000011000 - ...and that's how we put different kind of instructions in the control unit! - Alternatively, like in the homework, you could do this with 3 ROM's and affecting the input to the state register - Alright, so we've got our datapath implemented, everything's good, and it's working (IT'S ALIVE!!!!) - ...but there's some questions still: how do we handle input from the I/O hardware? What do we do when the user divides by 0 or does something else invalid? This is what chapter 4 goes over! - In Chapter 4, we handle interrupts, traps, and exceptions - DISCONTINUITIES in the program flow - "It's like a teacher giving a lecture: I don't just stand here talking perfectly from a script for 2 hours, I have to respond to student questions, crazed gunmen, enemy ninjas, etc." - First, let's understand synchronous vs asynchronous events - SYNCHRONOUS events occur at well-defined, predictable points of activity in the system - ASYNCHRONOUS events can happen at times we don't specify in advance/ can't predict - There are 3 kinds of events we have to handle in the LC-2200: - INTERRUPTS are asynchronous, external, intentional messages (e.g. I/O device input) - TRAPS are synchronous, internal, intentional messages that do some pre-coded low-level operation (e.g. system calls to the OS) - EXCEPTIONS are synchronous, internal, non-intentional errors in our program's execution (e.g. buffer overflow, division by zero) - When one of these events occur, 2 things happen: we SUSPEND the original program's execution (i.e. make sure none of its state changes) until it's time to go back to it, and then HANDLE whatever event interrupted us - "Getting wrong answers quickly usually isn't impressive; when one of these thing happen, we have to handle it first" - In the LC-3, there were 3 TRAP instructions: - IN (get an input character from the keyboard and store it in R0) - OUT (output a chracter in R0 to the monitor) - HALT (halt the program) - So, to handle this, the program would run like normal until it found the TRAP instruction, then it would FREEZE, store the current PC, go to the "TRAP vector table" (basically, the table of contents for these instructions), go to the instruction for handling the TRAP, run those instructions, and then jump back to our original program and resume operation as normal - This is similar to what we'll want for interrupts, but interrupts have the extra problem of being asynchronous - Now, these discontinuities can happen at ANY TIME, even in the middle of us executing an instruction...so how should we handle this? How does the processor know when one occurs? When should it handle it? How do we save the return address/get the handler address? How do we handle multiple interrupts in a row (i.e. if an interrupt happens while we're handling an interrupt)? How do we return to the old state after an interrupt? - As we know from 2110, there's 2 types of ways to handle these interrupts: "polling" and interrupts - POLLING is where we add a new "check for interrupt" state to Fetch/Decode/Execute; it's like continuously asking "Are we there yet? Are we there yet? Are we there yet?..." - It's simple from an engineering perspective, but polling has some MAJOR problems and wastes a lot of time - - Whichever method we use to detect the interrupt, the same principles apply for handling the interrupt itself: - Save processor registers - Execute device code - Restore processor registers - Resume original code - "BUT, what if another interrupt occurs in the middle of this?" Sadly, this complicates things - For a brief moment, we need to disable ALL interrupts so we can just save the current state on the stack without being interrupted; then, after we've saved the state on the stack, we can re-enable interrupts and continue handling our current interrupt - For the 2200, we have to do this since we're saving the interrupt value on a register as soon as it occurs; we don't have a stack to stick it on, so it'd clobber the old interrupt's value - "The book has a pretty good explanation of this, and the complications of handling multiple interrupts" - So, we need to add instructions for enabling/disabling interrupts and for returning to the original address - Hardware-wise, we need to add some stuff to enable all this: - (on the slides, went through it quickly) - Software-wise, we need to worry about system calls to the operating system - 99% of the time, we're running in "User Mode," which only runs on a limited instruction set - In KERNEL MODE, we can use ALL the instructions, including temporarily disabling interrupts - Writing the interrupt handler code that'll handle the specific interrupt - To handle the interrupts, we can add a new 1-bit "Interrupt" line for siginaling that some device is trying to interrupt, and an "Inta" line that checks which device is trying to interrupt it - Can possibly have multiple interrupt buses for handling multiplte devices simultaneously - When we get an interrupt, our state controller goes to the "Interrupt" macro state, checks using the "Inta" bus what the first device talking is; when the device receives that acknowledgement, it turns off it's "Int" line on the interrupt bus and puts it's address in the interrupt table onto the data bus; then, we go to memory, check our "interrupt table" in memory to see what the device handler's address is (usually, this is installed in the table by drivers), run the program to handle it, and then resume as normal - We often have a special stack in memory just to handle this, with its own stack pointer - Alright, for now, good luck on your current homework, and I'll see you on Thursday!