Starlet: keep a matched alarm's timer cause until HW_ALARM is rewritten
A console run (StarOS M3C gate H1, 2026-10-16) acknowledged the timer through HW_ARMIRQFLAG before rewriting HW_ALARM, tens of microseconds after the match, and read the cause still set. MINI's IRQ handler rewrites HW_ALARM first, then acknowledges. The model fired the cause once on the crossing, and a write-one-to-clear ended it for good, so the probe hid the difference. Once the counter reaches HW_ALARM, the timer's cause now stays asserted: an acknowledgement sets it again until HW_ALARM is written. Writing the comparator re-arms it (an immediate match when equal to the counter, as before); a counter write does not end the match. One run does not tell a latch from a "counter >= alarm" comparison: the latch is the smaller model that explains it. The state is saved (STATE_VERSION 197). Tests: StarletTimer.MatchedAlarmStaysAssertedUntilRewritten (both write paths, a counter write, MINI's order, the next match) and MatchedAlarmSurvivesStateRoundTrip. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
6c44367b5c
commit
dd2336f278
4 files changed
+119
-4
No files matched your search
@@ -649,6 +649,96 @@ TEST(StarletInterrupts, FIQMaskWritesResampleTheAttachedCoreLine)
|
||||
EXPECT_FALSE(core.IsFIQLineAsserted());
|
||||
}
|
||||
|
||||
TEST(StarletTimer, MatchedAlarmStaysAssertedUntilRewritten)
|
||||
{
|
||||
constexpr u32 hardware_base = 0x0d800000;
|
||||
constexpr u32 timer = hardware_base + 0x10;
|
||||
constexpr u32 alarm = hardware_base + 0x14;
|
||||
constexpr u32 arm_irq_flag = hardware_base + 0x38;
|
||||
constexpr u32 arm_irq_mask = hardware_base + 0x3c;
|
||||
Core::DeclareAsCPUThread();
|
||||
auto& system = Core::System::GetInstance();
|
||||
auto& ipc = system.GetWiiIPC();
|
||||
ipc.Reset();
|
||||
StarletMemory memory(system);
|
||||
memory.Reset();
|
||||
ARMCore core(memory);
|
||||
memory.AttachInterruptLines(&core);
|
||||
const auto timer_cause = [&ipc] { return ipc.ReadStarletRegister(0x38) & INT_CAUSE_TIMER; };
|
||||
|
||||
memory.Write32(arm_irq_mask, INT_CAUSE_TIMER);
|
||||
memory.Write32(alarm, 64);
|
||||
memory.AdvanceCycles(64 * 128);
|
||||
ASSERT_EQ(memory.Read32(timer), 64u);
|
||||
ASSERT_EQ(timer_cause(), INT_CAUSE_TIMER);
|
||||
core.SetIRQLine(ipc.IsStarletIRQAsserted());
|
||||
ASSERT_TRUE(core.IsIRQLineAsserted());
|
||||
|
||||
// Acknowledged before HW_ALARM is rewritten, after the counter moved on: the
|
||||
// cause and the line stay asserted (the console's StarOS M3C gate H1).
|
||||
memory.AdvanceCycles(32 * 128);
|
||||
memory.Write32(arm_irq_flag, INT_CAUSE_TIMER);
|
||||
EXPECT_EQ(timer_cause(), INT_CAUSE_TIMER);
|
||||
EXPECT_TRUE(core.IsIRQLineAsserted());
|
||||
for (u32 i = 0; i < 4; ++i)
|
||||
memory.Write8(arm_irq_flag + i, static_cast<u8>(INT_CAUSE_TIMER >> (24 - 8 * i)));
|
||||
EXPECT_EQ(timer_cause(), INT_CAUSE_TIMER);
|
||||
|
||||
// A counter write does not end the match either.
|
||||
memory.Write32(timer, 0);
|
||||
memory.Write32(arm_irq_flag, INT_CAUSE_TIMER);
|
||||
EXPECT_EQ(timer_cause(), INT_CAUSE_TIMER);
|
||||
|
||||
// MINI's order: rewrite HW_ALARM ahead, then acknowledge. The rewrite alone
|
||||
// acknowledges nothing; the acknowledgement then clears the cause and the line.
|
||||
memory.Write32(alarm, memory.Read32(timer) + 64);
|
||||
EXPECT_EQ(timer_cause(), INT_CAUSE_TIMER);
|
||||
memory.Write32(arm_irq_flag, INT_CAUSE_TIMER);
|
||||
EXPECT_EQ(timer_cause(), 0u);
|
||||
EXPECT_FALSE(core.IsIRQLineAsserted());
|
||||
|
||||
// The next match latches again.
|
||||
memory.AdvanceCycles(64 * 128);
|
||||
EXPECT_EQ(timer_cause(), INT_CAUSE_TIMER);
|
||||
memory.Write32(arm_irq_flag, INT_CAUSE_TIMER);
|
||||
EXPECT_EQ(timer_cause(), INT_CAUSE_TIMER);
|
||||
}
|
||||
|
||||
TEST(StarletTimer, MatchedAlarmSurvivesStateRoundTrip)
|
||||
{
|
||||
constexpr u32 alarm = 0x0d800014;
|
||||
constexpr u32 arm_irq_flag = 0x0d800038;
|
||||
Core::DeclareAsCPUThread();
|
||||
auto& system = Core::System::GetInstance();
|
||||
auto& ipc = system.GetWiiIPC();
|
||||
ipc.Reset();
|
||||
StarletMemory memory(system);
|
||||
memory.Reset();
|
||||
memory.Write32(alarm, 8);
|
||||
memory.AdvanceCycles(8 * 128);
|
||||
ASSERT_EQ(ipc.ReadStarletRegister(0x38) & INT_CAUSE_TIMER, INT_CAUSE_TIMER);
|
||||
|
||||
std::vector<u8> state_buffer(1024 * 1024);
|
||||
u8* state_pointer = state_buffer.data();
|
||||
PointerWrap writer(&state_pointer, state_buffer.size(), PointerWrap::Mode::Write);
|
||||
memory.DoState(writer);
|
||||
ASSERT_TRUE(writer.IsWriteMode());
|
||||
const size_t state_size = state_pointer - state_buffer.data();
|
||||
|
||||
// A reset forgets the match: the acknowledgement clears the cause.
|
||||
memory.Reset();
|
||||
memory.Write32(arm_irq_flag, INT_CAUSE_TIMER);
|
||||
ASSERT_EQ(ipc.ReadStarletRegister(0x38) & INT_CAUSE_TIMER, 0u);
|
||||
|
||||
// The restored state holds it again.
|
||||
state_pointer = state_buffer.data();
|
||||
PointerWrap reader(&state_pointer, state_size, PointerWrap::Mode::Read);
|
||||
memory.DoState(reader);
|
||||
ASSERT_TRUE(reader.IsReadMode());
|
||||
memory.Write32(arm_irq_flag, INT_CAUSE_TIMER);
|
||||
EXPECT_EQ(ipc.ReadStarletRegister(0x38) & INT_CAUSE_TIMER, INT_CAUSE_TIMER);
|
||||
}
|
||||
|
||||
TEST(StarletInterrupts, AcknowledgingOneOfTwoPendingSourcesKeepsIRQAsserted)
|
||||
{
|
||||
constexpr u32 hardware_base = 0x0d800000;
|
||||
|
||||
Reference in new issue
Block a user