tests: add execution tests
all but data processing Signed-off-by: Amneesh Singh <natto@weirdnatto.in>
This commit is contained in:
590
src/cpu/arm/exec.cc
Normal file
590
src/cpu/arm/exec.cc
Normal file
@@ -0,0 +1,590 @@
|
||||
#include "cpu/cpu-impl.hh"
|
||||
#include "util/bits.hh"
|
||||
#include "util/log.hh"
|
||||
|
||||
using namespace logger;
|
||||
|
||||
void
|
||||
CpuImpl::exec_arm(const arm::Instruction instruction) {
|
||||
auto cond = instruction.condition;
|
||||
auto data = instruction.data;
|
||||
|
||||
if (!cpsr.condition(cond)) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto pc_error = [](uint8_t r) {
|
||||
if (r == PC_INDEX)
|
||||
log_error("Using PC (R15) as operand register");
|
||||
};
|
||||
|
||||
auto pc_warn = [](uint8_t r) {
|
||||
if (r == PC_INDEX)
|
||||
log_warn("Using PC (R15) as operand register");
|
||||
};
|
||||
|
||||
using namespace arm;
|
||||
|
||||
std::visit(
|
||||
overloaded{
|
||||
[this, pc_warn](BranchAndExchange& data) {
|
||||
State state = static_cast<State>(data.rn & 1);
|
||||
|
||||
pc_warn(data.rn);
|
||||
|
||||
// set state
|
||||
cpsr.set_state(state);
|
||||
|
||||
// copy to PC
|
||||
pc = gpr[data.rn];
|
||||
|
||||
// ignore [1:0] bits for arm and 0 bit for thumb
|
||||
rst_bit(pc, 0);
|
||||
|
||||
if (state == State::Arm)
|
||||
rst_bit(pc, 1);
|
||||
|
||||
// pc is affected so flush the pipeline
|
||||
is_flushed = true;
|
||||
},
|
||||
[this](Branch& data) {
|
||||
if (data.link)
|
||||
gpr[14] = pc - INSTRUCTION_SIZE;
|
||||
|
||||
// data.offset accounts for two instructions ahead when
|
||||
// disassembling, so need to adjust
|
||||
pc = static_cast<int32_t>(pc) - 2 * INSTRUCTION_SIZE + data.offset;
|
||||
|
||||
// pc is affected so flush the pipeline
|
||||
is_flushed = true;
|
||||
},
|
||||
[this, pc_error](Multiply& data) {
|
||||
if (data.rd == data.rm)
|
||||
log_error("rd and rm are not distinct in {}",
|
||||
typeid(data).name());
|
||||
|
||||
pc_error(data.rd);
|
||||
pc_error(data.rd);
|
||||
pc_error(data.rd);
|
||||
|
||||
gpr[data.rd] =
|
||||
gpr[data.rm] * gpr[data.rs] + (data.acc ? gpr[data.rn] : 0);
|
||||
|
||||
if (data.set) {
|
||||
cpsr.set_z(gpr[data.rd] == 0);
|
||||
cpsr.set_n(get_bit(gpr[data.rd], 31));
|
||||
cpsr.set_c(0);
|
||||
}
|
||||
},
|
||||
[this, pc_error](MultiplyLong& data) {
|
||||
if (data.rdhi == data.rdlo || data.rdhi == data.rm ||
|
||||
data.rdlo == data.rm)
|
||||
log_error("rdhi, rdlo and rm are not distinct in {}",
|
||||
typeid(data).name());
|
||||
|
||||
pc_error(data.rdhi);
|
||||
pc_error(data.rdlo);
|
||||
pc_error(data.rm);
|
||||
pc_error(data.rs);
|
||||
|
||||
if (data.uns) {
|
||||
auto cast = [](uint32_t x) -> uint64_t {
|
||||
return static_cast<uint64_t>(x);
|
||||
};
|
||||
|
||||
uint64_t eval = cast(gpr[data.rm]) * cast(gpr[data.rs]) +
|
||||
(data.acc ? (cast(gpr[data.rdhi]) << 32) |
|
||||
cast(gpr[data.rdlo])
|
||||
: 0);
|
||||
|
||||
gpr[data.rdlo] = bit_range(eval, 0, 31);
|
||||
gpr[data.rdhi] = bit_range(eval, 32, 63);
|
||||
|
||||
} else {
|
||||
auto cast = [](uint32_t x) -> int64_t {
|
||||
return static_cast<int64_t>(static_cast<int32_t>(x));
|
||||
};
|
||||
|
||||
int64_t eval = cast(gpr[data.rm]) * cast(gpr[data.rs]) +
|
||||
(data.acc ? (cast(gpr[data.rdhi]) << 32) |
|
||||
cast(gpr[data.rdlo])
|
||||
: 0);
|
||||
|
||||
gpr[data.rdlo] = bit_range(eval, 0, 31);
|
||||
gpr[data.rdhi] = bit_range(eval, 32, 63);
|
||||
}
|
||||
|
||||
if (data.set) {
|
||||
cpsr.set_z(gpr[data.rdhi] == 0 && gpr[data.rdlo] == 0);
|
||||
cpsr.set_n(get_bit(gpr[data.rdhi], 31));
|
||||
cpsr.set_c(0);
|
||||
cpsr.set_v(0);
|
||||
}
|
||||
},
|
||||
[](Undefined) { log_warn("Undefined instruction"); },
|
||||
[this, pc_error](SingleDataSwap& data) {
|
||||
pc_error(data.rm);
|
||||
pc_error(data.rn);
|
||||
pc_error(data.rd);
|
||||
|
||||
if (data.byte) {
|
||||
gpr[data.rd] = bus->read_byte(gpr[data.rn]);
|
||||
bus->write_byte(gpr[data.rn], gpr[data.rm] & 0xFF);
|
||||
} else {
|
||||
gpr[data.rd] = bus->read_word(gpr[data.rn]);
|
||||
bus->write_word(gpr[data.rn], gpr[data.rm]);
|
||||
}
|
||||
},
|
||||
[this, pc_warn, pc_error](SingleDataTransfer& data) {
|
||||
uint32_t offset = 0;
|
||||
uint32_t address = gpr[data.rn];
|
||||
|
||||
if (!data.pre && data.write)
|
||||
log_warn("Write-back enabled with post-indexing in {}",
|
||||
typeid(data).name());
|
||||
|
||||
if (data.rn == PC_INDEX && data.write)
|
||||
log_warn("Write-back enabled with base register as PC {}",
|
||||
typeid(data).name());
|
||||
|
||||
if (data.write)
|
||||
pc_warn(data.rn);
|
||||
|
||||
// evaluate the offset
|
||||
if (const uint16_t* immediate =
|
||||
std::get_if<uint16_t>(&data.offset)) {
|
||||
offset = *immediate;
|
||||
} else if (const Shift* shift = std::get_if<Shift>(&data.offset)) {
|
||||
uint8_t amount =
|
||||
(shift->data.immediate ? shift->data.operand
|
||||
: gpr[shift->data.operand] & 0xFF);
|
||||
|
||||
bool carry = cpsr.c();
|
||||
|
||||
if (!shift->data.immediate)
|
||||
pc_error(shift->data.operand);
|
||||
pc_error(shift->rm);
|
||||
|
||||
offset =
|
||||
eval_shift(shift->data.type, gpr[shift->rm], amount, carry);
|
||||
|
||||
cpsr.set_c(carry);
|
||||
}
|
||||
|
||||
// PC is always two instructions ahead
|
||||
if (data.rn == PC_INDEX)
|
||||
address -= 2 * INSTRUCTION_SIZE;
|
||||
|
||||
if (data.pre)
|
||||
address += (data.up ? offset : -offset);
|
||||
|
||||
// load
|
||||
if (data.load) {
|
||||
// byte
|
||||
if (data.byte)
|
||||
gpr[data.rd] = bus->read_byte(address);
|
||||
// word
|
||||
else
|
||||
gpr[data.rd] = bus->read_word(address);
|
||||
// store
|
||||
} else {
|
||||
// take PC into consideration
|
||||
if (data.rd == PC_INDEX)
|
||||
address += INSTRUCTION_SIZE;
|
||||
|
||||
// byte
|
||||
if (data.byte)
|
||||
bus->write_byte(address, gpr[data.rd] & 0xFF);
|
||||
// word
|
||||
else
|
||||
bus->write_word(address, gpr[data.rd]);
|
||||
}
|
||||
|
||||
if (!data.pre)
|
||||
address += (data.up ? offset : -offset);
|
||||
|
||||
if (!data.pre || data.write)
|
||||
gpr[data.rn] = address;
|
||||
|
||||
if (data.rd == PC_INDEX && data.load)
|
||||
is_flushed = true;
|
||||
},
|
||||
[this, pc_warn, pc_error](HalfwordTransfer& data) {
|
||||
uint32_t address = gpr[data.rn];
|
||||
uint32_t offset = 0;
|
||||
|
||||
if (!data.pre && data.write)
|
||||
log_error("Write-back enabled with post-indexing in {}",
|
||||
typeid(data).name());
|
||||
|
||||
if (data.sign && !data.load)
|
||||
log_error("Signed data found in {}", typeid(data).name());
|
||||
|
||||
if (data.write)
|
||||
pc_warn(data.rn);
|
||||
|
||||
// offset is register number (4 bits) when not an immediate
|
||||
if (!data.imm) {
|
||||
pc_error(data.offset);
|
||||
offset = gpr[data.offset];
|
||||
} else {
|
||||
offset = data.offset;
|
||||
}
|
||||
|
||||
// PC is always two instructions ahead
|
||||
if (data.rn == PC_INDEX)
|
||||
address -= 2 * INSTRUCTION_SIZE;
|
||||
|
||||
if (data.pre)
|
||||
address += (data.up ? offset : -offset);
|
||||
|
||||
// load
|
||||
if (data.load) {
|
||||
// signed
|
||||
if (data.sign) {
|
||||
// halfword
|
||||
if (data.half) {
|
||||
gpr[data.rd] = bus->read_halfword(address);
|
||||
|
||||
// sign extend the halfword
|
||||
gpr[data.rd] =
|
||||
(static_cast<int32_t>(gpr[data.rd]) << 16) >> 16;
|
||||
|
||||
// byte
|
||||
} else {
|
||||
gpr[data.rd] = bus->read_byte(address);
|
||||
|
||||
// sign extend the byte
|
||||
gpr[data.rd] =
|
||||
(static_cast<int32_t>(gpr[data.rd]) << 24) >> 24;
|
||||
}
|
||||
// unsigned halfword
|
||||
} else if (data.half) {
|
||||
gpr[data.rd] = bus->read_halfword(address);
|
||||
}
|
||||
// store
|
||||
} else {
|
||||
// take PC into consideration
|
||||
if (data.rd == PC_INDEX)
|
||||
address += INSTRUCTION_SIZE;
|
||||
|
||||
// halfword
|
||||
if (data.half)
|
||||
bus->write_halfword(address, gpr[data.rd]);
|
||||
}
|
||||
|
||||
if (!data.pre)
|
||||
address += (data.up ? offset : -offset);
|
||||
|
||||
if (!data.pre || data.write)
|
||||
gpr[data.rn] = address;
|
||||
|
||||
if (data.rd == PC_INDEX && data.load)
|
||||
is_flushed = true;
|
||||
},
|
||||
[this, pc_error](BlockDataTransfer& data) {
|
||||
uint32_t address = gpr[data.rn];
|
||||
Mode mode = cpsr.mode();
|
||||
uint8_t alignment = 4; // word
|
||||
uint8_t i = 0;
|
||||
uint8_t n_regs = std::popcount(data.regs);
|
||||
|
||||
pc_error(data.rn);
|
||||
|
||||
if (cpsr.mode() == Mode::User && data.s) {
|
||||
log_error("Bit S is set outside priviliged modes in {}",
|
||||
typeid(data).name());
|
||||
}
|
||||
|
||||
// we just change modes to load user registers
|
||||
if ((!get_bit(data.regs, PC_INDEX) && data.s) ||
|
||||
(!data.load && data.s)) {
|
||||
chg_mode(Mode::User);
|
||||
|
||||
if (data.write) {
|
||||
log_error("Write-back enable for user bank registers in {}",
|
||||
typeid(data).name());
|
||||
}
|
||||
}
|
||||
|
||||
// account for decrement
|
||||
if (!data.up)
|
||||
address -= (n_regs - 1) * alignment;
|
||||
|
||||
if (data.pre)
|
||||
address += (data.up ? alignment : -alignment);
|
||||
|
||||
if (data.load) {
|
||||
if (get_bit(data.regs, PC_INDEX) && data.s && data.load) {
|
||||
// current mode's spsr is already loaded when it was
|
||||
// switched
|
||||
spsr = cpsr;
|
||||
}
|
||||
|
||||
for (i = 0; i < GPR_COUNT; i++) {
|
||||
if (get_bit(data.regs, i)) {
|
||||
gpr[i] = bus->read_word(address);
|
||||
address += alignment;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (i = 0; i < GPR_COUNT; i++) {
|
||||
if (get_bit(data.regs, i)) {
|
||||
bus->write_word(address, gpr[i]);
|
||||
address += alignment;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!data.pre)
|
||||
address += (data.up ? alignment : -alignment);
|
||||
|
||||
// reset back to original address + offset if incremented earlier
|
||||
if (data.up)
|
||||
address -= n_regs * alignment;
|
||||
else
|
||||
address -= alignment;
|
||||
|
||||
if (!data.pre || data.write)
|
||||
gpr[data.rn] = address;
|
||||
|
||||
if (data.load && get_bit(data.regs, PC_INDEX))
|
||||
is_flushed = true;
|
||||
|
||||
// load back the original mode registers
|
||||
chg_mode(mode);
|
||||
},
|
||||
[this, pc_error](PsrTransfer& data) {
|
||||
if (data.spsr && cpsr.mode() == Mode::User) {
|
||||
log_error("Accessing SPSR in User mode in {}",
|
||||
typeid(data).name());
|
||||
}
|
||||
|
||||
Psr& psr = data.spsr ? spsr : cpsr;
|
||||
|
||||
switch (data.type) {
|
||||
case PsrTransfer::Type::Mrs:
|
||||
pc_error(data.operand);
|
||||
gpr[data.operand] = psr.raw();
|
||||
break;
|
||||
case PsrTransfer::Type::Msr:
|
||||
pc_error(data.operand);
|
||||
|
||||
if (cpsr.mode() != Mode::User) {
|
||||
psr.set_all(gpr[data.operand]);
|
||||
}
|
||||
break;
|
||||
case PsrTransfer::Type::Msr_flg:
|
||||
uint32_t operand =
|
||||
(data.imm ? data.operand : gpr[data.operand]);
|
||||
psr.set_n(get_bit(operand, 31));
|
||||
psr.set_z(get_bit(operand, 30));
|
||||
psr.set_c(get_bit(operand, 29));
|
||||
psr.set_v(get_bit(operand, 28));
|
||||
break;
|
||||
}
|
||||
},
|
||||
[this, pc_error](DataProcessing& data) {
|
||||
uint32_t op_1 = gpr[data.rn];
|
||||
uint32_t op_2 = 0;
|
||||
|
||||
uint32_t result = 0;
|
||||
|
||||
bool overflow = cpsr.v();
|
||||
bool carry = cpsr.c();
|
||||
bool negative = cpsr.n();
|
||||
bool zero = cpsr.z();
|
||||
|
||||
if (const uint32_t* immediate =
|
||||
std::get_if<uint32_t>(&data.operand)) {
|
||||
op_2 = *immediate;
|
||||
} else if (const Shift* shift = std::get_if<Shift>(&data.operand)) {
|
||||
uint8_t amount =
|
||||
(shift->data.immediate ? shift->data.operand
|
||||
: gpr[shift->data.operand] & 0xFF);
|
||||
|
||||
bool carry = cpsr.c();
|
||||
|
||||
if (!shift->data.immediate)
|
||||
pc_error(shift->data.operand);
|
||||
pc_error(shift->rm);
|
||||
|
||||
op_2 =
|
||||
eval_shift(shift->data.type, gpr[shift->rm], amount, carry);
|
||||
|
||||
cpsr.set_c(carry);
|
||||
|
||||
// PC is 12 bytes ahead when shifting
|
||||
if (data.rn == PC_INDEX)
|
||||
op_1 += INSTRUCTION_SIZE;
|
||||
}
|
||||
|
||||
switch (data.opcode) {
|
||||
case OpCode::AND: {
|
||||
result = op_1 & op_2;
|
||||
|
||||
negative = get_bit(result, 31);
|
||||
} break;
|
||||
case OpCode::EOR: {
|
||||
result = op_1 ^ op_2;
|
||||
|
||||
negative = get_bit(result, 31);
|
||||
} break;
|
||||
case OpCode::SUB: {
|
||||
bool s1 = get_bit(op_1, 31);
|
||||
bool s2 = get_bit(op_2, 31);
|
||||
result = op_1 - op_2;
|
||||
negative = get_bit(result, 31);
|
||||
carry = op_1 < op_2;
|
||||
overflow = s1 != s2 && s2 == negative;
|
||||
} break;
|
||||
case OpCode::RSB: {
|
||||
bool s1 = get_bit(op_1, 31);
|
||||
bool s2 = get_bit(op_2, 31);
|
||||
result = op_2 - op_1;
|
||||
|
||||
negative = get_bit(result, 31);
|
||||
carry = op_2 < op_1;
|
||||
overflow = s1 != s2 && s1 == negative;
|
||||
} break;
|
||||
case OpCode::ADD: {
|
||||
bool s1 = get_bit(op_1, 31);
|
||||
bool s2 = get_bit(op_2, 31);
|
||||
|
||||
// result_ is 33 bits
|
||||
uint64_t result_ = op_2 + op_1;
|
||||
result = result_ & 0xFFFFFFFF;
|
||||
|
||||
negative = get_bit(result, 31);
|
||||
carry = get_bit(result_, 32);
|
||||
overflow = s1 == s2 && s1 != negative;
|
||||
} break;
|
||||
case OpCode::ADC: {
|
||||
bool s1 = get_bit(op_1, 31);
|
||||
bool s2 = get_bit(op_2, 31);
|
||||
|
||||
uint64_t result_ = op_2 + op_1 + carry;
|
||||
result = result_ & 0xFFFFFFFF;
|
||||
|
||||
negative = get_bit(result, 31);
|
||||
carry = get_bit(result_, 32);
|
||||
overflow = s1 == s2 && s1 != negative;
|
||||
} break;
|
||||
case OpCode::SBC: {
|
||||
bool s1 = get_bit(op_1, 31);
|
||||
bool s2 = get_bit(op_2, 31);
|
||||
|
||||
uint64_t result_ = op_1 - op_2 + carry - 1;
|
||||
result = result_ & 0xFFFFFFFF;
|
||||
|
||||
negative = get_bit(result, 31);
|
||||
carry = get_bit(result_, 32);
|
||||
overflow = s1 != s2 && s2 == negative;
|
||||
} break;
|
||||
case OpCode::RSC: {
|
||||
bool s1 = get_bit(op_1, 31);
|
||||
bool s2 = get_bit(op_2, 31);
|
||||
|
||||
uint64_t result_ = op_1 - op_2 + carry - 1;
|
||||
result = result_ & 0xFFFFFFFF;
|
||||
|
||||
negative = get_bit(result, 31);
|
||||
carry = get_bit(result_, 32);
|
||||
overflow = s1 != s2 && s1 == negative;
|
||||
} break;
|
||||
case OpCode::TST: {
|
||||
result = op_1 & op_2;
|
||||
|
||||
negative = get_bit(result, 31);
|
||||
} break;
|
||||
case OpCode::TEQ: {
|
||||
result = op_1 ^ op_2;
|
||||
|
||||
negative = get_bit(result, 31);
|
||||
} break;
|
||||
case OpCode::CMP: {
|
||||
bool s1 = get_bit(op_1, 31);
|
||||
bool s2 = get_bit(op_2, 31);
|
||||
|
||||
result = op_1 - op_2;
|
||||
|
||||
negative = get_bit(result, 31);
|
||||
carry = op_1 < op_2;
|
||||
overflow = s1 != s2 && s2 == negative;
|
||||
} break;
|
||||
case OpCode::CMN: {
|
||||
bool s1 = get_bit(op_1, 31);
|
||||
bool s2 = get_bit(op_2, 31);
|
||||
|
||||
uint64_t result_ = op_2 + op_1;
|
||||
result = result_ & 0xFFFFFFFF;
|
||||
|
||||
negative = get_bit(result, 31);
|
||||
carry = get_bit(result_, 32);
|
||||
overflow = s1 == s2 && s1 != negative;
|
||||
} break;
|
||||
case OpCode::ORR: {
|
||||
result = op_1 | op_2;
|
||||
|
||||
negative = get_bit(result, 31);
|
||||
} break;
|
||||
case OpCode::MOV: {
|
||||
result = op_2;
|
||||
|
||||
negative = get_bit(result, 31);
|
||||
} break;
|
||||
case OpCode::BIC: {
|
||||
result = op_1 & ~op_2;
|
||||
|
||||
negative = get_bit(result, 31);
|
||||
} break;
|
||||
case OpCode::MVN: {
|
||||
result = ~op_2;
|
||||
|
||||
negative = get_bit(result, 31);
|
||||
} break;
|
||||
}
|
||||
|
||||
zero = result == 0;
|
||||
|
||||
debug(carry);
|
||||
debug(overflow);
|
||||
debug(zero);
|
||||
debug(negative);
|
||||
|
||||
auto set_conditions = [this, carry, overflow, negative, zero]() {
|
||||
cpsr.set_c(carry);
|
||||
cpsr.set_v(overflow);
|
||||
cpsr.set_n(negative);
|
||||
cpsr.set_z(zero);
|
||||
};
|
||||
|
||||
if (data.set) {
|
||||
if (data.rd == PC_INDEX) {
|
||||
if (cpsr.mode() == Mode::User)
|
||||
log_error("Running {} in User mode",
|
||||
typeid(data).name());
|
||||
} else {
|
||||
set_conditions();
|
||||
}
|
||||
}
|
||||
|
||||
if (data.opcode == OpCode::TST || data.opcode == OpCode::TEQ ||
|
||||
data.opcode == OpCode::CMP || data.opcode == OpCode::CMN) {
|
||||
set_conditions();
|
||||
} else {
|
||||
gpr[data.rd] = result;
|
||||
if (data.rd == PC_INDEX || data.opcode == OpCode::MVN)
|
||||
is_flushed = true;
|
||||
}
|
||||
},
|
||||
[this](SoftwareInterrupt) {
|
||||
chg_mode(Mode::Supervisor);
|
||||
pc = 0x08;
|
||||
spsr = cpsr;
|
||||
},
|
||||
[](auto& data) {
|
||||
log_error("Unimplemented {} instruction", typeid(data).name());
|
||||
} },
|
||||
data);
|
||||
}
|
497
src/cpu/arm/instruction.cc
Normal file
497
src/cpu/arm/instruction.cc
Normal file
@@ -0,0 +1,497 @@
|
||||
#include "instruction.hh"
|
||||
#include "cpu/utility.hh"
|
||||
#include "util/bits.hh"
|
||||
#include <iterator>
|
||||
|
||||
using namespace arm;
|
||||
|
||||
Instruction::Instruction(uint32_t insn)
|
||||
: condition(static_cast<Condition>(bit_range(insn, 28, 31))) {
|
||||
// Branch and exhcange
|
||||
if ((insn & 0x0FFFFFF0) == 0x012FFF10) {
|
||||
uint8_t rn = insn & 0b1111;
|
||||
|
||||
data = BranchAndExchange{ rn };
|
||||
|
||||
// Branch
|
||||
} else if ((insn & 0x0E000000) == 0x0A000000) {
|
||||
bool link = get_bit(insn, 24);
|
||||
uint32_t offset = bit_range(insn, 0, 23);
|
||||
|
||||
// lsh 2 and sign extend the 26 bit offset to 32 bits
|
||||
offset = (static_cast<int32_t>(offset) << 8) >> 6;
|
||||
|
||||
offset += 2 * INSTRUCTION_SIZE;
|
||||
|
||||
data = Branch{ .link = link, .offset = offset };
|
||||
|
||||
// Multiply
|
||||
} else if ((insn & 0x0FC000F0) == 0x00000090) {
|
||||
uint8_t rm = bit_range(insn, 0, 3);
|
||||
uint8_t rs = bit_range(insn, 8, 11);
|
||||
uint8_t rn = bit_range(insn, 12, 15);
|
||||
uint8_t rd = bit_range(insn, 16, 19);
|
||||
bool set = get_bit(insn, 20);
|
||||
bool acc = get_bit(insn, 21);
|
||||
|
||||
data = Multiply{
|
||||
.rm = rm, .rs = rs, .rn = rn, .rd = rd, .set = set, .acc = acc
|
||||
};
|
||||
|
||||
// Multiply long
|
||||
} else if ((insn & 0x0F8000F0) == 0x00800090) {
|
||||
uint8_t rm = bit_range(insn, 0, 3);
|
||||
uint8_t rs = bit_range(insn, 8, 11);
|
||||
uint8_t rdlo = bit_range(insn, 12, 15);
|
||||
uint8_t rdhi = bit_range(insn, 16, 19);
|
||||
bool set = get_bit(insn, 20);
|
||||
bool acc = get_bit(insn, 21);
|
||||
bool uns = !get_bit(insn, 22);
|
||||
|
||||
data = MultiplyLong{ .rm = rm,
|
||||
.rs = rs,
|
||||
.rdlo = rdlo,
|
||||
.rdhi = rdhi,
|
||||
.set = set,
|
||||
.acc = acc,
|
||||
.uns = uns };
|
||||
|
||||
// Undefined
|
||||
} else if ((insn & 0x0E000010) == 0x06000010) {
|
||||
data = Undefined{};
|
||||
|
||||
// Single data swap
|
||||
} else if ((insn & 0x0FB00FF0) == 0x01000090) {
|
||||
uint8_t rm = bit_range(insn, 0, 3);
|
||||
uint8_t rd = bit_range(insn, 12, 15);
|
||||
uint8_t rn = bit_range(insn, 16, 19);
|
||||
bool byte = get_bit(insn, 22);
|
||||
|
||||
data = SingleDataSwap{ .rm = rm, .rd = rd, .rn = rn, .byte = byte };
|
||||
|
||||
// Single data transfer
|
||||
} else if ((insn & 0x0C000000) == 0x04000000) {
|
||||
std::variant<uint16_t, Shift> offset;
|
||||
uint8_t rd = bit_range(insn, 12, 15);
|
||||
uint8_t rn = bit_range(insn, 16, 19);
|
||||
bool load = get_bit(insn, 20);
|
||||
bool write = get_bit(insn, 21);
|
||||
bool byte = get_bit(insn, 22);
|
||||
bool up = get_bit(insn, 23);
|
||||
bool pre = get_bit(insn, 24);
|
||||
bool imm = get_bit(insn, 25);
|
||||
|
||||
if (imm) {
|
||||
// register specified shift amounts not available in single data
|
||||
// transfer (see Undefined)
|
||||
uint8_t rm = bit_range(insn, 0, 3);
|
||||
ShiftType shift_type =
|
||||
static_cast<ShiftType>(bit_range(insn, 5, 6));
|
||||
uint8_t operand = bit_range(insn, 7, 11);
|
||||
|
||||
offset = Shift{ .rm = rm,
|
||||
.data = ShiftData{ .type = shift_type,
|
||||
.immediate = true,
|
||||
.operand = operand } };
|
||||
} else {
|
||||
offset = static_cast<uint16_t>(bit_range(insn, 0, 11));
|
||||
}
|
||||
|
||||
data = SingleDataTransfer{ .offset = offset,
|
||||
.rd = rd,
|
||||
.rn = rn,
|
||||
.load = load,
|
||||
.write = write,
|
||||
.byte = byte,
|
||||
.up = up,
|
||||
.pre = pre };
|
||||
|
||||
// Halfword transfer
|
||||
} else if ((insn & 0x0E000090) == 0x00000090) {
|
||||
uint8_t offset = bit_range(insn, 0, 3);
|
||||
bool half = get_bit(insn, 5);
|
||||
bool sign = get_bit(insn, 6);
|
||||
uint8_t rd = bit_range(insn, 12, 15);
|
||||
uint8_t rn = bit_range(insn, 16, 19);
|
||||
bool load = get_bit(insn, 20);
|
||||
bool write = get_bit(insn, 21);
|
||||
bool imm = get_bit(insn, 22);
|
||||
bool up = get_bit(insn, 23);
|
||||
bool pre = get_bit(insn, 24);
|
||||
|
||||
offset |= (imm ? bit_range(insn, 8, 11) << 2 : 0);
|
||||
|
||||
data = HalfwordTransfer{ .offset = offset,
|
||||
.half = half,
|
||||
.sign = sign,
|
||||
.rd = rd,
|
||||
.rn = rn,
|
||||
.load = load,
|
||||
.write = write,
|
||||
.imm = imm,
|
||||
.up = up,
|
||||
.pre = pre };
|
||||
|
||||
// Block data transfer
|
||||
} else if ((insn & 0x0E000000) == 0x08000000) {
|
||||
uint16_t regs = bit_range(insn, 0, 15);
|
||||
uint8_t rn = bit_range(insn, 16, 19);
|
||||
bool load = get_bit(insn, 20);
|
||||
bool write = get_bit(insn, 21);
|
||||
bool s = get_bit(insn, 22);
|
||||
bool up = get_bit(insn, 23);
|
||||
bool pre = get_bit(insn, 24);
|
||||
|
||||
data = BlockDataTransfer{ .regs = regs,
|
||||
.rn = rn,
|
||||
.load = load,
|
||||
.write = write,
|
||||
.s = s,
|
||||
.up = up,
|
||||
.pre = pre };
|
||||
|
||||
// Data Processing
|
||||
} else if ((insn & 0x0C000000) == 0x00000000) {
|
||||
uint8_t rd = bit_range(insn, 12, 15);
|
||||
uint8_t rn = bit_range(insn, 16, 19);
|
||||
bool set = get_bit(insn, 20);
|
||||
OpCode opcode = static_cast<OpCode>(bit_range(insn, 21, 24));
|
||||
bool imm = get_bit(insn, 25);
|
||||
|
||||
if ((opcode == OpCode::TST || opcode == OpCode::CMP) && !set) {
|
||||
data = PsrTransfer{ .operand = rd,
|
||||
.spsr = get_bit(insn, 22),
|
||||
.type = PsrTransfer::Type::Mrs,
|
||||
.imm = 0 };
|
||||
} else if ((opcode == OpCode::TEQ || opcode == OpCode::CMN) && !set) {
|
||||
uint32_t operand = 0;
|
||||
|
||||
if (imm) {
|
||||
uint32_t immediate = bit_range(insn, 0, 7);
|
||||
uint8_t rotate = bit_range(insn, 8, 11);
|
||||
|
||||
operand = std::rotr(immediate, rotate * 2);
|
||||
} else {
|
||||
operand = bit_range(insn, 0, 3);
|
||||
}
|
||||
|
||||
data = PsrTransfer{ .operand = operand,
|
||||
.spsr = get_bit(insn, 22),
|
||||
.type = (get_bit(insn, 16)
|
||||
? PsrTransfer::Type::Msr
|
||||
: PsrTransfer::Type::Msr_flg),
|
||||
.imm = imm };
|
||||
} else {
|
||||
std::variant<Shift, uint32_t> operand;
|
||||
|
||||
if (imm) {
|
||||
uint32_t immediate = bit_range(insn, 0, 7);
|
||||
uint8_t rotate = bit_range(insn, 8, 11);
|
||||
|
||||
operand = std::rotr(immediate, rotate * 2);
|
||||
} else {
|
||||
uint8_t rm = bit_range(insn, 0, 3);
|
||||
bool reg = get_bit(insn, 4);
|
||||
ShiftType shift_type =
|
||||
static_cast<ShiftType>(bit_range(insn, 5, 6));
|
||||
uint8_t sh_operand = bit_range(insn, (reg ? 8 : 7), 11);
|
||||
|
||||
operand = Shift{ .rm = rm,
|
||||
.data = ShiftData{ .type = shift_type,
|
||||
.immediate = !reg,
|
||||
.operand = sh_operand } };
|
||||
}
|
||||
|
||||
data = DataProcessing{ .operand = operand,
|
||||
.rd = rd,
|
||||
.rn = rn,
|
||||
.set = set,
|
||||
.opcode = opcode };
|
||||
}
|
||||
|
||||
// Software interrupt
|
||||
} else if ((insn & 0x0F000000) == 0x0F000000) {
|
||||
|
||||
data = SoftwareInterrupt{};
|
||||
|
||||
// Coprocessor data transfer
|
||||
} else if ((insn & 0x0E000000) == 0x0C000000) {
|
||||
uint8_t offset = bit_range(insn, 0, 7);
|
||||
uint8_t cpn = bit_range(insn, 8, 11);
|
||||
uint8_t crd = bit_range(insn, 12, 15);
|
||||
uint8_t rn = bit_range(insn, 16, 19);
|
||||
bool load = get_bit(insn, 20);
|
||||
bool write = get_bit(insn, 21);
|
||||
bool len = get_bit(insn, 22);
|
||||
bool up = get_bit(insn, 23);
|
||||
bool pre = get_bit(insn, 24);
|
||||
|
||||
data = CoprocessorDataTransfer{ .offset = offset,
|
||||
.cpn = cpn,
|
||||
.crd = crd,
|
||||
.rn = rn,
|
||||
.load = load,
|
||||
.write = write,
|
||||
.len = len,
|
||||
.up = up,
|
||||
.pre = pre };
|
||||
|
||||
// Coprocessor data operation
|
||||
} else if ((insn & 0x0F000010) == 0x0E000000) {
|
||||
uint8_t crm = bit_range(insn, 0, 3);
|
||||
uint8_t cp = bit_range(insn, 5, 7);
|
||||
uint8_t cpn = bit_range(insn, 8, 11);
|
||||
uint8_t crd = bit_range(insn, 12, 15);
|
||||
uint8_t crn = bit_range(insn, 16, 19);
|
||||
uint8_t cp_opc = bit_range(insn, 20, 23);
|
||||
|
||||
data = CoprocessorDataOperation{ .crm = crm,
|
||||
.cp = cp,
|
||||
.cpn = cpn,
|
||||
.crd = crd,
|
||||
.crn = crn,
|
||||
.cp_opc = cp_opc };
|
||||
|
||||
// Coprocessor register transfer
|
||||
} else if ((insn & 0x0F000010) == 0x0E000010) {
|
||||
uint8_t crm = bit_range(insn, 0, 3);
|
||||
uint8_t cp = bit_range(insn, 5, 7);
|
||||
uint8_t cpn = bit_range(insn, 8, 11);
|
||||
uint8_t rd = bit_range(insn, 12, 15);
|
||||
uint8_t crn = bit_range(insn, 16, 19);
|
||||
bool load = get_bit(insn, 20);
|
||||
uint8_t cp_opc = bit_range(insn, 21, 23);
|
||||
|
||||
data = CoprocessorRegisterTransfer{ .crm = crm,
|
||||
.cp = cp,
|
||||
.cpn = cpn,
|
||||
.rd = rd,
|
||||
.crn = crn,
|
||||
.load = load,
|
||||
.cp_opc = cp_opc };
|
||||
} else {
|
||||
data = Undefined{};
|
||||
}
|
||||
}
|
||||
|
||||
std::string
|
||||
Instruction::disassemble() {
|
||||
// goddamn this is gore
|
||||
// TODO: make this less ugly
|
||||
return std::visit(
|
||||
overloaded{
|
||||
[this](BranchAndExchange& data) {
|
||||
return fmt::format("BX{} R{:d}", condition, data.rn);
|
||||
},
|
||||
[this](Branch& data) {
|
||||
return fmt::format(
|
||||
"B{}{} 0x{:06X}", (data.link ? "L" : ""), condition, data.offset);
|
||||
},
|
||||
[this](Multiply& data) {
|
||||
if (data.acc) {
|
||||
return fmt::format("MLA{}{} R{:d},R{:d},R{:d},R{:d}",
|
||||
condition,
|
||||
(data.set ? "S" : ""),
|
||||
data.rd,
|
||||
data.rm,
|
||||
data.rs,
|
||||
data.rn);
|
||||
} else {
|
||||
return fmt::format("MUL{}{} R{:d},R{:d},R{:d}",
|
||||
condition,
|
||||
(data.set ? "S" : ""),
|
||||
data.rd,
|
||||
data.rm,
|
||||
data.rs);
|
||||
}
|
||||
},
|
||||
[this](MultiplyLong& data) {
|
||||
return fmt::format("{}{}{}{} R{:d},R{:d},R{:d},R{:d}",
|
||||
(data.uns ? 'U' : 'S'),
|
||||
(data.acc ? "MLAL" : "MULL"),
|
||||
condition,
|
||||
(data.set ? "S" : ""),
|
||||
data.rdlo,
|
||||
data.rdhi,
|
||||
data.rm,
|
||||
data.rs);
|
||||
},
|
||||
[](Undefined) { return std::string("UND"); },
|
||||
[this](SingleDataSwap& data) {
|
||||
return fmt::format("SWP{}{} R{:d},R{:d},[R{:d}]",
|
||||
condition,
|
||||
(data.byte ? "B" : ""),
|
||||
data.rd,
|
||||
data.rm,
|
||||
data.rn);
|
||||
},
|
||||
[this](SingleDataTransfer& data) {
|
||||
std::string expression;
|
||||
std::string address;
|
||||
|
||||
if (const uint16_t* offset = std::get_if<uint16_t>(&data.offset)) {
|
||||
if (*offset == 0) {
|
||||
expression = "";
|
||||
} else {
|
||||
expression =
|
||||
fmt::format(",{}#{:d}", (data.up ? '+' : '-'), *offset);
|
||||
}
|
||||
} else if (const Shift* shift = std::get_if<Shift>(&data.offset)) {
|
||||
// Shifts are always immediate in single data transfer
|
||||
expression = fmt::format(",{}R{:d},{} #{:d}",
|
||||
(data.up ? '+' : '-'),
|
||||
shift->rm,
|
||||
shift->data.type,
|
||||
shift->data.operand);
|
||||
}
|
||||
|
||||
return fmt::format(
|
||||
"{}{}{}{} R{:d},[R{:d}{}]{}",
|
||||
(data.load ? "LDR" : "STR"),
|
||||
condition,
|
||||
(data.byte ? "B" : ""),
|
||||
(!data.pre && data.write ? "T" : ""),
|
||||
data.rd,
|
||||
data.rn,
|
||||
(data.pre ? expression : ""),
|
||||
(data.pre ? (data.write ? "!" : "") : expression));
|
||||
},
|
||||
[this](HalfwordTransfer& data) {
|
||||
std::string expression;
|
||||
|
||||
if (data.imm) {
|
||||
if (data.offset == 0) {
|
||||
expression = "";
|
||||
} else {
|
||||
expression = fmt::format(
|
||||
",{}#{:d}", (data.up ? '+' : '-'), data.offset);
|
||||
}
|
||||
} else {
|
||||
expression =
|
||||
fmt::format(",{}R{:d}", (data.up ? '+' : '-'), data.offset);
|
||||
}
|
||||
|
||||
return fmt::format(
|
||||
"{}{}{}{} R{:d},[R{:d}{}]{}",
|
||||
(data.load ? "LDR" : "STR"),
|
||||
condition,
|
||||
(data.sign ? "S" : ""),
|
||||
(data.half ? 'H' : 'B'),
|
||||
data.rd,
|
||||
data.rn,
|
||||
(data.pre ? expression : ""),
|
||||
(data.pre ? (data.write ? "!" : "") : expression));
|
||||
},
|
||||
[this](BlockDataTransfer& data) {
|
||||
std::string regs;
|
||||
|
||||
for (uint8_t i = 0; i < 16; i++) {
|
||||
if (get_bit(data.regs, i))
|
||||
fmt::format_to(std::back_inserter(regs), "R{:d},", i);
|
||||
};
|
||||
|
||||
regs.pop_back();
|
||||
|
||||
return fmt::format("{}{}{}{} R{:d}{},{{{}}}{}",
|
||||
(data.load ? "LDM" : "STM"),
|
||||
condition,
|
||||
(data.up ? 'I' : 'D'),
|
||||
(data.pre ? 'B' : 'A'),
|
||||
data.rn,
|
||||
(data.write ? "!" : ""),
|
||||
regs,
|
||||
(data.s ? "^" : ""));
|
||||
},
|
||||
[this](PsrTransfer& data) {
|
||||
if (data.type == PsrTransfer::Type::Mrs) {
|
||||
return fmt::format("MRS{} R{:d},{}",
|
||||
condition,
|
||||
data.operand,
|
||||
(data.spsr ? "SPSR_all" : "CPSR_all"));
|
||||
} else {
|
||||
return fmt::format(
|
||||
"MSR{} {}_{},{}{}",
|
||||
condition,
|
||||
(data.spsr ? "SPSR" : "CPSR"),
|
||||
(data.type == PsrTransfer::Type::Msr_flg ? "flg" : "all"),
|
||||
(data.imm ? '#' : 'R'),
|
||||
data.operand);
|
||||
}
|
||||
},
|
||||
[this](DataProcessing& data) {
|
||||
std::string op_2;
|
||||
|
||||
if (const uint32_t* operand =
|
||||
std::get_if<uint32_t>(&data.operand)) {
|
||||
op_2 = fmt::format("#{:d}", *operand);
|
||||
} else if (const Shift* shift = std::get_if<Shift>(&data.operand)) {
|
||||
op_2 = fmt::format("R{:d},{} {}{:d}",
|
||||
shift->rm,
|
||||
shift->data.type,
|
||||
(shift->data.immediate ? '#' : 'R'),
|
||||
shift->data.operand);
|
||||
}
|
||||
|
||||
switch (data.opcode) {
|
||||
case OpCode::MOV:
|
||||
case OpCode::MVN:
|
||||
return fmt::format("{}{}{} R{:d},{}",
|
||||
data.opcode,
|
||||
condition,
|
||||
(data.set ? "S" : ""),
|
||||
data.rd,
|
||||
op_2);
|
||||
case OpCode::TST:
|
||||
case OpCode::TEQ:
|
||||
case OpCode::CMP:
|
||||
case OpCode::CMN:
|
||||
return fmt::format(
|
||||
"{}{} R{:d},{}", data.opcode, condition, data.rn, op_2);
|
||||
default:
|
||||
return fmt::format("{}{}{} R{:d},R{:d},{}",
|
||||
data.opcode,
|
||||
condition,
|
||||
(data.set ? "S" : ""),
|
||||
data.rd,
|
||||
data.rn,
|
||||
op_2);
|
||||
}
|
||||
},
|
||||
[this](SoftwareInterrupt) { return fmt::format("SWI{}", condition); },
|
||||
[this](CoprocessorDataTransfer& data) {
|
||||
std::string expression = fmt::format(",#{:d}", data.offset);
|
||||
return fmt::format(
|
||||
"{}{}{} p{:d},c{:d},[R{:d}{}]{}",
|
||||
(data.load ? "LDC" : "STC"),
|
||||
condition,
|
||||
(data.len ? "L" : ""),
|
||||
data.cpn,
|
||||
data.crd,
|
||||
data.rn,
|
||||
(data.pre ? expression : ""),
|
||||
(data.pre ? (data.write ? "!" : "") : expression));
|
||||
},
|
||||
[this](CoprocessorDataOperation& data) {
|
||||
return fmt::format("CDP{} p{},{},c{},c{},c{},{}",
|
||||
condition,
|
||||
data.cpn,
|
||||
data.cp_opc,
|
||||
data.crd,
|
||||
data.crn,
|
||||
data.crm,
|
||||
data.cp);
|
||||
},
|
||||
[this](CoprocessorRegisterTransfer& data) {
|
||||
return fmt::format("{}{} p{},{},R{},c{},c{},{}",
|
||||
(data.load ? "MRC" : "MCR"),
|
||||
condition,
|
||||
data.cpn,
|
||||
data.cp_opc,
|
||||
data.rd,
|
||||
data.crn,
|
||||
data.crm,
|
||||
data.cp);
|
||||
},
|
||||
[](auto) { return std::string("unknown instruction"); } },
|
||||
data);
|
||||
}
|
167
src/cpu/arm/instruction.hh
Normal file
167
src/cpu/arm/instruction.hh
Normal file
@@ -0,0 +1,167 @@
|
||||
#include "cpu/utility.hh"
|
||||
#include <cstdint>
|
||||
#include <variant>
|
||||
|
||||
template<class... Ts>
|
||||
struct overloaded : Ts... {
|
||||
using Ts::operator()...;
|
||||
};
|
||||
template<class... Ts>
|
||||
overloaded(Ts...) -> overloaded<Ts...>;
|
||||
|
||||
namespace arm {
|
||||
static constexpr size_t INSTRUCTION_SIZE = 4;
|
||||
|
||||
struct BranchAndExchange {
|
||||
uint8_t rn;
|
||||
};
|
||||
|
||||
struct Branch {
|
||||
bool link;
|
||||
uint32_t offset;
|
||||
};
|
||||
|
||||
struct Multiply {
|
||||
uint8_t rm;
|
||||
uint8_t rs;
|
||||
uint8_t rn;
|
||||
uint8_t rd;
|
||||
bool set;
|
||||
bool acc;
|
||||
};
|
||||
|
||||
struct MultiplyLong {
|
||||
uint8_t rm;
|
||||
uint8_t rs;
|
||||
uint8_t rdlo;
|
||||
uint8_t rdhi;
|
||||
bool set;
|
||||
bool acc;
|
||||
bool uns;
|
||||
};
|
||||
|
||||
struct SingleDataSwap {
|
||||
uint8_t rm;
|
||||
uint8_t rd;
|
||||
uint8_t rn;
|
||||
bool byte;
|
||||
};
|
||||
|
||||
struct SingleDataTransfer {
|
||||
std::variant<uint16_t, Shift> offset;
|
||||
uint8_t rd;
|
||||
uint8_t rn;
|
||||
bool load;
|
||||
bool write;
|
||||
bool byte;
|
||||
bool up;
|
||||
bool pre;
|
||||
};
|
||||
|
||||
struct HalfwordTransfer {
|
||||
uint8_t offset;
|
||||
bool half;
|
||||
bool sign;
|
||||
uint8_t rd;
|
||||
uint8_t rn;
|
||||
bool load;
|
||||
bool write;
|
||||
bool imm;
|
||||
bool up;
|
||||
bool pre;
|
||||
};
|
||||
|
||||
struct BlockDataTransfer {
|
||||
uint16_t regs;
|
||||
uint8_t rn;
|
||||
bool load;
|
||||
bool write;
|
||||
bool s;
|
||||
bool up;
|
||||
bool pre;
|
||||
};
|
||||
|
||||
struct DataProcessing {
|
||||
std::variant<Shift, uint32_t> operand;
|
||||
uint8_t rd;
|
||||
uint8_t rn;
|
||||
bool set;
|
||||
OpCode opcode;
|
||||
};
|
||||
|
||||
struct PsrTransfer {
|
||||
enum class Type {
|
||||
Mrs,
|
||||
Msr,
|
||||
Msr_flg
|
||||
};
|
||||
|
||||
uint32_t operand;
|
||||
bool spsr;
|
||||
Type type;
|
||||
// ignored outside MSR_flg
|
||||
bool imm;
|
||||
};
|
||||
|
||||
struct CoprocessorDataTransfer {
|
||||
uint8_t offset;
|
||||
uint8_t cpn;
|
||||
uint8_t crd;
|
||||
uint8_t rn;
|
||||
bool load;
|
||||
bool write;
|
||||
bool len;
|
||||
bool up;
|
||||
bool pre;
|
||||
};
|
||||
|
||||
struct CoprocessorDataOperation {
|
||||
uint8_t crm;
|
||||
uint8_t cp;
|
||||
uint8_t cpn;
|
||||
uint8_t crd;
|
||||
uint8_t crn;
|
||||
uint8_t cp_opc;
|
||||
};
|
||||
|
||||
struct CoprocessorRegisterTransfer {
|
||||
uint8_t crm;
|
||||
uint8_t cp;
|
||||
uint8_t cpn;
|
||||
uint8_t rd;
|
||||
uint8_t crn;
|
||||
bool load;
|
||||
uint8_t cp_opc;
|
||||
};
|
||||
|
||||
struct Undefined {};
|
||||
struct SoftwareInterrupt {};
|
||||
|
||||
using InstructionData = std::variant<BranchAndExchange,
|
||||
Branch,
|
||||
Multiply,
|
||||
MultiplyLong,
|
||||
SingleDataSwap,
|
||||
SingleDataTransfer,
|
||||
HalfwordTransfer,
|
||||
BlockDataTransfer,
|
||||
DataProcessing,
|
||||
PsrTransfer,
|
||||
CoprocessorDataTransfer,
|
||||
CoprocessorDataOperation,
|
||||
CoprocessorRegisterTransfer,
|
||||
Undefined,
|
||||
SoftwareInterrupt>;
|
||||
|
||||
struct Instruction {
|
||||
Condition condition;
|
||||
InstructionData data;
|
||||
|
||||
Instruction(uint32_t insn);
|
||||
Instruction(Condition condition, InstructionData data) noexcept
|
||||
: condition(condition)
|
||||
, data(data){};
|
||||
|
||||
std::string disassemble();
|
||||
};
|
||||
}
|
4
src/cpu/arm/meson.build
Normal file
4
src/cpu/arm/meson.build
Normal file
@@ -0,0 +1,4 @@
|
||||
lib_sources += files(
|
||||
'instruction.cc',
|
||||
'exec.cc'
|
||||
)
|
Reference in New Issue
Block a user