//
// LSU EE 4702-1 Spring 2000 Homework 6 Solution
//


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 [1:0]  head_word;
   reg [2:0]  tail_nibble;

   reg        empty_in, empty_out, full_in, full_out;

   assign out      = empty ? 0 : sto >> { head_word, 3'b0 };
   assign empty    = empty_in ^ empty_out;
   assign full     = full_in ^ full_out;
   assign complete = !empty && 
          ( full || head_word != tail_nibble[2:1] );

   always @( posedge inclk or posedge reset )
     if( reset ) begin
        sto[7:0] = 0; tail_nibble = 0; empty_in = 1; full_in = 0;
     end else if( !full ) begin
        if( empty ) begin empty_in = !empty_in; tail_nibble = 0; end

        case ( tail_nibble )
          0: sto[7:0] = {4'b0,in};
          1: sto[7:4] = in;
          2: sto[15:8] = {4'b0,in};
          3: sto[15:12] = in;
          4: sto[23:16] = {4'b0,in};
          5: sto[23:20] = in;
          6: sto[31:24] = {4'b0,in};
          7: sto[31:28] = in;
        endcase 

        tail_nibble = tail_nibble + 1;

        if( tail_nibble[0]==0 && tail_nibble>>1 == head_word )
          full_in = !full_in;

     end
   
   always @( posedge outclk or posedge reset )
     if( reset ) begin
        head_word = 0; empty_out = 0; full_out = 0;
     end else 
       if( !empty ) begin
          if( full ) begin
             full_out = !full_out;
             head_word = head_word + 1;
          end else if( head_word == ( 3 & (tail_nibble-1)>>1 ) ) begin
             empty_out = !empty_out;
             head_word = 0;
          end else begin
             head_word = head_word + 1;
          end
       end

endmodule // width_change

// exemplar translate_off

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

// exemplar translate_on