mirror of
https://github.com/ares-emulator/ares.git
synced 2026-10-11 22:39:55 +00:00
armv6m: add Cortex-M0 processor component
This commit is contained in:
@@ -30,7 +30,9 @@ add_subdirectory(desktop-ui)
|
||||
option(ARES_BUILD_OPTIONAL_TARGETS "Include supplemental tools and tests" OFF)
|
||||
|
||||
if(ARES_BUILD_OPTIONAL_TARGETS)
|
||||
enable_testing()
|
||||
add_subdirectory(tests/arm7tdmi)
|
||||
add_subdirectory(tests/armv6m)
|
||||
add_subdirectory(tests/i8080)
|
||||
add_subdirectory(tests/m68000)
|
||||
if(HIRO_BACKEND STREQUAL "GTK3")
|
||||
@@ -41,6 +43,7 @@ if(ARES_BUILD_OPTIONAL_TARGETS)
|
||||
add_subdirectory(tools/mame2bml)
|
||||
else()
|
||||
target_disable_subproject(arm7tdmi "arm7tdmi processor test harness")
|
||||
target_disable_subproject(armv6m "armv6m processor test harness")
|
||||
target_disable_subproject(i8080 "i8080 processor test harness")
|
||||
target_disable_subproject(m68000 "m68000 processor test harness")
|
||||
target_disable_subproject(mame2bml "mame2bml (MAME manifest converter)")
|
||||
|
||||
@@ -148,6 +148,19 @@ component_sources(
|
||||
processor/arm7tdmi/serialization.cpp
|
||||
)
|
||||
|
||||
component_sources(
|
||||
COMPONENT
|
||||
armv6m
|
||||
PRIMARY
|
||||
processor/armv6m/armv6m.cpp
|
||||
INCLUDED
|
||||
processor/armv6m/armv6m.hpp
|
||||
processor/armv6m/decoder.cpp
|
||||
processor/armv6m/disassembler.cpp
|
||||
processor/armv6m/instruction.cpp
|
||||
processor/armv6m/serialization.cpp
|
||||
)
|
||||
|
||||
component_sources(
|
||||
COMPONENT
|
||||
gsu
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
#include <ares/ares.hpp>
|
||||
#include "armv6m.hpp"
|
||||
|
||||
namespace ares {
|
||||
|
||||
#include "decoder.cpp"
|
||||
#include "disassembler.cpp"
|
||||
#include "instruction.cpp"
|
||||
#include "serialization.cpp"
|
||||
|
||||
auto ARMv6M::power() -> void {
|
||||
processor = {};
|
||||
opcode = 0;
|
||||
instructionAddress = 0;
|
||||
}
|
||||
|
||||
auto ARMv6M::load(u32 mode, u32 address, n32& data) -> Fault {
|
||||
auto size = mode & Byte ? 1u : mode & Half ? 2u : 4u;
|
||||
if(address & (size - 1)) return Fault::Alignment;
|
||||
n32 value = 0;
|
||||
if(auto error = get(mode | Load, address, value); error != Fault::None) return error;
|
||||
data = value;
|
||||
return Fault::None;
|
||||
}
|
||||
|
||||
auto ARMv6M::store(u32 mode, u32 address, n32 data) -> Fault {
|
||||
auto size = mode & Byte ? 1u : mode & Half ? 2u : 4u;
|
||||
if(address & (size - 1)) return Fault::Alignment;
|
||||
if(auto error = set(mode | Store, address, data); error != Fault::None) return error;
|
||||
return Fault::None;
|
||||
}
|
||||
|
||||
auto ARMv6M::branch(u32 address) -> Fault {
|
||||
if(address == 0xffff'ffff) return Fault::Return;
|
||||
if(!(address & 1)) return Fault::Alignment;
|
||||
r(15) = address & ~1u;
|
||||
return Fault::None;
|
||||
}
|
||||
|
||||
auto ARMv6M::condition(u32 code) const -> bool {
|
||||
switch(code & 15) {
|
||||
case 0x0: return psr().z;
|
||||
case 0x1: return !psr().z;
|
||||
case 0x2: return psr().c;
|
||||
case 0x3: return !psr().c;
|
||||
case 0x4: return psr().n;
|
||||
case 0x5: return !psr().n;
|
||||
case 0x6: return psr().v;
|
||||
case 0x7: return !psr().v;
|
||||
case 0x8: return psr().c && !psr().z;
|
||||
case 0x9: return !psr().c || psr().z;
|
||||
case 0xa: return psr().n == psr().v;
|
||||
case 0xb: return psr().n != psr().v;
|
||||
case 0xc: return !psr().z && psr().n == psr().v;
|
||||
case 0xd: return psr().z || psr().n != psr().v;
|
||||
case 0xe: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
auto ARMv6M::setNZ(u32 value) -> void {
|
||||
psr().n = value >> 31;
|
||||
psr().z = value == 0;
|
||||
}
|
||||
|
||||
auto ARMv6M::add(u32 lhs, u32 rhs, u32 carryIn, bool flags) -> u32 {
|
||||
u64 wide = (u64)lhs + rhs + carryIn;
|
||||
auto result = (u32)wide;
|
||||
if(flags) {
|
||||
setNZ(result);
|
||||
psr().c = wide >> 32;
|
||||
auto signedWide = (i64)(i32)lhs + (i64)(i32)rhs + carryIn;
|
||||
psr().v = signedWide < -0x8000'0000ll || signedWide > 0x7fff'ffffll;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
auto ARMv6M::sub(u32 lhs, u32 rhs, u32 carryIn, bool flags) -> u32 {
|
||||
auto result = lhs - rhs - (1 - carryIn);
|
||||
if(flags) {
|
||||
setNZ(result);
|
||||
psr().c = (u64)lhs >= (u64)rhs + (1 - carryIn);
|
||||
auto signedWide = (i64)(i32)lhs - (i64)(i32)rhs - (1 - carryIn);
|
||||
psr().v = signedWide < -0x8000'0000ll || signedWide > 0x7fff'ffffll;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
auto ARMv6M::shiftLSL(u32 value, u32 amount, bool flags) -> u32 {
|
||||
if(!amount) { if(flags) setNZ(value); return value; }
|
||||
if(amount < 32) { if(flags) psr().c = value >> (32 - amount) & 1; value <<= amount; }
|
||||
else { if(flags) psr().c = amount == 32 ? value & 1 : 0; value = 0; }
|
||||
if(flags) setNZ(value);
|
||||
return value;
|
||||
}
|
||||
|
||||
auto ARMv6M::shiftLSR(u32 value, u32 amount, bool flags) -> u32 {
|
||||
if(!amount) amount = 32;
|
||||
if(amount < 32) { if(flags) psr().c = value >> (amount - 1) & 1; value >>= amount; }
|
||||
else { if(flags) psr().c = amount == 32 ? value >> 31 : 0; value = 0; }
|
||||
if(flags) setNZ(value);
|
||||
return value;
|
||||
}
|
||||
|
||||
auto ARMv6M::shiftASR(u32 value, u32 amount, bool flags) -> u32 {
|
||||
if(!amount) amount = 32;
|
||||
if(amount < 32) {
|
||||
if(flags) psr().c = value >> (amount - 1) & 1;
|
||||
value = (u32)((i32)value >> amount);
|
||||
} else {
|
||||
if(flags) psr().c = value >> 31;
|
||||
value = (i32)value < 0 ? ~0u : 0u;
|
||||
}
|
||||
if(flags) setNZ(value);
|
||||
return value;
|
||||
}
|
||||
|
||||
auto ARMv6M::shiftROR(u32 value, u32 amount, bool flags) -> u32 {
|
||||
if(!amount) { if(flags) setNZ(value); return value; }
|
||||
amount &= 31;
|
||||
if(!amount) { if(flags) psr().c = value >> 31; }
|
||||
else {
|
||||
value = value >> amount | value << (32 - amount);
|
||||
if(flags) psr().c = value >> 31;
|
||||
}
|
||||
if(flags) setNZ(value);
|
||||
return value;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
#pragma once
|
||||
|
||||
//ARMv6-M (Cortex-M0 instruction profile)
|
||||
|
||||
namespace ares {
|
||||
|
||||
struct ARMv6M {
|
||||
enum class Fault : u32 {
|
||||
None,
|
||||
Fetch,
|
||||
Read,
|
||||
Write,
|
||||
Alignment,
|
||||
Undefined,
|
||||
Breakpoint,
|
||||
Suspend,
|
||||
Return,
|
||||
};
|
||||
|
||||
enum Mode : u32 {
|
||||
Load = 1 << 0,
|
||||
Store = 1 << 1,
|
||||
Prefetch = 1 << 2,
|
||||
Byte = 1 << 3,
|
||||
Half = 1 << 4,
|
||||
Word = 1 << 5,
|
||||
Signed = 1 << 6,
|
||||
};
|
||||
|
||||
virtual auto step(u32 clocks) -> void = 0;
|
||||
virtual auto get(u32 mode, n32 address, n32& data) -> Fault = 0;
|
||||
virtual auto getDebugger(u32 mode, n32 address, n32& data) -> Fault { return get(mode, address, data); }
|
||||
virtual auto set(u32 mode, n32 address, n32 data) -> Fault = 0;
|
||||
|
||||
auto power() -> void;
|
||||
auto instruction() -> Fault;
|
||||
auto disassembleInstruction(maybe<n32> pc = {}) -> string;
|
||||
auto disassembleContext() -> string;
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
enum class InstructionFamily : u32 {
|
||||
ShiftImmediate,
|
||||
AddSubtract,
|
||||
Immediate,
|
||||
DataProcessing,
|
||||
HighRegister,
|
||||
LoadLiteral,
|
||||
LoadStoreRegister,
|
||||
LoadStoreImmediate,
|
||||
LoadStoreHalf,
|
||||
LoadStoreStack,
|
||||
AddAddress,
|
||||
AdjustStack,
|
||||
ChangeInterrupt,
|
||||
SetEndian,
|
||||
Extend,
|
||||
Push,
|
||||
Reverse,
|
||||
Breakpoint,
|
||||
Hint,
|
||||
Pop,
|
||||
LoadStoreMultiple,
|
||||
BranchConditional,
|
||||
Branch,
|
||||
BranchLink,
|
||||
Undefined,
|
||||
};
|
||||
|
||||
struct InstructionDecode {
|
||||
InstructionFamily family = InstructionFamily::Undefined;
|
||||
u32 width = 2;
|
||||
bool valid = false;
|
||||
};
|
||||
|
||||
static auto decodeInstruction(n16 prefix, maybe<n16> suffix = {}) -> InstructionDecode;
|
||||
|
||||
struct PSR {
|
||||
b1 n;
|
||||
b1 z;
|
||||
b1 c;
|
||||
b1 v;
|
||||
n8 it;
|
||||
|
||||
auto serialize(serializer&) -> void;
|
||||
};
|
||||
|
||||
struct Processor {
|
||||
n32 r[16];
|
||||
PSR psr;
|
||||
|
||||
auto serialize(serializer&) -> void;
|
||||
} processor;
|
||||
|
||||
auto r(u32 index) -> n32& { return processor.r[index & 15]; }
|
||||
auto r(u32 index) const -> const n32& { return processor.r[index & 15]; }
|
||||
auto psr() -> PSR& { return processor.psr; }
|
||||
auto psr() const -> const PSR& { return processor.psr; }
|
||||
|
||||
protected:
|
||||
auto load(u32 mode, u32 address, n32& data) -> Fault;
|
||||
auto store(u32 mode, u32 address, n32 data) -> Fault;
|
||||
auto branch(u32 address) -> Fault;
|
||||
auto condition(u32 code) const -> bool;
|
||||
auto setNZ(u32 value) -> void;
|
||||
auto add(u32 lhs, u32 rhs, u32 carry, bool flags = true) -> u32;
|
||||
auto sub(u32 lhs, u32 rhs, u32 carry, bool flags = true) -> u32;
|
||||
auto shiftLSL(u32 value, u32 amount, bool flags = true) -> u32;
|
||||
auto shiftLSR(u32 value, u32 amount, bool flags = true) -> u32;
|
||||
auto shiftASR(u32 value, u32 amount, bool flags = true) -> u32;
|
||||
auto shiftROR(u32 value, u32 amount, bool flags = true) -> u32;
|
||||
|
||||
n16 opcode;
|
||||
n32 instructionAddress;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
auto ARMv6M::decodeInstruction(n16 prefix, maybe<n16> suffix) -> InstructionDecode {
|
||||
auto decode = [&](InstructionFamily family, bool valid = true, u32 width = 2) {
|
||||
return InstructionDecode{family, width, valid};
|
||||
};
|
||||
|
||||
//The order is architectural: some Thumb encodings are proper subsets of
|
||||
//later masks. Execution and disassembly both consume this single list.
|
||||
if((prefix & 0xe000) == 0x0000 && (prefix & 0x1800) != 0x1800) return decode(InstructionFamily::ShiftImmediate);
|
||||
if((prefix & 0xf800) == 0x1800) return decode(InstructionFamily::AddSubtract);
|
||||
if((prefix & 0xe000) == 0x2000) return decode(InstructionFamily::Immediate);
|
||||
if((prefix & 0xfc00) == 0x4000) return decode(InstructionFamily::DataProcessing);
|
||||
if((prefix & 0xfc00) == 0x4400) return decode(InstructionFamily::HighRegister);
|
||||
if((prefix & 0xf800) == 0x4800) return decode(InstructionFamily::LoadLiteral);
|
||||
if((prefix & 0xf000) == 0x5000) return decode(InstructionFamily::LoadStoreRegister);
|
||||
if((prefix & 0xe000) == 0x6000) return decode(InstructionFamily::LoadStoreImmediate);
|
||||
if((prefix & 0xf000) == 0x8000) return decode(InstructionFamily::LoadStoreHalf);
|
||||
if((prefix & 0xf000) == 0x9000) return decode(InstructionFamily::LoadStoreStack);
|
||||
if((prefix & 0xf000) == 0xa000) return decode(InstructionFamily::AddAddress);
|
||||
if((prefix & 0xff00) == 0xb000) return decode(InstructionFamily::AdjustStack);
|
||||
if((prefix & 0xffe8) == 0xb660) return decode(InstructionFamily::ChangeInterrupt);
|
||||
if(prefix == 0xb650 || prefix == 0xb658) return decode(InstructionFamily::SetEndian, prefix == 0xb650);
|
||||
if((prefix & 0xff00) == 0xb200) return decode(InstructionFamily::Extend);
|
||||
if((prefix & 0xfe00) == 0xb400) return decode(InstructionFamily::Push);
|
||||
if((prefix & 0xff00) == 0xba00) return decode(InstructionFamily::Reverse, (prefix >> 6 & 3) != 2);
|
||||
if((prefix & 0xff00) == 0xbe00) return decode(InstructionFamily::Breakpoint);
|
||||
if((prefix & 0xff00) == 0xbf00) {
|
||||
auto mask = prefix & 15;
|
||||
auto condition = prefix >> 4 & 15;
|
||||
return decode(InstructionFamily::Hint, !mask || condition < 14);
|
||||
}
|
||||
if((prefix & 0xfe00) == 0xbc00) return decode(InstructionFamily::Pop);
|
||||
if((prefix & 0xf000) == 0xc000) return decode(InstructionFamily::LoadStoreMultiple, (prefix & 0xff) != 0);
|
||||
if((prefix & 0xf000) == 0xd000) return decode(InstructionFamily::BranchConditional, (prefix >> 8 & 15) < 14);
|
||||
if((prefix & 0xf800) == 0xe000) return decode(InstructionFamily::Branch);
|
||||
if((prefix & 0xf800) == 0xf000) {
|
||||
//Without a suffix this still identifies a four-byte family. Supplying a
|
||||
//suffix additionally validates the ARMv6-M BL second-halfword pattern.
|
||||
auto valid = !suffix || ((*suffix & 0xd000) == 0xd000);
|
||||
return decode(InstructionFamily::BranchLink, valid, 4);
|
||||
}
|
||||
return decode(InstructionFamily::Undefined, false);
|
||||
}
|
||||
@@ -0,0 +1,303 @@
|
||||
namespace {
|
||||
|
||||
static const string armv6mRegisterNames[] = {
|
||||
"r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7",
|
||||
"r8", "r9", "r10", "r11", "r12", "sp", "lr", "pc",
|
||||
};
|
||||
|
||||
static const string armv6mConditionNames[] = {
|
||||
"eq", "ne", "cs", "cc", "mi", "pl", "vs", "vc",
|
||||
"hi", "ls", "ge", "lt", "gt", "le", "", "",
|
||||
};
|
||||
|
||||
auto armv6mSignExtend(u32 value, u32 bits) -> i32 {
|
||||
auto shift = 32 - bits;
|
||||
return (i32)(value << shift) >> shift;
|
||||
}
|
||||
|
||||
auto armv6mRegisterList(u32 list, maybe<u32> extra = {}) -> string {
|
||||
string output;
|
||||
for(u32 index : range(8)) {
|
||||
if(list & 1 << index) output.append(armv6mRegisterNames[index], ",");
|
||||
}
|
||||
if(extra) output.append(armv6mRegisterNames[*extra], ",");
|
||||
output.trimRight(",", 1L);
|
||||
return output;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
auto ARMv6M::disassembleInstruction(maybe<n32> pc) -> string {
|
||||
auto address = pc ? (u32)*pc : (u32)r(15);
|
||||
address &= ~1u;
|
||||
|
||||
n32 fetchedPrefix = 0;
|
||||
if(getDebugger(Half, address, fetchedPrefix) != Fault::None) return pad("unavailable", -40L);
|
||||
auto prefix = (u16)fetchedPrefix;
|
||||
auto decoded = decodeInstruction(prefix);
|
||||
|
||||
maybe<n16> suffix;
|
||||
if(decoded.width == 4) {
|
||||
n32 fetchedSuffix = 0;
|
||||
if(getDebugger(Half, address + 2, fetchedSuffix) != Fault::None) return pad("truncated", -40L);
|
||||
suffix = (u16)fetchedSuffix;
|
||||
decoded = decodeInstruction(prefix, suffix);
|
||||
}
|
||||
if(!decoded.valid) return pad("undefined", -40L);
|
||||
|
||||
string output;
|
||||
switch(decoded.family) {
|
||||
case InstructionFamily::ShiftImmediate: {
|
||||
static const string operationNames[] = {"lsl", "lsr", "asr"};
|
||||
auto operation = prefix >> 11 & 3;
|
||||
auto amount = prefix >> 6 & 31;
|
||||
if(operation) amount = amount ? amount : 32;
|
||||
auto source = prefix >> 3 & 7;
|
||||
auto target = prefix & 7;
|
||||
output = {
|
||||
operationNames[operation], " ", armv6mRegisterNames[target],
|
||||
",", armv6mRegisterNames[source], ",#", amount
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
case InstructionFamily::AddSubtract: {
|
||||
auto immediate = prefix >> 10 & 1;
|
||||
auto subtract = prefix >> 9 & 1;
|
||||
auto operand = prefix >> 6 & 7;
|
||||
auto source = prefix >> 3 & 7;
|
||||
auto target = prefix & 7;
|
||||
output = {
|
||||
subtract ? "sub " : "add ", armv6mRegisterNames[target], ",", armv6mRegisterNames[source], ",",
|
||||
immediate ? string{"#", operand} : armv6mRegisterNames[operand]
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
case InstructionFamily::Immediate: {
|
||||
static const string operationNames[] = {"mov", "cmp", "add", "sub"};
|
||||
auto operation = prefix >> 11 & 3;
|
||||
auto target = prefix >> 8 & 7;
|
||||
output = {operationNames[operation], " ", armv6mRegisterNames[target], ",#0x", hex(prefix & 0xff, 2L)};
|
||||
break;
|
||||
}
|
||||
|
||||
case InstructionFamily::DataProcessing: {
|
||||
static const string operationNames[] = {
|
||||
"and", "eor", "lsl", "lsr", "asr", "adc", "sbc", "ror",
|
||||
"tst", "neg", "cmp", "cmn", "orr", "mul", "bic", "mvn",
|
||||
};
|
||||
auto operation = prefix >> 6 & 15;
|
||||
auto source = prefix >> 3 & 7;
|
||||
auto target = prefix & 7;
|
||||
output = {operationNames[operation], " ", armv6mRegisterNames[target], ",", armv6mRegisterNames[source]};
|
||||
break;
|
||||
}
|
||||
|
||||
case InstructionFamily::HighRegister: {
|
||||
auto operation = prefix >> 8 & 3;
|
||||
auto target = (prefix & 7) | (prefix >> 4 & 8);
|
||||
auto source = prefix >> 3 & 15;
|
||||
if(operation == 3) {
|
||||
output = {prefix & 0x0080 ? "blx " : "bx ", armv6mRegisterNames[source]};
|
||||
} else {
|
||||
static const string operationNames[] = {"add", "cmp", "mov"};
|
||||
output = {operationNames[operation], " ", armv6mRegisterNames[target], ",", armv6mRegisterNames[source]};
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case InstructionFamily::LoadLiteral: {
|
||||
auto target = prefix >> 8 & 7;
|
||||
auto literalAddress = (address + 4 & ~3u) + ((prefix & 0xff) << 2);
|
||||
output = {"ldr ", armv6mRegisterNames[target], ",[pc,#0x", hex(literalAddress, 8L), "]"};
|
||||
n32 data = 0;
|
||||
if(getDebugger(Word, literalAddress, data) == Fault::None) output.append(" =0x", hex((u32)data, 8L));
|
||||
break;
|
||||
}
|
||||
|
||||
case InstructionFamily::LoadStoreRegister: {
|
||||
static const string operationNames[] = {"str", "strh", "strb", "ldsb", "ldr", "ldrh", "ldrb", "ldsh"};
|
||||
auto operation = prefix >> 9 & 7;
|
||||
auto offset = prefix >> 6 & 7;
|
||||
auto base = prefix >> 3 & 7;
|
||||
auto target = prefix & 7;
|
||||
output = {
|
||||
operationNames[operation], " ", armv6mRegisterNames[target],
|
||||
",[", armv6mRegisterNames[base], ",", armv6mRegisterNames[offset], "]"
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
case InstructionFamily::LoadStoreImmediate: {
|
||||
auto byte = prefix >> 12 & 1;
|
||||
auto loading = prefix >> 11 & 1;
|
||||
auto immediate = prefix >> 6 & 31;
|
||||
auto base = prefix >> 3 & 7;
|
||||
auto target = prefix & 7;
|
||||
auto offset = byte ? immediate : immediate << 2;
|
||||
output = {
|
||||
loading ? "ldr" : "str", byte ? "b " : " ", armv6mRegisterNames[target],
|
||||
",[", armv6mRegisterNames[base], ",#0x", hex(offset, 2L), "]"
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
case InstructionFamily::LoadStoreHalf: {
|
||||
auto loading = prefix >> 11 & 1;
|
||||
auto offset = (prefix >> 6 & 31) << 1;
|
||||
auto base = prefix >> 3 & 7;
|
||||
auto target = prefix & 7;
|
||||
output = {
|
||||
loading ? "ldrh " : "strh ", armv6mRegisterNames[target],
|
||||
",[", armv6mRegisterNames[base], ",#0x", hex(offset, 2L), "]"
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
case InstructionFamily::LoadStoreStack: {
|
||||
auto loading = prefix >> 11 & 1;
|
||||
auto target = prefix >> 8 & 7;
|
||||
auto offset = (prefix & 0xff) << 2;
|
||||
output = {loading ? "ldr " : "str ", armv6mRegisterNames[target], ",[sp,#0x", hex(offset, 3L), "]"};
|
||||
break;
|
||||
}
|
||||
|
||||
case InstructionFamily::AddAddress: {
|
||||
auto useStack = prefix >> 11 & 1;
|
||||
auto target = prefix >> 8 & 7;
|
||||
auto offset = (prefix & 0xff) << 2;
|
||||
if(useStack) output = {"add ", armv6mRegisterNames[target], ",sp,#0x", hex(offset, 3L)};
|
||||
else output = {"adr ", armv6mRegisterNames[target], ",0x", hex((address + 4 & ~3u) + offset, 8L)};
|
||||
break;
|
||||
}
|
||||
|
||||
case InstructionFamily::AdjustStack: {
|
||||
auto subtract = prefix >> 7 & 1;
|
||||
auto amount = (prefix & 0x7f) << 2;
|
||||
output = {subtract ? "sub" : "add", " sp,#0x", hex(amount, 3L)};
|
||||
break;
|
||||
}
|
||||
|
||||
case InstructionFamily::ChangeInterrupt: {
|
||||
string flags;
|
||||
if(prefix & 2) flags.append("i");
|
||||
if(prefix & 1) flags.append("f");
|
||||
output = {prefix & 0x0010 ? "cpsid" : "cpsie", flags ? string{" ", flags} : string{}};
|
||||
break;
|
||||
}
|
||||
|
||||
case InstructionFamily::SetEndian:
|
||||
output = "setend le";
|
||||
break;
|
||||
|
||||
case InstructionFamily::Extend: {
|
||||
static const string operationNames[] = {"sxth", "sxtb", "uxth", "uxtb"};
|
||||
auto operation = prefix >> 6 & 3;
|
||||
auto source = prefix >> 3 & 7;
|
||||
auto target = prefix & 7;
|
||||
output = {operationNames[operation], " ", armv6mRegisterNames[target], ",", armv6mRegisterNames[source]};
|
||||
break;
|
||||
}
|
||||
|
||||
case InstructionFamily::Push: {
|
||||
maybe<u32> extra;
|
||||
if(prefix & 0x0100) extra = 14;
|
||||
output = {"push {", armv6mRegisterList(prefix & 0xff, extra), "}"};
|
||||
break;
|
||||
}
|
||||
|
||||
case InstructionFamily::Reverse: {
|
||||
static const string operationNames[] = {"rev", "rev16", "", "revsh"};
|
||||
auto operation = prefix >> 6 & 3;
|
||||
auto source = prefix >> 3 & 7;
|
||||
auto target = prefix & 7;
|
||||
output = {operationNames[operation], " ", armv6mRegisterNames[target], ",", armv6mRegisterNames[source]};
|
||||
break;
|
||||
}
|
||||
|
||||
case InstructionFamily::Breakpoint:
|
||||
output = {"bkpt #0x", hex(prefix & 0xff, 2L)};
|
||||
break;
|
||||
|
||||
case InstructionFamily::Hint: {
|
||||
auto mask = prefix & 15;
|
||||
auto condition = prefix >> 4 & 15;
|
||||
if(!mask) {
|
||||
static const string hintNames[] = {"nop", "yield", "wfe", "wfi", "sev"};
|
||||
output = condition < 5 ? hintNames[condition] : string{"hint #0x", hex(condition, 2L)};
|
||||
break;
|
||||
}
|
||||
|
||||
string mnemonic = "it";
|
||||
auto state = (u32)(prefix & 0xff);
|
||||
while(state) {
|
||||
if((state & 7) == 0) state = 0;
|
||||
else state = (state & 0xe0) | ((state << 1) & 0x1f);
|
||||
if(state) mnemonic.append((state >> 4) == condition ? "t" : "e");
|
||||
}
|
||||
output = {mnemonic, " ", armv6mConditionNames[condition]};
|
||||
break;
|
||||
}
|
||||
|
||||
case InstructionFamily::Pop: {
|
||||
maybe<u32> extra;
|
||||
if(prefix & 0x0100) extra = 15;
|
||||
output = {"pop {", armv6mRegisterList(prefix & 0xff, extra), "}"};
|
||||
break;
|
||||
}
|
||||
|
||||
case InstructionFamily::LoadStoreMultiple: {
|
||||
auto loading = prefix >> 11 & 1;
|
||||
auto base = prefix >> 8 & 7;
|
||||
output = {loading ? "ldmia " : "stmia ", armv6mRegisterNames[base], "!,{", armv6mRegisterList(prefix & 0xff), "}"};
|
||||
break;
|
||||
}
|
||||
|
||||
case InstructionFamily::BranchConditional: {
|
||||
auto condition = prefix >> 8 & 15;
|
||||
auto displacement = armv6mSignExtend((prefix & 0xff) << 1, 9);
|
||||
output = {"b", armv6mConditionNames[condition], " 0x", hex(address + 4 + displacement, 8L)};
|
||||
break;
|
||||
}
|
||||
|
||||
case InstructionFamily::Branch: {
|
||||
auto displacement = armv6mSignExtend((prefix & 0x7ff) << 1, 12);
|
||||
output = {"b 0x", hex(address + 4 + displacement, 8L)};
|
||||
break;
|
||||
}
|
||||
|
||||
case InstructionFamily::BranchLink: {
|
||||
auto suffixValue = (u16)*suffix;
|
||||
auto sign = prefix >> 10 & 1;
|
||||
auto j1 = suffixValue >> 13 & 1;
|
||||
auto j2 = suffixValue >> 11 & 1;
|
||||
auto i1 = !(j1 ^ sign);
|
||||
auto i2 = !(j2 ^ sign);
|
||||
auto encoded = sign << 24 | i1 << 23 | i2 << 22
|
||||
| (prefix & 0x03ff) << 12 | (suffixValue & 0x07ff) << 1;
|
||||
auto target = address + 4 + armv6mSignExtend(encoded, 25);
|
||||
output = {"bl 0x", hex(target, 8L)};
|
||||
break;
|
||||
}
|
||||
|
||||
case InstructionFamily::Undefined:
|
||||
return pad("undefined", -40L);
|
||||
}
|
||||
|
||||
return pad(output, -40L);
|
||||
}
|
||||
|
||||
auto ARMv6M::disassembleContext() -> string {
|
||||
string output;
|
||||
for(u32 index : range(16)) {
|
||||
output.append(armv6mRegisterNames[index], ":", hex((u32)r(index), 8L), " ");
|
||||
}
|
||||
output.append("xpsr:");
|
||||
output.append(psr().n ? "N" : "n");
|
||||
output.append(psr().z ? "Z" : "z");
|
||||
output.append(psr().c ? "C" : "c");
|
||||
output.append(psr().v ? "V" : "v");
|
||||
output.append("/it:", hex((u32)psr().it, 2L));
|
||||
return output;
|
||||
}
|
||||
@@ -0,0 +1,340 @@
|
||||
auto ARMv6M::instruction() -> Fault {
|
||||
auto sx = [](u32 value, u32 bits) -> i32 {
|
||||
auto shift = 32 - bits;
|
||||
return (i32)(value << shift) >> shift;
|
||||
};
|
||||
auto reg = [&](u32 index) -> u32 {
|
||||
return index == 15 ? (u32)instructionAddress + 4 : (u32)r(index);
|
||||
};
|
||||
auto writeReg = [&](u32 index, u32 value) -> Fault {
|
||||
//High-register ADD/MOV use ALUWritePC: remain in Thumb state and discard
|
||||
//bit zero. BX/BLX use branch() below and still validate the Thumb bit.
|
||||
if(index == 15) {
|
||||
r(15) = value & ~1u;
|
||||
return Fault::None;
|
||||
}
|
||||
r(index) = value;
|
||||
return Fault::None;
|
||||
};
|
||||
|
||||
instructionAddress = r(15);
|
||||
n32 fetched = 0;
|
||||
if(auto error = get(Prefetch | Half, instructionAddress, fetched); error != Fault::None) return error;
|
||||
opcode = fetched;
|
||||
r(15) += 2;
|
||||
step(1);
|
||||
auto decoded = decodeInstruction(opcode);
|
||||
using Family = InstructionFamily;
|
||||
|
||||
//ARMv6-M keeps the IT condition/mask in xPSR. Advance it before executing
|
||||
//the current slot so all of the early-return decode paths share one rule.
|
||||
auto activeIT = psr().it != 0;
|
||||
auto execute = !activeIT || condition(psr().it >> 4);
|
||||
if(activeIT) {
|
||||
if((psr().it & 7) == 0) psr().it = 0;
|
||||
else psr().it = (psr().it & 0xe0) | ((psr().it << 1) & 0x1f);
|
||||
}
|
||||
if(!execute) {
|
||||
//BL is the only 32-bit ARMv6-M instruction implemented by this core.
|
||||
if(decoded.width == 4) {
|
||||
n32 suffix = 0;
|
||||
if(auto error = get(Prefetch | Half, r(15), suffix); error != Fault::None) return error;
|
||||
r(15) += 2;
|
||||
step(1);
|
||||
}
|
||||
return Fault::None;
|
||||
}
|
||||
|
||||
//Shift by immediate.
|
||||
if(decoded.family == Family::ShiftImmediate) {
|
||||
auto operation = opcode >> 11 & 3;
|
||||
auto amount = opcode >> 6 & 31;
|
||||
auto source = r(opcode >> 3 & 7);
|
||||
auto target = opcode & 7;
|
||||
if(operation == 0) r(target) = shiftLSL(source, amount);
|
||||
if(operation == 1) r(target) = shiftLSR(source, amount);
|
||||
if(operation == 2) r(target) = shiftASR(source, amount);
|
||||
return Fault::None;
|
||||
}
|
||||
|
||||
//Add/subtract register or three-bit immediate.
|
||||
if(decoded.family == Family::AddSubtract) {
|
||||
auto immediate = opcode >> 10 & 1;
|
||||
auto subtract = opcode >> 9 & 1;
|
||||
auto operand = immediate ? (u32)(opcode >> 6 & 7) : (u32)r(opcode >> 6 & 7);
|
||||
auto source = r(opcode >> 3 & 7);
|
||||
auto target = opcode & 7;
|
||||
r(target) = subtract ? sub(source, operand, 1) : add(source, operand, 0);
|
||||
return Fault::None;
|
||||
}
|
||||
|
||||
//Move/compare/add/subtract immediate.
|
||||
if(decoded.family == Family::Immediate) {
|
||||
auto operation = opcode >> 11 & 3;
|
||||
auto target = opcode >> 8 & 7;
|
||||
auto immediate = opcode & 0xff;
|
||||
if(operation == 0) { r(target) = immediate; setNZ(r(target)); }
|
||||
if(operation == 1) sub(r(target), immediate, 1);
|
||||
if(operation == 2) r(target) = add(r(target), immediate, 0);
|
||||
if(operation == 3) r(target) = sub(r(target), immediate, 1);
|
||||
return Fault::None;
|
||||
}
|
||||
|
||||
//Data-processing register.
|
||||
if(decoded.family == Family::DataProcessing) {
|
||||
auto operation = opcode >> 6 & 15;
|
||||
auto source = r(opcode >> 3 & 7);
|
||||
auto target = opcode & 7;
|
||||
auto value = (u32)r(target);
|
||||
switch(operation) {
|
||||
case 0x0: r(target) = value & source; setNZ(r(target)); break;
|
||||
case 0x1: r(target) = value ^ source; setNZ(r(target)); break;
|
||||
case 0x2: r(target) = shiftLSL(value, source & 0xff); break;
|
||||
case 0x3: {
|
||||
auto amount = source & 0xff;
|
||||
r(target) = amount ? shiftLSR(value, amount) : value;
|
||||
if(!amount) setNZ(value);
|
||||
break;
|
||||
}
|
||||
case 0x4: {
|
||||
auto amount = source & 0xff;
|
||||
r(target) = amount ? shiftASR(value, amount) : value;
|
||||
if(!amount) setNZ(value);
|
||||
break;
|
||||
}
|
||||
case 0x5: r(target) = add(value, source, psr().c); break;
|
||||
case 0x6: r(target) = sub(value, source, psr().c); break;
|
||||
case 0x7: r(target) = shiftROR(value, source & 0xff); break;
|
||||
case 0x8: setNZ(value & source); break;
|
||||
case 0x9: r(target) = sub(0, source, 1); break;
|
||||
case 0xa: sub(value, source, 1); break;
|
||||
case 0xb: add(value, source, 0); break;
|
||||
case 0xc: r(target) = value | source; setNZ(r(target)); break;
|
||||
case 0xd: r(target) = value * source; setNZ(r(target)); break;
|
||||
case 0xe: r(target) = value & ~source; setNZ(r(target)); break;
|
||||
case 0xf: r(target) = ~source; setNZ(r(target)); break;
|
||||
}
|
||||
return Fault::None;
|
||||
}
|
||||
|
||||
//High-register operations and branch/exchange.
|
||||
if(decoded.family == Family::HighRegister) {
|
||||
auto operation = opcode >> 8 & 3;
|
||||
auto target = (opcode & 7) | (opcode >> 4 & 8);
|
||||
auto source = opcode >> 3 & 15;
|
||||
if(operation == 0) return writeReg(target, reg(target) + reg(source));
|
||||
if(operation == 1) { sub(reg(target), reg(source), 1); return Fault::None; }
|
||||
if(operation == 2) return writeReg(target, reg(source));
|
||||
auto destination = reg(source);
|
||||
if(opcode & 0x0080) r(14) = (instructionAddress + 2) | 1;
|
||||
return branch(destination);
|
||||
}
|
||||
|
||||
//PC-relative literal load.
|
||||
if(decoded.family == Family::LoadLiteral) {
|
||||
auto target = opcode >> 8 & 7;
|
||||
auto address = (instructionAddress + 4 & ~3u) + ((opcode & 0xff) << 2);
|
||||
return load(Word, address, r(target));
|
||||
}
|
||||
|
||||
//Register-offset loads and stores.
|
||||
if(decoded.family == Family::LoadStoreRegister) {
|
||||
auto operation = opcode >> 9 & 7;
|
||||
auto source = opcode >> 6 & 7;
|
||||
auto base = opcode >> 3 & 7;
|
||||
auto target = opcode & 7;
|
||||
auto address = r(base) + r(source);
|
||||
n32 value = 0;
|
||||
if(operation == 0) return store(Word, address, r(target));
|
||||
if(operation == 1) return store(Half, address, r(target));
|
||||
if(operation == 2) return store(Byte, address, r(target));
|
||||
if(operation == 3) {
|
||||
if(auto error = load(Byte | Signed, address, value); error != Fault::None) return error;
|
||||
r(target) = (i8)value;
|
||||
return Fault::None;
|
||||
}
|
||||
if(operation == 4) return load(Word, address, r(target));
|
||||
if(operation == 5) return load(Half, address, r(target));
|
||||
if(operation == 6) return load(Byte, address, r(target));
|
||||
if(auto error = load(Half | Signed, address, value); error != Fault::None) return error;
|
||||
r(target) = (i16)value;
|
||||
return Fault::None;
|
||||
}
|
||||
|
||||
//Immediate word/byte load/store.
|
||||
if(decoded.family == Family::LoadStoreImmediate) {
|
||||
auto byte = opcode >> 12 & 1;
|
||||
auto loading = opcode >> 11 & 1;
|
||||
auto immediate = opcode >> 6 & 31;
|
||||
auto base = opcode >> 3 & 7;
|
||||
auto target = opcode & 7;
|
||||
auto mode = byte ? Byte : Word;
|
||||
auto address = r(base) + (byte ? immediate : immediate << 2);
|
||||
if(loading) return load(mode, address, r(target));
|
||||
return store(mode, address, r(target));
|
||||
}
|
||||
|
||||
//Immediate halfword load/store.
|
||||
if(decoded.family == Family::LoadStoreHalf) {
|
||||
auto loading = opcode >> 11 & 1;
|
||||
auto address = r(opcode >> 3 & 7) + ((opcode >> 6 & 31) << 1);
|
||||
auto target = opcode & 7;
|
||||
if(loading) return load(Half, address, r(target));
|
||||
return store(Half, address, r(target));
|
||||
}
|
||||
|
||||
//SP-relative word load/store.
|
||||
if(decoded.family == Family::LoadStoreStack) {
|
||||
auto target = opcode >> 8 & 7;
|
||||
auto address = r(13) + ((opcode & 0xff) << 2);
|
||||
if(opcode & 0x0800) return load(Word, address, r(target));
|
||||
return store(Word, address, r(target));
|
||||
}
|
||||
|
||||
//Form address from PC or SP.
|
||||
if(decoded.family == Family::AddAddress) {
|
||||
auto target = opcode >> 8 & 7;
|
||||
auto base = opcode & 0x0800 ? (u32)r(13) : (u32)(instructionAddress + 4) & ~3u;
|
||||
r(target) = base + ((opcode & 0xff) << 2);
|
||||
return Fault::None;
|
||||
}
|
||||
|
||||
//Adjust SP.
|
||||
if(decoded.family == Family::AdjustStack) {
|
||||
auto amount = (opcode & 0x7f) << 2;
|
||||
r(13) = opcode & 0x0080 ? r(13) - amount : r(13) + amount;
|
||||
return Fault::None;
|
||||
}
|
||||
|
||||
//CPSIE/CPSID. The isolated cartridge runtime has no architectural
|
||||
//interrupt sources, but compiler startup code is allowed to change PRIMASK.
|
||||
if(decoded.family == Family::ChangeInterrupt) return Fault::None;
|
||||
|
||||
//The executable format is fixed little-endian. SETEND LE is therefore a
|
||||
//no-op; accepting SETEND BE would make all mapped data semantics ambiguous.
|
||||
if(decoded.family == Family::SetEndian) return decoded.valid ? Fault::None : Fault::Undefined;
|
||||
|
||||
//Sign/zero extension.
|
||||
if(decoded.family == Family::Extend) {
|
||||
auto operation = opcode >> 6 & 3;
|
||||
auto source = r(opcode >> 3 & 7);
|
||||
auto target = opcode & 7;
|
||||
if(operation == 0) r(target) = (i16)source;
|
||||
if(operation == 1) r(target) = (i8)source;
|
||||
if(operation == 2) r(target) = (u16)source;
|
||||
if(operation == 3) r(target) = (u8)source;
|
||||
return Fault::None;
|
||||
}
|
||||
|
||||
//Push registers.
|
||||
if(decoded.family == Family::Push) {
|
||||
auto count = (u32)bit::count(opcode & 0xff) + (opcode >> 8 & 1);
|
||||
auto address = r(13) - count * 4;
|
||||
auto cursor = address;
|
||||
for(u32 index = 0; index < 8; index++) if(opcode & 1 << index) {
|
||||
if(auto error = store(Word, cursor, r(index)); error != Fault::None) return error;
|
||||
cursor += 4;
|
||||
}
|
||||
if(opcode & 0x0100) {
|
||||
if(auto error = store(Word, cursor, r(14)); error != Fault::None) return error;
|
||||
}
|
||||
r(13) = address;
|
||||
return Fault::None;
|
||||
}
|
||||
|
||||
//Byte reversal.
|
||||
if(decoded.family == Family::Reverse) {
|
||||
if(!decoded.valid) return Fault::Undefined;
|
||||
auto operation = opcode >> 6 & 3;
|
||||
auto value = r(opcode >> 3 & 7);
|
||||
auto target = opcode & 7;
|
||||
if(operation == 0) r(target) = bswap32(value);
|
||||
else if(operation == 1) r(target) = (value >> 8 & 0x00ff'00ff) | (value << 8 & 0xff00'ff00);
|
||||
else if(operation == 3) r(target) = (i16)bswap16((u16)value);
|
||||
else return Fault::Undefined;
|
||||
return Fault::None;
|
||||
}
|
||||
|
||||
if(decoded.family == Family::Breakpoint) return Fault::Breakpoint;
|
||||
if(decoded.family == Family::Hint) {
|
||||
if(opcode & 15) {
|
||||
auto code = opcode >> 4 & 15;
|
||||
if(activeIT || !decoded.valid) return Fault::Undefined;
|
||||
psr().it = opcode;
|
||||
}
|
||||
//NOP/YIELD/WFE/WFI/SEV are deterministic no-ops for this isolated runtime.
|
||||
return Fault::None;
|
||||
}
|
||||
|
||||
//Pop registers.
|
||||
if(decoded.family == Family::Pop) {
|
||||
auto address = r(13);
|
||||
for(u32 index = 0; index < 8; index++) if(opcode & 1 << index) {
|
||||
if(auto error = load(Word, address, r(index)); error != Fault::None) return error;
|
||||
address += 4;
|
||||
}
|
||||
if(opcode & 0x0100) {
|
||||
n32 target = 0;
|
||||
if(auto error = load(Word, address, target); error != Fault::None) return error;
|
||||
address += 4;
|
||||
r(13) = address;
|
||||
return branch(target);
|
||||
}
|
||||
r(13) = address;
|
||||
return Fault::None;
|
||||
}
|
||||
|
||||
//Store/load multiple.
|
||||
if(decoded.family == Family::LoadStoreMultiple) {
|
||||
auto loading = opcode >> 11 & 1;
|
||||
auto base = opcode >> 8 & 7;
|
||||
auto list = opcode & 0xff;
|
||||
if(!decoded.valid) return Fault::Undefined;
|
||||
auto address = r(base);
|
||||
for(u32 index = 0; index < 8; index++) if(list & 1 << index) {
|
||||
if(loading) {
|
||||
if(auto error = load(Word, address, r(index)); error != Fault::None) return error;
|
||||
} else {
|
||||
if(auto error = store(Word, address, r(index)); error != Fault::None) return error;
|
||||
}
|
||||
address += 4;
|
||||
}
|
||||
if(!loading || !(list & 1 << base)) r(base) = address;
|
||||
return Fault::None;
|
||||
}
|
||||
|
||||
//Conditional branch and supervisor call.
|
||||
if(decoded.family == Family::BranchConditional) {
|
||||
auto code = opcode >> 8 & 15;
|
||||
if(!decoded.valid) return Fault::Undefined;
|
||||
if(condition(code)) return branch((instructionAddress + 4 + sx((opcode & 0xff) << 1, 9)) | 1);
|
||||
return Fault::None;
|
||||
}
|
||||
|
||||
//Unconditional branch.
|
||||
if(decoded.family == Family::Branch) {
|
||||
return branch((instructionAddress + 4 + sx((opcode & 0x7ff) << 1, 12)) | 1);
|
||||
}
|
||||
|
||||
//BL immediate (ARMv6-M Thumb-1 pair).
|
||||
if(decoded.family == Family::BranchLink) {
|
||||
n32 fetchedSuffix = 0;
|
||||
if(auto error = get(Prefetch | Half, r(15), fetchedSuffix); error != Fault::None) return error;
|
||||
n16 suffix = fetchedSuffix;
|
||||
if(!decodeInstruction(opcode, suffix).valid) return Fault::Undefined;
|
||||
r(15) += 2;
|
||||
step(1);
|
||||
auto sign = opcode >> 10 & 1;
|
||||
auto j1 = suffix >> 13 & 1;
|
||||
auto j2 = suffix >> 11 & 1;
|
||||
auto i1 = !(j1 ^ sign);
|
||||
auto i2 = !(j2 ^ sign);
|
||||
auto encoded = sign << 24 | i1 << 23 | i2 << 22
|
||||
| (opcode & 0x03ff) << 12 | (suffix & 0x07ff) << 1;
|
||||
auto target = instructionAddress + 4 + sx(encoded, 25);
|
||||
r(14) = (instructionAddress + 4) | 1;
|
||||
return branch(target | 1);
|
||||
}
|
||||
|
||||
return Fault::Undefined;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
auto ARMv6M::PSR::serialize(serializer& s) -> void {
|
||||
s(n);
|
||||
s(z);
|
||||
s(c);
|
||||
s(v);
|
||||
s(it);
|
||||
}
|
||||
|
||||
auto ARMv6M::Processor::serialize(serializer& s) -> void {
|
||||
for(auto& value : r) s(value);
|
||||
psr.serialize(s);
|
||||
}
|
||||
|
||||
auto ARMv6M::serialize(serializer& s) -> void {
|
||||
processor.serialize(s);
|
||||
s(opcode);
|
||||
s(instructionAddress);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
if(armv6m IN_LIST ARES_COMPONENTS_LIST)
|
||||
add_executable(armv6m armv6m.cpp)
|
||||
|
||||
target_include_directories(armv6m PRIVATE ${CMAKE_SOURCE_DIR})
|
||||
|
||||
set(ARMV6M_SOURCE_DIR "${CMAKE_SOURCE_DIR}/ares/component/processor/armv6m")
|
||||
|
||||
target_sources(
|
||||
armv6m
|
||||
PRIVATE
|
||||
${ARMV6M_SOURCE_DIR}/armv6m.hpp
|
||||
${ARMV6M_SOURCE_DIR}/decoder.cpp
|
||||
${ARMV6M_SOURCE_DIR}/disassembler.cpp
|
||||
${ARMV6M_SOURCE_DIR}/instruction.cpp
|
||||
${ARMV6M_SOURCE_DIR}/serialization.cpp
|
||||
)
|
||||
|
||||
set_target_properties(armv6m PROPERTIES FOLDER tests PREFIX "")
|
||||
get_target_property(armv6m_SOURCES armv6m SOURCES)
|
||||
list(REMOVE_ITEM armv6m_SOURCES armv6m.cpp)
|
||||
set_source_files_properties(${armv6m_SOURCES} PROPERTIES HEADER_FILE_ONLY TRUE)
|
||||
|
||||
target_enable_subproject(armv6m "armv6m processor test harness")
|
||||
|
||||
target_link_libraries(armv6m PRIVATE ares::ares ares::nall)
|
||||
set(CONSOLE TRUE)
|
||||
ares_configure_executable(armv6m)
|
||||
|
||||
add_test(NAME armv6m-disassembler-contract COMMAND armv6m)
|
||||
source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES armv6m.cpp)
|
||||
endif()
|
||||
@@ -0,0 +1,288 @@
|
||||
#include <nall/nall.hpp>
|
||||
#include <nall/main.hpp>
|
||||
|
||||
#include <ares/ares.hpp>
|
||||
#include <component/processor/armv6m/armv6m.hpp>
|
||||
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
using namespace nall;
|
||||
|
||||
namespace {
|
||||
|
||||
auto expect(bool condition, const char* message) -> bool {
|
||||
if(condition) return true;
|
||||
std::cerr << "FAIL: " << message << "\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
struct Processor : ares::ARMv6M {
|
||||
std::vector<u8> memory = std::vector<u8>(0x20'000);
|
||||
u64 clocks = 0;
|
||||
u32 reads = 0;
|
||||
u32 debuggerReads = 0;
|
||||
u32 writes = 0;
|
||||
|
||||
auto step(u32 amount) -> void override { clocks += amount; }
|
||||
|
||||
auto readMemory(u32 mode, n32 address, n32& data) -> Fault {
|
||||
auto size = mode & Byte ? 1u : mode & Half ? 2u : 4u;
|
||||
auto offset = (u32)address;
|
||||
if(offset > memory.size() || size > memory.size() - offset) return mode & Prefetch ? Fault::Fetch : Fault::Read;
|
||||
data = 0;
|
||||
for(u32 byte = 0; byte < size; byte++) data |= (u32)memory[offset + byte] << byte * 8;
|
||||
return Fault::None;
|
||||
}
|
||||
|
||||
auto get(u32 mode, n32 address, n32& data) -> Fault override {
|
||||
reads++;
|
||||
return readMemory(mode, address, data);
|
||||
}
|
||||
|
||||
auto getDebugger(u32 mode, n32 address, n32& data) -> Fault override {
|
||||
debuggerReads++;
|
||||
return readMemory(mode, address, data);
|
||||
}
|
||||
|
||||
auto set(u32 mode, n32 address, n32 data) -> Fault override {
|
||||
writes++;
|
||||
auto size = mode & Byte ? 1u : mode & Half ? 2u : 4u;
|
||||
auto offset = (u32)address;
|
||||
if(offset > memory.size() || size > memory.size() - offset) return Fault::Write;
|
||||
for(u32 byte = 0; byte < size; byte++) memory[offset + byte] = data >> byte * 8;
|
||||
return Fault::None;
|
||||
}
|
||||
|
||||
auto emit(u32 address, u16 prefix, maybe<u16> suffix = {}) -> void {
|
||||
memory[address + 0] = prefix;
|
||||
memory[address + 1] = prefix >> 8;
|
||||
if(suffix) {
|
||||
memory[address + 2] = *suffix;
|
||||
memory[address + 3] = *suffix >> 8;
|
||||
}
|
||||
}
|
||||
|
||||
auto disassemble(u32 address) -> string {
|
||||
auto output = disassembleInstruction(n32{address});
|
||||
output.trimRight(" ");
|
||||
return output;
|
||||
}
|
||||
};
|
||||
|
||||
using Family = ares::ARMv6M::InstructionFamily;
|
||||
|
||||
auto expectedDecode(u16 opcode) -> ares::ARMv6M::InstructionDecode {
|
||||
auto decode = [&](Family family, bool valid = true, u32 width = 2) {
|
||||
return ares::ARMv6M::InstructionDecode{family, width, valid};
|
||||
};
|
||||
if((opcode & 0xe000) == 0x0000 && (opcode & 0x1800) != 0x1800) return decode(Family::ShiftImmediate);
|
||||
if((opcode & 0xf800) == 0x1800) return decode(Family::AddSubtract);
|
||||
if((opcode & 0xe000) == 0x2000) return decode(Family::Immediate);
|
||||
if((opcode & 0xfc00) == 0x4000) return decode(Family::DataProcessing);
|
||||
if((opcode & 0xfc00) == 0x4400) return decode(Family::HighRegister);
|
||||
if((opcode & 0xf800) == 0x4800) return decode(Family::LoadLiteral);
|
||||
if((opcode & 0xf000) == 0x5000) return decode(Family::LoadStoreRegister);
|
||||
if((opcode & 0xe000) == 0x6000) return decode(Family::LoadStoreImmediate);
|
||||
if((opcode & 0xf000) == 0x8000) return decode(Family::LoadStoreHalf);
|
||||
if((opcode & 0xf000) == 0x9000) return decode(Family::LoadStoreStack);
|
||||
if((opcode & 0xf000) == 0xa000) return decode(Family::AddAddress);
|
||||
if((opcode & 0xff00) == 0xb000) return decode(Family::AdjustStack);
|
||||
if((opcode & 0xffe8) == 0xb660) return decode(Family::ChangeInterrupt);
|
||||
if(opcode == 0xb650 || opcode == 0xb658) return decode(Family::SetEndian, opcode == 0xb650);
|
||||
if((opcode & 0xff00) == 0xb200) return decode(Family::Extend);
|
||||
if((opcode & 0xfe00) == 0xb400) return decode(Family::Push);
|
||||
if((opcode & 0xff00) == 0xba00) return decode(Family::Reverse, (opcode >> 6 & 3) != 2);
|
||||
if((opcode & 0xff00) == 0xbe00) return decode(Family::Breakpoint);
|
||||
if((opcode & 0xff00) == 0xbf00) return decode(Family::Hint, !(opcode & 15) || (opcode >> 4 & 15) < 14);
|
||||
if((opcode & 0xfe00) == 0xbc00) return decode(Family::Pop);
|
||||
if((opcode & 0xf000) == 0xc000) return decode(Family::LoadStoreMultiple, (opcode & 0xff) != 0);
|
||||
if((opcode & 0xf000) == 0xd000) return decode(Family::BranchConditional, (opcode >> 8 & 15) < 14);
|
||||
if((opcode & 0xf800) == 0xe000) return decode(Family::Branch);
|
||||
if((opcode & 0xf800) == 0xf000) return decode(Family::BranchLink, true, 4);
|
||||
return decode(Family::Undefined, false);
|
||||
}
|
||||
|
||||
auto classifierContract() -> bool {
|
||||
bool passed = true;
|
||||
u32 recognized = 0;
|
||||
for(u32 value = 0; value <= 0xffff; value++) {
|
||||
auto expected = expectedDecode(value);
|
||||
auto actual = ares::ARMv6M::decodeInstruction((u16)value);
|
||||
if(expected.family != Family::Undefined) recognized++;
|
||||
if(actual.family != expected.family || actual.width != expected.width || actual.valid != expected.valid) {
|
||||
std::cerr << "FAIL: classifier diverged at 0x" << std::hex << value << "\n";
|
||||
passed = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
passed &= expect(recognized == 59'666, "recognized first-halfword count diverged");
|
||||
auto validBL = ares::ARMv6M::decodeInstruction(0xf000, n16{0xf800});
|
||||
auto invalidBL = ares::ARMv6M::decodeInstruction(0xf000, n16{0x0000});
|
||||
passed &= expect(validBL.family == Family::BranchLink && validBL.width == 4 && validBL.valid,
|
||||
"valid BL suffix was rejected");
|
||||
passed &= expect(invalidBL.family == Family::BranchLink && invalidBL.width == 4 && !invalidBL.valid,
|
||||
"invalid BL suffix was accepted");
|
||||
return passed;
|
||||
}
|
||||
|
||||
auto disassemblyCoverageContract() -> bool {
|
||||
Processor cpu;
|
||||
bool passed = true;
|
||||
for(u32 value = 0; value <= 0xffff; value++) {
|
||||
auto decoded = ares::ARMv6M::decodeInstruction((u16)value);
|
||||
cpu.emit(0x1000, value, decoded.width == 4 ? maybe<u16>{0xf800} : maybe<u16>{});
|
||||
auto output = cpu.disassemble(0x1000);
|
||||
auto placeholder = output == "undefined" || output == "unavailable" || output == "truncated" || !output;
|
||||
if(placeholder == decoded.valid) {
|
||||
std::cerr << "FAIL: disassembly coverage diverged at 0x" << std::hex << value
|
||||
<< " with text '" << output.data() << "'\n";
|
||||
passed = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return passed;
|
||||
}
|
||||
|
||||
auto goldenDisassemblyContract() -> bool {
|
||||
struct Case { u16 prefix; u16 suffix; bool hasSuffix; const char* expected; };
|
||||
static const Case cases[] = {
|
||||
{0x0008, 0, false, "lsl r0,r1,#0"}, {0x0808, 0, false, "lsr r0,r1,#32"},
|
||||
{0x1008, 0, false, "asr r0,r1,#32"}, {0x1888, 0, false, "add r0,r1,r2"},
|
||||
{0x1ec8, 0, false, "sub r0,r1,#3"}, {0x2005, 0, false, "mov r0,#0x05"},
|
||||
{0x2805, 0, false, "cmp r0,#0x05"}, {0x3005, 0, false, "add r0,#0x05"},
|
||||
{0x3805, 0, false, "sub r0,#0x05"}, {0x4408, 0, false, "add r0,r1"},
|
||||
{0x4508, 0, false, "cmp r0,r1"}, {0x4608, 0, false, "mov r0,r1"},
|
||||
{0x46f7, 0, false, "mov pc,lr"}, {0x4708, 0, false, "bx r1"},
|
||||
{0x4770, 0, false, "bx lr"}, {0x4788, 0, false, "blx r1"},
|
||||
{0x5090, 0, false, "str r0,[r2,r2]"}, {0x5290, 0, false, "strh r0,[r2,r2]"},
|
||||
{0x5490, 0, false, "strb r0,[r2,r2]"}, {0x5690, 0, false, "ldsb r0,[r2,r2]"},
|
||||
{0x5890, 0, false, "ldr r0,[r2,r2]"}, {0x5a90, 0, false, "ldrh r0,[r2,r2]"},
|
||||
{0x5c90, 0, false, "ldrb r0,[r2,r2]"}, {0x5e90, 0, false, "ldsh r0,[r2,r2]"},
|
||||
{0x6048, 0, false, "str r0,[r1,#0x04]"}, {0x6848, 0, false, "ldr r0,[r1,#0x04]"},
|
||||
{0x7048, 0, false, "strb r0,[r1,#0x01]"}, {0x7848, 0, false, "ldrb r0,[r1,#0x01]"},
|
||||
{0x8048, 0, false, "strh r0,[r1,#0x02]"}, {0x8848, 0, false, "ldrh r0,[r1,#0x02]"},
|
||||
{0x9001, 0, false, "str r0,[sp,#0x004]"}, {0x9801, 0, false, "ldr r0,[sp,#0x004]"},
|
||||
{0xa001, 0, false, "adr r0,0x00001008"}, {0xa801, 0, false, "add r0,sp,#0x004"},
|
||||
{0xb001, 0, false, "add sp,#0x004"}, {0xb081, 0, false, "sub sp,#0x004"},
|
||||
{0xb662, 0, false, "cpsie i"}, {0xb672, 0, false, "cpsid i"},
|
||||
{0xb650, 0, false, "setend le"}, {0xb208, 0, false, "sxth r0,r1"},
|
||||
{0xb248, 0, false, "sxtb r0,r1"}, {0xb288, 0, false, "uxth r0,r1"},
|
||||
{0xb2c8, 0, false, "uxtb r0,r1"}, {0xb503, 0, false, "push {r0,r1,lr}"},
|
||||
{0xba08, 0, false, "rev r0,r1"}, {0xba48, 0, false, "rev16 r0,r1"},
|
||||
{0xbac8, 0, false, "revsh r0,r1"}, {0xbe5a, 0, false, "bkpt #0x5a"},
|
||||
{0xbf00, 0, false, "nop"}, {0xbf10, 0, false, "yield"},
|
||||
{0xbf20, 0, false, "wfe"}, {0xbf30, 0, false, "wfi"},
|
||||
{0xbf40, 0, false, "sev"}, {0xbf50, 0, false, "hint #0x05"},
|
||||
{0xbf08, 0, false, "it eq"}, {0xbf04, 0, false, "itt eq"},
|
||||
{0xbf0c, 0, false, "ite eq"}, {0xbd03, 0, false, "pop {r0,r1,pc}"},
|
||||
{0xc203, 0, false, "stmia r2!,{r0,r1}"}, {0xca18, 0, false, "ldmia r2!,{r3,r4}"},
|
||||
{0xd001, 0, false, "beq 0x00001006"}, {0xd0ff, 0, false, "beq 0x00001002"},
|
||||
{0xe000, 0, false, "b 0x00001004"}, {0xe7ff, 0, false, "b 0x00001002"},
|
||||
{0xf000, 0xf800, true, "bl 0x00001004"}, {0xf7ff, 0xfffe, true, "bl 0x00001000"},
|
||||
{0xb658, 0, false, "undefined"}, {0xba80, 0, false, "undefined"},
|
||||
{0xc000, 0, false, "undefined"}, {0xde00, 0, false, "undefined"},
|
||||
{0xdf00, 0, false, "undefined"}, {0xe800, 0, false, "undefined"},
|
||||
{0xf000, 0x0000, true, "undefined"},
|
||||
};
|
||||
|
||||
static const char* aluNames[] = {
|
||||
"and", "eor", "lsl", "lsr", "asr", "adc", "sbc", "ror",
|
||||
"tst", "neg", "cmp", "cmn", "orr", "mul", "bic", "mvn",
|
||||
};
|
||||
|
||||
bool passed = true;
|
||||
for(auto test : cases) {
|
||||
Processor cpu;
|
||||
cpu.emit(0x1000, test.prefix, test.hasSuffix ? maybe<u16>{test.suffix} : maybe<u16>{});
|
||||
auto actual = cpu.disassemble(0x1000);
|
||||
if(actual != test.expected) {
|
||||
std::cerr << "FAIL: 0x" << std::hex << test.prefix << " expected '" << test.expected
|
||||
<< "' got '" << actual.data() << "'\n";
|
||||
passed = false;
|
||||
}
|
||||
}
|
||||
|
||||
for(u32 operation = 0; operation < 16; operation++) {
|
||||
Processor cpu;
|
||||
cpu.emit(0x1000, 0x4008 | operation << 6);
|
||||
string expected{aluNames[operation], " r0,r1"};
|
||||
passed &= expect(cpu.disassemble(0x1000) == expected, "data-processing mnemonic diverged");
|
||||
}
|
||||
|
||||
Processor literal;
|
||||
literal.emit(0x1000, 0x4801);
|
||||
literal.memory[0x1008] = 0x12;
|
||||
literal.memory[0x1009] = 0x34;
|
||||
literal.memory[0x100a] = 0x56;
|
||||
literal.memory[0x100b] = 0x78;
|
||||
passed &= expect(literal.disassemble(0x1000) == "ldr r0,[pc,#0x00001008] =0x78563412",
|
||||
"literal address or debugger value formatting diverged");
|
||||
|
||||
Processor aligned;
|
||||
aligned.emit(0x1000, 0xa001);
|
||||
passed &= expect(aligned.disassemble(0x1001) == "adr r0,0x00001008",
|
||||
"odd requested PC was not aligned before decoding");
|
||||
return passed;
|
||||
}
|
||||
|
||||
auto observationContract() -> bool {
|
||||
Processor cpu;
|
||||
cpu.power();
|
||||
cpu.emit(0x1000, 0x2005);
|
||||
cpu.r(15) = 0x1000;
|
||||
for(u32 index = 0; index < 16; index++) cpu.r(index) = 0x1111'0000 + index;
|
||||
cpu.r(15) = 0x1000;
|
||||
cpu.psr().n = 1;
|
||||
cpu.psr().z = 0;
|
||||
cpu.psr().c = 1;
|
||||
cpu.psr().v = 0;
|
||||
cpu.psr().it = 0x18;
|
||||
|
||||
serializer before;
|
||||
cpu.serialize(before);
|
||||
std::vector<u8> stateBefore(before.data(), before.data() + before.size());
|
||||
auto memoryBefore = cpu.memory;
|
||||
auto context = cpu.disassembleContext();
|
||||
auto instruction = cpu.disassembleInstruction();
|
||||
serializer after;
|
||||
cpu.serialize(after);
|
||||
|
||||
bool passed = true;
|
||||
passed &= expect(context ==
|
||||
"r0:11110000 r1:11110001 r2:11110002 r3:11110003 r4:11110004 r5:11110005 r6:11110006 r7:11110007 "
|
||||
"r8:11110008 r9:11110009 r10:1111000a r11:1111000b r12:1111000c sp:1111000d lr:1111000e pc:00001000 "
|
||||
"xpsr:NzCv/it:18", "context formatting diverged");
|
||||
passed &= expect(instruction.beginsWith("mov r0,#0x05"), "default-PC disassembly diverged");
|
||||
passed &= expect(cpu.reads == 0 && cpu.writes == 0 && cpu.clocks == 0,
|
||||
"disassembly used execution callbacks or advanced time");
|
||||
passed &= expect(cpu.debuggerReads == 1, "disassembly did not use exactly one debugger read");
|
||||
passed &= expect(cpu.memory == memoryBefore, "disassembly changed memory");
|
||||
passed &= expect(after.size() == stateBefore.size()
|
||||
&& !std::memcmp(after.data(), stateBefore.data(), stateBefore.size()), "disassembly changed serialized CPU state");
|
||||
|
||||
Processor unavailable;
|
||||
unavailable.memory.resize(0x1000);
|
||||
passed &= expect(unavailable.disassemble(0x1000) == "unavailable", "unmapped fetch text diverged");
|
||||
Processor truncated;
|
||||
truncated.memory.resize(0x1002);
|
||||
truncated.emit(0x1000, 0xf000);
|
||||
passed &= expect(truncated.disassemble(0x1000) == "truncated", "truncated BL text diverged");
|
||||
return passed;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
auto nall::main(Arguments) -> void {
|
||||
bool passed = true;
|
||||
passed &= classifierContract();
|
||||
passed &= disassemblyCoverageContract();
|
||||
passed &= goldenDisassemblyContract();
|
||||
passed &= observationContract();
|
||||
if(passed) std::cout << "PASS: ARMv6-M decode, disassembly, context, and observation contracts\n";
|
||||
std::cout.flush();
|
||||
std::cerr.flush();
|
||||
std::_Exit(passed ? 0 : 1);
|
||||
}
|
||||
Reference in New Issue
Block a user