tgbr_core/
bus.rs

1use bitvec::prelude::*;
2use log::{debug, info, trace, warn};
3use serde::{Deserialize, Serialize};
4use std::cmp::max;
5
6use crate::{
7    config, context,
8    io::Io,
9    mbc::{Mbc, MbcTrait},
10    ppu,
11    util::{pack, trait_alias},
12};
13
14#[derive(Serialize, Deserialize)]
15pub struct Bus {
16    #[serde(with = "serde_bytes")]
17    ram: Vec<u8>,
18    ram_bank: u8,
19    #[serde(with = "serde_bytes")]
20    hiram: Vec<u8>,
21    #[serde(with = "serde_bytes")]
22    boot_rom: Option<Vec<u8>>,
23    map_boot_rom: bool,
24    vram_bank: u8,
25    current_speed: u8,
26    prepare_speed_switch: u8,
27    switch_delay: u16,
28    mbc: Mbc,
29    io: Io,
30    dma: Dma,
31    hdma: Hdma,
32    // Undocumented registers
33    reg_ff72: u8,
34    reg_ff73: u8,
35    reg_ff75: u8,
36}
37
38trait_alias!(pub trait Context =
39    context::Rom + context::ExternalRam + context::Vram +
40    context::Oam + context::Ppu + context::Apu + context::InterruptFlag + context::Model);
41
42#[derive(Default, Serialize, Deserialize)]
43struct Dma {
44    source: u8,
45    pos: u8,
46    enabled: bool,
47    delay: usize,
48}
49
50#[derive(Default, Serialize, Deserialize)]
51struct Hdma {
52    source: u16,
53    dest: u16,
54    mode: HdmaMode,
55    length: u8,
56    enabled_general_dma: bool,
57    enabled_hblank_dma: bool,
58    prev_hblank: bool,
59}
60
61#[derive(Serialize, Deserialize)]
62enum HdmaMode {
63    General,
64    HBlank,
65}
66
67impl Default for HdmaMode {
68    fn default() -> Self {
69        HdmaMode::General
70    }
71}
72
73impl Bus {
74    pub fn new(model: config::Model, mbc: Mbc, boot_rom: &Option<Vec<u8>>, io: Io) -> Self {
75        if let Some(boot_rom) = boot_rom {
76            if !model.is_cgb() {
77                assert_eq!(boot_rom.len(), 0x100, "DMG Boot ROM must be 256 bytes");
78            } else {
79                assert_eq!(boot_rom.len(), 0x900, "CGB Boot ROM must be 2304 bytes");
80            }
81        }
82
83        let ram_size = if model.is_cgb() { 0x8000 } else { 0x2000 };
84
85        Self {
86            ram: vec![0; ram_size],
87            ram_bank: 1,
88            hiram: vec![0; 0x7F],
89            boot_rom: boot_rom.clone(),
90            map_boot_rom: boot_rom.is_some(),
91            vram_bank: 0,
92            current_speed: 0,
93            prepare_speed_switch: 0,
94            switch_delay: 0,
95            mbc,
96            io,
97            dma: Dma::default(),
98            hdma: Hdma::default(),
99            reg_ff72: 0,
100            reg_ff73: 0,
101            reg_ff75: 0,
102        }
103    }
104
105    pub fn read(&mut self, ctx: &mut impl Context, addr: u16) -> u8 {
106        let data = self.read_(ctx, addr);
107        trace!("<-- Read:  ${addr:04X} = ${data:02X}");
108        data
109    }
110
111    pub fn read_immutable(&mut self, ctx: &mut impl Context, addr: u16) -> Option<u8> {
112        match addr {
113            0xFF00..=0xFF7F | 0xFFFF => None,
114            _ => Some(self.read_(ctx, addr)),
115        }
116    }
117
118    fn read_(&mut self, ctx: &mut impl Context, addr: u16) -> u8 {
119        match addr {
120            0x0100..=0x01FF => self.mbc.read(ctx, addr),
121            0x0000..=0x08FF => {
122                let is_boot_rom = self.map_boot_rom
123                    && self
124                        .boot_rom
125                        .as_ref()
126                        .map_or(false, |r| r.len() > addr as usize);
127                if is_boot_rom {
128                    if let Some(boot_rom) = &self.boot_rom {
129                        boot_rom[addr as usize]
130                    } else {
131                        !0
132                    }
133                } else {
134                    self.mbc.read(ctx, addr)
135                }
136            }
137            0x0900..=0x7FFF => self.mbc.read(ctx, addr),
138            0x8000..=0x9FFF => {
139                ctx.vram()[((addr & 0x1FFF) | (self.vram_bank as u16 * 0x2000)) as usize]
140            }
141            0xA000..=0xBFFF => self.mbc.read(ctx, addr),
142            0xC000..=0xFDFF => {
143                let bank = addr & 0x1000;
144                self.ram[((addr & 0x0FFF) + bank * self.ram_bank as u16) as usize]
145            }
146            0xFE00..=0xFE9F => {
147                if !self.dma.enabled {
148                    ctx.oam()[(addr & 0xff) as usize]
149                } else {
150                    !0
151                }
152            }
153            0xFEA0..=0xFEFF => {
154                warn!("Read from Unusable address: ${addr:04x}");
155                !0
156            }
157            0xFF46 => self.dma.source, // DMA
158            0xFF50 => !0,              // BANK
159
160            0xFF4C => !0, // KEY0 CPU mode register
161
162            // KEY1 - CGB Mode Only - Prepare Speed Switch
163            0xFF4D => {
164                if ctx.running_mode().is_cgb() {
165                    pack! {
166                        7..=7 => self.current_speed,
167                        1..=6 => !0,
168                        0..=0 => self.prepare_speed_switch,
169                    }
170                } else {
171                    !0
172                }
173            }
174            0xFF4E => !0, // ???
175
176            // VBK - CGB Mode Only - VRAM Bank (R/W)
177            0xFF4F => {
178                if ctx.running_mode().is_cgb() {
179                    pack!(0..=0 => self.vram_bank, 1..=7 => !0)
180                } else {
181                    !0
182                }
183            }
184
185            0xFF51 => {
186                warn!("Load HDMA1");
187                !0
188            } // HDMA1 (New DMA Source, High) (W) - CGB Mode Only
189            0xFF52 => {
190                warn!("Load HDMA2");
191                !0
192            } // HDMA2 (New DMA Source, Low) (W) - CGB Mode Only
193            0xFF53 => {
194                warn!("Load HDMA3");
195                !0
196            } // HDMA3 (New DMA Destination, High) (W) - CGB Mode Only
197            0xFF54 => {
198                warn!("Load HDMA4");
199                !0
200            } // HDMA4 (New DMA Destination, Low) (W) - CGB Mode Only
201
202            // HDMA5 (New DMA Length/Mode/Start) (W) - CGB Mode Only
203            0xFF55 => {
204                if ctx.running_mode().is_cgb() {
205                    pack! {
206                        7 => !self.hdma.enabled_hblank_dma,
207                        0..=6 => self.hdma.length,
208                    }
209                } else {
210                    !0
211                }
212            }
213            // SVBK - CGB Mode Only - WRAM Bank
214            0xFF70 => {
215                if ctx.running_mode().is_cgb() {
216                    pack!(0..=2 => self.ram_bank, 3..=7 => !0)
217                } else {
218                    !0
219                }
220            }
221
222            // Undocumented registers
223            0xFF72 => self.reg_ff72,
224            0xFF73 => self.reg_ff73,
225            0xFF75 => pack! {
226                7..=7 => !0,
227                4..=6 => self.reg_ff75,
228                0..=3 => !0,
229            },
230
231            0xFF00..=0xFF7F => self.io.read(ctx, addr),
232            0xFF80..=0xFFFE => self.hiram[(addr & 0x7F) as usize],
233            0xFFFF => self.io.read(ctx, addr),
234        }
235    }
236
237    pub fn write(&mut self, ctx: &mut impl Context, addr: u16, data: u8) {
238        trace!("--> Write: ${addr:04X} = ${data:02X}");
239        match addr {
240            0x0000..=0x7FFF => self.mbc.write(ctx, addr, data),
241            0x8000..=0x9FFF => {
242                ctx.vram_mut()[((addr & 0x1FFF) | (self.vram_bank as u16 * 0x2000)) as usize] = data
243            }
244            0xA000..=0xBFFF => self.mbc.write(ctx, addr, data),
245            0xC000..=0xFDFF => {
246                let bank = addr & 0x1000;
247                self.ram[((addr & 0x0FFF) + bank * self.ram_bank as u16) as usize] = data;
248            }
249            0xFE00..=0xFE9F => {
250                if !self.dma.enabled && !ctx.oam_lock() {
251                    ctx.oam_mut()[(addr & 0xff) as usize] = data;
252                }
253            }
254            0xFEA0..=0xFEFF => {
255                // warn!("Write to Unusable address: ${addr:04X} = ${data:02X}")
256            }
257
258            0xFF46 => {
259                // DMA
260                self.dma.source = data;
261                self.dma.pos = 0;
262                self.dma.enabled = false;
263                self.dma.delay = 2;
264            }
265            0xFF50 => self.map_boot_rom = data & 1 == 0, // BANK
266
267            // KEY0 CPU mode register
268            0xFF4C => {
269                if ctx.model().is_cgb() {
270                    let mode = match data.view_bits::<Lsb0>()[2..=3].load::<u8>() {
271                        0 => context::RunningMode::Cgb,
272                        1 => context::RunningMode::Dmg,
273                        2 => context::RunningMode::Pgb1,
274                        3 => context::RunningMode::Pgb2,
275                        _ => unreachable!(),
276                    };
277
278                    ctx.set_running_mode(mode);
279                    debug!("KEY0: CGB mode changed: {mode:?}");
280                } else {
281                    warn!("KEY0 write on non-CGB");
282                }
283            }
284
285            // KEY1 - CGB Mode Only - Prepare Speed Switch
286            0xFF4D => {
287                if ctx.model().is_cgb() {
288                    debug!("KEY1: {data:02X}");
289                    self.prepare_speed_switch = data & 1;
290                } else {
291                    warn!("KEY1 write on non-CGB");
292                }
293            }
294
295            // VBK - CGB Mode Only - VRAM Bank (R/W)
296            0xFF4F => {
297                if ctx.model().is_cgb() {
298                    self.vram_bank = data & 1;
299                } else {
300                    warn!("VBK write on non-CGB");
301                }
302            }
303            // HDMA1 (New DMA Source, High) (W) - CGB Mode Only
304            0xFF51 => {
305                if ctx.model().is_cgb() {
306                    self.hdma.source.view_bits_mut::<Lsb0>()[8..=15].store(data);
307                } else {
308                    warn!("HDMA1 write on non-CGB");
309                }
310            }
311            // HDMA2 (New DMA Source, Low) (W) - CGB Mode Only
312            0xFF52 => {
313                if ctx.model().is_cgb() {
314                    self.hdma.source.view_bits_mut::<Lsb0>()[0..=7].store(data & !0xf);
315                } else {
316                    warn!("HDMA2 write on non-CGB");
317                }
318            }
319            // HDMA3 (New DMA Destination, High) (W) - CGB Mode Only
320            0xFF53 => {
321                if ctx.model().is_cgb() {
322                    self.hdma.dest.view_bits_mut::<Lsb0>()[8..=12].store(data & 0x1f);
323                } else {
324                    warn!("HDMA3 write on non-CGB");
325                }
326            }
327            // HDMA4 (New DMA Destination, Low) (W) - CGB Mode Only
328            0xFF54 => {
329                if ctx.model().is_cgb() {
330                    self.hdma.dest.view_bits_mut::<Lsb0>()[0..=7].store(data & !0xf);
331                } else {
332                    warn!("HDMA4 write on non-CGB");
333                }
334            }
335            // HDMA5 (New DMA Length/Mode/Start) (W) - CGB Mode Only
336            0xFF55 => {
337                if ctx.model().is_cgb() {
338                    let v = data.view_bits::<Lsb0>();
339
340                    assert!(!self.hdma.enabled_general_dma);
341
342                    if self.hdma.enabled_hblank_dma {
343                        assert!(!v[7], "General DMA start on doing HBLANK DMA");
344                        self.hdma.enabled_hblank_dma = false;
345                    } else if v[7] {
346                        // HBlank DMA
347                        self.hdma.enabled_hblank_dma = true;
348                        self.hdma.length = v[0..=6].load();
349                    } else {
350                        // General Purpose DMA
351                        self.hdma.enabled_general_dma = true;
352                        self.hdma.length = v[0..=6].load();
353                    }
354                } else {
355                    warn!("HDMA5 write on non-CGB");
356                }
357            }
358
359            // SVBK - CGB Mode Only - WRAM Bank
360            0xFF70 => {
361                if ctx.model().is_cgb() {
362                    self.ram_bank = max(1, data & 0x7);
363                } else {
364                    warn!("SVBK write on non-CGB");
365                }
366            }
367
368            // Undocumented registers
369            0xFF72 => self.reg_ff72 = data,
370            0xFF73 => self.reg_ff73 = data,
371            0xFF75 => self.reg_ff75.view_bits_mut::<Lsb0>()[4..=6]
372                .store(data.view_bits::<Lsb0>()[4..=6].load::<u8>()),
373
374            0xFF00..=0xFF7F => self.io.write(ctx, addr, data),
375            0xFF80..=0xFFFE => self.hiram[(addr & 0x7f) as usize] = data,
376            0xFFFF => self.io.write(ctx, addr, data),
377        };
378    }
379
380    pub fn io(&mut self) -> &mut Io {
381        &mut self.io
382    }
383
384    pub fn mbc(&mut self) -> &mut Mbc {
385        &mut self.mbc
386    }
387
388    pub fn boot_rom(&self) -> &Option<Vec<u8>> {
389        &self.boot_rom
390    }
391
392    pub fn current_speed(&self) -> u8 {
393        self.current_speed
394    }
395
396    pub fn stop(&mut self) {
397        if self.prepare_speed_switch != 0 {
398            self.prepare_speed_switch = 0;
399            self.switch_delay = 2050;
400        }
401    }
402
403    pub fn tick(&mut self, ctx: &mut impl Context) {
404        self.process_dma(ctx);
405        self.process_hdma(ctx);
406
407        if self.switch_delay > 0 {
408            self.switch_delay -= 1;
409            if self.switch_delay == 0 {
410                info!(
411                    "Switch speed: {} -> {}",
412                    self.current_speed,
413                    self.current_speed ^ 1
414                );
415                self.current_speed ^= 1;
416                ctx.wake();
417            }
418        }
419    }
420
421    fn process_dma(&mut self, ctx: &mut impl Context) {
422        if self.dma.delay > 0 {
423            self.dma.delay -= 1;
424            if self.dma.delay == 0 {
425                self.dma.enabled = true;
426            }
427            return;
428        }
429        if !self.dma.enabled {
430            return;
431        }
432        log::trace!(
433            "<-> DMA:   ${:02X}{:02X} -> $FE{:02X}",
434            self.dma.source,
435            self.dma.pos,
436            self.dma.pos
437        );
438        let data = self.read_(ctx, (self.dma.source as u16) << 8 | self.dma.pos as u16);
439        ctx.oam_mut()[self.dma.pos as usize] = data;
440        self.dma.pos += 1;
441        if self.dma.pos == 0xA0 {
442            self.dma.enabled = false;
443        }
444    }
445
446    fn process_hdma(&mut self, ctx: &mut impl Context) {
447        assert!(!(self.hdma.enabled_general_dma && self.hdma.enabled_hblank_dma));
448
449        let cur_hblank = ctx.mode() == ppu::Mode::Hblank;
450        let enter_hblank = !self.hdma.prev_hblank && cur_hblank;
451        self.hdma.prev_hblank = cur_hblank;
452
453        if self.hdma.enabled_general_dma || (self.hdma.enabled_hblank_dma && enter_hblank) {
454            log::trace!("HDMA: ${:04X} -> ${:04X}", self.hdma.source, self.hdma.dest);
455            for i in 0..16 {
456                let dat = self.read_(ctx, self.hdma.source + i);
457                self.write(ctx, 0x8000 | (self.hdma.dest + i), dat);
458            }
459            self.hdma.source = self.hdma.source.wrapping_add(16);
460            self.hdma.dest = self.hdma.dest.wrapping_add(16);
461
462            let (new_length, ovf) = self.hdma.length.overflowing_sub(1);
463            self.hdma.length = new_length;
464            if ovf || self.hdma.dest >= 0x2000 {
465                self.hdma.enabled_general_dma = false;
466                self.hdma.enabled_hblank_dma = false;
467                self.hdma.dest &= 0x1FFF;
468            }
469
470            ctx.stall_cpu(if self.current_speed == 0 { 8 } else { 16 });
471        }
472    }
473}