//////////////////////////////////////////////////////////////////////////////// // project overview: (outdated) // When the reset button is pressed, the game generates a random four-digit // number X. Players can use the keyboard to enter a four-digit number Y. When // the player finishes entering Y, it calculates A and B based on X and Y and // displays them on SSD. // X will be generated with LFSR. Y will be entered with 0~9 on the keyboard. // ESC on the keyboard will be used as the reset button. LCD will be used to // display the game log. //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// // change log overview: // // 2020/05/26: // // (Jing) edits/changes: // - reg state [BIG CHANGE] (rewrite the state transition) // - reg cursor_position (tidy/clean up code) // - reg x (fix syntax error due to invalid dimension declaration) // - reg y (fix syntax error due to invalid dimension declaration) // // (Jing) adds/creates: // - integer i // - localparam KEY_ // - wire find_number_pressed // - reg last_change_bcd // - wire method_to_generate_x // - wire find_collision // - reg a // - reg b // // 2020/05/28 forenoon: // // (Jing) edits/changes: // - reg x (split always @ and extend dimension) // - reg y (split always @ and extend dimension) // - reg find_collision (split to find_x_collision and find_y_collision) // - reg state (remove latch inferring) // - reg last_change_bcd (add a localparam to represent non-BCD char) // - reg a (fix syntax error due to dimension expansion of x and y) // - reg b (fix syntax error due to dimension expansion of x and y) // // (Jing) adds/creates: // - reg round // - reg led // - reg rng_10 (random number generator) // // (Jing) sidenotes: // - split out the miscellaneous signals from "global" section. // - comment out the vga part. // // 2020/05/28 afternoon: // // (Jing) sidenotes: // - slightly modified the state transition. // - rename ssd_digit_? to extended_bcd_digit[0:3] // - separates extended_bcd_digit[0:3] out to their own sections // - rename extended_bcd_digit[0:3] to ssd_content_selector[0:3] // - [rewrite] anode 1-hot enable and cathode select. // - instantiates extended_bcd_to_ssd_decoder. // - adds reg ssd and reg point. // - ssd_cathode is now composed of {ssd, point}. // - the cursor is now a blinking point. // - rename find_?_collision to ?_valid. // - add clk_1hz and timer. // - the SSD will show total time after 4A0B. // - you can now press X to see the value of X. // - you can now press Y to see the value of Y. // - you can now press T to see the current time. // - (2020/05/28 23:51) fully debugged and playable. // // 2020/06/11: // (Jing) sidenote: // - instantiate the RandomX.v (see the "lfsr", "x_valid", and "x" sections) // - use a 16-bit LFSR to replace the timing-based randomizer (see "x") // //////////////////////////////////////////////////////////////////////////////// module final_project_TOP( // SSD output [7:0] ssd_cathode, output [3:0] ssd_anode, /* // LCD output [3:0] vgaRed, output [3:0] vgaGreen, output [3:0] vgaBlue, output hsync, output vsync, // I2S (speaker) output audio_mclk, // master clock output audio_lrck, // left-right clock output audio_sck, // serial clock output audio_sdin, // serial audio data input */ // LED output reg [15:0] led, // PS2 (keyboard) inout PS2_DATA, inout PS2_CLK, // DIP switch input generate_x_randomly, // debug switch input debugswitch0, // W17 input debugswitch1, // W16 input debugswitch2, // V16 // miscellaneous input clk_100mhz, input rst_n // active low reset ); ////////////////////////////////////////////////////////////////////////////// // global (declaration only) // // // integer: // // i: // for (; ; ) begin // // Statements // end // // reg: // // state: // the current state and localparam of the FSM. // the definition is in the "state transition" section below. // x: // the target number to be guessed // the definition is in the "x" section below. // usage: " x[cursor_position] " // y: // the definition is in the "y" section below. // usage: " y[cursor_position] " // ////////////////////////////////////////////////////////////////////////////// integer i; reg [1:0] state; localparam GAMEOVER = 4'd0; localparam WAIT_X = 4'd1; localparam WAIT_Y = 4'd2; localparam SHOW_AB = 4'd3; reg [3:0] x[0:3]; reg [3:0] y[0:3]; ////////////////////////////////////////////////////////////////////////////// // miscellaneous // // wire: // // rst: // active high reset. // reset: // active high reset. // // reg: // // clk_freerun: // a free-run n-bit binary counter to generate 100M / 2^n Hz clock. // ////////////////////////////////////////////////////////////////////////////// wire rst; wire reset; assign rst = ~rst_n; assign reset = ~rst_n; reg [31:0] clk_freerun; always @(posedge clk_100mhz or negedge rst_n) begin if (~rst_n) clk_freerun <= 'b0; else clk_freerun <= clk_freerun + 32'b1; end ////////////////////////////////////////////////////////////////////////////// // keyboard IP // // wire: // // key_down: // status bits. Each bit indicates pressed (1) or released (0) of each // button of the keyboard. // last_change: // represent the key which has been pressed or released. // key_valid: // should be active for one clock period when any key is pressed or // released. ////////////////////////////////////////////////////////////////////////////// wire [511:0] key_down; wire [8:0] last_change; wire key_valid; KeyboardDecoder KeyboardDecoder( // output .key_down, .last_change, .key_valid, // inout .PS2_DATA, .PS2_CLK, // input .rst, .clk (clk_100mhz) // 100mhz clock ); ////////////////////////////////////////////////////////////////////////////// // localparam KEY_??? // // usage: // " if (key_down[KEY_???]) " // " if (last_change == KEY_???) " // // note: // usually checked with " if (key_valid) " ////////////////////////////////////////////////////////////////////////////// // miscellaneous localparam KEY_ENTER = 9'h05A; localparam KEY_BACKSPACE = 9'h066; localparam KEY_UP = 9'h175; localparam KEY_DOWN = 9'h172; localparam KEY_LEFT = 9'h16B; localparam KEY_RIGHT = 9'h174; // left-hand-side numbers localparam KEY_0L = 9'h45; localparam KEY_1L = 9'h16; localparam KEY_2L = 9'h1E; localparam KEY_3L = 9'h26; localparam KEY_4L = 9'h25; localparam KEY_5L = 9'h2E; localparam KEY_6L = 9'h36; localparam KEY_7L = 9'h3D; localparam KEY_8L = 9'h3E; localparam KEY_9L = 9'h46; // right-hand-side numbers localparam KEY_0R = 9'h70; localparam KEY_1R = 9'h69; localparam KEY_2R = 9'h72; localparam KEY_3R = 9'h7A; localparam KEY_4R = 9'h6B; localparam KEY_5R = 9'h73; localparam KEY_6R = 9'h74; localparam KEY_7R = 9'h6C; localparam KEY_8R = 9'h75; localparam KEY_9R = 9'h7D; // alphabet localparam KEY_A = 9'h01C; localparam KEY_B = 9'h032; localparam KEY_C = 9'h021; localparam KEY_D = 9'h023; localparam KEY_E = 9'h024; localparam KEY_F = 9'h02B; localparam KEY_G = 9'h034; localparam KEY_H = 9'h033; localparam KEY_I = 9'h043; localparam KEY_J = 9'h03B; localparam KEY_K = 9'h042; localparam KEY_L = 9'h04B; localparam KEY_M = 9'h03A; localparam KEY_N = 9'h031; localparam KEY_O = 9'h044; localparam KEY_P = 9'h04D; localparam KEY_Q = 9'h015; localparam KEY_R = 9'h02D; localparam KEY_S = 9'h01B; localparam KEY_T = 9'h02C; localparam KEY_U = 9'h03C; localparam KEY_V = 9'h02A; localparam KEY_W = 9'h01D; localparam KEY_X = 9'h022; localparam KEY_Y = 9'h035; localparam KEY_Z = 9'h01A; ////////////////////////////////////////////////////////////////////////////// // find_number_pressed // ////////////////////////////////////////////////////////////////////////////// wire find_number_pressed; assign find_number_pressed = key_down[KEY_0R] | key_down[KEY_1R] | key_down[KEY_2R] | key_down[KEY_3R] | key_down[KEY_4R] | key_down[KEY_5R] | key_down[KEY_6R] | key_down[KEY_7R] | key_down[KEY_8R] | key_down[KEY_9R]; ////////////////////////////////////////////////////////////////////////////// // last_change_bcd // last_change in bcd decoded // (LUT) // // change log: // // 2020/05/28: // (Jing) add a localparam (defalut 4'hC) to represent a non-BCD char // // ////////////////////////////////////////////////////////////////////////////// reg [3:0] last_change_bcd; localparam NOT_BCD = 4'hC; always @* begin case (last_change) // left-hand-side keyboard 8'h45: last_change_bcd = 4'd0; 8'h16: last_change_bcd = 4'd1; 8'h1E: last_change_bcd = 4'd2; 8'h26: last_change_bcd = 4'd3; 8'h25: last_change_bcd = 4'd4; 8'h2E: last_change_bcd = 4'd5; 8'h36: last_change_bcd = 4'd6; 8'h3D: last_change_bcd = 4'd7; 8'h3E: last_change_bcd = 4'd8; 8'h46: last_change_bcd = 4'd9; // right-hand-side keyboard 8'h70: last_change_bcd = 4'd0; 8'h69: last_change_bcd = 4'd1; 8'h72: last_change_bcd = 4'd2; 8'h7A: last_change_bcd = 4'd3; 8'h6B: last_change_bcd = 4'd4; 8'h73: last_change_bcd = 4'd5; 8'h74: last_change_bcd = 4'd6; 8'h6C: last_change_bcd = 4'd7; 8'h75: last_change_bcd = 4'd8; 8'h7D: last_change_bcd = 4'd9; default: last_change_bcd = NOT_BCD; endcase end ////////////////////////////////////////////////////////////////////////////// // lfsr: // // log: // 2020/06/11: // (Jing) initial commit // ////////////////////////////////////////////////////////////////////////////// reg [15:0] lfsr; always @(posedge clk_100mhz or negedge rst_n) begin if (~rst_n) begin lfsr <= 'b0; end else if (state == GAMEOVER && generate_x_randomly) begin lfsr <= 16'hFFFF; // seed end else begin lfsr <= {lfsr, lfsr[10] ^ lfsr[12] ^ lfsr[13] ^ lfsr[15]}; end end ////////////////////////////////////////////////////////////////////////////// // round // ////////////////////////////////////////////////////////////////////////////// reg [3:0] round; always @(posedge clk_100mhz or negedge rst_n) begin if (~rst_n) begin round <= 'b0; end else if (key_valid && state == SHOW_AB && key_down[KEY_ENTER]) begin round <= round + 16'b1; end else begin round <= round; end end ////////////////////////////////////////////////////////////////////////////// // cursor_position // 3 2 1 0 (positional) ////////////////////////////////////////////////////////////////////////////// reg [1:0] cursor_position; always @(posedge clk_100mhz or negedge rst_n) begin if (~rst_n) begin cursor_position <= 'b0; end else if (key_valid) begin case (1'b1) key_down[KEY_ENTER]: cursor_position <= 2'd3; find_number_pressed: cursor_position <= cursor_position - 2'b1; key_down[KEY_BACKSPACE]: cursor_position <= cursor_position + 2'b1; key_down[KEY_RIGHT]: cursor_position <= cursor_position - 2'b1; key_down[KEY_LEFT]: cursor_position <= cursor_position + 2'b1; default: cursor_position <= cursor_position; endcase end else begin cursor_position <= cursor_position; end end ////////////////////////////////////////////////////////////////////////////// // x_valid // y_valid // // change log: // // 2020/05/28: // (Jing) find_collision split to find_x_collision and find_y_collision // (Jing) rename find_?_collision to ?_valid // // ////////////////////////////////////////////////////////////////////////////// wire x_valid; assign x_valid = (x[0] != x[1]) & (x[0] != x[2]) & (x[0] != x[3]) & (x[1] != x[2]) & (x[1] != x[3]) & (x[2] != x[3]) & (x[0] <= 4'd9) & (x[1] <= 4'd9) & (x[2] <= 4'd9) & (x[3] <= 4'd9); wire y_valid; assign y_valid = (y[0] != y[1]) & (y[0] != y[2]) & (y[0] != y[3]) & (y[1] != y[2]) & (y[1] != y[3]) & (y[2] != y[3]) & (y[0] <= 4'd9) & (y[1] <= 4'd9) & (y[2] <= 4'd9) & (y[3] <= 4'd9); ////////////////////////////////////////////////////////////////////////////// // x // // declared in the "global" section above: // reg [3:0] x[0:3]; // // change log: // // 2020/05/26: // (Jing) tidy up // // 2020/05/28: // (Jing) split always@ (improve readability) // (Jing) extend "reg [3:0] x[0:3];" to "reg [3:0] x[0:15][0:3];" // (Jing) the default value is now set to be "4'hF - cursor_position" // // 2020/06/11: // (Jing) use a 16-bit LFSR to replace the timing-based randomizer. // // ////////////////////////////////////////////////////////////////////////////// always @(posedge clk_100mhz or negedge rst_n) begin if (~rst_n) begin x[0] <= 'b0; x[1] <= 'b0; x[2] <= 'b0; x[3] <= 'b0; end else if (state == GAMEOVER) begin x[0] <= 4'hF; x[1] <= 4'hF; x[2] <= 4'hF; x[3] <= 4'hF; end else if (state == WAIT_X && key_valid) begin if (find_number_pressed) x[cursor_position] <= last_change_bcd; if (key_down[KEY_BACKSPACE]) x[cursor_position] <= 4'hF; end else if (state == SHOW_AB && generate_x_randomly && !x_valid) begin {x[0], x[1], x[2], x[3]} <= lfsr; end else begin x[0] <= x[0]; x[1] <= x[1]; x[2] <= x[2]; x[3] <= x[3]; end end ////////////////////////////////////////////////////////////////////////////// // y // // declared in the "global" section above: // reg [3:0] y[0:15][0:3]; // // change log: // // 2020/05/26: // (Jing) tidy up // // 2020/05/28: // (Jing) split always@ // (Jing) extend "reg [3:0] y[0:3];" to "reg [3:0] y[0:15][0:3];" // (Jing) the default value is now set to be "4'hF - cursor_position" // // // // ////////////////////////////////////////////////////////////////////////////// always @(posedge clk_100mhz or negedge rst_n) begin if (~rst_n) begin y[0] <= 'b0; y[1] <= 'b0; y[2] <= 'b0; y[3] <= 'b0; end else if (state == GAMEOVER) begin y[0] <= 4'hF; y[1] <= 4'hF; y[2] <= 4'hF; y[3] <= 4'hF; end else if (state == WAIT_Y && key_valid) begin if (find_number_pressed) y[cursor_position] <= last_change_bcd; if (key_down[KEY_BACKSPACE]) y[cursor_position] <= 4'hF; end else begin y[0] <= y[0]; y[1] <= y[1]; y[2] <= y[2]; y[3] <= y[3]; end end ////////////////////////////////////////////////////////////////////////////// // a b // // change log: // // 2020/05/28: // (Jing) fix syntax error due to dimension expansion of x and y // // // ////////////////////////////////////////////////////////////////////////////// wire [2:0] a; assign a = (x[0] == y[0]) + (x[1] == y[1]) + (x[2] == y[2]) + (x[3] == y[3]); wire [2:0] b; assign b = (x[0] == y[1]) + (x[0] == y[2]) + (x[0] == y[3]) + (x[1] == y[0]) + (x[1] == y[2]) + (x[1] == y[3]) + (x[2] == y[0]) + (x[2] == y[1]) + (x[2] == y[3]) + (x[3] == y[0]) + (x[3] == y[1]) + (x[3] == y[2]); ////////////////////////////////////////////////////////////////////////////// // reg: // // timer: // 0 ~ (2^12 - 1) = 4095 > 3600 = 60 * 60 seconds // count_100m: // 0 ~ 100m - 1 = 99_999_999 ////////////////////////////////////////////////////////////////////////////// reg [26:0] count_100m; always @(posedge clk_100mhz or negedge rst_n) begin if (~rst_n) count_100m <= 'b0; else count_100m <= (count_100m < 27'd99_999_999) ? (count_100m + 27'b1) : (27'b0); end reg [11:0] timer; always @(posedge count_100m[26] or negedge rst_n) begin if (~rst_n) begin timer <= 'b0; end else if (round == 16'h0 && state == WAIT_Y) begin timer <= 12'd0; end else if (state == GAMEOVER) begin timer <= timer; end else begin timer <= timer + 12'b1; end end ////////////////////////////////////////////////////////////////////////////// // // ssd_content_selector // // change log: // // 2020/05/28: // (Jing) rename extended_bcd_digit[0:3] to ssd_content_selector[0:3] // ////////////////////////////////////////////////////////////////////////////// reg [3:0] ssd_content_selector[0:3]; always @(posedge clk_100mhz or negedge rst_n) begin if (~rst_n) begin ssd_content_selector[0] <= 'b0; ssd_content_selector[1] <= 'b0; ssd_content_selector[2] <= 'b0; ssd_content_selector[3] <= 'b0; end else if (key_down[KEY_X]) begin ssd_content_selector[0] <= x[0]; ssd_content_selector[1] <= x[1]; ssd_content_selector[2] <= x[2]; ssd_content_selector[3] <= x[3]; end else if (key_down[KEY_Y]) begin ssd_content_selector[0] <= y[0]; ssd_content_selector[1] <= y[1]; ssd_content_selector[2] <= y[2]; ssd_content_selector[3] <= y[3]; end else if (key_down[KEY_T]) begin ssd_content_selector[0] <= timer % 60 % 10; ssd_content_selector[1] <= timer % 60 / 10; ssd_content_selector[2] <= timer / 60 % 10; ssd_content_selector[3] <= timer / 60 / 10; end else if (state == WAIT_X) begin ssd_content_selector[0] <= x[0]; ssd_content_selector[1] <= x[1]; ssd_content_selector[2] <= x[2]; ssd_content_selector[3] <= x[3]; end else if (state == WAIT_Y) begin ssd_content_selector[0] <= y[0]; ssd_content_selector[1] <= y[1]; ssd_content_selector[2] <= y[2]; ssd_content_selector[3] <= y[3]; end else if (state == GAMEOVER) begin ssd_content_selector[0] <= timer % 60 % 10; ssd_content_selector[1] <= timer % 60 / 10; ssd_content_selector[2] <= timer / 60 % 10; ssd_content_selector[3] <= timer / 60 / 10; end else begin ssd_content_selector[0] <= 4'hB; ssd_content_selector[1] <= b; ssd_content_selector[2] <= 4'hA; ssd_content_selector[3] <= a; end end ////////////////////////////////////////////////////////////////////////////// // SSD // The anodes of the seven LEDs forming each digit are tied together into one // "common anode" circuit node, but the LED cathodes remain separate. This // signal connection scheme creates a multiplexed display, where the cathode // signals are common to all digits but they can only illuminate the segments // of the digit whose corresponding anode signal is asserted. // // use free-run clock to create persistence of vision. drives the anode // signals and corresponding cathode patterns of each digit in a repeating, // continuous succession at an update rate that is faster than the human eye // can detect. Each digit is illuminated just one-fourth of the time, but // because the eye cannot perceive the darkening of a digit before it is // illuminated again, the digit appears continuously illuminated. If the // update, or "refresh", rate is slowed to around 45Hz, a flicker can be // noticed in the display. // // output: // // ssd_cathode: // The cathodes of similar segments on all four displays are connected into // seven circuit nodes labeled CA through CG. These seven cathode signals // are available as inputs to the 4-digit display. // ssd_anode: // The common anode signals are available as four "digit enable" input // signals to the 4-digit display. // // reg: // // ssd: // decoded from extended BCD. // point // indicate the blinking cursor. // // change log: // // 2020/05/28: // (Jing) rename ssd_digit_? to extended_bcd_digit[0:3] // (Jing) separate extended_bcd_digit[0:3] out to their own sections // (Jing) [rewrite] anode 1-hot enable and cathode select. // (Jing) instantiate extended_bcd_to_ssd_decoder. // (Jing) ssd_cathode is now composed of {ssd, point} // ////////////////////////////////////////////////////////////////////////////// // cathode select reg [6:0] ssd; always @* begin case(ssd_content_selector[clk_freerun[19:18]]) 4'h0: ssd = 7'b0000001; // display 0 4'h1: ssd = 7'b1001111; // display 1 4'h2: ssd = 7'b0010010; // display 2 4'h3: ssd = 7'b0000110; // display 3 4'h4: ssd = 7'b1001100; // display 4 4'h5: ssd = 7'b0100100; // display 5 4'h6: ssd = 7'b0100000; // display 6 4'h7: ssd = 7'b0001111; // display 7 4'h8: ssd = 7'b0000000; // display 8 4'h9: ssd = 7'b0000100; // display 9 4'hA: ssd = 7'b0001000; // display A 4'hB: ssd = 7'b1100000; // diaplay b 4'hF: ssd = 7'b1111111; // blank, the default value of x[] and y[] default: ssd = 'b0; // catch error endcase end // blinking cursor wire point; assign point = (state == WAIT_X || state == WAIT_Y) && (!key_down[KEY_T]) & (clk_freerun[19:18] == cursor_position) & (clk_freerun[25]); // merge cathode assign ssd_cathode = {ssd, ~point}; // anode 1-hot enable assign ssd_anode = ~( 'b1 << clk_freerun[19:18] ); ////////////////////////////////////////////////////////////////////////////// // state transition // // declared in the "global" section above: // // reg [1:0] state; // localparam GAMEOVER = 4'd0; // localparam WAIT_X = 4'd1; // localparam WAIT_Y = 4'd2; // localparam SHOW_AB = 4'd3; // // // change log: // // 2020/05/26: // (Jing) [rewrite] reduce the number of states from 11 to 4 // (require further discussion) // // 2020/05/28: // (Jing) remove latch inferring // // ////////////////////////////////////////////////////////////////////////////// // state always @(posedge clk_100mhz or negedge rst_n) begin if (~rst_n) begin state <= 'b0; end else if (key_valid && key_down[KEY_ENTER]) begin case (state) GAMEOVER: begin if (generate_x_randomly) state <= WAIT_Y; else state <= WAIT_X; end WAIT_X: begin if (x_valid) state <= WAIT_Y; end WAIT_Y: begin if (a == 3'd4) state <= GAMEOVER; else if (y_valid) state <= SHOW_AB; end SHOW_AB: begin state <= WAIT_Y; end default: begin state <= state; end endcase end else begin state <= state; end end ////////////////////////////////////////////////////////////////////////////// // led // ////////////////////////////////////////////////////////////////////////////// always @* begin led = ~(16'b1111_1111_1111_1111 << round); led[round] = clk_freerun[25]; // blinking if (debugswitch2) led = 'b1 << state; end endmodule