Machine Structures. Spring 2006, UC Berkeley
Project 3: MIPS Instruction Simulator

Project TA: David Marquardt <dmarquardt AT gmail.com>

Due: 3/13/2006 @ 12:00 noon

Updates:

Overview and getting started:

In this project, you will create an instruction simulator for a subset of the MIPS instruction set. Your simulator will fetch, decode, and execute MIPS machine instructions. As well as printing the assembly language version of the instructions, you will perform the actual operation (indicated by the instructions) on the machine state. You are creating what is effectively a miniature version of spim! There is one important difference though--spim takes in assembly language source files, so it performs many of the functions of an assembler in addition to being a simulator. Your simulator will take binary input, generated from spim's dump command.

You should make a directory in your home directory named proj3, and copy all the files from ~cs61c/lib/proj3 into your proj3 directory. Run make to compile your project. It will create sim for you.

Project Submission:

Make sure you are in your proj3 directory and run

submit proj3

Submit computer.c, which contains your code. Also, submit sim.c and computer.h that you have copied from the framework.

Assignment:

Part 1: regular instructions

The files sim.c, computer.h, and computer.c comprise the framework for a MIPS simulator. Complete the program by filling in appropriate procedures in computer.c. It should be the only file you modify. Your simulator must be able to simulate the machine code version of the following MIPS instructions:

·         addu Rdest, Rsrc1, Rsrc2

·         addiu Rdest, Rsrc1, imm

·         subu Rdest, Rsrc1, Rsrc2

·         sllv Rdest, Rsrc1, Rsrc2

·         srlv Rdest, Rsrc1, Rsrc2

·         and Rdest, Rsrc1, Rsrc2

·         andi Rdest, Rsrc1, imm

·         or Rdest, Rsrc1, Rsrc2

·         ori Rdest, Rsrc1, imm

·         xor Rdest, Rsrc1, Rsrc2

·         xori Rdest, Rsrc1, imm

·         lui Rdest, imm

·         slt Rdest, Rsrc1, Rsrc2

·         beq Rsrc1, Rsrc2, address

·         bne Rsrc1, Rsrc2, address

·         j address

·         jal address

·         jr Rsrc

Part 1 does not involve instructions that access memory (loads and stores); all computations are done in the registers.

Part 2: memory access

Add support for two more instructions:

·         lw Rdest, offset(Radd)

·         sw Rsrc, offset(Radd)

With these additions, the program can simulate real programs that do just about anything that can be done on a real MIPS processor (with the notable exceptions of floating-point math and interrupts).

How does the code work?

The framework code already does the following:

·         It reads the machine code into Computer->memory, starting at Computer->address = 0x00400000. In keeping with the SPIM convention, addresses from 0x00000000 to 0x00400000 are unused. We assume that the program will be no more than 1024 words long. The name of the file that contains the code is given as a command line argument.

·         It initializes the stack pointer to 0x00404000, the program counter to 0x00400000, and all other registers to 0x0000000.

·         It sets flags that govern how the program interacts with the user (see below).

·         It then enters a loop that repeatedly fetches and executes instructions. Your job is to provide the code for the Disassemble, Decode, Execute, Mem, and RegWrite functions, plus any auxiliary functions that you wish to call from those functions.

·         It provides simulated data memory starting at address 0x00401000 and ending at address 0x00404000. It stores instructions together with data in the same memory array. You may assume that the simulated instructions will not address memory illegally. An instruction not among those listed above should cause the program to terminate gracefully.

The framework code support several command line options:

·         -i : runs the program in "interactive mode". In this mode, the program prints a ">" prompt and waits for you to type a return before simulating each instruction in the file given as a command line argument. If you type a "q" (for "quit") followed by a return, the program exits. If this option isn't specified, the only way to terminate the program is to have it simulate an instruction that's not one of those listed in the previous section.

·         -r : prints all registers after the execution of an instruction. If this option isn't specified, only the register that was affected by the instruction should be printed. For a branch, a jump, or a store, the framework code prints a message saying that no registers were affected. (Hint: Your code needs to signal when a simulated instruction doesn't affect any registers by returning an appropriate value in the changedReg argument to RegWrite.)

·         -m : prints all data memory locations that contain nonzero values after the execution of an instruction. If this option isn't specified, only the memory location that was affected by the instruction should be printed. For any instruction that's not sw, the framework code prints a message saying that no memory locations were affected. (Hint: Your code needs to signal when a simulated instruction does not affect memory, by returning an appropriate value in the changedMem argument to Mem.)

·         -d : is a debugging flag that you might find useful. We will never specify -d, so you can use it for whatever you want.

All the printing in the framework code is done in the PrintInfo function. You will need to supply a Disassemble function that will also do some printing.

Do not change the framework code or add any more source files. All your code should go in the empty functions that need filling in or in additional functions called by your code.

Disassembly:

The Disassemble function is called by the simulator to print the assembly language version of each instruction. You will need to supply this function. Your output for each instruction should be in exactly this form:

 
instr   arg1, arg2, arg3

with a tab between the instruction name and the first argument and a comma and space between subsequent arguments.

The instruction name is the standard mnemonic as listed on pages A-54 to A-75. The arguments are register numbers or immediate values. A register operand of an instruction should be written with a dollar sign and a number, for instance $2, not $v0.

Immediates should be in signed decimal, except for in these cases:

·         when the immediate is a branch offset or jump target, write the absolute address of the target instruction instead of the offset. The addresses should be in hex and 8 digits long.

·         the argument to andi, ori, and lui should be written as a 16-bit unsigned hex number (padded to 4 digits, like "0x08ab")

Some instructions do not have all three arguments. Output them with one or two arguments just as spim would write them.

Here is example disassembly output for a three instruction sequence starting at address 00400000 and containing a two-instruction loop (your actual simulator output will contain more information per instruction than shown below):

ori   $5, $5, 0x00ff
addi  $5, $5, -1
beq   $5,$0,0x00400004

Testing:

We have provided two tests for you: test1 includes most of the instructions you need to implement for part 1; test2 includes some of those instructions, plus the memory access instructions. For each test (test1 and test2) there are actually three files: testN.s, which is the assembly code; testN.dump, which is the binary code your simulator will take as input; and testN.out, which is the correct output for your simulator. You can run the tests separately by typing make test1 or make test2. When you are fairly certain everything works, you can run them consecutively by typing make test.

The test files do not include all the instructions you need to implement, and this is done on purpose. You should be able to easily create your own test files (by writing .s files and dumping them using spim) and test your code thoroughly.

Final Remarks:

Programs that run under this simulator also run under spim. However, there are a few rules you need to follow as you write your own programs that run both under this small simulator and under spim:

·         Do not depend on any assembler directives. In particular, you can't use directives to set up memory. Instead, you must write MIPS code yourself to set up any values in memory that you need to use.

·         Do not use spim's normal data-memory section (0x10000000 to 0x10001000) or rely on the fact that the stack is between 0x7fff0000 and 0x80000000. Instead, simply assume that $sp ($29) is in a reasonable place when your program begins.

·         Basically, do not use absolute addresses except the addresses of instructions.

·         Obviously, do not use any instructions that aren't implemented by your simulator. Watch out for MIPS pseudo-instructions that expand to multiple machine instructions; you may not have implemented some of the expanded instructions. If you are in doubt, you can load the MIPS assembly source file in xspim to see how the instructions are translated.

To get your assembly code working correctly with this project, do the following:

·         Write your assembly code normally (with the limitations outlined above).

·         Create the dump file in spim. To do this, load you program into spim (probably want to test it while you are at it), then give spim the "dump" command. This will create a file called "spim.dump" in your working directory that contains binary machine instructions corresponding to the assembler source file.

·         Load the dump file into your simulator.

If your MIPS code runs under spim but not in your simulator, it either means you did not follow the above rules or your simulator has a bug in it, or both.

REMEMBER: the grading will be done almost entirely by automated scripts. Your output must exactly match the specification, which makes correctness the primary goal of this project. Make sure you know exactly how to output all kinds of instructions.

 
 
 
 

This page last modified 03/05/2006, by Hayden (added updates)