Skip to content

Commit ff99ea3

Browse files
committed
refactor(examples/gamepad): extra gamepad_pmod driver code to a separate file
1 parent 2a5b408 commit ff99ea3

File tree

3 files changed

+306
-307
lines changed

3 files changed

+306
-307
lines changed

src/examples/common/gamepad_pmod.v

Lines changed: 304 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,304 @@
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

src/examples/gamepad/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import hvsync_generator_v from '../common/hvsync_generator.v?raw';
22
import project_v from './project.v?raw';
3+
import gamepad_pmod_v from '../common/gamepad_pmod.v?raw';
34

45
export const gamepad = {
56
name: 'Gamepad',
67
author: 'Uri Shaked',
78
topModule: 'tt_um_vga_example',
89
sources: {
9-
'project.v': project_v,
10+
'project.v': project_v + '\n\n' + gamepad_pmod_v,
1011
'hvsync_generator.v': hvsync_generator_v,
1112
},
1213
};

0 commit comments

Comments
 (0)