jevsnes.git / third-party / rust / jgenesis / cpu / sh2-emu / src / registers.rs

SH-2 internal I/O registers (accessed using A29-31 = 111)

3use crate::bus::BusInterface;
4use crate::dma::DmaController;
5use crate::sci::SerialInterface;
6use crate::wdt::WatchdogTimer;
7use crate::{RESET_INTERRUPT_MASK, Sh2};
8use bincode::{Decode, Encode};
9use jgenesis_common::num::{GetBit, U16Ext};
11#[derive(Debug, Clone, Copy, Encode, Decode)]
12pub struct StatusRegister {
13    // Interrupt levels <= this value are masked
14    pub interrupt_mask: u8,
15    // Used as a carry/test flag by many instructions
16    pub t: bool,
17    // Saturation flag used by multiply-accumulate instructions
18    pub s: bool,
19    // Flags used by division instructions
20    pub q: bool,
21    pub m: bool,
22}
23
24impl Default for StatusRegister {
25    fn default() -> Self {
26        Self { t: false, s: false, interrupt_mask: RESET_INTERRUPT_MASK, q: false, m: false }
27    }
28}
29
30impl From<u32> for StatusRegister {
31    fn from(value: u32) -> Self {
32        Self {
33            interrupt_mask: ((value >> 4) & 0xF) as u8,
34            t: value.bit(0),
35            s: value.bit(1),
36            q: value.bit(8),
37            m: value.bit(9),
38        }
39    }
40}
41
42impl From<StatusRegister> for u32 {
43    fn from(value: StatusRegister) -> Self {
44        (u32::from(value.m) << 9)
45            | (u32::from(value.q) << 8)
46            | (u32::from(value.interrupt_mask) << 4)
47            | (u32::from(value.s) << 1)
48            | u32::from(value.t)
49    }
50}
51
52#[derive(Debug, Clone, Default, Encode, Decode)]
53pub struct Sh2Registers {
54    // General-purpose registers
55    pub gpr: [u32; 16],
56    // Status register
57    pub sr: StatusRegister,
58    // Global base register (used with GBR addressing modes)
59    pub gbr: u32,
60    // Vector base register (base of exception vector area)
61    pub vbr: u32,
62    // Multiply-accumulator
63    pub macl: u32,
64    pub mach: u32,
65    // Procedure register (return address)
66    pub pr: u32,
67    // Program counter
68    pub pc: u32,
69    pub next_pc: u32,
70    // Set when next_pc is changed by an instruction with a branch delay slot
71    pub next_op_in_delay_slot: bool,
72}
73
74impl Sh2Registers {
75    pub fn mac(&self) -> i64 {
76        (i64::from(self.mach) << 32) | i64::from(self.macl)
77    }
78
79    pub fn set_mac(&mut self, mac: i64) {
80        self.macl = mac as u32;
81        self.mach = ((mac as u64) >> 32) as u32;
82    }
83}

User break functionality is not emulated, but After Burner Complete uses these R/W registers to store state in its audio processing code

87#[derive(Debug, Clone, Default, Encode, Decode)]
88pub struct BreakRegisters {
89    pub break_address_a: u32,
90    pub break_address_b: u32,
91}
93impl BreakRegisters {
94    fn read_break_address_a_high(&self) -> u16 {
95        (self.break_address_a >> 16) as u16
96    }
97
98    fn read_break_address_a_low(&self) -> u16 {
99        self.break_address_a as u16
100    }
101
102    fn read_break_address_b_high(&self) -> u16 {
103        (self.break_address_b >> 16) as u16
104    }
105
106    fn read_break_address_b_low(&self) -> u16 {
107        self.break_address_b as u16
108    }
109
110    fn write_break_address_a(&mut self, value: u32) {
111        self.break_address_a = value;
112        log::trace!("Break address A write: {value:08X}");
113    }
114
115    fn write_break_address_a_high(&mut self, value: u16) {
116        self.break_address_a = (self.break_address_a & 0xFFFF) | (u32::from(value) << 16);
117        log::trace!("Break address A high write: {value:04X}");
118    }
119
120    fn write_break_address_a_low(&mut self, value: u16) {
121        self.break_address_a = (self.break_address_a & !0xFFFF) | u32::from(value);
122        log::trace!("Break address A low write: {value:04X}");
123    }
124
125    fn write_break_address_b(&mut self, value: u32) {
126        self.break_address_b = value;
127        log::trace!("Break address B write: {value:08X}");
128    }
129
130    fn write_break_address_b_high(&mut self, value: u16) {
131        self.break_address_b = (self.break_address_b & 0xFFFF) | (u32::from(value) << 16);
132        log::trace!("Break address B high write: {value:04X}");
133    }
134
135    fn write_break_address_b_low(&mut self, value: u16) {
136        self.break_address_b = (self.break_address_b & !0xFFFF) | u32::from(value);
137        log::trace!("Break address B low write: {value:04X}");
138    }
139}
140
141#[derive(Debug, Clone, Default, Encode, Decode)]
142pub struct InterruptRegisters {
143    pub divu_priority: u8,
144    pub dmac_priority: u8,
145    pub dma0_vector: u8,
146    pub dma1_vector: u8,
147    pub wdt_priority: u8,
148    pub wdt_vector: u8,
149    pub bsc_vector: u8,
150    pub sci_priority: u8,
151    pub sci_rx_error_vector: u8,
152    pub sci_rx_ok_vector: u8,
153    pub sci_tx_empty_vector: u8,
154    pub sci_transfer_end_vector: u8,
155    pub frt_priority: u8,
156}
157
158impl InterruptRegisters {
159    // $FFFFFEE2: IPRA (Interrupt priority A)
160    fn read_ipra(&self) -> u16 {
161        (u16::from(self.divu_priority) << 12)
162            | (u16::from(self.dmac_priority) << 8)
163            | (u16::from(self.wdt_priority) << 4)
164    }
165
166    // $FFFFFEE2: IPRA (Interrupt priority A)
167    fn write_ipra(&mut self, value: u16) {
168        self.divu_priority = (value >> 12) as u8;
169        self.dmac_priority = ((value >> 8) & 0xF) as u8;
170        self.wdt_priority = ((value >> 4) & 0xF) as u8;
171
172        log::debug!("IPRA write: {value:04X}");
173        log::debug!("  DIVU interrupt priority: {}", self.divu_priority);
174        log::debug!("  DMAC interrupt priority: {}", self.dmac_priority);
175        log::debug!("  WDT interrupt priority: {}", self.wdt_priority);
176    }
177
178    fn write_ipra_high(&mut self, value: u8) {
179        self.divu_priority = value >> 4;
180        self.dmac_priority = value & 0xF;
181
182        log::debug!("IPRA high write: {value:02X}");
183        log::debug!("  DIVU interrupt priority: {}", self.divu_priority);
184        log::debug!("  DMAC interrupt priority: {}", self.dmac_priority);
185    }
186
187    fn write_ipra_low(&mut self, value: u8) {
188        self.wdt_priority = value >> 4;
189
190        log::debug!("IPRA low write: {value:02X}");
191        log::debug!("  WDT interrupt priority: {}", self.wdt_priority);
192    }
193
194    // $FFFFFE60: IPRB (Interrupt priority B)
195    fn read_iprb(&self) -> u16 {
196        (u16::from(self.sci_priority) << 12) | (u16::from(self.frt_priority) << 8)
197    }
198
199    // $FFFFFE60: IPRB (Interrupt priority B)
200    fn write_iprb(&mut self, value: u16) {
201        self.sci_priority = (value >> 12) as u8;
202        self.frt_priority = ((value >> 8) & 0xF) as u8;
203
204        log::debug!("IPRB write: {value:04X}");
205        log::debug!("  SCI interrupt priority: {}", self.sci_priority);
206        log::debug!("  FRT interrupt priority: {}", self.frt_priority);
207    }
208
209    fn write_iprb_high(&mut self, value: u8) {
210        // IPRB low byte is not used; just shift the value
211        self.write_iprb(u16::from(value) << 8);
212    }
213
214    // $FFFFFE62: VCRA (Vector number register A)
215    fn read_vcra(&self) -> u16 {
216        (u16::from(self.sci_rx_error_vector) << 8) | u16::from(self.sci_rx_ok_vector)
217    }
218
219    // $FFFFFE62: VCRA (Vector number register A)
220    fn write_vcra(&mut self, value: u16) {
221        self.sci_rx_error_vector = ((value >> 8) & 0x7F) as u8;
222        self.sci_rx_ok_vector = (value & 0x7F) as u8;
223
224        log::debug!("VCRA write: {value:04X}");
225        log::debug!("  SCI RX error vector number: {}", self.sci_rx_error_vector);
226        log::debug!("  SCI RX ok vector number: {}", self.sci_rx_ok_vector);
227    }
228
229    fn write_vcra_high(&mut self, value: u8) {
230        self.sci_rx_error_vector = value & 0x7F;
231
232        log::debug!("VCRA high write: {value:02X}");
233        log::debug!("  SCI RX error vector number: {}", self.sci_rx_error_vector);
234    }
235
236    fn write_vcra_low(&mut self, value: u8) {
237        self.sci_rx_ok_vector = value & 0x7F;
238
239        log::debug!("VCRA low write: {value:02X}");
240        log::debug!("  SCI RX ok vector number: {}", self.sci_rx_ok_vector);
241    }
242
243    // $FFFFFE64: VCRB (Vector number register B)
244    fn read_vcrb(&self) -> u16 {
245        (u16::from(self.sci_tx_empty_vector) << 8) | u16::from(self.sci_transfer_end_vector)
246    }
247
248    // $FFFFFE64: VCRB (Vector number register B)
249    fn write_vcrb(&mut self, value: u16) {
250        self.sci_tx_empty_vector = ((value >> 8) & 0x7F) as u8;
251        self.sci_transfer_end_vector = (value & 0x7F) as u8;
252
253        log::debug!("VCRB write: {value:04X}");
254        log::debug!("  SCI TX empty vector number: {}", self.sci_tx_empty_vector);
255        log::debug!("  SCI transfer end vector number: {}", self.sci_transfer_end_vector);
256    }
257
258    fn write_vcrb_high(&mut self, value: u8) {
259        self.sci_tx_empty_vector = value & 0x7F;
260
261        log::debug!("VCRB high write: {value:02X}");
262        log::debug!("  SCI TX empty vector number: {}", self.sci_tx_empty_vector);
263    }
264
265    fn write_vcrb_low(&mut self, value: u8) {
266        self.sci_transfer_end_vector = value & 0x7F;
267
268        log::debug!("VCRB low write: {value:02X}");
269        log::debug!("  SCI transfer end vector number: {}", self.sci_transfer_end_vector);
270    }
271
272    // $FFFFFEE4: VCRWDT (WDT interrupt vector number)
273    fn read_vcrwdt(&self) -> u16 {
274        (u16::from(self.wdt_vector) << 8) | u16::from(self.bsc_vector)
275    }
276
277    // $FFFFFEE4: VCRWDT (WDT interrupt vector number)
278    fn write_vcrwdt(&mut self, value: u16) {
279        self.wdt_vector = ((value >> 8) & 0x7F) as u8;
280        self.bsc_vector = (value & 0x7F) as u8;
281
282        log::debug!("VCRWDT write: {value:04X}");
283        log::debug!("  WDT interrupt vector number: {}", self.wdt_vector);
284        log::debug!("  BSC interrupt vector number: {}", self.bsc_vector);
285    }
286
287    fn write_vcrwdt_high(&mut self, value: u8) {
288        self.wdt_vector = value & 0x7F;
289
290        log::debug!("VCRWDT high write: {value:02X}");
291        log::debug!("  WDT interrupt vector number: {}", self.wdt_vector);
292    }
293
294    fn write_vcrwdt_low(&mut self, value: u8) {
295        self.bsc_vector = value & 0x7F;
296
297        log::debug!("VCRWDT low write: {value:02X}");
298        log::debug!("  BSC interrupt vector number: {}", self.bsc_vector);
299    }
300
301    // $FFFFFFA0: VCRDMA0 (Interrupt vector number for DMA0)
302    fn write_vcrdma0(&mut self, value: u32) {
303        self.dma0_vector = value as u8;
304
305        log::debug!("VCRDMA0 write: {value:08X}");
306        log::debug!("  DMA0 vector number: {}", self.dma0_vector);
307    }
308
309    // $FFFFFFA8: VCRDMA1 (Interrupt vector number for DMA1)
310    fn write_vcrdma1(&mut self, value: u32) {
311        self.dma1_vector = value as u8;
312
313        log::debug!("VCRDMA1 write: {value:08X}");
314        log::debug!("  DMA1 vector number: {}", self.dma1_vector);
315    }
316}
317
318#[derive(Debug, Clone, Copy, Default, Encode, Decode)]
319pub struct InternalInterrupt {
320    pub priority: u8,
321    pub vector_number: u8,
322}
323
324#[derive(Debug, Clone, Encode, Decode)]
325pub struct Sh7604Registers {
326    pub break_registers: BreakRegisters,
327    pub interrupts: InterruptRegisters,
328    pub internal_interrupt: InternalInterrupt,
329}
330
331impl Sh7604Registers {
332    pub fn new() -> Self {
333        Self {
334            break_registers: BreakRegisters::default(),
335            interrupts: InterruptRegisters::default(),
336            internal_interrupt: InternalInterrupt::default(),
337        }
338    }
339
340    pub fn update_interrupt_level(
341        &mut self,
342        dma_controller: &DmaController,
343        watchdog_timer: &WatchdogTimer,
344        serial: &SerialInterface,
345    ) {
346        self.internal_interrupt = InternalInterrupt::default();
347
348        if self.interrupts.dmac_priority != 0 {
349            if dma_controller.channels[0].control.interrupt_pending() {
350                self.internal_interrupt = InternalInterrupt {
351                    priority: self.interrupts.dmac_priority,
352                    vector_number: self.interrupts.dma0_vector,
353                };
354            } else if dma_controller.channels[1].control.interrupt_pending() {
355                self.internal_interrupt = InternalInterrupt {
356                    priority: self.interrupts.dmac_priority,
357                    vector_number: self.interrupts.dma1_vector,
358                };
359            }
360        }
361
362        if serial.rx_interrupt_pending()
363            && self.interrupts.sci_priority > self.internal_interrupt.priority
364        {
365            self.internal_interrupt = InternalInterrupt {
366                priority: self.interrupts.sci_priority,
367                vector_number: self.interrupts.sci_rx_ok_vector,
368            };
369        }
370
371        if watchdog_timer.overflow_flag()
372            && self.interrupts.wdt_priority > self.internal_interrupt.priority
373        {
374            self.internal_interrupt = InternalInterrupt {
375                priority: self.interrupts.wdt_priority,
376                vector_number: self.interrupts.wdt_vector,
377            };
378        }
379    }
380}

The SH7604 manual says there's a 3-12 cycle latency on FRT accesses, though it's unclear whether this applies to only FRC or to all FRT registers. Applying to all registers fixes some timing issues around line 224 HINT and VINT

385const FRT_WAIT_CYCLES: u64 = 7;
387impl Sh2 {
388    #[allow(clippy::match_same_arms)]
389    pub(super) fn read_internal_register_byte(
390        &self,
391        address: u32,
392        bus: &mut impl BusInterface,
393    ) -> u8 {
394        log::trace!("[{}] Internal register byte read: {address:08X}", self.name);
395
396        match address {
397            0xFFFFFC17 => {
398                // Cosmic Carnage constantly accesses this address - not sure what it's supposed to be
399                0
400            }
401            0xFFFFFE00..=0xFFFFFE05 => self.serial.read_register(address),
402            0xFFFFFE10..=0xFFFFFE19 => {
403                bus.increment_cycle_counter(FRT_WAIT_CYCLES);
404                self.free_run_timer.read_register(address)
405            }
406            0xFFFFFE60 => self.sh7604.interrupts.read_iprb().msb(),
407            0xFFFFFE61 => self.sh7604.interrupts.read_iprb().lsb(),
408            0xFFFFFE62 => self.sh7604.interrupts.read_vcra().msb(),
409            0xFFFFFE63 => self.sh7604.interrupts.read_vcra().lsb(),
410            0xFFFFFE64 => self.sh7604.interrupts.read_vcrb().msb(),
411            0xFFFFFE65 => self.sh7604.interrupts.read_vcrb().lsb(),
412            0xFFFFFE80 => self.watchdog_timer.read_control(),
413            0xFFFFFE81 => self.watchdog_timer.read_counter(),
414            0xFFFFFE92 => self.cache.read_control(),
415            // Unmapped addresses; some games access them for unknown reasons
416            0xFFFFFE93..=0xFFFFFE9F => 0,
417            0xFFFFFEE2 => self.sh7604.interrupts.read_ipra().msb(),
418            0xFFFFFEE3 => self.sh7604.interrupts.read_ipra().lsb(),
419            0xFFFFFEE4 => self.sh7604.interrupts.read_vcrwdt().msb(),
420            0xFFFFFEE5 => self.sh7604.interrupts.read_vcrwdt().lsb(),
421            _ => {
422                log::warn!("[{}] Unexpected internal register byte read: {address:08X}", self.name);
423                0
424            }
425        }
426    }
427
428    pub(super) fn read_internal_register_word(&self, address: u32) -> u16 {
429        log::trace!("[{}] Internal register word read: {address:08X}", self.name);
430
431        match address {
432            0xFFFFFE60 => self.sh7604.interrupts.read_iprb(),
433            0xFFFFFE62 => self.sh7604.interrupts.read_vcra(),
434            0xFFFFFE64 => self.sh7604.interrupts.read_vcrb(),
435            0xFFFFFEE2 => self.sh7604.interrupts.read_ipra(),
436            0xFFFFFEE4 => self.sh7604.interrupts.read_vcrwdt(),
437            0xFFFFFF08 => (self.divu.read_control() >> 16) as u16,
438            0xFFFFFF0A => self.divu.read_control() as u16,
439            0xFFFFFF40 => self.sh7604.break_registers.read_break_address_a_high(),
440            0xFFFFFF42 => self.sh7604.break_registers.read_break_address_a_low(),
441            0xFFFFFF60 => self.sh7604.break_registers.read_break_address_b_high(),
442            0xFFFFFF62 => self.sh7604.break_registers.read_break_address_b_low(),
443            _ => {
444                log::warn!("[{}] Unexpected internal register word read: {address:08X}", self.name);
445                0
446            }
447        }
448    }
449
450    pub(super) fn read_internal_register_longword(&mut self, address: u32) -> u32 {
451        log::trace!("[{}] Internal register longword read: {address:08X}", self.name);
452
453        match address {
454            0xFFFFFF00..=0xFFFFFF1F => self.divu.read_register(address),
455            // Break registers; break functionality is not implemented but some games use the registers as R/W storage
456            0xFFFFFF40 => self.sh7604.break_registers.break_address_a,
457            0xFFFFFF60 => self.sh7604.break_registers.break_address_b,
458            0xFFFFFF80..=0xFFFFFF9F | 0xFFFFFFB0 => self.dmac.read_register(address),
459            0xFFFFFFA0 => self.sh7604.interrupts.dma0_vector.into(),
460            0xFFFFFFA8 => self.sh7604.interrupts.dma1_vector.into(),
461            // Bus control register; not emulated, 32X games only ever write this value to it before reading
462            0xFFFFFFE0 => 0xA55A0001,
463            _ => {
464                log::warn!(
465                    "[{}] Unexpected internal register longword read: {address:08X}",
466                    self.name
467                );
468                0
469            }
470        }
471    }
472
473    #[allow(clippy::match_same_arms)]
474    pub(super) fn write_internal_register_byte(
475        &mut self,
476        address: u32,
477        value: u8,
478        bus: &mut impl BusInterface,
479    ) {
480        log::trace!("[{}] Internal register byte write: {address:08X} {value:02X}", self.name);
481
482        match address {
483            // Cosmic Carnage constantly accesses this address - not sure what it's supposed to be
484            0xFFFFFC17 => {}
485            0xFFFFFE00..=0xFFFFFE05 => self.serial.write_register(address, value),
486            0xFFFFFE10..=0xFFFFFE19 => {
487                bus.increment_cycle_counter(FRT_WAIT_CYCLES);
488                self.free_run_timer.write_register(address, value);
489            }
490            0xFFFFFE60 => self.sh7604.interrupts.write_iprb_high(value),
491            // IPRB low byte; does not do anything
492            0xFFFFFE61 => {}
493            0xFFFFFE62 => self.sh7604.interrupts.write_vcra_high(value),
494            0xFFFFFE63 => self.sh7604.interrupts.write_vcra_low(value),
495            0xFFFFFE64 => self.sh7604.interrupts.write_vcrb_high(value),
496            0xFFFFFE65 => self.sh7604.interrupts.write_vcrb_low(value),
497            // DMA request/response selection control registers; unusual only if a non-zero value is written
498            0xFFFFFE71 | 0xFFFFFE72 => {
499                if value != 0 {
500                    log::warn!(
501                        "[{}] Unexpected DRCR0/DRCR1 write: {address:08X} {value:02X}",
502                        self.name
503                    );
504                }
505            }
506            0xFFFFFE91 => log_standby_control_write(value, &self.name),
507            0xFFFFFE92 => self.cache.write_control(value),
508            // Unmapped addresses; some games access them for unknown reasons
509            0xFFFFFE93..=0xFFFFFE9F => {}
510            0xFFFFFEE2 => self.sh7604.interrupts.write_ipra_high(value),
511            0xFFFFFEE3 => self.sh7604.interrupts.write_ipra_low(value),
512            0xFFFFFEE4 => self.sh7604.interrupts.write_vcrwdt_high(value),
513            0xFFFFFEE5 => self.sh7604.interrupts.write_vcrwdt_low(value),
514            _ => log::warn!(
515                "[{}] Unexpected internal register byte write: {address:08X} {value:02X}",
516                self.name
517            ),
518        }
519
520        self.update_internal_interrupt_level();
521    }
522
523    pub(super) fn write_internal_register_word(&mut self, address: u32, value: u16) {
524        log::trace!("[{}] Internal register word write: {address:08X} {value:04X}", self.name);
525
526        match address {
527            // Writing to this address sets SDRAM 16-bit CAS latency; ignore
528            0xFFFF8446 => {}
529            0xFFFFFE60 => self.sh7604.interrupts.write_iprb(value),
530            0xFFFFFE62 => self.sh7604.interrupts.write_vcra(value),
531            0xFFFFFE64 => self.sh7604.interrupts.write_vcrb(value),
532            0xFFFFFE80 => self.watchdog_timer.write_control(value),
533            0xFFFFFE92 => self.cache.write_control(value as u8),
534            0xFFFFFEE2 => self.sh7604.interrupts.write_ipra(value),
535            0xFFFFFEE4 => self.sh7604.interrupts.write_vcrwdt(value),
536            // DIVU control register is writable 16-bit
537            0xFFFFFF08 => self.divu.write_register(address, value.into()),
538            0xFFFFFF40 => self.sh7604.break_registers.write_break_address_a_high(value),
539            0xFFFFFF42 => self.sh7604.break_registers.write_break_address_a_low(value),
540            0xFFFFFF60 => self.sh7604.break_registers.write_break_address_b_high(value),
541            0xFFFFFF62 => self.sh7604.break_registers.write_break_address_b_low(value),
542            _ => log::warn!(
543                "[{}] Unexpected internal register word write: {address:08X} {value:04X}",
544                self.name
545            ),
546        }
547
548        self.update_internal_interrupt_level();
549    }
550
551    #[allow(clippy::match_same_arms)]
552    pub(super) fn write_internal_register_longword(&mut self, address: u32, value: u32) {
553        log::trace!("[{}] Internal register longword write: {address:08X} {value:08X}", self.name);
554
555        match address {
556            0xFFFFFF00..=0xFFFFFF1F => self.divu.write_register(address, value),
557            0xFFFFFF40 => self.sh7604.break_registers.write_break_address_a(value),
558            // Break bus cycle register A; ignore
559            0xFFFFFF48 => {}
560            0xFFFFFF60 => self.sh7604.break_registers.write_break_address_b(value),
561            // Break bus cycle register B; ignore
562            0xFFFFFF68 => {}
563            0xFFFFFF80..=0xFFFFFF9F | 0xFFFFFFB0 => self.dmac.write_register(address, value),
564            0xFFFFFFA0 => self.sh7604.interrupts.write_vcrdma0(value),
565            0xFFFFFFA8 => self.sh7604.interrupts.write_vcrdma1(value),
566            0xFFFFFFE0..=0xFFFFFFFF => log_bus_control_write(address, value),
567            _ => log::warn!(
568                "[{}] Unexpected internal register longword write: {address:08X} {value:08X}",
569                self.name
570            ),
571        }
572
573        self.update_internal_interrupt_level();
574    }
575}

$FFFFFE91: SBYCR (Standby control register); not emulated

578fn log_standby_control_write(value: u8, name: &str) {
579    log::trace!("[{name}] SBYCR write: {value:02X}");
580    log::trace!("  Standby mode enabled: {}", value.bit(7));
581    log::trace!("  Pins at Hi-Z in standby: {}", value.bit(6));
582    log::trace!("  DMAC clock halted: {}", value.bit(4));
583    log::trace!("  MULT clock halted: {}", value.bit(3));
584    log::trace!("  DIVU clock halted: {}", value.bit(2));
585    log::trace!("  FRT clock halted: {}", value.bit(1));
586    log::trace!("  SCI clock halted: {}", value.bit(0));
587}
589fn log_bus_control_write(address: u32, value: u32) {
590    // TODO actually emulate these registers?
591    match address {
592        0xFFFFFFE0 => {
593            log::trace!("BCR1 write: {value:08X}");
594            log::trace!("  Master mode: {}", !value.bit(15));
595            log::trace!("  Big endian mode: {}", !value.bit(12));
596            log::trace!("  Area 0 burst ROM enabled: {}", value.bit(11));
597            log::trace!("  Partial-share master mode: {}", value.bit(10));
598            log::trace!(
599                "  Long wait specification for areas 2/3: {} waits",
600                ((value >> 8) & 3) + 3
601            );
602            log::trace!("  Long wait specification for area 1: {} waits", ((value >> 6) & 3) + 3);
603            log::trace!("  Long wait specification for area 0: {} waits", ((value >> 4) & 3) + 3);
604            log::trace!("  DRAM specification bits: {}", value & 7);
605        }
606        0xFFFFFFE4 => {
607            log::trace!("BCR2 write: {value:08X}");
608            log::trace!("  Size specification for area 3: {}", bus_area_size(value >> 6));
609            log::trace!("  Size specification for area 2: {}", bus_area_size(value >> 4));
610            log::trace!("  Size specification for area 1: {}", bus_area_size(value >> 2));
611        }
612        0xFFFFFFE8 => {
613            log::trace!("WCR write: {value:08X}");
614            log::trace!("  Idles between cycles for area 3: {}", idle_cycles(value >> 14));
615            log::trace!("  Idles between cycles for area 2: {}", idle_cycles(value >> 12));
616            log::trace!("  Idles between cycles for area 1: {}", idle_cycles(value >> 10));
617            log::trace!("  Idles between cycles for area 0: {}", idle_cycles(value >> 8));
618            log::trace!("  Wait control for area 3: {}", (value >> 6) & 3);
619            log::trace!("  Wait control for area 2: {}", (value >> 4) & 3);
620            log::trace!("  Wait control for area 1: {}", (value >> 2) & 3);
621            log::trace!("  Wait control for area 0: {}", value & 3);
622        }
623        0xFFFFFFEC => {
624            log::trace!("MCR write: {value:08X}");
625            log::trace!("  RAS precharge time: {}", if value.bit(15) { 2 } else { 1 });
626            log::trace!("  RAS-CAS delay: {}", if value.bit(14) { 2 } else { 1 });
627            log::trace!("  Write precharge delay: {}", if value.bit(13) { 2 } else { 1 });
628            log::trace!(
629                "  CAS-before-RAS refresh RAS assert time: {}",
630                match (value >> 11) & 3 {
631                    0 => "2 cycles",
632                    1 => "3 cycles",
633                    2 => "4 cycles",
634                    3 => "(Reserved)",
635                    _ => unreachable!(),
636                }
637            );
638            log::trace!("  Burst enabled: {}", value.bit(10));
639            log::trace!("  RAS down mode enabled: {}", value.bit(9));
640            log::trace!(
641                "  Address multiplexing bits: {}",
642                ((value >> 5) & 0x4) | ((value >> 4) & 0x3)
643            );
644            log::trace!(
645                "  DRAM memory data size: {}",
646                if value.bit(6) { "Longword" } else { "Word" }
647            );
648            log::trace!("  DRAM refresh enabled: {}", value.bit(3));
649            log::trace!("  Self-refresh enabled: {}", value.bit(2));
650        }
651        0xFFFFFFF0 => {
652            log::trace!("RTCSR write: {value:08X}");
653            log::trace!("  Compare match flag: {}", value.bit(7));
654            log::trace!("  Compare match interrupt enabled: {}", value.bit(6));
655            log::trace!("  Clock select bits: {}", (value >> 3) & 7);
656
657            if value.bit(6) {
658                log::error!("SH-2 FRT compare match interrupt was enabled; not emulated");
659            }
660        }
661        0xFFFFFFF4 => {
662            log::trace!("RTCNT write: {value:08X}");
663            log::trace!("  Refresh timer counter: 0x{:02X}", value & 0xFF);
664        }
665        0xFFFFFFF8 => {
666            log::trace!("RTCOR write: {value:08X}");
667            log::trace!("  Refresh time constant for compare: 0x{:02X}", value & 0xFF);
668        }
669        _ => log::warn!("Bus control register write {address:08X} {value:08X}"),
670    }
671}
672
673fn bus_area_size(value: u32) -> &'static str {
674    match value & 3 {
675        0 => "(Reserved)",
676        1 => "Byte",
677        2 => "Word",
678        3 => "Longword",
679        _ => unreachable!("value & 3 is always <= 3"),
680    }
681}
682
683fn idle_cycles(value: u32) -> &'static str {
684    match value & 3 {
685        0 => "0 cycles",
686        1 => "1 cycle",
687        2 => "2 cycles",
688        3 => "(Reserved)",
689        _ => unreachable!("value & 3 is always <= 3"),
690    }
691}