Machine Structures. Fall 2003, UC Berkeley
Project 3: MIPS Instruction Simulator
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
You only need to submit the computer.c file.
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
- 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 10/12/2003, by J. Wawrzynek.
using emacs.