////////////////////////////////////////////////////////////////////////////////
///
/// Solution to LSU EE 3755 Fall 2001 Homework 6
///


////////////////////////////////////////////////////////////////////////////////
/// Problem 1 and 2 Solution
///

`define MEMBASE 'h400000
`define DATABASE 'h10010000
`define TEXTSIZE 'h100
`define MEMSIZE 'h200
`define MEMRANGE `MEMBASE:`MEMBASE+`MEMSIZE-1
`define A(addr) ((addr)-`DATABASE+`MEMBASE+`TEXTSIZE)
`define MEM(addr) mem[((addr)-`DATABASE+`MEMBASE+`TEXTSIZE)]

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

   reg    exc;

   reg [7:0] mem [`MEMRANGE];

   reg [31:0] pc, npc, nnpc, ir;
   reg [31:0] gpr [0:31];

   reg [5:0]  opcode, funct;
   reg [4:0]  rs, rt, rd, sa;
   reg [15:0] immed;
   reg [25:0] ii;
   reg [31:0] uimm16, simm16;
   reg [31:0] branch_target;

   // Values for funct field.
   parameter  f_sll  = 6'h0;
   parameter  f_srl  = 6'h2;
   parameter  f_jalr = 6'h9;    // HW6: New function code.
   parameter  f_add  = 6'h20;
   parameter  f_sub  = 6'h22;
   parameter  f_or   = 6'h25;
   parameter  f_nor  = 6'h27;   // HW6: New function code.

   // Values for opcode field.
   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;

   initial begin
      exc = 0;
      pc = 0;
      npc = 4;
   end

   always @( posedge clk ) begin

      ir = {mem[pc],mem[pc+1],mem[pc+2],mem[pc+3]};

      // R Format
      {opcode,rs,rt,rd,sa,funct} = ir;

      nnpc = npc + 4; // May be reassigned below.

      if( !opcode ) begin
         //
         // R-Format Instructions
         case( funct )
           f_jalr  : {nnpc,gpr[rd]}={gpr[rs],nnpc};     // HW6: jalr
           f_sll   : gpr[rd] = gpr[rt] << sa;
           f_srl   : gpr[rd] = gpr[rt] >> sa;
           f_add   : gpr[rd] = gpr[rs] + gpr[rt];
           f_or    : gpr[rd] = gpr[rs] | gpr[rt];
           f_nor   : gpr[rd] = ~( gpr[rs] | gpr[rt] );  // HW6: nor
           f_sub   : gpr[rd] = gpr[rs] - gpr[rt];
           default : exc = 1;
         endcase

      end else begin
         //
         // I- and J-Format Instructions

         // I Format  (Also uses opcode, rs, and rt.)
         immed = ir[15:0];
         // J Format  (Also uses opcode.)
         ii = ir[25:0];

         uimm16 = { 16'b0, immed };
         simm16 = immed[15] ? { 16'hffff, immed } : uimm16;

         branch_target = npc + ( simm16 << 2 );

         case( opcode )
           o_j     : nnpc = {npc[31:28],ii,2'b0};
           o_beq   : if( gpr[rs] == gpr[rt] ) nnpc = branch_target;
           o_bne   : if( gpr[rs] != gpr[rt] ) nnpc = branch_target;
           o_andi  : gpr[rt] = gpr[rs] & uimm16;
           o_slti  : gpr[rt] = gpr[rs] < simm16;
           o_addi  : gpr[rt] = gpr[rs] + simm16;
           o_ori   : gpr[rt] = gpr[rs] | uimm16;
           o_lui   : gpr[rt] = { immed, 16'b0 };
           o_lbu   : gpr[rt] = { 24'b0, `MEM( gpr[rs] + simm16 ) };
           o_sb    : `MEM( gpr[rs] + simm16 ) = gpr[rt];
           default : exc = 1;
         endcase

      end

      gpr[0] = 0;

      pc = npc;
      npc = nnpc;

   end


endmodule

module orig_proc(exc,clk);
   input clk;
   output exc;

   reg    exc;

   reg [7:0] mem [`MEMRANGE];

   reg [31:0] pc, npc, nnpc, ir;
   reg [31:0] gpr [0:31];

   reg [5:0]  opcode, funct;
   reg [4:0]  rs, rt, rd, sa;
   reg [15:0] immed;
   reg [25:0] ii;
   reg [31:0] uimm16, simm16;
   reg [31:0] branch_target;

   // Values for 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 opcode field.
   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;

   initial begin
      exc = 0;
      pc = 0;
      npc = 4;
   end

   always @( posedge clk ) begin

      ir = {mem[pc],mem[pc+1],mem[pc+2],mem[pc+3]};

      // R Format
      {opcode,rs,rt,rd,sa,funct} = ir;

      nnpc = npc + 4; // May be reassigned below.

      if( !opcode ) begin
         //
         // R-Format Instructions
         case( funct )
           f_sll   : gpr[rd] = gpr[rt] << sa;
           f_srl   : gpr[rd] = gpr[rt] >> sa;
           f_add   : gpr[rd] = gpr[rs] + gpr[rt];
           f_or    : gpr[rd] = gpr[rs] | gpr[rt];
           f_sub   : gpr[rd] = gpr[rs] - gpr[rt];
           default : exc = 1;
         endcase

      end else begin
         //
         // I- and J-Format Instructions

         // I Format  (Also uses opcode, rs, and rt.)
         immed = ir[15:0];
         // J Format  (Also uses opcode.)
         ii = ir[25:0];

         uimm16 = { 16'b0, immed };
         simm16 = immed[15] ? { 16'hffff, immed } : uimm16;

         branch_target = npc + ( simm16 << 2 );

         case( opcode )
           o_j     : nnpc = {npc[31:28],ii,2'b0};
           o_beq   : if( gpr[rs] == gpr[rt] ) nnpc = branch_target;
           o_bne   : if( gpr[rs] != gpr[rt] ) nnpc = branch_target;
           o_andi  : gpr[rt] = gpr[rs] & uimm16;
           o_slti  : gpr[rt] = gpr[rs] < simm16;
           o_addi  : gpr[rt] = gpr[rs] + simm16;
           o_ori   : gpr[rt] = gpr[rs] | uimm16;
           o_lui   : gpr[rt] = { immed, 16'b0 };
           o_lbu   : gpr[rt] = { 24'b0, `MEM( gpr[rs] + simm16 ) };
           o_sb    : `MEM( gpr[rs] + simm16 ) = gpr[rt];
           default : exc = 1;
         endcase

      end

      gpr[0] = 0;

      pc = npc;
      npc = nnpc;

   end

endmodule

module test_shell();
   test_proc #(1) s();
endmodule

module test_sol();
   test_proc #(2) s();
endmodule

module test_prog();
   test_proc #(4) s();
endmodule



module test_proc();

   parameter mode = 2;
   parameter mode_test_shell = 1;
   parameter mode_test_solution = 2;
   parameter mode_read_trace = 3;
   parameter mode_write_trace = 4;

   integer read_trace, write_trace;
   parameter tr_size = 50;

   wire exception;
   reg  clk;

   proc p1(exception,clk);

   reg [31:0] gpr_shadow [0:31];
   reg [319:0] isource[`MEMBASE>>2:((`MEMBASE+`MEMSIZE)>>2)-1];
   reg [31:0]  regname [0:31];

   integer    i, reg_old, reg_new;
   integer    icount;

   reg [31:0] tr_pc [1:tr_size];
   reg [31:0] tr_regv [1:tr_size];
   reg [4:0]  tr_regno [1:tr_size];
   integer    tr_fd, tr_icount_end;
   integer    changed_reg, changed_reg_count;

   always begin clk = 0; #10; clk = 1; #10; end

   task initmem;
      input [31:0] addr;
      input [31:0] text;
      input [319:0] source;

      begin
         if( addr & 3 ) begin
            $display("Unaligned instruction address, 0x%h",addr);
            $stop;
         end

         if( addr < `MEMBASE || addr >= `MEMBASE + `TEXTSIZE ) begin
            $display("Address 0x%h out of range.",addr);
            $stop;
         end

         {p1.mem[addr],p1.mem[addr+1],p1.mem[addr+2],p1.mem[addr+3]} = text;

         while( source[319:312] === 8'b0 ) source = {source[311:0]," "};
         isource[addr>>2] = source;

      end

   endtask

   task initdmem;
      input [31:0] addr;
      input [31:0] word;

      reg [31:0] daddr;

      begin
         if( addr & 3 ) begin
            $display("Unaligned data address, 0x%h",addr);
            $stop;
         end

         if( addr < `DATABASE || `A(addr) >= `MEMBASE + `MEMSIZE ) begin
            $display("Data address 0x%h out of range.",addr);
            $stop;
         end

         daddr = `A(addr);

         {p1.mem[daddr],p1.mem[daddr+1],p1.mem[daddr+2],p1.mem[daddr+3]} = word;

      end

   endtask

   integer rno;

   task initregs;
      input [31:0] name;
      input [3:0] cnt;
      integer i;

      for(i=0; i<cnt; i=i+1) begin
         regname[rno] = name + i;
         rno = rno + 1;
      end

   endtask

   initial begin

      begin

         regname[0] = "zero";
         regname[1] = "at";
         rno = 2;
         initregs("v0",2);
         initregs("a0",4);
         initregs("t0",8);
         initregs("s0",8);
         initregs("t8",2);
         initregs("k0",2);
         regname[28] = "gp";
         regname[29] = "sp";
         regname[30] = "fp";
         regname[31] = "ra";

      end

      case( mode )

        mode_test_solution:
          begin
             read_trace = 1;
             write_trace = 0;
`include "/home/classes/ee3755/com/v/hw6_test_solution_object.v"
`include "/home/classes/ee3755/com/v/hw6_test_solution_trace.v"
             $display("Testing Problem 1 and 2 solution.");
          end

        mode_test_shell:
          begin
             read_trace = 1;
             write_trace = 0;
`include "/home/classes/ee3755/com/v/hw6_test_shell_object.v"
`include "/home/classes/ee3755/com/v/hw6_test_shell_trace.v"
             $display("Testing solution shell (should pass before and after modification).");
          end

        mode_write_trace:
          begin
             read_trace = 0;
             write_trace = 1;
`ifdef MIPS_PROG
             tr_fd = $fopen("hw6_trace.v");
`include `MIPS_PROG
             $display("Running user program %s",`MIPS_PROG);
`else
//`include "hw6_test_solution_object.v"
//             tr_fd = $fopen("hw6_test_solution_trace.v");
             $display("To run this test define MIPS_PROG.  See comment.");
             $stop;
`endif

          end

        `ifdef READ_TRACE
        mode_read_trace:
          begin
             read_trace = 1;
             write_trace = 0;
`include `MIPS_PROG
`include "hw6_trace.v"
             $display("Consuming trace for user program %s.",`MIPS_PROG);
          end
        `endif

        default:
          begin
             $display("Unknown test mode.");
             $stop;
          end
      endcase

      if( write_trace ) begin


      end

      for(i=0; i<32; i=i+1) p1.gpr[i] = i*10;

      for(i=0; i<32; i=i+1) gpr_shadow[i] = p1.gpr[i];

      p1.pc = 'h400000;
      p1.npc = p1.pc + 4;

      icount = 0;

      wait( exception === 0 );
      wait( clk == 0 );

      while( !exception ) begin
         icount = icount + 1;
         $display("PC 0x%h:  %-s",p1.pc,isource[p1.pc>>2]);

         if( write_trace ) $fdisplay(tr_fd," tr_pc[%d] = 'h%h;",icount,p1.pc);
         if( read_trace && tr_pc[icount] !== p1.pc ) begin
            $display("PC mismatch at instruction %d, 0x%h (correct) != %h",
                     icount, tr_pc[icount], p1.pc);
            $stop;
         end

         @( negedge clk );

         changed_reg = 0;
         changed_reg_count = 0;

         for(i=0; i<32; i=i+1)
           if( gpr_shadow[i] !== p1.gpr[i] ) begin
              changed_reg = i;
              changed_reg_count = changed_reg_count + 1;
              reg_old = gpr_shadow[i];
              reg_new = p1.gpr[i];
              $display(" Register %d (%s): 0x%h (%d) -> 0x%h (%d)",
                       i, regname[i], gpr_shadow[i],
                       reg_old, p1.gpr[i], reg_new);
              gpr_shadow[i] = p1.gpr[i];
           end

         if( write_trace )
           $fdisplay(tr_fd, " tr_regno[%d] = %d; tr_regv[%d] = 'h%h;",
                     icount, changed_reg,
                     icount, gpr_shadow[changed_reg]);

         if( !exception && read_trace ) begin

            if( changed_reg_count > 1 ) begin
               $display("Too many registers, %d, changed.\n",
                        changed_reg_count);
               $stop;
            end

            if( tr_regno[icount] != changed_reg ) begin
               $display("FAIL: Wrong register modified %d should change, %d changed.",
                        tr_regno[icount],changed_reg);
               $stop;
            end

            if( tr_regv[icount] != gpr_shadow[changed_reg] ) begin
               $display("FAIL: Wrong value written.  0x%h (correct) != 0x%h",
                        tr_regv[icount],gpr_shadow[changed_reg]);
               $stop;
            end

         end
      end

      if( write_trace ) begin
         if( icount > tr_size )
           $display("WARNING!!! Increase tr_size from %d to at least %d.\n",
                    tr_size,icount);
         $fdisplay(tr_fd," tr_icount_end = %d;",icount);
         $fclose(tr_fd);
      end

      if( read_trace ) begin
         if( icount != tr_icount_end ) begin
            $display("FAIL: Valid instruction unrecognized.");
         end else begin
            $display("PASS: Correctly executed %d instructions.",icount);
         end
      end

      if( exception ) begin
         if( p1.ir == 'hc )
           $display("Ending at a syscall instruction.");
         else
           $display("Illegal instruction exception at address 0x%h",
                    p1.pc);
      end

      $stop;

   end

endmodule