Skip to content

Assignment 3

Pavel Kryukov edited this page Apr 13, 2017 · 3 revisions
** Note: this assignment is finished.**

Introduction

In this assignment you will implement 1st simulator — single-cycle implementation.

All requirements remain the same as in previous tasks


Task

You should create a new branch task_3 and create four new files:

    func_sim/func_sim.h
    func_sim/func_sim.cpp
    func_sim/main.cpp
    func_sim/Makefile

where class MIPS will be implemented. Notice that you should create your own Makefile: you may modify Makefiles from previous tasks or create it from scratch. In both cases, you are recommended to read our Makefile guide

Your simulator must support every instruction in our list.

Single-cycle implementation

Single-cycle is the simplest architecture implementation. It is based on three basic states:

  • All operations are executed strongly sequentially
  • Execution of an instruction is not started until the previous one is completely executed (no overlapping)
  • All instructions take the same amount of time – a single cycle

These 3 postulates make development of functional simulator very easy. Simulator will have structure with internal state, standalone instructions and one method that will execute instructions.

Internal state

The internal state of single-cycle implementation will be very simple. We are not going to emulate latches, combination circuits etc. — the only things to emulate are explicit data storages:

  • register file
  • memory
  • program counter
     class MIPS {
          // storages of internal state
          RF* rf;
          uint32 PC;
          FuncMemory* mem;
     };
Register file

Register file must be simulated as a class shell around array/vector of registers:

   enum RegNum {
      /// ....
      MAX_REG
   };

   class RF {
       uint32 array[MAX_REG];
   public:
       // ...
       uint32 read( RegNum index) const;
       void write( RegNum index, uint32 data);
       void reset( RegNum index); // clears register to 0 value
       // ....
   };
Note: MIPS $zero register can not be overwritten!
Memory

You've implemented functional memory model during A1. We are going to reuse it.

PC

Program counter is a stand-alone register that can be stored in MIPS class. Two methods have to work with it:

   uint32 fetch() const { return mem->read( PC); }
   void updatePC( const FuncInstr& instr) { PC = instr->new_PC; }
Instructions

In A2, you've implemented class FuncInstr. You have to extend it with fields of register values:

   class FuncInstr {
       // ...
       uint32 v_src1;
       uint32 v_src2;
       uint32 v_dst;
       uint32 mem_addr;
       uint32 new_PC;
       // ...
   };

It may look useless for single-cycle implementation, but we'll need it for pipelines in future;

Execution

Each operation must be presented as void-void micromethod inside FuncInstr class. Method execute selects required method either by switch/case or function pointer (more preferable).

   class FuncInstr {
       // ...
       void add() { v_dst = v_src1 + v_src2; }
       void sub() { v_dst = v_src1 - v_src2; }
       void mul();
       // ...
       void execute();
   };
Hint: You may add pointer to function and/or other fields to isaTable
PC update

Branches and jumps have to update program counter, PC. Often these instructions require current PC as a base to a new one. So, we have to pass PC to FuncInstr constructor and store it inside:

    class FuncInstr {
        const uint32 PC;
        uint32 new_PC;
        FuncInstr( uint32 bytes, uint32 PC = 0);
    };

    FuncInstr( uint32 bytes, uint32 PC) : instr( bytes), PC(PC) {
    // ...
int main()

Class MIPS has two public methods:

    class MIPS {
    public:
        MIPS();
        void run( const string&, uint instr_to_run);
    };

Entry point must initialize MIPS and start values;

   int main( int argc, char** argv) {
       MIPS* mips = new MIPS;
       mips->run(/* argv[1] */, /* argv[2] */);
   }
Main loop

Let's look at run(..). This method must load a trace from disk and store it into the memory, and initiate main loop:

     void MIPS::run( const string& tr, uint instr_to_run);
     // load trace
     this->PC = startPC;
     for (uint i = 0; i < instr_to_run; ++i) {
         uint32 instr_bytes;
         // Fetch
         // Decode and read sources
         // Execute
         // Memory access
         // Writeback
         // Update PC
         // Dump
     }
Fetch

As we mentioned before, fetch is read from memory by address stored in PC. Data is stored in instr_bytes variable.

    instr_bytes = fetch();
Decode

Decode stage is performed by disassembler you've completed in A2.

   FuncInstr instr( instr_bytes, PC);
Read sources

Sources read must be implemented in separate class MIPS method

   class FuncInstr {
       int get_src1_num_index() const;
       int get_src2_num_index() const;
   };

   void MIPS::read_src( FuncInstr& instr) {
       // ...
       instr.v_src1 = rf->read( instr.get_src1_num_index());
       instr.v_src2 = rf->read( instr.get_src2_num_index());
       // ...
   }
Execution

Simple call of execute method:

     instr.execute();
Memory access

Again, you have to create standalone methods for loads and stores:

    void MIPS::load( FuncInstr& instr) {
        instr.v_dst = mem->read( instr.mem_addr);
    }
    void MIPS::store( const FuncInstr& instr) {
        mem->write( instr.mem_addr, instr.v_dst);
    }
    void MIPS::ld_st( FuncInstr& instr) {
        // calls load for loads, store for stores, nothing otherwise
    }
Writeback

Method wb( const FuncInstr& instr) should be very similar to read_src.

Update PC

Again, only 1 line required:

    void MIPS::updatePC( const FuncInstr& instr) { PC = instr.new_PC; }
Dump

Execution trace must be dumped to the standard output. You may use operator<< that you've created in A2, but you have to extend its output with values:

    std::cout << instr << std::endl;
    // add $t1 [0x0000000F], $t2 [0x0000001A], $t3 [0x00000029]

Summary

As you can see, almost every stage of simulator is only 1-2 lines long. This encapsulation will significantly help us on pipeline simulator development, so please try to keep it in your code. Good luck!

Validation

Your simulator should be built by make funcsim. Test programs and instructions are available in your branch, <branch-root>/tests/samples directory. We're going to extend our trace coverage before deadline and going to keep you in touch.

Clone this wiki locally