bus (feat): add cycle accuracy
Signed-off-by: Amneesh Singh <natto@weirdnatto.in>
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
#include "cpu/alu.hh"
|
||||
#include "cpu/cpu.hh"
|
||||
#include "util/bits.hh"
|
||||
#include "util/log.hh"
|
||||
@@ -15,6 +16,11 @@ Instruction::exec(Cpu& cpu) {
|
||||
std::visit(
|
||||
overloaded{
|
||||
[&cpu, set_cc](MoveShiftedRegister& data) {
|
||||
/*
|
||||
S -> prefetched instruction in step()
|
||||
|
||||
Total = S cycle
|
||||
*/
|
||||
if (data.opcode == ShiftType::ROR)
|
||||
glogger.error("Invalid opcode in {}", typeid(data).name());
|
||||
|
||||
@@ -28,6 +34,11 @@ Instruction::exec(Cpu& cpu) {
|
||||
set_cc(carry, cpu.cpsr.v(), get_bit(shifted, 31), shifted == 0);
|
||||
},
|
||||
[&cpu, set_cc](AddSubtract& data) {
|
||||
/*
|
||||
S -> prefetched instruction in step()
|
||||
|
||||
Total = S cycle
|
||||
*/
|
||||
uint32_t offset =
|
||||
data.imm ? static_cast<uint32_t>(static_cast<int8_t>(data.offset))
|
||||
: cpu.gpr[data.offset];
|
||||
@@ -48,6 +59,11 @@ Instruction::exec(Cpu& cpu) {
|
||||
set_cc(carry, overflow, get_bit(result, 31), result == 0);
|
||||
},
|
||||
[&cpu, set_cc](MovCmpAddSubImmediate& data) {
|
||||
/*
|
||||
S -> prefetched instruction in step()
|
||||
|
||||
Total = S cycle
|
||||
*/
|
||||
uint32_t result = 0;
|
||||
bool carry = cpu.cpsr.c();
|
||||
bool overflow = cpu.cpsr.v();
|
||||
@@ -73,6 +89,25 @@ Instruction::exec(Cpu& cpu) {
|
||||
cpu.gpr[data.rd] = result;
|
||||
},
|
||||
[&cpu, set_cc](AluOperations& data) {
|
||||
/*
|
||||
Data Processing
|
||||
===============
|
||||
S -> prefetched instruction in step()
|
||||
I -> only when register specified shift
|
||||
Total = S or S + I cycles
|
||||
|
||||
Multiply
|
||||
========
|
||||
S -> reading instruction in step()
|
||||
mI -> m internal cycles
|
||||
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 cycles
|
||||
*/
|
||||
uint32_t op_1 = cpu.gpr[data.rd];
|
||||
uint32_t op_2 = cpu.gpr[data.rs];
|
||||
uint32_t result = 0;
|
||||
@@ -90,12 +125,15 @@ Instruction::exec(Cpu& cpu) {
|
||||
break;
|
||||
case AluOperations::OpCode::LSL:
|
||||
result = eval_shift(ShiftType::LSL, op_1, op_2, carry);
|
||||
cpu.internal_cycle();
|
||||
break;
|
||||
case AluOperations::OpCode::LSR:
|
||||
result = eval_shift(ShiftType::LSR, op_1, op_2, carry);
|
||||
cpu.internal_cycle();
|
||||
break;
|
||||
case AluOperations::OpCode::ASR:
|
||||
result = eval_shift(ShiftType::ASR, op_1, op_2, carry);
|
||||
cpu.internal_cycle();
|
||||
break;
|
||||
case AluOperations::OpCode::ADC:
|
||||
result = add(op_1, op_2, carry, overflow, carry);
|
||||
@@ -105,6 +143,7 @@ Instruction::exec(Cpu& cpu) {
|
||||
break;
|
||||
case AluOperations::OpCode::ROR:
|
||||
result = eval_shift(ShiftType::ROR, op_1, op_2, carry);
|
||||
cpu.internal_cycle();
|
||||
break;
|
||||
case AluOperations::OpCode::NEG:
|
||||
result = -op_2;
|
||||
@@ -120,6 +159,9 @@ Instruction::exec(Cpu& cpu) {
|
||||
break;
|
||||
case AluOperations::OpCode::MUL:
|
||||
result = op_1 * op_2;
|
||||
// mI cycles
|
||||
for (int i = 0; i < multiplier_array_cycles(op_2); i++)
|
||||
cpu.internal_cycle();
|
||||
break;
|
||||
case AluOperations::OpCode::BIC:
|
||||
result = op_1 & ~op_2;
|
||||
@@ -137,6 +179,20 @@ Instruction::exec(Cpu& cpu) {
|
||||
set_cc(carry, overflow, get_bit(result, 31), result == 0);
|
||||
},
|
||||
[&cpu, set_cc](HiRegisterOperations& data) {
|
||||
/*
|
||||
Always
|
||||
======
|
||||
S -> prefetched instruction in step()
|
||||
|
||||
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 2S + N cycles
|
||||
*/
|
||||
|
||||
uint32_t op_1 = cpu.gpr[data.rd];
|
||||
uint32_t op_2 = cpu.gpr[data.rs];
|
||||
|
||||
@@ -191,95 +247,157 @@ Instruction::exec(Cpu& cpu) {
|
||||
}
|
||||
},
|
||||
[&cpu](PcRelativeLoad& data) {
|
||||
/*
|
||||
S -> reading instruction in step()
|
||||
N -> read from target
|
||||
I -> stored in register
|
||||
Total = S + N + I cycles
|
||||
*/
|
||||
uint32_t pc = cpu.pc;
|
||||
rst_bit(pc, 0);
|
||||
rst_bit(pc, 1);
|
||||
|
||||
cpu.gpr[data.rd] = cpu.bus->read_word(pc + data.word);
|
||||
cpu.gpr[data.rd] = cpu.bus->read_word(pc + data.word, false);
|
||||
|
||||
cpu.internal_cycle();
|
||||
|
||||
// last read is unrelated
|
||||
cpu.sequential = false;
|
||||
},
|
||||
[&cpu](LoadStoreRegisterOffset& data) {
|
||||
/*
|
||||
Load
|
||||
====
|
||||
S -> reading instruction in step()
|
||||
N -> read from target
|
||||
I -> stored in register
|
||||
Total = S + N + I
|
||||
|
||||
Store
|
||||
=====
|
||||
N -> calculating memory address
|
||||
N -> write at target
|
||||
Total = 2N
|
||||
*/
|
||||
|
||||
uint32_t address = cpu.gpr[data.rb] + cpu.gpr[data.ro];
|
||||
|
||||
if (data.load) {
|
||||
if (data.byte) {
|
||||
cpu.gpr[data.rd] = cpu.bus->read_byte(address);
|
||||
cpu.gpr[data.rd] = cpu.bus->read_byte(address, false);
|
||||
} else {
|
||||
cpu.gpr[data.rd] = cpu.bus->read_word(address);
|
||||
cpu.gpr[data.rd] = cpu.bus->read_word(address, false);
|
||||
}
|
||||
cpu.internal_cycle();
|
||||
} else {
|
||||
if (data.byte) {
|
||||
cpu.bus->write_byte(address, cpu.gpr[data.rd] & 0xFF);
|
||||
cpu.bus->write_byte(
|
||||
address, cpu.gpr[data.rd] & 0xFF, false);
|
||||
} else {
|
||||
cpu.bus->write_word(address, cpu.gpr[data.rd]);
|
||||
cpu.bus->write_word(address, cpu.gpr[data.rd], false);
|
||||
}
|
||||
}
|
||||
|
||||
// last read/write is unrelated
|
||||
cpu.sequential = false;
|
||||
},
|
||||
[&cpu](LoadStoreSignExtendedHalfword& data) {
|
||||
// Same cycles as above
|
||||
|
||||
uint32_t address = cpu.gpr[data.rb] + cpu.gpr[data.ro];
|
||||
|
||||
switch (data.s << 1 | data.h) {
|
||||
case 0b00:
|
||||
cpu.bus->write_halfword(address, cpu.gpr[data.rd] & 0xFFFF);
|
||||
cpu.bus->write_halfword(
|
||||
address, cpu.gpr[data.rd] & 0xFFFF, false);
|
||||
break;
|
||||
case 0b01:
|
||||
cpu.gpr[data.rd] = cpu.bus->read_halfword(address);
|
||||
cpu.gpr[data.rd] = cpu.bus->read_halfword(address, false);
|
||||
cpu.internal_cycle();
|
||||
break;
|
||||
case 0b10:
|
||||
// sign extend and load the byte
|
||||
cpu.gpr[data.rd] =
|
||||
(static_cast<int32_t>(cpu.bus->read_byte(address))
|
||||
(static_cast<int32_t>(cpu.bus->read_byte(address, false))
|
||||
<< 24) >>
|
||||
24;
|
||||
cpu.internal_cycle();
|
||||
break;
|
||||
case 0b11:
|
||||
// sign extend the halfword
|
||||
cpu.gpr[data.rd] =
|
||||
(static_cast<int32_t>(cpu.bus->read_halfword(address))
|
||||
(static_cast<int32_t>(
|
||||
cpu.bus->read_halfword(address, false))
|
||||
<< 16) >>
|
||||
16;
|
||||
cpu.internal_cycle();
|
||||
break;
|
||||
|
||||
// unreachable
|
||||
default: {
|
||||
}
|
||||
}
|
||||
|
||||
// last read/write is unrelated
|
||||
cpu.sequential = false;
|
||||
},
|
||||
[&cpu](LoadStoreImmediateOffset& data) {
|
||||
// Same cycles as above
|
||||
|
||||
uint32_t address = cpu.gpr[data.rb] + data.offset;
|
||||
|
||||
if (data.load) {
|
||||
if (data.byte) {
|
||||
cpu.gpr[data.rd] = cpu.bus->read_byte(address);
|
||||
cpu.gpr[data.rd] = cpu.bus->read_byte(address, false);
|
||||
} else {
|
||||
cpu.gpr[data.rd] = cpu.bus->read_word(address);
|
||||
cpu.gpr[data.rd] = cpu.bus->read_word(address, false);
|
||||
}
|
||||
cpu.internal_cycle();
|
||||
} else {
|
||||
if (data.byte) {
|
||||
cpu.bus->write_byte(address, cpu.gpr[data.rd] & 0xFF);
|
||||
cpu.bus->write_byte(
|
||||
address, cpu.gpr[data.rd] & 0xFF, false);
|
||||
} else {
|
||||
cpu.bus->write_word(address, cpu.gpr[data.rd]);
|
||||
cpu.bus->write_word(address, cpu.gpr[data.rd], false);
|
||||
}
|
||||
}
|
||||
|
||||
// last read/write is unrelated
|
||||
cpu.sequential = false;
|
||||
},
|
||||
[&cpu](LoadStoreHalfword& data) {
|
||||
// Same cycles as above
|
||||
|
||||
uint32_t address = cpu.gpr[data.rb] + data.offset;
|
||||
|
||||
if (data.load) {
|
||||
cpu.gpr[data.rd] = cpu.bus->read_halfword(address);
|
||||
cpu.internal_cycle();
|
||||
} else {
|
||||
cpu.bus->write_halfword(address, cpu.gpr[data.rd] & 0xFFFF);
|
||||
}
|
||||
|
||||
// last read/write is unrelated
|
||||
cpu.sequential = false;
|
||||
},
|
||||
[&cpu](SpRelativeLoad& data) {
|
||||
// Same cycles as above
|
||||
|
||||
uint32_t address = cpu.sp + data.word;
|
||||
|
||||
if (data.load) {
|
||||
cpu.gpr[data.rd] = cpu.bus->read_word(address);
|
||||
cpu.internal_cycle();
|
||||
} else {
|
||||
cpu.bus->write_word(address, cpu.gpr[data.rd]);
|
||||
}
|
||||
|
||||
// last read/write is unrelated
|
||||
cpu.sequential = false;
|
||||
},
|
||||
[&cpu](LoadAddress& data) {
|
||||
// 1S cycle in step()
|
||||
|
||||
if (data.sp) {
|
||||
cpu.gpr[data.rd] = cpu.sp + data.word;
|
||||
} else {
|
||||
@@ -288,15 +406,38 @@ Instruction::exec(Cpu& cpu) {
|
||||
cpu.gpr[data.rd] = (cpu.pc & ~(1 << 1)) + data.word;
|
||||
}
|
||||
},
|
||||
[&cpu](AddOffsetStackPointer& data) { cpu.sp += data.word; },
|
||||
[&cpu](AddOffsetStackPointer& data) {
|
||||
// 1S cycle in step()
|
||||
|
||||
cpu.sp += data.word;
|
||||
},
|
||||
[&cpu](PushPopRegister& 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;
|
||||
bool sequential = false;
|
||||
|
||||
if (data.load) {
|
||||
for (uint8_t i = 0; i < 8; i++) {
|
||||
if (get_bit(data.regs, i)) {
|
||||
cpu.gpr[i] = cpu.bus->read_word(cpu.sp);
|
||||
cpu.gpr[i] = cpu.bus->read_word(cpu.sp, sequential);
|
||||
cpu.sp += alignment;
|
||||
sequential = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -305,6 +446,9 @@ Instruction::exec(Cpu& cpu) {
|
||||
cpu.sp += alignment;
|
||||
cpu.is_flushed = true;
|
||||
}
|
||||
|
||||
// I
|
||||
cpu.internal_cycle();
|
||||
} else {
|
||||
if (data.pclr) {
|
||||
cpu.sp -= alignment;
|
||||
@@ -314,35 +458,68 @@ Instruction::exec(Cpu& cpu) {
|
||||
for (int8_t i = 7; i >= 0; i--) {
|
||||
if (get_bit(data.regs, i)) {
|
||||
cpu.sp -= alignment;
|
||||
cpu.bus->write_word(cpu.sp, cpu.gpr[i]);
|
||||
cpu.bus->write_word(cpu.sp, cpu.gpr[i], sequential);
|
||||
sequential = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// last read/write is unrelated
|
||||
cpu.sequential = false;
|
||||
},
|
||||
[&cpu](MultipleLoad& 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
|
||||
Total = nS + N + 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;
|
||||
|
||||
uint32_t rb = cpu.gpr[data.rb];
|
||||
uint32_t rb = cpu.gpr[data.rb];
|
||||
bool sequential = false;
|
||||
|
||||
if (data.load) {
|
||||
for (uint8_t i = 0; i < 8; i++) {
|
||||
if (get_bit(data.regs, i)) {
|
||||
cpu.gpr[i] = cpu.bus->read_word(rb);
|
||||
cpu.gpr[i] = cpu.bus->read_word(rb, sequential);
|
||||
rb += alignment;
|
||||
sequential = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (int8_t i = 7; i >= 0; i--) {
|
||||
if (get_bit(data.regs, i)) {
|
||||
rb -= alignment;
|
||||
cpu.bus->write_word(rb, cpu.gpr[i]);
|
||||
cpu.bus->write_word(rb, cpu.gpr[i], sequential);
|
||||
sequential = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cpu.gpr[data.rb] = rb;
|
||||
|
||||
// last read/write is unrelated
|
||||
cpu.sequential = false;
|
||||
},
|
||||
[&cpu](ConditionalBranch& data) {
|
||||
/*
|
||||
S -> reading instruction in step()
|
||||
N+S -> if condition is true, branch and refill pipeline
|
||||
Total = S or 2S + N
|
||||
*/
|
||||
|
||||
if (data.condition == Condition::AL)
|
||||
glogger.warn("Condition 1110 (AL) is undefined");
|
||||
|
||||
@@ -353,6 +530,12 @@ Instruction::exec(Cpu& cpu) {
|
||||
cpu.is_flushed = true;
|
||||
},
|
||||
[&cpu](SoftwareInterrupt& data) {
|
||||
/*
|
||||
S -> reading instruction in step()
|
||||
N+S -> refill pipeline
|
||||
Total = 2S + N
|
||||
*/
|
||||
|
||||
// next instruction is one instruction behind PC
|
||||
cpu.lr = cpu.pc - INSTRUCTION_SIZE;
|
||||
cpu.spsr = cpu.cpsr;
|
||||
@@ -362,10 +545,24 @@ Instruction::exec(Cpu& cpu) {
|
||||
cpu.is_flushed = true;
|
||||
},
|
||||
[&cpu](UnconditionalBranch& data) {
|
||||
/*
|
||||
S -> reading instruction in step()
|
||||
N+S -> branch and refill pipeline
|
||||
Total = 2S + N
|
||||
*/
|
||||
|
||||
cpu.pc += data.offset;
|
||||
cpu.is_flushed = true;
|
||||
},
|
||||
[&cpu](LongBranchWithLink& data) {
|
||||
/*
|
||||
S -> prefetched 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()
|
||||
*/
|
||||
|
||||
// 12 bit integer
|
||||
int32_t offset = data.offset;
|
||||
|
||||
|
Reference in New Issue
Block a user