1
0
mirror of https://github.com/TomHarte/CLK.git synced 2025-02-28 06:29:37 +00:00
2024-12-12 22:59:20 -05:00

362 lines
9.5 KiB
C++

//
// Plus4.cpp
// Clock Signal
//
// Created by Thomas Harte on 06/12/2024.
// Copyright © 2024 Thomas Harte. All rights reserved.
//
#include "Plus4.hpp"
#include "Interrupts.hpp"
#include "Pager.hpp"
#include "Video.hpp"
#include "../../MachineTypes.hpp"
#include "../../Utility/MemoryFuzzer.hpp"
#include "../../../Processors/6502/6502.hpp"
#include "../../../Analyser/Static/Commodore/Target.hpp"
using namespace Commodore::Plus4;
namespace {
class Timers {
public:
Timers(Interrupts &interrupts) : interrupts_(interrupts) {}
template <int offset>
void write(const uint8_t value) {
const auto load_low = [&](uint16_t &target) {
target = uint16_t((target & 0xff00) | (value << 0));
};
const auto load_high = [&](uint16_t &target) {
target = uint16_t((target & 0x00ff) | (value << 8));
};
constexpr auto timer = offset >> 1;
paused_[timer] = !(offset & 1);
if constexpr (offset & 1) {
load_high(timers_[timer]);
if(!timer) {
load_high(timer0_reload_);
}
} else {
load_low(timers_[timer]);
if(!timer) {
load_low(timer0_reload_);
}
}
}
template <int offset>
uint8_t read() {
constexpr auto timer = offset >> 1;
if constexpr (offset & 1) {
return uint8_t(timers_[timer] >> 8);
} else {
return uint8_t(timers_[timer] >> 0);
}
}
void tick(int count) {
// Quick hack here; do better than stepping through one at a time.
while(count--) {
decrement<0>();
decrement<1>();
decrement<2>();
}
}
private:
template <int timer>
void decrement() {
if(paused_[timer]) return;
// Check for reload.
if(!timer && !timers_[timer]) {
timers_[timer] = timer0_reload_;
}
-- timers_[timer];
// Check for interrupt.
if(!timers_[timer]) {
switch(timer) {
case 0: interrupts_.apply(Interrupts::Flag::Timer1); break;
case 1: interrupts_.apply(Interrupts::Flag::Timer2); break;
case 2: interrupts_.apply(Interrupts::Flag::Timer3); break;
}
}
}
uint16_t timers_[3]{};
uint16_t timer0_reload_ = 0xffff;
bool paused_[3]{};
Interrupts &interrupts_;
};
class ConcreteMachine:
public CPU::MOS6502::BusHandler,
public Interrupts::Delegate,
public MachineTypes::TimedMachine,
public MachineTypes::ScanProducer,
public MachineTypes::MediaTarget,
public Machine {
public:
ConcreteMachine(const Analyser::Static::Commodore::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) :
m6502_(*this),
interrupts_(*this),
timers_(interrupts_),
video_(map_, interrupts_)
{
// PAL: 8867240 divided by 5 or 4?
// NTSC: 7159090?
// i.e. colour subcarriers multiplied by two?
set_clock_rate(7159090); // TODO.
const auto kernel = ROM::Name::Plus4KernelPALv5;
const auto basic = ROM::Name::Plus4BASIC;
const ROM::Request request = ROM::Request(basic) && ROM::Request(kernel);
auto roms = rom_fetcher(request);
if(!request.validate(roms)) {
throw ROMMachine::Error::MissingROMs;
}
kernel_ = roms.find(kernel)->second;
basic_ = roms.find(basic)->second;
Memory::Fuzz(ram_);
map_.page<PagerSide::ReadWrite, 0, 65536>(ram_.data());
page_rom();
insert_media(target.media);
}
Cycles perform_bus_operation(
const CPU::MOS6502::BusOperation operation,
const uint16_t address,
uint8_t *const value
) {
// TODO: calculate length of this bus operation.
const auto length = Cycles(5);
// Update other subsystems.
// TODO: timers decrement at a 894 KHz rate for NTSC television systems, 884 KHZ for PAL systems.
// Probably a function of the speed register?
timers_subcycles_ += length;
const auto timers_cycles = timers_subcycles_.divide(Cycles(5));
timers_.tick(timers_cycles.as<int>());
video_.run_for(length);
// Perform actual access.
if(address < 0xfd00 || address >= 0xff40) {
if(isReadOperation(operation)) {
*value = map_.read(address);
} else {
map_.write(address) = *value;
}
} else if(address < 0xff00) {
// Miscellaneous hardware. All TODO.
// if(isReadOperation(operation)) {
// printf("TODO: read @ %04x\n", address);
// } else {
// printf("TODO: write of %02x @ %04x\n", *value, address);
// }
} else {
if(isReadOperation(operation)) {
switch(address) {
case 0xff00: *value = timers_.read<0>(); break;
case 0xff01: *value = timers_.read<1 >(); break;
case 0xff02: *value = timers_.read<2>(); break;
case 0xff03: *value = timers_.read<3>(); break;
case 0xff04: *value = timers_.read<4>(); break;
case 0xff05: *value = timers_.read<5>(); break;
case 0xff08:
// TODO: keyboard.
*value = 0xff;
break;
case 0xff09: *value = interrupts_.status(); break;
case 0xff0a: *value = interrupts_.mask(); break;
case 0xff0b: *value = video_.read<0xff0b>(); break;
case 0xff1c: *value = video_.read<0xff1c>(); break;
case 0xff1d: *value = video_.read<0xff1d>(); break;
case 0xff15: *value = video_.read<0xff15>(); break;
case 0xff16: *value = video_.read<0xff16>(); break;
case 0xff17: *value = video_.read<0xff17>(); break;
case 0xff18: *value = video_.read<0xff18>(); break;
case 0xff19: *value = video_.read<0xff19>(); break;
default:
printf("TODO: TED read at %04x\n", address);
}
} else {
switch(address) {
case 0xff00: timers_.write<0>(*value); break;
case 0xff01: timers_.write<1>(*value); break;
case 0xff02: timers_.write<2>(*value); break;
case 0xff03: timers_.write<3>(*value); break;
case 0xff04: timers_.write<4>(*value); break;
case 0xff05: timers_.write<5>(*value); break;
case 0xff08:
// TODO: keyboard.
break;
case 0xff09:
interrupts_.set_status(*value);
break;
case 0xff0a:
interrupts_.set_mask(*value);
video_.write<0xff0a>(*value);
break;
case 0xff0b: video_.write<0xff0b>(*value); break;
case 0xff06: video_.write<0xff06>(*value); break;
case 0xff07: video_.write<0xff07>(*value); break;
case 0xff0c: video_.write<0xff0c>(*value); break;
case 0xff0d: video_.write<0xff0d>(*value); break;
case 0xff12: video_.write<0xff12>(*value); break;
case 0xff14: video_.write<0xff14>(*value); break;
case 0xff1a: video_.write<0xff1a>(*value); break;
case 0xff1b: video_.write<0xff1b>(*value); break;
case 0xff15: video_.write<0xff15>(*value); break;
case 0xff16: video_.write<0xff16>(*value); break;
case 0xff17: video_.write<0xff17>(*value); break;
case 0xff18: video_.write<0xff18>(*value); break;
case 0xff19: video_.write<0xff19>(*value); break;
case 0xff3e: page_rom(); break;
case 0xff3f: page_ram(); break;
default:
printf("TODO: TED write at %04x\n", address);
}
}
}
return length;
}
private:
CPU::MOS6502::Processor<CPU::MOS6502::Personality::P6502, ConcreteMachine, true> m6502_;
void set_irq_line(bool active) override {
m6502_.set_irq_line(active);
}
void page_rom() {
map_.page<PagerSide::Read, 0x8000, 16384>(basic_.data());
map_.page<PagerSide::Read, 0xc000, 16384>(kernel_.data());
}
void page_ram() {
map_.page<PagerSide::Read, 0x8000, 32768>(&ram_[0x8000]);
}
void set_scan_target(Outputs::Display::ScanTarget *const target) final {
video_.set_scan_target(target);
}
Outputs::Display::ScanStatus get_scaled_scan_status() const final {
return video_.get_scaled_scan_status();
}
void run_for(const Cycles cycles) final {
static bool log = false;
m6502_.run_for(cycles);
if(!log) return;
for(size_t y = 0; y < 25; y++) {
for(size_t x = 0; x < 40; x++) {
const auto c = ram_[0xc00 + x + (y * 40)];
printf("%c", [&] {
switch(c) {
case 0x00: return '@';
case 0x01: return 'A';
case 0x02: return 'B';
case 0x03: return 'C';
case 0x04: return 'D';
case 0x05: return 'E';
case 0x06: return 'F';
case 0x07: return 'G';
case 0x08: return 'H';
case 0x09: return 'I';
case 0x0a: return 'J';
case 0x0b: return 'K';
case 0x0c: return 'L';
case 0x0d: return 'M';
case 0x0e: return 'N';
case 0x0f: return 'O';
case 0x10: return 'P';
case 0x11: return 'Q';
case 0x12: return 'R';
case 0x13: return 'S';
case 0x14: return 'T';
case 0x15: return 'U';
case 0x16: return 'V';
case 0x17: return 'W';
case 0x18: return 'X';
case 0x19: return 'Y';
case 0x1a: return 'Z';
case 0x1b: return '[';
case 0x1c: return '\\';
case 0x1d: return ']';
case 0x1e: return '?';
case 0x1f: return '?';
case 0x20: return ' ';
case 0x2e: return '.';
case 0x30: return '0';
case 0x31: return '1';
case 0x32: return '2';
case 0x33: return '3';
case 0x34: return '4';
case 0x35: return '5';
case 0x36: return '6';
case 0x37: return '7';
case 0x38: return '8';
case 0x39: return '9';
default: return '?';
}
}());
}
printf("\n");
}
printf("\n");
}
bool insert_media(const Analyser::Static::Media &) final {
return true;
}
Commodore::Plus4::Pager map_;
std::array<uint8_t, 65536> ram_;
std::vector<uint8_t> kernel_;
std::vector<uint8_t> basic_;
Interrupts interrupts_;
Cycles timers_subcycles_;
Timers timers_;
Video video_;
};
}
std::unique_ptr<Machine> Machine::Plus4(
const Analyser::Static::Target *target,
const ROMMachine::ROMFetcher &rom_fetcher
) {
using Target = Analyser::Static::Commodore::Target;
const Target *const commodore_target = dynamic_cast<const Target *>(target);
return std::make_unique<ConcreteMachine>(*commodore_target, rom_fetcher);
}