mirror of
https://github.com/TomHarte/CLK.git
synced 2024-12-25 03:32:01 +00:00
It'll be helpful to be able to isolate the tape interface when I'm faking a fast load. So factored out.
This commit is contained in:
parent
0efe4b312c
commit
43a7d1b7ae
@ -25,8 +25,7 @@ Machine::Machine() :
|
|||||||
_displayOutputPosition(0),
|
_displayOutputPosition(0),
|
||||||
_audioOutputPosition(0),
|
_audioOutputPosition(0),
|
||||||
_audioOutputPositionError(0),
|
_audioOutputPositionError(0),
|
||||||
_currentOutputLine(0),
|
_currentOutputLine(0)
|
||||||
_tape({.is_running = false, .dataRegister = 0})
|
|
||||||
{
|
{
|
||||||
memset(_keyStates, 0, sizeof(_keyStates));
|
memset(_keyStates, 0, sizeof(_keyStates));
|
||||||
memset(_palette, 0xf, sizeof(_palette));
|
memset(_palette, 0xf, sizeof(_palette));
|
||||||
@ -112,9 +111,8 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
|
|||||||
case 0x4:
|
case 0x4:
|
||||||
if(isReadOperation(operation))
|
if(isReadOperation(operation))
|
||||||
{
|
{
|
||||||
*value = (uint8_t)(_tape.dataRegister >> 2);
|
*value = _tape.get_data_register();
|
||||||
_interruptStatus &= ~Interrupt::TransmitDataEmpty;
|
_tape.clear_interrupts(Interrupt::TransmitDataEmpty);
|
||||||
evaluate_interrupts();
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -188,8 +186,8 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
|
|||||||
_speaker.set_is_enabled(new_speaker_is_enabled);
|
_speaker.set_is_enabled(new_speaker_is_enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: tape mode, tape motor, caps lock LED
|
_tape.set_is_running(((*value)&0x40) ? true : false);
|
||||||
_tape.is_running = ((*value)&0x40) ? true : false;
|
// TODO: tape mode, caps lock LED
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@ -292,89 +290,14 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(_tape.is_running && _tape.media != nullptr)
|
_tape.run_for_cycle();
|
||||||
{
|
|
||||||
_tape.time_into_pulse += (unsigned int)_tape.pulseStepper->step();
|
|
||||||
if(_tape.time_into_pulse == _tape.currentPulse.length.length)
|
|
||||||
{
|
|
||||||
get_next_tape_pulse();
|
|
||||||
|
|
||||||
_tape.crossings[0] = _tape.crossings[1];
|
|
||||||
_tape.crossings[1] = _tape.crossings[2];
|
|
||||||
_tape.crossings[2] = _tape.crossings[3];
|
|
||||||
|
|
||||||
_tape.crossings[3] = Tape::Unrecognised;
|
|
||||||
if(_tape.currentPulse.type != Storage::Tape::Pulse::Zero)
|
|
||||||
{
|
|
||||||
float pulse_length = (float)_tape.currentPulse.length.length / (float)_tape.currentPulse.length.clock_rate;
|
|
||||||
if(pulse_length > 0.4 / 2400.0 && pulse_length < 0.6 / 2400.0) _tape.crossings[3] = Tape::Short;
|
|
||||||
if(pulse_length > 0.4 / 1200.0 && pulse_length < 0.6 / 1200.0) _tape.crossings[3] = Tape::Long;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(_tape.crossings[0] == Tape::Long && _tape.crossings[1] == Tape::Long)
|
|
||||||
{
|
|
||||||
push_tape_bit(0);
|
|
||||||
_tape.crossings[1] = Tape::Unrecognised;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if(_tape.crossings[0] == Tape::Short && _tape.crossings[1] == Tape::Short && _tape.crossings[2] == Tape::Short && _tape.crossings[3] == Tape::Short)
|
|
||||||
{
|
|
||||||
push_tape_bit(1);
|
|
||||||
_tape.crossings[3] = Tape::Unrecognised;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return cycles;
|
return cycles;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Machine::set_tape(std::shared_ptr<Storage::Tape> tape)
|
void Machine::set_tape(std::shared_ptr<Storage::Tape> tape)
|
||||||
{
|
{
|
||||||
_tape.media = tape;
|
_tape.set_tape(tape);
|
||||||
get_next_tape_pulse();
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void Machine::get_next_tape_pulse()
|
|
||||||
{
|
|
||||||
_tape.time_into_pulse = 0;
|
|
||||||
_tape.currentPulse = _tape.media->get_next_pulse();
|
|
||||||
if(_tape.pulseStepper == nullptr || _tape.currentPulse.length.clock_rate != _tape.pulseStepper->get_output_rate())
|
|
||||||
{
|
|
||||||
_tape.pulseStepper = std::shared_ptr<SignalProcessing::Stepper>(new SignalProcessing::Stepper(_tape.currentPulse.length.clock_rate, 2000000));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void Machine::push_tape_bit(uint16_t bit)
|
|
||||||
{
|
|
||||||
_tape.dataRegister = (uint16_t)((_tape.dataRegister >> 1) | (bit << 10));
|
|
||||||
|
|
||||||
if(_tape.bits_since_start)
|
|
||||||
{
|
|
||||||
_tape.bits_since_start--;
|
|
||||||
|
|
||||||
if(_tape.bits_since_start == 7)
|
|
||||||
{
|
|
||||||
_interruptStatus &= ~Interrupt::TransmitDataEmpty;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if((_tape.dataRegister&0x3) == 0x1)
|
|
||||||
{
|
|
||||||
_interruptStatus |= Interrupt::TransmitDataEmpty;
|
|
||||||
_tape.bits_since_start = 9;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(_tape.dataRegister == 0x3ff)
|
|
||||||
_interruptStatus |= Interrupt::HighToneDetect;
|
|
||||||
else
|
|
||||||
_interruptStatus &= ~Interrupt::HighToneDetect;
|
|
||||||
}
|
|
||||||
// printf(".");
|
|
||||||
|
|
||||||
evaluate_interrupts();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Machine::set_rom(ROMSlot slot, size_t length, const uint8_t *data)
|
void Machine::set_rom(ROMSlot slot, size_t length, const uint8_t *data)
|
||||||
@ -395,6 +318,12 @@ inline void Machine::signal_interrupt(Electron::Interrupt interrupt)
|
|||||||
evaluate_interrupts();
|
evaluate_interrupts();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Machine::tape_did_change_interrupt_status(Tape *tape)
|
||||||
|
{
|
||||||
|
_interruptStatus = (_interruptStatus & ~(Interrupt::TransmitDataEmpty | Interrupt::ReceiveDataFull | Interrupt::HighToneDetect)) | _tape.get_interrupt_status();
|
||||||
|
evaluate_interrupts();
|
||||||
|
}
|
||||||
|
|
||||||
inline void Machine::evaluate_interrupts()
|
inline void Machine::evaluate_interrupts()
|
||||||
{
|
{
|
||||||
if(_interruptStatus & _interruptControl)
|
if(_interruptStatus & _interruptControl)
|
||||||
@ -664,3 +593,104 @@ Machine::Speaker::~Speaker()
|
|||||||
{
|
{
|
||||||
// fclose(rawStream);
|
// fclose(rawStream);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Tape
|
||||||
|
*/
|
||||||
|
|
||||||
|
Tape::Tape() : _is_running(false), _data_register(0), _delegate(nullptr) {}
|
||||||
|
|
||||||
|
void Tape::set_tape(std::shared_ptr<Storage::Tape> tape)
|
||||||
|
{
|
||||||
|
_tape = tape;
|
||||||
|
get_next_tape_pulse();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void Tape::get_next_tape_pulse()
|
||||||
|
{
|
||||||
|
_time_into_pulse = 0;
|
||||||
|
_current_pulse = _tape->get_next_pulse();
|
||||||
|
if(_pulse_stepper == nullptr || _current_pulse.length.clock_rate != _pulse_stepper->get_output_rate())
|
||||||
|
{
|
||||||
|
_pulse_stepper = std::shared_ptr<SignalProcessing::Stepper>(new SignalProcessing::Stepper(_current_pulse.length.clock_rate, 2000000));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void Tape::push_tape_bit(uint16_t bit)
|
||||||
|
{
|
||||||
|
_data_register = (uint16_t)((_data_register >> 1) | (bit << 10));
|
||||||
|
|
||||||
|
uint8_t old_interrupt_status = _interrupt_status;
|
||||||
|
|
||||||
|
if(_bits_since_start)
|
||||||
|
{
|
||||||
|
_bits_since_start--;
|
||||||
|
|
||||||
|
if(_bits_since_start == 7)
|
||||||
|
{
|
||||||
|
_interrupt_status &= ~Interrupt::TransmitDataEmpty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if((_data_register&0x3) == 0x1)
|
||||||
|
{
|
||||||
|
_interrupt_status |= Interrupt::TransmitDataEmpty;
|
||||||
|
_bits_since_start = 9;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(_data_register == 0x3ff)
|
||||||
|
_interrupt_status |= Interrupt::HighToneDetect;
|
||||||
|
else
|
||||||
|
_interrupt_status &= ~Interrupt::HighToneDetect;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(old_interrupt_status != _interrupt_status && _delegate) _delegate->tape_did_change_interrupt_status(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void Tape::clear_interrupts(uint8_t interrupts)
|
||||||
|
{
|
||||||
|
if(_interrupt_status & interrupts)
|
||||||
|
{
|
||||||
|
_interrupt_status &= ~interrupts;
|
||||||
|
if(_delegate) _delegate->tape_did_change_interrupt_status(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void Tape::run_for_cycle()
|
||||||
|
{
|
||||||
|
if(_is_running && _tape != nullptr)
|
||||||
|
{
|
||||||
|
_time_into_pulse += (unsigned int)_pulse_stepper->step();
|
||||||
|
if(_time_into_pulse == _current_pulse.length.length)
|
||||||
|
{
|
||||||
|
get_next_tape_pulse();
|
||||||
|
|
||||||
|
_crossings[0] = _crossings[1];
|
||||||
|
_crossings[1] = _crossings[2];
|
||||||
|
_crossings[2] = _crossings[3];
|
||||||
|
|
||||||
|
_crossings[3] = Tape::Unrecognised;
|
||||||
|
if(_current_pulse.type != Storage::Tape::Pulse::Zero)
|
||||||
|
{
|
||||||
|
float pulse_length = (float)_current_pulse.length.length / (float)_current_pulse.length.clock_rate;
|
||||||
|
if(pulse_length > 0.4 / 2400.0 && pulse_length < 0.6 / 2400.0) _crossings[3] = Tape::Short;
|
||||||
|
if(pulse_length > 0.4 / 1200.0 && pulse_length < 0.6 / 1200.0) _crossings[3] = Tape::Long;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(_crossings[0] == Tape::Long && _crossings[1] == Tape::Long)
|
||||||
|
{
|
||||||
|
push_tape_bit(0);
|
||||||
|
_crossings[1] = Tape::Unrecognised;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if(_crossings[0] == Tape::Short && _crossings[1] == Tape::Short && _crossings[2] == Tape::Short && _crossings[3] == Tape::Short)
|
||||||
|
{
|
||||||
|
push_tape_bit(1);
|
||||||
|
_crossings[3] = Tape::Unrecognised;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -57,13 +57,57 @@ enum Key: uint16_t {
|
|||||||
KeyBreak = 0xffff
|
KeyBreak = 0xffff
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class Tape {
|
||||||
|
public:
|
||||||
|
Tape();
|
||||||
|
|
||||||
|
void set_tape(std::shared_ptr<Storage::Tape> tape);
|
||||||
|
|
||||||
|
inline uint8_t get_data_register() { return (uint8_t)(_data_register >> 2); }
|
||||||
|
|
||||||
|
inline uint8_t get_interrupt_status() { return _interrupt_status; }
|
||||||
|
inline void clear_interrupts(uint8_t interrupts);
|
||||||
|
|
||||||
|
class Delegate {
|
||||||
|
public:
|
||||||
|
virtual void tape_did_change_interrupt_status(Tape *tape) = 0;
|
||||||
|
};
|
||||||
|
inline void set_delegate(Delegate *delegate) { _delegate = delegate; }
|
||||||
|
|
||||||
|
inline void run_for_cycle();
|
||||||
|
|
||||||
|
void set_is_running(bool is_running) { _is_running = is_running; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
inline void push_tape_bit(uint16_t bit);
|
||||||
|
inline void get_next_tape_pulse();
|
||||||
|
|
||||||
|
std::shared_ptr<Storage::Tape> _tape;
|
||||||
|
|
||||||
|
Storage::Tape::Pulse _current_pulse;
|
||||||
|
std::shared_ptr<SignalProcessing::Stepper> _pulse_stepper;
|
||||||
|
uint32_t _time_into_pulse;
|
||||||
|
|
||||||
|
bool _is_running;
|
||||||
|
|
||||||
|
int _bits_since_start;
|
||||||
|
uint16_t _data_register;
|
||||||
|
|
||||||
|
uint8_t _interrupt_status;
|
||||||
|
Delegate *_delegate;
|
||||||
|
|
||||||
|
enum {
|
||||||
|
Long, Short, Unrecognised
|
||||||
|
} _crossings[4];
|
||||||
|
};
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@abstract Represents an Acorn Electron.
|
@abstract Represents an Acorn Electron.
|
||||||
|
|
||||||
@discussion An instance of Electron::Machine represents the current state of an
|
@discussion An instance of Electron::Machine represents the current state of an
|
||||||
Acorn Electron.
|
Acorn Electron.
|
||||||
*/
|
*/
|
||||||
class Machine: public CPU6502::Processor<Machine> {
|
class Machine: public CPU6502::Processor<Machine>, Tape::Delegate {
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
@ -81,6 +125,8 @@ class Machine: public CPU6502::Processor<Machine> {
|
|||||||
Outputs::Speaker *get_speaker() { return &_speaker; }
|
Outputs::Speaker *get_speaker() { return &_speaker; }
|
||||||
const char *get_signal_decoder();
|
const char *get_signal_decoder();
|
||||||
|
|
||||||
|
virtual void tape_did_change_interrupt_status(Tape *tape);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
inline void update_display();
|
inline void update_display();
|
||||||
@ -88,9 +134,6 @@ class Machine: public CPU6502::Processor<Machine> {
|
|||||||
inline void signal_interrupt(Interrupt interrupt);
|
inline void signal_interrupt(Interrupt interrupt);
|
||||||
inline void evaluate_interrupts();
|
inline void evaluate_interrupts();
|
||||||
|
|
||||||
inline void get_next_tape_pulse();
|
|
||||||
inline void push_tape_bit(uint16_t bit);
|
|
||||||
|
|
||||||
// Things that directly constitute the memory map.
|
// Things that directly constitute the memory map.
|
||||||
uint8_t _roms[16][16384];
|
uint8_t _roms[16][16384];
|
||||||
uint8_t _os[16384], _ram[32768];
|
uint8_t _os[16384], _ram[32768];
|
||||||
@ -114,20 +157,7 @@ class Machine: public CPU6502::Processor<Machine> {
|
|||||||
uint8_t *_currentLine, *_writePointer;
|
uint8_t *_currentLine, *_writePointer;
|
||||||
|
|
||||||
// Tape.
|
// Tape.
|
||||||
struct Tape {
|
Tape _tape;
|
||||||
std::shared_ptr<Storage::Tape> media;
|
|
||||||
Storage::Tape::Pulse currentPulse;
|
|
||||||
std::shared_ptr<SignalProcessing::Stepper> pulseStepper;
|
|
||||||
uint32_t time_into_pulse;
|
|
||||||
bool is_running;
|
|
||||||
int bits_since_start;
|
|
||||||
|
|
||||||
uint16_t dataRegister;
|
|
||||||
|
|
||||||
enum {
|
|
||||||
Long, Short, Unrecognised
|
|
||||||
} crossings[4];
|
|
||||||
} _tape;
|
|
||||||
|
|
||||||
// Outputs.
|
// Outputs.
|
||||||
Outputs::CRT *_crt;
|
Outputs::CRT *_crt;
|
||||||
|
Loading…
Reference in New Issue
Block a user