CS 61C Summer 2004 Project 4

Interrupts and Memory-Mapped I/O

 

"Pooh was sitting in his house one day, counting his pots of honey, when there came a knock on the door. 'Fourteen', said Pooh. Or was it fifteen? Bother. That's muddled me."

The House at Pooh Corner

Background:

Interrupts are asynchronous breaks in program flow that occur as a result of events outside the running program. They are usually hardware related, stemming from events such as a button press, timer expiration, or completion of a data transfer. We can see from these examples that interrupt conditions are independent of particular instructions; they can happen at any time. Interrupts trigger execution of instructions that perform work on behalf of the system, but not necessarily the current program. Just like traps and exceptions (predictable, synchronous breaks in program flow), interrupts must be handled, while the state of the program at run is preserved. After handling the unexpected interruption, the program must be able to resumes and continue its task.

Project Errata:

All updates, corrections, etc. to this project will be posted on the Errata Page. Make sure to check this page regularly!

Administrative:

Submit your solution online by 6:00 p.m. on Saturday, Aug 15. NO LATE SUBMISSION!  To submit, create a directory in your unix account called ‘proj4’ which includes the following files:

single.v iecho.s

Then run the command: submit proj4 from within the directory.

NOTE: The due date is too close to your final. That means you will need to manage your time between the project and the final. You should not leave any work for Saturday, only testing and possible debugging. This means you need to have your project done before the final.

Project:

This project consists of two, independent parts: writing an interrupt-based io handler and adding an interrupt mechanism to your single-cycle processor. When you’ve finished both parts, you should be able to run your handler on your processor.

 

Part I: Exception handler in MIPS

Keep in mind that you are only responsible for replacing the single character i/o buffers with ring buffers. The current framework does everything mentioned here except that it does so with single character i/o buffers.

Start by writing an exception handler. We have provided the framework for your handler. It is stored in the file iecho_buggy.s which you can download from ~cs61c/lib/proj4. Change the name of the file to iecho.s once you download it to your directory. The file is heavily commented and includes many useful instructions and hints. Make sure to read the comments thoroughly. Remember, understanding the existing code of the handler is a major part of your project. So spend the time to read, and understand the code before you start the modification. The framework already works to some extent. Run it, and try to type really fast. You’ll see that when you type fast, the handler loses some of the keys you typed. This is because the handler has only a single character buffer. Your task for this part is to replace the single character i/o buffers with ring buffers. Fortunately, you can test your handler in SPIM with only slight modifications. Don’t forget to only use instructions that your processor supports! We have modified your single cycle processor from project 3 so it can handle more instructions. Here’s a list of instructions you are allowed to use in your handler:

add, addi, lw, sw, lb, sb, beq, bne, lui, ori, andi, jr, slt, mfc0, rfe, syscall 

Your handler should contain two circular buffers: an output buffer and an input buffer. Characters are placed in the output buffer when the user calls syscall with a 4 in $v0 (print syscall) -- the string to output is stored in $a0 -- and items are removed from the output buffer when a memory mapped i/o transmitter interrupt occurs. Characters are placed in the input buffer when a memory mapped i/o receiver interrupt occurs and items are removed from the input buffer when the user calls syscall with a 12 in $v0 (readchar syscall).

The output circular buffer works as follows: the label xmtRing points to a 32 character array. Two integers, xmtInput and xmtOutput, index into the array. When a print syscall adds something to the output buffer, it is placed at xmtRing[xmtInput] and xmtInput is incremented modulo 32 (by adding 1 and anding with decimal value 31). When an xmtintrp event occurs, xmtRing[xmtOutput] is stored to memory mapped i/o and xmtOutput is incremented modulo 32. If xmtInput == xmtOutput, there are no characters in the buffer. If (xmtInput+1) mod 32 == xmtOutput, the buffer is full.

The input circular buffer works similarly except a readchar syscall removes and rcvintrp adds to the buffer.

Here are the output behavior we are expecting (that is currently implemented, to some extend, with a single character buffer): On a print syscall occurs, add characters to the buffer until either the buffer is full or until you have added the entire string to the buffer. Return with $a0 pointing to the first character not added to the buffer (if you have added all the characters, $a0 should be pointing to the null character). Be sure to enable interrupts for the transmitter. On an xmtintrp event, if the buffer is not empty, store the next character to the memory mapped i/o. If the buffer is empty, do nothing but turn off interrupts (interrupts will be turned back on when a print syscall occurs).

The input behaviors we are expecting are similar: On a readchar syscall, if the buffer is not empty, return that character in $v0 and remove the character from the buffer. If the buffer is empty, return zero in $v0. On an rcvintrp event, if the buffer is not full, add the character and return. If the buffer is full, simply drop the character and return.

You can test your handler in spim. You have to force your handler code to be placed at 0x0800080, by using the notation .ktext

Test your handler thoroughly in spim before proceeding to the second part of the project.

Make sure that you only change code needs to be changed. You should not change any part of the main program (__start:), any part of the prologue and/or epilogue of the exception handler, or any part of .kdata or .data sections. Doing so may make your task for part 2 harder or (maybe) impossible. Lastly, notice that the prologue of the exception handler saves the registers $t0-$t6. You may use these registers (along with $k0 and $k1) within your exception handler. Do not use any other registers within the exception handler!

Part II: Processor Interrupt Support

For this part, you’ll need to extend your non-pipelined proc from proj 3 to support interrupts. We have modified the single cycle solution to support new instructions, changed the control unit to behavioral (to simplify adding new instructions) and also added interface to the newly created memory-mapped I/O module. Download the files from ~cs61c/lib/proj4 to your directory. To make sure you won't overwrite your iecho.s solution when you copy the files, we renamed the iecho.s to iecho_buggy.s! Although the mmio module is already written for you (it is in the file mmio.v), try to read and understand the implementation. This module interacts with an input (input.txt) and an output (output.txt) file as source and destination of its I/O. Before you start, it is important that you understand the current implementation thoroughly, so you can add your new supports. In the first place, we need a way to flag exceptions. There needs to be a global wire throughout your CPU which signals exceptions in any of the events supported by our design. There are three kinds of supported exceptions in our design: a receive event, a transmit event, and a system call. There are two wires in the mmio module, except_recv and except_trans that are set to 1 if interrupts are enabled in the device control reg and a new byte is ready to be read or written respectively. You will need to connect the mmio module to your CPU and add decode support for the ‘syscall’ exception.

Secondly, you’ll need to add a mechanism to raise exceptions. This will consist of setting Cause (look at Figure A.14 on page A-34, and set the Exception code field to 0 if a receiver or a transmitter exception, and 8 if a syscall, this means the value of the cause register will be 32 if there is a syscall) and EPC (to the PC of the current instruction*). On an exception, your processor should jump to address 0x00000008. The reason for this is explained later on this specification.

Once you have your coprocessor registers, you’ll need to add support for the coprocessor-0 instruction mfc0. The format for mfc0 is in the back of COD. You need only support moving Cause and EPC. (Hint: maybe extend the mux in front of regdst to patch in EPC and Cause?) Remember that the coprocessor registers are different from your CPU registers. Refer to section A.7, pages A-32 to A34 for information about coprocessor registers. Keep in mind that you will be only implementing registers 13 and 14 (Cause and EPS). Page A-69 has the details on the instruction mfcz (where z = 0 in our case). Remember that this instruction moves coprocessor 0's register rd to CPU register rt.

When testing your interrupt handler, remember that your processor always starts at PC equal to zero (hence the handler is at address 0x00000008 to count for a jump to the user code and its delay slot) and knows only a few instructions. Thus, you should lay your MIPs program out like this:

Address           Instr

0000                beq $0 $0 __start        #first instruction executed. Jump to main.

0004                add $0 $0 $0               #delay slot

0008                                                   #handler starts here …

….

__start:

00XX                                                 #main starts here

The mmio module works as follows: There are two text files used by mmio: input.txt and output.txt. These files contain the actual data going into the receiver or coming out of the transmitter. The mmio modules will simulate interrupts as follows: after the first byte is loaded from the receiver, the mmio will wait random number of cycles between 4 to 31, before loading the next value (and setting the ready bit). Similar behavior accompanies writes. If interrupts are enabled, when the ready bit goes high, interrupt will be signaled (and will continue to be signaled every cycle until either a load/store takes place or interrupts are turned off). In our implementation, interrupts are disabled as soon as the flow of instructions is switched to handler, to avoid reentrant.

Testing Your CPU:

The eventual and ultimate testing for your processor will be to run your code on your CPU. To translate the instructions into machine code, use MIPSASM. To run your handler program, write a string of characters in the file input.txt, then run your CPU, using command vvp cpu.vvp and wait for it to run the whole code. Once it is done, look at the file output.txt. You are strongly recommended to test your CPU in every small step of this task. You can test your CPU with the test cases from project 3, to ensure the rewiring did not disrupt your datapath. Also, you are encouraged (and might get rewarded!) to share test cases. To do this, create a folder in your directory, and use the command chmod 755 to let others read the directory, and put your test cases in there. Once you have created the folder, post its location on the newsgroup, and/or email it to cs61c-te@inst. Good test cases will bring extra credit points to your project grade!

 

 

* Notice that this is different from what was explained in lecture, which sets EPC to PC + 4.