IOS LLE: execute ARMv5TE enhanced DSP instructions (StarletOS phase 11 I1)
SMLAxy, SMLAWy, SMULWy, SMLALxy, SMULxy, QADD, QSUB, QDADD and QDSUB were decoded as compare data processing that writes no register; the ARM926EJ-S implements them. The interpreter now executes them with the Q flag and the JIT leaves them to it. Adds ARMv5TEEnhancedDSPInstructions. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
ba5b6de0d0
commit
3d12571127
6 files changed
+209
-7
No files matched your search
@@ -1423,6 +1423,16 @@ void ARMCore::ExecuteARM(u32 instruction)
|
||||
return;
|
||||
}
|
||||
|
||||
// ARMv5TE enhanced DSP instructions (ARM Architecture Reference Manual, ARMv5TE): SMLAxy,
|
||||
// SMLAWy, SMULWy, SMLALxy, SMULxy, then QADD, QSUB, QDADD, QDSUB. They sit in the
|
||||
// data-processing space with a compare opcode and S clear, which ARMv5 reserves for such
|
||||
// miscellaneous instructions; decoded as data processing they would write no register.
|
||||
if ((instruction & 0x0f900090) == 0x01000080 || (instruction & 0x0f900ff0) == 0x01000050)
|
||||
{
|
||||
ExecuteEnhancedDSP(instruction);
|
||||
return;
|
||||
}
|
||||
|
||||
if ((instruction & 0x0f8000f0) == 0x00800090)
|
||||
{
|
||||
ExecuteMultiplyLong(instruction);
|
||||
@@ -2327,6 +2337,87 @@ void ARMCore::ExecuteMultiply(u32 instruction)
|
||||
SetNZ(result);
|
||||
}
|
||||
|
||||
void ARMCore::ExecuteEnhancedDSP(u32 instruction)
|
||||
{
|
||||
// StarletOS phase 11 (I1). The ARM926EJ-S implements these; the Q flag (CPSR bit 27) is sticky
|
||||
// and set on signed overflow of an accumulation or on saturation.
|
||||
const auto half = [](u32 value, bool top) {
|
||||
return static_cast<s64>(static_cast<s16>(top ? value >> 16 : value));
|
||||
};
|
||||
const auto saturate = [this](s64 value) {
|
||||
if (value > std::numeric_limits<s32>::max())
|
||||
{
|
||||
m_cpsr |= CPSR_Q;
|
||||
return static_cast<u32>(std::numeric_limits<s32>::max());
|
||||
}
|
||||
if (value < std::numeric_limits<s32>::min())
|
||||
{
|
||||
m_cpsr |= CPSR_Q;
|
||||
return static_cast<u32>(std::numeric_limits<s32>::min());
|
||||
}
|
||||
return static_cast<u32>(static_cast<s32>(value));
|
||||
};
|
||||
const auto accumulate = [this](s64 sum) {
|
||||
if (sum != static_cast<s32>(sum))
|
||||
m_cpsr |= CPSR_Q;
|
||||
return static_cast<u32>(static_cast<s32>(sum));
|
||||
};
|
||||
const u32 op = (instruction >> 21) & 3;
|
||||
const u32 rm = instruction & 0xf;
|
||||
|
||||
if ((instruction & 0xf0) == 0x50)
|
||||
{
|
||||
// QADD (0), QSUB (1), QDADD (2), QDSUB (3): Rd = sat(Rm +/- [sat(2 *)] Rn).
|
||||
const u32 rn = (instruction >> 16) & 0xf;
|
||||
const u32 rd = (instruction >> 12) & 0xf;
|
||||
const s64 m = static_cast<s32>(ReadRegisterOperand(rm));
|
||||
s64 n = static_cast<s32>(ReadRegisterOperand(rn));
|
||||
if ((op & 2) != 0)
|
||||
n = static_cast<s32>(saturate(2 * n));
|
||||
m_registers[rd] = saturate((op & 1) != 0 ? m - n : m + n);
|
||||
return;
|
||||
}
|
||||
|
||||
// SMLAxy (0), SMLAWy/SMULWy (1), SMLALxy (2), SMULxy (3). Rd (RdHi) is bits 19-16, Rn (RdLo)
|
||||
// bits 15-12, Rs bits 11-8; x (bit 5) picks Rm's half, y (bit 6) Rs's half.
|
||||
const u32 rd = (instruction >> 16) & 0xf;
|
||||
const u32 rn = (instruction >> 12) & 0xf;
|
||||
const u32 rs = (instruction >> 8) & 0xf;
|
||||
const bool x = (instruction & (1U << 5)) != 0;
|
||||
const bool y = (instruction & (1U << 6)) != 0;
|
||||
const u32 m = ReadRegisterOperand(rm);
|
||||
const u32 s = ReadRegisterOperand(rs);
|
||||
switch (op)
|
||||
{
|
||||
case 0:
|
||||
m_registers[rd] =
|
||||
accumulate(half(m, x) * half(s, y) + static_cast<s32>(ReadRegisterOperand(rn)));
|
||||
break;
|
||||
case 1:
|
||||
{
|
||||
// The top 32 bits of the 48-bit product of Rm and a halfword of Rs.
|
||||
const s64 product = (static_cast<s64>(static_cast<s32>(m)) * half(s, y)) >> 16;
|
||||
if (x) // SMULWy
|
||||
m_registers[rd] = static_cast<u32>(static_cast<s32>(product));
|
||||
else // SMLAWy
|
||||
m_registers[rd] = accumulate(product + static_cast<s32>(ReadRegisterOperand(rn)));
|
||||
break;
|
||||
}
|
||||
case 2:
|
||||
{
|
||||
const u64 accumulator =
|
||||
(static_cast<u64>(ReadRegisterOperand(rd)) << 32) | ReadRegisterOperand(rn);
|
||||
const u64 result = accumulator + static_cast<u64>(half(m, x) * half(s, y));
|
||||
m_registers[rn] = static_cast<u32>(result);
|
||||
m_registers[rd] = static_cast<u32>(result >> 32);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
m_registers[rd] = static_cast<u32>(static_cast<s32>(half(m, x) * half(s, y)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void ARMCore::ExecuteMultiplyLong(u32 instruction)
|
||||
{
|
||||
const bool signed_multiply = (instruction & (1U << 22)) != 0;
|
||||
|
||||
@@ -315,6 +315,8 @@ private:
|
||||
void TraceWatchedBlockTransfer(u32 address, u32 size, bool load) const;
|
||||
void ExecuteMultiply(u32 instruction);
|
||||
void ExecuteMultiplyLong(u32 instruction);
|
||||
// ARMv5TE signed halfword multiplies and saturating arithmetic.
|
||||
void ExecuteEnhancedDSP(u32 instruction);
|
||||
void ExecuteBranch(u32 instruction);
|
||||
void ExecuteCoprocessor(u32 instruction);
|
||||
void ExecutePSRTransfer(u32 instruction);
|
||||
|
||||
@@ -1164,7 +1164,9 @@ bool ARMJitX64::CanEmitARMDataProcessing(u32 instruction) const
|
||||
if ((instruction & 0x0fff0ff0) == 0x016f0f10 || (instruction & 0x0f8000f0) == 0x00800090 ||
|
||||
(instruction & 0x0fc000f0) == 0x00000090 || (instruction & 0x0fb00ff0) == 0x01000090 ||
|
||||
(instruction & 0x0fbf0fff) == 0x010f0000 || (instruction & 0x0fb0fff0) == 0x0120f000 ||
|
||||
(instruction & 0x0fb0f000) == 0x0320f000 || (instruction & 0x0e000090) == 0x00000090)
|
||||
(instruction & 0x0fb0f000) == 0x0320f000 || (instruction & 0x0e000090) == 0x00000090 ||
|
||||
// ARMv5TE enhanced DSP multiplies and saturating arithmetic: interpreter (StarletOS I1).
|
||||
(instruction & 0x0f900090) == 0x01000080 || (instruction & 0x0f900ff0) == 0x01000050)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1755,6 +1755,83 @@ TEST(StarletARMCore, ARMJitCompilesLongMultiplyFamily)
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(StarletARMCore, ARMv5TEEnhancedDSPInstructions)
|
||||
{
|
||||
// StarletOS phase 11 (I1): encodings from GNU as (-march=armv5te); results from the ARM
|
||||
// Architecture Reference Manual. r0 = Rm, r1 = Rs or Rn, r7 = accumulator, r3/r4 results.
|
||||
struct Case
|
||||
{
|
||||
u32 instruction;
|
||||
u32 r0;
|
||||
u32 r1;
|
||||
u32 r3;
|
||||
u32 r4;
|
||||
u32 r7;
|
||||
u32 expected_r3;
|
||||
u32 expected_r4;
|
||||
bool expected_q;
|
||||
};
|
||||
constexpr std::array cases = {
|
||||
// smlabb r4, r0, r1, r7: a 88-byte entry 9 of a table (Revolution's RAM layer).
|
||||
Case{0xe1047180, 9, 88, 0, 0, 0x13780000, 0, 0x13780318, false},
|
||||
Case{0xe1640180, 0xffff0003, 0x0002fffe, 0, 0, 0, 0, 0xfffffffa, false}, // smulbb
|
||||
Case{0xe16401a0, 0xffff0003, 0x0002fffe, 0, 0, 0, 0, 2, false}, // smultb
|
||||
// smlatt r4, r0, r1, r7: 0x3fff0001 + 0x7fffffff overflows, wraps and sets Q.
|
||||
Case{0xe10471e0, 0x7fff0000, 0x7fff0000, 0, 0, 0x7fffffff, 0, 0xbfff0000, true},
|
||||
Case{0xe1247180, 0x00020000, 0x00000003, 0, 0, 10, 0, 16, false}, // smlawb
|
||||
Case{0xe12401e0, 0xfffe0000, 0x00030000, 0, 0, 0, 0, 0xfffffffa, false}, // smulwt
|
||||
// smlalbb r3, r4, r0, r1: 0x00000000_ffffffff + 1 carries into RdHi.
|
||||
Case{0xe1443180, 1, 1, 0xffffffff, 0, 0, 0, 1, false},
|
||||
Case{0xe1014050, 0x7fffffff, 1, 0, 0, 0, 0, 0x7fffffff, true}, // qadd
|
||||
Case{0xe1214050, 0x80000000, 1, 0, 0, 0, 0, 0x80000000, true}, // qsub
|
||||
Case{0xe1414050, 1, 0x40000000, 0, 0, 0, 0, 0x7fffffff, true}, // qdadd
|
||||
Case{0xe1614050, 0, 3, 0, 0, 0, 0, 0xfffffffa, false}, // qdsub
|
||||
};
|
||||
|
||||
for (const Case& test : cases)
|
||||
{
|
||||
TestBus interpreter_bus;
|
||||
ARMCore interpreter(interpreter_bus);
|
||||
#if defined(_M_X86_64)
|
||||
TestBus jit_bus;
|
||||
ARMCore jit(jit_bus);
|
||||
std::array<ARMCore*, 2> cores = {&interpreter, &jit};
|
||||
jit_bus.WriteARM(0, test.instruction);
|
||||
jit_bus.WriteARM(4, 0xeafffffe); // b .
|
||||
#else
|
||||
std::array<ARMCore*, 1> cores = {&interpreter};
|
||||
#endif
|
||||
interpreter_bus.WriteARM(0, test.instruction);
|
||||
interpreter_bus.WriteARM(4, 0xeafffffe); // b .
|
||||
for (ARMCore* core : cores)
|
||||
{
|
||||
core->SetCPSR(static_cast<u32>(ARMCore::Mode::System));
|
||||
core->SetRegister(0, test.r0);
|
||||
core->SetRegister(1, test.r1);
|
||||
core->SetRegister(3, test.r3);
|
||||
core->SetRegister(4, test.r4);
|
||||
core->SetRegister(7, test.r7);
|
||||
core->SetRegister(15, 0);
|
||||
}
|
||||
#if defined(_M_X86_64)
|
||||
jit.SetJitEnabled(true);
|
||||
#endif
|
||||
for (ARMCore* core : cores)
|
||||
{
|
||||
ASSERT_EQ(core->RunCycles(2), 2u);
|
||||
EXPECT_EQ(core->GetRegister(4), test.expected_r4) << std::hex << test.instruction;
|
||||
if (test.instruction == 0xe1443180)
|
||||
EXPECT_EQ(core->GetRegister(3), test.expected_r3);
|
||||
EXPECT_EQ((core->GetCPSR() & ARMCore::CPSR_Q) != 0, test.expected_q)
|
||||
<< std::hex << test.instruction;
|
||||
// The DSP instruction must not touch the condition flags (it is not a compare).
|
||||
EXPECT_EQ(core->GetCPSR() & (ARMCore::CPSR_N | ARMCore::CPSR_Z | ARMCore::CPSR_C |
|
||||
ARMCore::CPSR_V),
|
||||
0u);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(StarletARMCore, FCSESwitchPreservesTaggedTLBTranslations)
|
||||
{
|
||||
TestBus bus(0x10000);
|
||||
|
||||
@@ -53,18 +53,18 @@
|
||||
},
|
||||
{
|
||||
"path": "Source/Core/Core/IOS/Starlet/ARMCore.cpp",
|
||||
"sha256": "262946f1fb95250d1aa1ca73c9bc27ef5f83511731bf84c765a41b04fe7ff70a",
|
||||
"bytes": 76503
|
||||
"sha256": "2559d69e1ec654734f788312cf5f3dca4de63e22e0880626306e8d15a4585648",
|
||||
"bytes": 80026
|
||||
},
|
||||
{
|
||||
"path": "Source/Core/Core/IOS/Starlet/ARMCore.h",
|
||||
"sha256": "fa80b0d697e19f0aee178be4b13097ad5466f0bbd0cb9b3e7a74876c045b731f",
|
||||
"bytes": 14647
|
||||
"sha256": "c3263bebbbdb7cd1119cc74ad328a63ad969136c1755ccf8e06e6549f23da3aa",
|
||||
"bytes": 14758
|
||||
},
|
||||
{
|
||||
"path": "Source/Core/Core/IOS/Starlet/ARMJitX64.cpp",
|
||||
"sha256": "30ac3e6f74edd40a76406394ed40d66a1ca5b6b9a17b9699a1534d255320108f",
|
||||
"bytes": 109425
|
||||
"sha256": "724382f4f66442d51382d3249bea68620d7417a6c7f48b0c19efa60e942a183c",
|
||||
"bytes": 109615
|
||||
},
|
||||
{
|
||||
"path": "Source/Core/Core/IOS/Starlet/ARMJitX64.h",
|
||||
@@ -189,6 +189,15 @@
|
||||
"Source/Core/Core/IOS/Starlet/StarletMemory.cpp",
|
||||
"Source/Core/Core/IOS/Starlet/StarletMemory.h"
|
||||
]
|
||||
},
|
||||
{
|
||||
"recordedAt": "2026-10-06",
|
||||
"purpose": "StarletOS Phase 11 proposal I1: the ARM interpreter executes the ARMv5TE enhanced DSP instructions (SMLAxy, SMLAWy, SMULWy, SMLALxy, SMULxy, QADD, QSUB, QDADD, QDSUB, Q flag), previously decoded as compare data processing that wrote no register; the JIT leaves them to the interpreter; test ARMv5TEEnhancedDSPInstructions",
|
||||
"files": [
|
||||
"Source/Core/Core/IOS/Starlet/ARMCore.cpp",
|
||||
"Source/Core/Core/IOS/Starlet/ARMCore.h",
|
||||
"Source/Core/Core/IOS/Starlet/ARMJitX64.cpp"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -212,3 +212,24 @@ the one it had just written, with no other Starlet store or device DMA seen.
|
||||
- `ARMCore.cpp` keeps its CRLF bytes. 95 Starlet tests passed. Companion
|
||||
smoke run (P11-H06D, 150 s): no DIFF, no AUDIT, 9 LDM with identical
|
||||
virtual and physical addresses.
|
||||
|
||||
## ARMv5TE enhanced DSP instructions — 2026-10-06
|
||||
|
||||
Companion proposal I1 (approved). The companion's Revolution kernel computes
|
||||
table addresses with `smlabb`; the ARM926EJ-S executes it, but
|
||||
`ARMCore::ExecuteARM` had no case for the ARMv5TE enhanced DSP instructions,
|
||||
so `smlabb` fell into data processing as `TST` (no destination written, flags
|
||||
changed). The kernel then read the wrong RAM-layer entry and refused a
|
||||
creation -106 ("system files corrupted" in the System Menu), in the fork only.
|
||||
|
||||
- `ARMCore::ExecuteEnhancedDSP`: SMLAxy, SMLAWy, SMULWy, SMLALxy, SMULxy,
|
||||
QADD, QSUB, QDADD, QDSUB, with the sticky Q flag (ARM Architecture
|
||||
Reference Manual semantics), decoded before the generic decoders.
|
||||
- `CanEmitARMDataProcessing` excludes these encodings, so the JIT leaves them
|
||||
to the interpreter.
|
||||
- Test `StarletARMCore.ARMv5TEEnhancedDSPInstructions`: eleven encodings from
|
||||
GNU as (`-march=armv5te`), interpreter and JIT, results, Q and NZCV
|
||||
untouched. 96 selected Starlet tests passed.
|
||||
- The companion's images use, besides these (16 `smlabb`, 4 `smulbb`), only
|
||||
`blx`, `clz`, `ldrd`, `strd` among the ARMv5TE additions; those were
|
||||
already handled.
|
||||
Reference in new issue
Block a user