2024-03-04 17:06:43 +00:00
|
|
|
//
|
|
|
|
// Archimedes.cpp
|
|
|
|
// Clock Signal
|
|
|
|
//
|
|
|
|
// Created by Thomas Harte on 04/03/2024.
|
|
|
|
// Copyright © 2024 Thomas Harte. All rights reserved.
|
|
|
|
//
|
|
|
|
|
|
|
|
#include "Archimedes.hpp"
|
|
|
|
|
2024-03-20 18:25:20 +00:00
|
|
|
#include "HalfDuplexSerial.hpp"
|
|
|
|
#include "InputOutputController.hpp"
|
|
|
|
#include "Keyboard.hpp"
|
2024-03-23 19:43:04 +00:00
|
|
|
#include "KeyboardMapper.hpp"
|
2024-03-20 18:25:20 +00:00
|
|
|
#include "MemoryController.hpp"
|
|
|
|
#include "Sound.hpp"
|
|
|
|
|
2024-03-04 17:06:43 +00:00
|
|
|
#include "../../AudioProducer.hpp"
|
|
|
|
#include "../../KeyboardMachine.hpp"
|
|
|
|
#include "../../MediaTarget.hpp"
|
2024-04-06 17:32:59 +00:00
|
|
|
#include "../../MouseMachine.hpp"
|
2024-03-04 17:06:43 +00:00
|
|
|
#include "../../ScanProducer.hpp"
|
|
|
|
#include "../../TimedMachine.hpp"
|
|
|
|
|
2024-03-25 19:03:54 +00:00
|
|
|
#include "../../../Activity/Source.hpp"
|
|
|
|
|
2024-03-19 15:34:10 +00:00
|
|
|
#include "../../../InstructionSets/ARM/Disassembler.hpp"
|
2024-03-04 17:08:46 +00:00
|
|
|
#include "../../../InstructionSets/ARM/Executor.hpp"
|
2024-03-06 14:54:39 +00:00
|
|
|
#include "../../../Outputs/Log.hpp"
|
2024-03-16 19:00:23 +00:00
|
|
|
#include "../../../Components/I2C/I2C.hpp"
|
2024-03-04 17:08:46 +00:00
|
|
|
|
2024-03-05 02:09:24 +00:00
|
|
|
#include <algorithm>
|
|
|
|
#include <array>
|
2024-03-09 03:54:42 +00:00
|
|
|
#include <set>
|
2024-03-05 02:09:24 +00:00
|
|
|
#include <vector>
|
|
|
|
|
2024-04-18 02:15:05 +00:00
|
|
|
namespace Archimedes {
|
2024-03-05 02:43:06 +00:00
|
|
|
|
2024-04-18 02:15:05 +00:00
|
|
|
#ifndef NDEBUG
|
|
|
|
namespace {
|
2024-03-06 14:54:39 +00:00
|
|
|
Log::Logger<Log::Source::Archimedes> logger;
|
2024-03-05 02:43:06 +00:00
|
|
|
}
|
|
|
|
|
2024-03-31 22:15:48 +00:00
|
|
|
template <InstructionSet::ARM::Model model, typename Executor>
|
|
|
|
struct HackyDebugger {
|
|
|
|
void notify(uint32_t address, uint32_t instruction, Executor &executor) {
|
|
|
|
pc_history[pc_history_ptr] = address;
|
|
|
|
pc_history_ptr = (pc_history_ptr + 1) % pc_history.size();
|
|
|
|
|
|
|
|
// if(
|
|
|
|
// executor_.pc() > 0x038021d0 &&
|
|
|
|
// last_r1 != executor_.registers()[1]
|
|
|
|
// ||
|
|
|
|
// (
|
|
|
|
// last_link != executor_.registers()[14] ||
|
|
|
|
// last_r0 != executor_.registers()[0] ||
|
|
|
|
// last_r10 != executor_.registers()[10] ||
|
|
|
|
// last_r1 != executor_.registers()[1]
|
|
|
|
// )
|
|
|
|
// ) {
|
|
|
|
// logger.info().append("%08x modified R14 to %08x; R0 to %08x; R10 to %08x; R1 to %08x",
|
|
|
|
// last_pc,
|
|
|
|
// executor_.registers()[14],
|
|
|
|
// executor_.registers()[0],
|
|
|
|
// executor_.registers()[10],
|
|
|
|
// executor_.registers()[1]
|
|
|
|
// );
|
|
|
|
// logger.info().append("%08x modified R1 to %08x",
|
|
|
|
// last_pc,
|
|
|
|
// executor_.registers()[1]
|
|
|
|
// );
|
|
|
|
// last_link = executor_.registers()[14];
|
|
|
|
// last_r0 = executor_.registers()[0];
|
|
|
|
// last_r10 = executor_.registers()[10];
|
|
|
|
// last_r1 = executor_.registers()[1];
|
|
|
|
// }
|
|
|
|
|
2024-04-05 02:16:11 +00:00
|
|
|
// if(instruction == 0xe8fd7fff) {
|
|
|
|
// printf("At %08x [%d]; after last PC %08x and %zu ago was %08x\n",
|
|
|
|
// address,
|
|
|
|
// instr_count,
|
|
|
|
// pc_history[(pc_history_ptr - 2 + pc_history.size()) % pc_history.size()],
|
|
|
|
// pc_history.size(),
|
|
|
|
// pc_history[pc_history_ptr]);
|
|
|
|
// }
|
2024-03-31 22:15:48 +00:00
|
|
|
// last_r9 = executor_.registers()[9];
|
|
|
|
|
2024-04-05 01:59:18 +00:00
|
|
|
// log |= address == 0x038031c4;
|
|
|
|
// log |= instr_count == 53552731 - 30;
|
2024-03-31 22:15:48 +00:00
|
|
|
// log &= executor_.pc() != 0x000000a0;
|
|
|
|
|
|
|
|
// log = (executor_.pc() == 0x038162afc) || (executor_.pc() == 0x03824b00);
|
|
|
|
// log |= instruction & ;
|
|
|
|
|
2024-04-05 02:16:11 +00:00
|
|
|
// The following has the effect of logging all taken SWIs and their return codes.
|
|
|
|
/* if(
|
2024-03-31 22:15:48 +00:00
|
|
|
(instruction & 0x0f00'0000) == 0x0f00'0000 &&
|
|
|
|
executor.registers().test(InstructionSet::ARM::Condition(instruction >> 28))
|
|
|
|
) {
|
|
|
|
if(instruction & 0x2'0000) {
|
|
|
|
swis.emplace_back();
|
2024-03-31 22:18:26 +00:00
|
|
|
swis.back().count = swi_count++;
|
2024-03-31 22:15:48 +00:00
|
|
|
swis.back().opcode = instruction;
|
|
|
|
swis.back().address = executor.pc();
|
|
|
|
swis.back().return_address = executor.registers().pc(4);
|
|
|
|
for(int c = 0; c < 10; c++) swis.back().regs[c] = executor.registers()[uint32_t(c)];
|
|
|
|
|
|
|
|
// Possibly capture more detail.
|
|
|
|
//
|
|
|
|
// Cf. http://productsdb.riscos.com/support/developers/prm_index/numswilist.html
|
|
|
|
uint32_t pointer = 0;
|
|
|
|
switch(instruction & 0xfd'ffff) {
|
|
|
|
case 0x41501:
|
|
|
|
swis.back().swi_name = "MessageTrans_OpenFile";
|
|
|
|
|
|
|
|
// R0: pointer to file descriptor; R1: pointer to filename; R2: pointer to hold file data.
|
|
|
|
// (R0 and R1 are in the RMA if R2 = 0)
|
|
|
|
pointer = executor.registers()[1];
|
|
|
|
break;
|
|
|
|
case 0x41502:
|
|
|
|
swis.back().swi_name = "MessageTrans_Lookup";
|
|
|
|
break;
|
|
|
|
case 0x41506:
|
|
|
|
swis.back().swi_name = "MessageTrans_ErrorLookup";
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x4028a:
|
|
|
|
swis.back().swi_name = "Podule_EnumerateChunksWithInfo";
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x4000a:
|
|
|
|
swis.back().swi_name = "Econet_ReadLocalStationAndNet";
|
|
|
|
break;
|
|
|
|
case 0x4000e:
|
|
|
|
swis.back().swi_name = "Econet_SetProtection";
|
|
|
|
break;
|
|
|
|
case 0x40015:
|
|
|
|
swis.back().swi_name = "Econet_ClaimPort";
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x40541:
|
|
|
|
swis.back().swi_name = "FileCore_Create";
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x80156:
|
|
|
|
case 0x8015b:
|
|
|
|
swis.back().swi_name = "PDriver_MiscOpForDriver";
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x05:
|
|
|
|
swis.back().swi_name = "OS_CLI";
|
|
|
|
pointer = executor.registers()[0];
|
|
|
|
break;
|
|
|
|
case 0x0d:
|
|
|
|
swis.back().swi_name = "OS_Find";
|
|
|
|
if(executor.registers()[0] >= 0x40) {
|
|
|
|
pointer = executor.registers()[1];
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 0x1d:
|
|
|
|
swis.back().swi_name = "OS_Heap";
|
|
|
|
break;
|
|
|
|
case 0x1e:
|
|
|
|
swis.back().swi_name = "OS_Module";
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x20:
|
|
|
|
swis.back().swi_name = "OS_Release";
|
|
|
|
break;
|
|
|
|
case 0x21:
|
|
|
|
swis.back().swi_name = "OS_ReadUnsigned";
|
|
|
|
break;
|
|
|
|
case 0x23:
|
|
|
|
swis.back().swi_name = "OS_ReadVarVal";
|
|
|
|
|
|
|
|
// R0: pointer to variable name.
|
|
|
|
pointer = executor.registers()[0];
|
|
|
|
break;
|
|
|
|
case 0x24:
|
|
|
|
swis.back().swi_name = "OS_SetVarVal";
|
|
|
|
|
|
|
|
// R0: pointer to variable name.
|
|
|
|
pointer = executor.registers()[0];
|
|
|
|
break;
|
|
|
|
case 0x26:
|
|
|
|
swis.back().swi_name = "OS_GSRead";
|
|
|
|
break;
|
|
|
|
case 0x27:
|
|
|
|
swis.back().swi_name = "OS_GSTrans";
|
|
|
|
pointer = executor.registers()[0];
|
|
|
|
break;
|
|
|
|
case 0x29:
|
|
|
|
swis.back().swi_name = "OS_FSControl";
|
|
|
|
break;
|
|
|
|
case 0x2a:
|
|
|
|
swis.back().swi_name = "OS_ChangeDynamicArea";
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x4c:
|
|
|
|
swis.back().swi_name = "OS_ReleaseDeviceVector";
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x43057:
|
|
|
|
swis.back().swi_name = "Territory_LowerCaseTable";
|
|
|
|
break;
|
|
|
|
case 0x43058:
|
|
|
|
swis.back().swi_name = "Territory_UpperCaseTable";
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x42fc0:
|
|
|
|
swis.back().swi_name = "Portable_Speed";
|
|
|
|
break;
|
|
|
|
case 0x42fc1:
|
|
|
|
swis.back().swi_name = "Portable_Control";
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(pointer) {
|
|
|
|
while(true) {
|
|
|
|
uint8_t next;
|
|
|
|
executor.bus.template read<uint8_t>(pointer, next, InstructionSet::ARM::Mode::Supervisor, false);
|
|
|
|
++pointer;
|
|
|
|
|
|
|
|
if(next < 32) break;
|
|
|
|
swis.back().value_name.push_back(static_cast<char>(next));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if(executor.registers().pc_status(0) & InstructionSet::ARM::ConditionCode::Overflow) {
|
|
|
|
logger.error().append("SWI called with V set");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if(!swis.empty() && executor.pc() == swis.back().return_address) {
|
|
|
|
// Overflow set => SWI failure.
|
|
|
|
auto &back = swis.back();
|
|
|
|
if(executor.registers().pc_status(0) & InstructionSet::ARM::ConditionCode::Overflow) {
|
|
|
|
auto info = logger.info();
|
|
|
|
|
2024-03-31 22:18:26 +00:00
|
|
|
info.append("[%d] Failed swi ", back.count);
|
2024-03-31 22:15:48 +00:00
|
|
|
if(back.swi_name.empty()) {
|
|
|
|
info.append("&%x", back.opcode & 0xfd'ffff);
|
|
|
|
} else {
|
|
|
|
info.append("%s", back.swi_name.c_str());
|
|
|
|
}
|
|
|
|
|
|
|
|
if(!back.value_name.empty()) {
|
|
|
|
info.append(" %s", back.value_name.c_str());
|
|
|
|
}
|
|
|
|
|
|
|
|
info.append(" @ %08x ", back.address);
|
|
|
|
for(uint32_t c = 0; c < 10; c++) {
|
|
|
|
info.append("r%d:%08x ", c, back.regs[c]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
swis.pop_back();
|
2024-04-05 02:16:11 +00:00
|
|
|
}*/
|
2024-03-31 22:15:48 +00:00
|
|
|
|
|
|
|
if(log) {
|
|
|
|
InstructionSet::ARM::Disassembler<model> disassembler;
|
|
|
|
InstructionSet::ARM::dispatch<model>(instruction, disassembler);
|
|
|
|
|
|
|
|
auto info = logger.info();
|
|
|
|
info.append("[%d] %08x: %08x\t\t%s\t prior:[",
|
|
|
|
instr_count,
|
|
|
|
executor.pc(),
|
|
|
|
instruction,
|
|
|
|
disassembler.last().to_string(executor.pc()).c_str());
|
|
|
|
for(uint32_t c = 0; c < 15; c++) {
|
|
|
|
info.append("r%d:%08x ", c, executor.registers()[c]);
|
|
|
|
}
|
|
|
|
info.append("]");
|
|
|
|
}
|
2024-04-05 02:16:11 +00:00
|
|
|
// opcodes.insert(instruction);
|
|
|
|
// if(accumulate) {
|
|
|
|
// int c = 0;
|
|
|
|
// for(auto instr : opcodes) {
|
|
|
|
// printf("0x%08x, ", instr);
|
|
|
|
// ++c;
|
|
|
|
// if(!(c&15)) printf("\n");
|
|
|
|
// }
|
|
|
|
// accumulate = false;
|
|
|
|
// }
|
2024-03-31 22:15:48 +00:00
|
|
|
|
|
|
|
++instr_count;
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
std::array<uint32_t, 75> pc_history;
|
|
|
|
std::size_t pc_history_ptr = 0;
|
|
|
|
uint32_t instr_count = 0;
|
2024-03-31 22:18:26 +00:00
|
|
|
uint32_t swi_count = 0;
|
2024-03-31 22:15:48 +00:00
|
|
|
|
|
|
|
struct SWICall {
|
2024-03-31 22:18:26 +00:00
|
|
|
uint32_t count;
|
2024-03-31 22:15:48 +00:00
|
|
|
uint32_t opcode;
|
|
|
|
uint32_t address;
|
|
|
|
uint32_t regs[10];
|
|
|
|
uint32_t return_address;
|
|
|
|
std::string value_name;
|
|
|
|
std::string swi_name;
|
|
|
|
};
|
|
|
|
std::vector<SWICall> swis;
|
|
|
|
uint32_t last_pc = 0;
|
|
|
|
// uint32_t last_r9 = 0;
|
|
|
|
bool log = false;
|
|
|
|
bool accumulate = true;
|
|
|
|
|
|
|
|
std::set<uint32_t> opcodes;
|
|
|
|
};
|
|
|
|
#else
|
2024-03-31 23:17:55 +00:00
|
|
|
template <InstructionSet::ARM::Model model, typename Executor>
|
2024-03-31 22:15:48 +00:00
|
|
|
struct HackyDebugger {
|
|
|
|
void notify(uint32_t, uint32_t, Executor &) {}
|
|
|
|
};
|
|
|
|
#endif
|
|
|
|
|
2024-03-04 17:06:43 +00:00
|
|
|
class ConcreteMachine:
|
|
|
|
public Machine,
|
2024-04-14 01:54:50 +00:00
|
|
|
public MachineTypes::AudioProducer,
|
2024-03-23 19:43:04 +00:00
|
|
|
public MachineTypes::MappedKeyboardMachine,
|
2024-03-05 02:09:24 +00:00
|
|
|
public MachineTypes::MediaTarget,
|
2024-04-06 17:32:59 +00:00
|
|
|
public MachineTypes::MouseMachine,
|
2024-03-04 17:06:43 +00:00
|
|
|
public MachineTypes::TimedMachine,
|
2024-03-25 19:03:54 +00:00
|
|
|
public MachineTypes::ScanProducer,
|
|
|
|
public Activity::Source
|
2024-03-04 17:06:43 +00:00
|
|
|
{
|
2024-03-20 15:42:37 +00:00
|
|
|
private:
|
|
|
|
// TODO: pick a sensible clock rate; this is just code for '24 MIPS, please'.
|
|
|
|
static constexpr int ClockRate = 24'000'000;
|
2024-03-07 16:12:40 +00:00
|
|
|
|
2024-03-20 15:42:37 +00:00
|
|
|
// Runs for 24 cycles, distributing calls to the various ticking subsystems
|
|
|
|
// 'correctly' (i.e. correctly for the approximation in use).
|
|
|
|
//
|
|
|
|
// The implementation of this is coupled to the ClockRate above, hence its
|
|
|
|
// appearance here.
|
2024-03-22 14:01:34 +00:00
|
|
|
template <int video_divider>
|
2024-03-20 15:42:37 +00:00
|
|
|
void macro_tick() {
|
|
|
|
macro_counter_ -= 24;
|
|
|
|
|
|
|
|
// This is a 24-cycle window, so at 24Mhz macro_tick() is called at 1Mhz.
|
|
|
|
// Hence, required ticks are:
|
|
|
|
//
|
|
|
|
// * CPU: 24;
|
2024-03-22 14:01:34 +00:00
|
|
|
// * video: 24 / video_divider;
|
2024-04-08 01:22:35 +00:00
|
|
|
// * floppy: 8;
|
2024-03-20 15:42:37 +00:00
|
|
|
// * timers: 2;
|
2024-03-21 15:24:47 +00:00
|
|
|
// * sound: 1.
|
2024-03-20 15:42:37 +00:00
|
|
|
|
2024-03-22 14:01:34 +00:00
|
|
|
tick_cpu_video<0, video_divider>(); tick_cpu_video<1, video_divider>();
|
2024-04-08 01:22:35 +00:00
|
|
|
tick_cpu_video<2, video_divider>(); tick_floppy();
|
|
|
|
tick_cpu_video<3, video_divider>(); tick_cpu_video<4, video_divider>();
|
|
|
|
tick_cpu_video<5, video_divider>(); tick_floppy();
|
2024-03-22 14:01:34 +00:00
|
|
|
tick_cpu_video<6, video_divider>(); tick_cpu_video<7, video_divider>();
|
2024-04-08 01:22:35 +00:00
|
|
|
tick_cpu_video<8, video_divider>(); tick_floppy();
|
|
|
|
tick_cpu_video<9, video_divider>(); tick_cpu_video<10, video_divider>();
|
|
|
|
tick_cpu_video<11, video_divider>(); tick_floppy();
|
2024-03-20 15:42:37 +00:00
|
|
|
tick_timers();
|
|
|
|
|
2024-03-22 14:01:34 +00:00
|
|
|
tick_cpu_video<12, video_divider>(); tick_cpu_video<13, video_divider>();
|
2024-04-08 01:22:35 +00:00
|
|
|
tick_cpu_video<14, video_divider>(); tick_floppy();
|
|
|
|
tick_cpu_video<15, video_divider>(); tick_cpu_video<16, video_divider>();
|
|
|
|
tick_cpu_video<17, video_divider>(); tick_floppy();
|
2024-03-22 14:01:34 +00:00
|
|
|
tick_cpu_video<18, video_divider>(); tick_cpu_video<19, video_divider>();
|
2024-04-08 01:22:35 +00:00
|
|
|
tick_cpu_video<20, video_divider>(); tick_floppy();
|
|
|
|
tick_cpu_video<21, video_divider>(); tick_cpu_video<22, video_divider>();
|
|
|
|
tick_cpu_video<23, video_divider>(); tick_floppy();
|
2024-03-20 15:42:37 +00:00
|
|
|
tick_timers();
|
2024-03-21 15:24:47 +00:00
|
|
|
tick_sound();
|
2024-03-20 15:42:37 +00:00
|
|
|
}
|
|
|
|
int macro_counter_ = 0;
|
2024-03-07 16:12:40 +00:00
|
|
|
|
2024-03-22 14:01:34 +00:00
|
|
|
template <int offset, int video_divider>
|
|
|
|
void tick_cpu_video() {
|
|
|
|
if constexpr (!(offset % video_divider)) {
|
|
|
|
tick_video();
|
|
|
|
}
|
2024-03-25 19:50:59 +00:00
|
|
|
|
|
|
|
#ifndef NDEBUG
|
2024-03-26 01:31:33 +00:00
|
|
|
// Debug mode: run CPU a lot slower. Actually at close to original advertised MIPS speed.
|
|
|
|
if constexpr (offset & 7) return;
|
2024-03-25 19:50:59 +00:00
|
|
|
#endif
|
2024-04-17 02:32:00 +00:00
|
|
|
if constexpr (offset & 1) return;
|
2024-03-25 19:50:59 +00:00
|
|
|
tick_cpu();
|
2024-03-22 14:01:34 +00:00
|
|
|
}
|
|
|
|
|
2024-03-04 17:06:43 +00:00
|
|
|
public:
|
|
|
|
ConcreteMachine(
|
|
|
|
const Analyser::Static::Target &target,
|
|
|
|
const ROMMachine::ROMFetcher &rom_fetcher
|
2024-04-08 02:08:12 +00:00
|
|
|
) : executor_(*this, *this, *this) {
|
2024-03-07 16:12:40 +00:00
|
|
|
set_clock_rate(ClockRate);
|
|
|
|
|
2024-04-02 01:44:42 +00:00
|
|
|
constexpr ROM::Name risc_os = ROM::Name::AcornRISCOS311;
|
2024-03-05 02:09:24 +00:00
|
|
|
ROM::Request request(risc_os);
|
|
|
|
auto roms = rom_fetcher(request);
|
|
|
|
if(!request.validate(roms)) {
|
|
|
|
throw ROMMachine::Error::MissingROMs;
|
|
|
|
}
|
|
|
|
|
|
|
|
executor_.bus.set_rom(roms.find(risc_os)->second);
|
|
|
|
insert_media(target.media);
|
2024-04-20 01:30:15 +00:00
|
|
|
|
|
|
|
fill_pipeline(0);
|
2024-03-04 17:06:43 +00:00
|
|
|
}
|
|
|
|
|
2024-03-20 18:25:20 +00:00
|
|
|
void update_interrupts() {
|
|
|
|
using Exception = InstructionSet::ARM::Registers::Exception;
|
|
|
|
|
|
|
|
const int requests = executor_.bus.interrupt_mask();
|
2024-04-20 02:21:23 +00:00
|
|
|
if((requests & InterruptRequests::FIQ) && executor_.registers().would_interrupt<Exception::FIQ>()) {
|
|
|
|
pipeline_.reschedule(Pipeline::SWISubversion::FIQ);
|
2024-03-20 18:25:20 +00:00
|
|
|
return;
|
|
|
|
}
|
2024-04-20 02:21:23 +00:00
|
|
|
if((requests & InterruptRequests::IRQ) && executor_.registers().would_interrupt<Exception::IRQ>()) {
|
|
|
|
pipeline_.reschedule(Pipeline::SWISubversion::IRQ);
|
2024-03-20 18:25:20 +00:00
|
|
|
}
|
2024-03-12 15:34:31 +00:00
|
|
|
}
|
2024-03-04 17:06:43 +00:00
|
|
|
|
2024-04-08 02:08:12 +00:00
|
|
|
void did_set_status() {
|
2024-04-20 01:30:15 +00:00
|
|
|
// This might have been a change of mode, so...
|
2024-04-30 02:35:01 +00:00
|
|
|
trans_ = executor_.registers().mode() == InstructionSet::ARM::Mode::User;
|
2024-04-20 01:30:15 +00:00
|
|
|
fill_pipeline(executor_.pc());
|
2024-04-08 02:08:12 +00:00
|
|
|
update_interrupts();
|
|
|
|
}
|
|
|
|
|
2024-04-18 23:30:07 +00:00
|
|
|
void did_set_pc() {
|
2024-04-20 01:30:15 +00:00
|
|
|
fill_pipeline(executor_.pc());
|
2024-04-18 23:30:07 +00:00
|
|
|
}
|
|
|
|
|
2024-04-19 02:13:58 +00:00
|
|
|
bool should_swi(uint32_t) {
|
2024-04-20 01:30:15 +00:00
|
|
|
using Exception = InstructionSet::ARM::Registers::Exception;
|
|
|
|
using SWISubversion = Pipeline::SWISubversion;
|
|
|
|
|
|
|
|
switch(pipeline_.swi_subversion()) {
|
|
|
|
case Pipeline::SWISubversion::None:
|
|
|
|
return true;
|
|
|
|
|
|
|
|
case SWISubversion::DataAbort:
|
|
|
|
// executor_.set_pc(executor_.pc() - 4);
|
2024-04-20 02:21:23 +00:00
|
|
|
executor_.registers().exception<Exception::DataAbort>();
|
|
|
|
break;
|
|
|
|
|
|
|
|
// FIQ and IRQ decrement the PC because their apperance in the pipeline causes
|
|
|
|
// it to look as though they were fetched, but they weren't.
|
|
|
|
case SWISubversion::FIQ:
|
|
|
|
executor_.set_pc(executor_.pc() - 4);
|
|
|
|
executor_.registers().exception<Exception::FIQ>();
|
|
|
|
break;
|
|
|
|
case SWISubversion::IRQ:
|
|
|
|
executor_.set_pc(executor_.pc() - 4);
|
|
|
|
executor_.registers().exception<Exception::IRQ>();
|
2024-04-20 01:30:15 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
did_set_pc();
|
|
|
|
return false;
|
2024-04-19 02:13:58 +00:00
|
|
|
}
|
|
|
|
|
2024-03-22 14:01:34 +00:00
|
|
|
void update_clock_rates() {
|
2024-03-22 14:24:24 +00:00
|
|
|
video_divider_ = executor_.bus.video().clock_divider();
|
2024-03-22 14:01:34 +00:00
|
|
|
}
|
|
|
|
|
2024-03-12 15:34:31 +00:00
|
|
|
private:
|
2024-03-04 17:06:43 +00:00
|
|
|
// MARK: - ScanProducer.
|
|
|
|
void set_scan_target(Outputs::Display::ScanTarget *scan_target) override {
|
2024-03-22 00:41:24 +00:00
|
|
|
executor_.bus.video().crt().set_scan_target(scan_target);
|
2024-03-04 17:06:43 +00:00
|
|
|
}
|
|
|
|
Outputs::Display::ScanStatus get_scaled_scan_status() const override {
|
2024-04-10 01:56:42 +00:00
|
|
|
return executor_.bus.video().crt().get_scaled_scan_status() * video_divider_;
|
2024-03-04 17:06:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// MARK: - TimedMachine.
|
|
|
|
void run_for(Cycles cycles) override {
|
2024-03-20 15:42:37 +00:00
|
|
|
macro_counter_ += cycles.as<int>();
|
2024-03-07 15:23:46 +00:00
|
|
|
|
2024-03-20 15:42:37 +00:00
|
|
|
while(macro_counter_ > 0) {
|
2024-03-22 14:01:34 +00:00
|
|
|
switch(video_divider_) {
|
|
|
|
default: macro_tick<2>(); break;
|
|
|
|
case 3: macro_tick<3>(); break;
|
|
|
|
case 4: macro_tick<4>(); break;
|
|
|
|
case 6: macro_tick<6>(); break;
|
|
|
|
}
|
|
|
|
|
2024-03-20 15:42:37 +00:00
|
|
|
}
|
|
|
|
}
|
2024-03-22 14:01:34 +00:00
|
|
|
int video_divider_ = 1;
|
2024-03-07 16:12:40 +00:00
|
|
|
|
2024-03-20 15:42:37 +00:00
|
|
|
void tick_cpu() {
|
2024-04-20 01:30:15 +00:00
|
|
|
const uint32_t instruction = advance_pipeline(executor_.pc() + 8);
|
2024-03-31 22:15:48 +00:00
|
|
|
debugger_.notify(executor_.pc(), instruction, executor_);
|
2024-03-20 15:42:37 +00:00
|
|
|
InstructionSet::ARM::execute(instruction, executor_);
|
2024-03-05 02:09:24 +00:00
|
|
|
}
|
|
|
|
|
2024-03-20 18:25:20 +00:00
|
|
|
void tick_timers() { executor_.bus.tick_timers(); }
|
2024-03-22 00:41:24 +00:00
|
|
|
void tick_sound() { executor_.bus.sound().tick(); }
|
|
|
|
void tick_video() { executor_.bus.video().tick(); }
|
2024-04-08 01:22:35 +00:00
|
|
|
void tick_floppy() { executor_.bus.tick_floppy(); }
|
2024-03-20 15:42:37 +00:00
|
|
|
|
2024-03-05 02:09:24 +00:00
|
|
|
// MARK: - MediaTarget
|
2024-04-09 01:15:40 +00:00
|
|
|
bool insert_media(const Analyser::Static::Media &media) override {
|
2024-04-10 01:56:42 +00:00
|
|
|
size_t c = 0;
|
2024-04-09 01:15:40 +00:00
|
|
|
for(auto &disk : media.disks) {
|
|
|
|
executor_.bus.set_disk(disk, c);
|
|
|
|
c++;
|
|
|
|
if(c == 4) break;
|
|
|
|
}
|
|
|
|
return true;
|
2024-03-04 17:06:43 +00:00
|
|
|
}
|
2024-03-04 17:08:46 +00:00
|
|
|
|
2024-04-14 01:54:50 +00:00
|
|
|
// MARK: - AudioProducer
|
|
|
|
Outputs::Speaker::Speaker *get_speaker() override {
|
|
|
|
return executor_.bus.speaker();
|
|
|
|
}
|
|
|
|
|
2024-03-25 19:03:54 +00:00
|
|
|
// MARK: - Activity::Source.
|
|
|
|
void set_activity_observer(Activity::Observer *observer) final {
|
|
|
|
executor_.bus.set_activity_observer(observer);
|
|
|
|
}
|
|
|
|
|
2024-03-23 19:43:04 +00:00
|
|
|
// MARK: - MappedKeyboardMachine.
|
|
|
|
MappedKeyboardMachine::KeyboardMapper *get_keyboard_mapper() override {
|
|
|
|
return &keyboard_mapper_;
|
|
|
|
}
|
|
|
|
Archimedes::KeyboardMapper keyboard_mapper_;
|
|
|
|
|
|
|
|
void set_key_state(uint16_t key, bool is_pressed) override {
|
|
|
|
const int row = Archimedes::KeyboardMapper::row(key);
|
|
|
|
const int column = Archimedes::KeyboardMapper::column(key);
|
|
|
|
executor_.bus.keyboard().set_key_state(row, column, is_pressed);
|
|
|
|
}
|
|
|
|
|
2024-04-06 17:32:59 +00:00
|
|
|
// MARK: - MouseMachine.
|
|
|
|
Inputs::Mouse &get_mouse() override {
|
|
|
|
return executor_.bus.keyboard().mouse();
|
|
|
|
}
|
|
|
|
|
2024-04-20 01:30:15 +00:00
|
|
|
// MARK: - ARM execution.
|
2024-03-05 02:09:24 +00:00
|
|
|
static constexpr auto arm_model = InstructionSet::ARM::Model::ARMv2;
|
2024-04-08 02:08:12 +00:00
|
|
|
using Executor = InstructionSet::ARM::Executor<arm_model, MemoryController<ConcreteMachine, ConcreteMachine>, ConcreteMachine>;
|
2024-03-31 22:15:48 +00:00
|
|
|
Executor executor_;
|
2024-04-30 02:35:01 +00:00
|
|
|
bool trans_ = false;
|
2024-03-31 22:15:48 +00:00
|
|
|
|
2024-04-20 01:30:15 +00:00
|
|
|
void fill_pipeline(uint32_t pc) {
|
2024-04-20 02:21:23 +00:00
|
|
|
if(pipeline_.interrupt_next()) return;
|
2024-04-20 01:30:15 +00:00
|
|
|
advance_pipeline(pc);
|
|
|
|
advance_pipeline(pc + 4);
|
|
|
|
}
|
|
|
|
|
|
|
|
uint32_t advance_pipeline(uint32_t pc) {
|
2024-04-24 02:20:13 +00:00
|
|
|
uint32_t instruction = 0; // Value should never be used; this avoids a spurious GCC warning.
|
2024-04-30 02:35:01 +00:00
|
|
|
const bool did_read = executor_.bus.read(pc, instruction, trans_);
|
2024-04-20 01:30:15 +00:00
|
|
|
return pipeline_.exchange(
|
2024-04-23 02:08:09 +00:00
|
|
|
did_read ? instruction : Pipeline::SWI,
|
2024-04-20 01:30:15 +00:00
|
|
|
did_read ? Pipeline::SWISubversion::None : Pipeline::SWISubversion::DataAbort);
|
|
|
|
}
|
|
|
|
|
|
|
|
struct Pipeline {
|
|
|
|
enum SWISubversion: uint8_t {
|
|
|
|
None,
|
|
|
|
DataAbort,
|
2024-04-20 02:21:23 +00:00
|
|
|
IRQ,
|
|
|
|
FIQ,
|
2024-04-20 01:30:15 +00:00
|
|
|
};
|
|
|
|
|
2024-04-23 02:08:09 +00:00
|
|
|
static constexpr uint32_t SWI = 0xef'000000;
|
|
|
|
|
2024-04-20 01:30:15 +00:00
|
|
|
uint32_t exchange(uint32_t next, SWISubversion subversion) {
|
|
|
|
const uint32_t result = upcoming_[active_].opcode;
|
|
|
|
latched_subversion_ = upcoming_[active_].subversion;
|
|
|
|
|
|
|
|
upcoming_[active_].opcode = next;
|
|
|
|
upcoming_[active_].subversion = subversion;
|
|
|
|
active_ ^= 1;
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
SWISubversion swi_subversion() const {
|
|
|
|
return latched_subversion_;
|
|
|
|
}
|
|
|
|
|
2024-04-20 02:21:23 +00:00
|
|
|
// TODO: one day, possibly: schedule the subversion one slot further into the future
|
|
|
|
// (i.e. active_ ^ 1) to allow one further instruction to occur as usual before the
|
|
|
|
// action paplies. That is, if interrupts take effect one instruction later after a flags
|
|
|
|
// change, which I don't yet know.
|
|
|
|
//
|
|
|
|
// In practice I got into a bit of a race condition between interrupt scheduling and
|
|
|
|
// flags changes, so have backed off for now.
|
|
|
|
void reschedule(SWISubversion subversion) {
|
2024-04-23 02:08:09 +00:00
|
|
|
upcoming_[active_].opcode = SWI;
|
2024-04-20 02:21:23 +00:00
|
|
|
upcoming_[active_].subversion = subversion;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool interrupt_next() const {
|
|
|
|
return upcoming_[active_].subversion == SWISubversion::IRQ || upcoming_[active_].subversion == SWISubversion::FIQ;
|
|
|
|
}
|
2024-04-20 01:46:09 +00:00
|
|
|
|
2024-04-20 01:30:15 +00:00
|
|
|
private:
|
|
|
|
struct Stage {
|
|
|
|
uint32_t opcode;
|
2024-04-20 01:46:09 +00:00
|
|
|
SWISubversion subversion = SWISubversion::None;
|
2024-04-20 01:30:15 +00:00
|
|
|
};
|
|
|
|
Stage upcoming_[2];
|
|
|
|
int active_ = 0;
|
|
|
|
|
|
|
|
SWISubversion latched_subversion_;
|
|
|
|
} pipeline_;
|
|
|
|
|
2024-03-31 22:15:48 +00:00
|
|
|
// MARK: - Yucky, temporary junk.
|
|
|
|
HackyDebugger<arm_model, Executor> debugger_;
|
2024-03-04 17:06:43 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
using namespace Archimedes;
|
|
|
|
|
|
|
|
std::unique_ptr<Machine> Machine::Archimedes(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) {
|
|
|
|
return std::make_unique<ConcreteMachine>(*target, rom_fetcher);
|
|
|
|
}
|