// Solution to LSU EE4702-1 Spring 2000 Homework 2


`timescale 1us/1us
`define timeunit 1000000

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

module tach1(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  update_interval = 0.5;  // Update every update_interval seconds.
   parameter  perwhat = 60;  // Measure in revolutions per 60 seconds.
   
   parameter  precision = perwhat / ( marks * update_interval );
   parameter  timer_ticks = update_interval * freq - 1;

   reg [`vsize] update_timer;
   reg [`vsize] count;
   reg          reset;
   
   initial 
     begin:INIT
        integer preci;
        integer tt;

        // Convert timer_ticks and precision to integers.
        tt = timer_ticks;
        preci = precision;

        if( preci != precision )
          begin
             $display("Invalid tach1 parameters: prec not an integer.");
             $stop;
          end

        if( tt >= (1<<`bits) )
          begin
             $display("Invalid tach1 parameters: update timer too small.");
             $stop;
          end
        
        update_timer = 0; reset = 0; count = 0;
        
     end

   always @( posedge clk )
     if( reset && count === 0 )
       begin
          update_timer <= timer_ticks;
          reset <= 0;
       end
     else if( !reset && update_timer === 0 )
       begin
          rpx <= count;
          reset <= 1;
       end
     else if( !reset )
       begin
          update_timer <= update_timer - 1;
       end 

   always @( posedge pd ) count <= reset ? 0 : count + precision;


endmodule // tach1

// Same solution, using case statement.

module tach1a(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  update_interval = 0.5;  // Update every update_interval seconds.
   parameter  perwhat = 60;  // Measure in revolutions per 60 seconds.
   parameter  precision = perwhat / ( marks * update_interval );
   parameter  timer_ticks = update_interval * freq - 1;

   reg [`vsize] update_timer;
   reg [`vsize] count;
   reg          reset;
   
   initial 
     begin:INIT
        integer preci;
        integer tt;

        // Convert timer_ticks and precision to integers.
        tt = timer_ticks;
        preci = precision;

        if( preci != precision )
          begin
             $display("Invalid tach1 parameters: prec not an integer.");
             $stop;
          end

        if( tt >= (1<<`bits) )
          begin
             $display("Invalid tach1 parameters: update timer too small.");
             $stop;
          end
        
        update_timer = 0; reset = 0; count = 0;
        
     end

   always @( posedge clk )
     casez ( {reset, count != 0, update_timer != 0 } )
       3'b10?: // reset && count === 0 
         begin
            update_timer <= timer_ticks;
            reset <= 0;
         end
       3'b0?0: // !reset && update_timer === 0 
         begin
            rpx <= count;
            reset <= 1;
         end
       3'b0?1: // !reset && update_timer !== 0
         begin
            update_timer <= update_timer - 1;
         end
     endcase // casex( {reset, count != 0, update_timer != 0 } )
   

   always @( posedge pd ) count <= reset ? 0 : count + precision;

endmodule // tach1




module test_tach1_fast();

   wire done;
   
   // Test at default values.
   test_tach1a ts1(done,1'b1);
   // Module should complain about parameters below:
//   test_tach1a #(500,17) ts2(done2,1'b1); 

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

   
module test_tach1_detailed();

   wire d1,d2,d3,d4,d5;

   // Test at default values.
   test_tach1a ts1(d1,1'b1);
   test_tach1a #(100) ts2(d2,d1);
   test_tach1a #(500,20) ts3(d3,d2);
   test_tach1a #(500,3,2) ts4(d4,d3);
   test_tach1a #(500,4,0.5,2) ts5(d5,d4);

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

   


module test_tach1a(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_ui = 0.5;
   parameter   p_pw = 60;

   integer     sample_period;

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

   initial
     begin:IBLOCK

        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_duration;
        // Time (scaled) between speed updates.
        realtime update_interval;
        // 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.
        integer old_check_low_speed;
        // Number of errors during and after adjustment period.
        integer adjust_error, check_error;
        // Tolerance of measured speed. (E.g., +/- 20 RPM.)
        integer precision;
        // Number of speeds tested at.
        integer tests;
        integer loop_increment;
        integer sample_freq;

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

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

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

        markratio = 0.7;
        markcount = s1.marks;
        
        update_interval = `timeunit * s1.update_interval;

        precision = s1.perwhat / ( s1.marks * s1.update_interval );

        // Assume tach initially at zero.
        check_low_speed = 0;

        // Iterate over about 24 tests, fastest speed chosen
        // to avoid overflow of counters, just barely.

        loop_increment = ( 1 << (`bits-3) ) - ( precision >> 3 ) - 1;

        sample_freq = 4*(((1<<`bits)-precision)/s1.perwhat) * s1.marks;

        sample_period = `timeunit/sample_freq;
        
        $display("Sample frequency %d\n",sample_freq);

        for(speed_rpx = 0; speed_rpx <= (1<<`bits) - precision;
            speed_rpx = speed_rpx + loop_increment)
        begin:SPEEDLOOP

           tests = tests + 1;
           
           speed_rps = speed_rpx / s1.perwhat;

           // Compute amount of time mark under photodetector...
           markon = 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 = 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 ));

           old_check_low_speed = check_low_speed;
           
           check_low_speed = true_rpx <= precision ? 0 : true_rpx - precision;
           check_high_speed = true_rpx + precision;

           adjust_low_speed = old_check_low_speed;
           adjust_high_speed = check_high_speed;

           // Time at which a correct speed is expected.
           adjust_time = $time + update_interval * 2;
           // Amount of time to test this speed.
           test_duration = update_interval * 4.3;

           // fork, not begin!
           fork:CHECKSPEED

             // Note that line below stops the "forevers"
             # test_duration disable CHECKSPEED;

              // Simulate photodetectors.
              forever
                begin
                   pd <= 1;
                   # markon;
                   pd <= 0;
                   # markoff;
                end

              // Check tach whenever its output changes.
              forever @( rpx )
                begin
                   if( $time < adjust_time && 
                       ( rpx === `bits'bx ||
                         rpx < adjust_low_speed || rpx > adjust_high_speed ) )
                     adjust_error = adjust_error + 1;
                   if( $time > adjust_time &&
                       ( rpx === `bits'bx ||
                         rpx < check_low_speed || rpx > check_high_speed ) )
                     check_error = check_error + 1;
                end

              // Check tach at fixed intervals.
              forever # update_interval
                begin
                   if( $time < adjust_time &&
                       ( rpx === `bits'bx ||
                         rpx < adjust_low_speed || rpx > adjust_high_speed ) )
                     adjust_error = adjust_error + 1;
                   if( $time > adjust_time &&
                       ( rpx === `bits'bx ||
                         rpx < check_low_speed || rpx > check_high_speed ) )
                     check_error = check_error + 1;
                end

              
           join

        end // for (speed_rpx = 0; speed_rpx < 'h10000;...


        // Done with all tests, report results.

        $display("Finished %d tests with %d adjust errors and %d check errors.\n",
                 tests,adjust_error,check_error);
        if( check_error || adjust_error )
          $display("*** ERRORS FOUND ***\n");
        
        done = 1;

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

     end // block: IBLOCK

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