armv6m: add Cortex-M0 processor component

This commit is contained in:
ZengGengSen
2026-08-30 19:24:24 +01:00
committed by Luke Usher
parent 6495f9ea0f
commit 742cc93106
10 changed files with 1284 additions and 0 deletions
+3
View File
@@ -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)")
+13
View File
@@ -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
+130
View File
@@ -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;
}
}
+116
View File
@@ -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);
}
+31
View File
@@ -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()
+288
View File
@@ -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);
}