diff --git a/Machines/Acorn/Archimedes/Archimedes.cpp b/Machines/Acorn/Archimedes/Archimedes.cpp index 3cb706e42..348094fc7 100644 --- a/Machines/Acorn/Archimedes/Archimedes.cpp +++ b/Machines/Acorn/Archimedes/Archimedes.cpp @@ -8,6 +8,12 @@ #include "Archimedes.hpp" +#include "HalfDuplexSerial.hpp" +#include "InputOutputController.hpp" +#include "Keyboard.hpp" +#include "MemoryController.hpp" +#include "Sound.hpp" + #include "../../AudioProducer.hpp" #include "../../KeyboardMachine.hpp" #include "../../MediaTarget.hpp" @@ -28,1091 +34,10 @@ namespace { Log::Logger logger; -enum class Zone { - LogicallyMappedRAM, - PhysicallyMappedRAM, - IOControllers, - LowROM, - HighROM, - VideoController, - DMAAndMEMC, - AddressTranslator, -}; -constexpr std::array zones(bool is_read) { - std::array zones{}; - for(size_t c = 0; c < zones.size(); c++) { - const auto address = c << 21; - if(address < 0x200'0000) { - zones[c] = Zone::LogicallyMappedRAM; - } else if(address < 0x300'0000) { - zones[c] = Zone::PhysicallyMappedRAM; - } else if(address < 0x340'0000) { - zones[c] = Zone::IOControllers; - } else if(address < 0x360'0000) { - zones[c] = is_read ? Zone::LowROM : Zone::VideoController; - } else if(address < 0x380'0000) { - zones[c] = is_read ? Zone::LowROM : Zone::DMAAndMEMC; - } else { - zones[c] = is_read ? Zone::HighROM : Zone::AddressTranslator; - } - } - return zones; -} - -template struct BitMask { - static_assert(start >= end); - static constexpr uint32_t value = ((1 << (start + 1)) - 1) - ((1 << end) - 1); -}; -static_assert(BitMask<0, 0>::value == 1); -static_assert(BitMask<1, 1>::value == 2); -static_assert(BitMask<15, 15>::value == 32768); -static_assert(BitMask<15, 0>::value == 0xffff); -static_assert(BitMask<15, 14>::value == 49152); - } namespace Archimedes { -struct CMOSRAM: public I2C::Peripheral { - -}; - -/// Models a half-duplex serial link between two parties, framing bytes with one start bit and two stop bits. -struct HalfDuplexSerial { - static constexpr uint16_t ShiftMask = 0b1111'1110'0000'0000; - - /// Enqueues @c value for output. - void output(int party, uint8_t value) { - parties_[party].output_count = 11; - parties_[party].input = 0x7ff; - parties_[party].output = uint16_t((value << 1) | ShiftMask); - } - - /// @returns The last observed input. - uint8_t input(int party) const { - return uint8_t(parties_[party].input >> 1); - } - - static constexpr uint8_t Receive = 1 << 0; - static constexpr uint8_t Transmit = 1 << 1; - - /// @returns A bitmask of events that occurred during the last shift. - uint8_t events(int party) { - const auto result = parties_[party].events; - parties_[party].events = 0; - return result; - } - - bool is_outputting(int party) const { - return parties_[party].output_count != 11; - } - - /// Updates the shifters on both sides of the serial link. - void shift() { - const uint16_t next = parties_[0].output & parties_[1].output & 1; - - for(int c = 0; c < 2; c++) { - if(parties_[c].output_count) { - --parties_[c].output_count; - if(!parties_[c].output_count) { - parties_[c].events |= Transmit; - parties_[c].input_count = -1; - } - parties_[c].output = (parties_[c].output >> 1) | ShiftMask; - } else { - // Check for a start bit. - if(parties_[c].input_count == -1 && !next) { - parties_[c].input_count = 0; - } - - // Shift in if currently observing. - if(parties_[c].input_count >= 0 && parties_[c].input_count < 11) { - parties_[c].input = uint16_t((parties_[c].input >> 1) | (next << 10)); - - ++parties_[c].input_count; - if(parties_[c].input_count == 11) { - parties_[c].events |= Receive; - parties_[c].input_count = -1; - } - } - } - } - } - -private: - struct Party { - int output_count = 0; - int input_count = -1; - uint16_t output = 0xffff; - uint16_t input = 0; - uint8_t events = 0; - } parties_[2]; -}; - -static constexpr int IOCParty = 0; -static constexpr int KeyboardParty = 1; - -// Resource for the keyboard protocol: https://github.com/tmk/tmk_keyboard/wiki/ACORN-ARCHIMEDES-Keyboard -struct Keyboard { - Keyboard(HalfDuplexSerial &serial) : serial_(serial) {} - - void update() { - if(serial_.events(KeyboardParty) & HalfDuplexSerial::Receive) { - const uint8_t input = serial_.input(KeyboardParty); - switch(input) { - case HRST: - // TODO: - case RAK1: - case RAK2: - serial_.output(KeyboardParty, input); - break; - - case RQID: - serial_.output(KeyboardParty, 0x81); // TODO: what keyboard type? - break; - - default: - printf("Keyboard declines to respond to %02x\n", input); - break; - } - } - } - -private: - HalfDuplexSerial &serial_; - - static constexpr uint8_t HRST = 0b1111'1111; // Keyboard reset. - static constexpr uint8_t RAK1 = 0b1111'1110; // Reset response #1. - static constexpr uint8_t RAK2 = 0b1111'1101; // Reset response #2. - - static constexpr uint8_t RQID = 0b0010'0000; // Request for keyboard ID. - static constexpr uint8_t RQMP = 0b0010'0010; // Request for mouse data. - - static constexpr uint8_t BACK = 0b0011'1111; // Acknowledge for first keyboard data byte pair. - static constexpr uint8_t NACK = 0b0011'0000; // Acknowledge for last keyboard data byte pair, selects scan/mouse mode. - static constexpr uint8_t SACK = 0b0011'0001; // Last data byte acknowledge. - static constexpr uint8_t MACK = 0b0011'0010; // Last data byte acknowledge. - static constexpr uint8_t SMAK = 0b0011'0011; // Last data byte acknowledge. - static constexpr uint8_t PRST = 0b0010'0001; // Does nothing. -}; - -struct Audio { - void set_next_end(uint32_t value) { - next_.end = value; - } - - void set_next_start(uint32_t value) { - next_.start = value; - next_buffer_valid_ = true; - } - - bool interrupt() const { - return !next_buffer_valid_; - } - - void swap() { - current_.start = next_.start; - std::swap(current_.end, next_.end); - next_buffer_valid_ = false; - halted_ = false; - } - - bool tick() { - if(halted_) { - return false; - } - - current_.start += 16; - if(current_.start == current_.end) { - if(next_buffer_valid_) { - swap(); - return true; - } else { - halted_ = true; - return false; - } - } - return false; - } - -private: - bool next_buffer_valid_ = false; - bool halted_ = true; // This is a bit of a guess. - - struct Buffer { - uint32_t start = 0, end = 0; - }; - Buffer current_, next_; -}; -struct Video { - void write(uint32_t value) { - const auto target = (value >> 24) & 0xfc; - - switch(target) { - case 0x00: case 0x04: case 0x08: case 0x0c: - case 0x10: case 0x14: case 0x18: case 0x1c: - case 0x20: case 0x24: case 0x28: case 0x2c: - case 0x30: case 0x34: case 0x38: case 0x3c: - logger.error().append("TODO: Video palette logical colour %d to %03x", (target >> 2), value & 0x1fff); - break; - - case 0x40: - logger.error().append("TODO: Video border colour to %03x", value & 0x1fff); - break; - - case 0x44: case 0x48: case 0x4c: - logger.error().append("TODO: Cursor colour %d to %03x", (target - 0x44) >> 2, value & 0x1fff); - break; - - case 0x60: case 0x64: case 0x68: case 0x6c: - case 0x70: case 0x74: case 0x78: case 0x7c: - logger.error().append("TODO: Stereo image register %d to %03x", (target - 0x60) >> 2, value & 0x7); - break; - - case 0x80: - logger.error().append("TODO: Video horizontal period: %d", (value >> 14) & 0x3ff); - break; - case 0x84: - logger.error().append("TODO: Video horizontal sync width: %d", (value >> 14) & 0x3ff); - break; - case 0x88: - logger.error().append("TODO: Video horizontal border start: %d", (value >> 14) & 0x3ff); - break; - case 0x8c: - logger.error().append("TODO: Video horizontal display start: %d", (value >> 14) & 0x3ff); - break; - case 0x90: - logger.error().append("TODO: Video horizontal display end: %d", (value >> 14) & 0x3ff); - break; - case 0x94: - logger.error().append("TODO: Video horizontal border end: %d", (value >> 14) & 0x3ff); - break; - case 0x98: - logger.error().append("TODO: Video horizontal cursor end: %d", (value >> 14) & 0x3ff); - break; - case 0x9c: - logger.error().append("TODO: Video horizontal interlace: %d", (value >> 14) & 0x3ff); - break; - - case 0xa0: - logger.error().append("TODO: Video vertical period: %d", (value >> 14) & 0x3ff); - break; - case 0xa4: - logger.error().append("TODO: Video vertical sync width: %d", (value >> 14) & 0x3ff); - break; - case 0xa8: - logger.error().append("TODO: Video vertical border start: %d", (value >> 14) & 0x3ff); - break; - case 0xac: - logger.error().append("TODO: Video vertical display start: %d", (value >> 14) & 0x3ff); - break; - case 0xb0: - logger.error().append("TODO: Video vertical display end: %d", (value >> 14) & 0x3ff); - break; - case 0xb4: - logger.error().append("TODO: Video vertical border end: %d", (value >> 14) & 0x3ff); - break; - case 0xb8: - logger.error().append("TODO: Video vertical cursor start: %d", (value >> 14) & 0x3ff); - break; - case 0xbc: - logger.error().append("TODO: Video vertical cursor end: %d", (value >> 14) & 0x3ff); - break; - - case 0xc0: - logger.error().append("TODO: Sound frequency: %d", value & 0x7f); - break; - - case 0xe0: - logger.error().append("TODO: video control: %08x", value); - break; - - default: - logger.error().append("TODO: unrecognised VIDC write of %08x", value); - break; - } - } -}; - -// IRQ A flags -namespace IRQA { - // The first four of these are taken from the A500 documentation and may be inaccurate. - static constexpr uint8_t PrinterBusy = 0x01; - static constexpr uint8_t SerialRinging = 0x02; - static constexpr uint8_t PrinterAcknowledge = 0x04; - static constexpr uint8_t VerticalFlyback = 0x08; - static constexpr uint8_t PowerOnReset = 0x10; - static constexpr uint8_t Timer0 = 0x20; - static constexpr uint8_t Timer1 = 0x40; - static constexpr uint8_t SetAlways = 0x80; -} - -// IRQ B flags -namespace IRQB { - // These are taken from the A3010 documentation. - static constexpr uint8_t PoduleFIQRequest = 0x01; - static constexpr uint8_t SoundBufferPointerUsed = 0x02; - static constexpr uint8_t SerialLine = 0x04; - static constexpr uint8_t IDE = 0x08; - static constexpr uint8_t FloppyDiscInterrupt = 0x10; - static constexpr uint8_t PoduleIRQRequest = 0x20; - static constexpr uint8_t KeyboardTransmitEmpty = 0x40; - static constexpr uint8_t KeyboardReceiveFull = 0x80; -} - -// FIQ flags -namespace FIQ { - // These are taken from the A3010 documentation. - static constexpr uint8_t FloppyDiscData = 0x01; - static constexpr uint8_t SerialLine = 0x10; - static constexpr uint8_t PoduleFIQRequest = 0x40; - static constexpr uint8_t SetAlways = 0x80; -} - -namespace InterruptRequests { - static constexpr int IRQ = 0x01; - static constexpr int FIQ = 0x02; -}; - -struct InputOutput { - int interrupt_mask() const { - return - ((irq_a_.request() | irq_b_.request()) ? InterruptRequests::IRQ : 0) | - (fiq_.request() ? InterruptRequests::FIQ : 0); - } - - template - bool tick_timer() { - if(!counters_[c].value && !counters_[c].reload) { - return false; - } - - --counters_[c].value; - if(!counters_[c].value) { - counters_[c].value = counters_[c].reload; - - switch(c) { - case 0: return irq_a_.apply(IRQA::Timer0); - case 1: return irq_a_.apply(IRQA::Timer1); - case 3: { - serial_.shift(); - keyboard_.update(); - - const uint8_t events = serial_.events(IOCParty); - bool did_interrupt = false; - if(events & HalfDuplexSerial::Receive) { - did_interrupt |= irq_b_.apply(IRQB::KeyboardReceiveFull); - } - if(events & HalfDuplexSerial::Transmit) { - did_interrupt |= irq_b_.apply(IRQB::KeyboardTransmitEmpty); - } - - return did_interrupt; - } - default: break; - } - // TODO: events for timers 2 (baud). - } - - return false; - } - - bool tick_timers() { - bool did_change_interrupts = false; - did_change_interrupts |= tick_timer<0>(); - did_change_interrupts |= tick_timer<1>(); - did_change_interrupts |= tick_timer<2>(); - did_change_interrupts |= tick_timer<3>(); - return did_change_interrupts; - } - - static constexpr uint32_t AddressMask = 0x1f'ffff; - - bool read(uint32_t address, uint8_t &value) { - const auto target = address & AddressMask; - value = 0xff; - switch(target) { - default: - logger.error().append("Unrecognised IOC read from %08x", address); - break; - - case 0x3200000 & AddressMask: - value = control_ | 0xc0; - value &= ~(i2c_.clock() ? 2 : 0); - value &= ~(i2c_.data() ? 1 : 0); -// logger.error().append("IOC control read: C:%d D:%d", !(value & 2), !(value & 1)); - return true; - - case 0x3200004 & AddressMask: - value = serial_.input(IOCParty); - irq_b_.clear(IRQB::KeyboardReceiveFull); - logger.error().append("IOC keyboard receive: %02x", value); - return true; - - // IRQ A. - case 0x3200010 & AddressMask: - value = irq_a_.status; -// logger.error().append("IRQ A status is %02x", value); - return true; - case 0x3200014 & AddressMask: - value = irq_a_.request(); - logger.error().append("IRQ A request is %02x", value); - return true; - case 0x3200018 & AddressMask: - value = irq_a_.mask; - logger.error().append("IRQ A mask is %02x", value); - return true; - - // IRQ B. - case 0x3200020 & AddressMask: - value = irq_b_.status; -// logger.error().append("IRQ B status is %02x", value); - return true; - case 0x3200024 & AddressMask: - value = irq_b_.request(); - logger.error().append("IRQ B request is %02x", value); - return true; - case 0x3200028 & AddressMask: - value = irq_b_.mask; - logger.error().append("IRQ B mask is %02x", value); - return true; - - // FIQ. - case 0x3200030 & AddressMask: - value = fiq_.status; - logger.error().append("FIQ status is %02x", value); - return true; - case 0x3200034 & AddressMask: - value = fiq_.request(); - logger.error().append("FIQ request is %02x", value); - return true; - case 0x3200038 & AddressMask: - value = fiq_.mask; - logger.error().append("FIQ mask is %02x", value); - return true; - - // Counters. - case 0x3200040 & AddressMask: - case 0x3200050 & AddressMask: - case 0x3200060 & AddressMask: - case 0x3200070 & AddressMask: - value = counters_[(target >> 4) - 0x4].output & 0xff; -// logger.error().append("%02x: Counter %d low is %02x", target, (target >> 4) - 0x4, value); - return true; - - case 0x3200044 & AddressMask: - case 0x3200054 & AddressMask: - case 0x3200064 & AddressMask: - case 0x3200074 & AddressMask: - value = counters_[(target >> 4) - 0x4].output >> 8; -// logger.error().append("%02x: Counter %d high is %02x", target, (target >> 4) - 0x4, value); - return true; - } - - return true; - } - - bool write(uint32_t address, uint8_t value) { - const auto target = address & AddressMask; - switch(target) { - default: - logger.error().append("Unrecognised IOC write of %02x at %08x", value, address); - break; - - case 0x320'0000 & AddressMask: - // TODO: does the rest of the control register relate to anything? -// logger.error().append("TODO: IOC control write: C:%d D:%d", !(value & 2), !(value & 1)); - - control_ = value; - i2c_.set_clock_data(!(value & 2), !(value & 1)); - return true; - - case 0x320'0004 & AddressMask: - logger.error().append("IOC keyboard transmit %02x", value); - serial_.output(IOCParty, value); - irq_b_.clear(IRQB::KeyboardTransmitEmpty); - return true; - - case 0x320'0014 & AddressMask: - // b2: clear IF. - // b3: clear IR. - // b4: clear POR. - // b5: clear TM[0]. - // b6: clear TM[1]. - irq_a_.clear(value & 0x7c); - return true; - - // Interrupts. - case 0x320'0018 & AddressMask: irq_a_.mask = value; return true; - case 0x320'0028 & AddressMask: irq_b_.mask = value; return true; - case 0x320'0038 & AddressMask: fiq_.mask = value; return true; - - // Counters. - case 0x320'0040 & AddressMask: - case 0x320'0050 & AddressMask: - case 0x320'0060 & AddressMask: - case 0x320'0070 & AddressMask: - counters_[(target >> 4) - 0x4].reload = uint16_t( - (counters_[(target >> 4) - 0x4].reload & 0xff00) | value - ); - return true; - - case 0x320'0044 & AddressMask: - case 0x320'0054 & AddressMask: - case 0x320'0064 & AddressMask: - case 0x320'0074 & AddressMask: - counters_[(target >> 4) - 0x4].reload = uint16_t( - (counters_[(target >> 4) - 0x4].reload & 0x00ff) | (value << 8) - ); - return true; - - case 0x320'0048 & AddressMask: - case 0x320'0058 & AddressMask: - case 0x320'0068 & AddressMask: - case 0x320'0078 & AddressMask: - counters_[(target >> 4) - 0x4].value = counters_[(target >> 4) - 0x4].reload; - return true; - - case 0x320'004c & AddressMask: - case 0x320'005c & AddressMask: - case 0x320'006c & AddressMask: - case 0x320'007c & AddressMask: - counters_[(target >> 4) - 0x4].output = counters_[(target >> 4) - 0x4].value; - return true; - - case 0x327'0000 & AddressMask: - logger.error().append("TODO: exteded external podule space"); - return true; - - case 0x331'0000 & AddressMask: - logger.error().append("TODO: 1772 / disk write"); - return true; - - case 0x335'0000 & AddressMask: - logger.error().append("TODO: LS374 / printer data write"); - return true; - - case 0x335'0018 & AddressMask: - logger.error().append("TODO: latch B write: %02x", value); - return true; - - case 0x335'0040 & AddressMask: - logger.error().append("TODO: latch A write: %02x", value); - return true; - - case 0x335'0048 & AddressMask: - logger.error().append("TODO: latch C write: %02x", value); - return true; - - case 0x336'0000 & AddressMask: - logger.error().append("TODO: podule interrupt request"); - return true; - - case 0x336'0004 & AddressMask: - logger.error().append("TODO: podule interrupt mask"); - return true; - - case 0x33a'0000 & AddressMask: - logger.error().append("TODO: 6854 / econet write"); - return true; - - case 0x33b'0000 & AddressMask: - logger.error().append("TODO: 6551 / serial line write"); - return true; - } - - return true; - } - - InputOutput() : keyboard_(serial_) { - irq_a_.status = IRQA::SetAlways | IRQA::PowerOnReset; - irq_b_.status = 0x00; - fiq_.status = 0x80; // 'set always'. - - i2c_.add_peripheral(&cmos_, 0xa0); - } - - Audio &audio() { - return audio_; - } - - bool tick_audio() { - if(audio_.tick()) { - if(audio_.interrupt()) { - irq_b_.apply(IRQB::SoundBufferPointerUsed); - } else { - irq_b_.clear(IRQB::SoundBufferPointerUsed); - } - return true; - } - return false; - } - - void swap_audio() { - audio_.swap(); - irq_b_.clear(IRQB::SoundBufferPointerUsed); - } - -private: - // IRQA, IRQB and FIQ states. - struct Interrupt { - uint8_t status, mask; - uint8_t request() const { - return status & mask; - } - bool apply(uint8_t value) { - status |= value; - return status & mask; - } - void clear(uint8_t bits) { - status &= ~bits; - } - }; - Interrupt irq_a_, irq_b_, fiq_; - - // The IOCs four counters. - struct Counter { - uint16_t value; - uint16_t reload; - uint16_t output; - }; - Counter counters_[4]; - - // The KART and keyboard beyond it. - HalfDuplexSerial serial_; - Keyboard keyboard_; - - // The control register. - uint8_t control_ = 0xff; - - // The I2C bus. - I2C::Bus i2c_; - CMOSRAM cmos_; - - // Audio. - Audio audio_; -}; - -/// Primarily models the MEMC. -template -struct Memory { - Memory(IOCWriteDelegateT &ioc_write_delegate) : ioc_write_delegate_(ioc_write_delegate) {} - - int interrupt_mask() const { - return ioc_.interrupt_mask(); - } - - void set_rom(const std::vector &rom) { - std::copy( - rom.begin(), - rom.begin() + static_cast(std::min(rom.size(), rom_.size())), - rom_.begin()); - } - - template - uint32_t aligned(uint32_t address) { - if constexpr (std::is_same_v) { - return address & static_cast(~3); - } - return address; - } - - template - bool write(uint32_t address, IntT source, InstructionSet::ARM::Mode mode, bool) { - // User mode may only _write_ to logically-mapped RAM (subject to further testing below). - if(mode == InstructionSet::ARM::Mode::User && address >= 0x200'0000) { - return false; - } - - switch(write_zones_[(address >> 21) & 31]) { - case Zone::DMAAndMEMC: { - const auto buffer_address = [](uint32_t source) -> uint32_t { - return (source & 0x1fffc0) << 2; - }; - - // The MEMC itself isn't on the data bus; all values below should be taken from `address`. - switch((address >> 17) & 0b111) { - case 0b000: - logger.error().append("TODO: DMA/MEMC Vinit = %04x", address & 0x1fffc0); - return true; - - case 0b001: - logger.error().append("TODO: DMA/MEMC Vstart = %04x", address & 0x1fffc0); - return true; - - case 0b010: - logger.error().append("TODO: DMA/MEMC Vend = %04x", address & 0x1fffc0); - return true; - - case 0b011: - logger.error().append("TODO: DMA/MEMC Cinit = %04x", address & 0x1fffc0); - return true; - - case 0b100: - logger.error().append("TODO: DMA/MEMC Sstart = %04x", address & 0x1fffc0); - ioc_.audio().set_next_start(buffer_address(address)); - return true; - - case 0b101: - logger.error().append("TODO: DMA/MEMC SendN = %04x", address & 0x1fffc0); - ioc_.audio().set_next_end(buffer_address(address)); - return true; - - case 0b110: - logger.error().append("TODO: DMA/MEMC Sptr"); - ioc_.swap_audio(); - return true; - - case 0b111: - os_mode_ = address & (1 << 12); - audio_dma_enable_ = address & (1 << 11); - video_dma_enable_ = address & (1 << 10); - switch((address >> 8) & 3) { - default: - dynamic_ram_refresh_ = DynamicRAMRefresh::None; - break; - case 0b01: - case 0b11: - dynamic_ram_refresh_ = DynamicRAMRefresh((address >> 8) & 3); - break; - } - high_rom_access_time_ = ROMAccessTime((address >> 6) & 3); - low_rom_access_time_ = ROMAccessTime((address >> 4) & 3); - page_size_ = PageSize((address >> 2) & 3); - - logger.info().append("MEMC Control: %08x -> OS:%d audio:%d video:%d refresh:%d high:%d low:%d size:%d", address, os_mode_, audio_dma_enable_, video_dma_enable_, dynamic_ram_refresh_, high_rom_access_time_, low_rom_access_time_, page_size_); - map_dirty_ = true; - return true; - } - } break; - - case Zone::LogicallyMappedRAM: { - const auto item = logical_ram(address, mode); - if(!item) { - return false; - } - *item = source; - return true; - } break; - - case Zone::IOControllers: - // TODO: have I overrestricted the value type for the IOC area? - ioc_.write(address, uint8_t(source)); - ioc_write_delegate_.did_write_ioc(); - return true; - - case Zone::VideoController: - // TODO: handle byte writes correctly. - vidc_.write(source); - break; - - case Zone::PhysicallyMappedRAM: - physical_ram(address) = source; - return true; - - case Zone::AddressTranslator: -// printf("Translator write at %08x; replaces %08x\n", address, pages_[address & 0x7f]); - pages_[address & 0x7f] = address; - map_dirty_ = true; - break; - - default: -// printf("TODO: write of %08x to %08x [%lu]\n", source, address, sizeof(IntT)); - break; - } - - return true; - } - - template - bool read(uint32_t address, IntT &source, InstructionSet::ARM::Mode mode, bool) { - // User mode may only read logically-maped RAM and ROM. - if(mode == InstructionSet::ARM::Mode::User && address >= 0x200'0000 && address < 0x380'0000) { - return false; - } - - switch (read_zones_[(address >> 21) & 31]) { - case Zone::PhysicallyMappedRAM: - source = physical_ram(address); - return true; - - case Zone::LogicallyMappedRAM: { - if(!has_moved_rom_) { // TODO: maintain this state in the zones table. - source = high_rom(address); - return true; - } - - const auto item = logical_ram(address, mode); - if(!item) { - return false; - } - source = *item; - return true; - } break; - - case Zone::LowROM: -// logger.error().append("TODO: Low ROM read from %08x", address); - source = IntT(~0); - return true; - - case Zone::HighROM: - // Real test is: require A24=A25=0, then A25=1. - // TODO: as above, move this test into the zones tables. - has_moved_rom_ = true; - source = high_rom(address); - return true; - - case Zone::IOControllers: { - if constexpr (std::is_same_v) { - ioc_.read(address, source); - return true; - } else { - // TODO: generalise this adaptation of an 8-bit device to the 32-bit bus, which probably isn't right anyway. - uint8_t value; - ioc_.read(address, value); - source = value; - return true; - } - } - - default: - logger.error().append("TODO: read from %08x", address); - break; - } - - source = 0; - return false; - } - - bool tick_timers() { - return ioc_.tick_timers(); - } - - bool tick_audio() { - // TODO: does disabling audio DMA pause output, or leave it ticking and merely - // stop allowing it to use the bus? - return ioc_.tick_audio(); - } - - private: - bool has_moved_rom_ = false; - std::array ram_{}; - std::array rom_; - InputOutput ioc_; - Video vidc_; - IOCWriteDelegateT &ioc_write_delegate_; - - template - IntT &physical_ram(uint32_t address) { - address = aligned(address); - address &= (ram_.size() - 1); - return *reinterpret_cast(&ram_[address]); - } - - template - IntT &high_rom(uint32_t address) { - address = aligned(address); - return *reinterpret_cast(&rom_[address & (rom_.size() - 1)]); - } - - static constexpr std::array read_zones_ = zones(true); - static constexpr std::array write_zones_ = zones(false); - - // Control register values. - bool os_mode_ = false; - bool audio_dma_enable_ = false; - bool video_dma_enable_ = false; // "Unaffected" by reset, so here picked arbitrarily. - - enum class DynamicRAMRefresh { - None = 0b00, - DuringFlyback = 0b01, - Continuous = 0b11, - } dynamic_ram_refresh_ = DynamicRAMRefresh::None; // State at reset is undefined; constrain to a valid enum value. - - enum class ROMAccessTime { - ns450 = 0b00, - ns325 = 0b01, - ns200 = 0b10, - ns200with60nsNibble = 0b11, - } high_rom_access_time_ = ROMAccessTime::ns450, low_rom_access_time_ = ROMAccessTime::ns450; - - enum class PageSize { - kb4 = 0b00, - kb8 = 0b01, - kb16 = 0b10, - kb32 = 0b11, - } page_size_ = PageSize::kb4; - - // Address translator. - // - // MEMC contains one entry per a physical page number, indicating where it goes logically. - // Any logical access is tested against all 128 mappings. So that's backwards compared to - // the ideal for an emulator, which would map from logical to physical, even if a lot more - // compact — there are always 128 physical pages; there are up to 8192 logical pages. - // - // So captured here are both the physical -> logical map as representative of the real - // hardware, and the reverse logical -> physical map, which is built (and rebuilt, and rebuilt) - // from the other. - - // Physical to logical mapping. - std::array pages_{}; - - // Logical to physical mapping. - struct MappedPage { - uint8_t *target = nullptr; - uint8_t protection_level = 0; - }; - std::array mapping_; - bool map_dirty_ = true; - - template - IntT *logical_ram(uint32_t address, InstructionSet::ARM::Mode mode) { - // Possibly TODO: this recompute-if-dirty flag is supposed to ameliorate for an expensive - // mapping process. It can be eliminated when the process is improved. - if(map_dirty_) { - update_mapping(); - map_dirty_ = false; - } - - address = aligned(address); - address &= 0x1ff'ffff; - size_t page; - - // TODO: eliminate switch here. - switch(page_size_) { - default: - case PageSize::kb4: - page = address >> 12; - address &= 0x0fff; - break; - case PageSize::kb8: - page = address >> 13; - address &= 0x1fff; - break; - case PageSize::kb16: - page = address >> 14; - address &= 0x3fff; - break; - case PageSize::kb32: - page = address >> 15; - address &= 0x7fff; - break; - } - - if(!mapping_[page].target) { - return nullptr; - } - - // TODO: eliminate switch here. - // Top of my head idea: is_read, is_user and is_os_mode make three bits, so - // keep a one-byte bitmap of permitted accesses rather than the raw protection - // level? - switch(mapping_[page].protection_level) { - case 0b00: break; - case 0b01: - if(!is_read && mode == InstructionSet::ARM::Mode::User) { - return nullptr; - } - break; - default: - if(mode == InstructionSet::ARM::Mode::User) { - return nullptr; - } - if(!is_read && !os_mode_) { - return nullptr; - } - break; - } - - return reinterpret_cast(mapping_[page].target + address); - } - - void update_mapping() { - // For each physical page, project it into logical space. - switch(page_size_) { - default: - case PageSize::kb4: update_mapping(); break; - case PageSize::kb8: update_mapping(); break; - case PageSize::kb16: update_mapping(); break; - case PageSize::kb32: update_mapping(); break; - } - } - - template - void update_mapping() { - // Clear all logical mappings. - std::fill(mapping_.begin(), mapping_.end(), MappedPage{}); - - // For each physical page, project it into logical space - // and store it. - for(const auto page: pages_) { - uint32_t physical, logical; - - switch(size) { - case PageSize::kb4: - // 4kb: - // A[6:0] -> PPN[6:0] - // A[11:10] -> LPN[12:11]; A[22:12] -> LPN[10:0] i.e. 8192 logical pages - physical = page & BitMask<6, 0>::value; - - physical <<= 12; - - logical = (page & BitMask<11, 10>::value) << 1; - logical |= (page & BitMask<22, 12>::value) >> 12; - break; - - case PageSize::kb8: - // 8kb: - // A[0] -> PPN[6]; A[6:1] -> PPN[5:0] - // A[11:10] -> LPN[11:10]; A[22:13] -> LPN[9:0] i.e. 4096 logical pages - physical = (page & BitMask<0, 0>::value) << 6; - physical |= (page & BitMask<6, 1>::value) >> 1; - - physical <<= 13; - - logical = page & BitMask<11, 10>::value; - logical |= (page & BitMask<22, 13>::value) >> 13; - break; - - case PageSize::kb16: - // 16kb: - // A[1:0] -> PPN[6:5]; A[6:2] -> PPN[4:0] - // A[11:10] -> LPN[10:9]; A[22:14] -> LPN[8:0] i.e. 2048 logical pages - physical = (page & BitMask<1, 0>::value) << 5; - physical |= (page & BitMask<6, 2>::value) >> 2; - - physical <<= 14; - - logical = (page & BitMask<11, 10>::value) >> 1; - logical |= (page & BitMask<22, 14>::value) >> 14; - break; - - case PageSize::kb32: - // 32kb: - // A[1] -> PPN[6]; A[2] -> PPN[5]; A[0] -> PPN[4]; A[6:3] -> PPN[3:0] - // A[11:10] -> LPN[9:8]; A[22:15] -> LPN[7:0] i.e. 1024 logical pages - physical = (page & BitMask<1, 1>::value) << 5; - physical |= (page & BitMask<2, 2>::value) << 3; - physical |= (page & BitMask<0, 0>::value) << 4; - physical |= (page & BitMask<6, 3>::value) >> 3; - - physical <<= 15; - - logical = (page & BitMask<11, 10>::value) >> 2; - logical |= (page & BitMask<22, 15>::value) >> 15; - break; - } - -// printf("%08x => physical %d -> logical %d\n", page, (physical >> 15), logical); - - // TODO: consider clashes. - // TODO: what if there's less than 4mb present? - mapping_[logical].target = &ram_[physical]; - mapping_[logical].protection_level = (page >> 8) & 3; - } - } -}; - class ConcreteMachine: public Machine, public MachineTypes::MediaTarget, @@ -1139,36 +64,14 @@ class ConcreteMachine: // * timers: 2; // * audio: 1. [TODO] - tick_cpu(); - tick_cpu(); - tick_cpu(); - tick_cpu(); - - tick_cpu(); - tick_cpu(); - tick_cpu(); - tick_cpu(); - - tick_cpu(); - tick_cpu(); - tick_cpu(); - tick_cpu(); + tick_cpu(); tick_cpu(); tick_cpu(); tick_cpu(); + tick_cpu(); tick_cpu(); tick_cpu(); tick_cpu(); + tick_cpu(); tick_cpu(); tick_cpu(); tick_cpu(); tick_timers(); - tick_cpu(); - tick_cpu(); - tick_cpu(); - tick_cpu(); - - tick_cpu(); - tick_cpu(); - tick_cpu(); - tick_cpu(); - - tick_cpu(); - tick_cpu(); - tick_cpu(); - tick_cpu(); + tick_cpu(); tick_cpu(); tick_cpu(); tick_cpu(); + tick_cpu(); tick_cpu(); tick_cpu(); tick_cpu(); + tick_cpu(); tick_cpu(); tick_cpu(); tick_cpu(); tick_timers(); tick_audio(); } @@ -1192,8 +95,16 @@ class ConcreteMachine: insert_media(target.media); } - void did_write_ioc() { - test_interrupts(); + void update_interrupts() { + using Exception = InstructionSet::ARM::Registers::Exception; + + const int requests = executor_.bus.interrupt_mask(); + if((requests & InterruptRequests::FIQ) && executor_.registers().interrupt()) { + return; + } + if(requests & InterruptRequests::IRQ) { + executor_.registers().interrupt(); + } } private: @@ -1290,29 +201,8 @@ class ConcreteMachine: // } } - void test_interrupts() { - using Exception = InstructionSet::ARM::Registers::Exception; - - const int requests = executor_.bus.interrupt_mask(); - if((requests & InterruptRequests::FIQ) && executor_.registers().interrupt()) { - return; - } - if(requests & InterruptRequests::IRQ) { - executor_.registers().interrupt(); - } - } - - void tick_timers() { - if(executor_.bus.tick_timers()) { - test_interrupts(); - } - } - - void tick_audio() { - if(executor_.bus.tick_audio()) { - test_interrupts(); - } - } + void tick_timers() { executor_.bus.tick_timers(); } + void tick_audio() { executor_.bus.tick_audio(); } // MARK: - MediaTarget bool insert_media(const Analyser::Static::Media &) override { @@ -1328,7 +218,7 @@ class ConcreteMachine: // MARK: - ARM execution static constexpr auto arm_model = InstructionSet::ARM::Model::ARMv2; - InstructionSet::ARM::Executor> executor_; + InstructionSet::ARM::Executor> executor_; }; } diff --git a/Machines/Acorn/Archimedes/CMOSRAM.hpp b/Machines/Acorn/Archimedes/CMOSRAM.hpp new file mode 100644 index 000000000..4b2ff6102 --- /dev/null +++ b/Machines/Acorn/Archimedes/CMOSRAM.hpp @@ -0,0 +1,19 @@ +// +// CMOSRAM.hpp +// Clock Signal +// +// Created by Thomas Harte on 20/03/2024. +// Copyright © 2024 Thomas Harte. All rights reserved. +// + +#pragma once + +#include "../../../Components/I2C/I2C.hpp" + +namespace Archimedes { + +struct CMOSRAM: public I2C::Peripheral { + // All TODO. +}; + +} diff --git a/Machines/Acorn/Archimedes/HalfDuplexSerial.hpp b/Machines/Acorn/Archimedes/HalfDuplexSerial.hpp new file mode 100644 index 000000000..e47a0a17d --- /dev/null +++ b/Machines/Acorn/Archimedes/HalfDuplexSerial.hpp @@ -0,0 +1,88 @@ +// +// HalfDuplexSerial.hpp +// Clock Signal +// +// Created by Thomas Harte on 20/03/2024. +// Copyright © 2024 Thomas Harte. All rights reserved. +// + +#pragma once + +namespace Archimedes { + +/// Models a half-duplex serial link between two parties, framing bytes with one start bit and two stop bits. +struct HalfDuplexSerial { + static constexpr uint16_t ShiftMask = 0b1111'1110'0000'0000; + + /// Enqueues @c value for output. + void output(int party, uint8_t value) { + parties_[party].output_count = 11; + parties_[party].input = 0x7ff; + parties_[party].output = uint16_t((value << 1) | ShiftMask); + } + + /// @returns The last observed input. + uint8_t input(int party) const { + return uint8_t(parties_[party].input >> 1); + } + + static constexpr uint8_t Receive = 1 << 0; + static constexpr uint8_t Transmit = 1 << 1; + + /// @returns A bitmask of events that occurred during the last shift. + uint8_t events(int party) { + const auto result = parties_[party].events; + parties_[party].events = 0; + return result; + } + + bool is_outputting(int party) const { + return parties_[party].output_count != 11; + } + + /// Updates the shifters on both sides of the serial link. + void shift() { + const uint16_t next = parties_[0].output & parties_[1].output & 1; + + for(int c = 0; c < 2; c++) { + if(parties_[c].output_count) { + --parties_[c].output_count; + if(!parties_[c].output_count) { + parties_[c].events |= Transmit; + parties_[c].input_count = -1; + } + parties_[c].output = (parties_[c].output >> 1) | ShiftMask; + } else { + // Check for a start bit. + if(parties_[c].input_count == -1 && !next) { + parties_[c].input_count = 0; + } + + // Shift in if currently observing. + if(parties_[c].input_count >= 0 && parties_[c].input_count < 11) { + parties_[c].input = uint16_t((parties_[c].input >> 1) | (next << 10)); + + ++parties_[c].input_count; + if(parties_[c].input_count == 11) { + parties_[c].events |= Receive; + parties_[c].input_count = -1; + } + } + } + } + } + +private: + struct Party { + int output_count = 0; + int input_count = -1; + uint16_t output = 0xffff; + uint16_t input = 0; + uint8_t events = 0; + } parties_[2]; +}; + +static constexpr int IOCParty = 0; +static constexpr int KeyboardParty = 1; + +} diff --git a/Machines/Acorn/Archimedes/InputOutputController.hpp b/Machines/Acorn/Archimedes/InputOutputController.hpp new file mode 100644 index 000000000..3b747f9e3 --- /dev/null +++ b/Machines/Acorn/Archimedes/InputOutputController.hpp @@ -0,0 +1,382 @@ +// +// InputOutputController.h +// Clock Signal +// +// Created by Thomas Harte on 20/03/2024. +// Copyright © 2024 Thomas Harte. All rights reserved. +// + +#pragma once + +#include "../../../Outputs/Log.hpp" + +#include "CMOSRAM.hpp" +#include "Keyboard.hpp" +#include "Sound.hpp" + +namespace Archimedes { + +// IRQ A flags +namespace IRQA { + // The first four of these are taken from the A500 documentation and may be inaccurate. + static constexpr uint8_t PrinterBusy = 0x01; + static constexpr uint8_t SerialRinging = 0x02; + static constexpr uint8_t PrinterAcknowledge = 0x04; + static constexpr uint8_t VerticalFlyback = 0x08; + static constexpr uint8_t PowerOnReset = 0x10; + static constexpr uint8_t Timer0 = 0x20; + static constexpr uint8_t Timer1 = 0x40; + static constexpr uint8_t SetAlways = 0x80; +} + +// IRQ B flags +namespace IRQB { + // These are taken from the A3010 documentation. + static constexpr uint8_t PoduleFIQRequest = 0x01; + static constexpr uint8_t SoundBufferPointerUsed = 0x02; + static constexpr uint8_t SerialLine = 0x04; + static constexpr uint8_t IDE = 0x08; + static constexpr uint8_t FloppyDiscInterrupt = 0x10; + static constexpr uint8_t PoduleIRQRequest = 0x20; + static constexpr uint8_t KeyboardTransmitEmpty = 0x40; + static constexpr uint8_t KeyboardReceiveFull = 0x80; +} + +// FIQ flags +namespace FIQ { + // These are taken from the A3010 documentation. + static constexpr uint8_t FloppyDiscData = 0x01; + static constexpr uint8_t SerialLine = 0x10; + static constexpr uint8_t PoduleFIQRequest = 0x40; + static constexpr uint8_t SetAlways = 0x80; +} + +namespace InterruptRequests { + static constexpr int IRQ = 0x01; + static constexpr int FIQ = 0x02; +}; + +template +struct InputOutputController { + int interrupt_mask() const { + return + ((irq_a_.request() | irq_b_.request()) ? InterruptRequests::IRQ : 0) | + (fiq_.request() ? InterruptRequests::FIQ : 0); + } + + template + bool tick_timer() { + if(!counters_[c].value && !counters_[c].reload) { + return false; + } + + --counters_[c].value; + if(!counters_[c].value) { + counters_[c].value = counters_[c].reload; + + switch(c) { + case 0: return irq_a_.set(IRQA::Timer0); + case 1: return irq_a_.set(IRQA::Timer1); + case 3: { + serial_.shift(); + keyboard_.update(); + + const uint8_t events = serial_.events(IOCParty); + bool did_interrupt = false; + if(events & HalfDuplexSerial::Receive) { + did_interrupt |= irq_b_.set(IRQB::KeyboardReceiveFull); + } + if(events & HalfDuplexSerial::Transmit) { + did_interrupt |= irq_b_.set(IRQB::KeyboardTransmitEmpty); + } + + return did_interrupt; + } + default: break; + } + // TODO: events for timers 2 (baud). + } + + return false; + } + + void tick_timers() { + bool did_change_interrupts = false; + did_change_interrupts |= tick_timer<0>(); + did_change_interrupts |= tick_timer<1>(); + did_change_interrupts |= tick_timer<2>(); + did_change_interrupts |= tick_timer<3>(); + if(did_change_interrupts) { + observer_.update_interrupts(); + } + } + + static constexpr uint32_t AddressMask = 0x1f'ffff; + + bool read(uint32_t address, uint8_t &value) { + const auto target = address & AddressMask; + value = 0xff; + switch(target) { + default: + logger.error().append("Unrecognised IOC read from %08x", address); + break; + + case 0x3200000 & AddressMask: + value = control_ | 0xc0; + value &= ~(i2c_.clock() ? 2 : 0); + value &= ~(i2c_.data() ? 1 : 0); +// logger.error().append("IOC control read: C:%d D:%d", !(value & 2), !(value & 1)); + return true; + + case 0x3200004 & AddressMask: + value = serial_.input(IOCParty); + irq_b_.clear(IRQB::KeyboardReceiveFull); + logger.error().append("IOC keyboard receive: %02x", value); + return true; + + // IRQ A. + case 0x3200010 & AddressMask: + value = irq_a_.status; +// logger.error().append("IRQ A status is %02x", value); + return true; + case 0x3200014 & AddressMask: + value = irq_a_.request(); + logger.error().append("IRQ A request is %02x", value); + return true; + case 0x3200018 & AddressMask: + value = irq_a_.mask; + logger.error().append("IRQ A mask is %02x", value); + return true; + + // IRQ B. + case 0x3200020 & AddressMask: + value = irq_b_.status; +// logger.error().append("IRQ B status is %02x", value); + return true; + case 0x3200024 & AddressMask: + value = irq_b_.request(); + logger.error().append("IRQ B request is %02x", value); + return true; + case 0x3200028 & AddressMask: + value = irq_b_.mask; + logger.error().append("IRQ B mask is %02x", value); + return true; + + // FIQ. + case 0x3200030 & AddressMask: + value = fiq_.status; + logger.error().append("FIQ status is %02x", value); + return true; + case 0x3200034 & AddressMask: + value = fiq_.request(); + logger.error().append("FIQ request is %02x", value); + return true; + case 0x3200038 & AddressMask: + value = fiq_.mask; + logger.error().append("FIQ mask is %02x", value); + return true; + + // Counters. + case 0x3200040 & AddressMask: + case 0x3200050 & AddressMask: + case 0x3200060 & AddressMask: + case 0x3200070 & AddressMask: + value = counters_[(target >> 4) - 0x4].output & 0xff; +// logger.error().append("%02x: Counter %d low is %02x", target, (target >> 4) - 0x4, value); + return true; + + case 0x3200044 & AddressMask: + case 0x3200054 & AddressMask: + case 0x3200064 & AddressMask: + case 0x3200074 & AddressMask: + value = counters_[(target >> 4) - 0x4].output >> 8; +// logger.error().append("%02x: Counter %d high is %02x", target, (target >> 4) - 0x4, value); + return true; + } + + return true; + } + + bool write(uint32_t address, uint8_t value) { + const auto target = address & AddressMask; + switch(target) { + default: + logger.error().append("Unrecognised IOC write of %02x at %08x", value, address); + break; + + case 0x320'0000 & AddressMask: + // TODO: does the rest of the control register relate to anything? +// logger.error().append("TODO: IOC control write: C:%d D:%d", !(value & 2), !(value & 1)); + + control_ = value; + i2c_.set_clock_data(!(value & 2), !(value & 1)); + return true; + + case 0x320'0004 & AddressMask: + logger.error().append("IOC keyboard transmit %02x", value); + serial_.output(IOCParty, value); + irq_b_.clear(IRQB::KeyboardTransmitEmpty); + return true; + + case 0x320'0014 & AddressMask: + // b2: clear IF. + // b3: clear IR. + // b4: clear POR. + // b5: clear TM[0]. + // b6: clear TM[1]. + irq_a_.clear(value & 0x7c); + return true; + + // Interrupts. + case 0x320'0018 & AddressMask: irq_a_.mask = value; return true; + case 0x320'0028 & AddressMask: irq_b_.mask = value; return true; + case 0x320'0038 & AddressMask: fiq_.mask = value; return true; + + // Counters. + case 0x320'0040 & AddressMask: + case 0x320'0050 & AddressMask: + case 0x320'0060 & AddressMask: + case 0x320'0070 & AddressMask: + counters_[(target >> 4) - 0x4].reload = uint16_t( + (counters_[(target >> 4) - 0x4].reload & 0xff00) | value + ); + return true; + + case 0x320'0044 & AddressMask: + case 0x320'0054 & AddressMask: + case 0x320'0064 & AddressMask: + case 0x320'0074 & AddressMask: + counters_[(target >> 4) - 0x4].reload = uint16_t( + (counters_[(target >> 4) - 0x4].reload & 0x00ff) | (value << 8) + ); + return true; + + case 0x320'0048 & AddressMask: + case 0x320'0058 & AddressMask: + case 0x320'0068 & AddressMask: + case 0x320'0078 & AddressMask: + counters_[(target >> 4) - 0x4].value = counters_[(target >> 4) - 0x4].reload; + return true; + + case 0x320'004c & AddressMask: + case 0x320'005c & AddressMask: + case 0x320'006c & AddressMask: + case 0x320'007c & AddressMask: + counters_[(target >> 4) - 0x4].output = counters_[(target >> 4) - 0x4].value; + return true; + + case 0x327'0000 & AddressMask: + logger.error().append("TODO: exteded external podule space"); + return true; + + case 0x331'0000 & AddressMask: + logger.error().append("TODO: 1772 / disk write"); + return true; + + case 0x335'0000 & AddressMask: + logger.error().append("TODO: LS374 / printer data write"); + return true; + + case 0x335'0018 & AddressMask: + logger.error().append("TODO: latch B write: %02x", value); + return true; + + case 0x335'0040 & AddressMask: + logger.error().append("TODO: latch A write: %02x", value); + return true; + + case 0x335'0048 & AddressMask: + logger.error().append("TODO: latch C write: %02x", value); + return true; + + case 0x336'0000 & AddressMask: + logger.error().append("TODO: podule interrupt request"); + return true; + + case 0x336'0004 & AddressMask: + logger.error().append("TODO: podule interrupt mask"); + return true; + + case 0x33a'0000 & AddressMask: + logger.error().append("TODO: 6854 / econet write"); + return true; + + case 0x33b'0000 & AddressMask: + logger.error().append("TODO: 6551 / serial line write"); + return true; + } + + return true; + } + + InputOutputController(InterruptObserverT &observer) : + observer_(observer), + keyboard_(serial_), + sound_(*this) + { + irq_a_.status = IRQA::SetAlways | IRQA::PowerOnReset; + irq_b_.status = 0x00; + fiq_.status = 0x80; // 'set always'. + + i2c_.add_peripheral(&cmos_, 0xa0); + update_sound_interrupt(); + } + + Sound &sound() { + return sound_; + } + + void update_sound_interrupt() { + if(sound_.interrupt()) { + irq_b_.set(IRQB::SoundBufferPointerUsed); + } else { + irq_b_.clear(IRQB::SoundBufferPointerUsed); + } + observer_.update_interrupts(); + } + +private: + Log::Logger logger; + InterruptObserverT &observer_; + + // IRQA, IRQB and FIQ states. + struct Interrupt { + uint8_t status, mask; + uint8_t request() const { + return status & mask; + } + bool set(uint8_t value) { + status |= value; + return status & mask; + } + void clear(uint8_t bits) { + status &= ~bits; + } + }; + Interrupt irq_a_, irq_b_, fiq_; + + // The IOCs four counters. + struct Counter { + uint16_t value; + uint16_t reload; + uint16_t output; + }; + Counter counters_[4]; + + // The KART and keyboard beyond it. + HalfDuplexSerial serial_; + Keyboard keyboard_; + + // The control register. + uint8_t control_ = 0xff; + + // The I2C bus. + I2C::Bus i2c_; + CMOSRAM cmos_; + + // Audio. + Sound sound_; +}; + +} + diff --git a/Machines/Acorn/Archimedes/Keyboard.hpp b/Machines/Acorn/Archimedes/Keyboard.hpp new file mode 100644 index 000000000..5140e772b --- /dev/null +++ b/Machines/Acorn/Archimedes/Keyboard.hpp @@ -0,0 +1,59 @@ +// +// Keyboard.hpp +// Clock Signal +// +// Created by Thomas Harte on 20/03/2024. +// Copyright © 2024 Thomas Harte. All rights reserved. +// + +#pragma once + +#include "HalfDuplexSerial.hpp" + +namespace Archimedes { + +// Resource for the keyboard protocol: https://github.com/tmk/tmk_keyboard/wiki/ACORN-ARCHIMEDES-Keyboard +struct Keyboard { + Keyboard(HalfDuplexSerial &serial) : serial_(serial) {} + + void update() { + if(serial_.events(KeyboardParty) & HalfDuplexSerial::Receive) { + const uint8_t input = serial_.input(KeyboardParty); + switch(input) { + case HRST: + // TODO: + case RAK1: + case RAK2: + serial_.output(KeyboardParty, input); + break; + + case RQID: + serial_.output(KeyboardParty, 0x81); // TODO: what keyboard type? + break; + + default: + printf("Keyboard declines to respond to %02x\n", input); + break; + } + } + } + +private: + HalfDuplexSerial &serial_; + + static constexpr uint8_t HRST = 0b1111'1111; // Keyboard reset. + static constexpr uint8_t RAK1 = 0b1111'1110; // Reset response #1. + static constexpr uint8_t RAK2 = 0b1111'1101; // Reset response #2. + + static constexpr uint8_t RQID = 0b0010'0000; // Request for keyboard ID. + static constexpr uint8_t RQMP = 0b0010'0010; // Request for mouse data. + + static constexpr uint8_t BACK = 0b0011'1111; // Acknowledge for first keyboard data byte pair. + static constexpr uint8_t NACK = 0b0011'0000; // Acknowledge for last keyboard data byte pair, selects scan/mouse mode. + static constexpr uint8_t SACK = 0b0011'0001; // Last data byte acknowledge. + static constexpr uint8_t MACK = 0b0011'0010; // Last data byte acknowledge. + static constexpr uint8_t SMAK = 0b0011'0011; // Last data byte acknowledge. + static constexpr uint8_t PRST = 0b0010'0001; // Does nothing. +}; + +} diff --git a/Machines/Acorn/Archimedes/MemoryController.hpp b/Machines/Acorn/Archimedes/MemoryController.hpp new file mode 100644 index 000000000..ff200b8f5 --- /dev/null +++ b/Machines/Acorn/Archimedes/MemoryController.hpp @@ -0,0 +1,467 @@ +// +// MemoryController.hpp +// Clock Signal +// +// Created by Thomas Harte on 20/03/2024. +// Copyright © 2024 Thomas Harte. All rights reserved. +// + +#pragma once + +#include "InputOutputController.hpp" +#include "Video.hpp" + +#include "../../../InstructionSets/ARM/Registers.hpp" +#include "../../../Outputs/Log.hpp" + +namespace Archimedes { + +/// Provides the mask with all bits set in the range [start, end], where start must be >= end. +template struct BitMask { + static_assert(start >= end); + static constexpr uint32_t value = ((1 << (start + 1)) - 1) - ((1 << end) - 1); +}; +static_assert(BitMask<0, 0>::value == 1); +static_assert(BitMask<1, 1>::value == 2); +static_assert(BitMask<15, 15>::value == 32768); +static_assert(BitMask<15, 0>::value == 0xffff); +static_assert(BitMask<15, 14>::value == 49152); + +/// Models the MEMC, making this the Archimedes bus. Owns various other chips on the bus as a result. +template +struct MemoryController { + MemoryController(InterruptObserverT &delegate) : ioc_(delegate) {} + + int interrupt_mask() const { + return ioc_.interrupt_mask(); + } + + void set_rom(const std::vector &rom) { + std::copy( + rom.begin(), + rom.begin() + static_cast(std::min(rom.size(), rom_.size())), + rom_.begin()); + } + + template + uint32_t aligned(uint32_t address) { + if constexpr (std::is_same_v) { + return address & static_cast(~3); + } + return address; + } + + template + bool write(uint32_t address, IntT source, InstructionSet::ARM::Mode mode, bool) { + // User mode may only _write_ to logically-mapped RAM (subject to further testing below). + if(mode == InstructionSet::ARM::Mode::User && address >= 0x200'0000) { + return false; + } + + switch(write_zones_[(address >> 21) & 31]) { + case Zone::DMAAndMEMC: { + const auto buffer_address = [](uint32_t source) -> uint32_t { + return (source & 0x1fffc0) << 2; + }; + + // The MEMC itself isn't on the data bus; all values below should be taken from `address`. + switch((address >> 17) & 0b111) { + case 0b000: + logger.error().append("TODO: DMA/MEMC Vinit = %04x", address & 0x1fffc0); + return true; + + case 0b001: + logger.error().append("TODO: DMA/MEMC Vstart = %04x", address & 0x1fffc0); + return true; + + case 0b010: + logger.error().append("TODO: DMA/MEMC Vend = %04x", address & 0x1fffc0); + return true; + + case 0b011: + logger.error().append("TODO: DMA/MEMC Cinit = %04x", address & 0x1fffc0); + return true; + + case 0b100: ioc_.sound().set_next_start(buffer_address(address)); return true; + case 0b101: ioc_.sound().set_next_end(buffer_address(address)); return true; + case 0b110: ioc_.sound().swap(); return true; + + case 0b111: + os_mode_ = address & (1 << 12); + audio_dma_enable_ = address & (1 << 11); + video_dma_enable_ = address & (1 << 10); + switch((address >> 8) & 3) { + default: + dynamic_ram_refresh_ = DynamicRAMRefresh::None; + break; + case 0b01: + case 0b11: + dynamic_ram_refresh_ = DynamicRAMRefresh((address >> 8) & 3); + break; + } + high_rom_access_time_ = ROMAccessTime((address >> 6) & 3); + low_rom_access_time_ = ROMAccessTime((address >> 4) & 3); + page_size_ = PageSize((address >> 2) & 3); + + logger.info().append("MEMC Control: %08x -> OS:%d audio:%d video:%d refresh:%d high:%d low:%d size:%d", address, os_mode_, audio_dma_enable_, video_dma_enable_, dynamic_ram_refresh_, high_rom_access_time_, low_rom_access_time_, page_size_); + map_dirty_ = true; + return true; + } + } break; + + case Zone::LogicallyMappedRAM: { + const auto item = logical_ram(address, mode); + if(!item) { + return false; + } + *item = source; + return true; + } break; + + case Zone::IOControllers: + // TODO: have I overrestricted the value type for the IOC area? + ioc_.write(address, uint8_t(source)); + return true; + + case Zone::VideoController: + // TODO: handle byte writes correctly. + vidc_.write(source); + break; + + case Zone::PhysicallyMappedRAM: + physical_ram(address) = source; + return true; + + case Zone::AddressTranslator: +// printf("Translator write at %08x; replaces %08x\n", address, pages_[address & 0x7f]); + pages_[address & 0x7f] = address; + map_dirty_ = true; + break; + + default: +// printf("TODO: write of %08x to %08x [%lu]\n", source, address, sizeof(IntT)); + break; + } + + return true; + } + + template + bool read(uint32_t address, IntT &source, InstructionSet::ARM::Mode mode, bool) { + // User mode may only read logically-maped RAM and ROM. + if(mode == InstructionSet::ARM::Mode::User && address >= 0x200'0000 && address < 0x380'0000) { + return false; + } + + switch (read_zones_[(address >> 21) & 31]) { + case Zone::PhysicallyMappedRAM: + source = physical_ram(address); + return true; + + case Zone::LogicallyMappedRAM: { + if(!has_moved_rom_) { // TODO: maintain this state in the zones table. + source = high_rom(address); + return true; + } + + const auto item = logical_ram(address, mode); + if(!item) { + return false; + } + source = *item; + return true; + } break; + + case Zone::LowROM: +// logger.error().append("TODO: Low ROM read from %08x", address); + source = IntT(~0); + return true; + + case Zone::HighROM: + // Real test is: require A24=A25=0, then A25=1. + // TODO: as above, move this test into the zones tables. + has_moved_rom_ = true; + source = high_rom(address); + return true; + + case Zone::IOControllers: { + if constexpr (std::is_same_v) { + ioc_.read(address, source); + return true; + } else { + // TODO: generalise this adaptation of an 8-bit device to the 32-bit bus, which probably isn't right anyway. + uint8_t value; + ioc_.read(address, value); + source = value; + return true; + } + } + + default: + logger.error().append("TODO: read from %08x", address); + break; + } + + source = 0; + return false; + } + + void tick_timers() { ioc_.tick_timers(); } + void tick_audio() { + // TODO: does disabling audio DMA pause output, or leave it ticking and merely + // stop allowing it to use the bus? + ioc_.sound().tick(); + } + + private: + Log::Logger logger; + + enum class Zone { + LogicallyMappedRAM, + PhysicallyMappedRAM, + IOControllers, + LowROM, + HighROM, + VideoController, + DMAAndMEMC, + AddressTranslator, + }; + static std::array zones(bool is_read) { + std::array zones{}; + for(size_t c = 0; c < zones.size(); c++) { + const auto address = c << 21; + if(address < 0x200'0000) { + zones[c] = Zone::LogicallyMappedRAM; + } else if(address < 0x300'0000) { + zones[c] = Zone::PhysicallyMappedRAM; + } else if(address < 0x340'0000) { + zones[c] = Zone::IOControllers; + } else if(address < 0x360'0000) { + zones[c] = is_read ? Zone::LowROM : Zone::VideoController; + } else if(address < 0x380'0000) { + zones[c] = is_read ? Zone::LowROM : Zone::DMAAndMEMC; + } else { + zones[c] = is_read ? Zone::HighROM : Zone::AddressTranslator; + } + } + return zones; + } + + bool has_moved_rom_ = false; + std::array ram_{}; + std::array rom_; + InputOutputController ioc_; + Video vidc_; + + template + IntT &physical_ram(uint32_t address) { + address = aligned(address); + address &= (ram_.size() - 1); + return *reinterpret_cast(&ram_[address]); + } + + template + IntT &high_rom(uint32_t address) { + address = aligned(address); + return *reinterpret_cast(&rom_[address & (rom_.size() - 1)]); + } + + const std::array read_zones_ = zones(true); + const std::array write_zones_ = zones(false); + + // Control register values. + bool os_mode_ = false; + bool audio_dma_enable_ = false; + bool video_dma_enable_ = false; // "Unaffected" by reset, so here picked arbitrarily. + + enum class DynamicRAMRefresh { + None = 0b00, + DuringFlyback = 0b01, + Continuous = 0b11, + } dynamic_ram_refresh_ = DynamicRAMRefresh::None; // State at reset is undefined; constrain to a valid enum value. + + enum class ROMAccessTime { + ns450 = 0b00, + ns325 = 0b01, + ns200 = 0b10, + ns200with60nsNibble = 0b11, + } high_rom_access_time_ = ROMAccessTime::ns450, low_rom_access_time_ = ROMAccessTime::ns450; + + enum class PageSize { + kb4 = 0b00, + kb8 = 0b01, + kb16 = 0b10, + kb32 = 0b11, + } page_size_ = PageSize::kb4; + + // Address translator. + // + // MEMC contains one entry per a physical page number, indicating where it goes logically. + // Any logical access is tested against all 128 mappings. So that's backwards compared to + // the ideal for an emulator, which would map from logical to physical, even if a lot more + // compact — there are always 128 physical pages; there are up to 8192 logical pages. + // + // So captured here are both the physical -> logical map as representative of the real + // hardware, and the reverse logical -> physical map, which is built (and rebuilt, and rebuilt) + // from the other. + + // Physical to logical mapping. + std::array pages_{}; + + // Logical to physical mapping. + struct MappedPage { + uint8_t *target = nullptr; + uint8_t protection_level = 0; + }; + std::array mapping_; + bool map_dirty_ = true; + + template + IntT *logical_ram(uint32_t address, InstructionSet::ARM::Mode mode) { + // Possibly TODO: this recompute-if-dirty flag is supposed to ameliorate for an expensive + // mapping process. It can be eliminated when the process is improved. + if(map_dirty_) { + update_mapping(); + map_dirty_ = false; + } + + address = aligned(address); + address &= 0x1ff'ffff; + size_t page; + + // TODO: eliminate switch here. + switch(page_size_) { + default: + case PageSize::kb4: + page = address >> 12; + address &= 0x0fff; + break; + case PageSize::kb8: + page = address >> 13; + address &= 0x1fff; + break; + case PageSize::kb16: + page = address >> 14; + address &= 0x3fff; + break; + case PageSize::kb32: + page = address >> 15; + address &= 0x7fff; + break; + } + + if(!mapping_[page].target) { + return nullptr; + } + + // TODO: eliminate switch here. + // Top of my head idea: is_read, is_user and is_os_mode make three bits, so + // keep a one-byte bitmap of permitted accesses rather than the raw protection + // level? + switch(mapping_[page].protection_level) { + case 0b00: break; + case 0b01: + if(!is_read && mode == InstructionSet::ARM::Mode::User) { + return nullptr; + } + break; + default: + if(mode == InstructionSet::ARM::Mode::User) { + return nullptr; + } + if(!is_read && !os_mode_) { + return nullptr; + } + break; + } + + return reinterpret_cast(mapping_[page].target + address); + } + + void update_mapping() { + // For each physical page, project it into logical space. + switch(page_size_) { + default: + case PageSize::kb4: update_mapping(); break; + case PageSize::kb8: update_mapping(); break; + case PageSize::kb16: update_mapping(); break; + case PageSize::kb32: update_mapping(); break; + } + } + + template + void update_mapping() { + // Clear all logical mappings. + std::fill(mapping_.begin(), mapping_.end(), MappedPage{}); + + // For each physical page, project it into logical space + // and store it. + for(const auto page: pages_) { + uint32_t physical, logical; + + switch(size) { + case PageSize::kb4: + // 4kb: + // A[6:0] -> PPN[6:0] + // A[11:10] -> LPN[12:11]; A[22:12] -> LPN[10:0] i.e. 8192 logical pages + physical = page & BitMask<6, 0>::value; + + physical <<= 12; + + logical = (page & BitMask<11, 10>::value) << 1; + logical |= (page & BitMask<22, 12>::value) >> 12; + break; + + case PageSize::kb8: + // 8kb: + // A[0] -> PPN[6]; A[6:1] -> PPN[5:0] + // A[11:10] -> LPN[11:10]; A[22:13] -> LPN[9:0] i.e. 4096 logical pages + physical = (page & BitMask<0, 0>::value) << 6; + physical |= (page & BitMask<6, 1>::value) >> 1; + + physical <<= 13; + + logical = page & BitMask<11, 10>::value; + logical |= (page & BitMask<22, 13>::value) >> 13; + break; + + case PageSize::kb16: + // 16kb: + // A[1:0] -> PPN[6:5]; A[6:2] -> PPN[4:0] + // A[11:10] -> LPN[10:9]; A[22:14] -> LPN[8:0] i.e. 2048 logical pages + physical = (page & BitMask<1, 0>::value) << 5; + physical |= (page & BitMask<6, 2>::value) >> 2; + + physical <<= 14; + + logical = (page & BitMask<11, 10>::value) >> 1; + logical |= (page & BitMask<22, 14>::value) >> 14; + break; + + case PageSize::kb32: + // 32kb: + // A[1] -> PPN[6]; A[2] -> PPN[5]; A[0] -> PPN[4]; A[6:3] -> PPN[3:0] + // A[11:10] -> LPN[9:8]; A[22:15] -> LPN[7:0] i.e. 1024 logical pages + physical = (page & BitMask<1, 1>::value) << 5; + physical |= (page & BitMask<2, 2>::value) << 3; + physical |= (page & BitMask<0, 0>::value) << 4; + physical |= (page & BitMask<6, 3>::value) >> 3; + + physical <<= 15; + + logical = (page & BitMask<11, 10>::value) >> 2; + logical |= (page & BitMask<22, 15>::value) >> 15; + break; + } + +// printf("%08x => physical %d -> logical %d\n", page, (physical >> 15), logical); + + // TODO: consider clashes. + // TODO: what if there's less than 4mb present? + mapping_[logical].target = &ram_[physical]; + mapping_[logical].protection_level = (page >> 8) & 3; + } + } +}; + +} diff --git a/Machines/Acorn/Archimedes/Sound.hpp b/Machines/Acorn/Archimedes/Sound.hpp new file mode 100644 index 000000000..aad9bd1f0 --- /dev/null +++ b/Machines/Acorn/Archimedes/Sound.hpp @@ -0,0 +1,69 @@ +// +// Audio.hpp +// Clock Signal +// +// Created by Thomas Harte on 20/03/2024. +// Copyright © 2024 Thomas Harte. All rights reserved. +// + +#pragma once + +namespace Archimedes { + +template +struct Sound { + Sound(InterruptObserverT &observer) : observer_(observer) {} + + void set_next_end(uint32_t value) { + next_.end = value; + } + + void set_next_start(uint32_t value) { + next_.start = value; + set_buffer_valid(true); // My guess: this is triggered on next buffer start write. + } + + bool interrupt() const { + return !next_buffer_valid_; + } + + void swap() { + current_.start = next_.start; + std::swap(current_.end, next_.end); + set_buffer_valid(false); + halted_ = false; + } + + void tick() { + if(halted_) { + return; + } + + current_.start += 16; + if(current_.start == current_.end) { + if(next_buffer_valid_) { + swap(); + } else { + halted_ = true; + } + } + } + +private: + void set_buffer_valid(bool valid) { + next_buffer_valid_ = valid; + observer_.update_sound_interrupt(); + } + + bool next_buffer_valid_ = false; + bool halted_ = true; // This is a bit of a guess. + + struct Buffer { + uint32_t start = 0, end = 0; + }; + Buffer current_, next_; + + InterruptObserverT &observer_; +}; + +} diff --git a/Machines/Acorn/Archimedes/Video.hpp b/Machines/Acorn/Archimedes/Video.hpp new file mode 100644 index 000000000..3a69d0c03 --- /dev/null +++ b/Machines/Acorn/Archimedes/Video.hpp @@ -0,0 +1,110 @@ +// +// Video.hpp +// Clock Signal +// +// Created by Thomas Harte on 20/03/2024. +// Copyright © 2024 Thomas Harte. All rights reserved. +// + +#pragma once + +#include "../../../Outputs/Log.hpp" + +#include + +namespace Archimedes { + +struct Video { + void write(uint32_t value) { + const auto target = (value >> 24) & 0xfc; + + switch(target) { + case 0x00: case 0x04: case 0x08: case 0x0c: + case 0x10: case 0x14: case 0x18: case 0x1c: + case 0x20: case 0x24: case 0x28: case 0x2c: + case 0x30: case 0x34: case 0x38: case 0x3c: + logger.error().append("TODO: Video palette logical colour %d to %03x", (target >> 2), value & 0x1fff); + break; + + case 0x40: + logger.error().append("TODO: Video border colour to %03x", value & 0x1fff); + break; + + case 0x44: case 0x48: case 0x4c: + logger.error().append("TODO: Cursor colour %d to %03x", (target - 0x44) >> 2, value & 0x1fff); + break; + + case 0x60: case 0x64: case 0x68: case 0x6c: + case 0x70: case 0x74: case 0x78: case 0x7c: + logger.error().append("TODO: Stereo image register %d to %03x", (target - 0x60) >> 2, value & 0x7); + break; + + case 0x80: + logger.error().append("TODO: Video horizontal period: %d", (value >> 14) & 0x3ff); + break; + case 0x84: + logger.error().append("TODO: Video horizontal sync width: %d", (value >> 14) & 0x3ff); + break; + case 0x88: + logger.error().append("TODO: Video horizontal border start: %d", (value >> 14) & 0x3ff); + break; + case 0x8c: + logger.error().append("TODO: Video horizontal display start: %d", (value >> 14) & 0x3ff); + break; + case 0x90: + logger.error().append("TODO: Video horizontal display end: %d", (value >> 14) & 0x3ff); + break; + case 0x94: + logger.error().append("TODO: Video horizontal border end: %d", (value >> 14) & 0x3ff); + break; + case 0x98: + logger.error().append("TODO: Video horizontal cursor end: %d", (value >> 14) & 0x3ff); + break; + case 0x9c: + logger.error().append("TODO: Video horizontal interlace: %d", (value >> 14) & 0x3ff); + break; + + case 0xa0: + logger.error().append("TODO: Video vertical period: %d", (value >> 14) & 0x3ff); + break; + case 0xa4: + logger.error().append("TODO: Video vertical sync width: %d", (value >> 14) & 0x3ff); + break; + case 0xa8: + logger.error().append("TODO: Video vertical border start: %d", (value >> 14) & 0x3ff); + break; + case 0xac: + logger.error().append("TODO: Video vertical display start: %d", (value >> 14) & 0x3ff); + break; + case 0xb0: + logger.error().append("TODO: Video vertical display end: %d", (value >> 14) & 0x3ff); + break; + case 0xb4: + logger.error().append("TODO: Video vertical border end: %d", (value >> 14) & 0x3ff); + break; + case 0xb8: + logger.error().append("TODO: Video vertical cursor start: %d", (value >> 14) & 0x3ff); + break; + case 0xbc: + logger.error().append("TODO: Video vertical cursor end: %d", (value >> 14) & 0x3ff); + break; + + case 0xc0: + logger.error().append("TODO: Sound frequency: %d", value & 0x7f); + break; + + case 0xe0: + logger.error().append("TODO: video control: %08x", value); + break; + + default: + logger.error().append("TODO: unrecognised VIDC write of %08x", value); + break; + } + } + +private: + Log::Logger logger; +}; + +} diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index f74c8fc91..54a36d96d 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -1808,6 +1808,13 @@ 4BA9C3CF1D8164A9002DDB61 /* MediaTarget.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MediaTarget.hpp; sourceTree = ""; }; 4BAA167B21582B1D008A3276 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = ""; }; 4BAB1E522BA9D9950002C9B9 /* Disassembler.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Disassembler.hpp; sourceTree = ""; }; + 4BAB1E532BAB5B040002C9B9 /* Sound.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Sound.hpp; sourceTree = ""; }; + 4BAB1E542BAB5B3F0002C9B9 /* Keyboard.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Keyboard.hpp; sourceTree = ""; }; + 4BAB1E552BAB5B6D0002C9B9 /* HalfDuplexSerial.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = HalfDuplexSerial.hpp; sourceTree = ""; }; + 4BAB1E562BAB5BC60002C9B9 /* Video.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Video.hpp; sourceTree = ""; }; + 4BAB1E582BAB5C210002C9B9 /* MemoryController.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MemoryController.hpp; sourceTree = ""; }; + 4BAB1E592BAB5CB90002C9B9 /* CMOSRAM.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = CMOSRAM.hpp; sourceTree = ""; }; + 4BAB1E5A2BAB5F400002C9B9 /* InputOutputController.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = InputOutputController.hpp; sourceTree = ""; }; 4BAB62AC1D3272D200DF5BA0 /* Disk.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Disk.hpp; sourceTree = ""; }; 4BAB62AE1D32730D00DF5BA0 /* Storage.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Storage.hpp; sourceTree = ""; }; 4BAF2B4C2004580C00480230 /* DMK.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = DMK.cpp; sourceTree = ""; }; @@ -4394,6 +4401,13 @@ children = ( 4BB505842B9634F30031C43C /* Archimedes.cpp */, 4BB505852B9634F30031C43C /* Archimedes.hpp */, + 4BAB1E592BAB5CB90002C9B9 /* CMOSRAM.hpp */, + 4BAB1E552BAB5B6D0002C9B9 /* HalfDuplexSerial.hpp */, + 4BAB1E5A2BAB5F400002C9B9 /* InputOutputController.hpp */, + 4BAB1E542BAB5B3F0002C9B9 /* Keyboard.hpp */, + 4BAB1E582BAB5C210002C9B9 /* MemoryController.hpp */, + 4BAB1E532BAB5B040002C9B9 /* Sound.hpp */, + 4BAB1E562BAB5BC60002C9B9 /* Video.hpp */, ); path = Archimedes; sourceTree = ""; diff --git a/Outputs/Log.hpp b/Outputs/Log.hpp index a0827d4fa..22b02f8d0 100644 --- a/Outputs/Log.hpp +++ b/Outputs/Log.hpp @@ -25,6 +25,9 @@ enum class Source { AmigaBlitter, AppleIISCSICard, Archimedes, + ARMIOC, + ARMMEMC, + ARMVIDC, AtariST, AtariSTDMAController, CommodoreStaticAnalyser, @@ -90,6 +93,9 @@ constexpr const char *prefix(Source source) { case Source::AmigaDisk: return "Disk"; case Source::AppleIISCSICard: return "SCSI card"; case Source::Archimedes: return "Archimedes"; + case Source::ARMIOC: return "IOC"; + case Source::ARMMEMC: return "MEMC"; + case Source::ARMVIDC: return "VIDC"; case Source::AtariST: return "AtariST"; case Source::AtariSTDMAController: return "DMA"; case Source::CommodoreStaticAnalyser: return "Commodore Static Analyser";