xe1ap.rsannotatedxe1ap.rssource199 lines · 6.8 KB · raw
1use crate::input::Pins;
2use bincode::{Decode, Encode};
3use genesis_config::Xe1apJoypadState;
4use jgenesis_common::num::GetBit;
5use std::cmp;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)]
8pub enum Xe1apTransferState {
9    Idle,
10    Active,
11}
12
13#[derive(Debug, Clone, Encode, Decode)]
14pub struct Xe1apState {
15    pub joypad: Xe1apJoypadState,
16    latched: Xe1apJoypadState,
17    transfer_state: Xe1apTransferState,
18    transfer_counter: u8,
19    transfer_ack: bool,
20    transfer_cycles_remaining: u32,
21    last_th: bool,
22}
23
24impl Xe1apState {
25    // Timings based on: https://archive.org/details/micomBASIC_1990-10/ (pages 79-80)
26    // When connected to the Genesis, TR is ACK and TL is L/H
27    // These timings are based on the fastest transfer speed
28    //
29    // Each pair of nibbles is transferred in a 4-step pattern:
30    //   A: ACK=0, L/H=0 (game reads first nibble here)
31    //   B: ACK=1, L/H=1
32    //   C: ACK=0, L/H=1 (game reads second nibble here)
33    //   D: ACK=1, L/H=0
34    //
35    // L/H appears to change shortly after ACK 0->1 transitions; this is not emulated
36    //
37    // Transfers seem to begin in step D after a TH 1->0 transition
38    const TRANSFER_A_CYCLES: u32 = 92; // Roughly 12 μs
39    const TRANSFER_B_CYCLES: u32 = 30; // Roughly 4 μs
40    const TRANSFER_C_CYCLES: u32 = 92; // Roughly 12 μs
41    const TRANSFER_D_CYCLES: u32 = 168; // Roughly 22 μs
42
43    pub fn new(joypad: Xe1apJoypadState) -> Self {
44        Self {
45            joypad,
46            latched: joypad,
47            transfer_state: Xe1apTransferState::Idle,
48            transfer_counter: 0,
49            transfer_ack: true,
50            transfer_cycles_remaining: 0,
51            last_th: true,
52        }
53    }
54
55    pub fn update_pins(&mut self, pins: &mut Pins) {
56        let th = pins.th();
57        if self.last_th && !th {
58            // TH 1->0 transition begins a new transfer
59            self.latched = self.joypad;
60            self.transfer_state = Xe1apTransferState::Active;
61            self.transfer_counter = 0;
62            self.transfer_ack = true;
63            self.transfer_cycles_remaining = Self::TRANSFER_D_CYCLES;
64        }
65        self.last_th = th;
66
67        pins.input_tl(self.transfer_counter.bit(0));
68        pins.input_tr(self.transfer_ack);
69
70        match self.transfer_state {
71            Xe1apTransferState::Idle => {
72                pins.input_data_nibble(0b1111);
73            }
74            Xe1apTransferState::Active => {
75                // Only update D3-D0 pins when TR=0
76                if !self.transfer_ack {
77                    self.update_data_pins(pins);
78                }
79            }
80        }
81
82        log::debug!(
83            "XE-1AP pins update: data={:04b}, TL={}, TR={}, counter={}",
84            pins.pins & 0x0F,
85            u8::from(pins.tl()),
86            u8::from(pins.tr()),
87            self.transfer_counter
88        );
89    }
90
91    #[allow(clippy::match_same_arms)]
92    pub fn update_data_pins(&self, pins: &mut Pins) {
93        match self.transfer_counter {
94            0 => {
95                // E1, E2, Start, Select
96                pins.input_d3(!self.latched.e1);
97                pins.input_d2(!self.latched.e2);
98                pins.input_d1(!self.latched.start);
99                pins.input_d0(!self.latched.select);
100            }
101            1 => {
102                // A|A', B|B', C, D
103                pins.input_d3(!(self.latched.a || self.latched.ap));
104                pins.input_d2(!(self.latched.b || self.latched.bp));
105                pins.input_d1(!self.latched.c);
106                pins.input_d0(!self.latched.d);
107            }
108            2 => {
109                // Analog stick X, high nibble
110                pins.input_data_nibble(self.latched.analog_x >> 4);
111            }
112            3 => {
113                // Analog stick Y, high nibble
114                pins.input_data_nibble(self.latched.analog_y >> 4);
115            }
116            4 => {
117                // Always 0s
118                pins.input_data_nibble(0b0000);
119            }
120            5 => {
121                // Analog slider, high nibble
122                pins.input_data_nibble(self.latched.slider >> 4);
123            }
124            6 => {
125                // Analog stick X, low nibble
126                pins.input_data_nibble(self.latched.analog_x & 0x0F);
127            }
128            7 => {
129                // Analog stick Y, low nibble
130                pins.input_data_nibble(self.latched.analog_y & 0x0F);
131            }
132            8 => {
133                // Always 0s
134                pins.input_data_nibble(0b0000);
135            }
136            9 => {
137                // Analog slider, low nibble
138                pins.input_data_nibble(self.latched.slider & 0x0F);
139            }
140            10 => {
141                // Always 1s
142                pins.input_data_nibble(0b1111);
143            }
144            11 => {
145                // A, B, A', B'
146                pins.input_d3(!self.latched.a);
147                pins.input_d2(!self.latched.b);
148                pins.input_d1(!self.latched.ap);
149                pins.input_d0(!self.latched.bp);
150            }
151            _ => panic!(
152                "XE-1AP transfer counter should always be <= 11, was {}",
153                self.transfer_counter
154            ),
155        }
156    }
157
158    pub fn tick(&mut self, mut m68k_cycles: u32, pins: &mut Pins) {
159        if self.transfer_state == Xe1apTransferState::Idle {
160            return;
161        }
162
163        while m68k_cycles != 0 {
164            match self.transfer_state {
165                Xe1apTransferState::Idle => return,
166                Xe1apTransferState::Active => {
167                    let elapsed = cmp::min(m68k_cycles, self.transfer_cycles_remaining);
168                    m68k_cycles -= elapsed;
169                    self.transfer_cycles_remaining -= elapsed;
170                    if self.transfer_cycles_remaining != 0 {
171                        return;
172                    }
173
174                    self.transfer_cycles_remaining =
175                        match (self.transfer_ack, self.transfer_counter.bit(0)) {
176                            (true, false) => Self::TRANSFER_A_CYCLES,
177                            (false, false) => Self::TRANSFER_B_CYCLES,
178                            (true, true) => Self::TRANSFER_C_CYCLES,
179                            (false, true) => Self::TRANSFER_D_CYCLES,
180                        };
181
182                    self.transfer_ack = !self.transfer_ack;
183                    if self.transfer_ack {
184                        if self.transfer_counter < 11 {
185                            self.transfer_counter += 1;
186                        } else {
187                            // Transfer has ended
188                            self.transfer_state = Xe1apTransferState::Idle;
189                            self.transfer_counter = 0;
190                            self.transfer_ack = true;
191                        }
192                    }
193
194                    self.update_pins(pins);
195                }
196            }
197        }
198    }
199}