1//! Code for handling word RAM, a 256KB block of RAM that can be exchanged between the main CPU
2//! and the sub CPU
3
4use crate::ScdCpu;
5use bincode::{Decode, Encode};
6use jgenesis_common::boxedarray::BoxedByteArray;
7use jgenesis_common::debug::{DebugBytesView, DebugMemoryView};
8use jgenesis_common::num::GetBit;
9
10// Word RAM is 256KB
11pub const ADDRESS_MASK: u32 = 0x03FFFF;
12
13// Word RAM is mapped to $080000-$0BFFFF in sub CPU memory map
14pub const SUB_BASE_ADDRESS: u32 = 0x080000;
15
16const WORD_RAM_LEN: usize = 256 * 1024;
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)]
19pub enum WordRamMode {
20    #[default]
21    TwoM,
22    OneM,
23}
24
25impl WordRamMode {
26    fn to_bit(self) -> bool {
27        self == Self::OneM
28    }
29
30    fn from_bit(bit: bool) -> Self {
31        if bit { Self::OneM } else { Self::TwoM }
32    }
33}
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)]
36pub enum WordRamPriorityMode {
37    #[default]
38    Off,
39    Underwrite,
40    Overwrite,
41    Invalid,
42}
43
44impl WordRamPriorityMode {
45    pub fn to_bits(self) -> u8 {
46        match self {
47            Self::Off => 0x00,
48            Self::Underwrite => 0x01,
49            Self::Overwrite => 0x02,
50            Self::Invalid => 0x03,
51        }
52    }
53
54    pub fn from_bits(bits: u8) -> Self {
55        match bits & 0x03 {
56            0x00 => Self::Off,
57            0x01 => Self::Underwrite,
58            0x02 => Self::Overwrite,
59            0x03 => Self::Invalid,
60            _ => unreachable!("value & 0x03 is always <= 0x03"),
61        }
62    }
63}
64
65#[derive(Debug, Clone, Copy, PartialEq, Eq)]
66pub enum Nibble {
67    High,
68    Low,
69}
70
71#[derive(Debug, Clone, Copy, PartialEq, Eq)]
72enum WordRamSubMapResult {
73    None,
74    Byte(u32),
75    Pixel(u32),
76}
77
78#[derive(Debug, Clone, Encode, Decode)]
79pub struct WordRam {
80    ram: BoxedByteArray<WORD_RAM_LEN>,
81    sub_buffered_writes: Vec<(u32, u8)>,
82    sub_blocked_read: bool,
83    mode: WordRamMode,
84    priority_mode: WordRamPriorityMode,
85    owner_2m: ScdCpu,
86    bank_0_owner_1m: ScdCpu,
87    swap_request: bool,
88}
89
90// N cells, 8 pixels vertically, 8 pixels horizontally, 2 pixels per byte
91const CELL_IMAGE_V32_SIZE_BYTES: u32 = 32 * 8 * 8 / 2;
92const CELL_IMAGE_V16_SIZE_BYTES: u32 = 16 * 8 * 8 / 2;
93const CELL_IMAGE_V8_SIZE_BYTES: u32 = 8 * 8 * 8 / 2;
94const CELL_IMAGE_V4_SIZE_BYTES: u32 = 4 * 8 * 8 / 2;
95
96const CELL_IMAGE_H_SIZE_BYTES: u32 = 64 * 8 / 2;
97
98impl WordRam {
99    #[must_use]
100    pub fn new() -> Self {
101        Self {
102            ram: BoxedByteArray::new(),
103            sub_buffered_writes: Vec::with_capacity(5),
104            sub_blocked_read: false,
105            mode: WordRamMode::default(),
106            priority_mode: WordRamPriorityMode::default(),
107            owner_2m: ScdCpu::Main,
108            bank_0_owner_1m: ScdCpu::Main,
109            swap_request: false,
110        }
111    }
112
113    #[must_use]
114    pub fn mode(&self) -> WordRamMode {
115        self.mode
116    }
117
118    #[must_use]
119    pub fn read_control(&self) -> u8 {
120        let (dmna, ret) = match self.mode {
121            WordRamMode::TwoM => {
122                let dmna = self.owner_2m == ScdCpu::Sub;
123                let ret = !dmna;
124                (dmna, ret)
125            }
126            WordRamMode::OneM => {
127                let dmna = self.swap_request;
128                let ret = self.bank_0_owner_1m == ScdCpu::Sub;
129                (dmna, ret)
130            }
131        };
132
133        (u8::from(self.mode.to_bit()) << 2) | (u8::from(dmna) << 1) | u8::from(ret)
134    }
135
136    pub(crate) fn main_cpu_write_control(&mut self, value: u8) {
137        let dmna = value.bit(1);
138
139        // DMNA=1 always returns 2M word RAM to sub CPU, regardless of mode
140        if dmna {
141            self.owner_2m = ScdCpu::Sub;
142            self.flush_buffered_sub_writes();
143            self.sub_blocked_read = false;
144        }
145
146        // In 1M mode, setting DMNA=0 sends a swap request to the sub CPU
147        if self.mode == WordRamMode::OneM && !dmna {
148            self.swap_request = true;
149        }
150
151        log::trace!("Main CPU control write; DMNA={}, mode={:?}", u8::from(dmna), self.mode);
152    }
153
154    pub(crate) fn sub_cpu_write_control(&mut self, value: u8) {
155        self.mode = WordRamMode::from_bit(value.bit(2));
156        let ret = value.bit(0);
157
158        // RET=1 always returns 2M word RAM to main CPU, regardless of mode
159        if ret {
160            self.owner_2m = ScdCpu::Main;
161        }
162
163        let prev_bank_0_owner = self.bank_0_owner_1m;
164        // In 1M mode, RET returns the given bank to the main CPU
165        // RET=0 -> main owns bank 0, sub owns bank 1
166        // RET=1 -> main owns bank 1, sub owns bank 0
167        self.bank_0_owner_1m = if ret { ScdCpu::Sub } else { ScdCpu::Main };
168
169        // Only clear swap request if 1M bank 0 owner changed
170        if prev_bank_0_owner != self.bank_0_owner_1m {
171            self.swap_request = false;
172        }
173
174        self.priority_mode = WordRamPriorityMode::from_bits(value >> 3);
175
176        log::trace!(
177            "Sub CPU control write; RET={}, mode={:?}, priority_mode={:?}",
178            u8::from(ret),
179            self.mode,
180            self.priority_mode
181        );
182    }
183
184    fn main_cpu_map_address(&self, address: u32) -> Option<u32> {
185        let address = address & 0x3FFFF;
186        match self.mode {
187            WordRamMode::TwoM => (self.owner_2m == ScdCpu::Main).then_some(address),
188            WordRamMode::OneM => {
189                if address <= 0x1FFFF {
190                    Some(determine_1m_address(address, ScdCpu::Main, self.bank_0_owner_1m))
191                } else {
192                    match address & 0x1FFFF {
193                        address @ 0x00000..=0x0FFFF => {
194                            // V32xH64 image
195                            Some(map_cell_image_address(
196                                address & 0xFFFF,
197                                CELL_IMAGE_V32_SIZE_BYTES,
198                                self.bank_0_owner_1m,
199                                0x00000,
200                            ))
201                        }
202                        address @ 0x10000..=0x17FFF => {
203                            // V16xH64 image
204                            Some(map_cell_image_address(
205                                address & 0x7FFF,
206                                CELL_IMAGE_V16_SIZE_BYTES,
207                                self.bank_0_owner_1m,
208                                0x10000,
209                            ))
210                        }
211                        address @ 0x18000..=0x1BFFF => {
212                            // V8xH64 image
213                            Some(map_cell_image_address(
214                                address & 0x3FFF,
215                                CELL_IMAGE_V8_SIZE_BYTES,
216                                self.bank_0_owner_1m,
217                                0x18000,
218                            ))
219                        }
220                        address @ 0x1C000..=0x1DFFF => {
221                            // V4xH64 image #1
222                            Some(map_cell_image_address(
223                                address & 0x1FFF,
224                                CELL_IMAGE_V4_SIZE_BYTES,
225                                self.bank_0_owner_1m,
226                                0x1C000,
227                            ))
228                        }
229                        address @ 0x1E000..=0x1FFFF => {
230                            // V4xH64 image #2
231                            Some(map_cell_image_address(
232                                address & 0x1FFF,
233                                CELL_IMAGE_V4_SIZE_BYTES,
234                                self.bank_0_owner_1m,
235                                0x1E000,
236                            ))
237                        }
238                        _ => unreachable!("address masked with 0x1FFFF"),
239                    }
240                }
241            }
242        }
243    }
244
245    #[must_use]
246    pub fn main_cpu_read_ram(&self, address: u32) -> u8 {
247        match self.main_cpu_map_address(address) {
248            None => 0x00,
249            Some(addr) => self.ram[addr as usize],
250        }
251    }
252
253    pub(crate) fn main_cpu_write_ram(&mut self, address: u32, value: u8) {
254        match self.main_cpu_map_address(address) {
255            None => {}
256            Some(addr) => {
257                self.ram[addr as usize] = value;
258            }
259        }
260    }
261
262    fn sub_cpu_map_address(&self, address: u32) -> WordRamSubMapResult {
263        match (self.mode, address) {
264            (WordRamMode::TwoM, 0x080000..=0x0BFFFF) => {
265                WordRamSubMapResult::Byte(address & ADDRESS_MASK)
266            }
267            (WordRamMode::TwoM, 0x0C0000..=0x0DFFFF) => WordRamSubMapResult::None,
268            (WordRamMode::OneM, 0x080000..=0x0BFFFF) => {
269                let byte_addr = determine_1m_address(
270                    (address & 0x3FFFF) >> 1,
271                    ScdCpu::Sub,
272                    self.bank_0_owner_1m,
273                );
274                WordRamSubMapResult::Pixel((byte_addr << 1) | (address & 0x000001))
275            }
276            (WordRamMode::OneM, 0x0C0000..=0x0DFFFF) => WordRamSubMapResult::Byte(
277                determine_1m_address(address & 0x1FFFF, ScdCpu::Sub, self.bank_0_owner_1m),
278            ),
279            _ => panic!("Invalid sub CPU word RAM address: {address:06X}"),
280        }
281    }
282
283    pub(crate) fn sub_cpu_read_ram(&mut self, address: u32) -> u8 {
284        match self.sub_cpu_map_address(address) {
285            WordRamSubMapResult::None => 0,
286            WordRamSubMapResult::Byte(addr) => {
287                self.sub_blocked_read |= self.is_sub_access_blocked();
288                self.ram[addr as usize]
289            }
290            WordRamSubMapResult::Pixel(pixel_addr) => {
291                let byte_addr = (pixel_addr >> 1) as usize;
292                if pixel_addr.bit(0) {
293                    self.ram[byte_addr] & 0x0F
294                } else {
295                    self.ram[byte_addr] >> 4
296                }
297            }
298        }
299    }
300
301    #[must_use]
302    pub fn sub_cpu_peek_ram(&self, address: u32) -> u8 {
303        match self.sub_cpu_map_address(address) {
304            WordRamSubMapResult::None => 0,
305            WordRamSubMapResult::Byte(addr) => self.ram[addr as usize],
306            WordRamSubMapResult::Pixel(pixel_addr) => {
307                let byte_addr = (pixel_addr >> 1) as usize;
308                if pixel_addr.bit(0) {
309                    self.ram[byte_addr] & 0x0F
310                } else {
311                    self.ram[byte_addr] >> 4
312                }
313            }
314        }
315    }
316
317    pub(crate) fn sub_cpu_write_ram(&mut self, address: u32, value: u8) {
318        match self.sub_cpu_map_address(address) {
319            WordRamSubMapResult::None => {}
320            WordRamSubMapResult::Byte(byte_addr) => {
321                if !self.is_sub_access_blocked() {
322                    self.ram[byte_addr as usize] = value;
323                } else {
324                    self.sub_buffered_writes.push((byte_addr, value));
325                }
326            }
327            WordRamSubMapResult::Pixel(pixel_addr) => {
328                self.write_1m_pixel(pixel_addr, value & 0x0F);
329            }
330        }
331    }
332
333    /// Is sub CPU access to word RAM currently blocked (i.e. in 2M mode and main CPU owns word RAM)
334    #[must_use]
335    pub fn is_sub_access_blocked(&self) -> bool {
336        self.mode == WordRamMode::TwoM && self.owner_2m == ScdCpu::Main
337    }
338
339    /// Did the sub CPU access word RAM while access was blocked
340    #[must_use]
341    pub fn sub_performed_blocked_access(&self) -> bool {
342        !self.sub_buffered_writes.is_empty() || self.sub_blocked_read
343    }
344
345    fn flush_buffered_sub_writes(&mut self) {
346        for (address, byte) in self.sub_buffered_writes.drain(..) {
347            self.ram[address as usize] = byte;
348        }
349    }
350
351    pub(crate) fn graphics_write_ram(&mut self, address: u32, nibble: Nibble, pixel: u8) {
352        match self.sub_cpu_map_address(address) {
353            WordRamSubMapResult::None => {}
354            WordRamSubMapResult::Byte(addr) => {
355                let current_value = self.ram[addr as usize];
356                let current_nibble = match nibble {
357                    Nibble::High => current_value >> 4,
358                    Nibble::Low => current_value & 0x0F,
359                };
360
361                let should_write = match self.priority_mode {
362                    WordRamPriorityMode::Off | WordRamPriorityMode::Invalid => true,
363                    WordRamPriorityMode::Underwrite => current_nibble == 0,
364                    WordRamPriorityMode::Overwrite => pixel != 0,
365                };
366
367                if should_write {
368                    let new_value = match nibble {
369                        Nibble::High => (pixel << 4) | (current_value & 0x0F),
370                        Nibble::Low => pixel | (current_value & 0xF0),
371                    };
372                    self.ram[addr as usize] = new_value;
373                }
374            }
375            WordRamSubMapResult::Pixel(addr) => {
376                // High nibble is ignored for pixel writes
377                if nibble == Nibble::Low {
378                    self.write_1m_pixel(addr, pixel);
379                }
380            }
381        }
382    }
383
384    fn write_1m_pixel(&mut self, pixel_addr: u32, pixel: u8) {
385        let byte_addr = (pixel_addr >> 1) as usize;
386        let current_byte = self.ram[byte_addr];
387        let current_nibble =
388            if pixel_addr.bit(0) { current_byte & 0x0F } else { current_byte >> 4 };
389
390        let should_write = match self.priority_mode {
391            WordRamPriorityMode::Off | WordRamPriorityMode::Invalid => true,
392            WordRamPriorityMode::Underwrite => current_nibble == 0,
393            WordRamPriorityMode::Overwrite => pixel != 0,
394        };
395
396        if should_write {
397            let new_value = if pixel_addr.bit(0) {
398                pixel | (current_byte & 0xF0)
399            } else {
400                (pixel << 4) | (current_byte & 0x0F)
401            };
402            self.ram[byte_addr] = new_value;
403        }
404    }
405
406    pub(crate) fn dma_write(&mut self, address: u32, value: u8) {
407        // Word RAM DMA writes should go to $080000 in 2M mode and $0C0000 in 1M mode
408        // In 1M mode, $080000-$0BFFFF is a dot image of word RAM, and the raw bytes are at $0C0000-$0DFFFF
409        let base_address = match self.mode {
410            WordRamMode::TwoM => {
411                assert!(address <= 0x03FFFF);
412                SUB_BASE_ADDRESS
413            }
414            WordRamMode::OneM => {
415                assert!(address <= 0x01FFFF);
416                0x0C0000
417            }
418        };
419
420        self.sub_cpu_write_ram(base_address | address, value);
421    }
422
423    #[must_use]
424    pub fn priority_mode(&self) -> WordRamPriorityMode {
425        self.priority_mode
426    }
427
428    #[must_use]
429    pub fn debug_view(&mut self) -> impl DebugMemoryView {
430        DebugBytesView(self.ram.as_mut_slice())
431    }
432}
433
434impl Default for WordRam {
435    fn default() -> Self {
436        Self::new()
437    }
438}
439
440fn determine_1m_address(address: u32, cpu: ScdCpu, bank_0_owner: ScdCpu) -> u32 {
441    ((address & !0x01) << 1) | (u32::from(cpu != bank_0_owner) << 1) | (address & 0x01)
442}
443
444#[inline]
445fn map_cell_image_address(
446    masked_address: u32,
447    v_size_bytes: u32,
448    bank_0_owner: ScdCpu,
449    base_word_ram_addr: u32,
450) -> u32 {
451    let row = (masked_address & (v_size_bytes - 1)) >> 2;
452    let col = masked_address / v_size_bytes;
453
454    let byte_addr =
455        base_word_ram_addr | (row * CELL_IMAGE_H_SIZE_BYTES) | (col << 2) | (masked_address & 0x03);
456    determine_1m_address(byte_addr, ScdCpu::Main, bank_0_owner)
457}
458
459#[cfg(test)]
460mod tests {
461    use super::*;
462
463    #[test]
464    fn word_ram_main_cpu_2m_mapping() {
465        let mut word_ram = WordRam::new();
466        word_ram.mode = WordRamMode::TwoM;
467
468        word_ram.owner_2m = ScdCpu::Main;
469        assert_eq!(Some(0x000000), word_ram.main_cpu_map_address(0x200000));
470        assert_eq!(Some(0x03FFFF), word_ram.main_cpu_map_address(0x23FFFF));
471
472        word_ram.owner_2m = ScdCpu::Sub;
473        assert_eq!(None, word_ram.main_cpu_map_address(0x200000));
474        assert_eq!(None, word_ram.main_cpu_map_address(0x23FFFF));
475    }
476
477    #[test]
478    fn word_ram_main_cpu_1m_mapping() {
479        let mut word_ram = WordRam::new();
480        word_ram.mode = WordRamMode::OneM;
481
482        word_ram.bank_0_owner_1m = ScdCpu::Main;
483        assert_eq!(Some(0x000000), word_ram.main_cpu_map_address(0x200000));
484        assert_eq!(Some(0x020001), word_ram.main_cpu_map_address(0x210001));
485        assert_eq!(Some(0x020004), word_ram.main_cpu_map_address(0x210002));
486        assert_eq!(Some(0x03FFFD), word_ram.main_cpu_map_address(0x21FFFF));
487
488        word_ram.bank_0_owner_1m = ScdCpu::Sub;
489        assert_eq!(Some(0x000002), word_ram.main_cpu_map_address(0x200000));
490        assert_eq!(Some(0x020003), word_ram.main_cpu_map_address(0x210001));
491        assert_eq!(Some(0x03FFFF), word_ram.main_cpu_map_address(0x21FFFF));
492    }
493
494    #[test]
495    fn word_ram_main_cpu_1m_cell_image_mapping() {
496        let mut word_ram = WordRam::new();
497        word_ram.mode = WordRamMode::OneM;
498
499        // V32-cell
500        word_ram.bank_0_owner_1m = ScdCpu::Main;
501        assert_eq!(Some(0x000000), word_ram.main_cpu_map_address(0x220000));
502        assert_eq!(Some(0x000001), word_ram.main_cpu_map_address(0x220001));
503        assert_eq!(Some(0x000004), word_ram.main_cpu_map_address(0x220002));
504        assert_eq!(Some(0x000005), word_ram.main_cpu_map_address(0x220003));
505
506        assert_eq!(Some(0x000200), word_ram.main_cpu_map_address(0x220004));
507        assert_eq!(Some(0x000201), word_ram.main_cpu_map_address(0x220005));
508        assert_eq!(Some(0x000205), word_ram.main_cpu_map_address(0x220007));
509
510        assert_eq!(Some(0x000008), word_ram.main_cpu_map_address(0x220400));
511        assert_eq!(Some(0x000009), word_ram.main_cpu_map_address(0x220401));
512        assert_eq!(Some(0x00000C), word_ram.main_cpu_map_address(0x220402));
513        assert_eq!(Some(0x000208), word_ram.main_cpu_map_address(0x220404));
514
515        assert_eq!(Some(0x000010), word_ram.main_cpu_map_address(0x220800));
516
517        assert_eq!(Some(0x01FFFD), word_ram.main_cpu_map_address(0x22FFFF));
518
519        // V16-cell
520        assert_eq!(Some(0x020000), word_ram.main_cpu_map_address(0x230000));
521        assert_eq!(Some(0x020001), word_ram.main_cpu_map_address(0x230001));
522        assert_eq!(Some(0x020004), word_ram.main_cpu_map_address(0x230002));
523
524        assert_eq!(Some(0x020200), word_ram.main_cpu_map_address(0x230004));
525        assert_eq!(Some(0x020201), word_ram.main_cpu_map_address(0x230005));
526
527        assert_eq!(Some(0x020008), word_ram.main_cpu_map_address(0x230200));
528        assert_eq!(Some(0x020009), word_ram.main_cpu_map_address(0x230201));
529
530        assert_eq!(Some(0x020010), word_ram.main_cpu_map_address(0x230400));
531
532        assert_eq!(Some(0x02FFFD), word_ram.main_cpu_map_address(0x237FFF));
533
534        // V8-cell
535        assert_eq!(Some(0x030000), word_ram.main_cpu_map_address(0x238000));
536        assert_eq!(Some(0x030001), word_ram.main_cpu_map_address(0x238001));
537
538        assert_eq!(Some(0x030200), word_ram.main_cpu_map_address(0x238004));
539        assert_eq!(Some(0x030201), word_ram.main_cpu_map_address(0x238005));
540
541        assert_eq!(Some(0x030008), word_ram.main_cpu_map_address(0x238100));
542        assert_eq!(Some(0x030009), word_ram.main_cpu_map_address(0x238101));
543
544        assert_eq!(Some(0x030010), word_ram.main_cpu_map_address(0x238200));
545
546        assert_eq!(Some(0x037FFD), word_ram.main_cpu_map_address(0x23BFFF));
547
548        // V4-cell #1
549        assert_eq!(Some(0x038000), word_ram.main_cpu_map_address(0x23C000));
550        assert_eq!(Some(0x038001), word_ram.main_cpu_map_address(0x23C001));
551
552        assert_eq!(Some(0x038200), word_ram.main_cpu_map_address(0x23C004));
553        assert_eq!(Some(0x038201), word_ram.main_cpu_map_address(0x23C005));
554
555        assert_eq!(Some(0x038008), word_ram.main_cpu_map_address(0x23C080));
556        assert_eq!(Some(0x038009), word_ram.main_cpu_map_address(0x23C081));
557
558        assert_eq!(Some(0x038010), word_ram.main_cpu_map_address(0x23C100));
559
560        assert_eq!(Some(0x03BFFD), word_ram.main_cpu_map_address(0x23DFFF));
561
562        // V4-cell #2
563        assert_eq!(Some(0x03C000), word_ram.main_cpu_map_address(0x23E000));
564        assert_eq!(Some(0x03C001), word_ram.main_cpu_map_address(0x23E001));
565
566        assert_eq!(Some(0x03C200), word_ram.main_cpu_map_address(0x23E004));
567        assert_eq!(Some(0x03C201), word_ram.main_cpu_map_address(0x23E005));
568
569        assert_eq!(Some(0x03C008), word_ram.main_cpu_map_address(0x23E080));
570        assert_eq!(Some(0x03C009), word_ram.main_cpu_map_address(0x23E081));
571
572        assert_eq!(Some(0x03C010), word_ram.main_cpu_map_address(0x23E100));
573
574        assert_eq!(Some(0x03FFFD), word_ram.main_cpu_map_address(0x23FFFF));
575
576        // Test with main CPU having bank 1
577        word_ram.bank_0_owner_1m = ScdCpu::Sub;
578        assert_eq!(Some(0x000002), word_ram.main_cpu_map_address(0x220000));
579        assert_eq!(Some(0x03FFFF), word_ram.main_cpu_map_address(0x23FFFF));
580    }
581
582    #[test]
583    fn word_ram_sub_cpu_2m_mapping_sub_owner() {
584        use WordRamSubMapResult as R;
585
586        let mut word_ram = WordRam::new();
587        word_ram.mode = WordRamMode::TwoM;
588
589        word_ram.owner_2m = ScdCpu::Sub;
590        assert_eq!(R::Byte(0x000000), word_ram.sub_cpu_map_address(0x080000));
591        assert_eq!(R::Byte(0x03FFFF), word_ram.sub_cpu_map_address(0x0BFFFF));
592
593        assert_eq!(R::None, word_ram.sub_cpu_map_address(0x0C0000));
594        assert_eq!(R::None, word_ram.sub_cpu_map_address(0x0DFFFF));
595    }
596
597    #[test]
598    fn word_ram_sub_cpu_2m_mapping_main_owner() {
599        let mut word_ram = WordRam::new();
600        word_ram.ram.fill(0xFF);
601        word_ram.mode = WordRamMode::TwoM;
602        word_ram.owner_2m = ScdCpu::Main;
603
604        // Valid addresses but access is blocked
605        word_ram.sub_cpu_write_ram(0x080000, 0x01);
606        word_ram.sub_cpu_write_ram(0x0BFFFF, 0x02);
607
608        // Invalid addresses
609        word_ram.sub_cpu_write_ram(0x0C0000, 0x03);
610        word_ram.sub_cpu_write_ram(0x0DFFFF, 0x04);
611
612        assert!(word_ram.ram.iter().copied().all(|value| value == 0xFF));
613        assert_eq!(word_ram.sub_buffered_writes, vec![(0x00000, 0x01), (0x3FFFF, 0x02)]);
614
615        word_ram.flush_buffered_sub_writes();
616        assert_eq!(word_ram.ram[0x00000], 0x01);
617        assert_eq!(word_ram.ram[0x3FFFF], 0x02);
618    }
619
620    #[test]
621    fn word_ram_sub_cpu_1m_byte_mapping() {
622        use WordRamSubMapResult as R;
623
624        let mut word_ram = WordRam::new();
625        word_ram.mode = WordRamMode::OneM;
626
627        word_ram.bank_0_owner_1m = ScdCpu::Sub;
628        assert_eq!(R::Byte(0x000000), word_ram.sub_cpu_map_address(0x0C0000));
629        assert_eq!(R::Byte(0x010000), word_ram.sub_cpu_map_address(0x0C8000));
630        assert_eq!(R::Byte(0x03FFFD), word_ram.sub_cpu_map_address(0x0DFFFF));
631
632        word_ram.bank_0_owner_1m = ScdCpu::Main;
633        assert_eq!(R::Byte(0x000002), word_ram.sub_cpu_map_address(0x0C0000));
634        assert_eq!(R::Byte(0x010002), word_ram.sub_cpu_map_address(0x0C8000));
635        assert_eq!(R::Byte(0x03FFFF), word_ram.sub_cpu_map_address(0x0DFFFF));
636    }
637
638    #[test]
639    fn word_ram_sub_cpu_1m_pixel_mapping() {
640        use WordRamSubMapResult as R;
641
642        let mut word_ram = WordRam::new();
643        word_ram.mode = WordRamMode::OneM;
644
645        word_ram.bank_0_owner_1m = ScdCpu::Sub;
646        assert_eq!(R::Pixel(0x000000), word_ram.sub_cpu_map_address(0x080000));
647        assert_eq!(R::Pixel(0x000001), word_ram.sub_cpu_map_address(0x080001));
648        assert_eq!(R::Pixel(0x000002), word_ram.sub_cpu_map_address(0x080002));
649        assert_eq!(R::Pixel(0x000003), word_ram.sub_cpu_map_address(0x080003));
650        assert_eq!(R::Pixel(0x000008), word_ram.sub_cpu_map_address(0x080004));
651        assert_eq!(R::Pixel(0x000009), word_ram.sub_cpu_map_address(0x080005));
652        assert_eq!(R::Pixel(0x07FFFA), word_ram.sub_cpu_map_address(0x0BFFFE));
653        assert_eq!(R::Pixel(0x07FFFB), word_ram.sub_cpu_map_address(0x0BFFFF));
654
655        word_ram.bank_0_owner_1m = ScdCpu::Main;
656        assert_eq!(R::Pixel(0x000004), word_ram.sub_cpu_map_address(0x080000));
657        assert_eq!(R::Pixel(0x000005), word_ram.sub_cpu_map_address(0x080001));
658        assert_eq!(R::Pixel(0x000006), word_ram.sub_cpu_map_address(0x080002));
659        assert_eq!(R::Pixel(0x000007), word_ram.sub_cpu_map_address(0x080003));
660        assert_eq!(R::Pixel(0x00000C), word_ram.sub_cpu_map_address(0x080004));
661        assert_eq!(R::Pixel(0x00000D), word_ram.sub_cpu_map_address(0x080005));
662        assert_eq!(R::Pixel(0x07FFFE), word_ram.sub_cpu_map_address(0x0BFFFE));
663        assert_eq!(R::Pixel(0x07FFFF), word_ram.sub_cpu_map_address(0x0BFFFF));
664    }
665}