/// LSU EE 3755 -- Fall 2013 -- Computer Architecture
//
/// MIPS Processor Functional Simulator
//
// Implements a small subset of MIPS32 instructions and features.
//
// Time-stamp: <22 November 2013, 13:29:30 CST, koppel@sky.ece.lsu.edu>
//

/// Dependencies

 /// To compile run this the following is also needed:
//
// mips_fs_tb.v      The testbench.  It is included in here for convenience.
// mipsmemmacros.v   Macros needed for memory access.
//
// An "assembled" program, for example tthird.v.  The name of the
//  file is specified in the MIPS_PROG macro, the testbench uses the
//  macro to include the file.

/// Limitations
//
//  This code will probably never implement all of MIPS32.  The intent
//  is to demonstrate basic MIPS simulation techniques as a first step
//  towards developing synthesizable and implementatble descriptions.
//

 /// Specify Program to Load
//
// A quick-and-dirty method of loading a program is used.
// Therefore the description must be re-compiled each time the program
// is changed.  (The program is assembled by SPIM and some perl code.)
//

//  `define MIPS_PROG "tthird.v"
`define MIPS_PROG "tfirst-live.v"
//`define MIPS_PROG "tfirst.v"
`include "mipsmemmacros.v"

module proc(exc,clk);
   input wire clk;
   output reg exc;

   // Storage defined by the MIPS instruction set.
   //
   reg [31:0] pc, npc;
   reg [31:0] gpr [0:31];

   // Memory.
   //
   // The full range of addresses would be mem [0:'hffffffff] however
   // that might make simulation too slow. The range of addresses below
   // covers the region of memory used for code and data by the Spim
   // simulator, which we are using to prepare programs for this
   // simulator.
   //
   reg [7:0]  mem ['h400000:'h400200];

   //
   // Storage needed to implement MIPS.
   //
   reg [31:0] nnpc;
   reg [31:0] ir;

   // Instruction Fields
   //
   reg [5:0]  opcode, func;
   reg [4:0]  rs, rt, rd, sa;
   reg [15:0] immed;
   reg [25:0] ii;

   // Values retrieved from the register file and memory.
   //
   reg [31:0] rs_val, rt_val;
   reg [7:0]  md_byte;
   reg [15:0] md_half;

   // Values computed using instruction fields and register data.
   //
   reg [31:0] simmed, uimmed;
   reg [31:0] addr;  // Used by load and store instructions.

   // Instruction opcodes.  (Not a complete list.)
   //
   parameter  F_add = 6'h20;
   parameter  F_sll = 6'h0;
   parameter  F_srl = 6'h2;
   parameter  F_sub = 6'h22;
   parameter  F_or  = 6'h25;
   parameter  F_jr  = 6'h8;

   // Instruction function-field values. (Not a complete list.)
   //
   parameter  O_tyr  = 6'h0;
   parameter  O_addi = 6'h8;
   parameter  O_j    = 6'h2;
   parameter  O_jal  = 6'h3;
   parameter  O_beq  = 6'h4;
   parameter  O_bne  = 6'h5;
   parameter  O_slti = 6'ha;
   parameter  O_andi = 6'hc;
   parameter  O_ori  = 6'hd;
   parameter  O_lui  = 6'hf;
   parameter  O_lw   = 6'h23;
   parameter  O_lhu  = 6'h25;
   parameter  O_lh   = 6'h21;
   parameter  O_lbu  = 6'h24;
   parameter  O_lb   = 6'h20;
   parameter  O_sw   = 6'h2b;
   parameter  O_sb   = 6'h28;


   always @( posedge clk ) begin

      /// Execute Instruction at pc.
      //
      //  Entire execution is in this one clock cycle.
      //  Since this is a functional simulator, that's fine.
      //  If this were to be synthesized the resulting hardware
      //  would be slow and expensive.

      // Load instruction from memory.
      //
      ir = { mem[pc], mem[pc+1], mem[pc+2], mem[pc+3] };

      // Split instruction into Type-r fields.
      //
      {opcode, rs, rt, rd, sa, func} = ir;

      // Extract immed, in case instruction is Type I.
      //
      immed = ir[15:0];

      // Extract ii, in case instruction is Type J.
      //
      ii = ir[25:0];

      // Prepare signed immediate.
      //
      simmed = {immed[15] ? 16'hffff : 16'h0, immed};

      // Prepare unsigned immediate.
      //
      uimmed = { 16'h0, immed };

      // Retrieve register values.
      //
      rs_val = gpr[rs];
      rt_val = gpr[rt];

      // Provisionally increment next-next program counter.
      // (If we are executing a taken control-transfer insn nnpc will be
      // changed, that's why it's provisional.)
      //
      nnpc = npc + 4;

      // Compute address used for load and store instructions.
      //
      // (The 'A macro adjusts the address, this is to accommodate our
      //  quick-and-dirty machine model. The next processor model
      //  doesn't need to do this.)
      //
      addr = `A( rs_val + simmed );

      case ( opcode )

        // Handle Type-R instructions.
        //
        O_tyr:

          // Look at the func field to determine what kind of type R
          // instruction it is.
          //
          case ( func )
            F_add: gpr[rd] = rs_val + rt_val;
            F_sub: gpr[rd] = rs_val - rt_val;
            F_sll: gpr[rd] = rt_val << sa;
            F_jr:  nnpc = rs_val;
            // cadence translate_off
            default:
            begin
               $display("Unexpected func-field value: 0x%x\n",func);
               $stop;
            end
            // cadence translate_on
          endcase

        //
        // Handle Type-I and Type-J instructions.
        //

        O_addi: gpr[rt] = rs_val + simmed;
        O_andi: gpr[rt] = rs_val & uimmed;
        O_ori:  gpr[rt] = rs_val | uimmed;
        O_slti: gpr[rt] = rs_val < simmed;

        O_lui:  gpr[rt] = {immed,16'h0};

        // Control-Transfer Instructions
        //
        O_bne:  if ( rt_val != rs_val  ) nnpc = npc + ( simmed << 2 );
        O_beq:  if ( rt_val == rs_val  ) nnpc = npc + ( simmed << 2 );
        O_j:    nnpc = {npc[31:28],ii,2'd0};
        O_jal:  begin gpr[31] = nnpc;  nnpc = {npc[31:28],ii,2'd0};  end


        // Load and Store Instructions
        //
        O_lw: gpr[rt] = { mem[addr], mem[addr+1], mem[addr+2], mem[addr+3] };
        O_lhu: gpr[rt] = { 16'h0, mem[addr], mem[addr+1] };
        O_lh: begin
           md_half = { mem[addr], mem[addr+1] };
           gpr[rt] = { md_half[15] ? 16'hffff : 16'h0, md_half };
        end
        O_lbu: gpr[rt] = { 24'b0, mem[addr] };
        O_lb: begin
           md_byte = mem[addr];
           gpr[rt] = {  md_byte[7] ? 24'hffffff : 24'h0,  md_byte };
        end
        O_sb: mem[addr] = rt_val;

        // cadence translate_off
        default:
        begin
           $display("Unexpected opcode: 0x%x\n",opcode);
           $stop;
        end
        // cadence translate_on

      endcase

      // Reset zero register.
      gpr[0] = 0;

      // Update the program counters.
      pc = npc;     // pc now holds the next instruction we execute ...
      npc = nnpc;   // ... and npc now holds the instruction after that.

   end

endmodule

// cadence translate_off
`include "mips_fs_tb.v"
// cadence translate_on