mirror of
https://github.com/TomHarte/CLK.git
synced 2024-12-25 18:30:21 +00:00
Fills in enough of the SCC to allow completion of the Macintosh side of that relationship.
This commit is contained in:
parent
ec5701459c
commit
ccd2cb44a2
@ -8,34 +8,73 @@
|
||||
|
||||
#include "z8530.hpp"
|
||||
|
||||
#include "../../Outputs/Log.hpp"
|
||||
|
||||
using namespace Zilog::SCC;
|
||||
|
||||
void z8530::reset() {
|
||||
// TODO.
|
||||
}
|
||||
|
||||
bool z8530::get_interrupt_line() {
|
||||
// TODO.
|
||||
return false;
|
||||
}
|
||||
|
||||
std::uint8_t z8530::read(int address) {
|
||||
return channels_[address & 1].read(address & 2);
|
||||
const auto result = channels_[address & 1].read(address & 2, pointer_);
|
||||
pointer_ = 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
void z8530::write(int address, std::uint8_t value) {
|
||||
channels_[address & 1].write(address & 2, pointer_, value);
|
||||
if(address & 2) {
|
||||
// Write data register for channel.
|
||||
} else {
|
||||
// Write control register for channel.
|
||||
|
||||
/*
|
||||
Control register 0, which includes the pointer bits,
|
||||
is decoded here because there's only one set of pointer bits.
|
||||
*/
|
||||
if(!(address & 2)) {
|
||||
// Most registers are per channel, but a couple are shared; sever
|
||||
// them here.
|
||||
switch(pointer_) {
|
||||
default:
|
||||
channels_[address & 1].write(address & 2, pointer_, value);
|
||||
break;
|
||||
|
||||
case 2: // Interrupt vector register; shared between both channels.
|
||||
interrupt_vector_ = value;
|
||||
LOG("[SCC] Interrupt vector set to " << PADHEX(2) << int(value));
|
||||
break;
|
||||
|
||||
case 9: // Master interrupt and reset register; also shared between both channels.
|
||||
LOG("[SCC] TODO: master interrupt and reset register " << PADHEX(2) << int(value));
|
||||
break;
|
||||
}
|
||||
|
||||
// The pointer number resets to 0 after every access, but if it is zero
|
||||
// then crib at least the next set of pointer bits (which, similarly, are shared
|
||||
// between the two channels).
|
||||
if(pointer_) {
|
||||
pointer_ = 0;
|
||||
} else {
|
||||
// The lowest three bits are the lowest three bits of the pointer.
|
||||
pointer_ = value & 7;
|
||||
|
||||
// If the command part of the byte is a 'point high', also set the
|
||||
// top bit of the pointer.
|
||||
if(((value >> 3)&7) == 1) {
|
||||
pointer_ |= 8;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t z8530::Channel::read(bool data) {
|
||||
uint8_t z8530::Channel::read(bool data, uint8_t pointer) {
|
||||
// If this is a data read, just return it.
|
||||
if(data) return data_;
|
||||
if(data) {
|
||||
return data_;
|
||||
} else {
|
||||
LOG("[SCC] Unrecognised control read from register " << int(pointer));
|
||||
}
|
||||
|
||||
// Otherwise, this is a control read...
|
||||
return 0x00;
|
||||
@ -45,5 +84,100 @@ void z8530::Channel::write(bool data, uint8_t pointer, uint8_t value) {
|
||||
if(data) {
|
||||
data_ = value;
|
||||
return;
|
||||
} else {
|
||||
switch(pointer) {
|
||||
default:
|
||||
LOG("[SCC] Unrecognised control write: " << PADHEX(2) << int(value) << " to register " << int(pointer));
|
||||
break;
|
||||
|
||||
case 0x0: // Write register 0 — CRC reset and other functions.
|
||||
// Decode CRC reset instructions.
|
||||
switch(value >> 6) {
|
||||
default: /* Do nothing. */ break;
|
||||
case 1:
|
||||
LOG("[SCC] TODO: reset Rx CRC checker.");
|
||||
break;
|
||||
case 2:
|
||||
LOG("[SCC] TODO: reset Tx CRC checker.");
|
||||
break;
|
||||
case 3:
|
||||
LOG("[SCC] TODO: reset Tx underrun/EOM latch.");
|
||||
break;
|
||||
}
|
||||
|
||||
// Decode command code.
|
||||
switch((value >> 3)&7) {
|
||||
default: /* Do nothing. */ break;
|
||||
case 2:
|
||||
LOG("[SCC] TODO: reset ext/status interrupts.");
|
||||
break;
|
||||
case 3:
|
||||
LOG("[SCC] TODO: send abort (SDLC).");
|
||||
break;
|
||||
case 4:
|
||||
LOG("[SCC] TODO: enable interrupt on next Rx character.");
|
||||
break;
|
||||
case 5:
|
||||
LOG("[SCC] TODO: reset Tx interrupt pending.");
|
||||
break;
|
||||
case 6:
|
||||
LOG("[SCC] TODO: reset error.");
|
||||
break;
|
||||
case 7:
|
||||
LOG("[SCC] TODO: reset highest IUS.");
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x1: // Write register 1 — Transmit/Receive Interrupt and Data Transfer Mode Definition.
|
||||
transfer_interrupt_mask_ = value;
|
||||
break;
|
||||
|
||||
case 0x4: // Write register 4 — Transmit/Receive Miscellaneous Parameters and Modes.
|
||||
// Bits 0 and 1 select parity mode.
|
||||
if(!(value&1)) {
|
||||
parity_ = Parity::Off;
|
||||
} else {
|
||||
parity_ = (value&2) ? Parity::Even : Parity::Odd;
|
||||
}
|
||||
|
||||
// Bits 2 and 3 select stop bits.
|
||||
switch((value >> 2)&3) {
|
||||
default: stop_bits_ = StopBits::Synchronous; break;
|
||||
case 1: stop_bits_ = StopBits::OneBit; break;
|
||||
case 2: stop_bits_ = StopBits::OneAndAHalfBits; break;
|
||||
case 3: stop_bits_ = StopBits::TwoBits; break;
|
||||
}
|
||||
|
||||
// Bits 4 and 5 pick a sync mode.
|
||||
switch((value >> 4)&3) {
|
||||
default: sync_mode_ = Sync::Monosync; break;
|
||||
case 1: sync_mode_ = Sync::Bisync; break;
|
||||
case 2: sync_mode_ = Sync::SDLC; break;
|
||||
case 3: sync_mode_ = Sync::External; break;
|
||||
}
|
||||
|
||||
// Bits 6 and 7 select a clock rate multiplier, unless synchronous
|
||||
// mode is enabled (and this is ignored if sync mode is external).
|
||||
if(stop_bits_ == StopBits::Synchronous) {
|
||||
clock_rate_multiplier_ = 1;
|
||||
} else {
|
||||
switch((value >> 6)&3) {
|
||||
default: clock_rate_multiplier_ = 1; break;
|
||||
case 1: clock_rate_multiplier_ = 16; break;
|
||||
case 2: clock_rate_multiplier_ = 32; break;
|
||||
case 3: clock_rate_multiplier_ = 64; break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 0xf: // Write register 15 — External/Status Interrupt Control.
|
||||
interrupt_mask_ = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void z8530::set_dcd(int port, bool level) {
|
||||
// TODO.
|
||||
}
|
||||
|
@ -19,9 +19,9 @@ namespace SCC {
|
||||
*/
|
||||
class z8530 {
|
||||
public:
|
||||
void reset();
|
||||
|
||||
/*
|
||||
**Interface for emulated machine.**
|
||||
|
||||
Notes on addressing below:
|
||||
|
||||
There's no inherent ordering of the two 'address' lines,
|
||||
@ -30,20 +30,46 @@ class z8530 {
|
||||
A/B = A0
|
||||
C/D = A1
|
||||
*/
|
||||
|
||||
std::uint8_t read(int address);
|
||||
void write(int address, std::uint8_t value);
|
||||
void reset();
|
||||
bool get_interrupt_line();
|
||||
|
||||
/*
|
||||
**Interface for serial port input.**
|
||||
*/
|
||||
void set_dcd(int port, bool level);
|
||||
|
||||
private:
|
||||
class Channel {
|
||||
public:
|
||||
uint8_t read(bool data);
|
||||
uint8_t read(bool data, uint8_t pointer);
|
||||
void write(bool data, uint8_t pointer, uint8_t value);
|
||||
|
||||
private:
|
||||
uint8_t data_ = 0xff;
|
||||
|
||||
enum class Parity {
|
||||
Even, Odd, Off
|
||||
} parity_ = Parity::Off;
|
||||
|
||||
enum class StopBits {
|
||||
Synchronous, OneBit, OneAndAHalfBits, TwoBits
|
||||
} stop_bits_ = StopBits::Synchronous;
|
||||
|
||||
enum class Sync {
|
||||
Monosync, Bisync, SDLC, External
|
||||
} sync_mode_ = Sync::Monosync;
|
||||
|
||||
int clock_rate_multiplier_ = 1;
|
||||
|
||||
uint8_t transfer_interrupt_mask_ = 0; // i.e. Write Register 0x1.
|
||||
uint8_t interrupt_mask_ = 0; // i.e. Write Register 0xf.
|
||||
|
||||
bool dcd_ = false;
|
||||
} channels_[2];
|
||||
uint8_t pointer_ = 0;
|
||||
uint8_t interrupt_vector_ = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -52,35 +52,33 @@ class QuadratureMouse: public Mouse {
|
||||
*/
|
||||
|
||||
/*!
|
||||
Removes a single step from the current accumulated mouse movement;
|
||||
the step removed will henceforth be queriable via get_step.
|
||||
Applies a single step from the current accumulated mouse movement, which
|
||||
might involve the mouse moving right, or left, or not at all.
|
||||
*/
|
||||
void prepare_step() {
|
||||
for(int axis = 0; axis < 2; ++axis) {
|
||||
const int axis_value = axes_[axis];
|
||||
if(!axis_value) {
|
||||
step_[axis] = 0;
|
||||
if(!axis_value) continue;
|
||||
|
||||
primaries_[axis] ^= 1;
|
||||
secondaries_[axis] = primaries_[axis];
|
||||
if(axis_value > 0) {
|
||||
-- axes_[axis];
|
||||
} else {
|
||||
if(axis_value > 0) {
|
||||
-- axes_[axis];
|
||||
step_[axis] = 1;
|
||||
} else {
|
||||
++ axes_[axis];
|
||||
step_[axis] = -1;
|
||||
}
|
||||
++ axes_[axis];
|
||||
secondaries_[axis] ^= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Gets and removes a single step from the current accumulated mouse
|
||||
movement for @c axis — axis 0 is x, axis 1 is y.
|
||||
|
||||
@returns 0 if no movement is outstanding, -1 if there is outstanding
|
||||
negative movement, +1 if there is outstanding positive movement.
|
||||
@returns the two quadrature channels — bit 0 is the 'primary' channel
|
||||
(i.e. the one that can be monitored to observe velocity) and
|
||||
bit 1 is the 'secondary' (i.e. that which can be queried to
|
||||
observe direction).
|
||||
*/
|
||||
int get_step(int axis) {
|
||||
return step_[axis];
|
||||
int get_channel(int axis) {
|
||||
return primaries_[axis] | (secondaries_[axis] << 1);
|
||||
}
|
||||
|
||||
/*!
|
||||
@ -94,8 +92,9 @@ class QuadratureMouse: public Mouse {
|
||||
int number_of_buttons_ = 0;
|
||||
std::atomic<int> button_flags_;
|
||||
std::atomic<int> axes_[2];
|
||||
int step_[2];
|
||||
|
||||
int primaries_[2] = {0, 0};
|
||||
int secondaries_[2] = {0, 0};
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -142,14 +142,24 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
|
||||
// The keyboard also has a clock, albeit a very slow one.
|
||||
// Its clock and data lines are connected to the VIA.
|
||||
keyboard_clock_ += cycle.length;
|
||||
auto keyboard_ticks = keyboard_clock_.divide(HalfCycles(CLOCK_RATE / 100000));
|
||||
const auto keyboard_ticks = keyboard_clock_.divide(HalfCycles(CLOCK_RATE / 100000));
|
||||
if(keyboard_ticks > HalfCycles(0)) {
|
||||
keyboard_.run_for(keyboard_ticks);
|
||||
via_.set_control_line_input(MOS::MOS6522::Port::B, MOS::MOS6522::Line::Two, keyboard_.get_data());
|
||||
via_.set_control_line_input(MOS::MOS6522::Port::B, MOS::MOS6522::Line::One, keyboard_.get_clock());
|
||||
}
|
||||
|
||||
// TODO: SCC is a divide-by-two.
|
||||
// Feed mouse inputs within at most 100 cycles of each other.
|
||||
time_since_mouse_update_ += cycle.length;
|
||||
const auto mouse_ticks = keyboard_clock_.divide(HalfCycles(200));
|
||||
if(mouse_ticks > HalfCycles(0)) {
|
||||
mouse_.prepare_step();
|
||||
scc_.set_dcd(0, mouse_.get_channel(0) & 1);
|
||||
scc_.set_dcd(1, mouse_.get_channel(1) & 1);
|
||||
}
|
||||
|
||||
// TODO: SCC should be clocked at a divide-by-two, if and when it actually has
|
||||
// anything connected.
|
||||
|
||||
// Consider updating the real-time clock.
|
||||
real_time_clock_ += cycle.length;
|
||||
@ -166,7 +176,11 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
|
||||
via_.set_control_line_input(MOS::MOS6522::Port::A, MOS::MOS6522::Line::One, !video_.vsync());
|
||||
|
||||
// Update interrupt input. TODO: move this into a VIA/etc delegate callback?
|
||||
mc68000_.set_interrupt_level( (via_.get_interrupt_line() ? 1 : 0) );
|
||||
mc68000_.set_interrupt_level(
|
||||
(via_.get_interrupt_line() ? 1 : 0) |
|
||||
(scc_.get_interrupt_line() ? 2 : 0)
|
||||
/* TODO: to emulate a programmer's switch: have it set bit 2 when pressed. */
|
||||
);
|
||||
|
||||
// A null cycle leaves nothing else to do.
|
||||
if(cycle.operation) {
|
||||
@ -432,8 +446,8 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
|
||||
case Port::B:
|
||||
return
|
||||
(mouse_.get_button_mask() & 1) ? 0x00 : 0x08 |
|
||||
(mouse_.get_step(1) > 0) ? 0x00 : 0x10 |
|
||||
(mouse_.get_step(0) > 0) ? 0x00 : 0x20 |
|
||||
((mouse_.get_channel(1) & 2) << 3) |
|
||||
((mouse_.get_channel(0) & 2) << 4) |
|
||||
(clock_.get_data() ? 0x02 : 0x00) |
|
||||
(video_.is_outputting() ? 0x00 : 0x40);
|
||||
}
|
||||
@ -492,6 +506,7 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
|
||||
HalfCycles video_clock_;
|
||||
// HalfCycles time_since_video_update_;
|
||||
HalfCycles time_since_iwm_update_;
|
||||
HalfCycles time_since_mouse_update_;
|
||||
|
||||
bool ROM_is_overlay_ = true;
|
||||
int phase_ = 1;
|
||||
|
Loading…
Reference in New Issue
Block a user