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