1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-07-09 21:29:53 +00:00
CLK/Machines/Amiga/Amiga.cpp
2021-07-22 19:00:26 -04:00

537 lines
18 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// Amiga.cpp
// Clock Signal
//
// Created by Thomas Harte on 16/07/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#include "Amiga.hpp"
#include "../../Activity/Source.hpp"
#include "../MachineTypes.hpp"
#include "../../Processors/68000/68000.hpp"
#include "../../Components/6526/6526.hpp"
#include "../../Analyser/Static/Amiga/Target.hpp"
#include "../Utility/MemoryPacker.hpp"
#include "../Utility/MemoryFuzzer.hpp"
//#define NDEBUG
#define LOG_PREFIX "[Amiga] "
#include "../../Outputs/Log.hpp"
#include "Blitter.hpp"
namespace Amiga {
class ConcreteMachine:
public Activity::Source,
public CPU::MC68000::BusHandler,
public MachineTypes::ScanProducer,
public MachineTypes::TimedMachine,
public Machine {
public:
ConcreteMachine(const Analyser::Static::Amiga::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) :
mc68000_(*this),
blitter_(reinterpret_cast<uint16_t *>(memory_.chip_ram.data()), memory_.chip_ram.size()),
cia_a_handler_(memory_),
cia_a_(cia_a_handler_),
cia_b_(cia_b_handler_)
{
(void)target;
// Temporary: use a hard-coded Kickstart selection.
constexpr ROM::Name rom_name = ROM::Name::AmigaA500Kickstart13;
ROM::Request request(rom_name);
auto roms = rom_fetcher(request);
if(!request.validate(roms)) {
throw ROMMachine::Error::MissingROMs;
}
Memory::PackBigEndian16(roms.find(rom_name)->second, memory_.kickstart.data());
// NTSC clock rate: 2*3.579545 = 7.15909Mhz.
// PAL clock rate: 7.09379Mhz.
set_clock_rate(7'093'790.0);
}
// MARK: - MC68000::BusHandler.
using Microcycle = CPU::MC68000::Microcycle;
HalfCycles perform_bus_operation(const CPU::MC68000::Microcycle &cycle, int) {
// Intended 1-2 step here is:
//
// (1) determine when this CPU access will be scheduled;
// (2) do all the other actions prior to this CPU access being scheduled.
//
// (or at least enqueue them, JIT-wise).
// Advance time.
// TODO: I think there's a divide-by-ten here. Probably these are driven off the 68000 E clock?
cia_a_.run_for(cycle.length);
cia_b_.run_for(cycle.length);
// Check for assertion of reset.
if(cycle.operation & Microcycle::Reset) {
memory_.reset();
LOG("Reset; PC is around " << PADHEX(8) << mc68000_.get_state().program_counter);
}
// Do nothing if no address is exposed.
if(!(cycle.operation & (Microcycle::NewAddress | Microcycle::SameAddress))) return HalfCycles(0);
// TODO: interrupt acknowledgement.
// Grab the target address to pick a memory source.
const uint32_t address = cycle.host_endian_byte_address();
// if((cycle.operation & (Microcycle::SelectByte | Microcycle::SelectWord)) && !(cycle.operation & Microcycle::IsProgram)) {
// printf("%06x\n", *cycle.address);
// }
if(!memory_.regions[address >> 18].read_write_mask) {
if((cycle.operation & (Microcycle::SelectByte | Microcycle::SelectWord))) {
// Check for various potential chip accesses.
// Per the manual:
//
// CIA A is: 101x xxxx xx01 rrrr xxxx xxx0 (i.e. loaded into high byte)
// CIA B is: 101x xxxx xx10 rrrr xxxx xxx1 (i.e. loaded into low byte)
//
// but in order to map 0xbfexxx to CIA A and 0xbfdxxx to CIA B, I think
// these might be listed the wrong way around.
//
// Additional assumption: the relevant CIA select lines are connected
// directly to the chip enables.
if((address & 0xe0'0000) == 0xa0'0000) {
const int reg = address >> 8;
LOG("CIA access: " << PADHEX(4) << *cycle.address);
if(cycle.operation & Microcycle::Read) {
uint16_t result = 0xffff;
if(!(address & 0x1000)) result &= 0xff00 | (cia_a_.read(reg) << 0);
if(!(address & 0x2000)) result &= 0x00ff | (cia_b_.read(reg) << 8);
cycle.set_value16(result);
} else {
if(!(address & 0x1000)) cia_a_.write(reg, cycle.value8_low());
if(!(address & 0x2000)) cia_b_.write(reg, cycle.value8_high());
}
} else if(address >= 0xdf'f000 && address <= 0xdf'f1be) {
#define RW(address) (address & 0xffe) | ((cycle.operation & Microcycle::Read) << 7)
#define Read(address) address | 0x1000
#define Write(address) address
#define ApplySetClear(target) { \
const uint16_t value = cycle.value16(); \
if(value & 0x8000) { \
target |= (value & 0x7fff); \
} else { \
target &= ~(value & 0x7fff); \
} \
}
switch(RW(address)) {
default:
LOG("Unimplemented chipset " << (cycle.operation & Microcycle::Read ? "read" : "write") << " " << PADHEX(6) << *cycle.address);
assert(false);
break;
// Disk DMA.
case Write(0x020): case Write(0x022): case Write(0x024):
case Write(0x026):
LOG("TODO: disk DMA; " << PADHEX(4) << cycle.value16() << " to " << *cycle.address);
break;
// Refresh.
case Write(0x028):
LOG("TODO (maybe): refresh; " << PADHEX(4) << cycle.value16() << " to " << *cycle.address);
break;
// Serial port.
case Write(0x030):
LOG("TODO: serial data: " << PADHEX(4) << cycle.value16());
break;
case Write(0x032):
LOG("TODO: serial control: " << PADHEX(4) << cycle.value16());
break;
// DMA management.
case Read(0x002):
LOG("DMA control and status read");
cycle.set_value16(dma_control_ | blitter_.get_status());
break;
case Write(0x096):
ApplySetClear(dma_control_);
LOG("DMA control modified by " << PADHEX(4) << cycle.value16() << "; is now " << std::bitset<16>{dma_control_});
break;
// Interrupts.
case Write(0x09a):
ApplySetClear(interrupt_enable_);
update_interrupts();
LOG("Interrupt enable mask modified by " << PADHEX(4) << cycle.value16() << "; is now " << std::bitset<16>{interrupt_enable_});
break;
case Write(0x09c):
ApplySetClear(interrupt_requests_);
update_interrupts();
LOG("Interrupt request modified by " << PADHEX(4) << cycle.value16() << "; is now " << std::bitset<16>{interrupt_requests_});
break;
// Bitplanes.
case Write(0x100):
case Write(0x102):
case Write(0x104):
case Write(0x106):
LOG("TODO: Bitplane control; " << PADHEX(4) << cycle.value16() << " to " << *cycle.address);
break;
case Write(0x108):
case Write(0x10a):
LOG("TODO: Bitplane modulo; " << PADHEX(4) << cycle.value16() << " to " << *cycle.address);
break;
case Write(0x110):
case Write(0x112):
case Write(0x114):
case Write(0x116):
case Write(0x118):
case Write(0x11a):
LOG("TODO: Bitplane data; " << PADHEX(4) << cycle.value16() << " to " << *cycle.address);
break;
case Read(0x110): case Read(0x112): case Read(0x114): case Read(0x116):
case Read(0x118): case Read(0x11a):
cycle.set_value16(0xffff);
break;
// Blitter.
case Write(0x040): blitter_.set_control(0, cycle.value16()); break;
case Write(0x042): blitter_.set_control(1, cycle.value16()); break;
case Write(0x044): blitter_.set_first_word_mask(cycle.value16()); break;
case Write(0x046): blitter_.set_last_word_mask(cycle.value16()); break;
case Write(0x048): blitter_.set_pointer(2, 16, cycle.value16()); break;
case Write(0x04a): blitter_.set_pointer(2, 0, cycle.value16()); break;
case Write(0x04c): blitter_.set_pointer(1, 16, cycle.value16()); break;
case Write(0x04e): blitter_.set_pointer(1, 0, cycle.value16()); break;
case Write(0x050): blitter_.set_pointer(0, 16, cycle.value16()); break;
case Write(0x052): blitter_.set_pointer(0, 0, cycle.value16()); break;
case Write(0x054): blitter_.set_pointer(3, 16, cycle.value16()); break;
case Write(0x056): blitter_.set_pointer(3, 0, cycle.value16()); break;
case Write(0x058): blitter_.set_size(cycle.value16()); break;
case Write(0x05a): blitter_.set_minterms(cycle.value16()); break;
case Write(0x05c): blitter_.set_vertical_size(cycle.value16()); break;
case Write(0x05e): blitter_.set_horizontal_size(cycle.value16()); break;
case Write(0x060): blitter_.set_modulo(2, cycle.value16()); break;
case Write(0x062): blitter_.set_modulo(1, cycle.value16()); break;
case Write(0x064): blitter_.set_modulo(0, cycle.value16()); break;
case Write(0x066): blitter_.set_modulo(3, cycle.value16()); break;
case Write(0x070): blitter_.set_data(2, cycle.value16()); break;
case Write(0x072): blitter_.set_data(1, cycle.value16()); break;
case Write(0x074): blitter_.set_data(0, cycle.value16()); break;
// Copper.
case Write(0x02e):
LOG("TODO: coprocessor control " << PADHEX(4) << cycle.value16());
break;
case Write(0x080):
LOG("TODO: coprocessor first location register high " << PADHEX(4) << cycle.value16());
break;
case Write(0x082):
LOG("TODO: coprocessor first location register low " << PADHEX(4) << cycle.value16());
break;
case Write(0x084):
LOG("TODO: coprocessor second location register high " << PADHEX(4) << cycle.value16());
break;
case Write(0x086):
LOG("TODO: coprocessor second location register low " << PADHEX(4) << cycle.value16());
break;
case Write(0x088): case Read(0x088):
LOG("TODO: coprocessor restart at first location");
break;
case Write(0x08a): case Read(0x08a):
LOG("TODO: coprocessor restart at second location");
break;
case Write(0x08c):
LOG("TODO: coprocessor instruction fetch identity " << PADHEX(4) << cycle.value16());
break;
// Sprites.
#define Sprite(index, pointer, position) \
case Write(pointer + 0): sprites_[index].set_pointer(16, cycle.value16()); break; \
case Write(pointer + 2): sprites_[index].set_pointer(0, cycle.value16()); break; \
case Write(position + 0): sprites_[index].set_start_position(cycle.value16()); break; \
case Write(position + 2): sprites_[index].set_stop_and_control(cycle.value16()); break; \
case Write(position + 4): sprites_[index].set_image_data(0, cycle.value16()); break; \
case Write(position + 6): sprites_[index].set_image_data(1, cycle.value16()); break;
Sprite(0, 0x120, 0x140);
Sprite(1, 0x124, 0x148);
Sprite(2, 0x128, 0x150);
Sprite(3, 0x12c, 0x158);
Sprite(4, 0x130, 0x160);
Sprite(5, 0x134, 0x168);
Sprite(6, 0x138, 0x170);
Sprite(7, 0x13c, 0x178);
#undef Sprite
// Colour palette.
case Write(0x180): case Write(0x182): case Write(0x184): case Write(0x186):
case Write(0x188): case Write(0x18a): case Write(0x18c): case Write(0x18e):
case Write(0x190): case Write(0x192): case Write(0x194): case Write(0x196):
case Write(0x198): case Write(0x19a): case Write(0x19c): case Write(0x19e):
case Write(0x1a0): case Write(0x1a2): case Write(0x1a4): case Write(0x1a6):
case Write(0x1a8): case Write(0x1aa): case Write(0x1ac): case Write(0x1ae):
case Write(0x1b0): case Write(0x1b2): case Write(0x1b4): case Write(0x1b6):
case Write(0x1b8): case Write(0x1ba): case Write(0x1bc): case Write(0x1be):
LOG("TODO: colour palette; " << PADHEX(4) << cycle.value16() << " to " << *cycle.address);
break;
}
#undef ApplySetClear
#undef Write
#undef Read
#undef RW
} else {
// This'll do for open bus, for now.
if(cycle.operation & Microcycle::Read) {
cycle.set_value16(0xffff);
}
LOG("Unmapped access to " << PADHEX(4) << *cycle.address);
}
}
} else {
// A regular memory access.
cycle.apply(
&memory_.regions[address >> 18].contents[address],
memory_.regions[address >> 18].read_write_mask
);
}
return HalfCycles(0);
}
private:
CPU::MC68000::Processor<ConcreteMachine, true> mc68000_;
// MARK: - Memory map.
struct MemoryMap {
public:
std::array<uint8_t, 512*1024> chip_ram{};
std::array<uint8_t, 512*1024> kickstart{0xff};
struct MemoryRegion {
uint8_t *contents = nullptr;
unsigned int read_write_mask = 0;
} regions[64]; // i.e. top six bits are used as an index.
MemoryMap() {
// Address spaces that matter:
//
// 00'0000 08'0000: chip RAM. [or overlayed KickStart]
// 10'0000: extended chip ram for ECS.
// 20'0000: auto-config space (/fast RAM).
// ...
// bf'd000 c0'0000: 8250s.
// c0'0000 d8'0000: pseudo-fast RAM.
// ...
// dc'0000 dd'0000: optional real-time clock.
// df'f000 - e0'0000: custom chip registers.
// ...
// f0'0000 — : 512kb Kickstart (or possibly just an extra 512kb reserved for hypothetical 1mb Kickstart?).
// f8'0000 — : 256kb Kickstart if 2.04 or higher.
// fc'0000 : 256kb Kickstart otherwise.
set_region(0xfc'0000, 0x1'00'0000, kickstart.data(), CPU::MC68000::Microcycle::PermitRead);
reset();
}
void reset() {
set_overlay(true);
}
void set_overlay(bool enabled) {
if(overlay_ == enabled) {
return;
}
overlay_ = enabled;
if(enabled) {
set_region(0x00'0000, 0x08'0000, kickstart.data(), CPU::MC68000::Microcycle::PermitRead);
} else {
// Mirror RAM to fill out the address range up to $20'0000 (?)
set_region(0x00'0000, 0x08'0000, chip_ram.data(), CPU::MC68000::Microcycle::PermitRead | CPU::MC68000::Microcycle::PermitWrite);
}
}
private:
bool overlay_ = false;
void set_region(int start, int end, uint8_t *base, unsigned int read_write_mask) {
assert(!(start & ~0xfc'0000));
assert(!((end - (1 << 18)) & ~0xfc'0000));
base -= start;
for(int c = start >> 18; c < end >> 18; c++) {
regions[c].contents = base;
regions[c].read_write_mask = read_write_mask;
}
}
} memory_;
// MARK: - Interrupts.
uint16_t interrupt_enable_ = 0;
uint16_t interrupt_requests_ = 0;
void update_interrupts() {
// TODO.
}
// MARK: - Sprites.
struct Sprite {
void set_pointer(int shift, uint16_t value) {
LOG("Sprite pointer with shift " << shift << " to " << PADHEX(4) << value);
}
void set_start_position(uint16_t value) {
LOG("Sprite start position " << PADHEX(4) << value);
}
void set_stop_and_control(uint16_t value) {
LOG("Sprite stop and control " << PADHEX(4) << value);
}
void set_image_data(int slot, uint16_t value) {
LOG("Sprite image data " << slot << " to " << PADHEX(4) << value);
}
} sprites_[8];
// MARK: - DMA control, blitter and Paula.
uint16_t dma_control_ = 0;
Blitter blitter_;
// MARK: - CIAs.
class CIAAHandler: public MOS::MOS6526::PortHandler {
public:
CIAAHandler(MemoryMap &map) : map_(map) {}
void set_port_output(MOS::MOS6526::Port port, uint8_t value) {
if(port) {
// Parallel port output.
LOG("TODO: parallel output " << PADHEX(2) << +value);
} else {
// b7: /FIR1
// b6: /FIR0
// b5: /RDY
// b4: /TRK0
// b3: /WPRO
// b2: /CHNG
// b1: /LED [output]
// b0: OVL [output]
LOG("LED & memory map: " << PADHEX(2) << +value);
if(observer_) {
observer_->set_led_status(led_name, !(value & 2));
}
map_.set_overlay(value & 1);
}
}
uint8_t get_port_input(MOS::MOS6526::Port port) {
if(port) {
LOG("TODO: parallel input?");
} else {
LOG("TODO: CIA A, port A input — FIR, RDY, TRK0, etc");
}
return 0xff;
}
void set_activity_observer(Activity::Observer *observer) {
observer_ = observer;
if(observer) {
observer->register_led(led_name, Activity::Observer::LEDPresentation::Persistent);
}
}
private:
MemoryMap &map_;
Activity::Observer *observer_ = nullptr;
inline static const std::string led_name = "Power";
} cia_a_handler_;
struct CIABHandler: public MOS::MOS6526::PortHandler {
void set_port_output(MOS::MOS6526::Port port, uint8_t value) {
if(port) {
// Serial port control.
//
// b7: /DTR
// b6: /RTS
// b5: /CD
// b4: /CTS
// b3: /DSR
// b2: SEL
// b1: POUT
// b0: BUSY
LOG("TODO: Serial control: " << PADHEX(2) << +value);
} else {
// Disk motor control, drive and head selection,
// and stepper control:
//
// b7: /MTR
// b6: /SEL3
// b5: /SEL2
// b4: /SEL1
// b3: /SEL0
// b2: /SIDE
// b1: DIR
// b0: /STEP
LOG("TODO: Stepping, etc; " << PADHEX(2) << +value);
}
}
} cia_b_handler_;
MOS::MOS6526::MOS6526<CIAAHandler, MOS::MOS6526::Personality::P8250> cia_a_;
MOS::MOS6526::MOS6526<CIABHandler, MOS::MOS6526::Personality::P8250> cia_b_;
// MARK: - Activity Source
void set_activity_observer(Activity::Observer *observer) final {
cia_a_handler_.set_activity_observer(observer);
}
// MARK: - MachineTypes::ScanProducer.
void set_scan_target(Outputs::Display::ScanTarget *scan_target) final {
(void)scan_target;
}
Outputs::Display::ScanStatus get_scaled_scan_status() const {
return Outputs::Display::ScanStatus();
}
// MARK: - MachineTypes::TimedMachine.
void run_for(const Cycles cycles) {
mc68000_.run_for(cycles);
}
};
}
using namespace Amiga;
Machine *Machine::Amiga(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) {
using Target = Analyser::Static::Amiga::Target;
const Target *const amiga_target = dynamic_cast<const Target *>(target);
return new Amiga::ConcreteMachine(*amiga_target, rom_fetcher);
}
Machine::~Machine() {}