1
0
mirror of https://github.com/TomHarte/CLK.git synced 2026-04-21 02:17:08 +00:00

Take another stab at read_dipole.

This commit is contained in:
Thomas Harte
2025-01-29 22:07:17 -05:00
parent d749c305ed
commit 8ba57dec03
3 changed files with 347 additions and 95 deletions
+326 -89
View File
@@ -30,6 +30,8 @@
#include "../SerialBus.hpp"
#include "../1540/C1540.hpp"
#include "../../../Processors/6502Esque/Implementation/LazyFlags.hpp"
#include <algorithm>
#include <cmath>
#include <vector>
@@ -249,11 +251,7 @@ public:
const auto length = video_.cycle_length(operation == CPU::MOS6502::BusOperation::Ready);
// Update other subsystems.
timers_subcycles_ += length;
const auto timers_cycles = timers_subcycles_.divide(video_.timer_cycle_length());
timers_.tick(timers_cycles.as<int>());
tape_player_->run_for(length);
advance_timers_and_tape(length);
video_.run_for(length);
if(c1541_) {
@@ -284,13 +282,7 @@ public:
if(!address) {
*value = io_direction_;
} else {
const uint8_t all_inputs =
(tape_player_->input() ? 0x00 : 0x10) |
(serial_port_.level(Serial::Line::Data) ? 0x80 : 0x00) |
(serial_port_.level(Serial::Line::Clock) ? 0x40 : 0x00);
*value =
(io_direction_ & io_output_) |
(~io_direction_ & all_inputs);
*value = io_input();
}
} else {
if(!address) {
@@ -306,13 +298,18 @@ public:
serial_port_.set_output(Serial::Line::Attention, Serial::LineLevel(~output & 0x04));
}
} else if(address < 0xfd00 || address >= 0xff40) {
if(is_read(operation)) {
*value = map_.read(address);
if(use_fast_tape_hack_ && operation == CPU::MOS6502Esque::BusOperation::ReadOpcode && address == 0xe5fd) {
read_dipole();
*value = 0x60;
} else {
map_.write(address) = *value;
if(is_read(operation)) {
*value = map_.read(address);
} else {
map_.write(address) = *value;
}
}
if(use_fast_tape_hack_ && operation == CPU::MOS6502Esque::BusOperation::ReadOpcode) {
/* if(use_fast_tape_hack_ && operation == CPU::MOS6502Esque::BusOperation::ReadOpcode) {
if(address == 0xe9cc) {
// Skip the `jsr rdblok` that opens `fah` (i.e. find any header), performing
// its function as a high-level emulation.
@@ -341,7 +338,7 @@ public:
*value = 0x0c; // NOP abs.
}
}
}*/
} else if(address < 0xff00) {
// Miscellaneous hardware. All TODO.
if(is_read(operation)) {
@@ -572,7 +569,8 @@ public:
}
private:
CPU::MOS6502::Processor<CPU::MOS6502::Personality::P6502, ConcreteMachine, true> m6502_;
using Processor = CPU::MOS6502::Processor<CPU::MOS6502::Personality::P6502, ConcreteMachine, true>;
Processor m6502_;
Outputs::Speaker::Speaker *get_speaker() override {
return &speaker_;
@@ -721,103 +719,342 @@ private:
tape_player_->set_motor_control(play_button_ && (~output & 0x08));
set_use_fast_tape();
}
void advance_timers_and_tape(const Cycles length) {
timers_subcycles_ += length;
const auto timers_cycles = timers_subcycles_.divide(video_.timer_cycle_length());
timers_.tick(timers_cycles.as<int>());
tape_player_->run_for(length);
}
void read_dipole() {
// if(pulse_num_ >= 15225) {
// printf("");
// }
// Provides an HLE implementation of the routine beginning at address
// 0xe5fd in the ROM, i.e. rddipl (read dipole) as that's the one that
// spins awaiting changes in tape input.
using Register = CPU::MOS6502::Register;
using Pulse = Storage::Tape::Pulse;
using Flag = CPU::MOS6502::Flag;
auto s = uint8_t(m6502_.value_of(Register::StackPointer));
auto flags = uint8_t(m6502_.value_of(Register::Flags));
const unsigned int timer_clock_rate =
static_cast<unsigned int>(get_clock_rate()) / video_.timer_cycle_length().as<unsigned int>();
//
// Get registers now and ensure they'll be written back at function exit.
//
CPU::MOS6502Esque::LazyFlags flags(uint8_t(m6502_.value_of(Register::Flags)));
uint8_t x, y, a;
uint8_t s = uint8_t(m6502_.value_of(Register::StackPointer));
struct ScopeGuard {
ScopeGuard(std::function<void(void)> at_exit) : at_exit_(at_exit) {}
~ScopeGuard() { at_exit_(); }
private:
std::function<void(void)> at_exit_;
} registers([&] {
m6502_.set_value_of(Register::Flags, flags.get());
m6502_.set_value_of(Register::A, a);
m6502_.set_value_of(Register::X, x);
m6502_.set_value_of(Register::Y, y);
m6502_.set_value_of(Register::StackPointer, s);
});
const auto wait_for = [&](Pulse::Type type) -> bool {
while(!tape_player_->is_at_end() && tape_player_->current_pulse().type != type) {
tape_player_->complete_pulse();
//
// Time advancement.
//
const auto advance_cycles = [&](int cycles) {
advance_timers_and_tape(video_.cycle_length(false) * cycles);
};
//
// 6502 pseudo-ops.
//
const auto ldabs = [&] (uint8_t &target, const uint16_t address) {
flags.set_nz(target = map_.read(address));
};
const auto ldimm = [&] (uint8_t &target, const uint8_t value) {
flags.set_nz(target = value);
};
const auto pha = [&] () {
map_.write(0x100 + s) = a;
--s;
};
const auto pla = [&] () {
++s;
a = map_.read(0x100 + s);
};
const auto bit = [&] (const uint8_t value) {
flags.zero_result = a & value;
flags.negative_result = value;
flags.overflow = value & CPU::MOS6502Esque::Flag::Overflow;
};
const auto cmp = [&] (const uint8_t value) {
const uint16_t temp16 = a - value;
flags.set_nz(uint8_t(temp16));
flags.carry = ((~temp16) >> 8)&1;
};
const auto andimm = [&] (const uint8_t value) {
a &= value;
flags.set_nz(a);
};
const auto ne = [&]() -> bool {
return flags.zero_result;
};
const auto eq = [&]() -> bool {
return !flags.zero_result;
};
//
// Common branch points.
//
const auto dipok = [&] {
// clc ; everything's fine
// rts
flags.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
flags.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(x, dsamp1);
ldabs(y, dsamp1 + 1);
//badeg1
do {
// lda dsamp2+1 ; put 2nd samp value on stack in reverse order
// pha
// lda dsamp2
// pha
ldabs(a, dsamp2 + 1);
pha();
ldabs(a, dsamp2);
pha();
// lda #$10
//rwtl ; wait till rd line is high
// bit port [= $0001]
// beq rwtl ; !ls!
ldimm(a, 0x10);
do {
bit(io_input());
advance_cycles(7);
} while(eq());
//rwth ;it's high...now wait till it's low
// bit port
// bne rwth ; caught the edge
do {
bit(io_input());
advance_cycles(7);
} while(ne());
// stx timr2l
// sty timr2h
timers_.write<0xff02>(x);
timers_.write<0xff03>(y);
//; go! ...ta
//
// pla ;go! ...ta
// sta timr3l
// pla
// sta timr3h ;go! ...tb
timers_.tick(4);
pla();
timers_.write<0xff04>(a);
pla();
timers_.write<0xff05>(a);
//; clear timer flags
//
// lda #$50 ; clr ta,tb
// sta tedirq
ldimm(a, 0x50);
interrupts_.set_status(a);
//; 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(a, io_input());
cmp(io_input());
advance_cycles(11);
} while(ne());
andimm(0x10);
} 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(a, 0x10);
do {
advance_cycles(13);
// bit port ; kuldge, kludge, kludge !!! <<><>>
// bne rshort ; kuldge, kludge, kludge !!! <<><>>
bit(io_input());
if(ne()) {
rshort();
return;
}
return tape_player_->current_pulse().type == type;
};
const auto read16 = [&](uint16_t address) {
return uint16_t( map_.read(address) | (map_.read(address + 1) << 8) );
};
const auto pulse_timer_cycles = [&] {
const auto length = tape_player_->current_pulse().length;
return (length.length * timer_clock_rate) / length.clock_rate;
};
// bit tedirq
// beq wata
bit(interrupts_.status());
} while(eq());
//
// Grab timing constants, and put one into memory as it had been on the stack.
//; now do the dipole sample #1
//
const auto dsamp1 = read16(0x7b8);
const auto dsamp2 = read16(0x7ba);
const auto zcell = read16(0x7bc);
//casdb2
do {
advance_cycles(11);
map_.write(0x100 + s) = uint8_t(dsamp2 >> 8);
--s;
map_.write(0x100 + s) = uint8_t (dsamp2);
// lda port
// cmp port
ldimm(a, io_input());
cmp(io_input());
//
// Busy wait for high input from tape (Elaboration: exiting with error if the tape ends).
//
if(!wait_for(Pulse::Low)) {
flags |= Flag::Carry;
m6502_.set_value_of(Register::Flags, flags);
// bne casdb2
} while(ne());
// and #$10
// bne rshort ; shorts anyone?
andimm(0x10);
if(ne()) {
rshort();
return;
}
//
// Busy wait for low input from tape (Elaboration: exiting with error if the tape ends).
//; perhaps a long or a word?
//
if(!wait_for(Pulse::High)) {
flags |= Flag::Carry;
m6502_.set_value_of(Register::Flags, flags);
// lda #$40
//watb
// bit tedirq
// beq watb
//
//; wait for tb to timeout
//; now do the dipole sample #2
ldimm(a, 0x40);
do {
advance_cycles(7);
bit(interrupts_.status());
} while(eq());
//casdb3
// lda port
// cmp port
// bne casdb3
do {
advance_cycles(11);
ldimm(a, io_input());
cmp(io_input());
} while(ne());
// and #$10
// bne rlong ; looks like a long from here !ls!
andimm(0x10);
if(ne()) {
rlong();
return;
}
//
// If tape input goes high before dsamp1 cycles, report a short.
//
const auto pulse_length = pulse_timer_cycles();
if(pulse_length <= dsamp1) {
flags |= Flag::Overflow;
flags &= ~Flag::Carry;
m6502_.set_value_of(Register::Flags, flags);
// ; or could it be a word?
// lda zcell
// sta timr2l
// lda zcell+1
// sta timr2h
ldabs(a, zcell);
timers_.write<0xff02>(a);
ldabs(a, zcell + 1);
timers_.write<0xff03>(y);
// ; go! z-cell check
// ; clear ta flag
// lda #$10
// sta tedirq ; verify +180 half of word dipole
// lda #$10
ldimm(a, 0x10);
interrupts_.set_status(a);
ldimm(a, 0x10);
//wata2
// bit tedirq
// beq wata2 ; check z-cell is low
do {
advance_cycles(7);
bit(interrupts_.status());
} while(eq());
//casdb4
// lda port
// cmp port
// bne casdb4
do {
advance_cycles(7);
ldimm(a, io_input());
cmp(io_input());
} while(ne());
// and #$10
// beq rderr1 ; !ls!
// bit twordd ; got a word dipole
// bmi dipok ; !bra
andimm(0x10);
if(eq()) {
rderr1();
return;
}
//
// Wait until end of dsamp1 period, then wait on until input is high, then wait until end of dsamp2 period.
// If input is then low, it's a long.
//
tape_player_->complete_pulse();
const auto second_pulse_length = pulse_timer_cycles();
if(second_pulse_length + pulse_length <= dsamp2) {
// Goto rlong, i.e. ...
flags &= ~(Flag::Carry | Flag::Sign);
m6502_.set_value_of(Register::Flags, flags);
return;
}
//
// Now wait an additional zcell clocks. If input is still low, that's an error. Otherwise it's a word.
//
if(second_pulse_length + pulse_length > dsamp2 + zcell) {
flags |= Flag::Carry;
} else {
flags |= Flag::Sign;
flags &= ~(Flag::Carry | Flag::Overflow);
}
m6502_.set_value_of(Register::Flags, flags);
bit(0x80);
dipok();
}
uint8_t io_direction_ = 0x00, io_output_ = 0x00;
uint8_t io_input() const {
const uint8_t all_inputs =
(tape_player_->input() ? 0x00 : 0x10) |
(serial_port_.level(Serial::Line::Data) ? 0x80 : 0x00) |
(serial_port_.level(Serial::Line::Clock) ? 0x40 : 0x00);
return
(io_direction_ & io_output_) |
(~io_direction_ & all_inputs);
}
std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override {
return joysticks_;
@@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 54;
objectVersion = 70;
objects = {
/* Begin PBXBuildFile section */
@@ -2468,6 +2468,10 @@
4BFF1D3C2235C3C100838EA1 /* EmuTOSTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = EmuTOSTests.mm; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFileSystemSynchronizedRootGroup section */
4B24B3ED2D49BA4400691F28 /* 65x02 */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = 65x02; sourceTree = "<group>"; };
/* End PBXFileSystemSynchronizedRootGroup section */
/* Begin PBXFrameworksBuildPhase section */
4B055A671FAE763F0060FFFF /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
@@ -5289,6 +5293,7 @@
4BEDA3B225B25563000C2DBD /* InstructionSets */ = {
isa = PBXGroup;
children = (
4B24B3ED2D49BA4400691F28 /* 65x02 */,
4BEDA42925B3C26B000C2DBD /* AccessType.hpp */,
4BEDA45425B5ECAB000C2DBD /* CachingExecutor.hpp */,
4BE8EB5425C0E9D40040BC40 /* Disassembler.hpp */,
@@ -5454,6 +5459,9 @@
);
dependencies = (
);
fileSystemSynchronizedGroups = (
4B24B3ED2D49BA4400691F28 /* 65x02 */,
);
name = "Clock Signal";
productName = "Clock Signal";
productReference = 4BB73E9E1B587A5100552FC2 /* Clock Signal.app */;
@@ -5472,6 +5480,9 @@
dependencies = (
4BB73EB41B587A5100552FC2 /* PBXTargetDependency */,
);
fileSystemSynchronizedGroups = (
4B24B3ED2D49BA4400691F28 /* 65x02 */,
);
name = "Clock SignalTests";
productName = "Clock SignalTests";
productReference = 4BB73EB21B587A5100552FC2 /* Clock SignalTests.xctest */;
@@ -32,27 +32,27 @@ struct LazyFlags {
uint8_t inverse_interrupt = 0;
/// Sets N and Z flags per the 8-bit value @c value.
void set_nz(uint8_t value) {
void set_nz(const uint8_t value) {
zero_result = negative_result = value;
}
/// Sets N and Z flags per the 8- or 16-bit value @c value; @c shift should be 0 to indicate an 8-bit value or 8 to indicate a 16-bit value.
void set_nz(uint16_t value, int shift) {
void set_nz(const uint16_t value, const int shift) {
negative_result = uint8_t(value >> shift);
zero_result = uint8_t(value | (value >> shift));
}
/// Sets the Z flag per the 8- or 16-bit value @c value; @c shift should be 0 to indicate an 8-bit value or 8 to indicate a 16-bit value.
void set_z(uint16_t value, int shift) {
void set_z(const uint16_t value, const int shift) {
zero_result = uint8_t(value | (value >> shift));
}
/// Sets the N flag per the 8- or 16-bit value @c value; @c shift should be 0 to indicate an 8-bit value or 8 to indicate a 16-bit value.
void set_n(uint16_t value, int shift) {
void set_n(const uint16_t value, const int shift) {
negative_result = uint8_t(value >> shift);
}
void set(uint8_t flags) {
void set(const uint8_t flags) {
carry = flags & Flag::Carry;
negative_result = flags & Flag::Sign;
zero_result = (~flags) & Flag::Zero;
@@ -72,6 +72,10 @@ struct LazyFlags {
decimal &= Flag::Decimal;
overflow &= Flag::Overflow;
}
LazyFlags(const uint8_t value) {
set(value);
}
};
}