// // Title : LCD test fixture // Author : Joshua Snyder // Company : CSE 370 // //------------------------------------------------------------------------------------------------- // // File : lcd_tf.v // Description : This test fixture emulates the LCD, checking as much as possible valid timing // //------------------------------------------------------------------------------------------------- // // Write Timing // _____ ____________________________ ________ // RS _____X____________________________X________ // | | // _____| |________ // RW _____\____________________________/________ // |-T_AS-|------PW_E----- |T_AH| // | |________________| | __ // E ____________/ \________________/ // | |-T_DSW-|T_H-| | // ____________|________|____________|________ | // data ____________|________X____________X________ | // | ^VALID DATA^ | // |------------T_CYC_E--------------| // // Read Timing // _____ ____________________________ ________ // RS _____X____________________________X________ // | | // _____|____________________________|________ // RW _____/ \________ // |-T_AS-|------PW_E----- |T_AH| // | |________________| | __ // E ____________/ \________________/ // |--T_DDR--| |T_DHR| | // ____________|_________|____________|_______ | // data ____________|_________X____________X_______ | // | ^VALID DATA^ | // |------------T_CYC_E--------------| // // NOTE: The only supported read insruction is read busy flag `timescale 1ns / 1ns `define STOP_ON_ERROR `define STOP_ON_WARNING `ifdef STOP_ON_ERROR `define ERROR_STOP $stop `else `define ERROR_STOP `endif `ifdef STOP_ON_WARNING `define WARNING_STOP $stop `else `define WARNING_STOP `endif module lcd_tf ( input RS, input RW, input E, inout [7:0] data, input reset_button ); // All times are in nanoseconds parameter T_CYC_E = 1000; parameter PW_E = 450; parameter T_AS = 140; parameter T_AH = 10; parameter T_DSW = 195; parameter T_H = 10; parameter T_DDR = 320; parameter T_DHR = 20; parameter T_CLEAR_DISPLAY = 1640000; parameter T_FUNCTION_SET = 40000; parameter T_DISPLAY_ON = 40000; parameter T_ENTRY_MODE_SET = 40000; parameter T_WRITE_CHARACTER = 40000; parameter T_UNKNOWN_INSTRUCTION = 40000; event e_clear_display; event e_function_set; event e_display_on; event e_entry_mode_set; event e_write_character; event e_unknown_instruction; event e_read_start; event e_read_end; time t_RS_RW; time t_neg_E; time t_pos_E; time t_data; time t_reset; reg display_cleared; reg function_set; reg display_on; reg entry_mode_set; reg prev_RS; reg prev_RW; reg prev_E; reg [7:0] prev_data; reg [7:0] out_data; reg busy; reg output_enable; assign data = !RW ? 8'bzzzz_zzzz : (output_enable ? out_data : 8'bxxxx_xxxx); initial begin t_RS_RW = 0; t_pos_E = 0; t_neg_E = 0; t_data = 0; t_reset = 0; display_cleared = 0; function_set = 0; display_on = 0; entry_mode_set = 0; busy = 0; output_enable = 0; $timeformat(-9, 0, " ns", 0); end always @(posedge reset_button) begin t_RS_RW = 0; t_pos_E = 0; t_neg_E = 0; t_data = 0; t_reset = $time; display_cleared = 0; function_set = 0; display_on = 0; entry_mode_set = 0; busy = 0; output_enable = 0; prev_RS = RS; prev_RW = RW; prev_E = E; prev_data = data; end always @(RS, RW, E, data) if (reset_button) begin if (E !== 0 && E !== 1) begin // E = x or z $display("Error @ %t", $time); $display("E = %b", E); `ERROR_STOP; end if (RS !== prev_RS || RW !== prev_RW) t_RS_RW = $time; if (E && !prev_E) begin if ($time - t_pos_E < T_CYC_E && $time > t_reset && t_pos_E > t_reset) begin $display("Error @ %t", $time); $display("Enable Cycle Time (T_CYC_E) not met"); $display("T_CYC_E = %t, should be at least %t", $time - t_pos_E, T_CYC_E); `ERROR_STOP; end if (RS !== 0 && RS !== 1) begin // RS = x or z $display("Error @ %t", $time); $display("RS = %b", RS); `ERROR_STOP; end if (RW !== 0 && RW !== 1) begin // RW = x or z $display("Error @ %t", $time); $display("RW = %b", RS); `ERROR_STOP; end t_pos_E = $time; end if (!E && prev_E) t_neg_E = $time; if (data !== prev_data) t_data = $time; if ($time > t_reset) begin if (RS !== prev_RS || RW !== prev_RW) begin if (E) begin $display("Error @ %t", $time); $display("RS and RW should not change while E is high"); `ERROR_STOP; end if (!E && t_RS_RW - t_neg_E < T_AH) begin $display("Error @ %t", $time); $display("Address Hold Time (T_AH) not met"); $display("T_AH = %t, should be at least %t", t_RS_RW - t_neg_E, T_AH); `ERROR_STOP; end end if (E && !prev_E && t_pos_E - t_RS_RW < T_AS) begin $display("Error @ %t", $time); $display("Address Set-up Time (T_AS) not met"); $display("T_AS = %t, should be at least %t", t_pos_E - t_RS_RW, T_AS); `ERROR_STOP; end if (!E && prev_E) begin if (t_neg_E - t_pos_E < PW_E) begin $display("Error @ %t", $time); $display("Enable Pulse Width (PW_E) not met"); $display("PW_E = %t, should be at least %t", t_neg_E - t_pos_E, PW_E); `ERROR_STOP; end if (!RW && t_neg_E - t_data < T_DSW) begin $display("Error @ %t", $time); $display("Data Start-up Time (T_DSW) not met"); $display("T_DSW = %t, should be at least %t", t_neg_E - t_data, T_DSW); `ERROR_STOP; end end if (data !== prev_data && !RW && t_data - t_neg_E < T_H) begin $display("Error @ %t", $time); $display("Data Hold Time (T_H) not met"); $display("T_H = %t, should be at least %t", t_data - t_neg_E, T_H); `ERROR_STOP; end end if (!E && prev_E && !RW) begin if (busy) begin $display("Error @ %t", $time); $display("Display was busy with the previous command"); `ERROR_STOP; end else if (RS) -> e_write_character; else if (data == 8'b0000_0001) -> e_clear_display; else if (data[7:2] == 6'b0011_00) -> e_function_set; else if (data == 8'b0000_1100) -> e_display_on; else if (data === 8'b0000_0110) -> e_entry_mode_set; else -> e_unknown_instruction; end if (E && (!prev_E || $time == t_reset) && RW) begin if (RS) begin $display("Error @ %t", $time); $display("RS must be low to read the busy flag"); `ERROR_STOP; end -> e_read_start; end if (!E && prev_E && RW) -> e_read_end; prev_RS = RS; prev_RW = RW; prev_E = E; prev_data = data; end always @(e_write_character) begin $display("Write Character: %c", data); if (!display_cleared || !function_set || !display_on || !entry_mode_set) begin $display("Warning @ %t", $time); $display("Display may not have been initialized properly"); `WARNING_STOP; end busy = 1; #T_WRITE_CHARACTER busy = 0; end always @(e_clear_display) begin $display("Display cleared"); if (display_cleared) begin $display("Warning @ %t", $time); $display("Display has already been cleared"); `WARNING_STOP; end display_cleared = 1; busy = 1; #T_CLEAR_DISPLAY busy = 0; end always @(e_function_set) begin $display("Display function set"); if (function_set) begin $display("Warning @ %t", $time); $display("Display function has already been set"); `WARNING_STOP; end function_set = 1; busy = 1; #T_FUNCTION_SET busy = 0; end always @(e_display_on) begin $display("Display turned on"); if (display_on) begin $display("Warning @ %t", $time); $display("Display has already been turned on"); `WARNING_STOP; end display_on = 1; busy = 1; #T_DISPLAY_ON busy = 0; end always @(e_entry_mode_set) begin $display("Display entry mode set"); if (entry_mode_set) begin $display("Warning @ %t", $time); $display("Display entry mode has already been set"); `WARNING_STOP; end entry_mode_set = 1; busy = 1; #T_ENTRY_MODE_SET busy = 0; end always @(e_unknown_instruction) begin $display("Warning @ %t", $time); $display("Unknown command: %b", data); `WARNING_STOP; busy = 1; #T_UNKNOWN_INSTRUCTION busy = 0; end always @(e_read_start) begin #T_DDR if (E && RW) output_enable = 1; end always @(e_read_end) begin #T_DHR output_enable = 0; end always @(busy, RS) begin if (RS === 0) out_data = {busy, 7'bxxx_xxxx}; else out_data = 8'bxxxx_xxxx; end `ifdef stop_on_error always @(e_error) #1 $stop; `endif `ifdef stop_on_warning always @(e_warning) #1 $stop; `endif endmodule