mirror of
https://github.com/TomHarte/CLK.git
synced 2025-04-19 20:37:34 +00:00
Rips out my high-level ADB microcontroller protocol implementation.
Adds just enough that the main computer validates the ADB controller as present and talking.
This commit is contained in:
parent
5eddc92846
commit
b8c6d4b153
@ -54,14 +54,19 @@ void Executor::reset() {
|
||||
}
|
||||
|
||||
uint8_t Executor::read(uint16_t address) {
|
||||
// printf("%04x -> ", address);
|
||||
|
||||
address &= 0x1fff;
|
||||
if(address < 0x60) {
|
||||
// printf("%02x\n", memory_[address]);
|
||||
return memory_[address];
|
||||
}
|
||||
|
||||
port_handler_.run_ports_for(cycles_since_port_handler_.flush<Cycles>());
|
||||
switch(address) {
|
||||
default: return 0xff;
|
||||
default:
|
||||
// printf("??? [ff]\n");
|
||||
return 0xff;
|
||||
|
||||
// TODO: external IO ports.
|
||||
|
||||
@ -69,6 +74,7 @@ uint8_t Executor::read(uint16_t address) {
|
||||
case 0xd0: case 0xd1: case 0xd2: case 0xd3: case 0xd4: case 0xd5: case 0xd6: case 0xd7:
|
||||
case 0xd8: case 0xd9: case 0xda: case 0xdb: case 0xdc: case 0xdd: case 0xde: case 0xdf:
|
||||
// printf("TODO: Port R [r %04x]\n", address);
|
||||
// printf("R?? [ff]\n");
|
||||
return 0xff;
|
||||
|
||||
// Ports P0–P3.
|
||||
@ -77,6 +83,8 @@ uint8_t Executor::read(uint16_t address) {
|
||||
const int port = port_remap[(address - 0xe0) >> 1];
|
||||
const uint8_t input = port_handler_.get_port_input(port);
|
||||
|
||||
// printf("P%d %02x\n", port, (input &~ port_directions_[port]) | (port_outputs_[port] & port_directions_[port]));
|
||||
|
||||
// In the direction registers, a 0 indicates input, a 1 indicates output.
|
||||
return (input &~ port_directions_[port]) | (port_outputs_[port] & port_directions_[port]);
|
||||
}
|
||||
@ -84,16 +92,20 @@ uint8_t Executor::read(uint16_t address) {
|
||||
case 0xe1: case 0xe3:
|
||||
case 0xe5: case 0xe9:
|
||||
// printf("TODO: Ports P0–P3 direction [r %04x]\n", address);
|
||||
return 0xff;
|
||||
// printf("dir%d %02x\n", port_remap[(address - 0xe0) >> 1], port_directions_[port_remap[(address - 0xe0) >> 1]]);
|
||||
return port_directions_[port_remap[(address - 0xe0) >> 1]];
|
||||
|
||||
// Timers.
|
||||
case 0xf9: case 0xfa: case 0xfb: case 0xfc: case 0xfd: case 0xfe: case 0xff:
|
||||
// printf("TODO: Timers [r %04x]\n", address);
|
||||
// printf("T?? [ff]\n");
|
||||
return 0xff;
|
||||
}
|
||||
}
|
||||
|
||||
void Executor::write(uint16_t address, uint8_t value) {
|
||||
// printf("%04x <- %02x\n", address, value);
|
||||
|
||||
address &= 0x1fff;
|
||||
if(address < 0x60) {
|
||||
memory_[address] = value;
|
||||
@ -173,6 +185,8 @@ template<bool is_brk> inline void Executor::perform_interrupt() {
|
||||
}
|
||||
|
||||
template <Operation operation, AddressingMode addressing_mode> void Executor::perform() {
|
||||
// printf("%04x\t[a:%02x x:%02x y:%02x p:%02x s:%02x]\n", program_counter_ & 0x1fff, a_, x_, y_, flags(), s_);
|
||||
// printf("%04x")
|
||||
// printf("%04x\t%02x\t%d %d\t[a:%02x x:%02x y:%02x p:%02x s:%02x]\t(%s)\n", program_counter_ & 0x1fff, memory_[program_counter_ & 0x1fff], int(operation), int(addressing_mode), a_, x_, y_, flags(), s_, __PRETTY_FUNCTION__ );
|
||||
|
||||
// Post cycle cost; this emulation _does not provide accurate timing_.
|
||||
@ -440,6 +454,7 @@ template <Operation operation, AddressingMode addressing_mode> void Executor::pe
|
||||
return;
|
||||
|
||||
case Operation::JSR: {
|
||||
// Push one less than the actual return address.
|
||||
const auto return_address = program_counter_ - 1;
|
||||
push(uint8_t(return_address >> 8));
|
||||
push(uint8_t(return_address & 0xff));
|
||||
|
@ -18,30 +18,46 @@
|
||||
|
||||
using namespace Apple::IIgs::ADB;
|
||||
|
||||
GLU::GLU() : executor_(*this) {}
|
||||
namespace {
|
||||
|
||||
// MARK: - Configuration.
|
||||
// Flags affecting the CPU-visible status register.
|
||||
enum class CPUFlags: uint8_t {
|
||||
MouseDataFull = 0x80,
|
||||
MouseInterruptEnabled = 0x40,
|
||||
CommandDataIsValid = 0x20,
|
||||
CommandDataInterruptEnabled = 0x10,
|
||||
KeyboardDataFull = 0x08,
|
||||
KeyboardDataInterruptEnabled = 0x04,
|
||||
MouseXIsAvailable = 0x02,
|
||||
CommandRegisterFull = 0x01,
|
||||
};
|
||||
|
||||
// Flags affecting the microcontroller-visible register.
|
||||
enum class MicrocontrollerFlags: uint8_t {
|
||||
CommandRegisterFull = 0x40,
|
||||
};
|
||||
|
||||
void GLU::set_is_rom03(bool is_rom03) {
|
||||
is_rom03_ = is_rom03;
|
||||
}
|
||||
|
||||
GLU::GLU() : executor_(*this) {}
|
||||
|
||||
// MARK: - External interface.
|
||||
|
||||
uint8_t GLU::get_keyboard_data() {
|
||||
// The classic Apple II serial keyboard register:
|
||||
// b7: key strobe.
|
||||
// b6–b0: ASCII code.
|
||||
return 0x00;
|
||||
return registers_[0];
|
||||
}
|
||||
|
||||
void GLU::clear_key_strobe() {
|
||||
// Clears the key strobe of the classic Apple II serial keyboard register.
|
||||
registers_[0] &= 0x7f; // ???
|
||||
}
|
||||
|
||||
uint8_t GLU::get_any_key_down() {
|
||||
// The Apple IIe check-for-any-key-down bit.
|
||||
return 0x00;
|
||||
return registers_[5];
|
||||
}
|
||||
|
||||
uint8_t GLU::get_mouse_data() {
|
||||
@ -50,7 +66,7 @@ uint8_t GLU::get_mouse_data() {
|
||||
// b7: 1 = button is up; 0 = button is down.
|
||||
// b6: delta sign bit; 1 = negative.
|
||||
// b5–b0: mouse delta.
|
||||
return 0x80;
|
||||
return 0x80; // TODO. Should alternate between registers 2 and 3.
|
||||
}
|
||||
|
||||
uint8_t GLU::get_modifier_status() {
|
||||
@ -62,26 +78,18 @@ uint8_t GLU::get_modifier_status() {
|
||||
// b2: caps lock is pressed.
|
||||
// b1: control key.
|
||||
// b0: shift key.
|
||||
return 0x00;
|
||||
return registers_[6];
|
||||
}
|
||||
|
||||
uint8_t GLU::get_data() {
|
||||
printf("ADB get data\n");
|
||||
if(!pending_response_.empty()) {
|
||||
// A bit yucky, pull from the from the front.
|
||||
// This keeps my life simple for now, even though it's inefficient.
|
||||
const uint8_t next = pending_response_[0];
|
||||
pending_response_.erase(pending_response_.begin());
|
||||
return next;
|
||||
}
|
||||
|
||||
// b0–2: number of data bytes to be returned.
|
||||
// b3: 1 = a valid service request is pending; 0 = no request pending.
|
||||
// b4: 1 = control, command and delete keys have been pressed simultaneously; 0 = they haven't.
|
||||
// b5: 1 = control, command and reset have all been pressed together; 0 = they haven't.
|
||||
// b6: 1 = ADB controller encountered an error and reset itself; 0 = no error.
|
||||
// b7: 1 = ADB has received a response from the addressed ADB device; 0 = no respone.
|
||||
return 0x00;
|
||||
// status_ &= ~(CPUFlags::CommandDataIsValid | CPUFlags::CommandRegisterFull);
|
||||
return registers_[7];
|
||||
}
|
||||
|
||||
uint8_t GLU::get_status() {
|
||||
@ -93,138 +101,27 @@ uint8_t GLU::get_status() {
|
||||
// b2: 1 = keyboard data interrupt is enabled.
|
||||
// b1: 1 = mouse x-data is available; 0 = y.
|
||||
// b0: 1 = command register is full (set when command is written); 0 = empty (cleared when data is read).
|
||||
const uint8_t status =
|
||||
(pending_response_.empty() ? 0 : 0x20); // Data is valid if a response is pending.
|
||||
// printf("ADB get status : %02x\n", status);
|
||||
return status;
|
||||
return status_;
|
||||
}
|
||||
|
||||
void GLU::set_command(uint8_t command) {
|
||||
// Accumulate input until a full comamnd is received;
|
||||
// the state machine otherwise gets somewhat oversized.
|
||||
next_command_.push_back(command);
|
||||
|
||||
// Command dispatch; each entry should do whatever it needs
|
||||
// to do and then either: (i) break, if completed; or
|
||||
// (ii) return, if awaiting further input.
|
||||
#define RequireSize(n) if(next_command_.size() < (n)) return;
|
||||
switch(next_command_[0]) {
|
||||
case 0x01: abort(); break;
|
||||
case 0x02: reset_microcontroller(); break;
|
||||
case 0x03: flush_keyboard_buffer(); break;
|
||||
|
||||
case 0x04: // Set modes.
|
||||
RequireSize(2);
|
||||
set_modes(modes_ | next_command_[1]);
|
||||
break;
|
||||
|
||||
case 0x05: // Clear modes.
|
||||
RequireSize(2);
|
||||
set_modes(modes_ & ~next_command_[1]);
|
||||
break;
|
||||
|
||||
case 0x06: // Set configuration bytes
|
||||
RequireSize(4);
|
||||
set_configuration_bytes(&next_command_[1]);
|
||||
break;
|
||||
|
||||
case 0x07: // Sync.
|
||||
RequireSize(is_rom03_ ? 9 : 5);
|
||||
set_modes(next_command_[1]);
|
||||
set_configuration_bytes(&next_command_[2]);
|
||||
|
||||
// ROM03 seems to expect to send an extra four bytes here,
|
||||
// with values 0x20, 0x26, 0x2d, 0x2d.
|
||||
// TODO: what does the ROM03-paired ADB GLU do with the extra four bytes?
|
||||
break;
|
||||
|
||||
case 0x09: // Read microcontroller memory.
|
||||
RequireSize(3);
|
||||
pending_response_.push_back(read_microcontroller_address(uint16_t(next_command_[1] | (next_command_[2] << 8))));
|
||||
break;
|
||||
|
||||
case 0x0d: // Get version number.
|
||||
pending_response_.push_back(6); // Seems to be what ROM03 is looking for?
|
||||
break;
|
||||
|
||||
case 0x12: // ???
|
||||
RequireSize(3);
|
||||
break;
|
||||
|
||||
case 0x13: // ???
|
||||
RequireSize(3);
|
||||
break;
|
||||
|
||||
// Enable device SRQ.
|
||||
case 0x50: case 0x51: case 0x52: case 0x53: case 0x54: case 0x55: case 0x56: case 0x57:
|
||||
case 0x58: case 0x59: case 0x5a: case 0x5b: case 0x5c: case 0x5d: case 0x5e: case 0x5f:
|
||||
set_device_srq(device_srq_ | (1 << (next_command_[1] & 0xf)));
|
||||
break;
|
||||
|
||||
// Disable device SRQ.
|
||||
case 0x70: case 0x71: case 0x72: case 0x73: case 0x74: case 0x75: case 0x76: case 0x77:
|
||||
case 0x78: case 0x79: case 0x7a: case 0x7b: case 0x7c: case 0x7d: case 0x7e: case 0x7f:
|
||||
set_device_srq(device_srq_ & ~(1 << (next_command_[1] & 0xf)));
|
||||
break;
|
||||
|
||||
// Transmit two bytes.
|
||||
case 0xb3:
|
||||
RequireSize(3);
|
||||
break;
|
||||
|
||||
default:
|
||||
printf("TODO: ADB command %02x\n", next_command_[0]);
|
||||
break;
|
||||
}
|
||||
#undef RequireSize
|
||||
|
||||
printf("ADB executed: ");
|
||||
for(auto c : next_command_) printf("%02x ", c);
|
||||
printf("\n");
|
||||
|
||||
// Input was dealt with, so throw it away.
|
||||
next_command_.clear();
|
||||
registers_[1] = command;
|
||||
registers_[4] |= uint8_t(MicrocontrollerFlags::CommandRegisterFull);
|
||||
status_ |= uint8_t(CPUFlags::CommandRegisterFull);
|
||||
// printf("!!!%02x!!!\n", command);
|
||||
}
|
||||
|
||||
void GLU::set_status(uint8_t status) {
|
||||
printf("TODO: set ADB status %02x\n", status);
|
||||
}
|
||||
|
||||
// MARK: - Internal commands.
|
||||
|
||||
void GLU::set_modes(uint8_t modes) {
|
||||
modes_ = modes; // TODO: and this has what effect?
|
||||
}
|
||||
|
||||
void GLU::abort() {
|
||||
}
|
||||
|
||||
void GLU::reset_microcontroller() {
|
||||
}
|
||||
|
||||
void GLU::flush_keyboard_buffer() {
|
||||
}
|
||||
|
||||
void GLU::set_configuration_bytes(uint8_t *) {
|
||||
}
|
||||
|
||||
void GLU::set_device_srq(int mask) {
|
||||
// TODO: do I need to do any more than this here?
|
||||
device_srq_ = mask;
|
||||
}
|
||||
|
||||
uint8_t GLU::read_microcontroller_address(uint16_t address) {
|
||||
printf("Read from microcontroller %04x\n", address);
|
||||
if(address == 0xe8)
|
||||
return 0x40;
|
||||
return 0;
|
||||
}
|
||||
// MARK: - Setup and run.
|
||||
|
||||
void GLU::set_microcontroller_rom(const std::vector<uint8_t> &rom) {
|
||||
executor_.set_rom(rom);
|
||||
|
||||
// TEST invocation.
|
||||
InstructionSet::Disassembler<InstructionSet::M50740::Parser, 0x1fff, InstructionSet::M50740::Instruction, uint8_t, uint16_t> disassembler;
|
||||
/* InstructionSet::Disassembler<InstructionSet::M50740::Parser, 0x1fff, InstructionSet::M50740::Instruction, uint8_t, uint16_t> disassembler;
|
||||
disassembler.disassemble(rom.data(), 0x1000, uint16_t(rom.size()), 0x1000);
|
||||
|
||||
const auto instructions = disassembler.instructions();
|
||||
@ -240,7 +137,7 @@ void GLU::set_microcontroller_rom(const std::vector<uint8_t> &rom) {
|
||||
std::cout << address(pair.second.addressing_mode, &rom[pair.first - 0x1000], pair.first);
|
||||
|
||||
std::cout << std::endl;
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
void GLU::run_for(Cycles cycles) {
|
||||
@ -253,8 +150,13 @@ void GLU::set_port_output(int port, uint8_t value) {
|
||||
ports_[port] = value;
|
||||
switch(port) {
|
||||
case 0:
|
||||
// printf(" {R%d} ", register_address_);
|
||||
// printf("Set R%d: %02x\n", register_address_, value);
|
||||
registers_[register_address_] = value;
|
||||
switch(register_address_) {
|
||||
default: break;
|
||||
case 7: status_ |= uint8_t(CPUFlags::CommandDataIsValid); break;
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
// printf("Keyboard write: %02x???\n", value);
|
||||
@ -281,12 +183,12 @@ void GLU::set_port_output(int port, uint8_t value) {
|
||||
// Check for a valid bit length — 70 to 130 microseconds.
|
||||
// (Plus a little).
|
||||
if(seconds >= 0.000'56 && seconds <= 0.001'04) {
|
||||
printf("Attention\n");
|
||||
printf("!!! Attention\n");
|
||||
} else if(seconds >= 0.000'06 && seconds <= 0.000'14) {
|
||||
printf("bit: %d\n", (low_period_.as<int>() * 2) < total_period_.as<int>());
|
||||
printf("!!! bit: %d\n", (low_period_.as<int>() * 2) < total_period_.as<int>());
|
||||
// printf("tested: %0.2f\n", float(low_period_.as<int>()) / float(total_period_.as<int>()));
|
||||
} else {
|
||||
printf("Rejected %d microseconds\n", int(seconds * 1'000'000.0f));
|
||||
printf("!!! Rejected %d microseconds\n", int(seconds * 1'000'000.0f));
|
||||
}
|
||||
|
||||
total_period_ = low_period_ = Cycles(0);
|
||||
@ -302,7 +204,14 @@ void GLU::set_port_output(int port, uint8_t value) {
|
||||
uint8_t GLU::get_port_input(int port) {
|
||||
switch(port) {
|
||||
case 0:
|
||||
// printf("Get R%d\n", register_address_);
|
||||
// printf(" {R%d} ", register_address_);
|
||||
switch(register_address_) {
|
||||
default: break;
|
||||
case 1:
|
||||
registers_[4] &= ~uint8_t(MicrocontrollerFlags::CommandRegisterFull);
|
||||
status_ &= ~uint8_t(CPUFlags::CommandRegisterFull);
|
||||
break;
|
||||
}
|
||||
return registers_[register_address_];
|
||||
case 1:
|
||||
// printf("IIe keyboard read\n");
|
||||
|
@ -23,7 +23,7 @@ class GLU: public InstructionSet::M50740::PortHandler {
|
||||
|
||||
// Behaviour varies slightly between the controller shipped with ROM01 machines
|
||||
// and that shipped with ROM03 machines; use this to set the desired behaviour.
|
||||
void set_is_rom03(bool);
|
||||
// void set_is_rom03(bool);
|
||||
|
||||
uint8_t get_keyboard_data();
|
||||
uint8_t get_mouse_data();
|
||||
@ -42,24 +42,6 @@ class GLU: public InstructionSet::M50740::PortHandler {
|
||||
void run_for(Cycles cycles);
|
||||
|
||||
private:
|
||||
bool is_rom03_ = false;
|
||||
std::vector<uint8_t> next_command_;
|
||||
std::vector<uint8_t> pending_response_;
|
||||
|
||||
void abort();
|
||||
void reset_microcontroller();
|
||||
void flush_keyboard_buffer();
|
||||
|
||||
void set_modes(uint8_t modes);
|
||||
uint8_t modes_ = 0;
|
||||
|
||||
void set_device_srq(int mask);
|
||||
int device_srq_ = 0;
|
||||
|
||||
void set_configuration_bytes(uint8_t *);
|
||||
|
||||
uint8_t read_microcontroller_address(uint16_t);
|
||||
|
||||
InstructionSet::M50740::Executor executor_;
|
||||
|
||||
void run_ports_for(Cycles) override;
|
||||
@ -69,6 +51,9 @@ class GLU: public InstructionSet::M50740::PortHandler {
|
||||
uint8_t registers_[16];
|
||||
uint8_t register_address_;
|
||||
uint8_t ports_[4];
|
||||
uint8_t register_latch_ = 0xff;
|
||||
|
||||
uint8_t status_ = 0x00;
|
||||
|
||||
// TODO: this should be per peripheral. But I'm putting it here for now as an exploratory step.
|
||||
bool adb_level_ = true;
|
||||
|
@ -88,6 +88,8 @@ class ConcreteMachine:
|
||||
break;
|
||||
}
|
||||
rom_descriptions.push_back(video_->rom_description(Video::Video::CharacterROM::EnhancedIIe));
|
||||
|
||||
// TODO: pick a different ADB ROM for earlier machine revisions?
|
||||
rom_descriptions.emplace_back(machine_name, "the Apple IIgs ADB microcontroller ROM", "341s0632-2", 4*1024, 0xe1c11fb0);
|
||||
|
||||
const auto roms = rom_fetcher(rom_descriptions);
|
||||
@ -159,9 +161,6 @@ class ConcreteMachine:
|
||||
memory_.set_storage(ram_, rom_);
|
||||
video_->set_internal_ram(&ram_[ram_.size() - 128*1024]);
|
||||
|
||||
// Select appropriate ADB behaviour.
|
||||
adb_glu_->set_is_rom03(target.model == Target::Model::ROM03);
|
||||
|
||||
// Attach drives to the IWM.
|
||||
iwm_->set_drive(0, &drives35_[0]);
|
||||
iwm_->set_drive(1, &drives35_[1]);
|
||||
|
Loading…
x
Reference in New Issue
Block a user