Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/cpu/mips_cache_shadow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -541,8 +541,9 @@ impl<
/// instruction lines and 32-byte data lines, a 1 MB secondary with 128-byte
/// lines — 64 TLB entries, MIPS IV, and the R10000 cache operation encodings.
/// Nothing of the microarchitecture: no ways, no LRU, no out-of-order.
/// Reports revision 2.5, matching R10000Cache and enabling the IP28 boot tune.
pub type R10000ShadowCache =
ShadowCache<32768, 64, 32768, 32, 1048576, 128, true, 0x0000_0900, 0x0000_0900, 64, true>;
ShadowCache<32768, 64, 32768, 32, 1048576, 128, true, 0x0000_0925, 0x0000_0900, 64, true>;

#[cfg(test)]
mod tests {
Expand Down
3 changes: 2 additions & 1 deletion src/cpu/mips_cache_v2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1173,10 +1173,11 @@ pub type R5000Cache = CpuCache<32768, 32, 2, 1024,
/// What is *not* faked is anything software reads back: the PRId, the TLB
/// size, MIPS IV decoding, and the cache tag layout the PROM's diagnostics
/// inspect directly.
/// Reports revision 2.5; the IP28 PROM skips its boot tune on pre-2.0 CPUs.
pub type R10000Cache = CpuCache<32768, 64, 1, 512,
32768, 32, 1, 1024, 4096,
1048576, 128, 8192, 131072, 262144, true,
true, 0x0000_0900, 0x0000_0900, 64, { model::R10000 }>;
true, 0x0000_0925, 0x0000_0900, 64, { model::R10000 }>;

impl<const IC_SIZE: usize, const IC_LINE: usize, const IC_WAYS: usize, const IC_TAGS: usize,
const DC_SIZE: usize, const DC_LINE: usize, const DC_WAYS: usize, const DC_TAGS: usize, const DC_DATA: usize,
Expand Down
21 changes: 21 additions & 0 deletions src/cpu/mips_exec_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,27 @@ mod tests {
assert_eq!(core.fpu_fir, 0x0000_2300, "R5000 FIR after reset");
}

/// IP28's play_hello_tune returns before touching HAL2 on pre-2.0 CPUs.
/// Check both cache models and the live CP0 value after construction/reset.
#[test]
fn r10000_revision_allows_ip28_boot_tune() {
fn check<C: crate::cpu::mips_cache_v2::CpuModel + From<Arc<dyn BusDevice>>>() {
let mem: Arc<dyn BusDevice> = Arc::new(MockMemory::new());
let mut exec = MipsExecutor::<PassthroughTlb, C>::new(
mem, PassthroughTlb::default(), &MipsCpuConfig::indy());
for reset in [false, true] {
if reset { exec.core.reset(false); }
// The PROM uses (r4k_getprid() & 0x00f0) < 0x0020.
let prid = exec.core.read_cp0(15);
assert_eq!(prid, 0x0925);
assert!((prid & 0x00f0) >= 0x0020);
assert_eq!(exec.core.fpu_fir, 0x0900);
}
}
check::<crate::cpu::mips_cache_v2::R10000Cache>();
check::<crate::cpu::mips_cache_shadow::R10000ShadowCache>();
}

// Instruction builders
fn make_r(op: u32, rs: u32, rt: u32, rd: u32, sa: u32, funct: u32) -> u32 {
(op << 26) | ((rs & 0x1F) << 21) | ((rt & 0x1F) << 16) | ((rd & 0x1F) << 11) | ((sa & 0x1F) << 6) | (funct & 0x3F)
Expand Down
88 changes: 88 additions & 0 deletions src/dev/hpc3.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1604,6 +1604,18 @@ impl BusDevice for Hpc3 {
fn read8(&self, addr: u32) -> BusRead8 {
let offset = addr - HPC3_BASE;

// IP28's PROM copies the PBUS control bitfield with byte loads.
if offset < 8 * PBUS_DMA_STRIDE {
let idx = (offset / PBUS_DMA_STRIDE) as usize;
let reg = (offset % PBUS_DMA_STRIDE) & !3;
let lane = offset & 3;
// Status/interrupt flags are in the low byte. Reading another
// lane must not acknowledge an interrupt through PbusDmaOps::read.
if reg == HPC3_PDMA_CTRL && lane != 3 { return BusRead8::ok(0); }
let val = self.pdma_ops[idx].read(&mut self.pdma_channels[idx].lock(), reg);
return BusRead8::ok((val >> ((3 - lane) * 8)) as u8);
}

// INT2 (fullhouse only, PBUS PIO channel 4) — forwarded to Ioc's
// BusDevice::read8, which detects the INT2 window itself.
if !self.guinness && (HPC3_INT2_BASE..HPC3_INT2_BASE + HPC3_INT2_SIZE).contains(&offset) {
Expand Down Expand Up @@ -1704,6 +1716,25 @@ impl BusDevice for Hpc3 {
fn write8(&self, addr: u32, val: u8) -> u32 {
let offset = addr - HPC3_BASE;

// IP28's PROM emits four sb instructions for its DMA control struct.
// Use the addressed big-endian byte lane, without reading CTRL back:
// its read status and write strobes have different bit assignments.
if offset < 8 * PBUS_DMA_STRIDE {
let idx = (offset / PBUS_DMA_STRIDE) as usize;
let reg = (offset % PBUS_DMA_STRIDE) & !3;
let lane = offset & 3;
let shift = (3 - lane) * 8;
let mut chan = self.pdma_channels[idx].lock();
match reg {
HPC3_PDMA_CBP => chan.cbp = (chan.cbp & !(0xff << shift)) | ((val as u32) << shift),
HPC3_PDMA_NBDP => chan.nbdp = (chan.nbdp & !(0xff << shift)) | ((val as u32) << shift),
HPC3_PDMA_CTRL if lane == 3 => self.pdma_ops[idx].write(&mut chan, reg, val as u32),
// Upper CTRL lanes configure the FIFO, which is not modeled.
_ => {}
}
return BUS_OK;
}

// INT2 (fullhouse only, PBUS PIO channel 4) — forwarded to Ioc's
// BusDevice::write8, which detects the INT2 window itself.
if !self.guinness && (HPC3_INT2_BASE..HPC3_INT2_BASE + HPC3_INT2_SIZE).contains(&offset) {
Expand Down Expand Up @@ -2303,6 +2334,63 @@ mod tests {
)
}

#[test]
fn ip28_byte_control_writes_start_and_drain_audio_dma() {
struct TuneMemory;
impl BusDevice for TuneMemory {
fn read32(&self, addr: u32) -> BusRead32 {
match addr {
0x1000 => BusRead32::ok(0x2000),
0x1004 => BusRead32::ok(0x8000_0004), // one sample, EOX
0x1008 => BusRead32::ok(0),
0x2000 => BusRead32::ok(0x0012_3400), // PROM: sample << 8
_ => BusRead32::err(),
}
}
}
let hpc = hpc3_for_test();
let base = HPC3_BASE + PBUS_DMA_STRIDE; // Codec A channel 1
hpc.pdma_channels[1].lock().sys_mem = Some(Arc::new(TuneMemory));
hpc.write32(HPC3_BASE + PBUS_CFGDMA_BASE + PBUS_CFGDMA_STRIDE, PBUS_DMACFG_DS16);
hpc.write32(base + PBUS_DMA_DP, 0x1000);
// InitiateDMA's four byte stores, with ACT/ACT_LD in the final lane.
for (lane, byte) in [0x07, 0x04, 0x0b, 0x70].into_iter().enumerate() {
assert_eq!(hpc.write8(base + PBUS_DMA_CTRL + lane as u32, byte), BUS_OK);
assert_eq!(hpc.pdma_channels[1].lock().is_active(), lane == 3);
}
assert_eq!(hpc.read8(base + PBUS_DMA_CTRL + 3).data & 2, 2);
let (sample, status, _) = hpc.pdma_channels[1].lock().dma_read().expect("audio DMA sample");
assert_eq!(sample, 0x1234);
assert!(!status.refused());
assert_eq!(hpc.read8(base + PBUS_DMA_CTRL + 3).data & 2, 0);
assert_eq!(hpc.read32(base + PBUS_DMA_BP).data, 0x2004);
}

#[test]
fn pbus_byte_lanes_preserve_addresses_and_control_side_effects() {
let hpc = hpc3_for_test();
let base = HPC3_BASE + PBUS_DMA_STRIDE;
for reg in [PBUS_DMA_BP, PBUS_DMA_DP] {
hpc.write32(base + reg, 0x1234_5678);
hpc.write8(base + reg + 1, 0xab);
assert_eq!(hpc.read32(base + reg).data, 0x12ab_5678);
for (lane, byte) in [0x12, 0xab, 0x56, 0x78].into_iter().enumerate() {
assert_eq!(hpc.read8(base + reg + lane as u32).data, byte);
}
}
hpc.write8(base + PBUS_DMA_CTRL + 3, PDMA_CTRL_LITTLE as u8);
hpc.pdma_channels[1].lock().ctrl |= PDMA_CTRL_INT;
for lane in 0..3 {
hpc.write8(base + PBUS_DMA_CTRL + lane, 0xff);
assert_eq!(hpc.read8(base + PBUS_DMA_CTRL + lane).data, 0);
let c = hpc.pdma_channels[1].lock();
assert!(c.endian);
assert_ne!(c.ctrl & PDMA_CTRL_INT, 0);
}
assert_eq!(hpc.read8(base + PBUS_DMA_CTRL + 3).data & 1, 1);
assert_eq!(hpc.pdma_channels[1].lock().ctrl & PDMA_CTRL_INT, 0);
}

fn set_latched_flags(hpc3: &Hpc3) {
for &i in SAMPLED.iter() {
let mut c = hpc3.pdma_channels[i].lock();
Expand Down