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}