So, I ended up moving exec methods from Instruction to Cpu for encapsulating cycle emulation, and this has caused me lots of pain since I had to rewrite a shit ton of tests which are not even useful or comprehensible, i do no know why i put myself through this Signed-off-by: Amneesh Singh <natto@weirdnatto.in>
718 lines
24 KiB
C++
718 lines
24 KiB
C++
#include "bus.hh"
|
|
#include "cpu/cpu.hh"
|
|
#include "util/bits.hh"
|
|
#include "util/log.hh"
|
|
|
|
namespace matar {
|
|
void
|
|
Cpu::exec(arm::Instruction& instruction) {
|
|
bool is_flushed = false;
|
|
|
|
if (!cpsr.condition(instruction.condition)) {
|
|
advance_pc_arm();
|
|
return;
|
|
}
|
|
|
|
auto pc_error = [](uint8_t r) {
|
|
if (r == PC_INDEX)
|
|
glogger.error("Using PC (R15) as operand register");
|
|
};
|
|
|
|
auto pc_warn = [](uint8_t r) {
|
|
if (r == PC_INDEX)
|
|
glogger.warn("Using PC (R15) as operand register");
|
|
};
|
|
|
|
using namespace arm;
|
|
|
|
std::visit(
|
|
overloaded{
|
|
[this, pc_warn, &is_flushed](BranchAndExchange& data) {
|
|
/*
|
|
S -> reading instruction in step()
|
|
N -> fetch from the new address in branch
|
|
S -> last opcode fetch at +L to refill the pipeline
|
|
Total = 2S + N cycles
|
|
1S done, S+N taken care of by flush_pipeline()
|
|
*/
|
|
|
|
uint32_t addr = gpr[data.rn];
|
|
State state = static_cast<State>(get_bit(addr, 0));
|
|
|
|
pc_warn(data.rn);
|
|
|
|
if (state != cpsr.state())
|
|
glogger.info_bold("State changed");
|
|
|
|
// set state
|
|
cpsr.set_state(state);
|
|
|
|
// copy to PC
|
|
pc = addr;
|
|
|
|
// 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, &is_flushed](Branch& data) {
|
|
/*
|
|
S -> reading instruction in step()
|
|
N -> fetch from the new address in branch
|
|
S -> last opcode fetch at +L to refill the pipeline
|
|
Total = 2S + N cycles
|
|
1S done, S+N taken care of by flush_pipeline()
|
|
*/
|
|
|
|
if (data.link)
|
|
gpr[14] = pc - INSTRUCTION_SIZE;
|
|
|
|
pc += data.offset;
|
|
|
|
// pc is affected so flush the pipeline
|
|
is_flushed = true;
|
|
},
|
|
[this, pc_error](Multiply& data) {
|
|
/*
|
|
S -> reading instruction in step()
|
|
mI -> m internal cycles
|
|
I -> only when accumulating
|
|
let v = data at rn
|
|
m = 1 if bits [32:8] of v are all zero or all one
|
|
m = 2 [32:16]
|
|
m = 3 [32:24]
|
|
m = 4 otherwise
|
|
|
|
Total = S + mI or S + (m+1)I
|
|
*/
|
|
|
|
if (data.rd == data.rm)
|
|
glogger.error("rd and rm are not distinct in {}",
|
|
typeid(data).name());
|
|
|
|
pc_error(data.rd);
|
|
pc_error(data.rd);
|
|
pc_error(data.rd);
|
|
|
|
// mI
|
|
for (int i = 0; i < multiplier_array_cycles(gpr[data.rs]); i++)
|
|
internal_cycle();
|
|
|
|
gpr[data.rd] = gpr[data.rm] * gpr[data.rs];
|
|
|
|
if (data.acc) {
|
|
gpr[data.rd] += gpr[data.rn];
|
|
// 1I
|
|
internal_cycle();
|
|
}
|
|
|
|
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) {
|
|
/*
|
|
S -> reading instruction in step()
|
|
(m+1)I -> m + 1 internal cycles
|
|
I -> only when accumulating
|
|
let v = data at rs
|
|
m = 1 if bits [32:8] of v are all zeroes (or all ones if signed)
|
|
m = 2 [32:16]
|
|
m = 3 [32:24]
|
|
m = 4 otherwise
|
|
|
|
Total = S + (m+1)I or S + (m+2)I
|
|
*/
|
|
|
|
if (data.rdhi == data.rdlo || data.rdhi == data.rm ||
|
|
data.rdlo == data.rm)
|
|
glogger.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);
|
|
|
|
// 1I
|
|
if (data.acc)
|
|
internal_cycle();
|
|
|
|
// m+1 internal cycles
|
|
for (int i = 0;
|
|
i <= multiplier_array_cycles(gpr[data.rs], data.uns);
|
|
i++)
|
|
internal_cycle();
|
|
|
|
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) {
|
|
// this should be 2S + N + I, should i flush the pipeline? i
|
|
// dont know. TODO: study
|
|
glogger.warn("Undefined instruction");
|
|
},
|
|
[this, pc_error](SingleDataSwap& data) {
|
|
/*
|
|
N -> reading instruction in step()
|
|
N -> unrelated read
|
|
S -> related write
|
|
I -> earlier read value is written to register
|
|
Total = S + 2N +I
|
|
*/
|
|
|
|
pc_error(data.rm);
|
|
pc_error(data.rn);
|
|
pc_error(data.rd);
|
|
|
|
if (data.byte) {
|
|
gpr[data.rd] =
|
|
bus->read_byte(gpr[data.rn], CpuAccess::NonSequential);
|
|
bus->write_byte(
|
|
gpr[data.rn], gpr[data.rm] & 0xFF, CpuAccess::Sequential);
|
|
} else {
|
|
gpr[data.rd] =
|
|
bus->read_word(gpr[data.rn], CpuAccess::NonSequential);
|
|
bus->write_word(
|
|
gpr[data.rn], gpr[data.rm], CpuAccess::Sequential);
|
|
}
|
|
|
|
internal_cycle();
|
|
// last write address is unrelated to next
|
|
next_access = CpuAccess::NonSequential;
|
|
},
|
|
[this, pc_warn, pc_error, &is_flushed](SingleDataTransfer& data) {
|
|
/*
|
|
Load
|
|
====
|
|
S -> reading instruction in step()
|
|
N -> read from target
|
|
I -> stored in register
|
|
N+S -> if PC is written - taken care of by flush_pipeline()
|
|
Total = S + N + I or 2S + 2N + I
|
|
|
|
Store
|
|
=====
|
|
N -> calculating memory address
|
|
N -> write at target
|
|
Total = 2N
|
|
*/
|
|
uint32_t offset = 0;
|
|
uint32_t address = gpr[data.rn];
|
|
|
|
if (!data.pre && data.write)
|
|
glogger.warn("Write-back enabled with post-indexing in {}",
|
|
typeid(data).name());
|
|
|
|
if (data.rn == PC_INDEX && data.write)
|
|
glogger.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);
|
|
}
|
|
|
|
if (data.pre)
|
|
address += (data.up ? offset : -offset);
|
|
|
|
// load
|
|
if (data.load) {
|
|
// byte
|
|
if (data.byte)
|
|
gpr[data.rd] =
|
|
bus->read_byte(address, CpuAccess::NonSequential);
|
|
// word
|
|
else
|
|
gpr[data.rd] =
|
|
bus->read_word(address, CpuAccess::NonSequential);
|
|
|
|
// N + S
|
|
if (data.rd == PC_INDEX)
|
|
is_flushed = true;
|
|
|
|
// I
|
|
internal_cycle();
|
|
// store
|
|
} else {
|
|
// take PC into consideration
|
|
uint32_t value = gpr[data.rd];
|
|
|
|
if (data.rd == PC_INDEX)
|
|
value += INSTRUCTION_SIZE;
|
|
|
|
// byte
|
|
if (data.byte)
|
|
bus->write_byte(
|
|
address, value & 0xFF, CpuAccess::NonSequential);
|
|
// word
|
|
else
|
|
bus->write_word(address, value, CpuAccess::NonSequential);
|
|
}
|
|
|
|
if (!data.pre)
|
|
address += (data.up ? offset : -offset);
|
|
|
|
if (!data.pre || data.write)
|
|
gpr[data.rn] = address;
|
|
|
|
// last read/write is unrelated, this will be overwriten if
|
|
// flushed
|
|
next_access = CpuAccess::NonSequential;
|
|
},
|
|
[this, pc_warn, pc_error, &is_flushed](HalfwordTransfer& data) {
|
|
/*
|
|
Load
|
|
====
|
|
S -> reading instruction in step()
|
|
N -> read from target
|
|
I -> stored in register
|
|
N+S -> if PC is written - taken care of by flush_pipeline()
|
|
Total = S + N + I or 2S + 2N + I
|
|
|
|
Store
|
|
=====
|
|
N -> calculating memory address
|
|
N -> write at target
|
|
Total = 2N
|
|
*/
|
|
uint32_t address = gpr[data.rn];
|
|
uint32_t offset = 0;
|
|
|
|
if (!data.pre && data.write)
|
|
glogger.error("Write-back enabled with post-indexing in {}",
|
|
typeid(data).name());
|
|
|
|
if (data.sign && !data.load)
|
|
glogger.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;
|
|
}
|
|
|
|
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, CpuAccess::NonSequential);
|
|
|
|
// 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, CpuAccess::NonSequential);
|
|
|
|
// 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, CpuAccess::NonSequential);
|
|
}
|
|
|
|
// I
|
|
internal_cycle();
|
|
|
|
if (data.rd == PC_INDEX)
|
|
is_flushed = true;
|
|
|
|
// store
|
|
} else {
|
|
uint32_t value = gpr[data.rd];
|
|
|
|
// take PC into consideration
|
|
if (data.rd == PC_INDEX)
|
|
value += INSTRUCTION_SIZE;
|
|
|
|
// halfword
|
|
if (data.half)
|
|
bus->write_halfword(
|
|
address, value & 0xFFFF, CpuAccess::NonSequential);
|
|
}
|
|
|
|
if (!data.pre)
|
|
address += (data.up ? offset : -offset);
|
|
|
|
if (!data.pre || data.write)
|
|
gpr[data.rn] = address;
|
|
|
|
// last read/write is unrelated, this will be overwriten if
|
|
// flushed
|
|
next_access = CpuAccess::NonSequential;
|
|
},
|
|
[this, pc_error, &is_flushed](BlockDataTransfer& data) {
|
|
/*
|
|
Load
|
|
====
|
|
S -> reading instruction in step()
|
|
N -> unrelated read from target
|
|
(n-1) S -> next n - 1 related reads from target
|
|
I -> stored in register
|
|
N+S -> if PC is written - taken care of by
|
|
flush_pipeline() Total = nS + N + I or (n+1)S + 2N + I
|
|
|
|
Store
|
|
=====
|
|
N -> calculating memory address
|
|
N -> unrelated write at target
|
|
(n-1) S -> next n - 1 related writes
|
|
Total = 2N + (n-1)S
|
|
*/
|
|
|
|
static constexpr uint8_t alignment = 4; // word
|
|
|
|
uint32_t address = gpr[data.rn];
|
|
Mode mode = cpsr.mode();
|
|
int8_t i = 0;
|
|
CpuAccess access = CpuAccess::NonSequential;
|
|
|
|
pc_error(data.rn);
|
|
|
|
if (cpsr.mode() == Mode::User && data.s) {
|
|
glogger.error("Bit S is set outside priviliged modes in block "
|
|
"data transfer");
|
|
}
|
|
|
|
// 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) {
|
|
glogger.error("Write-back enable for user bank registers "
|
|
"in block data transfer");
|
|
}
|
|
}
|
|
|
|
// increment beforehand
|
|
if (data.pre)
|
|
address += (data.up ? alignment : -alignment);
|
|
|
|
if (data.load) {
|
|
if (get_bit(data.regs, PC_INDEX)) {
|
|
is_flushed = true;
|
|
|
|
// current mode's spsr is already loaded when it was
|
|
// switched
|
|
if (data.s)
|
|
spsr = cpsr;
|
|
}
|
|
|
|
if (data.up) {
|
|
for (i = 0; i < GPR_COUNT; i++) {
|
|
if (get_bit(data.regs, i)) {
|
|
gpr[i] = bus->read_word(address, access);
|
|
address += alignment;
|
|
access = CpuAccess::Sequential;
|
|
}
|
|
}
|
|
} else {
|
|
for (i = GPR_COUNT - 1; i >= 0; i--) {
|
|
if (get_bit(data.regs, i)) {
|
|
gpr[i] = bus->read_word(address, access);
|
|
address -= alignment;
|
|
access = CpuAccess::Sequential;
|
|
}
|
|
}
|
|
}
|
|
|
|
// I
|
|
internal_cycle();
|
|
} else {
|
|
if (data.up) {
|
|
for (i = 0; i < GPR_COUNT; i++) {
|
|
if (get_bit(data.regs, i)) {
|
|
bus->write_word(address, gpr[i], access);
|
|
address += alignment;
|
|
access = CpuAccess::Sequential;
|
|
}
|
|
}
|
|
} else {
|
|
for (i = GPR_COUNT - 1; i >= 0; i--) {
|
|
if (get_bit(data.regs, i)) {
|
|
bus->write_word(address, gpr[i], access);
|
|
address -= alignment;
|
|
access = CpuAccess::Sequential;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// fix increment
|
|
if (data.pre)
|
|
address += (data.up ? -alignment : alignment);
|
|
|
|
if (!data.pre || data.write)
|
|
gpr[data.rn] = address;
|
|
|
|
// load back the original mode registers
|
|
chg_mode(mode);
|
|
|
|
// last read/write is unrelated, this will be overwriten if
|
|
// flushed
|
|
next_access = CpuAccess::NonSequential;
|
|
},
|
|
[this, pc_error](PsrTransfer& data) {
|
|
/*
|
|
S -> prefetched instruction in step()
|
|
Total = 1S cycle
|
|
*/
|
|
|
|
if (data.spsr && cpsr.mode() == Mode::User) {
|
|
glogger.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) {
|
|
if (!data.spsr) {
|
|
Psr tmp = Psr(gpr[data.operand]);
|
|
chg_mode(tmp.mode());
|
|
}
|
|
|
|
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, &is_flushed](DataProcessing& data) {
|
|
/*
|
|
Always
|
|
======
|
|
S -> prefetched instruction in step()
|
|
|
|
With Register specified shift
|
|
=============================
|
|
I -> internal cycle
|
|
|
|
When PC is written
|
|
==================
|
|
N -> fetch from the new address in branch
|
|
S -> last opcode fetch at +L to refill the pipeline
|
|
S+N taken care of by flush_pipeline()
|
|
|
|
Total = S or S + I or 2S + N + I or 2S + N cycles
|
|
*/
|
|
|
|
using OpCode = DataProcessing::OpCode;
|
|
|
|
uint32_t op_1 = gpr[data.rn];
|
|
uint32_t op_2 = 0;
|
|
|
|
uint32_t result = 0;
|
|
|
|
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;
|
|
|
|
// 1I when register specified shift
|
|
if (!shift->data.immediate)
|
|
internal_cycle();
|
|
}
|
|
|
|
bool overflow = cpsr.v();
|
|
bool carry = cpsr.c();
|
|
|
|
switch (data.opcode) {
|
|
case OpCode::AND:
|
|
case OpCode::TST:
|
|
result = op_1 & op_2;
|
|
result = op_1 & op_2;
|
|
break;
|
|
case OpCode::EOR:
|
|
case OpCode::TEQ:
|
|
result = op_1 ^ op_2;
|
|
break;
|
|
case OpCode::SUB:
|
|
case OpCode::CMP:
|
|
result = sub(op_1, op_2, carry, overflow);
|
|
break;
|
|
case OpCode::RSB:
|
|
result = sub(op_2, op_1, carry, overflow);
|
|
break;
|
|
case OpCode::ADD:
|
|
case OpCode::CMN:
|
|
result = add(op_1, op_2, carry, overflow);
|
|
break;
|
|
case OpCode::ADC:
|
|
result = add(op_1, op_2, carry, overflow, carry);
|
|
break;
|
|
case OpCode::SBC:
|
|
result = sbc(op_1, op_2, carry, overflow, carry);
|
|
break;
|
|
case OpCode::RSC:
|
|
result = sbc(op_2, op_1, carry, overflow, carry);
|
|
break;
|
|
case OpCode::ORR:
|
|
result = op_1 | op_2;
|
|
break;
|
|
case OpCode::MOV:
|
|
result = op_2;
|
|
break;
|
|
case OpCode::BIC:
|
|
result = op_1 & ~op_2;
|
|
break;
|
|
case OpCode::MVN:
|
|
result = ~op_2;
|
|
break;
|
|
}
|
|
|
|
auto set_conditions = [this, carry, overflow, result]() {
|
|
cpsr.set_c(carry);
|
|
cpsr.set_v(overflow);
|
|
cpsr.set_n(get_bit(result, 31));
|
|
cpsr.set_z(result == 0);
|
|
};
|
|
|
|
if (data.set) {
|
|
if (data.rd == PC_INDEX) {
|
|
if (cpsr.mode() == Mode::User)
|
|
glogger.error("Running {} in User mode",
|
|
typeid(data).name());
|
|
spsr = cpsr;
|
|
} 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, &is_flushed](SoftwareInterrupt) {
|
|
chg_mode(Mode::Supervisor);
|
|
pc = 0x00;
|
|
spsr = cpsr;
|
|
is_flushed = true;
|
|
},
|
|
[](auto& data) {
|
|
glogger.error("Unimplemented {} instruction", typeid(data).name());
|
|
} },
|
|
instruction.data);
|
|
|
|
if (is_flushed) {
|
|
opcodes[0] = bus->read_word(pc, CpuAccess::NonSequential);
|
|
advance_pc_arm();
|
|
opcodes[1] = bus->read_word(pc, CpuAccess::Sequential);
|
|
advance_pc_arm();
|
|
next_access = CpuAccess::Sequential;
|
|
} else
|
|
advance_pc_arm();
|
|
}
|
|
}
|