1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-06-09 01:29:44 +00:00
This commit is contained in:
Thomas Harte 2024-05-17 02:19:36 +00:00 committed by GitHub
commit b95cb4eab7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 525 additions and 429 deletions

View File

@ -145,8 +145,9 @@ std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetADFSCatalogue(const std::
}
name[c] = '\0';
// Skip if the name is empty.
if(name[0] == '\0') continue;
// An empty name implies the directory has ended; files are always listed in case-insensitive
// sorted order, with that list being terminated by a '\0'.
if(name[0] == '\0') break;
// Populate a file then.
File new_file;

View File

@ -60,8 +60,8 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
}
Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
auto target8bit = std::make_unique<Target>();
auto targetArchimedes = std::make_unique<Analyser::Static::Target>(Machine::Archimedes);
auto target8bit = std::make_unique<ElectronTarget>();
auto targetArchimedes = std::make_unique<ArchimedesTarget>();
// Copy appropriate cartridges to the 8-bit target.
target8bit->media.cartridges = AcornCartridgesFrom(media.cartridges);
@ -102,14 +102,14 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &me
}
if(!media.disks.empty()) {
// TODO: below requires an [8-bit compatible] 'Hugo' ADFS catalogue, disallowing
// [Archimedes-exclusive] 'Nick' catalogues.
//
// Would be better to form the appropriate target in the latter case.
std::shared_ptr<Storage::Disk::Disk> disk = media.disks.front();
std::unique_ptr<Catalogue> dfs_catalogue, adfs_catalogue;
// Get any sort of catalogue that can be found.
dfs_catalogue = GetDFSCatalogue(disk);
if(dfs_catalogue == nullptr) adfs_catalogue = GetADFSCatalogue(disk);
// 8-bit options: DFS and Hugo-style ADFS.
if(dfs_catalogue || (adfs_catalogue && !adfs_catalogue->has_large_sectors && adfs_catalogue->is_hugo)) {
// Accept the disk and determine whether DFS or ADFS ROMs are implied.
// Use the Pres ADFS if using an ADFS, as it leaves Page at &EOO.
@ -144,7 +144,36 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &me
}
}
} else if(adfs_catalogue) {
// Archimedes options, implicitly: ADFS, non-Hugo.
targetArchimedes->media.disks = media.disks;
// Always try a shift-restart; it's worth a go.
targetArchimedes->should_shift_restart = true;
// Also look for the best possible startup program name, if it can be discerned.
for(const auto &file: adfs_catalogue->files) {
// Skip files that would have been caught by shift-restart if suitable.
if(file.name == "!System" || file.name == "!Boot") continue;
// Skip non-Pling files.
if(file.name[0] != '!') continue;
// Take whatever else comes with a preference for things that don't
// have 'read' in them (which will tend to be read_me or read_this or similar).
constexpr char read[] = "read";
const auto has_read =
std::search(
file.name.begin(), file.name.end(),
std::begin(read), std::end(read) - 1, // i.e. don't compare the trailing NULL.
[](char lhs, char rhs) {
return std::tolower(lhs) == rhs;
}
) != file.name.end();
if(targetArchimedes->main_program.empty() || !has_read) {
targetArchimedes->main_program = file.name;
}
}
}
}

View File

@ -14,7 +14,7 @@
namespace Analyser::Static::Acorn {
struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<Target> {
struct ElectronTarget: public ::Analyser::Static::Target, public Reflection::StructImpl<ElectronTarget> {
bool has_acorn_adfs = false;
bool has_pres_adfs = false;
bool has_dfs = false;
@ -23,7 +23,7 @@ struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<
bool should_shift_restart = false;
std::string loading_command;
Target() : Analyser::Static::Target(Machine::Electron) {
ElectronTarget() : Analyser::Static::Target(Machine::Electron) {
if(needs_declare()) {
DeclareField(has_pres_adfs);
DeclareField(has_acorn_adfs);
@ -34,4 +34,11 @@ struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<
}
};
struct ArchimedesTarget: public ::Analyser::Static::Target, public Reflection::StructImpl<ArchimedesTarget> {
bool should_shift_restart = false;
std::string main_program;
ArchimedesTarget() : Analyser::Static::Target(Machine::Archimedes) {}
};
}

View File

@ -29,6 +29,8 @@
#include "../../../Outputs/Log.hpp"
#include "../../../Components/I2C/I2C.hpp"
#include "../../../Analyser/Static/Acorn/Target.hpp"
#include <algorithm>
#include <array>
#include <set>
@ -36,287 +38,6 @@
namespace Archimedes {
#ifndef NDEBUG
namespace {
Log::Logger<Log::Source::Archimedes> logger;
}
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];
// }
// 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<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();
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<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("]");
}
// 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<uint32_t, 75> 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<SWICall> swis;
uint32_t last_pc = 0;
// uint32_t last_r9 = 0;
bool log = false;
bool accumulate = true;
std::set<uint32_t> opcodes;
};
#else
template <InstructionSet::ARM::Model model, typename Executor>
struct HackyDebugger {
void notify(uint32_t, uint32_t, Executor &) {}
};
#endif
class ConcreteMachine:
public Machine,
public MachineTypes::AudioProducer,
@ -328,7 +49,9 @@ class ConcreteMachine:
public Activity::Source
{
private:
// TODO: pick a sensible clock rate; this is just code for '24 MIPS, please'.
Log::Logger<Log::Source::Archimedes> logger;
// This fictitious clock rate just means '24 MIPS, please'; it's divided elsewhere.
static constexpr int ClockRate = 24'000'000;
// Runs for 24 cycles, distributing calls to the various ticking subsystems
@ -386,7 +109,7 @@ class ConcreteMachine:
public:
ConcreteMachine(
const Analyser::Static::Target &target,
const Analyser::Static::Acorn::ArchimedesTarget &target,
const ROMMachine::ROMFetcher &rom_fetcher
) : executor_(*this, *this, *this) {
set_clock_rate(ClockRate);
@ -401,6 +124,11 @@ class ConcreteMachine:
executor_.bus.set_rom(roms.find(risc_os)->second);
insert_media(target.media);
if(!target.media.disks.empty()) {
autoload_phase_ = AutoloadPhase::WaitingForStartup;
target_program_ = target.main_program;
}
fill_pipeline(0);
}
@ -428,13 +156,133 @@ class ConcreteMachine:
fill_pipeline(executor_.pc());
}
bool should_swi(uint32_t) {
bool should_swi(uint32_t comment) {
using Exception = InstructionSet::ARM::Registers::Exception;
using SWISubversion = Pipeline::SWISubversion;
switch(pipeline_.swi_subversion()) {
case Pipeline::SWISubversion::None:
return true;
case Pipeline::SWISubversion::None: {
// TODO: 400C1 to intercept create window 400C1 and positioning; then
// plot icon 400e2 to listen for icons in window. That'll give a click area.
// Probably also 400c2 which seems to be used to add icons to the icon bar.
//
// 400D4 for menus?
const auto get_string = [&](uint32_t address, bool indirect) -> std::string {
std::string desc;
if(indirect) {
executor_.bus.read(address, address, false);
}
while(true) {
uint8_t next;
executor_.bus.read(address, next, false);
if(next < 0x20) break;
desc.push_back(static_cast<char>(next));
++address;
}
return desc;
};
switch(comment & static_cast<uint32_t>(~(1 << 17))) {
case 0x400d4: {
uint32_t address = executor_.registers()[1] + 28;
printf("Menu:\n");
while(true) {
uint32_t icon_flags;
uint32_t item_flags;
executor_.bus.read(address, item_flags, false);
executor_.bus.read(address + 8, icon_flags, false);
auto desc = get_string(address + 12, icon_flags & (1 << 8));
printf("%s\n", desc.c_str());
address += 24;
if(item_flags & (1 << 7)) break;
}
} break;
// Wimp_OpenWindow.
case 0x400c5: {
if(autoload_phase_ == AutoloadPhase::WaitingForDiskContents) {
autoload_phase_ = AutoloadPhase::WaitingForTargetIcon;
const uint32_t address = executor_.registers()[1];
uint32_t x1, y1, x2, y2;
executor_.bus.read(address + 4, x1, false);
executor_.bus.read(address + 8, y1, false);
executor_.bus.read(address + 12, x2, false);
executor_.bus.read(address + 16, y2, false);
// Crib top left of window content.
target_window_[0] = static_cast<int32_t>(x1);
target_window_[1] = static_cast<int32_t>(y2);
printf("Wimp_OpenWindow: %d, %d -> %d, %d\n", x1, y1, x2, y2);
}
} break;
// Wimp_CreateIcon, which also adds to the icon bar.
case 0x400c2:
// Creation of any icon is used to spot that RISC OS has started up.
if(autoload_phase_ == AutoloadPhase::WaitingForStartup) {
autoload_phase_ = AutoloadPhase::OpeningDisk;
// Wait a further second, mouse down to (32, 240), left click.
// That'll trigger disk access. Then move up to the top left,
// in anticipation of the appearance of a window.
cursor_actions_.push_back(CursorAction::wait(24'000'000));
cursor_actions_.push_back(CursorAction::move_to(32, 240));
cursor_actions_.push_back(CursorAction::button(0, true));
cursor_actions_.push_back(CursorAction::wait(12'000'000));
cursor_actions_.push_back(CursorAction::button(0, false));
cursor_actions_.push_back(CursorAction::move_to(64, 32));
cursor_actions_.push_back(CursorAction::set_phase(
target_program_.empty() ? AutoloadPhase::Ended : AutoloadPhase::WaitingForDiskContents)
);
}
// TODO: spot potential addition of extra program icon.
break;
// Wimp_PlotIcon.
case 0x400e2: {
if(autoload_phase_ == AutoloadPhase::WaitingForTargetIcon) {
const uint32_t address = executor_.registers()[1];
uint32_t flags;
executor_.bus.read(address + 16, flags, false);
std::string desc;
if(flags & 1) {
desc = get_string(address + 20, flags & (1 << 8));
}
if(desc == target_program_) {
uint32_t x1, y1, x2, y2;
executor_.bus.read(address + 0, x1, false);
executor_.bus.read(address + 4, y1, false);
executor_.bus.read(address + 8, x2, false);
executor_.bus.read(address + 12, y2, false);
autoload_phase_ = AutoloadPhase::OpeningProgram;
// Some default icon sizing assumptions are baked in here.
const auto x_target = target_window_[0] + static_cast<int32_t>(x1) + 200;
const auto y_target = target_window_[1] + static_cast<int32_t>(y1) + 24;
cursor_actions_.push_back(CursorAction::move_to(
x_target >> 2,
256 - (y_target >> 2)
));
cursor_actions_.push_back(CursorAction::button(0, true));
cursor_actions_.push_back(CursorAction::wait(6'000'000));
cursor_actions_.push_back(CursorAction::button(0, false));
cursor_actions_.push_back(CursorAction::wait(6'000'000));
cursor_actions_.push_back(CursorAction::button(0, true));
cursor_actions_.push_back(CursorAction::wait(6'000'000));
cursor_actions_.push_back(CursorAction::button(0, false));
}
}
} break;
}
} return true;
case SWISubversion::DataAbort:
// executor_.set_pc(executor_.pc() - 4);
@ -483,6 +331,68 @@ class ConcreteMachine:
const bool use_original_speed = executor_.bus.video().frame_rate_overages() > 10;
#endif
//
// Mouse scripting.
//
if(!cursor_actions_.empty()) {
const auto move_to_next = [&]() {
cursor_action_waited_ = 0;
cursor_actions_.erase(cursor_actions_.begin());
};
const auto &action = cursor_actions_.front();
switch(action.type) {
case CursorAction::Type::MoveTo: {
// A measure of where within the tip lies within
// the default RISC OS cursor.
constexpr int ActionPointOffset = 20;
constexpr int MaxStep = 8;
const auto position = executor_.bus.video().cursor_location();
if(!position) break;
const auto [x, y] = *position;
auto x_diff = action.value.move_to.x - (x + ActionPointOffset);
auto y_diff = action.value.move_to.y - y;
if(abs(x_diff) < 2 && abs(y_diff) < 2) {
move_to_next();
break;
}
if(abs(y_diff) > MaxStep || abs(x_diff) > MaxStep) {
if(abs(y_diff) > abs(x_diff)) {
x_diff = (x_diff * MaxStep + (abs(y_diff) >> 1)) / abs(y_diff);
y_diff = std::clamp(y_diff, -MaxStep, MaxStep);
} else {
y_diff = (y_diff * MaxStep + (abs(x_diff) >> 1)) / abs(x_diff);
x_diff = std::clamp(x_diff, -MaxStep, MaxStep);
}
}
get_mouse().move(x_diff, y_diff);
} break;
case CursorAction::Type::Wait:
cursor_action_waited_ += cycles.as<int>();
if(cursor_action_waited_ >= action.value.wait.duration) {
move_to_next();
}
break;
case CursorAction::Type::Button:
get_mouse().set_button_pressed(action.value.button.button, action.value.button.down);
move_to_next();
break;
case CursorAction::Type::SetPhase:
autoload_phase_ = action.value.set_phase.phase;
move_to_next();
break;
}
}
//
// Execution proper.
//
// TODO: divide up the following if necessary to put scripted mouse actions
// at predictably-regular steps.
if(use_original_speed) run_for<true>(cycles);
else run_for<false>(cycles);
}
@ -498,13 +408,11 @@ class ConcreteMachine:
case 4: macro_tick<4, original_speed>(); break;
case 6: macro_tick<6, original_speed>(); break;
}
}
}
void tick_cpu() {
const uint32_t instruction = advance_pipeline(executor_.pc() + 8);
debugger_.notify(executor_.pc(), instruction, executor_);
InstructionSet::ARM::execute(instruction, executor_);
}
@ -623,14 +531,83 @@ class ConcreteMachine:
SWISubversion latched_subversion_;
} pipeline_;
// MARK: - Yucky, temporary junk.
HackyDebugger<arm_model, Executor> debugger_;
// MARK: - Autoload, including cursor scripting.
enum class AutoloadPhase {
WaitingForStartup,
OpeningDisk,
WaitingForDiskContents,
WaitingForTargetIcon,
OpeningProgram,
Ended,
};
AutoloadPhase autoload_phase_ = AutoloadPhase::Ended;
std::string target_program_;
struct CursorAction {
enum class Type {
MoveTo,
Button,
Wait,
SetPhase,
} type;
union {
struct {
int x, y;
} move_to;
struct {
int duration;
} wait;
struct {
int button;
bool down;
} button;
struct {
AutoloadPhase phase;
} set_phase;
} value;
static CursorAction move_to(int x, int y) {
CursorAction action;
action.type = Type::MoveTo;
action.value.move_to.x = x;
action.value.move_to.y = y;
return action;
}
static CursorAction wait(int duration) {
CursorAction action;
action.type = Type::Wait;
action.value.wait.duration = duration;
return action;
}
static CursorAction button(int button, bool down) {
CursorAction action;
action.type = Type::Button;
action.value.button.button = button;
action.value.button.down = down;
return action;
}
static CursorAction set_phase(AutoloadPhase phase) {
CursorAction action;
action.type = Type::SetPhase;
action.value.set_phase.phase = phase;
return action;
}
};
std::vector<CursorAction> cursor_actions_;
int cursor_action_waited_ = 0;
int32_t target_window_[2];
};
}
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);
std::unique_ptr<Machine> Machine::Archimedes(
const Analyser::Static::Target *target,
const ROMMachine::ROMFetcher &rom_fetcher
) {
const auto archimedes_target = dynamic_cast<const Analyser::Static::Acorn::ArchimedesTarget *>(target);
return std::make_unique<ConcreteMachine>(*archimedes_target, rom_fetcher);
}

View File

@ -20,19 +20,18 @@ struct Keyboard {
Keyboard(HalfDuplexSerial &serial) : serial_(serial), mouse_(*this) {}
void set_key_state(int row, int column, bool is_pressed) {
states_[row][column] = is_pressed;
if(!scan_keyboard_) {
logger_.info().append("Ignored key event as key scanning disabled");
return;
}
// Don't waste bandwidth on repeating facts.
if(states_[row][column] == is_pressed) return;
states_[row][column] = is_pressed;
if(posted_states_[row][column] == is_pressed) return;
// Post new key event.
logger_.info().append("Posting row %d, column %d is now %s", row, column, is_pressed ? "pressed" : "released");
const uint8_t prefix = is_pressed ? 0b1100'0000 : 0b1101'0000;
enqueue(static_cast<uint8_t>(prefix | row), static_cast<uint8_t>(prefix | column));
enqueue_key_event(row, column, is_pressed);
consider_dequeue();
}
@ -141,7 +140,17 @@ struct Keyboard {
void consider_dequeue() {
if(state_ == State::Idle) {
// If the key event queue is empty, grab as much mouse motion
// If the key event queue is empty but keyboard scanning is enabled, check for
// any disparity between posted keys states and actuals.
for(int row = 0; row < 16 && event_queue_.empty(); row++) {
for(int column = 0; column < 16 && event_queue_.empty(); column++) {
if(posted_states_[row][column] != states_[row][column]) {
enqueue_key_event(row, column, states_[row][column]);
}
}
}
// If the key event queue is _still_ empty, grab as much mouse motion
// as available.
if(event_queue_.empty()) {
const int x = std::clamp(mouse_x_, -0x3f, 0x3f);
@ -169,6 +178,7 @@ private:
Log::Logger<Log::Source::Keyboard> logger_;
bool states_[16][16]{};
bool posted_states_[16][16]{};
bool scan_keyboard_ = false;
bool scan_mouse_ = false;
@ -196,6 +206,12 @@ private:
event_queue_.erase(event_queue_.begin());
return true;
}
void enqueue_key_event(int row, int column, bool is_pressed) {
logger_.info().append("Posting row %d, column %d is now %s", row, column, is_pressed ? "pressed" : "released");
posted_states_[row][column] = is_pressed;
const uint8_t prefix = is_pressed ? 0b1100'0000 : 0b1101'0000;
enqueue(static_cast<uint8_t>(prefix | row), static_cast<uint8_t>(prefix | column));
}
static constexpr uint8_t HRST = 0b1111'1111; // Keyboard reset.
static constexpr uint8_t RAK1 = 0b1111'1110; // Reset response #1.

View File

@ -12,12 +12,49 @@
namespace Archimedes {
static constexpr uint16_t map(int row, int column) {
return static_cast<uint16_t>((row << 4) | column);
}
/// Named key codes that the machine wlll accept directly.
enum Key: uint16_t {
Escape = map(0, 0), F1 = map(0, 1), F2 = map(0, 2), F3 = map(0, 3),
F4 = map(0, 4), F5 = map(0, 5), F6 = map(0, 6), F7 = map(0, 7),
F8 = map(0, 8), F9 = map(0, 9), F10 = map(0, 10), F11 = map(0, 11),
F12 = map(0, 12), Print = map(0, 13), Scroll = map(0, 14), Break = map(0, 15),
Tilde = map(1, 0), k1 = map(1, 1), k2 = map(1, 2), k3 = map(1, 3),
k4 = map(1, 4), k5 = map(1, 5), k6 = map(1, 6), k7 = map(1, 7),
k8 = map(1, 8), k9 = map(1, 9), k0 = map(1, 10), Hyphen = map(1, 11),
Equals = map(1, 12), GBPound = map(1, 13), Backspace = map(1, 14), Insert = map(1, 15),
Home = map(2, 0), PageUp = map(2, 1), NumLock = map(2, 2), KeypadSlash = map(2, 3),
KeypadAsterisk = map(2, 4), KeypadHash = map(2, 5), Tab = map(2, 6), Q = map(2, 7),
W = map(2, 8), E = map(2, 9), R = map(2, 10), T = map(2, 11),
Y = map(2, 12), U = map(2, 13), I = map(2, 14), O = map(2, 15),
P = map(3, 0), OpenSquareBracket = map(3, 1), CloseSquareBracket = map(3, 2), Backslash = map(3, 3),
Delete = map(3, 4), Copy = map(3, 5), PageDown = map(3, 6), Keypad7 = map(3, 7),
Keypad8 = map(3, 8), Keypad9 = map(3, 9), KeypadMinus = map(3, 10), LeftControl = map(3, 11),
A = map(3, 12), S = map(3, 13), D = map(3, 14), F = map(3, 15),
G = map(4, 0), H = map(4, 1), J = map(4, 2), K = map(4, 3),
L = map(4, 4), Semicolon = map(4, 5), Quote = map(4, 6), Return = map(4, 7),
Keypad4 = map(4, 8), Keypad5 = map(4, 9), Keypad6 = map(4, 10), KeypadPlus = map(4, 11),
LeftShift = map(4, 12), /* unused */ Z = map(4, 14), X = map(4, 14),
C = map(5, 0), V = map(5, 1), B = map(5, 2), N = map(5, 3),
M = map(5, 4), Comma = map(5, 5), FullStop = map(5, 6), ForwardSlash = map(5, 7),
RightShift = map(5, 8), Up = map(5, 9), Keypad1 = map(5, 10), Keypad2 = map(5, 11),
Keypad3 = map(5, 12), CapsLock = map(5, 13), LeftAlt = map(5, 14), Space = map(5, 15),
RightAlt = map(6, 0), RightControl = map(6, 1), Left = map(6, 2), Down = map(6, 3),
Right = map(6, 4), Keypad0 = map(6, 5), KeypadDecimalPoint = map(6, 6), KeypadEnter = map(6, 7),
};
/// Converter from this emulator's custom definition of a generic keyboard to the machine-specific key set defined above.
class KeyboardMapper: public MachineTypes::MappedKeyboardMachine::KeyboardMapper {
public:
static constexpr uint16_t map(int row, int column) {
return static_cast<uint16_t>((row << 4) | column);
}
static constexpr int row(uint16_t key) {
return key >> 4;
}
@ -30,115 +67,115 @@ class KeyboardMapper: public MachineTypes::MappedKeyboardMachine::KeyboardMapper
uint16_t mapped_key_for_key(Inputs::Keyboard::Key key) const override {
using k = Inputs::Keyboard::Key;
switch(key) {
case k::Escape: return map(0, 0);
case k::F1: return map(0, 1);
case k::F2: return map(0, 2);
case k::F3: return map(0, 3);
case k::F4: return map(0, 4);
case k::F5: return map(0, 5);
case k::F6: return map(0, 6);
case k::F7: return map(0, 7);
case k::F8: return map(0, 8);
case k::F9: return map(0, 9);
case k::F10: return map(0, 10);
case k::F11: return map(0, 11);
case k::F12: return map(0, 12);
case k::PrintScreen: return map(0, 13);
case k::ScrollLock: return map(0, 14);
case k::Pause: return map(0, 15);
case k::Escape: return Key::Escape;
case k::F1: return Key::F1;
case k::F2: return Key::F2;
case k::F3: return Key::F3;
case k::F4: return Key::F4;
case k::F5: return Key::F5;
case k::F6: return Key::F6;
case k::F7: return Key::F7;
case k::F8: return Key::F8;
case k::F9: return Key::F9;
case k::F10: return Key::F10;
case k::F11: return Key::F11;
case k::F12: return Key::F12;
case k::PrintScreen: return Key::Print;
case k::ScrollLock: return Key::Scroll;
case k::Pause: return Key::Break;
case k::BackTick: return map(1, 0);
case k::k1: return map(1, 1);
case k::k2: return map(1, 2);
case k::k3: return map(1, 3);
case k::k4: return map(1, 4);
case k::k5: return map(1, 5);
case k::k6: return map(1, 6);
case k::k7: return map(1, 7);
case k::k8: return map(1, 8);
case k::k9: return map(1, 9);
case k::k0: return map(1, 10);
case k::Hyphen: return map(1, 11);
case k::Equals: return map(1, 12);
case k::BackTick: return Key::Tilde;
case k::k1: return Key::k1;
case k::k2: return Key::k2;
case k::k3: return Key::k3;
case k::k4: return Key::k4;
case k::k5: return Key::k5;
case k::k6: return Key::k6;
case k::k7: return Key::k7;
case k::k8: return Key::k8;
case k::k9: return Key::k9;
case k::k0: return Key::k0;
case k::Hyphen: return Key::Hyphen;
case k::Equals: return Key::Equals;
// TODO: pound key.
case k::Backspace: return map(1, 14);
case k::Insert: return map(1, 15);
case k::Backspace: return Key::Backspace;
case k::Insert: return Key::Insert;
case k::Home: return map(2, 0);
case k::PageUp: return map(2, 1);
case k::NumLock: return map(2, 2);
case k::KeypadSlash: return map(2, 3);
case k::KeypadAsterisk: return map(2, 4);
case k::Home: return Key::Home;
case k::PageUp: return Key::PageUp;
case k::NumLock: return Key::NumLock;
case k::KeypadSlash: return Key::KeypadSlash;
case k::KeypadAsterisk: return Key::KeypadAsterisk;
// TODO: keypad hash key
case k::Tab: return map(2, 6);
case k::Q: return map(2, 7);
case k::W: return map(2, 8);
case k::E: return map(2, 9);
case k::R: return map(2, 10);
case k::T: return map(2, 11);
case k::Y: return map(2, 12);
case k::U: return map(2, 13);
case k::I: return map(2, 14);
case k::O: return map(2, 15);
case k::Tab: return Key::Tab;
case k::Q: return Key::Q;
case k::W: return Key::W;
case k::E: return Key::E;
case k::R: return Key::R;
case k::T: return Key::T;
case k::Y: return Key::Y;
case k::U: return Key::U;
case k::I: return Key::I;
case k::O: return Key::O;
case k::P: return map(3, 0);
case k::OpenSquareBracket: return map(3, 1);
case k::CloseSquareBracket: return map(3, 2);
case k::Backslash: return map(3, 3);
case k::Delete: return map(3, 4);
case k::End: return map(3, 5);
case k::PageDown: return map(3, 6);
case k::Keypad7: return map(3, 7);
case k::Keypad8: return map(3, 8);
case k::Keypad9: return map(3, 9);
case k::KeypadMinus: return map(3, 10);
case k::LeftControl: return map(3, 11);
case k::A: return map(3, 12);
case k::S: return map(3, 13);
case k::D: return map(3, 14);
case k::F: return map(3, 15);
case k::P: return Key::P;
case k::OpenSquareBracket: return Key::OpenSquareBracket;
case k::CloseSquareBracket: return Key::CloseSquareBracket;
case k::Backslash: return Key::Backslash;
case k::Delete: return Key::Delete;
case k::End: return Key::Copy;
case k::PageDown: return Key::PageDown;
case k::Keypad7: return Key::Keypad7;
case k::Keypad8: return Key::Keypad8;
case k::Keypad9: return Key::Keypad9;
case k::KeypadMinus: return Key::KeypadMinus;
case k::LeftControl: return Key::LeftControl;
case k::A: return Key::A;
case k::S: return Key::S;
case k::D: return Key::D;
case k::F: return Key::F;
case k::G: return map(4, 0);
case k::H: return map(4, 1);
case k::J: return map(4, 2);
case k::K: return map(4, 3);
case k::L: return map(4, 4);
case k::Semicolon: return map(4, 5);
case k::Quote: return map(4, 6);
case k::Enter: return map(4, 7);
case k::Keypad4: return map(4, 8);
case k::Keypad5: return map(4, 9);
case k::Keypad6: return map(4, 10);
case k::KeypadPlus: return map(4, 11);
case k::LeftShift: return map(4, 12);
case k::Z: return map(4, 14);
case k::X: return map(4, 15);
case k::G: return Key::G;
case k::H: return Key::H;
case k::J: return Key::J;
case k::K: return Key::K;
case k::L: return Key::L;
case k::Semicolon: return Key::Semicolon;
case k::Quote: return Key::Quote;
case k::Enter: return Key::Return;
case k::Keypad4: return Key::Keypad4;
case k::Keypad5: return Key::Keypad5;
case k::Keypad6: return Key::Keypad6;
case k::KeypadPlus: return Key::KeypadPlus;
case k::LeftShift: return Key::LeftShift;
case k::Z: return Key::Z;
case k::X: return Key::X;
case k::C: return map(5, 0);
case k::V: return map(5, 1);
case k::B: return map(5, 2);
case k::N: return map(5, 3);
case k::M: return map(5, 4);
case k::Comma: return map(5, 5);
case k::FullStop: return map(5, 6);
case k::ForwardSlash: return map(5, 7);
case k::RightShift: return map(5, 8);
case k::Up: return map(5, 9);
case k::Keypad1: return map(5, 10);
case k::Keypad2: return map(5, 11);
case k::Keypad3: return map(5, 12);
case k::CapsLock: return map(5, 13);
case k::LeftOption: return map(5, 14);
case k::Space: return map(5, 15);
case k::C: return Key::C;
case k::V: return Key::V;
case k::B: return Key::B;
case k::N: return Key::N;
case k::M: return Key::M;
case k::Comma: return Key::Comma;
case k::FullStop: return Key::FullStop;
case k::ForwardSlash: return Key::ForwardSlash;
case k::RightShift: return Key::RightShift;
case k::Up: return Key::Up;
case k::Keypad1: return Key::Keypad1;
case k::Keypad2: return Key::Keypad2;
case k::Keypad3: return Key::Keypad3;
case k::CapsLock: return Key::CapsLock;
case k::LeftOption: return Key::LeftAlt;
case k::Space: return Key::Space;
case k::RightOption: return map(6, 0);
case k::RightControl: return map(6, 1);
case k::Left: return map(6, 2);
case k::Down: return map(6, 3);
case k::Right: return map(6, 4);
case k::Keypad0: return map(6, 5);
case k::KeypadDecimalPoint: return map(6, 6);
case k::KeypadEnter: return map(6, 7);
case k::RightOption: return Key::RightAlt;
case k::RightControl: return Key::RightControl;
case k::Left: return Key::Left;
case k::Down: return Key::Down;
case k::Right: return Key::Right;
case k::Keypad0: return Key::Keypad0;
case k::KeypadDecimalPoint: return Key::KeypadDecimalPoint;
case k::KeypadEnter: return Key::KeypadEnter;
default: return MachineTypes::MappedKeyboardMachine::KeyNotMapped;
}

View File

@ -106,8 +106,9 @@ struct MemoryController {
case 0b111:
os_mode_ = address & (1 << 12);
sound_dma_enable_ = address & (1 << 11);
ioc_.sound().set_dma_enabled(sound_dma_enable_);
video_dma_enable_ = address & (1 << 10);
ioc_.sound().set_dma_enabled(sound_dma_enable_);
ioc_.video().set_dma_enabled(video_dma_enable_);
switch((address >> 8) & 3) {
default:
dynamic_ram_refresh_ = DynamicRAMRefresh::None;

View File

@ -15,6 +15,7 @@
#include <cassert>
#include <cstdint>
#include <cstring>
#include <optional>
namespace Archimedes {
@ -243,6 +244,30 @@ struct Video {
return overages_;
}
void set_dma_enabled(bool dma_enabled) {
dma_enabled_ = dma_enabled;
}
//
// The following is provided for input automation;
// it does not correlate with real hardware functionality.
//
std::optional<std::pair<int, int>> cursor_location() {
if(
!dma_enabled_ ||
vertical_timing_.cursor_end <= vertical_timing_.cursor_start ||
horizontal_timing_.cursor_start >= (horizontal_timing_.period * 2)
) {
return std::nullopt;
}
const auto horizontal_start = horizontal_timing_.display_start + horizontal_state_.output_latency(colour_depth_);
return std::make_pair(
int(horizontal_timing_.cursor_start) + 6 - int(horizontal_start * 2),
int(vertical_timing_.cursor_start) - int(vertical_timing_.display_start));
}
private:
Log::Logger<Log::Source::ARMIOC> logger;
InterruptObserverT &interrupt_observer_;
@ -253,6 +278,7 @@ private:
// being deferred to the component itself.
const uint8_t *ram_ = nullptr;
Outputs::CRT::CRT crt_;
bool dma_enabled_ = false;
// Horizontal and vertical timing.
struct Timing {
@ -330,11 +356,12 @@ private:
}
bool is_outputting(Depth depth) const {
return position >= display_start + output_latencies[static_cast<uint32_t>(depth)] && position < display_end + output_latencies[static_cast<uint32_t>(depth)];
const auto latency = output_latency(depth);
return position >= display_start + latency && position < display_end + latency;
}
uint32_t output_cycle(Depth depth) const {
return position - display_start - output_latencies[static_cast<uint32_t>(depth)];
return position - display_start - output_latency(depth);
}
static constexpr uint32_t output_latencies[] = {
@ -343,6 +370,9 @@ private:
7 >> 1, // 4 bpp.
5 >> 1 // 8 bpp.
};
uint32_t output_latency(Depth depth) const {
return output_latencies[static_cast<uint32_t>(depth)];
}
static constexpr uint8_t SyncEnded = 0x1;
static constexpr uint8_t BorderStarted = 0x2;

View File

@ -51,7 +51,7 @@ template <bool has_scsi_bus> class ConcreteMachine:
public SCSI::Bus::Observer,
public ClockingHint::Observer {
public:
ConcreteMachine(const Analyser::Static::Acorn::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) :
ConcreteMachine(const Analyser::Static::Acorn::ElectronTarget &target, const ROMMachine::ROMFetcher &rom_fetcher) :
m6502_(*this),
scsi_bus_(4'000'000),
hard_drive_(scsi_bus_, 0),
@ -787,7 +787,7 @@ template <bool has_scsi_bus> class ConcreteMachine:
using namespace Electron;
std::unique_ptr<Machine> Machine::Electron(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) {
using Target = Analyser::Static::Acorn::Target;
using Target = Analyser::Static::Acorn::ElectronTarget;
const Target *const acorn_target = dynamic_cast<const Target *>(target);
if(acorn_target->media.mass_storage_devices.empty()) {

View File

@ -217,7 +217,7 @@ std::map<std::string, std::unique_ptr<Reflection::Struct>> Machine::AllOptionsBy
std::map<std::string, std::unique_ptr<Reflection::Struct>> options;
#define Emplace(machine, class) \
options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::machine), std::make_unique<class::Options>(Configurable::OptionsType::UserFriendly)));
options.emplace(LongNameForTargetMachine(Analyser::Machine::machine), std::make_unique<class::Options>(Configurable::OptionsType::UserFriendly))
Emplace(AmstradCPC, AmstradCPC::Machine);
Emplace(AppleII, Apple::II::Machine);
@ -243,16 +243,16 @@ std::map<std::string, std::unique_ptr<Analyser::Static::Target>> Machine::Target
std::map<std::string, std::unique_ptr<Analyser::Static::Target>> options;
#define AddMapped(Name, TargetNamespace) \
options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::Name), new Analyser::Static::TargetNamespace::Target));
options.emplace(LongNameForTargetMachine(Analyser::Machine::Name), std::make_unique<Analyser::Static::TargetNamespace::Target>());
#define Add(Name) AddMapped(Name, Name)
Add(Amiga);
Add(AmstradCPC);
Add(AppleII);
Add(AppleIIgs);
options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::Archimedes), new Analyser::Static::Target(Analyser::Machine::Archimedes)));
options.emplace(LongNameForTargetMachine(Analyser::Machine::Archimedes), std::make_unique<Analyser::Static::Acorn::ArchimedesTarget>());
Add(AtariST);
AddMapped(Electron, Acorn);
options.emplace(LongNameForTargetMachine(Analyser::Machine::Electron), std::make_unique<Analyser::Static::Acorn::ElectronTarget>());
Add(Enterprise);
Add(Macintosh);
Add(MSX);
@ -264,7 +264,7 @@ std::map<std::string, std::unique_ptr<Analyser::Static::Target>> Machine::Target
if(!meaningful_without_media_only) {
Add(Atari2600);
options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::ColecoVision), new Analyser::Static::Target(Analyser::Machine::ColecoVision)));
options.emplace(LongNameForTargetMachine(Analyser::Machine::ColecoVision), std::make_unique<Analyser::Static::Target>(Analyser::Machine::ColecoVision));
AddMapped(MasterSystem, Sega);
}

View File

@ -769,11 +769,11 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>24.04.20</string>
<string>24.05.05</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>24.04.20</string>
<string>24.05.05</string>
<key>LSApplicationCategoryType</key>
<string>public.app-category.entertainment</string>
<key>LSMinimumSystemVersion</key>

View File

@ -136,11 +136,10 @@
return self;
}
- (instancetype)initWithArchimedesModel:(CSMachineArchimedesModel)model {
self = [super init];
if(self) {
auto target = std::make_unique<Analyser::Static::Target>(Analyser::Machine::Archimedes);
auto target = std::make_unique<Analyser::Static::Acorn::ArchimedesTarget>();
_targets.push_back(std::move(target));
}
return self;
@ -164,8 +163,7 @@
- (instancetype)initWithElectronDFS:(BOOL)dfs adfs:(BOOL)adfs ap6:(BOOL)ap6 sidewaysRAM:(BOOL)sidewaysRAM {
self = [super init];
if(self) {
using Target = Analyser::Static::Acorn::Target;
auto target = std::make_unique<Target>();
auto target = std::make_unique<Analyser::Static::Acorn::ElectronTarget>();
target->has_dfs = dfs;
target->has_pres_adfs = adfs;
target->has_ap6_rom = ap6;