/// LSU EE 3755 -- Spring 2002 -- Computer Organization
//
/// Hardwired Control, Multicycle MIPS
//
// Time-stamp: <29 April 2002, 11:44:25 CDT, koppel@sol>
//

`define MIPS_PROG "uc.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;

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

   // MIPS Registers
   //
   reg [31:0] gpr [0:31];
   reg [31:0] pc, npc;
   reg [31:0] ir;

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

   // Values Derived From Immediates and Read From Register File
   //
   reg [31:0] simmed, uimmed, limmed;
   reg [31:0] rs_val, rt_val, sa_val;

   reg [75:0]     bndl;

   // ALU Connections
   //
   wire [31:0] alu_out;
   reg [31:0]  alu_a, alu_b;
   reg [5:0]   alu_op;

   // Processor Control Logic State
   //
   reg [2:0] state;
   
   reg [4:0] wb_rd;     // Register number to write.
   reg       me_we;     // we value to use in state st_me
   reg [1:0] me_size;   // size value to use in state st_me

   alu our_alu(alu_out, alu_a, alu_b, alu_op);

   // Values for the MIPS funct field.
   //
   parameter  F_sll = 6'h0;
   parameter  F_srl = 6'h2;
   parameter  F_add = 6'h20;
   parameter  F_sub = 6'h22;
   parameter  F_or  = 6'h25;

   // Values for the MIPS opcode field.
   //
   parameter  O_rfmt = 6'h0;
   parameter  O_j    = 6'h2;
   parameter  O_beq  = 6'h4;
   parameter  O_bne  = 6'h5;
   parameter  O_addi = 6'h8;
   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_sw   = 6'h2b;
   parameter  O_sb   = 6'h28;

   // Processor Control Logic States
   //
   parameter  ST_if = 1;
   parameter  ST_id = 2;
   parameter  ST_ex = 3;
   parameter  ST_ex_addr = 5;
   parameter  ST_ex_cond = 6;
   parameter  ST_ex_targ = 7;
   parameter  ST_me = 4;

   // ALU Operations
   //
   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;

   // Handy Constant
   //
   parameter  R0 = 5'd0;


   /// Set Memory Connection Values: addr, we, and size.
   //
   always @( state or pc or alu_out or me_size or me_we )
     case( state )
       ST_if   : begin addr = pc;       we = 0;      size = 3;       end
       ST_me   : begin addr = alu_out;  we = me_we;  size = me_size; end
       default : begin addr = pc;       we = 0;      size = 0;       end
       // Note: addr is set for default case to simplify synthesized hardware.
     endcase
   
   always @( posedge clk )
     if( reset ) begin

        state = ST_if;
        exc   = 0;
        pc    = 32'h400000;
        npc   = pc + 4;
        
     end else
     case ( state )

       ///       Instruction Fetch
       ST_if:  
         begin
            ir    = data_in;
            state = ST_id;
         end

       ///       Instruction Decode (and Register Read)
       ST_id:
         begin

            {opcode,rs,rt,rd,sa,func} = ir;
            ii     = ir[25:0];
            immed  = ir[15:0];
            
            simmed = { immed[15] ? 16'hffff : 16'h0, immed };
            uimmed = { 16'h0, immed };
            limmed = { immed, 16'h0 };

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

            // Set alu_a, alu_b, alu_op, and wb_rd.
            //
            case( opcode )

              O_rfmt:
                // R-Format Instructions
                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};
                  default:
                    begin bndl = {rd, sa_val, OP_sll, rt_val}; exc = 1; end
                endcase

              // I- and J-Format Instructions
              O_lbu:  bndl = {rt, rs_val, OP_add, simmed };
              O_sb:   bndl = {R0, rs_val, OP_add, simmed };
              O_lui:  bndl = {rt, rs_val, OP_or,  limmed };
              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_slti: bndl = {rt, rs_val, OP_slt, simmed };
              O_j:    bndl = {R0, rs_val, OP_nop, simmed };
              O_bne, O_beq: bndl = {R0, rs_val, OP_seq, rt_val };
              default: begin bndl = {R0, rs_val, OP_seq, rt_val }; exc = 1; end
            endcase

            {wb_rd,alu_a,alu_op,alu_b} = bndl;

            // Needed for a store instruction, doesn't hurt others.
            data_out = rt_val;

            // Set me_size and me_wb
            //
            case( opcode )
              O_lbu   : begin me_size = 1;  me_we = 0; end
              O_sb    : begin me_size = 1;  me_we = 1; end
              default : begin me_size = 0;  me_we = 0; end
            endcase

            pc = npc;

            // Set npc, branch instruction may change npc.
            //
            case( opcode )
              O_j     : npc = { pc[31:28], ii, 2'b0 };
              default : npc = pc + 4;
            endcase

            case( opcode )
              O_lbu, O_sb  : state = ST_ex_addr;
              O_bne, O_beq : state = ST_ex_cond;
              O_j          : state = ST_if;
              default      : state = ST_ex;
            endcase
         end

       ///       Execute (ALU instructions)
       ST_ex:
         begin
            if( wb_rd ) gpr[wb_rd] = alu_out;
            state = ST_if;
         end

       ///       Execute (Compute Effective Address for Loads and Stores)
       ST_ex_addr:
         begin
            state = ST_me;
         end

       ///       Execute (Compute Branch Condition)
       ST_ex_cond:
         begin
            if( opcode == O_beq == alu_out ) begin
               alu_a  = pc;
               alu_b  = simmed << 2;
               alu_op = OP_add;
               state  = ST_ex_targ;
            end else begin
               state = ST_if;
            end
         end

       ///       Execute (Compute Branch Target)
       ST_ex_targ:
         begin
            npc = alu_out;
            state = ST_if;
         end

       ///       Memory
       ST_me:
         begin
            if( wb_rd ) gpr[wb_rd] = data_in;
            state = ST_if;
         end

       default:
         begin
            $display("Unexpected state.");
            $stop;
         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 = 0;
   parameter  OP_sll = 1;
   parameter  OP_srl = 2;
   parameter  OP_add = 3;
   parameter  OP_sub = 4;
   parameter  OP_or  = 5;
   parameter  OP_and = 6;
   parameter  OP_slt = 7;
   parameter  OP_seq = 8;
   
   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_nop  : alu_out = 0;
       default : begin alu_out = 0;  $stop;  end
     endcase
   
endmodule

// exemplar translate_off

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"