//
// Template for solution to LSU EE 4702-1 Spring 2000 Homework 6.
//

// Name:

// Instructions:
//
//
// Copy this file to a file named hw06sol.v in your class directory,
// and use it for your solution.  
//
// Use the module names provided in this file.  The solution, named
// width_change, must be the first module in the file and it must
// compile without having to modify any defines.
//
// The files will be copied from the class accounts soon after the
// assignment is due.


module width_change(out,full,complete,empty,outclk,in,inclk,reset);
   input outclk, in, inclk, reset;
   output out, full, complete, empty;

   parameter storage = 32;

   wire [7:0] out;
   wire [3:0] in;
   wire       inclk, outclk, full, empty, complete;
   
   reg [storage-1:0] sto;
   reg [5:0]         amt;
   
   initial begin amt = 0; sto = 0; end

   assign complete = amt >= 8;
   assign full     = amt + 4 > storage;
   assign empty    = amt === 0;
   assign out      = sto[7:0];

   always @( reset or amt or sto ) if( reset ) 
     begin
        amt = 0; sto = 0;
     end

   always @( posedge outclk )
     begin
        sto = sto >> 8;
        amt = amt < 8 ? 0 : amt - 8;
     end

   always @( posedge inclk )
     if( !full ) begin
        sto = sto | in << amt;
        amt = amt + 4;
     end
   
endmodule // width_change

module wc_test();

   // If non-zero, stop simulation when an error is encountered.
   // If zero, when an error is encountered simulation will proceed
   // and a count of errors will be displayed.
   parameter stop_on_err = 1;

   wire [7:0] out;
   wire       full, empty, comp;

   reg        outclk, inclk, reset;
   reg [3:0]  indata;

   reg [31:0] shadow;
   reg        shadow_full, shadow_comp, shadow_empty;
   reg [7:0]  shadow_head;
   integer    shadow_occ;
   reg        check;
   integer    phasecount;
   time       remove_delay_limit_short, remove_delay_limit_long;
   time       remove_delay_limit;
   time       fill_delay_limit;
   time       next_empty;

   integer    allow_simultaneous_clocks;
   integer    allow_overlapping_clocks;
   integer    error_out_t, error_out;
   integer    error_empty_t, error_empty;
   integer    error_comp_t, error_comp;
   integer    error_full_t, error_full;
   integer    error_test, error_test_t;

   width_change wc(out,full,comp,empty,outclk,indata,inclk,reset);


   function [31:0] randi;
      input [31:0] limit;
      randi = ( $random >> 1 ) % limit;
   endfunction // randi
   
   initial begin
      indata = 0; outclk = 0; inclk = 0; reset = 0;
      shadow_empty = 1; shadow_comp = 0; shadow_full = 0; shadow_head = 0;
      check = 0; shadow = 0; shadow_occ = 0; phasecount = 0;

      fill_delay_limit = 20;
      
      remove_delay_limit_short = 0.5 * fill_delay_limit * 2;
      remove_delay_limit_long = 2 * fill_delay_limit * 2;
      // Start filling.
      remove_delay_limit = remove_delay_limit_long;

      allow_overlapping_clocks = 0;
      allow_simultaneous_clocks = 0;
      error_out = 0; error_out_t = 0;
      error_empty = 0; error_empty_t = 0;
      error_comp = 0; error_comp_t = 0;
      error_full = 0; error_full_t = 0;
      error_test = 0; error_test_t = 0;

      reset = 1; #10 reset = 0; #10;

      fork:TESTLOOP
         forever begin:FILL
            integer clk_fall_delay;
            integer clk_rise_delay;

            clk_fall_delay = allow_overlapping_clocks ?
                             randi(fill_delay_limit)+1 : 1;
            clk_rise_delay = randi(fill_delay_limit)+1;

            indata <= #(10*randi(clk_rise_delay)) $random;
            #(10*clk_rise_delay);

            // Special case: if FIFO is full or empty and data is
            // simultaneously clocked in and out there's no way to
            // tell if FIFO rejected the data begin clocked in.
            
            while( next_empty == $time && 
                   ( !allow_simultaneous_clocks ||
                     shadow_occ >= 20 || shadow_occ < 12 )) #10;

            inclk = 1;
            check <= #1 !check;
            shadow_empty = 0;

            if( !shadow_full ) begin
               shadow_occ = shadow_occ + 4;
               shadow = { indata, shadow[31:4] };
               if( shadow_occ > 28 )
                 begin
                    shadow_full = 1;
                    if( remove_delay_limit === remove_delay_limit_long ) begin
                       remove_delay_limit = remove_delay_limit_short;
                       phasecount = phasecount + 1;
                    end
                 end
               if( shadow_occ > 7 ) shadow_comp = 1;
            end

            shadow_head = shadow >> ( 32 - shadow_occ );

            indata <= #(10*randi(clk_fall_delay)) $random;
            inclk = #(10*clk_fall_delay) 0;
            
         end

         forever begin:EMPTY
            integer clk_fall_delay;
            integer clk_rise_delay;

            clk_fall_delay = allow_overlapping_clocks ?
                             randi(remove_delay_limit)+1 : 1;
            clk_rise_delay = randi(remove_delay_limit)+1;

            next_empty = $time + 10 * clk_rise_delay;
            outclk = #(10*clk_rise_delay) 1;
            check <= #1 !check;
            shadow_full = 0;

            if( !shadow_empty ) begin
               if( shadow_occ <= 8 ) begin
                  shadow_occ = 0; shadow_empty = 1; 
                  remove_delay_limit = remove_delay_limit_long;
               end else begin
                  shadow_occ = shadow_occ - 8;
               end
               shadow_head = shadow >> ( 32 - shadow_occ );
               if( shadow_occ < 8 ) shadow_comp = 0;
            end
            outclk = #(10*clk_fall_delay) 0;
         end // block: EMPTY

         forever @( out or check ) #1
           if( out !== shadow_head ) begin
              if( stop_on_err ) begin
                 $display("Wrong output.");
                 #2 $stop;
              end
              error_out = error_out + 1;
           end
           
         forever @( empty or check ) #1
           if( empty !== shadow_empty ) begin
              if( stop_on_err ) begin
                 $display("Wrong empty.");
                 #2 $stop;
              end
              error_empty = error_empty + 1;
           end
            
         forever @( comp or check ) #1
           if( comp !== shadow_comp ) begin
              if( stop_on_err ) begin
                 $display("Wrong complete.");
                 #2 $stop;
              end
              error_comp = error_comp + 1;
           end
            
         forever @( full or check ) #1
           if( full !== shadow_full ) begin
              if( stop_on_err ) begin
                 $display("Wrong full.");
                 #2 $stop;
              end
              error_full = error_full + 1;
           end
         
         forever @( phasecount ) begin:P
            reg [84:0] test_name;
            if( phasecount == 200 || phasecount == 400 || phasecount == 600 )
              begin

                 error_test = error_out + error_empty + error_comp + error_full;
                 error_test_t = error_test_t + error_test;
                 error_out_t   = error_out_t + error_out;
                 error_empty_t = error_empty_t + error_empty;
                 error_comp_t  = error_comp_t + error_comp;
                 error_full_t  = error_full_t + error_full;
                 
                 case( phasecount )
                   200:test_name = "No Overlap";
                   400:test_name = "Not Simult";
                   600:test_name = "Full Test";
                 endcase // case( phasecount )

                 $display("Test %s.  Total Errors %d. Errors by type:",
                          test_name, error_test);
                 $display("         Output %d, empty %d, compl %d, full %d",
                          error_out, error_empty, error_comp, error_full);


                 if( phasecount == 200 ) begin
                    allow_overlapping_clocks = 1;
                    if( error_test === 0 )
                      $display("Passed all non-overlapping tests!!");
                 end else if ( phasecount == 400 ) begin
                    allow_simultaneous_clocks = 1;
                    if( error_test === 0 )
                      $display("Passed all overlapping-but-not-simultaneous tests!!!!!");
                 end else if ( phasecount == 600 ) begin
                    if( error_test === 0 )
                      $display("Passed all simultaneous clock tests!!!!!!!");
                    if( error_test_t === 0 )
                      $display("Passed EVERY test!  PERFECT!!!!!!!!!!!!!!!");
                    else
                      $display("Failed %d tests. :-(",error_test_t);
                    disable TESTLOOP;
                 end

                 error_out = 0; error_empty = 0; error_comp = 0; error_full = 0;
                 error_test = 0;
                 
              end // if ( phasecount == 200 || phasecount == 400 || phasecount == 600 )
         end         
      join

   end // initial begin

endmodule // wc_test