Course Related

Tools & Style

Other

Project 5

Project 5

MIPS Simulator

In the previous project you wrote a disassembler for MIPS executables. You will now go a step farther and actually execute those MIPS instructions by simulating the hardware of a MIPS machine. Simulation is useful because it makes it possible to inspect the machine state (that is, the contents of the CPU registers, main memory, and cache) at any point in time, a task which would be difficult to achieve in a deterministic fashion on real hardware. Simulation would typically also be used when evaluating a hardware architecture that has not yet been built.

Your simulator will ned to keep track of the the CPU registers and main memory of a MIPS machine. (In a later project we will also consider the cache, but this is ignored for now.) A basic framework for the simulator is provided, please download this. You will modify run.c and fill in the body of the function void run() { }. Do not modify anything outside of run.c!

A number of global variables are available for your use. The program entry point, or address of the first instruction, is stored in the global variable unsigned int entry_point. The CPU registers are provided in the global array unsigned int R[], which of course has 32 elements. Some of these registers (e.g. the stack pointer) will be pre-loaded with appropriate values before run() is called.

To access memory, a set of functions is provided. Note that any addresses used by the MIPS executable (such as the address in entry_point) are valid only in the MIPS machine that is being simulated, and not on the host machine which is running the simulator. For that reason it's necessary to use the following functions to access memory, rather than simply dereferencing the MIPS address as a regular pointer.

The read functions take an address in MIPS address space as a parameter and return the value at that memory location:
unsigned int mem_read_word(unsigned int addr);
unsigned short mem_read_half(unsigned int addr);
unsigned char mem_read_byte(unsigned int addr);

The write functions take an address in MIPS address space and a value as parameters, and store the value at the specified address:
void mem_write_word(unsigned int addr, unsigned int value);
void mem_write_half(unsigned int addr, unsigned short value);
void mem_write_byte(unsigned int addr, unsigned char value);

You will need to maintain a program counter, incrementing it on each instruction. In the case of jump and branch instructions, of course, you'll need to update the program counter as well. Note that branches and jumps are delayed by one instruction; that is, when a branch or jump is taken, the next instruction after the branch or jump is executed anyway. This is a bit counter-intuitive, but is a side effect having an efficient pipelined CPU. Be sure to execute this "delay slot" instruction in all cases.

As with the disassembler project, you may omit floating point instructions. Also, to keep things simple, don't worry about detecting overflow or generating a trap when it occurs (i.e., add will have the same behaviour as addu).

When you encounter a syscall instruction, simply call dosyscall(). This function is part of the framework.

Be sure that R[0] always contains zero. Although you could check any register writes and make sure they're not overwriting R[0], it's probably easier to just initialize R[0] to zero at the start of each new instruction.

Refer to the instruction table in the disassembler project. Also, use the Instruction Set Reference to get more detailed information on any instruction.

It will help if you can disassemble and print each instruction that is being executed, as well as look at the register contents. This is possible with the command line options -d and -r, respectively. However, to enable these you will need to insert the following code into your run.c. You will also need to copy your print.c from project 4 into the simulator source directory.

// Dump registers
if (opt_dumpreg)
  print_registers();

// Print the stack
if (opt_printstack)
  print_stack();

// Print the instruction
if (opt_disassemble)
  print(sim_to_real_addr(pc), 1, pc);

How to Get Started

In case you have not previously compiled C programs on Linux, you will find the following commands helpful.
Extract the project from the tarball: tar -xvzf simulator.tar.gz
Compile the project: make
Delete the executable and object files, leaving your source code intact: make clobber (this is normally not necessary, as make will selectively rebuild the portions of your project that have changed)
Run the project: ./simulate <filename>

For the Adventurous

Note: These suggestions for an extra challenge will be examined (and commented on, if your project works well) but not graded. They will have no impact on the class grades. They are here to provide some direction to those who finish their assignments early and are looking for a way to impress friends and family.

Help and Hints

Ask the TAs for help. We expect to see most students in office hours during the course of the project. Extra hours will be scheduled as needed.

If you suspect a bug in the simulator framework... ask Michael for help. There could be bugs, although none were encountered while creating the example solution to this project.

Answers to Frequenty Asked Questions



Tools

  1. Logisim.
  2. Additional Logisim Components.

Manuals

  1. MIPS Volume 1: Basic MIPS Architecture
  2. MIPS Volume 2: Instruction Set Reference
  3. MIPS Volume 3: Privileged Resources and Systems Programming

Projects

  1. ALU Implementation
  2. Basic Execution
  3. MIPS Processor
  4. MIPS Disassembler
  5. MIPS Simulator
  6. Cache Simulation and Measurement
  7. Multicore Simulator
  8. Final Project