/// LSU EE 3755 -- Fall 2001 -- Computer Organization
//
/// Microcoded MIPS
//
// Time-stamp: <3 December 2001, 16:29:36 CST, koppel@sol>
//


`define MIPS_PROG "prog1.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 [7:0]     exc;

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

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

   assign      {opcode,rs,rt,rd,sa,func} = ir;

   // Values From Register File
   //
   wire [31:0] rs_val = gpr[rs];
   wire [31:0] rt_val = gpr[rt];

   // ALU Connections
   //
   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);

   // Branch Condition
   //
   reg         branch_cond;

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

   // ALU Operations
   //
   parameter  op_nop = 5'd0;
   parameter  op_sll = 5'd1;
   parameter  op_srl = 5'd2;
   parameter  op_add = 5'd3;
   parameter  op_sub = 5'd4;
   parameter  op_or  = 5'd5;
   parameter  op_and = 5'd6;
   parameter  op_slt = 5'd7;
   parameter  op_seq = 5'd8;
   parameter  op_sne = 5'd9;
   parameter  op_exc = 5'd31;


   ///
   /// Control Signal Constants
   ///

   // PC/NPC Assignment
   //
   parameter PC_000 = 2'd0;   // Do nothing.
   parameter PC_ADV = 2'd1;   // npc <- pc
   parameter PC_BRN = 2'd2;   // npc <- alu_out if branch_cond true.
   parameter PC_ALU = 2'd3;   // npc <- alu_out

   // Memory Address, Write Enable, and Size
   //
   parameter ADDR_PC   = 1'b0;
   parameter ADDR_ALU  = 1'b1;
   parameter WE_0      = 1'b0;
   parameter WE_1      = 1'b1;
   parameter SIZE_OFF  = 2'd0;
   parameter SIZE_BYTE = 2'd1;
   parameter SIZE_HALF = 2'd2;
   parameter SIZE_WORD = 2'd3;

   // Memory Operations (For compactness combine three constants.)
   //
   parameter MEM_IF    = {ADDR_PC, WE_0,SIZE_WORD};
   parameter MEM_LB    = {ADDR_ALU,WE_0,SIZE_BYTE};
   parameter MEM_SB    = {ADDR_ALU,WE_1,SIZE_BYTE};
   parameter MEM_00    = {ADDR_ALU,WE_0,SIZE_OFF};

   // IR and branch condition (branch_cond) Register Write Enables
   //
   parameter IR_000  = 1'b0;   // No change to ir.
   parameter IR_DIN  = 1'b1;   // ir <- data_in
   parameter BR_000  = 1'b0;   // No change to branch_cond.
   parameter BR_ALU  = 1'b1;   // branch_cond <- alu_out.

   // GPR Writeback
   //
   parameter DEST_00 = 2'd0;   // Write register zero.
   parameter DEST_RD = 2'd1;   // Write register specified in rd field.
   parameter DEST_RT = 2'd2;   // Write register specified in rt field.
   parameter GPR_ALU = 1'b0;   // Write register with alu_out.
   parameter GPR_MEM = 1'b1;   // Write register with data_in.

   // Handy combinations of ir, branch_cond, and GPR control signal constants.
   //
   parameter W_RD_ALU = {IR_000, BR_000, DEST_RD, GPR_ALU }; // gpr[rd]=alu_out;
   parameter W_RT_ALU = {IR_000, BR_000, DEST_RT, GPR_ALU }; // etc.
   parameter W_RT_MEM = {IR_000, BR_000, DEST_RT, GPR_MEM };
   parameter W_IR_DIN = {IR_DIN, BR_000, DEST_00, GPR_MEM };
   parameter W_BR_ALU = {IR_000, BR_ALU, DEST_00, GPR_MEM };
   parameter W_00_000 = {IR_000, BR_000, DEST_00, GPR_MEM };

   // ALU Operation Control Signal Constants
   //
   parameter ALU_DSP = 1'b1;   // Get alu operation from dispatch table.
   parameter ALU_ADD = 1'b0;   // Nothing fancy, just add.
   parameter ALU_000 = 1'b0;   // Add, but intend to ignore result.

   // ALU A Input Control Signal Constants
   //
   parameter ALU_A_00 = 2'd0;
   parameter ALU_A_RS = 2'd1;
   parameter ALU_A_SA = 2'd2;
   parameter ALU_A_PC = 2'd3;

   // ALU B Input Control Signal Constants
   //
   parameter ALU_B_00 = 3'd0;  // rt_val, but intend to ignore result.
   parameter ALU_B_RT = 3'd0;  // Contents of register number rt
   parameter ALU_B_UI = 3'd1;  // Unsigned Immediate
   parameter ALU_B_SI = 3'd2;  // Signed Immediate
   parameter ALU_B_BI = 3'd3;  // Branch Displacement
   parameter ALU_B_LI = 3'd4;  // Upper Immediate
   parameter ALU_B_PI = 3'd5;  // PC to use after a reset.
   parameter ALU_B_JI = 3'd6;  // Jump Target (region, j and jal instructions)
   parameter ALU_B_04 = 3'd7;  // Four

   // Next Microinstruction Constants
   //
   parameter NXT_IF =  2'd1;   // Go to instruction fetch microinstruction.
   parameter NXT_SEQ = 2'd2;   // Go to next microinstruction.
   parameter NXT_DSP = 2'd3;   // Use dispatch table to get next micro_pc.

   // Microinstruction Labels  (See micro_rom initialization.)
   //
   parameter MPC_IF    = 6'd1;
   parameter MPC_ID    = 6'd2;
   parameter MPC_I1    = 6'd3;
   parameter MPC_I2    = 6'd4;
   parameter MPC_I3    = 6'd5;
   parameter MPC_R2_EX = 6'd6;
   parameter MPC_R1_EX = 6'd7;
   parameter MPC_IU_EX = 6'd8;
   parameter MPC_IS_EX = 6'd9;
   parameter MPC_IL_EX = 6'd10;
   parameter MPC_BR_CO = 6'd11;
   parameter MPC_BR_TA = 6'd12;
   parameter MPC_LB_EA = 6'd13;
   parameter MPC_LB_ME = 6'd14;
   parameter MPC_SB_EA = 6'd15;
   parameter MPC_SB_ME = 6'd16;
   parameter MPC_JP_EA = 6'd17;


   /// Microinstruction ROM and its Program Counter
   //
   reg [18:0] micro_rom[0:31];
   reg [4:0]  micro_pc;

   // Fetch Microinstruction
   //
   wire [22:0] micro_ir = micro_rom[micro_pc];

   // Microinstruction Fields
   //
   // Sample: { W_RD_ALU, PC_000, MEM_00, ALU_DSP, ALU_A_RS, ALU_B_RT, NXT_IF };
   //
   wire        ir_en, br_en;     // Clock ir, branch_cond.
   wire [1:0]  dest_field;       // Source of register number for gpr write.
   wire        dest_src;         // Source of data for gpr write.
   wire [1:0]  pc_op;            // How to update pc and npc.
   wire        addr_src;         // Source of memory address.
   wire        we;               // Memory's we input.
   wire [1:0]  size;             // Memory's size input.
   wire        alu_op_src;       // Source of alu operation.
   wire [1:0]  alu_a_src;        // Source of alu input a.
   wire [2:0]  alu_b_src;        // Source of alu input b.
   wire [1:0]  seq;              // Source of next microinstruction.

   // Assign Microinstruction Fields
   //
   assign {ir_en, br_en,
           dest_field, dest_src,
           pc_op,
           addr_src, we, size,
           alu_op_src, alu_a_src, alu_b_src,
           seq } = micro_ir;

   
   /// Dispatch Table ROM
   //
   reg [10:0] micro_disp[0:127];

   // Dispatch Table Address: Combine opcode and func fields
   //
   wire [6:0] d_tab_addr = opcode == o_rfmt ? {1'b0,func} : {1'b1,opcode};

   // Dispatch Table Fields
   //
   wire [4:0]  micro_alu_op;     // ALU operation from dispatch table.
   wire [4:0]  micro_disp_pc;    // micro_pc value for MIPS instruction.

   // Read Dispatch Table
   //
   assign {micro_disp_pc, micro_alu_op} = micro_disp[d_tab_addr];

   // Advance Microprogram Program Counter
   //
   always @( posedge clk )
     if( reset )
       micro_pc = MPC_I1;
     else
     case( seq )
       NXT_SEQ : micro_pc = micro_pc + 1;
       NXT_IF  : micro_pc = 1;
       NXT_DSP : micro_pc = micro_disp_pc;
       default : begin micro_pc = 0; $stop; end
     endcase

   // Select ALU A Input
   //
   always @( alu_a_src or rs_val or pc or sa or reset )
     case( alu_a_src )
       ALU_A_00: alu_a = 32'h0;
       ALU_A_RS: alu_a = rs_val;
       ALU_A_PC: alu_a = pc;
       ALU_A_SA: alu_a = sa;
       default: begin alu_a = sa; if( !reset ) $stop; end
     endcase

   // Select ALU B Input
   //
   always @( alu_b_src or rt_val or immed or pc or ii or reset )
     case( alu_b_src )
       ALU_B_RT: alu_b = rt_val;
       ALU_B_SI: alu_b = { immed[15] ? 16'hffff : 16'h0, immed };
       ALU_B_BI: alu_b = { immed[15] ? 14'h3fff : 14'h0, immed, 2'h0 };
       ALU_B_UI: alu_b = { 16'h0, immed };
       ALU_B_LI: alu_b = { immed, 16'h0 };
       ALU_B_JI: alu_b = { pc[31:28], ii, 2'h0 };
       ALU_B_PI: alu_b = 32'h400000;
       ALU_B_04: alu_b = 32'h4;
       default: begin alu_b = rt_val; if( !reset ) $stop; end
     endcase

   // Select ALU Operation Source
   //
   always @( alu_op_src or micro_alu_op )
     case( alu_op_src )
       ALU_ADD: alu_op = op_add;        // Add.
       ALU_DSP: alu_op = micro_alu_op;  // Use dispatch table operation.
     endcase

   // Set Exception Code (using dispatch table output.)
   //
   always @( micro_alu_op or reset )
     case( micro_alu_op )
       op_exc  : exc = 1;
       default : exc = 0;
     endcase

   // Select Memory Address Source
   //
   always @( addr_src or pc or alu_out )
     case( addr_src )
       ADDR_PC:  addr = pc;
       ADDR_ALU: addr = alu_out;
     endcase

   assign      data_out = rt_val;

   //
   /// Register Writes (GPR, pc, ir, etc.)
   //
   
   // Write IR and Branch Condition
   //
   always @( posedge clk ) if( ir_en ) ir = data_in;
   always @( posedge clk ) if( br_en ) branch_cond = alu_out[0];

   // Write PC or NPC (or neither).
   //
   always @( posedge clk )
     case( pc_op )
       PC_000: ;
       PC_ADV: pc = npc;
       PC_BRN: if( branch_cond ) npc = alu_out;
       PC_ALU: npc = alu_out;
     endcase

   // Write GPR
   //
   reg [31:0] wb_data;
   reg [5:0]  wb_rd;
   always @( posedge clk ) begin
      case( dest_field )
        DEST_00: wb_rd = 0;
        DEST_RD: wb_rd = rd;
        DEST_RT: wb_rd = rt;
        default: begin wb_rd = 0; if( !reset ) $stop; end
      endcase
      case( dest_src )
        GPR_ALU: wb_data = alu_out;
        GPR_MEM: wb_data = data_in;
      endcase
      if( wb_rd ) gpr[wb_rd] = wb_data;
   end

   ///
   /// Initialize Microprogram ROM and Dispatch Table
   ///
   
   integer i;
   // The always below is an inelegant way to have the synthesis
   // program read the micro_rom and micro_disp tables.
   always @( posedge clk ) if( i !== 128 ) begin

      // Initialize the dispatch table to exceptions.
      // Entries that are actually used are re-written further below.
      //
      for(i=0; i<127; i=i+1) micro_disp[i] = {MPC_IF,op_exc};

      // Instruction Fetch and Decode
      //
      micro_rom[MPC_IF]         = { W_IR_DIN, PC_ADV, MEM_IF, ALU_000, ALU_A_00, ALU_B_00, NXT_SEQ };
      micro_rom[MPC_ID]         = { W_00_000, PC_ALU, MEM_00, ALU_ADD, ALU_A_PC, ALU_B_04, NXT_DSP };

      // Initialize PC and NPC (Done on Reset)
      //
      micro_rom[MPC_I1]         = { W_00_000, PC_ALU, MEM_00, ALU_ADD, ALU_A_00, ALU_B_PI, NXT_SEQ };
      micro_rom[MPC_I2]         = { W_00_000, PC_ADV, MEM_00, ALU_ADD, ALU_A_00, ALU_B_00, NXT_SEQ };
      micro_rom[MPC_I3]         = { W_00_000, PC_ALU, MEM_00, ALU_ADD, ALU_A_PC, ALU_B_04, NXT_IF };

      // Format R, Two-register ALU
      //
      micro_disp[{1'b0,f_add}]  = { MPC_R2_EX,op_add };
      micro_disp[{1'b0,f_sub}]  = { MPC_R2_EX,op_sub };
      micro_disp[{1'b0,f_or}]   = { MPC_R2_EX,op_or };
      //
      micro_rom[MPC_R2_EX]      = { W_RD_ALU, PC_000, MEM_00, ALU_DSP, ALU_A_RS, ALU_B_RT, NXT_IF };

      // Format R, One-register ALU (sll, slr, etc.)
      //
      micro_disp[{1'b0,f_sll}]  = { MPC_R1_EX,op_sll };
      micro_disp[{1'b0,f_srl}]  = { MPC_R1_EX,op_srl };
      //
      micro_rom[MPC_R1_EX]      = { W_RD_ALU, PC_000, MEM_00, ALU_DSP, ALU_A_SA, ALU_B_RT, NXT_IF };

      // Format I, Unsigned ALU
      //
      micro_disp[{1'b1,o_andi}] = { MPC_IU_EX,op_and };
      micro_disp[{1'b1,o_ori}]  = { MPC_IU_EX,op_or };
      //
      micro_rom[MPC_IU_EX]      = { W_RT_ALU, PC_000, MEM_00, ALU_DSP, ALU_A_RS, ALU_B_UI, NXT_IF };

      // Format I, Signed ALU
      //
      micro_disp[{1'b1,o_addi}] = { MPC_IS_EX,op_add };
      micro_disp[{1'b1,o_slti}] = { MPC_IS_EX,op_slt };
      //
      micro_rom[MPC_IS_EX]      = { W_RT_ALU, PC_000, MEM_00, ALU_DSP, ALU_A_RS, ALU_B_SI, NXT_IF };

      // Format I, Load Upper Immediate
      //
      micro_disp[{1'b1,o_lui}]  = { MPC_IL_EX,op_add };  // rs = 0.
      //
      micro_rom[MPC_IL_EX]      = { W_RT_ALU, PC_000, MEM_00, ALU_DSP, ALU_A_RS, ALU_B_LI, NXT_IF };

      // Format I, Branch
      //
      micro_disp[{1'b1,o_bne}]  = { MPC_BR_CO,op_sne };
      micro_disp[{1'b1,o_beq}]  = { MPC_BR_CO,op_seq };
      //
      micro_rom[MPC_BR_CO]      = { W_BR_ALU, PC_000, MEM_00, ALU_DSP, ALU_A_RS, ALU_B_RT, NXT_SEQ };
      micro_rom[MPC_BR_TA]      = { W_00_000, PC_BRN, MEM_00, ALU_ADD, ALU_A_PC, ALU_B_BI, NXT_IF };

      // Format I, Load Unsigned Byte
      //
      micro_disp[{1'b1,o_lbu}]  = { MPC_LB_EA,op_add };
      //
      micro_rom[MPC_LB_EA]      = { W_00_000, PC_000, MEM_00, ALU_DSP, ALU_A_RS, ALU_B_SI, NXT_SEQ };
      micro_rom[MPC_LB_ME]      = { W_RT_MEM, PC_000, MEM_LB, ALU_ADD, ALU_A_RS, ALU_B_SI, NXT_IF };

      // Format I, Store Byte
      //
      micro_disp[{1'b1,o_sb}]   = { MPC_SB_EA,op_add };
      //
      micro_rom[MPC_SB_EA]      = { W_00_000, PC_000, MEM_00, ALU_DSP, ALU_A_RS, ALU_B_SI, NXT_SEQ };
      micro_rom[MPC_SB_ME]      = { W_00_000, PC_000, MEM_SB, ALU_ADD, ALU_A_RS, ALU_B_SI, NXT_IF };

      // Format J, Jump
      //
      micro_disp[{1'b1,o_j}]    = { MPC_JP_EA,op_add };
      //
      micro_rom[MPC_JP_EA]      = { W_00_000, PC_ALU, MEM_00, ALU_DSP, ALU_A_00, ALU_B_JI, NXT_IF };

   end

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;
   parameter  op_sne = 9;

   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 : alu_out = 0;
     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);
   memory_3 m1(mem_data_out,mem_err_out,addr,size,we,cpu_data_out,clk);

endmodule

`include "mipsmctb.v"