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}