diff --git a/info.yaml b/info.yaml index 25cdc6c..d3c4c51 100644 --- a/info.yaml +++ b/info.yaml @@ -17,12 +17,20 @@ project: # Source files must be in ./src and you must list each source file separately, one per line. # Don't forget to also update `PROJECT_SOURCES` in test/Makefile. source_files: + - "audio_interface.v" + - "bg_line.v" + - "bg_object.v" + - "bg_object_rom.v" + - "bg_render.v" - "debounce.v" - "dino_game_top.v" - "dino_render.v" - "dino_rom.v" + - "gamepad_pmod.v" + - "game_over_sound_player.v" - "graphics_top.v" - "hvsync_generator.v" + - "jump_sound_player.v" - "lfsr.v" - "obs_render.v" - "obs_rom.v" @@ -31,13 +39,6 @@ project: - "player_physics.v" - "score.v" - "score_render.v" - - "audio_interface.v" - - "jump_sound_player.v" - - "game_over_sound_player.v" - - "bg_object.v" - - "bg_object_rom.v" - - "bg_render.v" - - "bg_line.v" # The pinout of your project. Leave unused pins blank. DO NOT delete or add any pins. diff --git a/src/dino_game_top.v b/src/dino_game_top.v index 8522c3d..7197c08 100644 --- a/src/dino_game_top.v +++ b/src/dino_game_top.v @@ -19,24 +19,48 @@ module tt_um_uwasic_dinogame #(parameter CONV = 2) ( wire game_tick_60hz; wire [1:0] game_tick_20hz; // two consecutive pulses generated ([0] and then [1]), enabling pipelining - wire debounce_countdown_en; // pulse on rising edge of 5th vpos bit - wire button_up; - wire button_down; + // Gamepad Pmod support + wire gamepad_pmod_latch = ui_in[4]; + wire gamepad_pmod_clk = ui_in[5]; + wire gamepad_pmod_data = ui_in[6]; + wire gamepad_is_present; // HIGH when gamepad is connected + wire gamepad_up; + wire gamepad_down; + wire gamepad_start; // Can leverage start, select from SNES + wire gamepad_b; + wire gamepad_y; + wire gamepad_select; + wire gamepad_left; + wire gamepad_right; + wire gamepad_a; + wire gamepad_x; + wire gamepad_l; + wire gamepad_r; - button_debounce button_up_debounce ( + // Synchronizes pmod_data, pmod_clk, pmod_latch signals to system clock + // domain. + gamepad_pmod_single gamepad_pmod ( + // Inputs: .clk(clk), .rst_n(rst_n), - .countdown_en(debounce_countdown_en), - .button_in(ui_in[0]), - .button_out(button_up) - ); - - button_debounce button_down_debounce ( - .clk(clk), - .rst_n(rst_n), - .countdown_en(debounce_countdown_en), - .button_in(ui_in[1]), - .button_out(button_down) + .pmod_latch(gamepad_pmod_latch), + .pmod_clk(gamepad_pmod_clk), + .pmod_data(gamepad_pmod_data), + + // Outputs: + .is_present(gamepad_is_present), + .up(gamepad_up), + .down(gamepad_down), + .start(gamepad_start), + .b(gamepad_b), + .y(gamepad_y), + .select(gamepad_select), + .left(gamepad_left), + .right(gamepad_right), + .a(gamepad_a), + .x(gamepad_x), + .l(gamepad_l), + .r(gamepad_r) ); // GAME STATE SIGNALS @@ -67,9 +91,9 @@ module tt_um_uwasic_dinogame #(parameter CONV = 2) ( .clk(clk), .rst_n(rst_n), .game_tick(game_tick_20hz), - .button_start(button_up), - .button_up(button_up), - .button_down(button_down), + .button_start(gamepad_start), + .button_up(gamepad_up), + .button_down(gamepad_down), .crash(crash), .player_position(player_position), .game_frozen(game_frozen), @@ -133,7 +157,7 @@ module tt_um_uwasic_dinogame #(parameter CONV = 2) ( obs_rom obs_rom_inst_2 (.clk(clk), .rst(~rst_n), .i_rom_counter(obs_rom_counter_2), .i_obs_type(obstacle2_type), .o_sprite_color(obs_color_2)); bg_object_rom bg_object_rom_inst (.clk(clk), .rst(~rst_n), .i_rom_counter(bg_objects_rom_counter), .o_sprite_color(bg_object_color)); wire [15:0] score; - + score_render #(.CONV(CONV), .OFFSET(120)) score_inst_1 ( .clk(clk), .rst(~rst_n), @@ -142,7 +166,7 @@ module tt_um_uwasic_dinogame #(parameter CONV = 2) ( .i_vpos(vpos), .o_score_color(score_color_1) ); - + score_render #(.CONV(CONV), .OFFSET(110)) score_inst_2 ( .clk(clk), .rst(~rst_n), @@ -234,7 +258,6 @@ module tt_um_uwasic_dinogame #(parameter CONV = 2) ( .o_game_tick_60hz(game_tick_60hz), .o_game_tick_20hz(game_tick_20hz[0]), .o_game_tick_20hz_r(game_tick_20hz[1]), - .o_vpos_5_r(debounce_countdown_en), .o_collision(crash) ); @@ -263,6 +286,6 @@ module tt_um_uwasic_dinogame #(parameter CONV = 2) ( assign uio_oe = 8'b10000000; // List all unused inputs to prevent warnings - wire _unused = &{ena, ui_in[7:2], uio_in, 1'b0}; + wire _unused = &{ena, ui_in[7], ui_in[3:0], uio_in, gamepad_start, gamepad_b, gamepad_y, gamepad_select, gamepad_left, gamepad_right, gamepad_a, gamepad_x, gamepad_l, gamepad_r, gamepad_is_present, 1'b0}; endmodule diff --git a/src/gamepad_pmod.v b/src/gamepad_pmod.v new file mode 100644 index 0000000..16ef8aa --- /dev/null +++ b/src/gamepad_pmod.v @@ -0,0 +1,311 @@ +// Vendored from: https://github.com/psychogenic/gamepad-pmod/blob/main/verilog/gamepad_pmod.v + +/* + * Copyright (c) 2025 Pat Deegan + * https://psychogenic.com + * SPDX-License-Identifier: Apache-2.0 + * + * Interfacing code for the Gamepad Pmod from Psycogenic Technologies, + * designed for Tiny Tapeout. + * + * There are two high-level modules that most users will be interested in: + * - gamepad_pmod_single: for a single controller; + * - gamepad_pmod_dual: for two controllers. + * + * There are also two lower-level modules that you can use if you want to + * handle the interfacing yourself: + * - gamepad_pmod_driver: interfaces with the Pmod and provides the raw data; + * - gamepad_pmod_decoder: decodes the raw data into button states. + * + * The docs, schematics, PCB files, and firmware code for the Gamepad Pmod + * are available at https://github.com/psychogenic/gamepad-pmod. + */ + +/** + * gamepad_pmod_driver -- Serial interface for the Gamepad Pmod. + * + * This module reads raw data from the Gamepad Pmod *serially* + * and stores it in a shift register. When the latch signal is received, + * the data is transferred into `data_reg` for further processing. + * + * Functionality: + * - Synchronizes the `pmod_data`, `pmod_clk`, and `pmod_latch` signals + * to the system clock domain. + * - Captures serial data on each falling edge of `pmod_clk`. + * - Transfers the shifted data into `data_reg` when `pmod_latch` goes low. + * + * Parameters: + * - `BIT_WIDTH`: Defines the width of `data_reg` (default: 24 bits). + * + * Inputs: + * - `rst_n`: Active-low reset. + * - `clk`: System clock. + * - `pmod_data`: Serial data input from the Pmod. + * - `pmod_clk`: Serial clock from the Pmod. + * - `pmod_latch`: Latch signal indicating the end of data transmission. + * + * Outputs: + * - `data_reg`: Captured parallel data after shifting is complete. + */ +module gamepad_pmod_driver #( + parameter BIT_WIDTH = 24 +) ( + input wire rst_n, + input wire clk, + input wire pmod_data, + input wire pmod_clk, + input wire pmod_latch, + output reg [BIT_WIDTH-1:0] data_reg +); + + reg pmod_clk_prev; + reg pmod_latch_prev; + reg [BIT_WIDTH-1:0] shift_reg; + + // Sync Pmod signals to the clk domain: + reg [1:0] pmod_data_sync; + reg [1:0] pmod_clk_sync; + reg [1:0] pmod_latch_sync; + + always @(posedge clk) begin + if (~rst_n) begin + pmod_data_sync <= 2'b0; + pmod_clk_sync <= 2'b0; + pmod_latch_sync <= 2'b0; + end else begin + pmod_data_sync <= {pmod_data_sync[0], pmod_data}; + pmod_clk_sync <= {pmod_clk_sync[0], pmod_clk}; + pmod_latch_sync <= {pmod_latch_sync[0], pmod_latch}; + end + end + + always @(posedge clk) begin + if (~rst_n) begin + /* set data and shift registers to all ones + * such that it is detected as "not present" yet. + */ + data_reg <= {BIT_WIDTH{1'b1}}; + shift_reg <= {BIT_WIDTH{1'b1}}; + pmod_clk_prev <= 1'b0; + pmod_latch_prev <= 1'b0; + end + begin + pmod_clk_prev <= pmod_clk_sync[1]; + pmod_latch_prev <= pmod_latch_sync[1]; + + // Capture data on rising edge of pmod_latch: + if (pmod_latch_sync[1] & ~pmod_latch_prev) begin + data_reg <= shift_reg; + end + + // Sample data on rising edge of pmod_clk: + if (pmod_clk_sync[1] & ~pmod_clk_prev) begin + shift_reg <= {shift_reg[BIT_WIDTH-2:0], pmod_data_sync[1]}; + end + end + end + +endmodule + + +/** + * gamepad_pmod_decoder -- Decodes raw data from the Gamepad Pmod. + * + * This module takes a 12-bit parallel data register (`data_reg`) + * and decodes it into individual button states. It also determines + * whether a controller is connected. + * + * Functionality: + * - If `data_reg` contains all `1's` (`0xFFF`), it indicates that no controller is connected. + * - Otherwise, it extracts individual button states from `data_reg`. + * + * Inputs: + * - `data_reg [11:0]`: Captured button state data from the gamepad. + * + * Outputs: + * - `b, y, select, start, up, down, left, right, a, x, l, r`: Individual button states (`1` = pressed, `0` = released). + * - `is_present`: Indicates whether a controller is connected (`1` = connected, `0` = not connected). + */ +module gamepad_pmod_decoder ( + input wire [11:0] data_reg, + output wire b, + output wire y, + output wire select, + output wire start, + output wire up, + output wire down, + output wire left, + output wire right, + output wire a, + output wire x, + output wire l, + output wire r, + output wire is_present +); + + wire _unused = &{b, y, select, left, right, a, x, l, r}; + + // When the controller is not connected, the data register will be all 1's + wire reg_empty = (data_reg == 12'hfff); + assign is_present = reg_empty ? 0 : 1'b1; + assign {b, y, select, start, up, down, left, right, a, x, l, r} = reg_empty ? 0 : data_reg; + +endmodule + + +/** + * gamepad_pmod_single -- Main interface for a single Gamepad Pmod controller. + * + * This module provides button states for a **single controller**, reducing + * resource usage (fewer flip-flops) compared to a dual-controller version. + * + * Inputs: + * - `pmod_data`, `pmod_clk`, and `pmod_latch` are the signals from the PMOD interface. + * + * Outputs: + * - Each button's state is provided as a single-bit wire (e.g., `start`, `up`, etc.). + * - `is_present` indicates whether the controller is connected (`1` = connected, `0` = not detected). + */ +module gamepad_pmod_single ( + input wire rst_n, + input wire clk, + input wire pmod_data, + input wire pmod_clk, + input wire pmod_latch, + + output wire b, + output wire y, + output wire select, + output wire start, + output wire up, + output wire down, + output wire left, + output wire right, + output wire a, + output wire x, + output wire l, + output wire r, + output wire is_present +); + + wire [11:0] gamepad_pmod_data; + + gamepad_pmod_driver #( + .BIT_WIDTH(12) + ) driver ( + .rst_n(rst_n), + .clk(clk), + .pmod_data(pmod_data), + .pmod_clk(pmod_clk), + .pmod_latch(pmod_latch), + .data_reg(gamepad_pmod_data) + ); + + gamepad_pmod_decoder decoder ( + .data_reg(gamepad_pmod_data), + .b(b), + .y(y), + .select(select), + .start(start), + .up(up), + .down(down), + .left(left), + .right(right), + .a(a), + .x(x), + .l(l), + .r(r), + .is_present(is_present) + ); + +endmodule + + +/** + * gamepad_pmod_dual -- Main interface for the Pmod gamepad. + * This module provides button states for two controllers using + * 2-bit vectors for each button (e.g., start[1:0], up[1:0], etc.). + * + * Each button state is represented as a 2-bit vector: + * - Index 0 corresponds to the first controller (e.g., up[0], y[0], etc.). + * - Index 1 corresponds to the second controller (e.g., up[1], y[1], etc.). + * + * The `is_present` signal indicates whether a controller is connected: + * - `is_present[0] == 1` when the first controller is connected. + * - `is_present[1] == 1` when the second controller is connected. + * + * Inputs: + * - `pmod_data`, `pmod_clk`, and `pmod_latch` are the 3 wires coming from the Pmod interface. + * + * Outputs: + * - Button state vectors for each controller. + * - Presence detection via `is_present`. + */ +module gamepad_pmod_dual ( + input wire rst_n, + input wire clk, + input wire pmod_data, + input wire pmod_clk, + input wire pmod_latch, + + output wire [1:0] b, + output wire [1:0] y, + output wire [1:0] select, + output wire [1:0] start, + output wire [1:0] up, + output wire [1:0] down, + output wire [1:0] left, + output wire [1:0] right, + output wire [1:0] a, + output wire [1:0] x, + output wire [1:0] l, + output wire [1:0] r, + output wire [1:0] is_present +); + + wire [23:0] gamepad_pmod_data; + + gamepad_pmod_driver driver ( + .rst_n(rst_n), + .clk(clk), + .pmod_data(pmod_data), + .pmod_clk(pmod_clk), + .pmod_latch(pmod_latch), + .data_reg(gamepad_pmod_data) + ); + + gamepad_pmod_decoder decoder1 ( + .data_reg(gamepad_pmod_data[11:0]), + .b(b[0]), + .y(y[0]), + .select(select[0]), + .start(start[0]), + .up(up[0]), + .down(down[0]), + .left(left[0]), + .right(right[0]), + .a(a[0]), + .x(x[0]), + .l(l[0]), + .r(r[0]), + .is_present(is_present[0]) + ); + + gamepad_pmod_decoder decoder2 ( + .data_reg(gamepad_pmod_data[23:12]), + .b(b[1]), + .y(y[1]), + .select(select[1]), + .start(start[1]), + .up(up[1]), + .down(down[1]), + .left(left[1]), + .right(right[1]), + .a(a[1]), + .x(x[1]), + .l(l[1]), + .r(r[1]), + .is_present(is_present[1]) + ); + +endmodule diff --git a/src/graphics_top.v b/src/graphics_top.v index 22387d4..a6e2a28 100644 --- a/src/graphics_top.v +++ b/src/graphics_top.v @@ -19,14 +19,12 @@ module graphics_top #(parameter CONV = 0)( output reg o_game_tick_60hz, output reg o_game_tick_20hz, output reg o_game_tick_20hz_r, - output reg o_vpos_5_r, output reg o_collision ); // ============== HVSYNC ============= // TODO can change hpos to increment by 2 to reduce bits reg [9:0] hpos; reg [9:0] vpos; - reg vpos_5_r; reg display_on; // TODO can remove this pipeline stage if we don't need it reg hsync; @@ -56,7 +54,6 @@ module graphics_top #(parameter CONV = 0)( vsync_r <= 1'b0; vsync_r_r <= 1'b0; hsync_r_r <= 1'b0; - vpos_5_r <= 1'b0; display_on_r <= 1'b0; display_on_r_r <= 1'b0; end else begin @@ -64,7 +61,6 @@ module graphics_top #(parameter CONV = 0)( hsync_r <= hsync; vsync_r_r <= vsync_r; hsync_r_r <= hsync_r; - vpos_5_r <= vpos[5]; display_on_r <= display_on; display_on_r_r <= display_on_r; end @@ -127,7 +123,6 @@ module graphics_top #(parameter CONV = 0)( o_game_tick_60hz = (vpos == 0) && (hpos == 0); o_game_tick_20hz = frame_counter == 1 && o_game_tick_60hz; o_game_tick_20hz_r = game_tick_r; - o_vpos_5_r = (vpos[5] == 1) && (vpos_5_r == 0); o_collision = collision; end