jevsnes.git / third-party / rust / jgenesis / cpu / sh2-emu / src / registers.rs
1//! SH-2 internal I/O registers (accessed using A29-31 = 111)
2
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};
10
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}
84
85// User break functionality is not emulated, but After Burner Complete uses these R/W registers to
86// 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}
92
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}
381
382// The SH7604 manual says there's a 3-12 cycle latency on FRT accesses, though it's unclear whether
383// this applies to only FRC or to all FRT registers.
384// Applying to all registers fixes some timing issues around line 224 HINT and VINT
385const FRT_WAIT_CYCLES: u64 = 7;
386
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}
576
577// $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}
588
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}