rustynes_core/controller.rs
1//! Standard NES controller (4016/4017) shift-register state.
2//!
3//! Per <https://www.nesdev.org/wiki/Standard_controller>:
4//!
5//! - Writing `$4016` with bit 0 set holds the controllers in *strobe* mode:
6//! the shift register is continuously reloaded with the current button
7//! state, and a read of `$4016` / `$4017` returns the state of the **A**
8//! button (the LSB of the latch).
9//! - Writing `$4016` with bit 0 clear takes the controllers out of strobe
10//! mode; the latched button state remains in the shift register and is
11//! shifted out one bit per `$4016`/`$4017` read in the order
12//! `A, B, Select, Start, Up, Down, Left, Right`.
13//! - After all eight buttons have been read, subsequent reads return `1`
14//! (open-bus + a stuck-high data line on the standard pad).
15//!
16//! Frontends update the *current* button state via
17//! [`Controller::set_buttons`]; the bus latches that state into the shift
18//! register on the rising edge of strobe (and continuously while strobe is
19//! held high).
20
21use bitflags::bitflags;
22
23bitflags! {
24 /// Standard NES controller buttons. Bits ordered to match the wire
25 /// shift order (LSB first): A, B, Select, Start, Up, Down, Left, Right.
26 #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
27 pub struct Buttons: u8 {
28 /// A button.
29 const A = 1 << 0;
30 /// B button.
31 const B = 1 << 1;
32 /// Select button.
33 const SELECT = 1 << 2;
34 /// Start button.
35 const START = 1 << 3;
36 /// D-pad up.
37 const UP = 1 << 4;
38 /// D-pad down.
39 const DOWN = 1 << 5;
40 /// D-pad left.
41 const LEFT = 1 << 6;
42 /// D-pad right.
43 const RIGHT = 1 << 7;
44 }
45}
46
47/// One standard NES controller plugged into `$4016` (player 1) or `$4017`
48/// (player 2).
49#[derive(Clone, Copy, Debug, Default)]
50pub struct Controller {
51 /// Current button state — set externally by the frontend.
52 pub(crate) buttons: Buttons,
53 /// Latched shift register: shifted right on each read.
54 pub(crate) shift: u8,
55 /// Strobe state (last bit-0 written to `$4016`).
56 pub(crate) strobe: bool,
57 /// A shift is owed to the CLK edge that ENDS the current read.
58 ///
59 /// `CLK` is low only while `$4016`/`$4017` is being read, and the shift
60 /// register advances on its LOW-TO-HIGH transition — i.e. when the read
61 /// ends, not when it begins (nesdev *Controller reading*). A run of
62 /// consecutive read cycles therefore holds `CLK` low throughout and
63 /// produces ONE rising edge, so it advances the register once and returns
64 /// the same bit each time. Shifting on the read instead made every read
65 /// its own clock, which is Famicom wiring, not NES.
66 ///
67 /// It is applied lazily, on the next read that is NOT a continuation of the
68 /// run, because a read is the only thing that can observe it. That makes it
69 /// state which outlives an instruction, so it is serialized.
70 pub(crate) pending_shift: bool,
71}
72
73impl Controller {
74 /// New controller with no buttons pressed.
75 #[must_use]
76 pub const fn new() -> Self {
77 Self {
78 buttons: Buttons::empty(),
79 shift: 0,
80 strobe: false,
81 pending_shift: false,
82 }
83 }
84
85 /// Set the current button state. Takes effect on the next strobe edge
86 /// (or immediately, while strobe is held high).
87 pub const fn set_buttons(&mut self, buttons: Buttons) {
88 self.buttons = buttons;
89 if self.strobe {
90 self.shift = buttons.bits();
91 }
92 }
93
94 /// Get the current button state.
95 #[must_use]
96 pub const fn buttons(&self) -> Buttons {
97 self.buttons
98 }
99
100 /// Handle a write to `$4016`. Only bit 0 matters for the standard
101 /// controller. While strobe is held high the shift register continuously
102 /// reloads from the live button state.
103 pub const fn write_strobe(&mut self, value: u8) {
104 let new_strobe = value & 1 != 0;
105 // Falling edge latches: while strobe was high, shift mirrors live
106 // buttons; on the falling edge, that snapshot becomes the value
107 // shifted out by subsequent reads.
108 if new_strobe {
109 self.shift = self.buttons.bits();
110 }
111 // Only an ACTUAL strobe drops an owed shift, and only because the
112 // reload leaves nothing for it to advance.
113 //
114 // This cleared unconditionally when `pending_shift` was introduced, so a
115 // write with bit 0 CLEAR -- not a strobe at all -- silently swallowed a
116 // shift the read run had already earned. That is wrong on the mechanism:
117 // `CLK` is low only while $4016/$4017 is being READ, so a write ENDS the
118 // run and produces exactly the rising edge the owed shift represents. A
119 // write cannot cancel it; if anything it is what causes it.
120 //
121 // Caught by the DUT, which models the edge directly and therefore could
122 // not reproduce this: at AccuracyCoin 25,196,442 a `$40` write lands
123 // between a consecutive read pair and the next read, and from there the
124 // two consoles' shift registers sat one bit apart. The co-simulation
125 // found a defect in the ORACLE, which is the direction that is supposed
126 // to be impossible and is the whole reason the DUT is worth building.
127 if new_strobe {
128 self.pending_shift = false;
129 }
130 self.strobe = new_strobe;
131 }
132
133 /// Handle a read of `$4016` / `$4017`. Returns the LSB of the shift
134 /// register and shifts. While strobe is held high, the LSB is always
135 /// the A button (bit 0 of `buttons`).
136 ///
137 /// Per the wiki, when the shift register has been emptied subsequent
138 /// reads return 1.
139 /// `continues_run` is true when the immediately preceding CPU cycle was
140 /// also a read of this same port — the case an absolute-indexed
141 /// read-modify-write (`SLO $4016,X`) and a DMC-DMA-interrupted read both
142 /// produce. `CLK` stays low across such a run, so the register does not
143 /// advance between the reads and both see the same bit.
144 pub const fn read(&mut self, continues_run: bool) -> u8 {
145 if self.strobe {
146 return self.buttons.bits() & 1;
147 }
148 if self.pending_shift && !continues_run {
149 // The previous run ended: its rising edge lands here.
150 // Shift in 1s from the left so post-empty reads yield 1.
151 self.shift = (self.shift >> 1) | 0x80;
152 }
153 self.pending_shift = true;
154 self.shift & 1
155 }
156
157 /// Side-effect-free sample of the next bit (debugger).
158 #[must_use]
159 pub const fn peek(&self) -> u8 {
160 if self.strobe {
161 self.buttons.bits() & 1
162 } else if self.pending_shift {
163 // A debugger peek must show what the NEXT read would return, and
164 // that read lands the owed edge first.
165 ((self.shift >> 1) | 0x80) & 1
166 } else {
167 self.shift & 1
168 }
169 }
170}
171
172#[cfg(test)]
173mod tests {
174 use super::*;
175
176 #[test]
177 fn empty_controller_reads_zero_then_ones() {
178 let mut c = Controller::new();
179 // Pulse strobe high then low to load.
180 c.write_strobe(1);
181 c.write_strobe(0);
182 for _ in 0..8 {
183 assert_eq!(c.read(false), 0);
184 }
185 // After 8 reads, ROMs see 1s.
186 for _ in 0..4 {
187 assert_eq!(c.read(false), 1);
188 }
189 }
190
191 #[test]
192 fn each_button_appears_in_canonical_shift_order() {
193 let mut c = Controller::new();
194 c.set_buttons(Buttons::A | Buttons::SELECT | Buttons::DOWN);
195 c.write_strobe(1);
196 c.write_strobe(0);
197 // A, B, Select, Start, Up, Down, Left, Right
198 let expected = [1u8, 0, 1, 0, 0, 1, 0, 0];
199 for &want in &expected {
200 assert_eq!(c.read(false), want);
201 }
202 }
203
204 #[test]
205 fn strobe_high_reads_a_button_repeatedly() {
206 let mut c = Controller::new();
207 c.set_buttons(Buttons::A);
208 c.write_strobe(1);
209 for _ in 0..16 {
210 assert_eq!(c.read(false), 1, "while strobing, $4016 returns A bit");
211 }
212 }
213
214 #[test]
215 fn buttons_set_during_strobe_reflect_immediately() {
216 let mut c = Controller::new();
217 c.write_strobe(1);
218 c.set_buttons(Buttons::A);
219 assert_eq!(c.read(false), 1);
220 c.set_buttons(Buttons::empty());
221 assert_eq!(c.read(false), 0);
222 }
223
224 #[test]
225 fn buttons_set_after_latch_take_effect_on_next_strobe() {
226 let mut c = Controller::new();
227 c.set_buttons(Buttons::A);
228 c.write_strobe(1);
229 c.write_strobe(0);
230 // Change buttons mid-readout — should NOT affect this scan.
231 c.set_buttons(Buttons::A | Buttons::B);
232 assert_eq!(c.read(false), 1, "A");
233 assert_eq!(c.read(false), 0, "B (latched as not pressed)");
234 // New strobe latches the new state.
235 c.write_strobe(1);
236 c.write_strobe(0);
237 assert_eq!(c.read(false), 1, "A");
238 assert_eq!(c.read(false), 1, "B (now latched as pressed)");
239 }
240}