mirror of
https://github.com/TomHarte/CLK.git
synced 2026-03-11 04:42:20 +00:00
Make broad sweeping swing at capturing the standard Commodore load file routine.
This commit is contained in:
@@ -250,18 +250,15 @@ public:
|
||||
|
||||
// Update other subsystems.
|
||||
advance_timers_and_tape(length);
|
||||
if(!superspeed_) {
|
||||
video_.run_for(length);
|
||||
video_.run_for(length);
|
||||
|
||||
if(c1541_) {
|
||||
c1541_cycles_ += length * Cycles(1'000'000);
|
||||
c1541_->run_for(c1541_cycles_.divide(media_divider_));
|
||||
}
|
||||
|
||||
|
||||
time_since_audio_update_ += length;
|
||||
if(c1541_) {
|
||||
c1541_cycles_ += length * Cycles(1'000'000);
|
||||
c1541_->run_for(c1541_cycles_.divide(media_divider_));
|
||||
}
|
||||
|
||||
time_since_audio_update_ += length;
|
||||
|
||||
if(operation == CPU::MOS6502Mk2::BusOperation::Ready) {
|
||||
return length;
|
||||
}
|
||||
@@ -299,100 +296,74 @@ public:
|
||||
serial_port_.set_output(Serial::Line::Attention, Serial::LineLevel(~output & 0x04));
|
||||
}
|
||||
} else if(address < 0xfd00 || address >= 0xff40) {
|
||||
// if(
|
||||
// use_fast_tape_hack_ &&
|
||||
// operation == CPU::MOS6502Esque::BusOperation::ReadOpcode
|
||||
// ) {
|
||||
// superspeed_ |= address == 0xe5fd;
|
||||
// superspeed_ &= (address != 0xe68b) && (address != 0xe68d);
|
||||
// }
|
||||
|
||||
static constexpr bool use_hle = true;
|
||||
|
||||
// if(
|
||||
// use_fast_tape_hack_ &&
|
||||
// operation == CPU::MOS6502Esque::BusOperation::ReadOpcode &&
|
||||
// address == 0xe5fd
|
||||
// ) {
|
||||
// printf("Pulse %d from %lld ",
|
||||
// pulse_num_,
|
||||
// tape_player_->serialiser()->offset()
|
||||
// );
|
||||
// }
|
||||
|
||||
if constexpr (is_read(operation)) {
|
||||
value = map_.read(address);
|
||||
|
||||
if(
|
||||
use_fast_tape_hack_ &&
|
||||
operation == CPU::MOS6502Mk2::BusOperation::ReadOpcode &&
|
||||
(
|
||||
(use_hle && address == 0xe5fd) ||
|
||||
address == 0xe68b ||
|
||||
address == 0xe68d
|
||||
)
|
||||
|
||||
address == 0xf0f0 // ldcass
|
||||
) {
|
||||
// ++pulse_num_;
|
||||
if(use_hle) {
|
||||
read_dipole();
|
||||
// Input:
|
||||
// A: 0 = Load, 1-255 = Verify;
|
||||
// X/Y = Load address (if secondary address = 0).
|
||||
// Output:
|
||||
// Carry: 0 = No errors, 1 = Error;
|
||||
// A = KERNAL error code (if Carry = 1);
|
||||
// X/Y = Address of last byte loaded/verified (if Carry = 0).
|
||||
// Used registers: A, X, Y. Real address: $F49E.
|
||||
|
||||
|
||||
auto registers = m6502_.registers();
|
||||
// static constexpr uint16_t tape_buffer = 0x0333;
|
||||
|
||||
// TODO: check byte at 0xab for a potential filename length; if set then get
|
||||
// filename... from somewhere?
|
||||
|
||||
const uint8_t name_length = ram_[0xab];
|
||||
if(name_length) {
|
||||
printf("Name: ??? [%d bytes]\n", name_length);
|
||||
}
|
||||
|
||||
// using Flag = CPU::MOS6502::Flag;
|
||||
// using Register = CPU::MOS6502::Register;
|
||||
// const auto flags = m6502_.value_of(Register::Flags);
|
||||
// printf("to %lld: %c%c%c\n",
|
||||
// tape_player_->serialiser()->offset(),
|
||||
// flags & Flag::Sign ? 'n' : '-',
|
||||
// flags & Flag::Overflow ? 'v' : '-',
|
||||
// flags & Flag::Carry ? 'c' : '-'
|
||||
// );
|
||||
value = 0x60;
|
||||
} else {
|
||||
value = map_.read(address);
|
||||
}
|
||||
} else {
|
||||
map_.write(address) = value;
|
||||
}
|
||||
|
||||
|
||||
// TODO: rdbyte and ldsync is probably sufficient?
|
||||
|
||||
// if(use_fast_tape_hack_ && operation == CPU::MOS6502Esque::BusOperation::ReadOpcode) {
|
||||
// static constexpr uint16_t ldsync = 0;
|
||||
// switch(address) {
|
||||
// default: break;
|
||||
//
|
||||
// case ldsync:
|
||||
// break;
|
||||
// }
|
||||
//
|
||||
// if(address == 0xe9cc) {
|
||||
// // Skip the `jsr rdblok` that opens `fah` (i.e. find any header), performing
|
||||
// // its function as a high-level emulation.
|
||||
// Storage::Tape::Commodore::Parser parser(TargetPlatform::Plus4);
|
||||
// auto header = parser.get_next_header(*tape_player_->serialiser());
|
||||
//
|
||||
// const auto tape_position = tape_player_->serialiser()->offset();
|
||||
// if(header) {
|
||||
// // Copy to in-memory buffer and set type.
|
||||
Storage::Tape::Commodore::Parser parser(TargetPlatform::Plus4);
|
||||
const auto header = parser.get_next_header(*tape_player_->serialiser());
|
||||
if(header) {
|
||||
// TODO: Copy header into place.
|
||||
// std::memcpy(&ram_[0x0333], header->data.data(), 191);
|
||||
// map_.write(0xb6) = 0x33;
|
||||
// map_.write(0xb7) = 0x03;
|
||||
// map_.write(0xf8) = header->type_descriptor();
|
||||
//// hold_tape_ = true;
|
||||
// Logger::info().append("Found header");
|
||||
// } else {
|
||||
// // no header found, so pretend this hack never interceded
|
||||
// tape_player_->serialiser()->set_offset(tape_position);
|
||||
//// hold_tape_ = false;
|
||||
// Logger::info().append("Didn't find header");
|
||||
// }
|
||||
//
|
||||
// // Clear status and the verify flags.
|
||||
|
||||
const auto body = parser.get_next_data(*tape_player_->serialiser());
|
||||
|
||||
if(body) {
|
||||
// auto load_address =
|
||||
// ram_[0xad] ? uint16_t((registers.y << 8) | registers.x) : header->starting_address;
|
||||
auto load_address =
|
||||
ram_[0xad] ? uint16_t((registers.y << 8) | registers.x) : header->starting_address;
|
||||
|
||||
for(const auto byte: body->data) {
|
||||
ram_[load_address] = byte;
|
||||
++load_address;
|
||||
}
|
||||
|
||||
--load_address;
|
||||
registers.x = load_address & 0xff;
|
||||
registers.y = load_address >> 8;
|
||||
registers.flags.set_per<CPU::MOS6502Mk2::Flag::Carry>(0); // C = 0 => success.
|
||||
}
|
||||
}
|
||||
|
||||
// ram_[0x90] = 0;
|
||||
// ram_[0x93] = 0;
|
||||
//
|
||||
// value = 0x0c; // NOP abs.
|
||||
// }
|
||||
// }
|
||||
|
||||
m6502_.set_registers(registers);
|
||||
value = 0x60; // i.e. RTS.
|
||||
}
|
||||
} else {
|
||||
map_.write(address) = value;
|
||||
}
|
||||
} else if(address < 0xff00) {
|
||||
// Miscellaneous hardware. All TODO.
|
||||
if constexpr (is_read(operation)) {
|
||||
@@ -621,7 +592,7 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
return superspeed_ ? Cycles(0) : length;
|
||||
return length;
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -769,12 +740,11 @@ private:
|
||||
|
||||
std::unique_ptr<Storage::Tape::BinaryTapePlayer> tape_player_;
|
||||
bool play_button_ = false;
|
||||
bool allow_fast_tape_hack_ = false; // TODO: implement fast-tape hack.
|
||||
bool allow_fast_tape_hack_ = false;
|
||||
bool use_fast_tape_hack_ = false;
|
||||
bool superspeed_ = false;
|
||||
void set_use_fast_tape() {
|
||||
use_fast_tape_hack_ =
|
||||
allow_fast_tape_hack_ && tape_player_->motor_control() && rom_is_paged_ && !tape_player_->is_at_end();
|
||||
allow_fast_tape_hack_ && rom_is_paged_ && !tape_player_->is_at_end();
|
||||
}
|
||||
void update_tape_motor() {
|
||||
const auto output = io_output_ | ~io_direction_;
|
||||
@@ -788,352 +758,6 @@ private:
|
||||
tape_player_->run_for(length);
|
||||
}
|
||||
|
||||
// TODO: substantially simplify the below; at the minute it's a
|
||||
// literal transcription of the original as a simple first step.
|
||||
void read_dipole() {
|
||||
using Flag = CPU::MOS6502Mk2::Flag;
|
||||
|
||||
//
|
||||
// Get registers now and ensure they'll be written back at function exit.
|
||||
//
|
||||
auto registers = m6502_.registers();
|
||||
struct ScopeGuard {
|
||||
ScopeGuard(std::function<void(void)> at_exit) : at_exit_(at_exit) {}
|
||||
~ScopeGuard() { at_exit_(); }
|
||||
private:
|
||||
std::function<void(void)> at_exit_;
|
||||
} store_registers([&] {
|
||||
m6502_.set_registers(registers);
|
||||
});
|
||||
|
||||
//
|
||||
// Time advancement.
|
||||
//
|
||||
const auto advance_cycles = [&](int cycles) -> bool {
|
||||
advance_timers_and_tape(video_.cycle_length(false) * cycles);
|
||||
return !use_fast_tape_hack_;
|
||||
};
|
||||
|
||||
//
|
||||
// 6502 pseudo-ops.
|
||||
//
|
||||
const auto ldabs = [&] (uint8_t &target, const uint16_t address) {
|
||||
registers.flags.set_per<Flag::NegativeZero>(target = map_.read(address));
|
||||
};
|
||||
const auto ldimm = [&] (uint8_t &target, const uint8_t value) {
|
||||
registers.flags.set_per<Flag::NegativeZero>(target = value);
|
||||
};
|
||||
const auto pha = [&] () {
|
||||
map_.write(0x100 + registers.s) = registers.a;
|
||||
--registers.s;
|
||||
};
|
||||
const auto pla = [&] () {
|
||||
++registers.s;
|
||||
registers.a = map_.read(0x100 + registers.s);
|
||||
};
|
||||
const auto bit = [&] (const uint8_t value) {
|
||||
registers.flags.set_per<Flag::Zero>(registers.a & value);
|
||||
registers.flags.set_per<Flag::Negative>(value);
|
||||
registers.flags.set_per<Flag::Overflow>(value);
|
||||
};
|
||||
const auto cmp = [&] (const uint8_t value) {
|
||||
const uint16_t temp16 = registers.a - value;
|
||||
registers.flags.set_per<Flag::NegativeZero>(uint8_t(temp16));
|
||||
registers.flags.set_per<Flag::Carry>(((~temp16) >> 8)&1);
|
||||
};
|
||||
const auto andimm = [&] (const uint8_t value) {
|
||||
registers.a &= value;
|
||||
registers.flags.set_per<Flag::NegativeZero>(registers.a);
|
||||
};
|
||||
const auto ne = [&]() -> bool {
|
||||
return !registers.flags.get<Flag::Zero>();
|
||||
};
|
||||
const auto eq = [&]() -> bool {
|
||||
return registers.flags.get<Flag::Zero>();
|
||||
};
|
||||
|
||||
//
|
||||
// Common branch points.
|
||||
//
|
||||
const auto dipok = [&] {
|
||||
// clc ; everything's fine
|
||||
// rts
|
||||
registers.flags.set_per<Flag::Carry>(0);
|
||||
};
|
||||
const auto rshort = [&] {
|
||||
// bit tshrtd ; got a short
|
||||
// bvs dipok ; !bra
|
||||
bit(0x40);
|
||||
dipok();
|
||||
};
|
||||
const auto rlong = [&] {
|
||||
// bit tlongd ; got a long
|
||||
bit(0x00);
|
||||
dipok();
|
||||
};
|
||||
const auto rderr1 = [&] {
|
||||
// sec ; i'm confused
|
||||
// rts
|
||||
registers.flags.set_per<Flag::Carry>(Flag::Carry);
|
||||
};
|
||||
|
||||
//
|
||||
// Labels.
|
||||
//
|
||||
static constexpr uint16_t dsamp1 = 0x7b8;
|
||||
static constexpr uint16_t dsamp2 = 0x7ba;
|
||||
static constexpr uint16_t zcell = 0x07bc;
|
||||
|
||||
//rddipl
|
||||
// ldx dsamp1 ; setup x,y with 1st sample point
|
||||
// ldy dsamp1+1
|
||||
ldabs(registers.x, dsamp1);
|
||||
ldabs(registers.y, dsamp1 + 1);
|
||||
advance_cycles(8);
|
||||
|
||||
//badeg1
|
||||
do {
|
||||
// lda dsamp2+1 ; put 2nd samp value on stack in reverse order
|
||||
// pha
|
||||
// lda dsamp2
|
||||
// pha
|
||||
ldabs(registers.a, dsamp2 + 1);
|
||||
pha();
|
||||
ldabs(registers.a, dsamp2);
|
||||
pha();
|
||||
advance_cycles(14);
|
||||
|
||||
// lda #$10
|
||||
//rwtl ; wait till rd line is high
|
||||
// bit port [= $0001]
|
||||
// beq rwtl ; !ls!
|
||||
ldimm(registers.a, 0x10);
|
||||
advance_cycles(2);
|
||||
do {
|
||||
bit(io_input());
|
||||
if(advance_cycles(6)) {
|
||||
return;
|
||||
}
|
||||
} while(eq());
|
||||
|
||||
//rwth ;it's high...now wait till it's low
|
||||
// bit port
|
||||
// bne rwth ; caught the edge
|
||||
do {
|
||||
bit(io_input());
|
||||
if(advance_cycles(6)) {
|
||||
return;
|
||||
}
|
||||
} while(ne());
|
||||
|
||||
|
||||
// stx timr2l
|
||||
// sty timr2h
|
||||
timers_.write<2>(registers.x);
|
||||
timers_.write<3>(registers.y);
|
||||
advance_cycles(8);
|
||||
|
||||
|
||||
//; go! ...ta
|
||||
//
|
||||
// pla ;go! ...ta
|
||||
// sta timr3l
|
||||
// pla
|
||||
// sta timr3h ;go! ...tb
|
||||
pla();
|
||||
timers_.write<4>(registers.a);
|
||||
pla();
|
||||
timers_.write<5>(registers.a);
|
||||
advance_cycles(14);
|
||||
|
||||
|
||||
//; clear timer flags
|
||||
//
|
||||
// lda #$50 ; clr ta,tb
|
||||
// sta tedirq
|
||||
ldimm(registers.a, 0x50);
|
||||
interrupts_.set_status(registers.a);
|
||||
advance_cycles(6);
|
||||
|
||||
|
||||
//; um...check that edge again
|
||||
//
|
||||
//casdb1
|
||||
// lda port
|
||||
// cmp port
|
||||
// bne casdb1 ; something is going on here...
|
||||
// and #$10 ; a look at that edge again
|
||||
// bne badeg1 ; woa! got a bad edge trigger !ls!
|
||||
do {
|
||||
ldimm(registers.a, io_input());
|
||||
cmp(io_input());
|
||||
if(advance_cycles(9)) {
|
||||
return;
|
||||
}
|
||||
} while(ne());
|
||||
andimm(0x10);
|
||||
advance_cycles(5);
|
||||
} while(ne());
|
||||
|
||||
|
||||
//
|
||||
//; must have been a valid edge
|
||||
//;
|
||||
//; do stop key check here
|
||||
//
|
||||
// jsr balout
|
||||
|
||||
/* balout not checked */
|
||||
|
||||
|
||||
// lda #$10
|
||||
//wata ; wait for ta to timeout
|
||||
ldimm(registers.a, 0x10);
|
||||
advance_cycles(3);
|
||||
do {
|
||||
// bit port ; kuldge, kludge, kludge !!! <<><>>
|
||||
// bne rshort ; kuldge, kludge, kludge !!! <<><>>
|
||||
bit(io_input());
|
||||
if(ne()) {
|
||||
rshort();
|
||||
return;
|
||||
}
|
||||
|
||||
// bit tedirq
|
||||
// beq wata
|
||||
bit(interrupts_.status());
|
||||
|
||||
if(advance_cycles(12)) {
|
||||
return;
|
||||
}
|
||||
} while(eq());
|
||||
|
||||
|
||||
//
|
||||
//; now do the dipole sample #1
|
||||
//
|
||||
//casdb2
|
||||
do {
|
||||
// lda port
|
||||
// cmp port
|
||||
ldimm(registers.a, io_input());
|
||||
cmp(io_input());
|
||||
|
||||
if(advance_cycles(9)) {
|
||||
return;
|
||||
}
|
||||
// bne casdb2
|
||||
} while(ne());
|
||||
|
||||
// and #$10
|
||||
// bne rshort ; shorts anyone?
|
||||
andimm(0x10);
|
||||
advance_cycles(3);
|
||||
if(ne()) {
|
||||
rshort();
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
//; perhaps a long or a word?
|
||||
//
|
||||
// lda #$40
|
||||
//watb
|
||||
// bit tedirq
|
||||
// beq watb
|
||||
//
|
||||
//; wait for tb to timeout
|
||||
//; now do the dipole sample #2
|
||||
ldimm(registers.a, 0x40);
|
||||
advance_cycles(3);
|
||||
do {
|
||||
bit(interrupts_.status());
|
||||
if(advance_cycles(6)) {
|
||||
return;
|
||||
}
|
||||
} while(eq());
|
||||
|
||||
|
||||
//casdb3
|
||||
// lda port
|
||||
// cmp port
|
||||
// bne casdb3
|
||||
do {
|
||||
ldimm(registers.a, io_input());
|
||||
cmp(io_input());
|
||||
if(advance_cycles(9)) {
|
||||
return;
|
||||
}
|
||||
} while(ne());
|
||||
|
||||
// and #$10
|
||||
// bne rlong ; looks like a long from here !ls!
|
||||
andimm(0x10);
|
||||
advance_cycles(2);
|
||||
if(ne()) {
|
||||
rlong();
|
||||
return;
|
||||
}
|
||||
|
||||
// ; or could it be a word?
|
||||
// lda zcell
|
||||
// sta timr2l
|
||||
// lda zcell+1
|
||||
// sta timr2h
|
||||
ldabs(registers.a, zcell);
|
||||
timers_.write<2>(registers.a);
|
||||
ldabs(registers.a, zcell + 1);
|
||||
timers_.write<3>(registers.y);
|
||||
advance_cycles(16);
|
||||
|
||||
|
||||
// ; go! z-cell check
|
||||
// ; clear ta flag
|
||||
// lda #$10
|
||||
// sta tedirq ; verify +180 half of word dipole
|
||||
// lda #$10
|
||||
ldimm(registers.a, 0x10);
|
||||
interrupts_.set_status(registers.a);
|
||||
ldimm(registers.a, 0x10);
|
||||
advance_cycles(8);
|
||||
|
||||
//wata2
|
||||
// bit tedirq
|
||||
// beq wata2 ; check z-cell is low
|
||||
do {
|
||||
bit(interrupts_.status());
|
||||
if(advance_cycles(7)) {
|
||||
return;
|
||||
}
|
||||
} while(eq());
|
||||
|
||||
//casdb4
|
||||
// lda port
|
||||
// cmp port
|
||||
// bne casdb4
|
||||
do {
|
||||
ldimm(registers.a, io_input());
|
||||
cmp(io_input());
|
||||
if(advance_cycles(9)) {
|
||||
return;
|
||||
}
|
||||
} while(ne());
|
||||
|
||||
// and #$10
|
||||
// beq rderr1 ; !ls!
|
||||
// bit twordd ; got a word dipole
|
||||
// bmi dipok ; !bra
|
||||
andimm(0x10);
|
||||
advance_cycles(2);
|
||||
if(eq()) {
|
||||
rderr1();
|
||||
return;
|
||||
}
|
||||
bit(0x80);
|
||||
advance_cycles(2);
|
||||
dipok();
|
||||
}
|
||||
|
||||
uint8_t io_direction_ = 0x00, io_output_ = 0x00;
|
||||
uint8_t io_input() const {
|
||||
const uint8_t all_inputs =
|
||||
|
||||
@@ -550,6 +550,7 @@ public:
|
||||
|
||||
value = 0x0c; // i.e. NOP abs, to swallow the entire JSR
|
||||
} else if(address == 0xf90b) {
|
||||
// second JSR in TAPE / LAB_F8F4.
|
||||
auto registers = m6502_.registers();
|
||||
if(registers.x == 0xe) {
|
||||
Storage::Tape::Commodore::Parser parser(TargetPlatform::Vic20);
|
||||
|
||||
Reference in New Issue
Block a user