mirror of
https://github.com/TomHarte/CLK.git
synced 2024-08-16 16:28:59 +00:00
I still think it's appropriate to do this in only a single place, given that using it is optional.
172 lines
4.5 KiB
C++
172 lines
4.5 KiB
C++
//
|
|
// Bus.hpp
|
|
// Clock Signal
|
|
//
|
|
// Created by Thomas Harte on 10/02/2021.
|
|
// Copyright © 2021 Thomas Harte. All rights reserved.
|
|
//
|
|
|
|
#ifndef Bus_hpp
|
|
#define Bus_hpp
|
|
|
|
#include "../../../ClockReceiver/ClockReceiver.hpp"
|
|
|
|
#include <bitset>
|
|
#include <cstddef>
|
|
#include <ostream>
|
|
#include <vector>
|
|
|
|
namespace Apple {
|
|
namespace ADB {
|
|
|
|
struct Command {
|
|
enum class Type {
|
|
Reset,
|
|
Flush,
|
|
Reserved,
|
|
/// The host wishes the device to store register contents.
|
|
Listen,
|
|
/// The host wishes the device to broadcast register contents.
|
|
Talk
|
|
};
|
|
static constexpr uint8_t AllDevices = 0xff;
|
|
static constexpr uint8_t NoRegister = 0xff;
|
|
|
|
Type type = Type::Reserved;
|
|
uint8_t device = AllDevices;
|
|
uint8_t reg = NoRegister;
|
|
|
|
Command() {}
|
|
Command(Type type) : type(type) {}
|
|
Command(Type type, uint8_t device) : type(type), device(device) {}
|
|
Command(Type type, uint8_t device, uint8_t reg) : type(type), device(device), reg(reg) {}
|
|
};
|
|
|
|
inline std::ostream &operator <<(std::ostream &stream, Command::Type type) {
|
|
switch(type) {
|
|
case Command::Type::Reset: stream << "reset"; break;
|
|
case Command::Type::Flush: stream << "flush"; break;
|
|
case Command::Type::Listen: stream << "listen"; break;
|
|
case Command::Type::Talk: stream << "talk"; break;
|
|
default: stream << "reserved"; break;
|
|
}
|
|
return stream;
|
|
}
|
|
|
|
inline std::ostream &operator <<(std::ostream &stream, Command command) {
|
|
stream << "Command {";
|
|
if(command.device != 0xff) stream << "device " << int(command.device) << ", ";
|
|
if(command.reg != 0xff) stream << "register " << int(command.reg) << ", ";
|
|
stream << command.type;
|
|
stream << "}";
|
|
return stream;
|
|
}
|
|
|
|
/*!
|
|
@returns The @c Command encoded in @c code.
|
|
*/
|
|
inline Command decode_command(uint8_t code) {
|
|
switch(code & 0x0f) {
|
|
default: return Command();
|
|
|
|
case 0: return Command(Command::Type::Reset);
|
|
case 1: return Command(Command::Type::Flush, code >> 4);
|
|
|
|
case 8: case 9: case 10: case 11:
|
|
return Command(Command::Type::Listen, code >> 4, code & 3);
|
|
|
|
case 12: case 13: case 14: case 15:
|
|
return Command(Command::Type::Talk, code >> 4, code & 3);
|
|
}
|
|
}
|
|
|
|
/*!
|
|
The ADB bus models the data line of the ADB bus; it allows multiple devices to
|
|
post their current data level, or read the current level, and also offers a tokenised
|
|
version of all activity on the bus.
|
|
|
|
In implementation terms, two types of device are envisaged:
|
|
|
|
* proactive devices, which use @c add_device() and then merely @c set_device_output
|
|
and @c get_state() as required, according to their own tracking of time; and
|
|
|
|
* reactive devices, which use @c add_device(Device*) and then merely react to
|
|
@c adb_bus_did_observe_event and @c advance_state in order to
|
|
update @c set_device_output.
|
|
*/
|
|
class Bus {
|
|
public:
|
|
Bus(HalfCycles clock_speed);
|
|
|
|
/*!
|
|
Advances time; ADB is a clocked serial signal.
|
|
*/
|
|
void run_for(HalfCycles);
|
|
|
|
/*!
|
|
Adds a device to the bus, returning the index it should use
|
|
to refer to itself in subsequent calls to set_device_output.
|
|
*/
|
|
size_t add_device();
|
|
|
|
/*!
|
|
Sets the current data line output for @c device.
|
|
*/
|
|
void set_device_output(size_t device_id, bool output);
|
|
|
|
/*!
|
|
@returns The current state of the ADB data line.
|
|
*/
|
|
bool get_state() const;
|
|
|
|
enum class Event {
|
|
Reset,
|
|
Attention,
|
|
Byte,
|
|
ServiceRequest,
|
|
|
|
Unrecognised
|
|
};
|
|
|
|
struct Device {
|
|
/// Reports to an observer that @c event was observed in the activity
|
|
/// observed on this bus. If this was a byte event, that byte's value is given as @c value.
|
|
virtual void adb_bus_did_observe_event(Event event, uint8_t value = 0xff) = 0;
|
|
|
|
/// Requests that the device update itself @c microseconds and, if necessary, post a
|
|
/// new value ot @c set_device_output. This will be called only when the bus needs
|
|
/// to reevaluate its current level. It cannot reliably be used to track the timing between
|
|
/// observed events.
|
|
virtual void advance_state(double microseconds, bool current_level) = 0;
|
|
};
|
|
/*!
|
|
Adds a device.
|
|
*/
|
|
size_t add_device(Device *);
|
|
|
|
private:
|
|
HalfCycles time_in_state_;
|
|
mutable HalfCycles time_since_get_state_;
|
|
|
|
double half_cycles_to_microseconds_ = 1.0;
|
|
std::vector<Device *> devices_;
|
|
unsigned int shift_register_ = 0;
|
|
bool data_level_ = true;
|
|
|
|
// ADB addressing supports at most 16 devices but that doesn't include
|
|
// the controller. So assume a maximum of 17 connected devices.
|
|
std::bitset<17> bus_state_{0xffffffff};
|
|
size_t next_device_id_ = 0;
|
|
|
|
inline void shift(unsigned int);
|
|
enum class Phase {
|
|
PacketCapture,
|
|
AttentionCapture
|
|
} phase_ = Phase::AttentionCapture;
|
|
};
|
|
|
|
}
|
|
}
|
|
|
|
#endif /* Bus_hpp */
|