/// EE 3755 -- Spring 2002 -- Computer Organization
//
/// Example used in class 8 May 2002
//
/// Hardwired Control, Multicycle MIPS, Day 5
// 
//
//  Will be added to each day.
//
 /// Day 1:  29 April 2002
//
// Started moving memory out of the processor by adding ports for an
// external memory.  The module system (at the end of this file)
// connects cpu to the memory.  Not in a working state.
//
// Copied functional simulator code.
// Added system module that instantiates cpu and memory.
// Added new ports.
//
 /// Day 2:  1 May 2002
//
// Removed old mem code.
// Added states for instruction fetch, decode, and execute.
// Was working during class, but by the end of class was in a
// non-working state.
//
 /// Day 3: 3 May 2002
//
// Got states working.
// Re-wrote ID to take advantage of separate ALU for arithmetic and log. inst.
// Re-wrote ID to take advantage of separate ALU for branches.
//
 /// Day 4: 6 May 2002
//
// Added load instruction.
// Started store instruction.
//
 /// Day 5: 8 May 2002
//
// Got lb working.
// Finished store instruction.
// This file does not include changes to overlap IF and ID.


`define MIPS_PROG "tthird.v"

module cpu(exc,data_out,addr,size,we,data_in,mem_error_in,reset,clk);
   input [31:0] data_in;
   input [2:0]  mem_error_in;
   input        reset,clk;
   output [7:0] exc;
   output [31:0] data_out, addr;
   output [1:0]  size;
   output        we;

   wire [31:0]   data_out;
   reg [31:0]    addr;
   reg [1:0]     size;
   reg           we;
   reg [7:0]     exc;

   reg [31:0] pc, npc, nnpc;
   reg [31:0] ir;
   wire [5:0]  opcode, func;
   wire [4:0]  rs, rt, rd, sa;
   wire [15:0] immed;
   wire [25:0] ii;

   reg [31:0] gpr [0:31];

   wire [31:0] simmed, uimmed;
   reg [7:0]  temp;
   
   reg [2:0]  state;
   reg [4:0]  dst;

   reg [74:0] bndl;
   wire [31:0]limmed;
   reg [31:0] bimmed;
   reg [31:0]  rs_val, rt_val, sa_val;
   
   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;

   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_lbu  = 6'h24;
   parameter  O_lb   = 6'h20;
   parameter  O_sw   = 6'h2b;
   parameter  O_sb   = 6'h28;

   // Control Signal Value Names
   parameter  OP_nop = 6'd0;
   parameter  OP_sll = 6'd1;
   parameter  OP_srl = 6'd2;
   parameter  OP_add = 6'd3;
   parameter  OP_sub = 6'd4;
   parameter  OP_or  = 6'd5;
   parameter  OP_and = 6'd6;
   parameter  OP_slt = 6'd7;
   parameter  OP_seq = 6'd8;
   parameter  OP_sne = 6'd9;

   parameter  ST_if = 1;  // Instruction Fetch 
   parameter  ST_id = 2;  // Instruction Decode
   parameter  ST_ex = 3;  // Executing Arithmetic or Logical Instruction
   parameter  ST_eb = 4;  // Executing Branch (Computing condition.)
   parameter  ST_ea = 5;  // Executing Memory (Computing address.)
   parameter  ST_me = 6;  // Performing load or store.

   wire [31:0] alu_out;
   reg [31:0]  alu_a, alu_b;
   reg [5:0]   alu_op;
   
   alu our_alu(alu_out, alu_a, alu_b, alu_op);

   reg [1:0]   me_size;
   reg         me_we, me_se;

   assign      data_out = rt_val;

   always @( state or pc or alu_out )
     case( state )
       ST_if:
         begin
            addr = pc;
            size = 3;
            we = 0;
         end
       
       ST_me:
         begin
            addr = alu_out;
            size = me_size;
            we = me_we;
         end

       default:
         begin
            size = 0;
            addr = pc;
            we = 0;
         end

     endcase

   assign immed = ir[15:0];

   assign {opcode, rs, rt, rd, sa, func} = ir;
   assign ii = ir[25:0];

   assign simmed = {immed[15] ? 16'hffff : 16'h0, immed};
   assign uimmed = { 16'h0, immed };
   assign limmed = { immed, 16'h0 };

   always @( posedge clk )
     if( reset ) begin

        state = ST_if;
        pc = 'h400000;
        npc = pc + 4;
        exc = 0;

     end else
   case( state )

     /// IF: Instruction Fetch
     ST_if:
       begin

          ir = data_in;
          state = ST_id;

       end

     /// Instruction Decode
     ST_id:
       begin

          nnpc = npc + 4;  // Reassigned below.

          rs_val = gpr[rs];
          rt_val = gpr[rt];
          sa_val = { 27'd0, sa };

          bndl = 75'bx;

          case( opcode )

            O_tyr:
              case( func )
                F_add: bndl = {rd, rs_val, OP_add, rt_val };
                F_sub: bndl = {rd, rs_val, OP_sub, rt_val };
                F_sll: bndl = {rd, sa_val, OP_sll, rt_val };
                F_jr: nnpc = rs_val;
                default: exc = 1;
              endcase

            O_addi: bndl = {rt, rs_val, OP_add, simmed };
            O_andi: bndl = {rt, rs_val, OP_and, uimmed };
            O_ori: bndl = {rt, rs_val, OP_or, uimmed };
            O_lui: bndl = {rt, rs_val, OP_or, limmed };
            O_bne: bndl = {5'd0, rs_val, OP_sne, rt_val };
            O_beq: bndl = {5'd0, rs_val, OP_seq, rt_val };
            O_j: nnpc = {npc[31:28],ii,2'd0};
            O_jal: begin gpr[31] = nnpc;  nnpc = {npc[31:28],ii,2'd0};  end
            O_lb,O_lbu: bndl = {rt, rs_val, OP_add, simmed };
            O_sb: bndl = {5'd0, rs_val, OP_add, simmed };
            //  O_lbu: gpr[rt] = {24'b0, mem[`A(gpr[rs]+simmed)]};
            //  O_sb: mem[`A(gpr[rs]+simmed)]= gpr[rt];
            default: exc = 1;

          endcase

          if( bndl !== 75'bx ) {dst, alu_a, alu_op, alu_b } = bndl;

          case( opcode )
            O_lb: begin me_size = 1; me_se = 1; me_we = 0; end
            O_lbu: begin me_size = 1; me_se = 0; me_we = 0; end
            O_sb: begin me_size = 1; me_se = 0; me_we = 1; end
            default: begin me_size = 0; me_se = 0; me_we = 0; end
          endcase
          
          case( opcode )
            O_beq,O_bne: state = ST_eb;
            O_sb,O_lb,O_lbu: state = ST_ea;
            O_j: state = ST_if;
            default: state = bndl === 75'bx ? ST_if : ST_ex;
          endcase

          bimmed = npc + ( simmed << 2 );
          pc = npc;
          npc = nnpc;

       end

     /// EX: Execute
     ST_ex:
       begin
          if( dst ) gpr[dst] = alu_out;
          state = ST_if;
       end

     /// EXB: Execute Branch
     ST_eb:
       begin
          if( alu_out[0] ) npc = bimmed;
          state = ST_if;
       end

     /// EA: Effective Address
     ST_ea:
       begin
          state = ST_me;
       end

     /// ME: Memory
     ST_me:
       begin
          if( dst != 0 )
            case( size )
              1: gpr[dst] = { me_se & data_in[7] ? 24'hffffff : 24'h0,
                              data_in[7:0] };
              2: gpr[dst] = { me_se & data_in[15] ? 16'hffff : 16'h0,
                              data_in[15:0] };
            endcase
          state = ST_if;
       end
     
   endcase

endmodule

module alu(alu_out, alu_a, alu_b, alu_op);
   output [31:0] alu_out;
   input [31:0]  alu_a, alu_b;
   input [5:0]   alu_op;

   reg [31:0]    alu_out;

   // Control Signal Value Names
   parameter  OP_nop = 6'd0;
   parameter  OP_sll = 6'd1;
   parameter  OP_srl = 6'd2;
   parameter  OP_add = 6'd3;
   parameter  OP_sub = 6'd4;
   parameter  OP_or  = 6'd5;
   parameter  OP_and = 6'd6;
   parameter  OP_slt = 6'd7;
   parameter  OP_seq = 6'd8;
   parameter  OP_sne = 6'd9;

   always @( alu_a or alu_b or alu_op )
     case( alu_op )
       OP_add  : alu_out = alu_a + alu_b;
       OP_and  : alu_out = alu_a & alu_b;
       OP_or   : alu_out = alu_a | alu_b;
       OP_sub  : alu_out = alu_a - alu_b;
       OP_slt  : alu_out = {alu_a[31],alu_a} < {alu_b[31],alu_b};
       OP_sll  : alu_out = alu_b << alu_a;
       OP_srl  : alu_out = alu_b >> alu_a;
       OP_seq  : alu_out = alu_a == alu_b;
       OP_sne  : alu_out = alu_a != alu_b;
       OP_nop  : alu_out = 0;
       default : begin alu_out = 0;  $stop;  end
     endcase
   
endmodule
   

module system(exc,reset,clk);
   input reset, clk;
   output [7:0] exc;

   wire [31:0] cpu_data_out, addr, mem_data_out;
   wire [2:0]  mem_err_out;
   wire [1:0]  size;
   wire        we;

   cpu cpu1(exc,cpu_data_out,addr,size,we,mem_data_out,mem_err_out,reset,clk);
   mips_memory_1p m1(mem_data_out,mem_err_out,addr,size,we,cpu_data_out,clk);
   
endmodule

`include "mips_hc_tb.v"