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