////////////////////////////////////////////////////////////////////////////////
///
/// Solution to LSU EE 4702-1 Spring 2001 Homework 3
///
///
// Includes a testbench (which was not graded).
`timescale 1us/1us
module microwave_oven_controller(beep,dmt,dmu,dst,dsu,mag_on,key_code,clk);
input key_code; // Key begin pressed (see parameters).
input clk; // A 64 Hz clock.
output mag_on; // When 1, magnetron is on (oven is heating).
output beep; // When 1, emit tone.
output dmt; // Tens digit of minute display.
output dmu; // Units digit of minute display.
output dst, dsu; // Tens and units digits of seconds display.
wire clk;
wire [5:0] key_code;
reg [3:0] dmt, dmu, dst, dsu;
reg mag_on;
parameter key_none = 6'd0; // No key pressed.
parameter key_never = 6'd1; // This code will never be returned.
parameter key_start = 6'd10;
parameter key_reset = 6'd11;
parameter key_power = 6'd12;
parameter key_0 = 6'd20;
parameter key_1 = 6'd21;
parameter key_2 = 6'd22;
parameter key_3 = 6'd23;
parameter key_4 = 6'd24;
parameter key_5 = 6'd25;
parameter key_6 = 6'd26;
parameter key_7 = 6'd27;
parameter key_8 = 6'd28;
parameter key_9 = 6'd29;
/// States
//
parameter st_reset = 0;
parameter st_entry_1 = 1; // One digit entered.
parameter st_entry_1p = 2; // One digit and power.
parameter st_entry_n = 3; // At least 2 digits (including power level).
parameter st_heating = 4;
parameter st_paused = 5;
reg [3:0] state, next_state;
reg [2:0] digit_count; // Number of digits entered.
reg [3:0] power; // Power level set by user.
reg [5:0] key_type; // Type of key. (unless digit, key_code)
parameter kty_digit = 6'd30;
// Number of tics before beep stops. Zero if not beeping.
//
reg [7:0] beep_timer;
assign beep = | beep_timer;
always @( posedge clk ) if( beep_timer ) beep_timer = beep_timer - 1;
// Add Digit to Display
//
task add_digit;
begin
dmt = dmu;
dmu = dst;
dst = dsu;
dsu = key_code - key_0;
digit_count = digit_count + 1;
end
endtask // add_digit
// Actions when switching to st_reset, including setting next_state.
//
task do_reset;
begin
dmt = 0;
dmu = 0;
dst = 0;
dsu = 0;
digit_count = 0;
beep_timer = 0;
next_state = st_reset;
mag_on = 0;
end
endtask // do_reset
initial begin do_reset; state = st_reset; end
/// State Transitions
//
always @( key_code ) if( key_code != key_none ) begin
key_type = key_code >= key_0 && key_code <= key_9
? kty_digit : key_code;
casez( {state, key_type} )
{st_reset,kty_digit}:
begin
add_digit;
power = 10;
next_state = st_entry_1;
end
{st_entry_1p,kty_digit}:
begin
dsu = 0;
add_digit;
next_state = st_entry_n;
end
{st_entry_n,kty_digit}:
begin
if( digit_count == 4 )
beep_timer = 16;
else
add_digit;
next_state = state;
end
{st_entry_1,kty_digit}:
begin
add_digit;
next_state = st_entry_n;
end
{st_entry_1,key_power}:
begin
power = dsu;
next_state = st_entry_1p;
end
{st_entry_n,key_start}, {st_entry_1,key_start}:
begin
if( dst > 5 )
begin
beep_timer = 16;
next_state = state;
end else
next_state = st_heating;
end
{st_heating,key_reset}:
begin
disable HEAT_LOOP;
next_state = st_paused;
end
{st_paused,key_start}:
begin
next_state = st_heating;
end
{4'b????,key_reset}: do_reset;
default: beep_timer = 16;
endcase // casez( {state, key_type} )
state = next_state;
end // if ( key_code != key_none )
// Clock Divider
//
// Divides 64 Hz clock by 64 so that sec_timer == 0 once per second.
//
reg [5:0] sec_timer;
initial sec_timer = 0;
always @( posedge clk ) sec_timer = sec_timer + 1;
always wait( state == st_heating ) begin
fork:HEAT_LOOP
reg [7:0] on_timer, off_timer;
// Turn magnetron on and off.
//
forever begin
on_timer = power * 64 * 2.5 / 10;
off_timer = 64 * 2.5 - on_timer;
mag_on = 1;
while ( on_timer ) @( posedge clk ) on_timer = on_timer - 1;
if( off_timer ) begin
mag_on = 0;
while ( off_timer ) @( posedge clk ) off_timer = off_timer - 1;
end
end
// Update display during heating.
//
forever @( posedge | sec_timer ) begin:T
if( {dmt,dmu,dst,dsu} == 0 ) begin
beep_timer = 128;
state = st_reset;
mag_on <= 0;
disable HEAT_LOOP;
end
if( dsu ) begin dsu = dsu - 1; disable T; end
dsu = 9;
if( dst ) begin dst = dst - 1; disable T; end
dst = 5;
if( dmu ) begin dmu = dmu - 1; disable T; end
dmu = 9;
if( dmt ) dmt = dmt - 1;
end // block: T
join
mag_on = 0;
end // always wait
endmodule // microwave_oven_controller
module test_oven();
reg clk;
wire [3:0] dmt, dmu, dst, dsu;
wire mag_on;
reg [5:0] key_mod;
reg reset;
// Set this to 1 to have each change in the oven display appear
// on the console.
reg monitor_display;
// Set this to one to have each key press appear on the console.
reg monitor_keys;
// Set this to one to get long test.
reg patient;
microwave_oven_controller oven(beep,dmt,dmu,dst,dsu,mag_on,key_mod,clk);
time tics;
initial tics = 0;
always begin clk = 0; #5625; tics = tics + 1; #0; clk=1; #10000; end
parameter ss_reset = 3'd0;
parameter ss_digit1 = 3'd1; // Single digit, power not entered.
parameter ss_digit2 = 3'd2; // Power entered or > 1 digit.
parameter ss_cook = 3'd3;
parameter ss_pause = 3'd4;
parameter kty_digit = 6'd30;
reg [15:0] shadow_display, alt_display;
reg [2:0] shadow_state;
integer shadow_secs, mod_secs, delta;
integer shadow_tics, pause_tics, start_tics;
integer shadow_power, shadow_digits;
integer expected_beep_done, expecting_done_beep;
integer watch_display;
integer error_display, error_mag, error_beep, error_total;
`include "oven_keys.v"
function integer abs;
input a;
integer a;
abs = a < 0 ? -a : a;
endfunction // abs
`define check_digit(d,l) \
if( (d) > (l) || (d) < 0 ) begin \
error_display = error_display + 1; \
secs = -1; \
disable tosecs; \
end
// Convert time on display to seconds.
//
task tosecs;
output secs;
integer secs;
begin
`check_digit(dmt,9)
`check_digit(dmu,9)
`check_digit(dst,5)
`check_digit(dsu,9)
secs = dmt * 600 + dmu * 60 + dst * 10 + dsu;
end
endtask // tosecs
/// Listen Beep
//
always @( beep )
if( beep ) begin
if( expecting_done_beep )
begin
expected_beep_done = 128;
expecting_done_beep = 0;
end
if( expected_beep_done == 0 ) begin
$display("Should not be beeping.");
error_beep = error_beep + 1;
end
end else begin:B // if ( beep )
integer delta;
delta = abs( tics - expected_beep_done );
if( shadow_state != ss_reset
&& expected_beep_done && delta > 5 ) begin
$display("Beep wrong time. %d",delta);
error_beep = error_beep + 1;
end
expected_beep_done = 0;
end
/// Watch Magnetron
//
integer mag_on_start, mag_on_total;
always @( mag_on )
if( mag_on ) begin
if( shadow_state != ss_cook )
begin
$display("Mag on when cooking off.");
error_mag = error_mag + 1;
end
if( mag_on_start != 0 ) begin:A
integer cycle_this;
cycle_this = tics - mag_on_start;
if( shadow_power > 0 && shadow_power < 10
&& abs( cycle_this - 160 ) > 10 )
begin
$display("Mag cycle error.");
error_mag = error_mag + 1;
end
end
mag_on_start = tics;
end else begin // if ( mag_on )
mag_on_total = mag_on_total + tics - mag_on_start;
end // else: !if( mag_on )
// Verify correct magnetron-on time.
//
task verify_cooking;
begin:A
integer correct_mag_tics;
integer delta;
correct_mag_tics = shadow_tics * shadow_power / 10;
delta = abs(correct_mag_tics - mag_on_total);
if( delta > 128 ) begin
$display("Wrong power level. %d %d ",
correct_mag_tics, mag_on_total);
error_mag = error_mag + 1;
end
end // block: A
endtask // verify_cooking
/// Watch display, etc.
//
always @( dmt or dmu or dst or dsu or shadow_display ) #1 begin
if( monitor_display ) $display("Display: %d%d:%d%d",dmt,dmu,dst,dsu);
if( shadow_state == ss_cook ) begin
shadow_secs = shadow_secs - 1;
shadow_tics = shadow_tics + 64;
tosecs(mod_secs);
if( mod_secs !== shadow_secs )
begin
$display("Wrong count. (cooking)");
error_display = error_display + 1;
end
delta = shadow_tics - ( tics - start_tics );
if( abs(delta) > 96 )
begin
$display("More than 96 tics off: %d",delta);
error_display = error_display + 1;
end
if( shadow_secs == 0 ) begin
expecting_done_beep = 1;
delay(1.5);
verify_cooking;
if( expecting_done_beep ) begin
$display("End of cooking beep missing.");
error_beep = error_beep + 1;
end
shadow_state = ss_reset;
shadow_digits = 0;
end // if ( shadow_secs == 0 )
end else if ( shadow_state == ss_pause ) begin
tosecs(mod_secs);
if( mod_secs !== shadow_secs ) begin
$display("Wrong count. (paused)");
error_display = error_display + 1;
end
end else if ( watch_display ) begin
if( {dmt,dmu,dst,dsu} !== shadow_display
&& {dmt,dmu,dst,dsu} !== alt_display ) begin
$display("Wrong display, should be %h",shadow_display);
error_display = error_display + 1;
end
end
end
// Reset shadow state and expected outputs maintained by
// testbench.
//
task to_reset;
begin
shadow_digits = 0;
shadow_state = ss_reset;
alt_display = shadow_display;
shadow_display = 0;
shadow_power = 10;
watch_display = 1;
end
endtask // to_reset
// Send keys to module, update correct state and expected output information.
//
task command;
input [799:0] cmd;
integer initialized;
integer c;
integer consec_reset;
reg [5:0] to_key [0:255];
reg [5:0] key;
begin
if( initialized === 'bx ) begin
for( c = 0; c < 256; c = c + 1 ) to_key[c] = key_never;
for( c = 0; c < 10; c = c + 1 ) to_key[ "0" + c ] = key_0 + c;
to_key["s"] = key_start;
to_key["r"] = key_reset;
to_key["p"] = key_power;
to_key[" "] = key_none;
to_key[0] = key_none;
initialized = 1;
consec_reset = 0;
end // if ( initialized === 'bx )
while( cmd ) begin:COMMAND_LOOP
reg [7:0] c;
reg [5:0] key_type;
c = cmd[799:792];
key_mod = to_key[ c ];
key = key_mod;
key_type = ( c >= "0" && c <= "9" ) ? kty_digit : key_mod;
if( key == key_never ) begin
$display("Testbench error: illegal key in command, %s (%d)",c,c);
$stop;
end
casez( {shadow_state,key_type} )
{3'b???,key_none}:;
{ss_reset,key_reset}:
begin
to_reset;
end
{ss_reset,kty_digit}:
begin
shadow_digits = 1;
shadow_state = ss_digit1;
alt_display = shadow_display;
shadow_display = key-key_0;
end
{ss_pause,key_reset},
{ss_digit1,key_reset},
{ss_digit2,key_reset}:
begin
to_reset;
end
{ss_digit1,kty_digit}:
begin
shadow_digits = 2;
shadow_state = ss_digit2;
alt_display = shadow_display;
shadow_display = (shadow_display << 4) | key-key_0;
end
{ss_digit1,key_power}:
begin
shadow_state = ss_digit2;
shadow_digits = 0;
alt_display = shadow_display; // Power level.
shadow_power = shadow_display;
shadow_display = 0;
end
{ss_digit1,key_start},{ss_digit2,key_start}:
begin
if( shadow_display[7:4] > 5 )
begin
expected_beep_done = tics + 16;
end else begin
tosecs(shadow_secs);
start_tics = tics;
shadow_tics = 0;
mag_on_total = 0;
mag_on_start = 0;
shadow_state = ss_cook;
end
end
{ss_digit2,kty_digit}:
if( shadow_digits == 4 )
expected_beep_done = tics + 16;
else
begin
shadow_digits = shadow_digits + 1;
// If shadow_display zero then power was pressed.
if( shadow_display ) alt_display = shadow_display;
shadow_display = (shadow_display << 4) | key-key_0;
end
{ss_cook,key_reset}:
begin
shadow_state = ss_pause;
pause_tics = tics;
end
{ss_pause,key_start}:
begin
verify_cooking;
mag_on_start = 0;
shadow_tics = 0;
mag_on_total = 0;
start_tics = tics;
shadow_state = ss_cook;
end
default:
begin
if( expected_beep_done && expected_beep_done < tics )
begin
$display("Missed a beep. (overlap)");
error_beep = error_beep + 1;
end
expected_beep_done = tics + 16;
end
endcase // casez( {shadow_state,key_type} )
@( posedge clk ) @( negedge clk );
repeat ( $random() & 15 + 3 ) @( negedge clk );
key_mod = key_none;
@( posedge clk ) @( negedge clk );
@( posedge clk ) @( negedge clk );
if( key != key_none )
consec_reset = key == key_reset ? consec_reset + 1 : 0;
if( monitor_keys && key != key_none && consec_reset < 2 )
$display("Key %s State %d, Display: %d%d:%d%d B %d",
c,shadow_state, dmt,dmu,dst,dsu,beep);
cmd = cmd << 8;
end // block: COMMAND_LOOP
end
endtask // command
// Reset oven module either using reset line or
// reset button.
//
task reset_oven;
input hard;
begin
if( 0 && hard ) begin
reset = 1;
fork:F
begin repeat ( 256 ) @( clk ); disable F; end
wait( !beep );
wait( !mag_on );
join
reset = 0;
end else begin
command("rrrrrr");
fork
wait( !beep );
wait( !mag_on );
join
command("rrrrrr");
end // else: !if( 0 && hard )
if( beep || mag_on )
begin
$display("Could not reset oven.");
end
end
endtask // reset_oven
// Actions to be done at the end of a test.
//
task endtest;
input [159:0] name;
begin
if( expected_beep_done )
begin
if( expected_beep_done >= tics )
$display("Testbench not waiting long enough for beep.");
$display("Missed a beep.");
error_beep = error_beep + 1;
expected_beep_done = 0;
end
if( {dmt,dmu,dst,dsu}!==0 )
begin
$display("Expected to be finished.");
error_display = error_display + 1;
end
watch_display = 0;
$display("Test %s completed. (dsp,beep,mag) (%d,%d,%d)",
name,
error_display, error_beep, error_mag);
error_total = error_total + error_display + error_beep + error_mag;
reset_oven(0);
error_display = 0;
error_beep = 0;
error_mag = 0;
end
endtask // endtest
task delay;
input [63:0] secs;
#( secs * 1000000 );
endtask // delay
initial begin
monitor_display = 0;
monitor_keys = 0;
patient = 0;
expected_beep_done = 0;
expecting_done_beep = 0;
error_display = 0;
error_beep = 0;
error_mag = 0;
error_total = 0;
#1;
to_reset;
reset_oven(1);
command("50prr");
delay(40);
endtest("Power Too High");
command("12s"); delay(16);
endtest("Basic");
if( patient ) begin
$display("Starting test Long, be patient or modify testbench.");
command("100s"); delay(90*60+5);
endtest("Long");
end
command("30s"); delay(14);
command("1"); delay(2); command("p");
delay(20);
endtest("Basic Disturbed");
command("5p30s");
delay(40);
endtest("Half Power");
command("9p3p0s");
delay(40);
endtest("Power Twice");
command("3ppp19s");
delay(40);
endtest("Power Thrice");
command("20s");
delay(10);
command("r");
delay(5);
command("s");
delay(10);
endtest("Reset Start");
command("20s");
delay(10);
command("r");
delay(5);
command("r");
delay(1);
endtest("Reset Reset");
command("s");
delay(5);
endtest("Null Start");
command("ps");
delay(5);
endtest("Null Power Start");
command("7p30s"); delay(10);
command("1"); delay(2); command("s"); delay(3);
command("12344321");
delay(40);
endtest("Power Disturbed");
command("12r5s"); delay(10);
endtest("Twelve no 5");
command("90s"); delay(1); command("rr");
endtest("Ninety Seconds");
command("12345rr");
endtest("Display Overflow");
$display("All tests completed, %d total errors.",error_total);
$stop;
end // initial begin
endmodule // test_oven