// Solution to LSU EE 4702-1 Spring 2000 Homework 3.

`timescale 1us/1us
`define timeunit 1000000

`define bits 10
`define vsize (`bits-1):0


module tach3(rpx,pd,clk);
   input pd, clk;
   output rpx;
   wire   pd, clk;
   reg [`vsize] rpx;

   parameter  freq = 500;
   parameter  marks = 4;     // Four pulses per revolution.
   parameter  perwhat = 60;  // Measure in revolutions per 60 seconds.
   parameter  one_cycle_rpx = freq * perwhat / marks;
   parameter  max_count = ( (1<<`bits) - 1 );
   parameter  min_count = 1 + one_cycle_rpx / max_count;
   parameter  mark_min = ( marks + 2 ) / 2; // Minimum no. of marks per rev.

   reg [`vsize] count;
   reg [4:0]    marks_to_go;
   reg [`vsize] low_count, next_low_count;
   reg          bit, last_bit;
   reg          new_mark, overflow;
   reg          first_mark, last_mark, at_overflow;

   function [`vsize] compute_rpx;
      input [`vsize] low_count;
      if( low_count == max_count )
        compute_rpx = 0;
      else if( low_count < min_count )
        compute_rpx = max_count;
      else
        compute_rpx = one_cycle_rpx / low_count;
   endfunction // compute_rpx
      
   initial 
     begin:INIT
        marks_to_go = 0;
        overflow = 0;
        at_overflow = 0;
        count = max_count;
        low_count = max_count;
        new_mark = 0;
        bit = 0;
        rpx = 0;
        last_bit = 0;
     end // block: INIT

   always @( posedge pd ) bit <= ~bit;

   always @( posedge clk ) begin

      new_mark    = last_bit !== bit;
      first_mark  = marks_to_go === marks - 1;
      last_mark   = marks_to_go === 0;
      at_overflow = count === max_count;
      
      if ( new_mark ) begin

         count <= 1;

         casez( { overflow, first_mark, count < low_count } )
           3'b11?:   next_low_count = max_count;
           3'b0?1:   next_low_count = count;
           default:  next_low_count = low_count;
         endcase 

      end else begin

         count <= count + 1;
         next_low_count = low_count;

      end // else: !if( new_mark )
      
      
      if( at_overflow || new_mark )
        if( last_mark ) begin
           
           rpx           <= compute_rpx(next_low_count);
           next_low_count = max_count;
           marks_to_go   <= marks - 1;

        end else begin

           marks_to_go   <= marks_to_go - 1;

        end // else: !if( last_mark )
      

      low_count <= next_low_count;
      
      overflow <= at_overflow ? 1 :
                  new_mark    ? 0 : overflow;

      last_bit <= bit;

   end // always @ ( negedge clk )

endmodule // tach3


`define FAST
`ifdef FAST

// Test fast.

module tt();

   wire done;
   
   // Test at default values.
   tta t1(done,1'b1);

   initial
     begin
        #1;
        wait( done === 1 );
        $stop;  
     end
   
endmodule // tt

`else // !ifdef FAST

 
module tt();

   wire d1,d2,d3,d4,d5;

   // Test at default values.
   tta t1(d1,1'b1);
   tta #(100,15) t2(d2,d1);
   tta #(500,20) t3(d3,d2);
   tta #(1000,17) t4(d4,d3);
   tta #(2000,3,2) t5(d5,d4);

   initial
     begin
        wait( d5 === 1 );
        $stop;  
     end
   
endmodule // tt

`endif // !ifdef FAST
  

// Utility functions for tt2a.

module Util();

   function integer min;
      input a,b;
      integer a,b;
      min = a < b ? a : b;
   endfunction // min

   function integer max;
      input a,b;
      integer a,b;
      max = a > b ? a : b;
   endfunction // min

endmodule // util




module tta(done,start);
   output done;
   input  start;

   wire   start;
   reg    done;    

   wire [`vsize] rpx;
   reg         pd, clk;

   parameter   p_fr = 500;
   parameter   p_ma = 4;
   parameter   p_pw = 60;

   tach3  #(p_fr,p_ma,p_pw) s1(rpx,pd,clk);

   // Number of errors during and after adjustment period.
   integer     adjust_error, check_error, low_speed_error;

   // Dummy module defining min and max functions.
   Util util();

   parameter   pi_reset = 0;
   parameter   pi_new_pattern = 1;
   parameter   pi_next_mark = 2;

   function integer pattern_iterator;
      input [1:0] operation;
      integer loc;
      integer pnum;
      integer pattern[0:100]; // Assumed maximum number of marks.
      integer i;
      integer mark_min;
      integer missing_max;

      if( operation === pi_next_mark )
        begin
           pattern_iterator = pattern[loc];
           loc = loc === s1.marks - 1 ? 0 : loc + 1;
           if( s1.marks > 101 )
             begin
                $display("pattern_iterator can only handle up to 101 marks.");
                $stop;
             end
        end
      else
        begin
           if( operation === pi_reset ) 
             begin
                loc = 0;
                pnum = 0;
             end
           else if ( operation !== pi_new_pattern )
             begin
                $display("Unexpected input to pattern iterator.");
                $stop;
             end

           pnum = pnum + 1;
           mark_min = s1.mark_min;
           missing_max = s1.marks - mark_min;
           for(i=0; i<s1.marks; i=i+1) pattern[i] = 0;
           case( pnum )
             1: // All marks present.
               for(i=0; i<s1.marks; i=i+1) pattern[i] = 1;
             2: // Alternate marks present.
               begin
                  for(i=0; i<s1.marks; i=i+2) pattern[i] = 1;
                  if( ( s1.marks & 1 ) === 0 ) pattern[1] = 1;
               end
             3: // Maximum gap.
               for(i=0; i<mark_min; i=i+1) pattern[i] = 1;
             4: // Half-size gap.
               for(i=0; i<s1.marks - missing_max/2 ; i=i+1) pattern[i] = 1;
             5: // Random
               begin
                  for(i=0; i<mark_min; i=i+1) pattern[i] = 1;
                  for(i=0; i<s1.marks; i=i+1)
                  begin:R
                     integer to, temp;
                     to = ($random>>1) % s1.marks;
                     temp = pattern[i]; pattern[i] = pattern[to];
                     pattern[to] = temp;
                  end
               end // case: 5
             default: pnum = 0;
           endcase // case( pnum )
           pattern_iterator = pnum;
        end 
   endfunction

   initial
     begin:I

        integer markcount; // Number of marks per revolution.
        real    markratio; // Fraction of circumference covered by marks.
        // Amount of time given by testbench to detect new speed.
        realtime adjust_time; 
        // Amount of time given by testbench to test a speed.
        realtime test_done;
        // Speed testbench would like to test at.
        integer  speed_rpx;  // Units: revolutions per s1.perwhat seconds.
        // Speed testbench actually testing at. (Due to rounding errors.)
        real     true_rpx;
        // Speed in revolutions per second.
        real     speed_rps;
        // Amount of time photodetector is on, and off (as mark passes under).
        time     markon, markoff;
        // Low and high range of correct speeds while adjusting.
        integer  adjust_low_speed, adjust_high_speed;
        // Low and high range of correct speed after adjusting.
        integer  check_low_speed, check_high_speed;
        // Previous value of check_low_speed and check_high_speed;
        integer  prev_check_low_speed, prev_check_high_speed;
        // Number of speeds tested at.
        integer  tests;
        // Lowest non-stationary speed that can be measured.
        integer  lower_limit;
        // Highest non-saturating speed that can be measured.
        integer  upper_limit;
        integer  i;
        integer  max_number;
        // Pattern number in use.
        integer  patnum;
        // Speeds to test tachometer at.
        integer  speeds[0:10];
        // Any speed lower than this will be considered slow.
        integer  low_speed_threshold;

        done = 0;
        wait( start === 1 );

        $display("Starting: f %d, m %d, per %d secs\n",
                 s1.freq, s1.marks, s1.perwhat);

        clk = 0;
        tests = 0;
        adjust_error = 0; check_error = 0; low_speed_error = 0;

        markratio = 0.7;
        markcount = s1.marks;

        max_number = (1<<`bits) - 1;
        
        // Assume tach initially at zero.
        check_low_speed = 0;
        prev_check_high_speed = max_number;
        check_high_speed = max_number;

        // Low speed based on size of count register.
        lower_limit = util.max(1,s1.one_cycle_rpx / max_number);
        // High speed based on size of output and clock frequency.
        upper_limit = util.min(max_number,s1.one_cycle_rpx);

        // Iterate through patterns. Iterator returns zero when done.
        for( patnum = pattern_iterator( pi_reset );
             patnum;
             patnum = pattern_iterator( pi_new_pattern ) ) 
        begin

           tests = 0;
           adjust_error = 0; check_error = 0; low_speed_error = 0;

           // Iterate through 3 types of tests. (i negative, zero, positive),
           // assign speeds to an array.
           for( i = 0; i < 5; i = i + 1 ) begin
              // High speeds.
              speeds[2*i] = 2 * upper_limit / ( i + 1 );
              // Low speeds.
              speeds[2*i+1] = lower_limit + i + 1;
           end

           low_speed_threshold = speeds[9] + 1;
           speeds[10] = 0;

           // Mix up speeds.
           for(i=0; i<11; i=i+1) begin:A
              integer r, temp;
              r = ( $random >> 1 ) % 11;
              temp = speeds[i]; speeds[i] = speeds[r]; speeds[r] = temp;
           end
           
           // Iterate through 3 types of tests. (i negative, zero, positive)
           for(i=0; i<11; i=i+1) begin:SPEEDLOOP
              integer count;
              integer update_interval;

              tests = tests + 1;

              speed_rpx = speeds[i];
              
              speed_rps = 1.0 * speed_rpx / s1.perwhat;

              // Compute amount of time mark under photodetector...
              markon = speed_rps==0 ? 10 * `timeunit * max_number / s1.freq :
                       1 + `timeunit * ( markratio / markcount ) / speed_rps;
              // ...and amount of time in gap between marks.
              // Add 1 so speed is rounded to a lower rather than higher val.
              markoff = speed_rps == 0 ? 0 :
                        1 + `timeunit * ( (1-markratio) / markcount )
              / speed_rps;

              // Compute the actual speed, which is different than
              // speed_rpx because markon and markoff are integers.
              true_rpx = 1.0 * s1.perwhat * `timeunit /
                         (markcount * ( markon + markoff ));

              // The number of cycles between marks.
              count = speed_rps == 0 ? max_number : 
                      util.min(max_number,s1.one_cycle_rpx / true_rpx);
              
              prev_check_low_speed = check_low_speed;
              prev_check_high_speed = check_high_speed;

              check_low_speed = count == max_number ? 
                                0 : util.min(max_number,
                                             s1.one_cycle_rpx / ( count+1 ));
              check_high_speed = util.min(max_number,
                                          s1.one_cycle_rpx / 
                                          util.max(1,count-1));

              adjust_low_speed = util.min(prev_check_low_speed,
                                          check_low_speed);
              adjust_high_speed = util.max(prev_check_high_speed,
                                           check_high_speed);

              update_interval = util.min( `timeunit * max_number / s1.freq,
                                          markon + markoff );
              
              // Time at which a correct speed is expected.
              adjust_time = $time + 4 * s1.marks * update_interval;
              // Amount of time to test this speed.
              test_done = adjust_time + 3 * s1.marks * update_interval;

              // fork, not begin!
              fork:CHECKSPEED
                 
                // Simulate photodetectors and exit loop when done.
                // Exit just before a new photodetector starts so that
                // next time loop entered tachometer will see new
                // speed instead of an transient speed that might be
                // slower or faster, as would happen in test bench for
                // hw02.
                begin
                   while( $time < test_done )
                     begin
                        if( pattern_iterator( pi_next_mark ) ) pd <= 1;
                        # markon;
                        if( markoff ) begin pd <= 0; # markoff; end
                     end
                   disable CHECKSPEED;
                end

                 // Check tach whenever its output changes.
                 forever @( rpx )
                   begin
                      if( $time < adjust_time &&
                          (rpx >= adjust_low_speed && rpx <= adjust_high_speed )
                          !== 1 )
                        begin
                           if( true_rpx < low_speed_threshold )
                             low_speed_error = low_speed_error + 1;
                           else
                             adjust_error = adjust_error + 1;
                        end

                      if( $time > adjust_time &&
                          ( rpx >= check_low_speed && rpx <= check_high_speed )
                          !== 1 )
                        begin
                           if( true_rpx < low_speed_threshold )
                             low_speed_error = low_speed_error + 1;
                           else
                             check_error = check_error + 1;
                        end
                   end

                 // Check tach at fixed intervals.
                 forever # update_interval
                   begin
                      if( $time < adjust_time &&
                          (rpx >= adjust_low_speed && rpx <= adjust_high_speed )
                          !== 1 )
                        begin
                           if( true_rpx < low_speed_threshold )
                             low_speed_error = low_speed_error + 1;
                           else
                             adjust_error = adjust_error + 1;
                        end 
                      
                      if( $time > adjust_time &&

                          ( rpx >= check_low_speed && rpx <= check_high_speed )
                          !== 1 )
                        begin
                           if( true_rpx < low_speed_threshold )
                             low_speed_error = low_speed_error + 1;
                           else
                             check_error = check_error + 1;
                        end
                   end
              join

           end // block: SPEEDLOOP

           // Done with all speeds for pattern, report results.

           $display("Pattern %1d,  %2d speeds. Errors: %3d adjust, %3d check, and %3d low-speed\n",
                    patnum,tests,adjust_error,check_error,low_speed_error);
           $display("OUTCOME: %s  With %2d Marks, High Speed Pattern %1d",
                    ( adjust_error || check_error ) ? "FAIL" : "PASS",
                    s1.marks, patnum);
           $display("OUTCOME: %s  With %2d Marks, Low Speed Pattern %1d",
                    ( low_speed_error ) ? "FAIL" : "PASS",
                    s1.marks, patnum);

        end // for ( patnum = pattern_iterator( pi_reset );...
        
        done = 1;

        // Stop the clock. (Simulator efficiency.)
        disable CLOCK;

     end // block: I

   // Clock.
   always
     begin:CLOCK
        wait( start === 1 && done === 0 );
        forever # (`timeunit * 0.5/s1.freq ) clk <= ~ clk;
     end

   always        
     forever @( check_error or adjust_error )
       if( check_error > 0 || adjust_error > 0 ) $stop;

   
endmodule // test_speed