1use crate::video::WordByte; 2use crate::video::vdc::{CgMode, DmaStep, PendingCpuAccess, Vdc}; 3use bincode::{Decode, Encode}; 4use jgenesis_common::define_bit_enum; 5use jgenesis_common::num::{GetBit, U16Ext}; 6use std::cmp; 7 8#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)] 9pub enum VramAccessWidth { 10 #[default] 11 One, // 1 CPU access every 2 dots during BG fetching 12 Two, // 1 CPU access every 8 dots during BG fetching 13 Four, // No CPU access during BG fetching, and BG fetches only fetch half of the bitplanes 14} 15 16impl VramAccessWidth { 17 fn from_bits(bits: u8) -> Self { 18 match bits & 3 { 19 0 => Self::One, 20 1 | 2 => Self::Two, 21 3 => Self::Four, 22 _ => unreachable!("value & 3 is always <= 3"), 23 } 24 } 25 26 fn display(self) -> &'static str { 27 match self { 28 Self::One => "1", 29 Self::Two => "2", 30 Self::Four => "4", 31 } 32 } 33} 34 35#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)] 36#[rustfmt::skip] 37pub enum SpriteAccessWidth { 38 #[default] 39 One, // Fetch 1 sprite per 4 dots 40 TwoHalfBpp, // Fetch 1 sprite per 4 dots but only half of the bitplanes 41 TwoFullBpp, // Fetch 1 sprite per 8 dots 42 Four, // Fetch 1 sprite per 8 dots but only half of the bitplanes 43} 44 45impl SpriteAccessWidth { 46 fn from_bits(bits: u8) -> Self { 47 match bits & 3 { 48 0 => Self::One, 49 1 => Self::TwoHalfBpp, 50 2 => Self::TwoFullBpp, 51 3 => Self::Four, 52 _ => unreachable!("value & 3 is always <= 3"), 53 } 54 } 55 56 fn display(self) -> &'static str { 57 match self { 58 Self::One => "1", 59 Self::TwoHalfBpp => "2 (2bpp sprites)", 60 Self::TwoFullBpp => "2 (4bpp sprites)", 61 Self::Four => "4", 62 } 63 } 64 65 pub fn is_half_bpp(self) -> bool { 66 matches!(self, Self::TwoHalfBpp | Self::Four) 67 } 68} 69 70define_bit_enum!(VirtualScreenHeight, [Single, Double]); 71 72impl VirtualScreenHeight { 73 fn display(self) -> &'static str { 74 match self { 75 Self::Single => "32 tiles", 76 Self::Double => "64 tiles", 77 } 78 } 79 80 pub fn to_tiles(self) -> u16 { 81 match self { 82 Self::Single => 32, 83 Self::Double => 64, 84 } 85 } 86} 87 88#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)] 89pub enum VirtualScreenWidth { 90 #[default] 91 Single, 92 Double, 93 Quad, 94} 95 96impl VirtualScreenWidth { 97 fn from_bits(bits: u8) -> Self { 98 match bits & 3 { 99 0 => Self::Single, 100 1 => Self::Double, 101 2 | 3 => Self::Quad, 102 _ => unreachable!("value & 3 is always <= 3"), 103 } 104 } 105 106 fn display(self) -> &'static str { 107 match self { 108 Self::Single => "32 tiles", 109 Self::Double => "64 tiles", 110 Self::Quad => "128 tiles", 111 } 112 } 113 114 pub fn to_tiles(self) -> u16 { 115 match self { 116 Self::Single => 32, 117 Self::Double => 64, 118 Self::Quad => 128, 119 } 120 } 121} 122 123#[derive(Debug, Clone, Encode, Decode)] 124pub struct VdcRegisters { 125 // $00: MAWR (Memory address write register) 126 pub vram_write_address: u16, 127 // $01: MARR (Memory address read register) 128 pub vram_read_address: u16, 129 // $02: VRR (VRAM read register) 130 pub vram_read_buffer: u16, 131 // $03: VWR (VRAM write register) 132 pub vram_write_latch: u8, 133 // $05: CR (Control register) 134 pub vblank_irq_enabled: bool, 135 pub raster_compare_irq_enabled: bool, 136 pub sprite_overflow_irq_enabled: bool, 137 pub sprite_collision_irq_enabled: bool, 138 pub bg_enabled: bool, 139 pub sprites_enabled: bool, 140 pub vram_address_increment: u16, 141 // $06: RCR (Raster compare register) 142 pub raster_compare: u16, 143 // $07: BXR (BG X scroll register) 144 pub bg_x_scroll: u16, 145 // $08: BYR (BG Y scroll register) 146 pub bg_y_scroll: u16, 147 // $09: MWR (Memory width register) 148 pub vram_access_width: VramAccessWidth, 149 pub sprite_access_width: SpriteAccessWidth, 150 pub virtual_screen_width: VirtualScreenWidth, 151 pub virtual_screen_height: VirtualScreenHeight, 152 pub bg_cg_mode: CgMode, 153 // $0A: HSR (Horizontal sync register) 154 pub h_sync_width: u16, // HSW (+1 * 8) 155 pub h_display_start: u16, // HDS (+1 * 8) 156 // $0B: HDR (Horizontal display register) 157 pub h_display_width: u16, // HDW (+1 * 8) 158 pub h_display_end: u16, // HDE (+1 * 8) 159 // $0C: VSR (Vertical sync register) 160 pub v_sync_width: u16, // VSW (+1) 161 pub v_display_start: u16, // VDS (+2) 162 // $0D: VDR (Vertical display register) 163 pub v_display_width_raw: u16, 164 pub v_display_width: u16, // VDW (+1) 165 // $0E: VCR (Vertical display end position register) 166 pub v_display_end: u16, // VCR 167 // $0F: DCR (DMA control register) 168 pub vram_dma_irq_enabled: bool, 169 pub sat_dma_irq_enabled: bool, 170 pub vram_dma_source_step: DmaStep, 171 pub vram_dma_destination_step: DmaStep, 172 pub sat_dma_repeat: bool, 173 // $10: SOUR (DMA source address register) 174 pub vram_dma_source_address: u16, 175 // $11: DESR (DMA destination address register) 176 pub vram_dma_destination_address: u16, 177 // $12: LENR (DMA length register) 178 pub vram_dma_length: u16, 179 // $13: DVSSR (VRAM-SAT DMA source address register) 180 pub sat_dma_source_address: u16, 181} 182 183impl VdcRegisters { 184 pub fn new() -> Self { 185 Self { 186 vram_write_address: !0, 187 vram_read_address: !0, 188 vram_read_buffer: !0, 189 vram_write_latch: !0, 190 vblank_irq_enabled: false, 191 raster_compare_irq_enabled: false, 192 sprite_overflow_irq_enabled: false, 193 sprite_collision_irq_enabled: false, 194 bg_enabled: false, 195 sprites_enabled: false, 196 vram_address_increment: 1, 197 raster_compare: !0, 198 bg_x_scroll: 0, 199 bg_y_scroll: 0, 200 vram_access_width: VramAccessWidth::default(), 201 sprite_access_width: SpriteAccessWidth::default(), 202 virtual_screen_width: VirtualScreenWidth::default(), 203 virtual_screen_height: VirtualScreenHeight::default(), 204 bg_cg_mode: CgMode::default(), 205 // Arbitrarily default H/V display registers to the settings used by Bonk's Adventure 206 h_sync_width: 24, 207 h_display_start: 24, 208 h_display_width: 256, 209 h_display_end: 32, 210 v_sync_width: 3, 211 v_display_start: 17, 212 v_display_width_raw: 239, 213 v_display_width: 240, 214 v_display_end: 3, 215 vram_dma_irq_enabled: false, 216 sat_dma_irq_enabled: false, 217 vram_dma_source_step: DmaStep::default(), 218 vram_dma_destination_step: DmaStep::default(), 219 sat_dma_repeat: false, 220 vram_dma_source_address: !0, 221 vram_dma_destination_address: !0, 222 vram_dma_length: !0, 223 sat_dma_source_address: !0, 224 } 225 } 226} 227 228impl Vdc { 229 pub fn read_status(&mut self) -> u8 { 230 // TODO should DMA accesses set the busy flag? 231 let busy = self.state.pending_cpu_access.is_some(); 232 233 let status = (u8::from(busy) << 6) 234 | (u8::from(self.state.vblank_irq_pending) << 5) 235 | (u8::from(self.state.vram_dma_irq_pending) << 4) 236 | (u8::from(self.state.sat_dma_irq_pending) << 3) 237 | (u8::from(self.state.raster_compare_irq_pending) << 2) 238 | (u8::from(self.state.sprite_overflow_irq_pending) << 1) 239 | u8::from(self.state.sprite_collision_irq_pending); 240 241 // All VDC IRQ flags are cleared on status register read 242 self.state.vblank_irq_pending = false; 243 self.state.raster_compare_irq_pending = false; 244 self.state.sprite_collision_irq_pending = false; 245 self.state.sprite_overflow_irq_pending = false; 246 self.state.vram_dma_irq_pending = false; 247 self.state.sat_dma_irq_pending = false; 248 249 self.state.any_irq_pending = false; 250 251 status 252 } 253 254 pub fn write_register_select(&mut self, value: u8) { 255 self.selected_register = value & 0x1F; 256 257 log::trace!("Selected register ${:02X}", self.selected_register); 258 } 259 260 pub fn read_data(&mut self, byte: WordByte) -> u8 { 261 let value = byte.get(self.registers.vram_read_buffer); 262 263 // MARR only increments when selected register is VRR/VWR 264 if self.selected_register == 0x02 && byte == WordByte::High { 265 debug_assert!(self.state.pending_cpu_access.is_none()); 266 267 self.state.pending_cpu_access = 268 Some(PendingCpuAccess::Read { address: self.registers.vram_read_address }); 269 self.increment_vram_read_address(); 270 } 271 272 value 273 } 274 275 pub fn write_data(&mut self, value: u8, byte: WordByte) { 276 match self.selected_register { 277 0x00 => { 278 // MAWR (Memory address write register) 279 byte.set(&mut self.registers.vram_write_address, value); 280 281 log::trace!( 282 "MAWR {byte:?} write: {value:02X} (address {:04X})", 283 self.registers.vram_write_address 284 ); 285 } 286 0x01 => { 287 // MARR (Memory address read register); 288 byte.set(&mut self.registers.vram_read_address, value); 289 290 log::trace!( 291 "MAAR {byte:?} write: {value:02X} (address {:04X})", 292 self.registers.vram_read_address 293 ); 294 295 // Writing to MSB initiates VRAM read 296 if byte == WordByte::High { 297 debug_assert!(self.state.pending_cpu_access.is_none()); 298 299 self.state.pending_cpu_access = 300 Some(PendingCpuAccess::Read { address: self.registers.vram_read_address }); 301 self.increment_vram_read_address(); 302 } 303 } 304 0x02 => { 305 // VWR (VRAM write register) 306 match byte { 307 WordByte::Low => { 308 // LSB writes latch the byte 309 self.registers.vram_write_latch = value; 310 } 311 WordByte::High => { 312 // MSB writes persist to VRAM along with latched byte 313 debug_assert!(self.state.pending_cpu_access.is_none()); 314 315 let word = u16::from_le_bytes([self.registers.vram_write_latch, value]); 316 self.state.pending_cpu_access = Some(PendingCpuAccess::Write { 317 address: self.registers.vram_write_address, 318 value: word, 319 }); 320 self.increment_vram_write_address(); 321 } 322 } 323 } 324 0x05 => { 325 // CR (Control register) 326 match byte { 327 WordByte::Low => { 328 self.registers.sprite_collision_irq_enabled = value.bit(0); 329 self.registers.sprite_overflow_irq_enabled = value.bit(1); 330 self.registers.raster_compare_irq_enabled = value.bit(2); 331 self.registers.vblank_irq_enabled = value.bit(3); 332 self.registers.sprites_enabled = value.bit(6); 333 self.registers.bg_enabled = value.bit(7); 334 335 log::trace!("CR Low write: {value:02X}"); 336 log::trace!(" BG enabled: {}", self.registers.bg_enabled); 337 log::trace!(" Sprites enabled: {}", self.registers.sprites_enabled); 338 log::trace!(" VBlank IRQ enabled: {}", self.registers.vblank_irq_enabled); 339 log::trace!( 340 " Raster compare IRQ enabled: {}", 341 self.registers.raster_compare_irq_enabled 342 ); 343 log::trace!( 344 " Sprite overflow IRQ enabled: {}", 345 self.registers.sprite_overflow_irq_enabled 346 ); 347 log::trace!( 348 " Sprite collision IRQ enabled: {}", 349 self.registers.sprite_collision_irq_enabled 350 ); 351 } 352 WordByte::High => { 353 let increment_idx = ((value >> 3) & 3) as usize; 354 self.registers.vram_address_increment = 355 [0x01, 0x20, 0x40, 0x80][increment_idx]; 356 357 log::trace!("CR High write: {value:02X}"); 358 log::trace!( 359 " VRAM address increment: 0x{:02X}", 360 self.registers.vram_address_increment 361 ); 362 } 363 } 364 } 365 0x06 => { 366 // RCR (Raster compare register) 367 match byte { 368 WordByte::Low => self.registers.raster_compare.set_lsb(value), 369 WordByte::High => self.registers.raster_compare.set_msb(value & 3), 370 } 371 372 log::trace!( 373 "RCR {byte:?} write: {value:02X} (raster compare {})", 374 self.registers.raster_compare 375 ); 376 } 377 0x07 => { 378 // BXR (BG X scroll register) 379 match byte { 380 WordByte::Low => self.registers.bg_x_scroll.set_lsb(value), 381 WordByte::High => self.registers.bg_x_scroll.set_msb(value & 3), 382 } 383 384 log::trace!( 385 "BXR {byte:?} write: {value:02X} (BG X scroll {})", 386 self.registers.bg_x_scroll 387 ); 388 } 389 0x08 => { 390 // BYR (BG Y scroll register) 391 match byte { 392 WordByte::Low => self.registers.bg_y_scroll.set_lsb(value), 393 WordByte::High => self.registers.bg_y_scroll.set_msb(value & 1), 394 } 395 396 self.state.bg_y_scroll_written = true; 397 398 log::trace!( 399 "BYR {byte:?} write: {value:02X} (BG Y scroll {})", 400 self.registers.bg_y_scroll 401 ); 402 } 403 0x09 => { 404 // MWR (Memory width register) 405 if byte == WordByte::Low { 406 self.registers.vram_access_width = VramAccessWidth::from_bits(value); 407 self.registers.sprite_access_width = SpriteAccessWidth::from_bits(value >> 2); 408 self.registers.virtual_screen_width = VirtualScreenWidth::from_bits(value >> 4); 409 self.registers.virtual_screen_height = 410 VirtualScreenHeight::from_bit(value.bit(6)); 411 self.registers.bg_cg_mode = CgMode::from_bit(value.bit(7)); 412 } 413 414 log::trace!("MWR {byte:?} write: {value:02X}"); 415 log::trace!(" VRAM access width: {}", self.registers.vram_access_width.display()); 416 log::trace!( 417 " Sprite access width: {}", 418 self.registers.sprite_access_width.display() 419 ); 420 log::trace!( 421 " Virtual screen width: {}", 422 self.registers.virtual_screen_width.display() 423 ); 424 log::trace!( 425 " Virtual screen height: {}", 426 self.registers.virtual_screen_height.display() 427 ); 428 log::trace!(" BG CG mode: {:?}", self.registers.bg_cg_mode); 429 } 430 0x0A => { 431 // HSR (Horizontal sync register) 432 log::trace!("HSR {byte:?} write: {value:02X}"); 433 434 match byte { 435 WordByte::Low => { 436 // Don't allow games to set extremely low HSW values 437 // Fixes horribly glitched graphics in Yo' Bro 438 self.registers.h_sync_width = 439 cmp::max(24, 8 * u16::from((value & 0x1F) + 1)); 440 log::trace!(" H sync pulse width: {}", self.registers.h_sync_width); 441 } 442 WordByte::High => { 443 self.registers.h_display_start = 8 * u16::from((value & 0x7F) + 1); 444 log::trace!( 445 " H display start position: {}", 446 self.registers.h_display_start 447 ); 448 } 449 } 450 } 451 0x0B => { 452 // HDR (Horizontal display register) 453 log::trace!("HDR {byte:?} write: {value:02X}"); 454 455 match byte { 456 WordByte::Low => { 457 self.registers.h_display_width = 8 * u16::from((value & 0x7F) + 1); 458 log::trace!(" H display width: {}", self.registers.h_display_width); 459 } 460 WordByte::High => { 461 self.registers.h_display_end = 8 * u16::from((value & 0x7F) + 1); 462 log::trace!(" H display end position: {}", self.registers.h_display_end); 463 } 464 } 465 } 466 0x0C => { 467 // VSR (Vertical sync register) 468 log::trace!("VSR {byte:?} write: {value:02X}"); 469 470 match byte { 471 WordByte::Low => { 472 self.registers.v_sync_width = ((value & 0x1F) + 1).into(); 473 log::trace!(" V sync pulse width: {}", self.registers.v_sync_width); 474 } 475 WordByte::High => { 476 self.registers.v_display_start = u16::from(value) + 2; 477 log::trace!( 478 " V display start position: {}", 479 self.registers.v_display_start 480 ); 481 } 482 } 483 } 484 0x0D => { 485 // VDR (Vertical display register) 486 match byte { 487 WordByte::Low => self.registers.v_display_width_raw.set_lsb(value), 488 WordByte::High => self.registers.v_display_width_raw.set_msb(value & 1), 489 } 490 self.registers.v_display_width = self.registers.v_display_width_raw + 1; 491 492 log::trace!("VDR {byte:?} write: {value:02X}"); 493 log::trace!(" V display width: {}", self.registers.v_display_width); 494 } 495 0x0E => { 496 // VCR (Vertical display end position register) 497 if byte == WordByte::Low { 498 self.registers.v_display_end = value.into(); 499 } 500 501 log::trace!("VCR {byte:?} write: {value:02X}"); 502 log::trace!(" V display end position: {}", self.registers.v_display_end); 503 } 504 0x0F => { 505 // DCR (DMA control register) 506 if byte == WordByte::Low { 507 self.registers.sat_dma_irq_enabled = value.bit(0); 508 self.registers.vram_dma_irq_enabled = value.bit(1); 509 self.registers.vram_dma_source_step = DmaStep::from_bit(value.bit(2)); 510 self.registers.vram_dma_destination_step = DmaStep::from_bit(value.bit(3)); 511 self.registers.sat_dma_repeat = value.bit(4); 512 } 513 514 log::trace!("DCR {byte:?} write: {value:02X}"); 515 log::trace!( 516 " VRAM-to-VRAM DMA IRQ enabled: {}", 517 self.registers.vram_dma_irq_enabled 518 ); 519 log::trace!( 520 " VRAM-to-SAT DMA IRQ enabled: {}", 521 self.registers.sat_dma_irq_enabled 522 ); 523 log::trace!( 524 " VRAM-to-VRAM DMA source step: {:?}", 525 self.registers.vram_dma_source_step 526 ); 527 log::trace!( 528 " VRAM-to-VRAM DMA destination step: {:?}", 529 self.registers.vram_dma_destination_step 530 ); 531 log::trace!(" VRAM_to-SAT DMA repeat: {}", self.registers.sat_dma_repeat); 532 } 533 0x10 => { 534 // SOUR (DMA source address register) 535 byte.set(&mut self.registers.vram_dma_source_address, value); 536 537 log::trace!( 538 "SOUR {byte:?} write: {value:02X} (address {:04X})", 539 self.registers.vram_dma_source_address 540 ); 541 } 542 0x11 => { 543 // DESR (DMA destination address register) 544 byte.set(&mut self.registers.vram_dma_destination_address, value); 545 546 log::trace!( 547 "DESR {byte:?} write: {value:02X} (address {:04X})", 548 self.registers.vram_dma_destination_address 549 ); 550 } 551 0x12 => { 552 // LENR (DMA length register) 553 byte.set(&mut self.registers.vram_dma_length, value); 554 555 if byte == WordByte::High { 556 self.state.dma.vram_triggered = true; 557 558 // VRAM-to-VRAM DMA can start immediately in burst mode or VBlank 559 if self.state.can_start_vram_dma() { 560 self.state.dma.start_vram(); 561 562 log::trace!("Starting VRAM-to-VRAM DMA on line {}", self.state.scanline); 563 } 564 } 565 566 log::trace!( 567 "LENR {byte:?} write: {value:02X} (length {:04X})", 568 self.registers.vram_dma_length 569 ); 570 } 571 0x13 => { 572 // DVSSR (VRAM-to-SAT DMA source address register) 573 byte.set(&mut self.registers.sat_dma_source_address, value); 574 575 if byte == WordByte::High { 576 self.state.dma.sat_triggered = true; 577 } 578 579 log::trace!( 580 "DVSSR {byte:?} write: {value:02X} (address {:04X})", 581 self.registers.sat_dma_source_address 582 ); 583 } 584 _ => { 585 log::warn!( 586 "Invalid VDC register write {:02X} {byte:?} {value:02X}", 587 self.selected_register 588 ); 589 } 590 } 591 } 592}