// // Archimedes.cpp // Clock Signal // // Created by Thomas Harte on 04/03/2024. // Copyright © 2024 Thomas Harte. All rights reserved. // #include "Archimedes.hpp" #include "HalfDuplexSerial.hpp" #include "InputOutputController.hpp" #include "Keyboard.hpp" #include "KeyboardMapper.hpp" #include "MemoryController.hpp" #include "Sound.hpp" #include "../../AudioProducer.hpp" #include "../../KeyboardMachine.hpp" #include "../../MediaTarget.hpp" #include "../../MouseMachine.hpp" #include "../../ScanProducer.hpp" #include "../../TimedMachine.hpp" #include "../../../Activity/Source.hpp" #include "../../../InstructionSets/ARM/Disassembler.hpp" #include "../../../InstructionSets/ARM/Executor.hpp" #include "../../../Outputs/Log.hpp" #include "../../../Components/I2C/I2C.hpp" #include #include #include #include namespace Archimedes { #ifndef NDEBUG namespace { Log::Logger logger; } template 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]; // } // 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]); // } // last_r9 = executor_.registers()[9]; // log |= address == 0x038031c4; // log |= instr_count == 53552731 - 30; // log &= executor_.pc() != 0x000000a0; // log = (executor_.pc() == 0x038162afc) || (executor_.pc() == 0x03824b00); // log |= instruction & ; // The following has the effect of logging all taken SWIs and their return codes. /* if( (instruction & 0x0f00'0000) == 0x0f00'0000 && executor.registers().test(InstructionSet::ARM::Condition(instruction >> 28)) ) { if(instruction & 0x2'0000) { swis.emplace_back(); swis.back().count = swi_count++; 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(pointer, next, InstructionSet::ARM::Mode::Supervisor, false); ++pointer; if(next < 32) break; swis.back().value_name.push_back(static_cast(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(); info.append("[%d] Failed swi ", back.count); 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(); }*/ if(log) { InstructionSet::ARM::Disassembler disassembler; InstructionSet::ARM::dispatch(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("]"); } // opcodes.insert(instruction); // if(accumulate) { // int c = 0; // for(auto instr : opcodes) { // printf("0x%08x, ", instr); // ++c; // if(!(c&15)) printf("\n"); // } // accumulate = false; // } ++instr_count; } private: std::array pc_history; std::size_t pc_history_ptr = 0; uint32_t instr_count = 0; uint32_t swi_count = 0; struct SWICall { uint32_t count; uint32_t opcode; uint32_t address; uint32_t regs[10]; uint32_t return_address; std::string value_name; std::string swi_name; }; std::vector swis; uint32_t last_pc = 0; // uint32_t last_r9 = 0; bool log = false; bool accumulate = true; std::set opcodes; }; #else template struct HackyDebugger { void notify(uint32_t, uint32_t, Executor &) {} }; #endif class ConcreteMachine: public Machine, public MachineTypes::AudioProducer, public MachineTypes::MappedKeyboardMachine, public MachineTypes::MediaTarget, public MachineTypes::MouseMachine, public MachineTypes::TimedMachine, public MachineTypes::ScanProducer, public Activity::Source { private: // TODO: pick a sensible clock rate; this is just code for '24 MIPS, please'. static constexpr int ClockRate = 24'000'000; // 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. template 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; // * video: 24 / video_divider; // * floppy: 8; // * timers: 2; // * sound: 1. tick_cpu_video<0, video_divider>(); tick_cpu_video<1, video_divider>(); 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(); tick_cpu_video<6, video_divider>(); tick_cpu_video<7, video_divider>(); 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(); tick_timers(); tick_cpu_video<12, video_divider>(); tick_cpu_video<13, video_divider>(); 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(); tick_cpu_video<18, video_divider>(); tick_cpu_video<19, video_divider>(); 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(); tick_timers(); tick_sound(); } int macro_counter_ = 0; template void tick_cpu_video() { if constexpr (!(offset % video_divider)) { tick_video(); } #ifndef NDEBUG // Debug mode: run CPU a lot slower. Actually at close to original advertised MIPS speed. if constexpr (offset & 7) return; #endif if constexpr (offset & 1) return; tick_cpu(); } public: ConcreteMachine( const Analyser::Static::Target &target, const ROMMachine::ROMFetcher &rom_fetcher ) : executor_(*this, *this, *this) { set_clock_rate(ClockRate); constexpr ROM::Name risc_os = ROM::Name::AcornRISCOS311; 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); } void update_interrupts() { using Exception = InstructionSet::ARM::Registers::Exception; const int requests = executor_.bus.interrupt_mask(); if((requests & InterruptRequests::FIQ) && executor_.registers().interrupt()) { return; } if(requests & InterruptRequests::IRQ) { executor_.registers().interrupt(); } } void did_set_status() { update_interrupts(); } void update_clock_rates() { video_divider_ = executor_.bus.video().clock_divider(); } private: // MARK: - ScanProducer. void set_scan_target(Outputs::Display::ScanTarget *scan_target) override { executor_.bus.video().crt().set_scan_target(scan_target); } Outputs::Display::ScanStatus get_scaled_scan_status() const override { return executor_.bus.video().crt().get_scaled_scan_status() * video_divider_; } // MARK: - TimedMachine. void run_for(Cycles cycles) override { macro_counter_ += cycles.as(); while(macro_counter_ > 0) { 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; } } } int video_divider_ = 1; void tick_cpu() { uint32_t instruction = 0; if(!executor_.bus.read(executor_.pc(), instruction, executor_.registers().mode(), false)) { // logger.info().append("Prefetch abort at %08x; last good was at %08x", executor_.pc(), last_pc); executor_.prefetch_abort(); // TODO: does a double abort cause a reset? executor_.bus.read(executor_.pc(), instruction, executor_.registers().mode(), false); } // TODO: pipeline prefetch? debugger_.notify(executor_.pc(), instruction, executor_); InstructionSet::ARM::execute(instruction, executor_); } void tick_timers() { executor_.bus.tick_timers(); } void tick_sound() { executor_.bus.sound().tick(); } void tick_video() { executor_.bus.video().tick(); } void tick_floppy() { executor_.bus.tick_floppy(); } // MARK: - MediaTarget bool insert_media(const Analyser::Static::Media &media) override { size_t c = 0; for(auto &disk : media.disks) { executor_.bus.set_disk(disk, c); c++; if(c == 4) break; } return true; } // MARK: - AudioProducer Outputs::Speaker::Speaker *get_speaker() override { return executor_.bus.speaker(); } // MARK: - Activity::Source. void set_activity_observer(Activity::Observer *observer) final { executor_.bus.set_activity_observer(observer); } // 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); } // MARK: - MouseMachine. Inputs::Mouse &get_mouse() override { return executor_.bus.keyboard().mouse(); } // MARK: - ARM execution static constexpr auto arm_model = InstructionSet::ARM::Model::ARMv2; using Executor = InstructionSet::ARM::Executor, ConcreteMachine>; Executor executor_; // MARK: - Yucky, temporary junk. HackyDebugger debugger_; }; } using namespace Archimedes; std::unique_ptr Machine::Archimedes(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) { return std::make_unique(*target, rom_fetcher); }