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