1mod dma;
2
3use crate::sa1::Iram;
4use crate::sa1::mmc::Sa1Mmc;
5use crate::sa1::timer::Sa1Timer;
6use bincode::{Decode, Encode};
7use jgenesis_common::num::{GetBit, SignBit, U16Ext, U24Ext};
8use std::ops::Range;
9use std::{array, cmp};
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)]
12pub enum InterruptVectorSource {
13    #[default]
14    Rom,
15    IoPorts,
16}
17
18impl InterruptVectorSource {
19    fn from_bit(bit: bool) -> Self {
20        if bit { Self::IoPorts } else { Self::Rom }
21    }
22
23    fn to_bit(self) -> bool {
24        self == Self::IoPorts
25    }
26}
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)]
29pub enum DmaSourceDevice {
30    #[default]
31    Rom,
32    Iram,
33    Bwram,
34}
35
36impl DmaSourceDevice {
37    fn from_byte(byte: u8) -> Self {
38        match byte & 0x03 {
39            0x00 => Self::Rom,
40            0x01 => Self::Bwram,
41            0x02 => Self::Iram,
42            0x03 => {
43                log::warn!("SA-1 set unsupported DMA source 3; defaulting to ROM");
44                Self::Rom
45            }
46            _ => unreachable!("value & 0x03 is always <= 0x03"),
47        }
48    }
49}
50
51#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)]
52pub enum DmaDestinationDevice {
53    #[default]
54    Iram,
55    Bwram,
56}
57
58impl DmaDestinationDevice {
59    fn from_bit(bit: bool) -> Self {
60        if bit { Self::Bwram } else { Self::Iram }
61    }
62}
63
64#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)]
65pub enum DmaType {
66    #[default]
67    Normal,
68    CharacterConversion,
69}
70
71impl DmaType {
72    fn from_bit(bit: bool) -> Self {
73        if bit { Self::CharacterConversion } else { Self::Normal }
74    }
75}
76
77#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)]
78pub enum DmaPriority {
79    #[default]
80    Cpu,
81    Dma,
82}
83
84impl DmaPriority {
85    fn from_bit(bit: bool) -> Self {
86        if bit { Self::Dma } else { Self::Cpu }
87    }
88}
89
90#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)]
91pub enum CharacterConversionType {
92    One,
93    #[default]
94    Two,
95}
96
97impl CharacterConversionType {
98    fn from_bit(bit: bool) -> Self {
99        if bit { Self::One } else { Self::Two }
100    }
101}
102
103#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)]
104pub enum CharacterConversionColorBits {
105    Two,
106    Four,
107    #[default]
108    Eight,
109}
110
111impl CharacterConversionColorBits {
112    fn from_byte(byte: u8) -> Self {
113        match byte & 0x03 {
114            0x00 => Self::Eight,
115            0x01 => Self::Four,
116            0x02 | 0x03 => Self::Two,
117            _ => unreachable!("value & 0x03 is always <= 0x03"),
118        }
119    }
120
121    fn bit_mask(self) -> u8 {
122        match self {
123            Self::Two => 0x03,
124            Self::Four => 0x0F,
125            Self::Eight => 0xFF,
126        }
127    }
128
129    fn tile_size(self) -> u32 {
130        match self {
131            Self::Two => 16,
132            Self::Four => 32,
133            Self::Eight => 64,
134        }
135    }
136
137    fn bitplanes(self) -> u32 {
138        match self {
139            Self::Two => 2,
140            Self::Four => 4,
141            Self::Eight => 8,
142        }
143    }
144}
145
146#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)]
147pub enum DmaState {
148    #[default]
149    Idle,
150    NormalCopying,
151    NormalWaitCycle,
152    CharacterConversion2 {
153        buffer_idx: u8,
154        rows_copied: u8,
155    },
156    CharacterConversion1Initial {
157        cycles_remaining: u8,
158    },
159    CharacterConversion1Active {
160        buffer_idx: u8,
161        dma_bytes_remaining: u8,
162        next_tile_number: u16,
163    },
164}
165
166impl DmaState {
167    fn is_character_conversion(self) -> bool {
168        matches!(
169            self,
170            Self::CharacterConversion2 { .. }
171                | Self::CharacterConversion1Initial { .. }
172                | Self::CharacterConversion1Active { .. }
173        )
174    }
175}
176
177#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)]
178pub enum ArithmeticOp {
179    #[default]
180    Multiply,
181    Divide,
182    MultiplyAccumulate,
183}
184
185impl ArithmeticOp {
186    fn from_byte(byte: u8) -> Self {
187        match byte & 0x03 {
188            0x00 => Self::Multiply,
189            0x01 => Self::Divide,
190            0x02 | 0x03 => Self::MultiplyAccumulate,
191            _ => unreachable!("value & 0x03 is always <= 0x03"),
192        }
193    }
194}
195
196#[derive(Debug, Clone, Encode, Decode)]
197pub struct Sa1Registers {
198    // CCNT / SA-1 CPU Control
199    pub sa1_irq_from_snes: bool,
200    pub sa1_nmi: bool,
201    pub sa1_reset: bool,
202    pub sa1_wait: bool,
203    pub message_to_sa1: u8,
204    // SCNT / SNES CPU Control
205    pub snes_irq_from_sa1: bool,
206    pub snes_irq_vector_source: InterruptVectorSource,
207    pub snes_nmi_vector_source: InterruptVectorSource,
208    pub message_to_snes: u8,
209    // SIE / SNES CPU Interrupt Enable
210    pub snes_irq_from_sa1_enabled: bool,
211    pub snes_irq_from_dma_enabled: bool,
212    // CIE / SA-1 CPU Interrupt Enable
213    pub sa1_irq_from_snes_enabled: bool,
214    pub timer_irq_enabled: bool,
215    pub dma_irq_enabled: bool,
216    pub sa1_nmi_enabled: bool,
217    // CRV / SA-1 CPU RESET Vector
218    pub sa1_reset_vector: u16,
219    // CNV / SA-1 CPU NMI Vector
220    pub sa1_nmi_vector: u16,
221    // CIV / SA-1 CPU IRQ Vector
222    pub sa1_irq_vector: u16,
223    // SNV / SNES CPU NMI Vector
224    pub snes_nmi_vector: u16,
225    // SIV / SNES CPU IRQ Vector
226    pub snes_irq_vector: u16,
227    // SBWE/CBWE / BW-RAM Writes Enabled
228    pub bwram_writes_enabled: bool,
229    // BWPA / BW-RAM Write Protected Area
230    pub bwram_write_protection_size: u32,
231    // SIWP / SNES I-RAM Write Protection
232    pub snes_iram_writes_enabled: [bool; 8],
233    // CIWP / SA-1 I-RAM Write Protection
234    pub sa1_iram_writes_enabled: [bool; 8],
235    // DCNT / DMA Control
236    pub dma_source: DmaSourceDevice,
237    pub dma_destination: DmaDestinationDevice,
238    pub dma_type: DmaType,
239    pub character_conversion_type: CharacterConversionType,
240    pub dma_priority: DmaPriority,
241    pub dma_enabled: bool,
242    // CDMA / Character Conversion DMA Parameters
243    pub ccdma_color_depth: CharacterConversionColorBits,
244    pub virtual_vram_width_tiles: u8,
245    // SDA / DMA Source Device Start Address
246    pub dma_source_address: u32,
247    // DDA / DMA Destination Device Start Address
248    pub dma_destination_address: u32,
249    // DTC / DMA Terminal Counter
250    pub dma_terminal_counter: u16,
251    // BRF / Bitmap Register File
252    pub bitmap_pixels: [u8; 16],
253    // MCNT / Arithmetic Control
254    pub arithmetic_op: ArithmeticOp,
255    // MA / Arithmetic Parameter A
256    pub arithmetic_param_a: u16,
257    // MB / Arithmetic Parameter B
258    pub arithmetic_param_b: u16,
259    // MR / Arithmetic Result
260    pub arithmetic_result: u64,
261    // OF / Arithmetic Overflow Flag
262    pub arithmetic_overflow: bool,
263    // VDA / Variable-Length Bit Data ROM Start Address
264    pub varlen_bit_start_address: u32,
265    // VDP / Variable-Length Bit Data Read Port
266    pub varlen_bit_data: u32,
267    pub varlen_bits_remaining: u8,
268    // TODO varlen auto-increment? not used by any games
269    // Miscellaneous internal state
270    pub dma_state: DmaState,
271    pub ccdma_transfer_in_progress: bool,
272    pub character_conversion_irq: bool,
273    pub sa1_dma_irq: bool,
274}
275
276impl Sa1Registers {
277    pub fn new() -> Self {
278        Self {
279            sa1_irq_from_snes: false,
280            sa1_nmi: false,
281            sa1_reset: true,
282            sa1_wait: false,
283            message_to_sa1: 0,
284            snes_irq_from_sa1: false,
285            snes_irq_vector_source: InterruptVectorSource::default(),
286            snes_nmi_vector_source: InterruptVectorSource::default(),
287            message_to_snes: 0,
288            snes_irq_from_sa1_enabled: false,
289            snes_irq_from_dma_enabled: false,
290            sa1_irq_from_snes_enabled: false,
291            timer_irq_enabled: false,
292            dma_irq_enabled: false,
293            sa1_nmi_enabled: false,
294            sa1_reset_vector: 0,
295            sa1_nmi_vector: 0,
296            sa1_irq_vector: 0,
297            snes_nmi_vector: 0,
298            snes_irq_vector: 0,
299            bwram_writes_enabled: false,
300            bwram_write_protection_size: 1 << 23,
301            snes_iram_writes_enabled: [false; 8],
302            sa1_iram_writes_enabled: [false; 8],
303            dma_source: DmaSourceDevice::default(),
304            dma_destination: DmaDestinationDevice::default(),
305            dma_type: DmaType::default(),
306            character_conversion_type: CharacterConversionType::default(),
307            dma_priority: DmaPriority::default(),
308            dma_enabled: false,
309            ccdma_color_depth: CharacterConversionColorBits::default(),
310            virtual_vram_width_tiles: 1,
311            dma_source_address: 0,
312            dma_destination_address: 0,
313            dma_terminal_counter: 0,
314            bitmap_pixels: [0; 16],
315            arithmetic_op: ArithmeticOp::default(),
316            arithmetic_param_a: 0,
317            arithmetic_param_b: 0,
318            arithmetic_result: 0,
319            arithmetic_overflow: false,
320            varlen_bit_start_address: 0,
321            varlen_bit_data: 0,
322            varlen_bits_remaining: 0,
323            dma_state: DmaState::default(),
324            ccdma_transfer_in_progress: false,
325            character_conversion_irq: false,
326            sa1_dma_irq: false,
327        }
328    }
329
330    pub fn snes_read(&self, address: u32) -> Option<u8> {
331        // Only $2300 (SFR) is readable by SNES CPU
332        // $230E is supposed to be a version code register, but hardware tests discovered that it
333        // is actually open bus
334        (address & 0xFFFF == 0x2300).then(|| self.read_sfr())
335    }
336
337    pub fn sa1_read(&self, address: u32, timer: &mut Sa1Timer) -> u8 {
338        log::trace!("SA-1 register read: {:04X}", address & 0xFFFF);
339
340        match address & 0xFFFF {
341            0x2301 => self.read_cfr(timer),
342            0x2302 => timer.read_hcr_low(),
343            0x2303 => timer.read_hcr_high(),
344            0x2304 => timer.read_vcr_low(),
345            0x2305 => timer.read_vcr_high(),
346            0x2306..=0x230A => self.read_mr(address),
347            0x230B => self.read_of(),
348            0x230C => self.read_vdp_low(),
349            0x230D => self.read_vdp_high(),
350            _ => 0,
351        }
352    }
353
354    pub fn snes_write(&mut self, address: u32, value: u8, mmc: &mut Sa1Mmc) {
355        log::trace!("SNES register write: {address:06X} {value:02X}");
356
357        match address & 0xFFFF {
358            0x2200 => self.write_ccnt(value),
359            0x2201 => self.write_sie(value),
360            0x2202 => self.write_sic(value),
361            0x2203 => self.write_crv_low(value),
362            0x2204 => self.write_crv_high(value),
363            0x2205 => self.write_cnv_low(value),
364            0x2206 => self.write_cnv_high(value),
365            0x2207 => self.write_civ_low(value),
366            0x2208 => self.write_civ_high(value),
367            0x2220 => mmc.write_cxb(value),
368            0x2221 => mmc.write_dxb(value),
369            0x2222 => mmc.write_exb(value),
370            0x2223 => mmc.write_fxb(value),
371            0x2224 => mmc.write_bmaps(value),
372            0x2226 => self.write_sbwe(value),
373            0x2228 => self.write_bwpa(value),
374            0x2229 => self.write_siwp(value),
375            0x2231 => self.write_cdma(value),
376            0x2232 => self.write_sda_low(value),
377            0x2233 => self.write_sda_mid(value),
378            0x2234 => self.write_sda_high(value),
379            0x2235 => self.write_dda_low(value),
380            0x2236 => self.write_dda_mid(value),
381            0x2237 => self.write_dda_high(value),
382            _ => {}
383        }
384    }
385
386    pub fn sa1_write(
387        &mut self,
388        address: u32,
389        value: u8,
390        timer: &mut Sa1Timer,
391        mmc: &mut Sa1Mmc,
392        rom: &[u8],
393        iram: &mut Iram,
394    ) {
395        log::trace!("SA-1 register write: {address:06X} {value:02X}");
396
397        match address & 0xFFFF {
398            0x2209 => self.write_scnt(value),
399            0x220A => self.write_cie(value),
400            0x220B => self.write_cic(value, timer),
401            0x220C => self.write_snv_low(value),
402            0x220D => self.write_snv_high(value),
403            0x220E => self.write_siv_low(value),
404            0x220F => self.write_siv_high(value),
405            0x2210 => timer.write_tmc(value),
406            0x2211 => timer.reset(),
407            0x2212 => timer.write_hcnt_low(value),
408            0x2213 => timer.write_hcnt_high(value),
409            0x2214 => timer.write_vcnt_low(value),
410            0x2215 => timer.write_vcnt_high(value),
411            0x2225 => mmc.write_bmap(value),
412            0x2227 => self.write_cbwe(value),
413            0x222A => self.write_ciwp(value),
414            0x2230 => self.write_dcnt(value),
415            0x2231 => self.write_cdma(value),
416            0x2232 => self.write_sda_low(value),
417            0x2233 => self.write_sda_mid(value),
418            0x2234 => self.write_sda_high(value),
419            0x2235 => self.write_dda_low(value),
420            0x2236 => self.write_dda_mid(value),
421            0x2237 => self.write_dda_high(value),
422            0x2238 => self.write_dtc_low(value),
423            0x2239 => self.write_dtc_high(value),
424            0x223F => mmc.write_bbf(value),
425            0x2240..=0x224F => self.write_brf(address, value, iram),
426            0x2250 => self.write_mcnt(value),
427            0x2251 => self.write_ma_low(value),
428            0x2252 => self.write_ma_high(value),
429            0x2253 => self.write_mb_low(value),
430            0x2254 => self.write_mb_high(value),
431            0x2258 => self.write_vbd(value, mmc, rom),
432            0x2259 => self.write_vda_low(value),
433            0x225A => self.write_vda_mid(value),
434            0x225B => self.write_vda_high(value, mmc, rom),
435            _ => {}
436        }
437    }
438
439    fn read_sfr(&self) -> u8 {
440        (u8::from(self.snes_irq_from_sa1) << 7)
441            | (u8::from(self.snes_irq_vector_source.to_bit()) << 6)
442            | (u8::from(self.character_conversion_irq) << 5)
443            | (u8::from(self.snes_nmi_vector_source.to_bit()) << 4)
444            | self.message_to_snes
445    }
446
447    fn read_cfr(&self, timer: &Sa1Timer) -> u8 {
448        (u8::from(self.sa1_irq_from_snes) << 7)
449            | (u8::from(timer.irq_pending) << 6)
450            | (u8::from(self.sa1_dma_irq) << 5)
451            | (u8::from(self.sa1_nmi) << 4)
452            | self.message_to_sa1
453    }
454
455    fn read_mr(&self, address: u32) -> u8 {
456        // $2306 is bits 0-7, $2307 is bits 8-15, $2308 is bits 16-23, etc.
457        let shift = 8 * ((address & 0xF) - 0x6);
458        (self.arithmetic_result >> shift) as u8
459    }
460
461    fn read_of(&self) -> u8 {
462        u8::from(self.arithmetic_overflow) << 7
463    }
464
465    fn read_vdp_low(&self) -> u8 {
466        (self.varlen_bit_data as u16).lsb()
467    }
468
469    fn read_vdp_high(&self) -> u8 {
470        (self.varlen_bit_data as u16).msb()
471    }
472
473    fn write_ccnt(&mut self, value: u8) {
474        if value.bit(7) {
475            self.sa1_irq_from_snes = true;
476            log::trace!("  Generating SA-1 IRQ from SNES");
477        }
478
479        self.sa1_wait = value.bit(6);
480        self.sa1_reset = value.bit(5);
481
482        if value.bit(4) {
483            self.sa1_nmi = true;
484            log::trace!("  Generating SA-1 NMI");
485        }
486
487        self.message_to_sa1 = value & 0x0F;
488
489        log::trace!("  SA-1 wait: {}", self.sa1_wait);
490        log::trace!("  SA-1 reset: {}", self.sa1_reset);
491        log::trace!("  Message to SA-1: {:X}", self.message_to_sa1);
492    }
493
494    fn write_sie(&mut self, value: u8) {
495        self.snes_irq_from_sa1_enabled = value.bit(7);
496        self.snes_irq_from_dma_enabled = value.bit(5);
497
498        log::trace!("  SNES IRQs from SA-1 enabled: {}", self.snes_irq_from_sa1_enabled);
499        log::trace!(
500            "  SNES character conversion DMA IRQs enabled: {}",
501            self.snes_irq_from_dma_enabled
502        );
503    }
504
505    fn write_sic(&mut self, value: u8) {
506        if value.bit(7) {
507            self.snes_irq_from_sa1 = false;
508            log::trace!("  SNES IRQ from SA-1 cleared");
509        }
510        if value.bit(5) {
511            self.character_conversion_irq = false;
512            log::trace!("  SNES character conversion DMA IRQ cleared");
513        }
514    }
515
516    fn write_cie(&mut self, value: u8) {
517        self.sa1_irq_from_snes_enabled = value.bit(7);
518        self.timer_irq_enabled = value.bit(6);
519        self.dma_irq_enabled = value.bit(5);
520        self.sa1_nmi_enabled = value.bit(4);
521
522        log::trace!("  SA-1 IRQs from SNES enabled: {}", self.sa1_irq_from_snes_enabled);
523        log::trace!("  SA-1 timer IRQs enabled: {}", self.timer_irq_enabled);
524        log::trace!("  SA-1 DMA IRQs enabled: {}", self.dma_irq_enabled);
525        log::trace!("  SA-1 NMIs enabled: {}", self.sa1_nmi_enabled);
526    }
527
528    fn write_cic(&mut self, value: u8, timer: &mut Sa1Timer) {
529        if value.bit(7) {
530            self.sa1_irq_from_snes = false;
531            log::trace!("  Cleared SA-1 IRQ from SNES");
532        }
533
534        if value.bit(6) {
535            timer.irq_pending = false;
536            log::trace!("  Cleared SA-1 timer IRQ");
537        }
538
539        if value.bit(5) {
540            self.sa1_dma_irq = false;
541            log::trace!("  Cleared SA-1 DMA IRQ");
542        }
543
544        if value.bit(4) {
545            self.sa1_nmi = false;
546            log::trace!("  Cleared SA-1 NMI");
547        }
548    }
549
550    fn write_crv_low(&mut self, value: u8) {
551        self.sa1_reset_vector.set_lsb(value);
552
553        log::trace!("  SA-1 RESET vector: {:04X}", self.sa1_reset_vector);
554    }
555
556    fn write_crv_high(&mut self, value: u8) {
557        self.sa1_reset_vector.set_msb(value);
558
559        log::trace!("  SA-1 RESET vector: {:04X}", self.sa1_reset_vector);
560    }
561
562    fn write_cnv_low(&mut self, value: u8) {
563        self.sa1_nmi_vector.set_lsb(value);
564
565        log::trace!("  SA-1 NMI vector: {:04X}", self.sa1_nmi_vector);
566    }
567
568    fn write_cnv_high(&mut self, value: u8) {
569        self.sa1_nmi_vector.set_msb(value);
570
571        log::trace!("  SA-1 NMI vector: {:04X}", self.sa1_nmi_vector);
572    }
573
574    fn write_civ_low(&mut self, value: u8) {
575        self.sa1_irq_vector.set_lsb(value);
576
577        log::trace!("  SA-1 IRQ vector: {:04X}", self.sa1_irq_vector);
578    }
579
580    fn write_civ_high(&mut self, value: u8) {
581        self.sa1_irq_vector.set_msb(value);
582
583        log::trace!("  SA-1 IRQ vector: {:04X}", self.sa1_irq_vector);
584    }
585
586    fn write_snv_low(&mut self, value: u8) {
587        self.snes_nmi_vector.set_lsb(value);
588
589        log::trace!("  SNES NMI vector: {:04X}", self.snes_nmi_vector);
590    }
591
592    fn write_snv_high(&mut self, value: u8) {
593        self.snes_nmi_vector.set_msb(value);
594
595        log::trace!("  SNES NMI vector: {:04X}", self.snes_nmi_vector);
596    }
597
598    fn write_siv_low(&mut self, value: u8) {
599        self.snes_irq_vector.set_lsb(value);
600
601        log::trace!("  SNES IRQ vector: {:04X}", self.snes_irq_vector);
602    }
603
604    fn write_siv_high(&mut self, value: u8) {
605        self.snes_irq_vector.set_msb(value);
606
607        log::trace!("  SNES IRQ vector: {:04X}", self.snes_irq_vector);
608    }
609
610    fn write_scnt(&mut self, value: u8) {
611        if value.bit(7) {
612            self.snes_irq_from_sa1 = true;
613            log::trace!("  Generated SNES IRQ from SA-1");
614        }
615
616        self.snes_irq_vector_source = InterruptVectorSource::from_bit(value.bit(6));
617        self.snes_nmi_vector_source = InterruptVectorSource::from_bit(value.bit(4));
618        self.message_to_snes = value & 0x0F;
619
620        log::trace!("  SNES IRQ vector source: {:?}", self.snes_irq_vector_source);
621        log::trace!("  SNES NMI vector source: {:?}", self.snes_nmi_vector_source);
622        log::trace!("  Message to SNES: {:X}", self.message_to_snes);
623    }
624
625    fn write_sbwe(&mut self, value: u8) {
626        self.bwram_writes_enabled = value.bit(7);
627
628        log::trace!("  BW-RAM writes enabled: {}", self.bwram_writes_enabled);
629    }
630
631    fn write_cbwe(&mut self, value: u8) {
632        self.bwram_writes_enabled = value.bit(7);
633
634        log::trace!("  BW-RAM writes enabled: {}", self.bwram_writes_enabled);
635    }
636
637    fn write_bwpa(&mut self, value: u8) {
638        // Write protected area size is 256 * 2^N bytes
639        self.bwram_write_protection_size = 1 << (8 + (value & 0x0F));
640
641        log::trace!("  BW-RAM write protection size: {:X}", self.bwram_write_protection_size);
642    }
643
644    fn write_siwp(&mut self, value: u8) {
645        self.snes_iram_writes_enabled = array::from_fn(|i| value.bit(i as u8));
646
647        log::trace!("  SNES I-RAM writes enabled: {value:02X}");
648    }
649
650    fn write_ciwp(&mut self, value: u8) {
651        self.sa1_iram_writes_enabled = array::from_fn(|i| value.bit(i as u8));
652
653        log::trace!("  SA-1 I-RAM writes enabled: {value:02X}");
654    }
655
656    fn write_dcnt(&mut self, value: u8) {
657        self.dma_source = DmaSourceDevice::from_byte(value);
658        self.dma_destination = DmaDestinationDevice::from_bit(value.bit(2));
659        self.character_conversion_type = CharacterConversionType::from_bit(value.bit(4));
660        self.dma_type = DmaType::from_bit(value.bit(5));
661        self.dma_priority = DmaPriority::from_bit(value.bit(6));
662        self.dma_enabled = value.bit(7);
663
664        log::trace!("  DMA source: {:?}", self.dma_source);
665        log::trace!("  DMA destination: {:?}", self.dma_destination);
666        log::trace!("  DMA character conversion type: {:?}", self.character_conversion_type);
667        log::trace!("  DMA type: {:?}", self.dma_type);
668        log::trace!("  DMA enabled: {}", self.dma_enabled);
669    }
670
671    fn write_cdma(&mut self, value: u8) {
672        self.ccdma_color_depth = CharacterConversionColorBits::from_byte(value);
673        self.virtual_vram_width_tiles = cmp::min(32, 1 << ((value >> 2) & 0x07));
674
675        if value.bit(7) && self.dma_state.is_character_conversion() {
676            log::trace!("  Terminating character conversion DMA");
677            self.dma_state = DmaState::Idle;
678        }
679
680        log::trace!("  Character conversion DMA color depth bits: {:?}", self.ccdma_color_depth);
681        log::trace!("  Virtual VRAM width in tiles: {}", self.virtual_vram_width_tiles);
682    }
683
684    fn write_sda_low(&mut self, value: u8) {
685        self.dma_source_address.set_low_byte(value);
686
687        log::trace!("  DMA source address: {:06X}", self.dma_source_address);
688    }
689
690    fn write_sda_mid(&mut self, value: u8) {
691        self.dma_source_address.set_mid_byte(value);
692
693        log::trace!("  DMA source address: {:06X}", self.dma_source_address);
694    }
695
696    fn write_sda_high(&mut self, value: u8) {
697        self.dma_source_address.set_high_byte(value);
698
699        log::trace!("  DMA source address: {:06X}", self.dma_source_address);
700    }
701
702    fn write_dda_low(&mut self, value: u8) {
703        self.dma_destination_address.set_low_byte(value);
704
705        log::trace!("  DMA destination address: {:06X}", self.dma_destination_address);
706    }
707
708    fn write_dda_mid(&mut self, value: u8) {
709        self.dma_destination_address.set_mid_byte(value);
710
711        log::trace!("  DMA destination address: {:06X}", self.dma_destination_address);
712
713        match (self.dma_enabled, self.dma_type, self.dma_destination) {
714            (true, DmaType::Normal, DmaDestinationDevice::Iram) => {
715                log::trace!("  Starting SA-1 DMA to I-RAM");
716                self.dma_state = DmaState::NormalCopying;
717            }
718            (true, DmaType::CharacterConversion, _) => {
719                log::trace!("  Starting character conversion DMA");
720                self.dma_state = match self.character_conversion_type {
721                    CharacterConversionType::Two => {
722                        DmaState::CharacterConversion2 { buffer_idx: 0, rows_copied: 0 }
723                    }
724                    CharacterConversionType::One => DmaState::CharacterConversion1Initial {
725                        cycles_remaining: self.ccdma_color_depth.tile_size() as u8,
726                    },
727                };
728            }
729            _ => {}
730        }
731    }
732
733    fn write_dda_high(&mut self, value: u8) {
734        self.dma_destination_address.set_high_byte(value);
735
736        log::trace!("  DMA destination address: {:06X}", self.dma_destination_address);
737
738        if self.dma_enabled
739            && self.dma_type == DmaType::Normal
740            && self.dma_destination == DmaDestinationDevice::Bwram
741        {
742            log::trace!("  Starting SA-1 DMA to BW-RAM");
743            self.dma_state = DmaState::NormalCopying;
744        }
745    }
746
747    fn write_dtc_low(&mut self, value: u8) {
748        self.dma_terminal_counter.set_lsb(value);
749
750        log::trace!("  DMA terminal counter: {:04X}", self.dma_terminal_counter);
751    }
752
753    fn write_dtc_high(&mut self, value: u8) {
754        self.dma_terminal_counter.set_msb(value);
755
756        log::trace!("  DMA terminal counter: {:04X}", self.dma_terminal_counter);
757    }
758
759    fn write_brf(&mut self, address: u32, value: u8, iram: &mut Iram) {
760        // BRF registers are $2240-$224F; lowest 4 bits of address are the register index
761        let idx = (address & 0xF) as usize;
762        self.bitmap_pixels[idx] = value & self.ccdma_color_depth.bit_mask();
763
764        log::trace!("  Bitmap register file #{idx}: {value:02X}");
765
766        if idx & 0x7 == 0x7 {
767            // Perform character conversion any time register 7 or 15 is written
768            if let DmaState::CharacterConversion2 { buffer_idx, rows_copied } = self.dma_state {
769                self.character_conversion_2(idx & 0x8, buffer_idx, rows_copied, iram);
770            }
771        }
772    }
773
774    fn write_mcnt(&mut self, value: u8) {
775        self.arithmetic_op = ArithmeticOp::from_byte(value);
776
777        // Setting multiply-accumulate clears result
778        if self.arithmetic_op == ArithmeticOp::MultiplyAccumulate {
779            self.arithmetic_result = 0;
780            self.arithmetic_overflow = false;
781        }
782
783        log::trace!("  Arithmetic mode: {:?}", self.arithmetic_op);
784    }
785
786    fn write_ma_low(&mut self, value: u8) {
787        self.arithmetic_param_a.set_lsb(value);
788
789        log::trace!("  Arithmetic parameter A: {:04X}", self.arithmetic_param_a);
790    }
791
792    fn write_ma_high(&mut self, value: u8) {
793        self.arithmetic_param_a.set_msb(value);
794
795        log::trace!("  Arithmetic parameter A: {:04X}", self.arithmetic_param_a);
796    }
797
798    fn write_mb_low(&mut self, value: u8) {
799        self.arithmetic_param_b.set_lsb(value);
800
801        log::trace!("  Arithmetic parameter B: {:04X}", self.arithmetic_param_b);
802    }
803
804    fn write_mb_high(&mut self, value: u8) {
805        self.arithmetic_param_b.set_msb(value);
806
807        // Writing MB high byte begins arithmetic operation
808        self.perform_arithmetic_op();
809
810        log::trace!("  Arithmetic parameter B: {:04X}", self.arithmetic_param_b);
811    }
812
813    fn write_vbd(&mut self, value: u8, mmc: &Sa1Mmc, rom: &[u8]) {
814        if self.varlen_bits_remaining == 0 {
815            // Variable-length bit data reading not initialized; do nothing
816            return;
817        }
818
819        let shift = if value & 0x0F == 0 { 16 } else { value & 0x0F };
820        self.varlen_bit_data >>= shift;
821        self.varlen_bits_remaining -= shift;
822
823        if self.varlen_bits_remaining < 16 {
824            // Read next word
825            let word = mmc.map_rom_address(self.varlen_bit_start_address).map_or(0, |rom_addr| {
826                let lsb = rom.get(rom_addr as usize).copied().unwrap_or(0);
827                let msb = rom.get((rom_addr + 1) as usize).copied().unwrap_or(0);
828                u16::from_le_bytes([lsb, msb])
829            });
830            let word: u32 = word.into();
831
832            self.varlen_bit_data |= word << self.varlen_bits_remaining;
833            self.varlen_bit_start_address = (self.varlen_bit_start_address + 2) & 0xFFFFFF;
834            self.varlen_bits_remaining += 16;
835        }
836
837        log::trace!("  Variable-length bit data shift: {shift}");
838    }
839
840    fn write_vda_low(&mut self, value: u8) {
841        self.varlen_bit_start_address.set_low_byte(value);
842
843        log::trace!(
844            "  Variable-length bit data ROM start address: {:06X}",
845            self.varlen_bit_start_address
846        );
847    }
848
849    fn write_vda_mid(&mut self, value: u8) {
850        self.varlen_bit_start_address.set_mid_byte(value);
851
852        log::trace!(
853            "  Variable-length bit data ROM start address: {:06X}",
854            self.varlen_bit_start_address
855        );
856    }
857
858    fn write_vda_high(&mut self, value: u8, mmc: &Sa1Mmc, rom: &[u8]) {
859        self.varlen_bit_start_address.set_high_byte(value);
860
861        // Writing MSB starts the variable-length bit data read
862        if let Some(rom_addr) = mmc.map_rom_address(self.varlen_bit_start_address) {
863            let lsb = rom[rom_addr as usize];
864            let msb = rom.get((rom_addr + 1) as usize).copied().unwrap_or(0);
865            self.varlen_bit_data = u16::from_le_bytes([lsb, msb]).into();
866            self.varlen_bit_start_address = (self.varlen_bit_start_address + 2) & 0xFFFFFF;
867            self.varlen_bits_remaining = 16;
868        }
869
870        log::trace!(
871            "  Variable-length bit data ROM start address: {:06X}",
872            self.varlen_bit_start_address
873        );
874    }
875
876    pub fn can_write_bwram(&self, bwram_addr: u32) -> bool {
877        self.bwram_writes_enabled || bwram_addr >= self.bwram_write_protection_size
878    }
879
880    pub fn tick_dma(&mut self, mmc: &Sa1Mmc, rom: &[u8], iram: &mut Iram, bwram: &mut [u8]) {
881        // Progress normal DMA or character conversion DMA type 1
882        match self.dma_state {
883            DmaState::NormalCopying => {
884                self.progress_normal_dma(mmc, rom, iram, bwram);
885            }
886            DmaState::NormalWaitCycle => {
887                self.dma_state = DmaState::NormalCopying;
888            }
889            DmaState::CharacterConversion1Initial { cycles_remaining } => {
890                if cycles_remaining == 1 {
891                    self.start_ccdma_type_1(iram, bwram);
892                } else {
893                    self.dma_state = DmaState::CharacterConversion1Initial {
894                        cycles_remaining: cycles_remaining - 1,
895                    };
896                }
897            }
898            DmaState::Idle
899            | DmaState::CharacterConversion2 { .. }
900            | DmaState::CharacterConversion1Active { .. } => {}
901        }
902    }
903
904    pub fn notify_snes_dma_start(&mut self, source_address: u32) {
905        // TODO check exact source address?
906        if matches!(self.dma_state, DmaState::CharacterConversion1Active { .. })
907            && (0x400000..0x500000).contains(&source_address)
908        {
909            self.ccdma_transfer_in_progress = true;
910        }
911    }
912
913    pub fn notify_snes_dma_end(&mut self) {
914        self.ccdma_transfer_in_progress = false;
915    }
916
917    fn perform_arithmetic_op(&mut self) {
918        const I40_RANGE: Range<i64> = -(1 << 39)..1 << 39;
919        const I40_MASK: u64 = (1 << 40) - 1;
920
921        match self.arithmetic_op {
922            ArithmeticOp::Multiply => {
923                // Signed 16-bit x Signed 16-bit -> Signed 32-bit
924                self.arithmetic_result =
925                    (multiply(self.arithmetic_param_a, self.arithmetic_param_b) as u64) & I40_MASK;
926            }
927            ArithmeticOp::Divide => {
928                // Signed 16-bit / Unsigned 16-bit -> Signed 16-bit Quotient, Unsigned 16-bit Remainder
929                let (quotient, remainder) =
930                    divide(self.arithmetic_param_a, self.arithmetic_param_b);
931                let quotient: u64 = (quotient as u16).into();
932                let remainder: u64 = remainder.into();
933
934                self.arithmetic_result = quotient | (remainder << 16);
935
936                // Division apparently clears parameter A in addition to B
937                self.arithmetic_param_a = 0;
938            }
939            ArithmeticOp::MultiplyAccumulate => {
940                // Signed 16-bit x Signed 16-bit -> Signed 32-bit
941                // Accumulates into a signed 40-bit sum
942                let product = multiply(self.arithmetic_param_a, self.arithmetic_param_b);
943                let sum = (((self.arithmetic_result as i64) << 24) >> 24) + product;
944
945                self.arithmetic_result = (sum as u64) & I40_MASK;
946                self.arithmetic_overflow = !I40_RANGE.contains(&sum);
947            }
948        }
949
950        // All ops apparently clear parameter B
951        self.arithmetic_param_b = 0;
952    }
953
954    pub fn reset(&mut self, timer: &mut Sa1Timer, mmc: &mut Sa1Mmc) {
955        self.write_ccnt(0x20);
956        self.write_sie(0x00);
957        self.write_sic(0x00);
958        self.write_scnt(0x00);
959        self.write_cie(0x00);
960        self.write_cic(0x00, timer);
961        timer.write_tmc(0x00);
962        self.write_sbwe(0x00);
963        self.write_cbwe(0x00);
964        self.write_bwpa(0xFF);
965        self.write_siwp(0x00);
966        self.write_ciwp(0x00);
967        self.write_dcnt(0x00);
968        self.write_cdma(0x80);
969        self.write_mcnt(0x00);
970
971        mmc.write_cxb(0x00);
972        mmc.write_dxb(0x01);
973        mmc.write_exb(0x02);
974        mmc.write_fxb(0x03);
975        mmc.write_bmaps(0x00);
976        mmc.write_bmap(0x00);
977        mmc.write_bbf(0x00);
978    }
979
980    pub fn cpu_halted(&self) -> bool {
981        matches!(self.dma_state, DmaState::NormalCopying | DmaState::NormalWaitCycle)
982            && (self.dma_priority == DmaPriority::Dma || self.dma_source == DmaSourceDevice::Rom)
983    }
984}
985
986fn multiply(a: u16, b: u16) -> i64 {
987    let a: i64 = (a as i16).into();
988    let b: i64 = (b as i16).into();
989    a * b
990}
991
992fn divide(a: u16, b: u16) -> (i16, u16) {
993    if b == 0 {
994        // Divide by zero
995        return if a.sign_bit() { (1, (!a).wrapping_add(1)) } else { (-1, a) };
996    }
997
998    // Signed dividend, unsigned divisor
999    let a: i32 = (a as i16).into();
1000    let b: i32 = b.into();
1001
1002    // Signed quotient, unsigned remainder
1003    let quotient = a.div_euclid(b);
1004    let remainder = a.rem_euclid(b);
1005
1006    (quotient as i16, remainder as u16)
1007}