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