//////////////////////////////////////////////////////////////////////////////// /// /// Solution to LSU EE 4702-1 Spring 2001 Homework 4 /// /// // Includes a testbench (which was not graded). `timescale 1us/1us module microwave_oven_controller(beep,dmt,dmu,dst,dsu,mag_on, key_code,reset,clk); input key_code; // Key begin pressed (see parameters). // Can be tested on the positive edge of clk. input reset; // Reset signal. Can be tested on posedge clk. input clk; // A 64 Hz clock. (Did Edison consider it?) 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; reg beep; 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; parameter kty_digit = 6'd30; parameter st_reset = 0; parameter st_entry = 1; parameter st_entry_p1 = 2; parameter st_entry_p2 = 5; parameter st_heating = 3; parameter st_paused = 4; reg [2:0] digit_count; reg [3:0] power; reg [5:0] key_type, last_key; reg [3:0] state, next_state; reg [7:0] beep_timer; task add_digit; begin if( digit_count == 4 ) beep_timer = 16; else begin dmt = dmu; dmu = dst; dst = dsu; dsu = key_code - key_0; digit_count = digit_count + 1; end end endtask // add_digit 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 reg [7:0] on_timer, off_timer; reg [5:0] sec_timer; always @( posedge clk ) if( reset ) begin do_reset; state = st_reset; sec_timer = 0; on_timer = 0; off_timer = 0; last_key = key_none; beep = 0; end else begin // if ( reset ) if( key_code != key_none && last_key == key_none ) begin if( key_code >= key_0 && key_code <= key_9 ) key_type = kty_digit; else key_type = key_code; casez( {state, key_type} ) {st_reset,kty_digit}: begin add_digit; power = 10; next_state = st_entry; end {st_entry_p1,kty_digit}: begin dsu = 0; add_digit; next_state = st_entry_p2; end {st_entry_p2,kty_digit}: begin add_digit; next_state = state; end {st_entry,kty_digit}: begin add_digit; next_state = state; end {st_entry,key_power}: begin if( digit_count == 1 ) power = dsu; else beep_timer = 16; next_state = st_entry_p1; end {st_entry_p2,key_start}, {st_entry,key_start}: begin next_state = st_heating; end {st_paused,key_start}: begin next_state = st_heating; end // Leonardo incorrectly infers parallel case, so need // to test state. {4'b????,key_reset}: if( state == st_heating ) next_state = st_paused; else do_reset; default: begin next_state = state; beep_timer = 16; end endcase // casez( {state, key_type} ) end // if ( key_code != key_none && last_key == key_none ) // next_state may be reassigned below. sec_timer = sec_timer + 1; beep = | beep_timer; if( beep_timer ) beep_timer = beep_timer - 1; if( state == st_heating ) begin if( {dmt,dmu,dst,dsu} == 0 ) begin beep_timer = 128; mag_on = 0; next_state = st_reset; end else begin if( !sec_timer ) begin:T 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 if( on_timer ) on_timer = on_timer - 1; else if( off_timer ) off_timer = off_timer - 1; mag_on = |on_timer | ~|off_timer; if( !on_timer && !off_timer ) begin on_timer = power * 16; off_timer = 160 - on_timer; end end // else: !if( {dmt,dmu,dst,dsu} == 0 ) end else begin // if ( state == st_heating ) on_timer = 0; off_timer = 0; mag_on = 0; end // else: !if( state == st_heating ) last_key = key_code; state = next_state; end // else: !if( reset ) endmodule // microwave_oven_controller // exemplar translate_off 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; reg monitor_beep; reg monitor_mag; // Set this to one to get long test. reg patient; microwave_oven_controller oven(beep,dmt,dmu,dst,dsu,mag_on,key_mod,reset,clk); time tics; wire [15:0] mod_digits = {dmt,dmu,dst,dsu}; 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; integer alt_disp_stale; 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; reg [7:0] error_display, error_mag, error_beep; integer error_total; reg [7:0] error_beep_total, error_mag_total, error_display_total; 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; parameter show_key = 0; 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( monitor_beep ) $display("Beep starting."); 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; if( monitor_beep ) $display("Beep ending."); 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 | 2; end expected_beep_done = 0; end /// Watch Magnetron // integer mag_on_start, mag_on_total; always @( mag_on ) if( mag_on ) begin if( monitor_mag ) $display("Mag on."); 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 | 2; end end mag_on_start = tics; end else begin // if ( mag_on ) if( monitor_mag ) $display("Mag off."); 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( mag_on ) begin $display("Mag should be off."); error_mag = error_mag | 'h10; end if( delta > 128 ) begin $display("Wrong power level. %d %d ", correct_mag_tics, mag_on_total); error_mag = error_mag | 4; 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: %h sh: %h alt: %h secs %d, state %d", mod_digits, shadow_display,alt_display, shadow_secs, shadow_state); 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 | 4; end delta = shadow_tics - ( tics - start_tics ); if( abs(delta) > 96 ) begin $display("More than 96 tics off: %d",delta); error_display = error_display | 2; end if( mod_digits == 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 | 4; end shadow_state = ss_reset; shadow_display = 0; end else if ( shadow_secs == 0 ) begin $display("Count problem."); end 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 | 8; end end else if ( watch_display ) begin if( mod_digits !== shadow_display && ( alt_disp_stale <= tics || mod_digits !== alt_display ) ) begin $display("Wrong display, should be %h or maybe %h but not %h", shadow_display,alt_display,mod_digits); error_display = error_display | 'h10; end end end // Reset shadow state and expected outputs maintained by // testbench. // task to_reset; begin shadow_digits = 0; alt_display = shadow_display; alt_disp_stale = tics + 2; shadow_state = ss_reset; 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]; cmd = cmd << 8; if( c == 0 || c == " " ) disable COMMAND_LOOP; 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 if( key != key_none ) consec_reset = key == key_reset ? consec_reset + 1 : 0; if( monitor_keys && key != key_none && consec_reset < 3 ) $display("Key %s ", c); 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; alt_disp_stale = tics + 2; 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; alt_disp_stale = tics + 2; 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. alt_disp_stale = 'h7fffffff; shadow_power = shadow_display; shadow_display = 0; end {ss_digit1,key_start},{ss_digit2,key_start}: begin tosecs(shadow_secs); start_tics = tics; shadow_tics = 0; mag_on_total = 0; mag_on_start = 0; shadow_state = ss_cook; 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. alt_disp_stale = tics + 2; alt_display = mod_digits; shadow_display = (shadow_display << 4) | key-key_0; end {ss_cook,key_reset}: begin shadow_state = ss_pause; shadow_display = mod_digits; 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 | 8; 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 ); end // block: COMMAND_LOOP end endtask // command // Reset oven module either using reset line or // reset button. // task reset_oven; input hard; begin if( hard ) begin reset = 1; fork:F begin repeat ( 5 * 60 * 64 ) @( posedge clk ); disable F; end wait( !beep ); wait( !mag_on ); join reset = 0; end else begin command("rrrrrr"); fork:T wait( !beep ); wait( !mag_on ); begin repeat ( 5 * 60 * 64 ) @( posedge clk ); disable T; end join command("rrrrrr"); end // else: !if( 0 && hard ) if( beep || mag_on ) begin $display("Could not reset oven."); if( beep ) error_beep = error_beep | 'h20; if( mag_on ) error_mag = error_mag | 8; end end endtask // reset_oven // Actions to be done at the end of a test. // task endtest; input [159:0] name; integer error_count; 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 | 'h10; expected_beep_done = 0; end if( mod_digits!==0 ) begin $display("Display should be zero."); error_display = error_display + 'h20; end verify_cooking; watch_display = 0; error_count = ( error_display ? 1 : 0 ) + ( error_beep ? 1 : 0 ) + ( error_mag ? 1 : 0 ); error_total = error_total + error_count; error_beep_total = error_beep_total | error_beep; error_mag_total = error_mag_total | error_mag; error_display_total = error_display_total | error_display; $display("OUTCOME: %s on test %s. (dsp,beep,mag) (%2h,%2h,%2h)", error_count ? "FAIL" : "PASS", name, error_display, error_beep, error_mag); reset_oven(0); error_display = 0; error_beep = 0; error_mag = 0; mag_on_total = 0; shadow_tics = 0; end endtask // endtest task delay; input [63:0] secs; #( secs * 1000000 ); endtask // delay initial begin monitor_display = 1; monitor_keys = 1; monitor_beep = 1; monitor_mag = 1; patient = 1; expected_beep_done = 0; expecting_done_beep = 0; error_display = 0; error_display_total = 0; error_beep = 0; error_beep_total = 0; error_mag = 0; error_mag_total = 0; error_total = 0; mag_on_total = 0; #1; to_reset; reset_oven(1); error_display = 0; error_display_total = 0; error_beep = 0; error_beep_total = 0; error_mag = 0; error_mag_total = 0; error_total = 0; mag_on_total = 0; command("50prr"); delay(54); endtest("Power Too High"); command("32s"); delay(36); endtest("Basic"); if( patient ) begin:PATIENT integer old_mon, old_mag; old_mon = monitor_display; old_mag = monitor_mag; monitor_display = 0; $display("Starting test Long, be patient or modify testbench."); command("1234s"); delay(14*60); endtest("Long"); monitor_display = old_mon; monitor_mag = old_mag; 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(16); endtest("Reset Start"); command("20s"); delay(10); command("r"); delay(5); command("r"); delay(16); 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(135); endtest("Twelve no 5"); command("12345rr"); delay(5); endtest("Display Overflow"); if( show_key ) begin $display("\n ** Error Codes ** Display (Codes in hexadecimal. Error codes or'ed together.) 1 Digit out of range. (Check digit.) 2 Display digit change more than 96 tics off. 4 Wrong count. (cooking) 8 Wrong count. (paused) 10 Wrong count displayed. (Not cooking nor paused.) 20 Display should be zero. Beep: 1 Should not be beeping. 2 Beep duration wrong. 4 End of cooking beep missing. 8 Missed a beep. (overlap) 10 Missed a beep. (endtest) 20 Wouldn't stop beeping! Mag 1 Mag on when cooking off. 2 Mag cycle error. 4 Wrong power level. 8 Would not turn off. 10 Should be off (when cooking verified). "); $display("\n ** Testbench Monitoring ** "); $display(" Display %s", monitor_display ? "on" : "off"); $display(" Keys %s", monitor_keys ? "on" : "off"); $display(" Beep %s", monitor_beep ? "on" : "off"); $display(" Mag %s", monitor_mag ? "on" : "off"); $display(" To turn monitoring on and off edit monitor_FOO variables."); $display(" at last \"initial begin\" in testbench.\n\n"); end $display("%s, beep tests (code: %h).", error_beep_total ? "FAIL" : "PASS", error_beep_total); $display("%s, mag tests (code: %h).", error_mag_total ? "FAIL" : "PASS", error_mag_total); $display("%s, display tests (code: %h).", error_display_total ? "FAIL" : "PASS", error_display_total); $display("All tests completed, %d total errors.",error_total); $stop; end // initial begin endmodule // test_oven // exemplar translate_on