This project is due October 24th, 2007.
This is an individual assignment; you must work alone. Submit your solution,
a file named proj3.c, as proj3.
Copy the directory ~cs61c/files/proj/3 to your home directory
and name it "proj3".
It includes several files: sim.c,
computer.h, computer.c, proj3.c, proj3.h,
These are described below.
In this project, you will create an instruction interpreter for a subset of MIPS code.
It will fetch, disassemble, decode, and execute MIPS machine instructions.
You're creating what is effectively a miniature version
of MARS! There is one important difference, though—MARS takes in
assembly language source files, not .dump files, so it contains an
The files sim.c,
computer.h, computer.c, proj3.c and proj3.h
comprise a framework for a MIPS simulator.
Complete the program by adding code to proj3.c.
Your simulator must be able to simulate the machine code versions
of the following MIPS machine instructions:
addu Rdest, Rsrc1, Rsrc2
addiu Rdest, Rsrc1, imm
subu Rdest, Rsrc1, Rsrc2
sll Rdest, Rsrc, shamt
srl Rdest, Rsrc, shamt
and Rdest, Rsrc1, Rsrc2
andi Rdest, Rsrc, imm
or Rdest, Rsrc1, Rsrc2
ori Rdest, Rsrc, imm
lui Rdest, imm
slt Rdest, Rsrc1, Rsrc2
beq Rsrc1, Rsrc2, raddr
bne Rsrc1, Rsrc2, raddr
lw Rdest, offset (Radd)
sw Rsrc, offset (Radd)
Once complete, your solution program will be able to simulate real programs that do
just about anything that can be done on a real MIPS, with the notable
exceptions of floating-point math and interrupts.
The framework code begins by doing the following.
It reads the machine code into "memory", starting at "address" 0x00400000.
(In keeping with the MARS convention, addresses from 0x0000000
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,
it initializes all other registers to 0x00000000,
and it initializes the program counter to 0x00400000.
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.
It sets flags that govern how the program interacts with the user.
It then enters a loop that repeatedly fetches and executes instructions,
printing information as it goes:
the machine instruction being executed, along with its address and disassembled form;
disassembled must call exit(0) if an unsupported operation is detected;
the new value of the program counter;
information about the current state of the registers;
information about the contents of memory.
Your job is to provide the code for the disassembled and simulateInstr
functions, plus any auxiliary functions that are appropriate.
You may assume that the simulated instructions will not address instruction memory illegally.
If the program encounters an instruction that's not one of the ones just listed,
it should quit.
The framework code supports several command line options:
Almost all the printing in the framework code is done in the printInfo function.
Also printed is the output of the disassembled function, which
requires some special attention.
Although it just prints the instructions and its operands in text,
we will be grading your project with automated scripts.
Therefore, the output must follow this part of the specification exactly.
Here are the details on the output format:
The disassembled instruction must have the instruction name
followed by a "tab" character (In C, this character is '\t'), followed by a comma-and-space separated list of the operations.
For addiu, srl, sll, lw and sw,
the immediate value must be
printed as a decimal number (with the negative sign, if
required) with no leading zeroes unless the value is exactly zero
(printed as 0).
For andi, ori, and lui, the immediate must be printed in
hex, with a leading 0x and no leading zeroes unless the value is exactly zero
(which is printed as 0x0).
For the branch and jump instructions, the target must be
printed as a full 8-digit hex number, even if it has leading
zeroes. (Note the difference between this format and the branch
and jump assembly language instructions that you write.) Finally,
the target of the branch or jump should be printed as an absolute address, rather
than being PC relative.
All hex values must use lower-case letters and have the leading 0x.
Argument fields must be separated by a comma followed by a single space.
Registers must be identified by number, with no leading zeroes (e.g. $10
and $3) and not by name
As an example, for a store-byte instruction you might return "sb\t$10, -4($21)".
You are responsible for managing the memory associated with any string you return.
If you use malloc(), you will somehow need to free() the memory later (note
that you cannot modify computer.c). Hint: static or global variables.
Is a lw or sw instruction accesses an invalid memory address, your code must print exactly the same
error message as in the contents() function in computer.c and then call exit(0)
Here are examples of good instructions:
addiu $1, $0, -2
lw $1, 8($3)
srl $6, $7, 3
ori $1, $1, 0x1234
lui $10, 0x5678
bne $3, $4, 0x00400044
Here are examples of bad instructions:
addiu $1, $0, 0xffffffff # shouldn't print hex for addiu
sw $1, 0x8($3) # shouldn't print hex for sw
sll $a1, $a0, 3 # should use reg numbers instead of names
srl $6,$7,3 # no spaces between arguments
ori $1 $1 0x1234 # forgot commas
lui $t0, 0x0000ABCD # hex should be lowercase and not zero extended
j 54345 # address should be in hex
jal 00400548 # forgot the leading 0x
bne $3, $4, 4 # needs full target address in hex
The files sample.s and
sample.output in the proj3 directory
provide an example output that you may use for a "sanity check".
We do not include any other test input files for this project.
You must write the test cases in MIPS, use MARS (mars-cs61c) to assemble them,
and then dump the binary code.
See the "Hints" section for suggestions on how to do this.
Please don't change the framework code. If we find errors in it, we'll provide updates.
Most of you will be tempted to immediately write a first version of the
entire project before properly testing. This will certainly complicate
your debugging. We recommend the following procedure for approaching
Read and understand the project specification and source code.
Before writing any C code, write a simple test in MIPS that
tests just one particular instruction; assemble it to a
binary using MARS (see below). Note that some of the instructions depend on
far more code than others. For example, the lw and sw
instructions require the memory code to work properly. Consider
this when choosing your instruction.
Code the minimal amount of C required to support the
instruction that you selected in #2 and debug it using your
test case. A first step might be to write the disassembly helper
functions that your test case requires.
Choose another instruction to test. It may make sense to
choose a set of instructions that are similar to the first one
that you chose to support. Write a second test file that only
uses this set of instructions.
Repeat steps 3 and 4 until all instructions are supported.
Clean up your code. Make sure to format it properly and add comments, this
will help you debug and understand your own work better.
Submit your solution.
A first step in testing is to write test code in MIPS. Here are some
important guidelines to consider as you write your MIPS test code:
Take a deep breath and forget that you are writing a simulator.
Write a simple, short, MIPS program that does something sensible.
The more sensible your program,
the easier it is to verify that your simulator works.
Naturally, you should only use instructions that you are explicitly supporting.
Our simulator starts program memory at 0x400000, data
memory at 0x401000,
and the stack at 0x00404000. However, MARS
initializes the stack pointer at 0x7ffffeffc. When writing test
programs, make certain that this difference will not create any
MARS places anything that follows the .data assembler directive
sequentially in memory.
However, this will not be reflected in the binary file that MARS
dumps. That dump file only contains instructions. Therefore,
instead of depending on MARS
to load data memory for you, you
should use instructions.
For example, suppose you want to write a MIPS program that uses an array
of 5 words called foo which is initialized with the integers 1, ..., 5.
Normally, you would write something like:
foo: .word 1,2,3,4,5
For this project, you would not use the .data section.
Instead you would have your program initialize the array:
lui $t0, 0x1001
ori $t0, 0x0000
addiu $t1, 1
sw $t1, 0($t0)
addiu $t1, 2
sw $t1, 4($t0)
addiu $t1, 3
sw $t1, 8($t0)
addiu $t1, 4
sw $t1, 12($t0)
addiu $t1, 5
sw $t1, 16($t0)
You could also do this in a loop. This may seem a bit tedious and time
consuming but it greatly simplifies the simulator.
Know what to expect of your tests. For example, if your test program
copies a bunch of data from one region of memory to another, you should
know what memory is supposed to look like after your program finishes.
The command line arguments -r, -m, and -c
will be useful for generating
output that helps provide evidence of your simulation's correctness.
The procedure for generating input files for the simulator is straightforward.
Here are the steps:
Write your test program in MIPS. Make sure you only use
supported instructions, and avoid assembler directives that set
up .data memory. Suppose your test file is named test0.s.
Terminate your program with a single unsupported instruction
(e.g., slti). As mentioned previously, attempting to execute an
unsupported instruction will cause the simulator to quit.
Be sure to use mars-cs61c on the instructional machines or download
~cs61c/bin/mars/mars-cs61c.jar to your local machine.
Debug your MIPS code with MARS.
Dump the code from MARS using the Save Dump button in the lower left of the data
segment window. Be sure to select the .text segment in the dropdown before
doing this, or you might accidentally dump the data memory. You should also
select the Binary dump format for this project. MARS will dump the binary
instruction into a file of your choosing.
Now, test0.dump is a valid input to the simulator. We
recommend that you save all of your .s and .dump test files in
an orderly fashion. This way, when you think you are done with
the simulator, you will have a comprehensive battery of tests to
put it through before submitting.
To view a binary dump file in emacs, open it like you normally would
and type M-x hexl-mode.
Use gdb. It can save you so much trouble.
For example, you can type "make" at the gdb prompt
and gdb will run the makefile for you.
This is convenient because you don't have to quit out of gdb to recompile your code.