|
| 1 | +/* |
| 2 | + * Copyright (c) 2025 Pat Deegan |
| 3 | + * https://psychogenic.com |
| 4 | + * SPDX-License-Identifier: Apache-2.0 |
| 5 | + * |
| 6 | + * Interfacing code for the Gamepad Pmod from Psycogenic Technologies, |
| 7 | + * designed for Tiny Tapeout. |
| 8 | + * |
| 9 | + * There are two high-level modules that most users will be interested in: |
| 10 | + * - gamepad_pmod_single: for a single controller; |
| 11 | + * - gamepad_pmod_dual: for two controllers. |
| 12 | + * |
| 13 | + * There are also two lower-level modules that you can use if you want to |
| 14 | + * handle the interfacing yourself: |
| 15 | + * - gamepad_pmod_driver: interfaces with the Pmod and provides the raw data; |
| 16 | + * - gamepad_pmod_decoder: decodes the raw data into button states. |
| 17 | + * |
| 18 | + * The docs, schematics, PCB files, and firmware code for the Gamepad Pmod |
| 19 | + * are available at https://github.com/psychogenic/gamepad-pmod. |
| 20 | + */ |
| 21 | + |
| 22 | +/** |
| 23 | + * gamepad_pmod_driver -- Serial interface for the Gamepad Pmod. |
| 24 | + * |
| 25 | + * This module reads raw data from the Gamepad Pmod *serially* |
| 26 | + * and stores it in a shift register. When the latch signal is received, |
| 27 | + * the data is transferred into `data_reg` for further processing. |
| 28 | + * |
| 29 | + * Functionality: |
| 30 | + * - Synchronizes the `pmod_data`, `pmod_clk`, and `pmod_latch` signals |
| 31 | + * to the system clock domain. |
| 32 | + * - Captures serial data on each falling edge of `pmod_clk`. |
| 33 | + * - Transfers the shifted data into `data_reg` when `pmod_latch` goes low. |
| 34 | + * |
| 35 | + * Parameters: |
| 36 | + * - `BIT_WIDTH`: Defines the width of `data_reg` (default: 24 bits). |
| 37 | + * |
| 38 | + * Inputs: |
| 39 | + * - `rst_n`: Active-low reset. |
| 40 | + * - `clk`: System clock. |
| 41 | + * - `pmod_data`: Serial data input from the Pmod. |
| 42 | + * - `pmod_clk`: Serial clock from the Pmod. |
| 43 | + * - `pmod_latch`: Latch signal indicating the end of data transmission. |
| 44 | + * |
| 45 | + * Outputs: |
| 46 | + * - `data_reg`: Captured parallel data after shifting is complete. |
| 47 | + */ |
| 48 | +module gamepad_pmod_driver #( |
| 49 | + parameter BIT_WIDTH = 24 |
| 50 | +) ( |
| 51 | + input wire rst_n, |
| 52 | + input wire clk, |
| 53 | + input wire pmod_data, |
| 54 | + input wire pmod_clk, |
| 55 | + input wire pmod_latch, |
| 56 | + output reg [BIT_WIDTH-1:0] data_reg |
| 57 | +); |
| 58 | + |
| 59 | + reg pmod_clk_prev; |
| 60 | + reg pmod_latch_prev; |
| 61 | + reg [BIT_WIDTH-1:0] shift_reg; |
| 62 | + |
| 63 | + // Sync Pmod signals to the clk domain: |
| 64 | + reg [1:0] pmod_data_sync; |
| 65 | + reg [1:0] pmod_clk_sync; |
| 66 | + reg [1:0] pmod_latch_sync; |
| 67 | + |
| 68 | + always @(posedge clk) begin |
| 69 | + if (~rst_n) begin |
| 70 | + pmod_data_sync <= 2'b0; |
| 71 | + pmod_clk_sync <= 2'b0; |
| 72 | + pmod_latch_sync <= 2'b0; |
| 73 | + end else begin |
| 74 | + pmod_data_sync <= {pmod_data_sync[0], pmod_data}; |
| 75 | + pmod_clk_sync <= {pmod_clk_sync[0], pmod_clk}; |
| 76 | + pmod_latch_sync <= {pmod_latch_sync[0], pmod_latch}; |
| 77 | + end |
| 78 | + end |
| 79 | + |
| 80 | + always @(posedge clk) begin |
| 81 | + if (~rst_n) begin |
| 82 | + shift_reg <= 0; |
| 83 | + data_reg <= 0; |
| 84 | + pmod_clk_prev <= 1'b0; |
| 85 | + pmod_latch_prev <= 1'b0; |
| 86 | + end |
| 87 | + begin |
| 88 | + pmod_clk_prev <= pmod_clk_sync[1]; |
| 89 | + pmod_latch_prev <= pmod_latch_sync[1]; |
| 90 | + |
| 91 | + // Capture data on rising edge of pmod_latch: |
| 92 | + if (pmod_latch_sync[1] & ~pmod_latch_prev) begin |
| 93 | + data_reg <= shift_reg; |
| 94 | + end |
| 95 | + |
| 96 | + // Sample data on falling edge of pmod_clk: |
| 97 | + if (~pmod_clk_sync[1] & pmod_clk_prev) begin |
| 98 | + shift_reg <= {shift_reg[BIT_WIDTH-2:0], pmod_data_sync[1]}; |
| 99 | + end |
| 100 | + end |
| 101 | + end |
| 102 | + |
| 103 | +endmodule |
| 104 | + |
| 105 | + |
| 106 | +/** |
| 107 | + * gamepad_pmod_decoder -- Decodes raw data from the Gamepad Pmod. |
| 108 | + * |
| 109 | + * This module takes a 12-bit parallel data register (`data_reg`) |
| 110 | + * and decodes it into individual button states. It also determines |
| 111 | + * whether a controller is connected. |
| 112 | + * |
| 113 | + * Functionality: |
| 114 | + * - If `data_reg` contains all `1's` (`0xFFF`), it indicates that no controller is connected. |
| 115 | + * - Otherwise, it extracts individual button states from `data_reg`. |
| 116 | + * |
| 117 | + * Inputs: |
| 118 | + * - `data_reg [11:0]`: Captured button state data from the gamepad. |
| 119 | + * |
| 120 | + * Outputs: |
| 121 | + * - `b, y, select, start, up, down, left, right, a, x, l, r`: Individual button states (`1` = pressed, `0` = released). |
| 122 | + * - `is_present`: Indicates whether a controller is connected (`1` = connected, `0` = not connected). |
| 123 | + */ |
| 124 | +module gamepad_pmod_decoder ( |
| 125 | + input wire [11:0] data_reg, |
| 126 | + output wire b, |
| 127 | + output wire y, |
| 128 | + output wire select, |
| 129 | + output wire start, |
| 130 | + output wire up, |
| 131 | + output wire down, |
| 132 | + output wire left, |
| 133 | + output wire right, |
| 134 | + output wire a, |
| 135 | + output wire x, |
| 136 | + output wire l, |
| 137 | + output wire r, |
| 138 | + output wire is_present |
| 139 | +); |
| 140 | + |
| 141 | + // When the controller is not connected, the data register will be all 1's |
| 142 | + wire reg_empty = (data_reg == 12'hfff); |
| 143 | + assign is_present = reg_empty ? 0 : 1'b1; |
| 144 | + assign {b, y, select, start, up, down, left, right, a, x, l, r} = reg_empty ? 0 : data_reg; |
| 145 | + |
| 146 | +endmodule |
| 147 | + |
| 148 | + |
| 149 | +/** |
| 150 | + * gamepad_pmod_single -- Main interface for a single Gamepad Pmod controller. |
| 151 | + * |
| 152 | + * This module provides button states for a **single controller**, reducing |
| 153 | + * resource usage (fewer flip-flops) compared to a dual-controller version. |
| 154 | + * |
| 155 | + * Inputs: |
| 156 | + * - `pmod_data`, `pmod_clk`, and `pmod_latch` are the signals from the PMOD interface. |
| 157 | + * |
| 158 | + * Outputs: |
| 159 | + * - Each button's state is provided as a single-bit wire (e.g., `start`, `up`, etc.). |
| 160 | + * - `is_present` indicates whether the controller is connected (`1` = connected, `0` = not detected). |
| 161 | + */ |
| 162 | +module gamepad_pmod_single ( |
| 163 | + input wire rst_n, |
| 164 | + input wire clk, |
| 165 | + input wire pmod_data, |
| 166 | + input wire pmod_clk, |
| 167 | + input wire pmod_latch, |
| 168 | + |
| 169 | + output wire b, |
| 170 | + output wire y, |
| 171 | + output wire select, |
| 172 | + output wire start, |
| 173 | + output wire up, |
| 174 | + output wire down, |
| 175 | + output wire left, |
| 176 | + output wire right, |
| 177 | + output wire a, |
| 178 | + output wire x, |
| 179 | + output wire l, |
| 180 | + output wire r, |
| 181 | + output wire is_present |
| 182 | +); |
| 183 | + |
| 184 | + wire [11:0] gamepad_pmod_data; |
| 185 | + |
| 186 | + gamepad_pmod_driver #( |
| 187 | + .BIT_WIDTH(12) |
| 188 | + ) driver ( |
| 189 | + .rst_n(rst_n), |
| 190 | + .clk(clk), |
| 191 | + .pmod_data(pmod_data), |
| 192 | + .pmod_clk(pmod_clk), |
| 193 | + .pmod_latch(pmod_latch), |
| 194 | + .data_reg(gamepad_pmod_data) |
| 195 | + ); |
| 196 | + |
| 197 | + gamepad_pmod_decoder decoder ( |
| 198 | + .data_reg(gamepad_pmod_data), |
| 199 | + .b(b), |
| 200 | + .y(y), |
| 201 | + .select(select), |
| 202 | + .start(start), |
| 203 | + .up(up), |
| 204 | + .down(down), |
| 205 | + .left(left), |
| 206 | + .right(right), |
| 207 | + .a(a), |
| 208 | + .x(x), |
| 209 | + .l(l), |
| 210 | + .r(r), |
| 211 | + .is_present(is_present) |
| 212 | + ); |
| 213 | + |
| 214 | +endmodule |
| 215 | + |
| 216 | + |
| 217 | +/** |
| 218 | + * gamepad_pmod_dual -- Main interface for the Pmod gamepad. |
| 219 | + * This module provides button states for two controllers using |
| 220 | + * 2-bit vectors for each button (e.g., start[1:0], up[1:0], etc.). |
| 221 | + * |
| 222 | + * Each button state is represented as a 2-bit vector: |
| 223 | + * - Index 0 corresponds to the first controller (e.g., up[0], y[0], etc.). |
| 224 | + * - Index 1 corresponds to the second controller (e.g., up[1], y[1], etc.). |
| 225 | + * |
| 226 | + * The `is_present` signal indicates whether a controller is connected: |
| 227 | + * - `is_present[0] == 1` when the first controller is connected. |
| 228 | + * - `is_present[1] == 1` when the second controller is connected. |
| 229 | + * |
| 230 | + * Inputs: |
| 231 | + * - `pmod_data`, `pmod_clk`, and `pmod_latch` are the 3 wires coming from the Pmod interface. |
| 232 | + * |
| 233 | + * Outputs: |
| 234 | + * - Button state vectors for each controller. |
| 235 | + * - Presence detection via `is_present`. |
| 236 | + */ |
| 237 | +module gamepad_pmod_dual ( |
| 238 | + input wire rst_n, |
| 239 | + input wire clk, |
| 240 | + input wire pmod_data, |
| 241 | + input wire pmod_clk, |
| 242 | + input wire pmod_latch, |
| 243 | + |
| 244 | + output wire [1:0] b, |
| 245 | + output wire [1:0] y, |
| 246 | + output wire [1:0] select, |
| 247 | + output wire [1:0] start, |
| 248 | + output wire [1:0] up, |
| 249 | + output wire [1:0] down, |
| 250 | + output wire [1:0] left, |
| 251 | + output wire [1:0] right, |
| 252 | + output wire [1:0] a, |
| 253 | + output wire [1:0] x, |
| 254 | + output wire [1:0] l, |
| 255 | + output wire [1:0] r, |
| 256 | + output wire [1:0] is_present |
| 257 | +); |
| 258 | + |
| 259 | + wire [23:0] gamepad_pmod_data; |
| 260 | + |
| 261 | + gamepad_pmod_driver driver ( |
| 262 | + .rst_n(rst_n), |
| 263 | + .clk(clk), |
| 264 | + .pmod_data(pmod_data), |
| 265 | + .pmod_clk(pmod_clk), |
| 266 | + .pmod_latch(pmod_latch), |
| 267 | + .data_reg(gamepad_pmod_data) |
| 268 | + ); |
| 269 | + |
| 270 | + gamepad_pmod_decoder decoder1 ( |
| 271 | + .data_reg(gamepad_pmod_data[11:0]), |
| 272 | + .b(b[0]), |
| 273 | + .y(y[0]), |
| 274 | + .select(select[0]), |
| 275 | + .start(start[0]), |
| 276 | + .up(up[0]), |
| 277 | + .down(down[0]), |
| 278 | + .left(left[0]), |
| 279 | + .right(right[0]), |
| 280 | + .a(a[0]), |
| 281 | + .x(x[0]), |
| 282 | + .l(l[0]), |
| 283 | + .r(r[0]), |
| 284 | + .is_present(is_present[0]) |
| 285 | + ); |
| 286 | + |
| 287 | + gamepad_pmod_decoder decoder2 ( |
| 288 | + .data_reg(gamepad_pmod_data[23:12]), |
| 289 | + .b(b[1]), |
| 290 | + .y(y[1]), |
| 291 | + .select(select[1]), |
| 292 | + .start(start[1]), |
| 293 | + .up(up[1]), |
| 294 | + .down(down[1]), |
| 295 | + .left(left[1]), |
| 296 | + .right(right[1]), |
| 297 | + .a(a[1]), |
| 298 | + .x(x[1]), |
| 299 | + .l(l[1]), |
| 300 | + .r(r[1]), |
| 301 | + .is_present(is_present[1]) |
| 302 | + ); |
| 303 | + |
| 304 | +endmodule |
0 commit comments