1
0
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:
Thomas Harte
2025-12-02 13:55:36 -05:00
parent 0a79abf0f7
commit 6aa55dd69f
2 changed files with 65 additions and 440 deletions

View File

@@ -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 =

View File

@@ -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);