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

// Name:

// Instructions:

//
// Copy this file to a file named hw03sol.v in your class directory,
// and use it for your solution.  There is no need to add the homework-2
// testbench to this file. (But it's okay if you do.)
//
// Use the module names provided in this file.  Other modules can be
// added, but use the names provided for the tachometers.
//
// The files will be copied from the class accounts on Friday, the
// time will be announced.


`timescale 1us/1us
`define timeunit 1000000

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

////////////////////////////////////////////////////////////////////////////////
//
// Code for Problem 1
//

// Master slave registers are included below for convenience, others
// may be added.

// Use the template below for your solution.  The template includes
// a parameter bug workaround: parameter values are assigned to
// integers.  These integers can be used in the description, but
// they cannot be changed.  (If a parameter is the result of evaluating
// an expression containing floating-point numbers it will be considered
// floating-point (even if it's an integer) and will evaluate to zero
// in integer expressions.


// Behavioral description of master-slave edge triggered register.
module DREGx(q,d,c);
   output      q;
   input       d,c;
   reg [`vsize]  q, nextq;
   wire [`vsize] d;
   wire        c;

   initial begin q = 0; nextq = 0; end

   always @( posedge c ) nextq <= d;
   always @( negedge c ) q <= nextq;
endmodule // DREGx

// Behavioral description of master-slave edge triggered flip-flop.
module DREG1(q,d,c);
   output q;
   input  d,c;
   reg    q, nextq;
   wire   d;
   wire        c;
   initial begin q = 0; nextq = 0; end
   always @( posedge c ) nextq <= d;
   always @( negedge c ) q <= nextq;
endmodule // DREG1


module tach1s(rpx,pd,clk);
   input pd, clk;
   output rpx;
   wire   pd, clk;
   wire [`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;

   integer    timer_ticks_i, precision_i;

   // Bug workaround.  No other procedural code should be used.
   initial begin timer_ticks_i = timer_ticks; precision_i = precision; end

   // Insert solution here.

   
endmodule // tach1s


////////////////////////////////////////////////////////////////////////////////
//
// Code for Problem 2
//

// Use the template provided below.

// The output and internal timers must all use `bits-bit registers.
// (Use the `vsize and `bits macros).  The testbench bases its test
// speeds and tolerances on these register sizes.

// The testbench follows the template.


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

   // Parameters intended for instantiation. 
   parameter  freq = 500;
   parameter  marks = 4;     // Four pulses per revolution.
   parameter  perwhat = 60;  // Measure in revolutions per 60 seconds.

   // Parameters that might be useful in the module. 
   parameter  one_cycle_rpx = freq * perwhat / marks;
   parameter  max_count = ( (1<<`bits) - 1 );
   parameter  min_count = 1 + one_cycle_rpx / max_count;
   
   reg [`vsize] count;
   reg          bit, last_bit;

   //  Insert solution here.

   
endmodule // tach2

//
// Problem 2 testbench code.
//

// Define fast to run the fast test, undefine it for the detailed test.
// Module names have been made shorter so that the signal names will
// be visible in the default wave window, and for easier typing.


`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) t2(d2,d1);
   tta #(500,20) t3(d3,d2);
   tta #(1000) t4(d4,d3);
   tta #(2000,4,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


// Test Tach 2a

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;

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

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

   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 errors during and after adjustment period.
        integer adjust_error, check_error;
        // 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;

        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;

        markratio = 0.7;
        markcount = s1.marks;
        
        // Assume tach initially at zero.
        check_low_speed = 0;

        max_number = (1<<`bits) - 1;
        // 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 three types of tests. (i negative, zero, positive)
        
        for(i=-5; i<5; i=i+1)
        begin:SPEEDLOOP
           integer count;
           integer update_interval;

           case( 1 )
             // Stationary.
             i == 0: speed_rpx = 0;
             // High speeds.
             i < 0:  speed_rpx = - 2 * upper_limit / i;
             // Low speeds.
             i > 0:  speed_rpx = lower_limit + i;
           endcase // case( 1 )

           tests = tests + 1;
           
           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 + 2 * update_interval;
           // Amount of time to test this speed.
           test_done = $time + update_interval * 4.3;

           // 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
                      pd <= 1;
                      # markon;
                      pd <= 0;
                      # markoff;
                   end
                 disable CHECKSPEED;
              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

        // 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

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