From 59b9e390225b18f964509e065f992bf970037fbd Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 23 Jul 2018 22:14:41 -0400 Subject: [PATCH 01/31] Starts the process of supporting the Apple IIe graphics modes. Albeit that I'm not yet even up on the proper soft switches. --- Machines/AppleII/AppleII.cpp | 3 ++ Machines/AppleII/Video.hpp | 83 +++++++++++++++++++++++++++++++++++- 2 files changed, 84 insertions(+), 2 deletions(-) diff --git a/Machines/AppleII/AppleII.cpp b/Machines/AppleII/AppleII.cpp index f6ed1eb20..7a7c229b2 100644 --- a/Machines/AppleII/AppleII.cpp +++ b/Machines/AppleII/AppleII.cpp @@ -62,6 +62,9 @@ class ConcreteMachine: uint8_t perform_read(uint16_t address) { return ram_[address]; } + uint16_t perform_aux_read(uint16_t address) { + return static_cast(ram_[address] | (ram_[address] << 8)); + } private: uint8_t *ram_; diff --git a/Machines/AppleII/Video.hpp b/Machines/AppleII/Video.hpp index ee7aa2607..d69573e7f 100644 --- a/Machines/AppleII/Video.hpp +++ b/Machines/AppleII/Video.hpp @@ -19,9 +19,21 @@ namespace Video { class BusHandler { public: + /*! + Reads an 8-bit value from the ordinary II/II+ memory pool. + */ uint8_t perform_read(uint16_t address) { return 0xff; } + + /*! + Reads two 8-bit values, from the same address — one from + main RAM, one from auxiliary. Should return as + (main) | (aux << 8). + */ + uint16_t perform_aux_read(uint16_t address) { + return 0xffff; + } }; class VideoBase { @@ -55,8 +67,11 @@ class VideoBase { enum class GraphicsMode { LowRes, + DoubleLowRes, HighRes, - Text + DoubleHighRes, + Text, + DoubleText } graphics_mode_ = GraphicsMode::LowRes; bool use_graphics_mode_ = false; bool mixed_mode_ = false; @@ -153,11 +168,51 @@ template class Video: public VideoBase { pixel_pointer_[4] = character_pattern & 0x04; pixel_pointer_[5] = character_pattern & 0x02; pixel_pointer_[6] = character_pattern & 0x01; - graphics_carry_ = character_pattern & 0x40; + graphics_carry_ = character_pattern & 0x01; pixel_pointer_ += 7; } } break; + case GraphicsMode::DoubleText: { + const uint8_t inverses[] = { + 0xff, + static_cast((flash_ / flash_length) * 0xff), + 0x00, + 0x00 + }; + for(int c = column_; c < pixel_end; ++c) { + const uint16_t characters = bus_handler_.perform_aux_read(static_cast(text_address + c)); + const std::size_t character_addresses[2] = { + static_cast(((characters & 0x3f) << 3) + pixel_row), + static_cast((((characters >> 8) & 0x3f) << 3) + pixel_row), + }; + + const uint8_t character_patterns[2] = { + static_cast(character_rom_[character_addresses[0]] ^ inverses[(characters >> 6) & 3]), + static_cast(character_rom_[character_addresses[1]] ^ inverses[(characters >> 14) & 3]), + }; + + // The character ROM is output MSB to LSB rather than LSB to MSB. + pixel_pointer_[0] = character_patterns[0] & 0x40; + pixel_pointer_[1] = character_patterns[0] & 0x20; + pixel_pointer_[2] = character_patterns[0] & 0x10; + pixel_pointer_[3] = character_patterns[0] & 0x08; + pixel_pointer_[4] = character_patterns[0] & 0x04; + pixel_pointer_[5] = character_patterns[0] & 0x02; + pixel_pointer_[6] = character_patterns[0] & 0x01; + pixel_pointer_[7] = character_patterns[1] & 0x40; + pixel_pointer_[8] = character_patterns[1] & 0x20; + pixel_pointer_[9] = character_patterns[1] & 0x10; + pixel_pointer_[10] = character_patterns[1] & 0x08; + pixel_pointer_[11] = character_patterns[1] & 0x04; + pixel_pointer_[12] = character_patterns[1] & 0x02; + pixel_pointer_[13] = character_patterns[1] & 0x01; + graphics_carry_ = character_patterns[1] & 0x01; + pixel_pointer_ += 14; + } + } break; + + case GraphicsMode::DoubleLowRes: case GraphicsMode::LowRes: { const int row_shift = (row_&4); // TODO: decompose into two loops, possibly. @@ -212,6 +267,30 @@ template class Video: public VideoBase { pixel_pointer_ += 14; } } break; + + case GraphicsMode::DoubleHighRes: { + const uint16_t graphics_address = static_cast(((video_page_+1) * 0x2000) + row_address + ((pixel_row&7) << 10)); + for(int c = column_; c < pixel_end; ++c) { + const uint16_t graphic = bus_handler_.perform_aux_read(static_cast(graphics_address + c)); + + pixel_pointer_[0] = graphic & 0x01; + pixel_pointer_[1] = graphic & 0x02; + pixel_pointer_[2] = graphic & 0x04; + pixel_pointer_[3] = graphic & 0x08; + pixel_pointer_[4] = graphic & 0x10; + pixel_pointer_[5] = graphic & 0x20; + pixel_pointer_[6] = graphic & 0x40; + pixel_pointer_[7] = (graphic >> 8) & 0x01; + pixel_pointer_[8] = (graphic >> 8) & 0x02; + pixel_pointer_[9] = (graphic >> 8) & 0x04; + pixel_pointer_[10] = (graphic >> 8) & 0x08; + pixel_pointer_[11] = (graphic >> 8) & 0x10; + pixel_pointer_[12] = (graphic >> 8) & 0x20; + pixel_pointer_[13] = (graphic >> 8) & 0x40; + graphics_carry_ = (graphic >> 8) & 0x40; + pixel_pointer_ += 14; + } + } break; } if(ending_column >= 40) { From ede2696a77f9d1bf269935b945638e195a843ba8 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 24 Jul 2018 22:15:42 -0400 Subject: [PATCH 02/31] Edges further towards implementing the IIe video subsystem. All video-specific switches are in place, and mostly honoured, and a IIe machine configuration is advertised at least. --- Analyser/Static/AppleII/Target.hpp | 3 +- Machines/AppleII/AppleII.cpp | 61 +++++++--- Machines/AppleII/Video.cpp | 67 +++++++++-- Machines/AppleII/Video.hpp | 176 +++++++++++++++++++++++++---- 4 files changed, 256 insertions(+), 51 deletions(-) diff --git a/Analyser/Static/AppleII/Target.hpp b/Analyser/Static/AppleII/Target.hpp index 6ac5616ad..adc4e3b5a 100644 --- a/Analyser/Static/AppleII/Target.hpp +++ b/Analyser/Static/AppleII/Target.hpp @@ -18,7 +18,8 @@ namespace AppleII { struct Target: public ::Analyser::Static::Target { enum class Model { II, - IIplus + IIplus, + IIe }; enum class DiskController { None, diff --git a/Machines/AppleII/AppleII.cpp b/Machines/AppleII/AppleII.cpp index 7a7c229b2..b87655287 100644 --- a/Machines/AppleII/AppleII.cpp +++ b/Machines/AppleII/AppleII.cpp @@ -43,7 +43,7 @@ std::vector> AppleII::get_options() { namespace { -class ConcreteMachine: +template class ConcreteMachine: public CRTMachine::Machine, public MediaTarget::Machine, public KeyboardMachine::Machine, @@ -494,9 +494,36 @@ class ConcreteMachine: *value |= 0x80; } } break; + + // The IIe-only state reads follow... + case 0xc018: if(is_iie) *value = (*value & 0x7f) | video_->get_80_store() ? 0x80 : 0x00; break; + case 0xc01a: if(is_iie) *value = (*value & 0x7f) | video_->get_text() ? 0x80 : 0x00; break; + case 0xc01b: if(is_iie) *value = (*value & 0x7f) | video_->get_mixed() ? 0x80 : 0x00; break; + case 0xc01c: if(is_iie) *value = (*value & 0x7f) | video_->get_page2() ? 0x80 : 0x00; break; + case 0xc01d: if(is_iie) *value = (*value & 0x7f) | video_->get_high_resolution() ? 0x80 : 0x00; break; + case 0xc01e: if(is_iie) *value = (*value & 0x7f) | video_->get_alternative_character_set() ? 0x80 : 0x00; break; + case 0xc01f: if(is_iie) *value = (*value & 0x7f) | video_->get_80_columns() ? 0x80 : 0x00; break; + case 0xc07f: if(is_iie) *value = (*value & 0x7f) | video_->get_double_high_resolution() ? 0x80 : 0x00; break; } } else { - // Write-only switches. + // Write-only switches. All IIe as currently implemented. + if(is_iie) { + switch(address) { + default: break; + + case 0xc00e: + case 0xc00f: video_->set_alternative_character_set(!!(address&1)); break; + + case 0xc00c: + case 0xc00d: video_->set_80_columns(!!(address&1)); break; + + case 0xc000: + case 0xc001: video_->set_80_store(!!(address&1)); break; + + case 0xc05e: + case 0xc05f: video_->set_double_high_resolution(!(address&1)); break; + } + } } break; @@ -513,14 +540,14 @@ class ConcreteMachine: } break; /* Read-write switches. */ - case 0xc050: update_video(); video_->set_graphics_mode(); break; - case 0xc051: update_video(); video_->set_text_mode(); break; - case 0xc052: update_video(); video_->set_mixed_mode(false); break; - case 0xc053: update_video(); video_->set_mixed_mode(true); break; - case 0xc054: update_video(); video_->set_video_page(0); break; - case 0xc055: update_video(); video_->set_video_page(1); break; - case 0xc056: update_video(); video_->set_low_resolution(); break; - case 0xc057: update_video(); video_->set_high_resolution(); break; + case 0xc050: update_video(); video_->set_text(false); break; + case 0xc051: update_video(); video_->set_text(true); break; + case 0xc052: update_video(); video_->set_mixed(false); break; + case 0xc053: update_video(); video_->set_mixed(true); break; + case 0xc054: update_video(); video_->set_page2(false); break; + case 0xc055: update_video(); video_->set_page2(true); break; + case 0xc056: update_video(); video_->set_high_resolution(false); break; + case 0xc057: update_video(); video_->set_high_resolution(true); break; case 0xc010: keyboard_input_ &= 0x7f; @@ -646,9 +673,11 @@ class ConcreteMachine: // If no ASCII value is supplied, look for a few special cases. if(!value) { switch(key) { - case Key::Left: value = 8; break; - case Key::Right: value = 21; break; - case Key::Down: value = 10; break; + case Key::Left: value = 0x08; break; + case Key::Right: value = 0x15; break; + case Key::Down: value = 0x0a; break; + case Key::Up: value = 0x0b; break; + case Key::BackSpace: value = 0x7f; break; default: break; } } @@ -718,7 +747,11 @@ using namespace AppleII; Machine *Machine::AppleII(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) { using Target = Analyser::Static::AppleII::Target; const Target *const appleii_target = dynamic_cast(target); - return new ConcreteMachine(*appleii_target, rom_fetcher); + if(appleii_target->model == Target::Model::IIe) { + return new ConcreteMachine(*appleii_target, rom_fetcher); + } else { + return new ConcreteMachine(*appleii_target, rom_fetcher); + } } Machine::~Machine() {} diff --git a/Machines/AppleII/Video.cpp b/Machines/AppleII/Video.cpp index 552bad130..e5dde24e2 100644 --- a/Machines/AppleII/Video.cpp +++ b/Machines/AppleII/Video.cpp @@ -31,28 +31,71 @@ Outputs::CRT::CRT *VideoBase::get_crt() { return crt_.get(); } -void VideoBase::set_graphics_mode() { - use_graphics_mode_ = true; +/* + Rote setters and getters. +*/ +void VideoBase::set_alternative_character_set(bool alternative_character_set) { + alternative_character_set_ = alternative_character_set; } -void VideoBase::set_text_mode() { - use_graphics_mode_ = false; +bool VideoBase::get_alternative_character_set() { + return alternative_character_set_; } -void VideoBase::set_mixed_mode(bool mixed_mode) { - mixed_mode_ = mixed_mode; +void VideoBase::set_80_columns(bool columns_80) { + columns_80_ = columns_80; } -void VideoBase::set_video_page(int page) { - video_page_ = page; +bool VideoBase::get_80_columns() { + return columns_80_; } -void VideoBase::set_low_resolution() { - graphics_mode_ = GraphicsMode::LowRes; +void VideoBase::set_80_store(bool store_80) { + store_80_ = store_80; } -void VideoBase::set_high_resolution() { - graphics_mode_ = GraphicsMode::HighRes; +bool VideoBase::get_80_store() { + return store_80_; +} + +void VideoBase::set_page2(bool page2) { + page2_ = page2;; +} + +bool VideoBase::get_page2() { + return page2_; +} + +void VideoBase::set_text(bool text) { + text_ = text; +} + +bool VideoBase::get_text() { + return text_; +} + +void VideoBase::set_mixed(bool mixed) { + mixed_ = mixed; +} + +bool VideoBase::get_mixed() { + return mixed_; +} + +void VideoBase::set_high_resolution(bool high_resolution) { + high_resolution_ = high_resolution; +} + +bool VideoBase::get_high_resolution() { + return high_resolution_; +} + +void VideoBase::set_double_high_resolution(bool double_high_resolution) { + double_high_resolution_ = double_high_resolution; +} + +bool VideoBase::get_double_high_resolution() { + return double_high_resolution_; } void VideoBase::set_character_rom(const std::vector &character_rom) { diff --git a/Machines/AppleII/Video.hpp b/Machines/AppleII/Video.hpp index d69573e7f..f453d162f 100644 --- a/Machines/AppleII/Video.hpp +++ b/Machines/AppleII/Video.hpp @@ -43,13 +43,107 @@ class VideoBase { /// @returns The CRT this video feed is feeding. Outputs::CRT::CRT *get_crt(); - // Inputs for the various soft switches. - void set_graphics_mode(); - void set_text_mode(); - void set_mixed_mode(bool); - void set_video_page(int); - void set_low_resolution(); - void set_high_resolution(); + /* + Descriptions for the setters below are taken verbatim from + the Apple IIe Technical Reference. Addresses are the conventional + locations within the Apple II memory map. Only those which affect + video output are implemented here. + + Those registers which don't exist on a II/II+ are marked. + */ + + /*! + Setter for ALTCHAR ($C00E/$C00F; triggers on write only): + + * Off: display text using primary character set. + * On: display text using alternate character set. + + Doesn't exist on a II/II+. + */ + void set_alternative_character_set(bool); + bool get_alternative_character_set(); + + /*! + Setter for 80COL ($C00C/$C00D; triggers on write only). + + * Off: display 40 columns. + * On: display 80 columns. + + Doesn't exist on a II/II+. + */ + void set_80_columns(bool); + bool get_80_columns(); + + /*! + Setter for 80STORE ($C000/$C001; triggers on write only). + + * Off: cause PAGE2 to select auxiliary RAM. + * On: cause PAGE2 to switch main RAM areas. + + Doesn't exist on a II/II+. + */ + void set_80_store(bool); + bool get_80_store(); + + /*! + Setter for PAGE2 ($C054/$C055; triggers on read or write). + + * Off: select Page 1. + * On: select Page 2 or, if 80STORE on, Page 1 in auxiliary memory. + + 80STORE doesn't exist on a II/II+; therefore this always selects + either Page 1 or Page 2 on those machines. + */ + void set_page2(bool); + bool get_page2(); + + /*! + Setter for TEXT ($C050/$C051; triggers on read or write). + + * Off: display graphics or, if MIXED on, mixed. + * On: display text. + */ + void set_text(bool); + bool get_text(); + + /*! + Setter for MIXED ($C052/$C053; triggers on read or write). + + * Off: display only text or only graphics. + * On: if TEXT off, display text and graphics. + */ + void set_mixed(bool); + bool get_mixed(); + + /*! + Setter for HIRES ($C056/$C057; triggers on read or write). + + * Off: if TEXT off, display low-resolution graphics. + * On: if TEXT off, display high-resolution or, if DHIRES on, double high-resolution graphics. + + DHIRES doesn't exist on a II/II+; therefore this always selects + either high- or low-resolution graphics on those machines. + + Despite Apple's documentation, the IIe also supports double low-resolution + graphics, which are the 80-column analogue to ordinary low-resolution 40-column + low-resolution graphics. + */ + void set_high_resolution(bool); + bool get_high_resolution(); + + /*! + Setter for DHIRES ($C05E/$C05F; triggers on write only). + + * On: turn on double-high resolution. + * Off: turn off double-high resolution. + + DHIRES doesn't exist on a II/II+. On the IIe there is another + register usually grouped with the graphics setters called IOUDIS + that affects visibility of this switch. But it has no effect on + video, so it's not modelled by this class. + */ + void set_double_high_resolution(bool); + bool get_double_high_resolution(); // Setup for text mode. void set_character_rom(const std::vector &); @@ -57,14 +151,15 @@ class VideoBase { protected: std::unique_ptr crt_; + // State affecting output video stream generation. uint8_t *pixel_pointer_ = nullptr; int pixel_pointer_column_ = 0; bool pixels_are_high_density_ = false; - int video_page_ = 0; + // State affecting logical state. int row_ = 0, column_ = 0, flash_ = 0; - std::vector character_rom_; + // An enum class GraphicsMode { LowRes, DoubleLowRes, @@ -72,10 +167,28 @@ class VideoBase { DoubleHighRes, Text, DoubleText - } graphics_mode_ = GraphicsMode::LowRes; - bool use_graphics_mode_ = false; - bool mixed_mode_ = false; + }; + bool is_text_mode(GraphicsMode m) { return m >= GraphicsMode::Text; } + + // Various soft-switch values. + bool alternative_character_set_ = false; + bool columns_80_ = false; + bool store_80_ = false; + bool page2_ = false; + bool text_ = true; + bool mixed_ = false; + bool high_resolution_ = false; + bool double_high_resolution_ = false; + + // Graphics carry is the final level output in a fetch window; + // it carries on into the next if it's high resolution with + // the delay bit set. uint8_t graphics_carry_ = 0; + + // This holds a copy of the character ROM. The regular character + // set is assumed to be in the first 64*8 bytes; the alternative + // is in the 128*8 bytes after that. + std::vector character_rom_; }; template class Video: public VideoBase { @@ -123,15 +236,14 @@ template class Video: public VideoBase { crt_->output_sync(static_cast(cycles_this_line) * 14); } } else { - const GraphicsMode line_mode = use_graphics_mode_ ? graphics_mode_ : GraphicsMode::Text; + const GraphicsMode line_mode = graphics_mode(row_); // The first 40 columns are submitted to the CRT only upon completion; // they'll be either graphics or blank, depending on which side we are // of line 192. if(column_ < 40) { if(row_ < 192) { - GraphicsMode pixel_mode = (!mixed_mode_ || row_ < 160) ? line_mode : GraphicsMode::Text; - bool requires_high_density = pixel_mode != GraphicsMode::Text; + const bool requires_high_density = line_mode != GraphicsMode::Text; if(!column_ || requires_high_density != pixels_are_high_density_) { if(column_) output_data_to_column(column_); pixel_pointer_ = crt_->allocate_write_area(561); @@ -144,9 +256,9 @@ template class Video: public VideoBase { const int character_row = row_ >> 3; const int pixel_row = row_ & 7; const uint16_t row_address = static_cast((character_row >> 3) * 40 + ((character_row&7) << 7)); - const uint16_t text_address = static_cast(((video_page_+1) * 0x400) + row_address); + const uint16_t text_address = static_cast(((video_page()+1) * 0x400) + row_address); - switch(pixel_mode) { + switch(line_mode) { case GraphicsMode::Text: { const uint8_t inverses[] = { 0xff, @@ -239,7 +351,7 @@ template class Video: public VideoBase { } break; case GraphicsMode::HighRes: { - const uint16_t graphics_address = static_cast(((video_page_+1) * 0x2000) + row_address + ((pixel_row&7) << 10)); + const uint16_t graphics_address = static_cast(((video_page()+1) * 0x2000) + row_address + ((pixel_row&7) << 10)); for(int c = column_; c < pixel_end; ++c) { const uint8_t graphic = bus_handler_.perform_read(static_cast(graphics_address + c)); @@ -269,7 +381,7 @@ template class Video: public VideoBase { } break; case GraphicsMode::DoubleHighRes: { - const uint16_t graphics_address = static_cast(((video_page_+1) * 0x2000) + row_address + ((pixel_row&7) << 10)); + const uint16_t graphics_address = static_cast(((video_page()+1) * 0x2000) + row_address + ((pixel_row&7) << 10)); for(int c = column_; c < pixel_end; ++c) { const uint16_t graphic = bus_handler_.perform_aux_read(static_cast(graphics_address + c)); @@ -321,7 +433,7 @@ template class Video: public VideoBase { } int second_blank_start; - if(line_mode != GraphicsMode::Text && (!mixed_mode_ || row_ < 159 || row_ >= 192)) { + if(!is_text_mode(graphics_mode(row_+1))) { const int colour_burst_start = std::max(first_sync_column + sync_length + 1, column_); const int colour_burst_end = std::min(first_sync_column + sync_length + 4, ending_column); if(colour_burst_end > colour_burst_start) { @@ -390,15 +502,31 @@ template class Video: public VideoBase { } private: + GraphicsMode graphics_mode(int row) { + if(text_) return columns_80_ ? GraphicsMode::DoubleText : GraphicsMode::Text; + if(mixed_ && row >= 160 && row < 192) { + return (columns_80_ || double_high_resolution_) ? GraphicsMode::DoubleText : GraphicsMode::Text; + } + if(high_resolution_) { + return double_high_resolution_ ? GraphicsMode::DoubleHighRes : GraphicsMode::HighRes; + } else { + return double_high_resolution_ ? GraphicsMode::DoubleLowRes : GraphicsMode::LowRes; + } + } + + int video_page() { + return (store_80_ || !page2_) ? 0 : 1; + } + uint16_t get_row_address(int row) { const int character_row = row >> 3; const int pixel_row = row & 7; const uint16_t row_address = static_cast((character_row >> 3) * 40 + ((character_row&7) << 7)); - GraphicsMode pixel_mode = ((!mixed_mode_ || row < 160) && use_graphics_mode_) ? graphics_mode_ : GraphicsMode::Text; - return (pixel_mode == GraphicsMode::HighRes) ? - static_cast(((video_page_+1) * 0x2000) + row_address + ((pixel_row&7) << 10)) : - static_cast(((video_page_+1) * 0x400) + row_address); + const GraphicsMode pixel_mode = graphics_mode(row); + return ((pixel_mode == GraphicsMode::HighRes) || (pixel_mode == GraphicsMode::DoubleHighRes)) ? + static_cast(((video_page()+1) * 0x2000) + row_address + ((pixel_row&7) << 10)) : + static_cast(((video_page()+1) * 0x400) + row_address); } static const int flash_length = 8406; From 55a6431fb338ece0faf6ed7e3dd7bddc8153f7a2 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 25 Jul 2018 18:58:34 -0400 Subject: [PATCH 03/31] Puts in enough logic to be able to launch a non-functional IIe. --- Machines/AppleII/AppleII.cpp | 12 ++++++++--- .../Machine/StaticAnalyser/CSStaticAnalyser.h | 3 ++- .../StaticAnalyser/CSStaticAnalyser.mm | 6 +++++- .../Base.lproj/MachinePicker.xib | 21 ++++++++++--------- .../MachinePicker/MachinePicker.swift | 1 + ROMImages/AppleII/readme.txt | 9 ++++++-- 6 files changed, 35 insertions(+), 17 deletions(-) diff --git a/Machines/AppleII/AppleII.cpp b/Machines/AppleII/AppleII.cpp index b87655287..1a08b42d8 100644 --- a/Machines/AppleII/AppleII.cpp +++ b/Machines/AppleII/AppleII.cpp @@ -259,6 +259,7 @@ template class ConcreteMachine: // Pick the required ROMs. using Target = Analyser::Static::AppleII::Target; std::vector rom_names = {"apple2-character.rom"}; + size_t rom_size = 12*1024; switch(target.model) { default: rom_names.push_back("apple2o.rom"); @@ -266,6 +267,10 @@ template class ConcreteMachine: case Target::Model::IIplus: rom_names.push_back("apple2.rom"); break; + case Target::Model::IIe: + rom_size = 15*1024; + rom_names.push_back("apple2e.rom"); + break; } const auto roms = rom_fetcher("AppleII", rom_names); @@ -273,12 +278,13 @@ template class ConcreteMachine: throw ROMMachine::Error::MissingROMs; } - character_rom_ = std::move(*roms[0]); rom_ = std::move(*roms[1]); - if(rom_.size() > 12*1024) { - rom_.erase(rom_.begin(), rom_.begin() + static_cast(rom_.size()) - 12*1024); + if(rom_.size() > rom_size) { + rom_.erase(rom_.begin(), rom_.end() - static_cast(rom_size)); } + character_rom_ = std::move(*roms[0]); + if(target.disk_controller != Target::DiskController::None) { // Apple recommended slot 6 for the (first) Disk II. install_card(6, new AppleII::DiskIICard(rom_fetcher, target.disk_controller == Target::DiskController::SixteenSector)); diff --git a/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.h b/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.h index e183d7217..943656c6c 100644 --- a/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.h +++ b/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.h @@ -12,7 +12,8 @@ typedef NS_ENUM(NSInteger, CSMachineAppleIIModel) { CSMachineAppleIIModelAppleII, - CSMachineAppleIIModelAppleIIPlus + CSMachineAppleIIModelAppleIIPlus, + CSMachineAppleIIModelAppleIIe }; typedef NS_ENUM(NSInteger, CSMachineAppleIIDiskController) { diff --git a/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm b/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm index fee228e7e..29f15a930 100644 --- a/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm +++ b/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm @@ -168,7 +168,11 @@ static Analyser::Static::ZX8081::Target::MemoryModel ZX8081MemoryModelFromSize(K using Target = Analyser::Static::AppleII::Target; std::unique_ptr target(new Target); target->machine = Analyser::Machine::AppleII; - target->model = (model == CSMachineAppleIIModelAppleII) ? Target::Model::II : Target::Model::IIplus; + switch(model) { + default: target->model = Target::Model::II; break; + case CSMachineAppleIIModelAppleIIPlus: target->model = Target::Model::IIplus; break; + case CSMachineAppleIIModelAppleIIe: target->model = Target::Model::IIe; break; + } switch(diskController) { default: case CSMachineAppleIIDiskControllerNone: target->disk_controller = Target::DiskController::None; break; diff --git a/OSBindings/Mac/Clock Signal/MachinePicker/Base.lproj/MachinePicker.xib b/OSBindings/Mac/Clock Signal/MachinePicker/Base.lproj/MachinePicker.xib index 40721d21a..52727ce02 100644 --- a/OSBindings/Mac/Clock Signal/MachinePicker/Base.lproj/MachinePicker.xib +++ b/OSBindings/Mac/Clock Signal/MachinePicker/Base.lproj/MachinePicker.xib @@ -1,8 +1,8 @@ - + - + @@ -64,11 +64,11 @@ Gw - + - + @@ -76,7 +76,7 @@ Gw - + @@ -84,7 +84,7 @@ Gw - + @@ -92,12 +92,13 @@ Gw + - + @@ -128,11 +129,11 @@ Gw - + - + @@ -146,7 +147,7 @@ Gw - + diff --git a/OSBindings/Mac/Clock Signal/MachinePicker/MachinePicker.swift b/OSBindings/Mac/Clock Signal/MachinePicker/MachinePicker.swift index 2bbd83287..da9a669e6 100644 --- a/OSBindings/Mac/Clock Signal/MachinePicker/MachinePicker.swift +++ b/OSBindings/Mac/Clock Signal/MachinePicker/MachinePicker.swift @@ -130,6 +130,7 @@ class MachinePicker: NSObject { var model: CSMachineAppleIIModel = .appleII switch appleIIModelButton!.selectedTag() { case 1: model = .appleIIPlus + case 2: model = .appleIIe case 0: fallthrough default: model = .appleII } diff --git a/ROMImages/AppleII/readme.txt b/ROMImages/AppleII/readme.txt index 397b6905e..6f93565af 100644 --- a/ROMImages/AppleII/readme.txt +++ b/ROMImages/AppleII/readme.txt @@ -2,5 +2,10 @@ ROM files would ordinarily go here; they are copyright Apple so are not included Expected files: -apple2o.rom — a 12kb image of the original Apple II's ROMs. -apple2-character.rom — a 2kb image of the Apple II+'s character ROM. \ No newline at end of file +apple2o.rom — an image at least 12kb big, in which the final 12kb is the original Apple II's ROM. +apple2.rom — an image at least 12kb big, in which the final 12kb is the Apple II+'s ROM. +apple2e.rom — a file at least 15kb big, in which the final 12kb is the main portion of the ROM, that is visible from $D000, and the 3kb before that is the portion that can be paged in from $C100. + +apple2-character.rom — a 2kb image of the Apple IIe's character ROM. + +Apologies for the wackiness around "at least xkb big", it's to allow for use of files such as those on ftp.apple.asimov.net, which tend to be a bunch of other things, then the system ROM. \ No newline at end of file From 5c4506a9db3462d274f6f2724c92d7d9e5ef470e Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 25 Jul 2018 21:43:12 -0400 Subject: [PATCH 04/31] Talks the IIe into proceeding to a beep and an improperly-formed logo. --- Machines/AppleII/AppleII.cpp | 80 +++++++++++++++++++++++++----------- ROMImages/AppleII/readme.txt | 2 +- 2 files changed, 56 insertions(+), 26 deletions(-) diff --git a/Machines/AppleII/AppleII.cpp b/Machines/AppleII/AppleII.cpp index 1a08b42d8..3d8cf16d8 100644 --- a/Machines/AppleII/AppleII.cpp +++ b/Machines/AppleII/AppleII.cpp @@ -143,7 +143,8 @@ template class ConcreteMachine: struct MemoryBlock { uint8_t *read_pointer = nullptr; uint8_t *write_pointer = nullptr; - } memory_blocks_[4]; // The IO page isn't included. + } memory_blocks_[4]; // The IO page isn't included. + MemoryBlock cx_rom_block_; // This is the IO page, for an IIe. // MARK: - The language card. struct { @@ -165,11 +166,16 @@ template class ConcreteMachine: memory_blocks_[2].read_pointer = &ram_[48*1024 + (language_card_.bank1 ? 0x1000 : 0x0000)]; memory_blocks_[3].read_pointer = &ram_[56*1024]; } else { - memory_blocks_[2].read_pointer = rom_.data(); - memory_blocks_[3].read_pointer = rom_.data() + 0x1000; + memory_blocks_[2].read_pointer = rom_.data() + (is_iie ? 3840 : 0); + memory_blocks_[3].read_pointer = memory_blocks_[2].read_pointer + 0x1000; } } + // MARK - The IIe's ROM controls. + bool internal_CX_rom_ = false; + bool slot_C3_rom_ = false; + bool internal_c8_rom_ = false; + // MARK - typing std::unique_ptr string_serialiser_; @@ -268,7 +274,7 @@ template class ConcreteMachine: rom_names.push_back("apple2.rom"); break; case Target::Model::IIe: - rom_size = 15*1024; + rom_size += 3840; rom_names.push_back("apple2e.rom"); break; } @@ -293,6 +299,7 @@ template class ConcreteMachine: // Set up the default memory blocks. memory_blocks_[0].read_pointer = memory_blocks_[0].write_pointer = ram_; memory_blocks_[1].read_pointer = memory_blocks_[1].write_pointer = &ram_[0x200]; + cx_rom_block_.read_pointer = rom_.data(); set_language_card_paging(); insert_media(target.media); @@ -354,7 +361,7 @@ template class ConcreteMachine: block = &memory_blocks_[1]; accessed_address -= 0x200; } - else if(address < 0xd000) block = nullptr; + else if(address < 0xd000) {block = (internal_CX_rom_ && address >= 0xc100) ? &cx_rom_block_: nullptr; accessed_address -= 0xc100; } else if(address < 0xe000) {block = &memory_blocks_[2]; accessed_address -= 0xd000; } else { block = &memory_blocks_[3]; accessed_address -= 0xe000; } @@ -464,7 +471,9 @@ template class ConcreteMachine: if(isReadOperation(operation)) { // Read-only switches. switch(address) { - default: break; + default: + printf("Unknown read from %04x\n", address); + break; case 0xc000: if(string_serialiser_) { @@ -502,6 +511,8 @@ template class ConcreteMachine: } break; // The IIe-only state reads follow... + case 0xc015: if(is_iie) *value = (*value & 0x7f) | internal_CX_rom_ ? 0x80 : 0x00; break; + case 0xc017: if(is_iie) *value = (*value & 0x7f) | slot_C3_rom_ ? 0x80 : 0x00; break; case 0xc018: if(is_iie) *value = (*value & 0x7f) | video_->get_80_store() ? 0x80 : 0x00; break; case 0xc01a: if(is_iie) *value = (*value & 0x7f) | video_->get_text() ? 0x80 : 0x00; break; case 0xc01b: if(is_iie) *value = (*value & 0x7f) | video_->get_mixed() ? 0x80 : 0x00; break; @@ -514,8 +525,16 @@ template class ConcreteMachine: } else { // Write-only switches. All IIe as currently implemented. if(is_iie) { + printf("w %04x\n", address); switch(address) { - default: break; + default: + printf("Unknown write to %04x\n", address); + break; + + case 0xc006: + case 0xc007: internal_CX_rom_ = !!(address&1); break; + case 0xc00a: + case 0xc00b: slot_C3_rom_ = !!(address&1); break; case 0xc00e: case 0xc00f: video_->set_alternative_character_set(!!(address&1)); break; @@ -600,10 +619,10 @@ template class ConcreteMachine: Communication with cards follows. */ - if(address >= 0xc090 && address < 0xc800) { + if(!block && address >= 0xc090 && address < 0xc800) { // If this is a card access, figure out which card is at play before determining // the totality of who needs messaging. - size_t card_number = 0; + int card_number = 0; AppleII::Card::Select select = AppleII::Card::None; if(address >= 0xc100) { @@ -613,6 +632,13 @@ template class ConcreteMachine: */ card_number = (address - 0xc100) >> 8; select = AppleII::Card::Device; + + // If this is a IIe then some of this space may actually be + // remapped to the internal ROM. Check out that condition and, + // if so, label this as card -1. + if(is_iie && slot_C3_rom_ && address >= 0xc300 && address < 0xc400) { + card_number = -1; + } } else { /* Decode the area conventionally used by cards for registers: @@ -622,24 +648,28 @@ template class ConcreteMachine: select = AppleII::Card::IO; } - // If the selected card is a just-in-time card, update the just-in-time cards, - // and then message it specifically. - const bool is_read = isReadOperation(operation); - AppleII::Card *const target = cards_[card_number].get(); - if(target && !is_every_cycle_card(target)) { - update_just_in_time_cards(); - target->perform_bus_operation(select, is_read, address, value); - } + if(card_number >= 0) { + // If the selected card is a just-in-time card, update the just-in-time cards, + // and then message it specifically. + const bool is_read = isReadOperation(operation); + AppleII::Card *const target = cards_[static_cast(card_number)].get(); + if(target && !is_every_cycle_card(target)) { + update_just_in_time_cards(); + target->perform_bus_operation(select, is_read, address, value); + } - // Update all the every-cycle cards regardless, but send them a ::None select if they're - // not the one actually selected. - for(const auto &card: every_cycle_cards_) { - card->run_for(Cycles(1), is_stretched_cycle); - card->perform_bus_operation( - (card == target) ? select : AppleII::Card::None, - is_read, address, value); + // Update all the every-cycle cards regardless, but send them a ::None select if they're + // not the one actually selected. + for(const auto &card: every_cycle_cards_) { + card->run_for(Cycles(1), is_stretched_cycle); + card->perform_bus_operation( + (card == target) ? select : AppleII::Card::None, + is_read, address, value); + } + has_updated_cards = true; + } else { + if(isReadOperation(operation)) *value = rom_[address - 0xc100]; } - has_updated_cards = true; } } diff --git a/ROMImages/AppleII/readme.txt b/ROMImages/AppleII/readme.txt index 6f93565af..c78248c0e 100644 --- a/ROMImages/AppleII/readme.txt +++ b/ROMImages/AppleII/readme.txt @@ -4,7 +4,7 @@ Expected files: apple2o.rom — an image at least 12kb big, in which the final 12kb is the original Apple II's ROM. apple2.rom — an image at least 12kb big, in which the final 12kb is the Apple II+'s ROM. -apple2e.rom — a file at least 15kb big, in which the final 12kb is the main portion of the ROM, that is visible from $D000, and the 3kb before that is the portion that can be paged in from $C100. +apple2e.rom — a file at least 15.75kb big, in which the final 12kb is the main portion of the ROM, that is visible from $D000, and the 3.75kb before that is the portion that can be paged in from $C100. apple2-character.rom — a 2kb image of the Apple IIe's character ROM. From 5f2b9b2d5ac61779b7deea4c6ccb7c62a8056d02 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 25 Jul 2018 22:10:21 -0400 Subject: [PATCH 05/31] Implements the alternative zero page soft switch. --- Machines/AppleII/AppleII.cpp | 41 ++++++++++++++++++++++++++++++------ 1 file changed, 34 insertions(+), 7 deletions(-) diff --git a/Machines/AppleII/AppleII.cpp b/Machines/AppleII/AppleII.cpp index 3d8cf16d8..d67418326 100644 --- a/Machines/AppleII/AppleII.cpp +++ b/Machines/AppleII/AppleII.cpp @@ -155,16 +155,18 @@ template class ConcreteMachine: } language_card_; bool has_language_card_ = true; void set_language_card_paging() { + uint8_t *const ram = alternative_zero_page_ ? aux_ram_ : ram_; + if(has_language_card_ && !language_card_.write) { - memory_blocks_[2].write_pointer = &ram_[48*1024 + (language_card_.bank1 ? 0x1000 : 0x0000)]; - memory_blocks_[3].write_pointer = &ram_[56*1024]; + memory_blocks_[2].write_pointer = &ram[48*1024 + (language_card_.bank1 ? 0x1000 : 0x0000)]; + memory_blocks_[3].write_pointer = &ram[56*1024]; } else { memory_blocks_[2].write_pointer = memory_blocks_[3].write_pointer = nullptr; } if(has_language_card_ && language_card_.read) { - memory_blocks_[2].read_pointer = &ram_[48*1024 + (language_card_.bank1 ? 0x1000 : 0x0000)]; - memory_blocks_[3].read_pointer = &ram_[56*1024]; + memory_blocks_[2].read_pointer = &ram[48*1024 + (language_card_.bank1 ? 0x1000 : 0x0000)]; + memory_blocks_[3].read_pointer = &ram[56*1024]; } else { memory_blocks_[2].read_pointer = rom_.data() + (is_iie ? 3840 : 0); memory_blocks_[3].read_pointer = memory_blocks_[2].read_pointer + 0x1000; @@ -174,7 +176,10 @@ template class ConcreteMachine: // MARK - The IIe's ROM controls. bool internal_CX_rom_ = false; bool slot_C3_rom_ = false; - bool internal_c8_rom_ = false; +// bool internal_c8_rom_ = false; + + // MARK - The IIe's auxiliary RAM controls. + bool alternative_zero_page_ = false; // MARK - typing std::unique_ptr string_serialiser_; @@ -257,6 +262,7 @@ template class ConcreteMachine: // Also, start with randomised memory contents. Memory::Fuzz(ram_, sizeof(ram_)); + Memory::Fuzz(aux_ram_, sizeof(aux_ram_)); // Add a couple of joysticks. joysticks_.emplace_back(new Joystick); @@ -296,10 +302,15 @@ template class ConcreteMachine: install_card(6, new AppleII::DiskIICard(rom_fetcher, target.disk_controller == Target::DiskController::SixteenSector)); } - // Set up the default memory blocks. + // Set up the block that will provide CX ROM access on a IIe. + cx_rom_block_.read_pointer = rom_.data(); + + // Set up the default memory blocks. On a II or II+ these values will never change. + // On a IIe they'll be affected by selection of auxiliary RAM. memory_blocks_[0].read_pointer = memory_blocks_[0].write_pointer = ram_; memory_blocks_[1].read_pointer = memory_blocks_[1].write_pointer = &ram_[0x200]; - cx_rom_block_.read_pointer = rom_.data(); + + // Set proper values for the language card/ROM area. set_language_card_paging(); insert_media(target.media); @@ -512,6 +523,7 @@ template class ConcreteMachine: // The IIe-only state reads follow... case 0xc015: if(is_iie) *value = (*value & 0x7f) | internal_CX_rom_ ? 0x80 : 0x00; break; + case 0xc016: if(is_iie) *value = (*value & 0x7f) | alternative_zero_page_ ? 0x80 : 0x00; break; case 0xc017: if(is_iie) *value = (*value & 0x7f) | slot_C3_rom_ ? 0x80 : 0x00; break; case 0xc018: if(is_iie) *value = (*value & 0x7f) | video_->get_80_store() ? 0x80 : 0x00; break; case 0xc01a: if(is_iie) *value = (*value & 0x7f) | video_->get_text() ? 0x80 : 0x00; break; @@ -547,6 +559,15 @@ template class ConcreteMachine: case 0xc05e: case 0xc05f: video_->set_double_high_resolution(!(address&1)); break; + + case 0xc008: + case 0xc009: + // The alternative zero page setting affects both bank 0 and any RAM + // that's paged as though it were on a language card. + alternative_zero_page_ = !!(address&1); + memory_blocks_[0].read_pointer = memory_blocks_[0].write_pointer = alternative_zero_page_ ? aux_ram_ : ram_; + set_language_card_paging(); + break; } } } @@ -580,6 +601,12 @@ template class ConcreteMachine: if(!string_serialiser_->advance()) string_serialiser_.reset(); } + + // On the IIe, reading C010 returns additional key info. + if(is_iie && isReadOperation(operation)) { + // TODO! + *value = 0; + } break; case 0xc030: From bc2afe69e1d687c07fe2fc6d709ea0f72e865bb8 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 28 Jul 2018 13:02:49 -0400 Subject: [PATCH 06/31] Accepting that memory mapping on a IIe is more complicated than I anticiapted, introduces mapping for all pages. Also picks a name for the Unenhanced Apple IIe ROM. --- Machines/AppleII/AppleII.cpp | 120 ++++++++++++++++++++--------------- ROMImages/AppleII/readme.txt | 3 +- 2 files changed, 71 insertions(+), 52 deletions(-) diff --git a/Machines/AppleII/AppleII.cpp b/Machines/AppleII/AppleII.cpp index d67418326..9bfd3d237 100644 --- a/Machines/AppleII/AppleII.cpp +++ b/Machines/AppleII/AppleII.cpp @@ -139,12 +139,40 @@ template class ConcreteMachine: return dynamic_cast(cards_[5].get()); } - // MARK: - Memory Map - struct MemoryBlock { - uint8_t *read_pointer = nullptr; - uint8_t *write_pointer = nullptr; - } memory_blocks_[4]; // The IO page isn't included. - MemoryBlock cx_rom_block_; // This is the IO page, for an IIe. + // MARK: - Memory Map. + + /* + The Apple II's paging mechanisms are byzantine to say the least. Painful is + another appropriate adjective. + + On a II and II+ there are five distinct zones of memory: + + 0000 to c000 : the main block of RAM + c000 to d000 : the IO area, including card ROMs + d000 to e000 : the low ROM area, which can alternatively contain either one of two 4kb blocks of RAM with a language card + e000 onward : the rest of ROM, also potentially replaced with RAM by a language card + + On a IIe with auxiliary memory the following orthogonal changes also need to be factored in: + + 0000 to 0200 : can be paged independently of the rest of RAM, other than part of the language card area which pages with it + 0400 to 0800 : the text screen, can be configured to write to auxiliary RAM + 2000 to 4000 : the graphics screen, which can be configured to write to auxiliary RAM + c100 to d000 : can be used to page an additional 3.75kb of ROM, replacing the IO area + c300 to c400 : can contain the same 256-byte segment of the ROM as if the whole IO area were switched, but while leaving cards visible in the rest + + If dealt with as individual blocks in the inner loop, that would therefore imply mapping + an address to one of 12 potential pageable zones. So I've gone reductive and surrendered + to paging every 6502 page of memory independently. It makes the paging events more expensive, + but hopefully is clear. + + Those 12 block, for the record: + + 0000 to 0200; 0200 to 0400; 0400 to 0800; 0800 to 2000; + 2000 to 4000; 4000 to c000; c000 to c100; c100 to c300; + c300 to c400; c400 to d000; d000 to e000; e000+ + */ + uint8_t *read_pages_[256]; // each is a pointer to the 256-block of memory the CPU should read when accessing that page of memory + uint8_t *write_pages_[256]; // as per read_pages_, but this is where the CPU should write. If a pointer is nullptr, don't write. // MARK: - The language card. struct { @@ -156,20 +184,15 @@ template class ConcreteMachine: bool has_language_card_ = true; void set_language_card_paging() { uint8_t *const ram = alternative_zero_page_ ? aux_ram_ : ram_; + uint8_t *const rom = is_iie ? &rom_[3840] : rom_.data(); - if(has_language_card_ && !language_card_.write) { - memory_blocks_[2].write_pointer = &ram[48*1024 + (language_card_.bank1 ? 0x1000 : 0x0000)]; - memory_blocks_[3].write_pointer = &ram[56*1024]; - } else { - memory_blocks_[2].write_pointer = memory_blocks_[3].write_pointer = nullptr; - } + printf("Configuring for %c%c%c [%c]\n", language_card_.read ? 'r' : '-', language_card_.write ? 'w' : '-', language_card_.bank1 ? '1' : '-', has_language_card_ ? 'c' : '-'); - if(has_language_card_ && language_card_.read) { - memory_blocks_[2].read_pointer = &ram[48*1024 + (language_card_.bank1 ? 0x1000 : 0x0000)]; - memory_blocks_[3].read_pointer = &ram[56*1024]; - } else { - memory_blocks_[2].read_pointer = rom_.data() + (is_iie ? 3840 : 0); - memory_blocks_[3].read_pointer = memory_blocks_[2].read_pointer + 0x1000; + for(int target = 0xd0; target < 0x100; ++target) { + uint8_t *const ram_page = &ram[(target << 8) - ((target < 0xe0 && language_card_.bank1) ? 0x1000 : 0x0000)]; + uint8_t *const rom_page = &rom[(target << 8) - 0xd000]; + write_pages_[target] = has_language_card_ && !language_card_.write ? ram_page : nullptr; + read_pages_[target] = has_language_card_ && language_card_.read ? ram_page : rom_page; } } @@ -281,7 +304,7 @@ template class ConcreteMachine: break; case Target::Model::IIe: rom_size += 3840; - rom_names.push_back("apple2e.rom"); + rom_names.push_back("apple2eu.rom"); break; } const auto roms = rom_fetcher("AppleII", rom_names); @@ -303,12 +326,16 @@ template class ConcreteMachine: } // Set up the block that will provide CX ROM access on a IIe. - cx_rom_block_.read_pointer = rom_.data(); +// cx_rom_block_.read_pointer = rom_.data(); // Set up the default memory blocks. On a II or II+ these values will never change. // On a IIe they'll be affected by selection of auxiliary RAM. - memory_blocks_[0].read_pointer = memory_blocks_[0].write_pointer = ram_; - memory_blocks_[1].read_pointer = memory_blocks_[1].write_pointer = &ram_[0x200]; + for(int c = 0; c < 0xc0; ++c) { + read_pages_[c] = write_pages_[c] = &ram_[c << 8]; + } + for(int c = 0xc0; c < 0xd0; ++c) { + read_pages_[c] = write_pages_[c] = nullptr; + } // Set proper values for the language card/ROM area. set_language_card_paging(); @@ -355,31 +382,14 @@ template class ConcreteMachine: ++ stretched_cycles_since_card_update_; } - /* - There are five distinct zones of memory on an Apple II: - - 0000 to 0200 : the zero and stack pages, which can be paged independently on a IIe - 0200 to c000 : the main block of RAM, which can be paged on a IIe - c000 to d000 : the IO area, including card ROMs - d000 to e000 : the low ROM area, which can contain indepdently-paged RAM with a language card - e000 onward : the rest of ROM, also potentially replaced with RAM by a language card - */ - uint16_t accessed_address = address; - MemoryBlock *block = nullptr; - if(address < 0x200) block = &memory_blocks_[0]; - else if(address < 0xc000) { - if(address < 0x6000 && !isReadOperation(operation)) update_video(); - block = &memory_blocks_[1]; - accessed_address -= 0x200; - } - else if(address < 0xd000) {block = (internal_CX_rom_ && address >= 0xc100) ? &cx_rom_block_: nullptr; accessed_address -= 0xc100; } - else if(address < 0xe000) {block = &memory_blocks_[2]; accessed_address -= 0xd000; } - else { block = &memory_blocks_[3]; accessed_address -= 0xe000; } - bool has_updated_cards = false; - if(block) { - if(isReadOperation(operation)) *value = block->read_pointer[accessed_address]; - else if(block->write_pointer) block->write_pointer[accessed_address] = *value; + if(read_pages_[address >> 8]) { + if(isReadOperation(operation)) *value = read_pages_[address >> 8][address & 0xff]; + else if(write_pages_[address >> 8]) write_pages_[address >> 8][address & 0xff] = *value; + +// if(!isReadOperation(operation) && address >= 0xd000) { +// printf("%02x -> %04x (%04x)\n", *value, address, &write_pages_[address >> 8][address & 0xff] - ram_); +// } if(should_load_quickly_) { // Check for a prima facie entry into RWTS. @@ -483,7 +493,7 @@ template class ConcreteMachine: // Read-only switches. switch(address) { default: - printf("Unknown read from %04x\n", address); +// printf("Unknown read from %04x\n", address); break; case 0xc000: @@ -537,10 +547,10 @@ template class ConcreteMachine: } else { // Write-only switches. All IIe as currently implemented. if(is_iie) { - printf("w %04x\n", address); +// printf("w %04x\n", address); switch(address) { default: - printf("Unknown write to %04x\n", address); +// printf("Unknown write to %04x\n", address); break; case 0xc006: @@ -565,7 +575,14 @@ template class ConcreteMachine: // The alternative zero page setting affects both bank 0 and any RAM // that's paged as though it were on a language card. alternative_zero_page_ = !!(address&1); - memory_blocks_[0].read_pointer = memory_blocks_[0].write_pointer = alternative_zero_page_ ? aux_ram_ : ram_; + if(alternative_zero_page_) { + read_pages_[0] = aux_ram_; + } else { + read_pages_[0] = ram_; + } + read_pages_[1] = read_pages_[0] + 256; + write_pages_[0] = read_pages_[0]; + write_pages_[1] = read_pages_[1]; set_language_card_paging(); break; } @@ -638,6 +655,7 @@ template class ConcreteMachine: // "The PRE-WRITE flip-flop is set by an odd read access in the $C08X range. It is reset by an even access or a write access." language_card_.pre_write = isReadOperation(operation) ? (address&1) : false; + // Apply whatever the net effect of all that is to the memory map. set_language_card_paging(); break; } @@ -646,7 +664,7 @@ template class ConcreteMachine: Communication with cards follows. */ - if(!block && address >= 0xc090 && address < 0xc800) { + if(!read_pages_[address >> 8] && address >= 0xc090 && address < 0xc800) { // If this is a card access, figure out which card is at play before determining // the totality of who needs messaging. int card_number = 0; diff --git a/ROMImages/AppleII/readme.txt b/ROMImages/AppleII/readme.txt index c78248c0e..c6af4423d 100644 --- a/ROMImages/AppleII/readme.txt +++ b/ROMImages/AppleII/readme.txt @@ -4,7 +4,8 @@ Expected files: apple2o.rom — an image at least 12kb big, in which the final 12kb is the original Apple II's ROM. apple2.rom — an image at least 12kb big, in which the final 12kb is the Apple II+'s ROM. -apple2e.rom — a file at least 15.75kb big, in which the final 12kb is the main portion of the ROM, that is visible from $D000, and the 3.75kb before that is the portion that can be paged in from $C100. +apple2e.rom — a file at least 15.75kb big, in which the final 12kb is the main portion of the Enhanced IIe ROM, that is visible from $D000, and the 3.75kb before that is the portion that can be paged in from $C100. +apple2eu.rom — as per apple2e.rom, but for the Unenhanced Apple II. apple2-character.rom — a 2kb image of the Apple IIe's character ROM. From c905de2e407e3443de68b140d4dd88ccf242eee2 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 28 Jul 2018 13:31:25 -0400 Subject: [PATCH 07/31] Restores IIe ROM-over-card paging. --- Machines/AppleII/AppleII.cpp | 71 +++++++++++++++++++----------------- 1 file changed, 37 insertions(+), 34 deletions(-) diff --git a/Machines/AppleII/AppleII.cpp b/Machines/AppleII/AppleII.cpp index 9bfd3d237..ef6769162 100644 --- a/Machines/AppleII/AppleII.cpp +++ b/Machines/AppleII/AppleII.cpp @@ -186,8 +186,6 @@ template class ConcreteMachine: uint8_t *const ram = alternative_zero_page_ ? aux_ram_ : ram_; uint8_t *const rom = is_iie ? &rom_[3840] : rom_.data(); - printf("Configuring for %c%c%c [%c]\n", language_card_.read ? 'r' : '-', language_card_.write ? 'w' : '-', language_card_.bank1 ? '1' : '-', has_language_card_ ? 'c' : '-'); - for(int target = 0xd0; target < 0x100; ++target) { uint8_t *const ram_page = &ram[(target << 8) - ((target < 0xe0 && language_card_.bank1) ? 0x1000 : 0x0000)]; uint8_t *const rom_page = &rom[(target << 8) - 0xd000]; @@ -201,6 +199,14 @@ template class ConcreteMachine: bool slot_C3_rom_ = false; // bool internal_c8_rom_ = false; + void set_card_paging() { + for(int c = 0xc1; c < 0xd0; ++c) { + read_pages_[c] = internal_CX_rom_ ? &rom_[static_cast(c << 8) - 0xc100] : nullptr; + } + if(slot_C3_rom_) read_pages_[0xc3] = &rom_[0xc300 - 0xc100]; + } + + // MARK - The IIe's auxiliary RAM controls. bool alternative_zero_page_ = false; @@ -304,7 +310,7 @@ template class ConcreteMachine: break; case Target::Model::IIe: rom_size += 3840; - rom_names.push_back("apple2eu.rom"); + rom_names.push_back("apple2e.rom"); break; } const auto roms = rom_fetcher("AppleII", rom_names); @@ -333,6 +339,8 @@ template class ConcreteMachine: for(int c = 0; c < 0xc0; ++c) { read_pages_[c] = write_pages_[c] = &ram_[c << 8]; } + + // Set the whole card area to initially backed by nothing. for(int c = 0xc0; c < 0xd0; ++c) { read_pages_[c] = write_pages_[c] = nullptr; } @@ -554,9 +562,15 @@ template class ConcreteMachine: break; case 0xc006: - case 0xc007: internal_CX_rom_ = !!(address&1); break; + case 0xc007: + internal_CX_rom_ = !!(address&1); + set_card_paging(); + break; case 0xc00a: - case 0xc00b: slot_C3_rom_ = !!(address&1); break; + case 0xc00b: + slot_C3_rom_ = !!(address&1); + set_card_paging(); + break; case 0xc00e: case 0xc00f: video_->set_alternative_character_set(!!(address&1)); break; @@ -667,7 +681,7 @@ template class ConcreteMachine: if(!read_pages_[address >> 8] && address >= 0xc090 && address < 0xc800) { // If this is a card access, figure out which card is at play before determining // the totality of who needs messaging. - int card_number = 0; + size_t card_number = 0; AppleII::Card::Select select = AppleII::Card::None; if(address >= 0xc100) { @@ -677,13 +691,6 @@ template class ConcreteMachine: */ card_number = (address - 0xc100) >> 8; select = AppleII::Card::Device; - - // If this is a IIe then some of this space may actually be - // remapped to the internal ROM. Check out that condition and, - // if so, label this as card -1. - if(is_iie && slot_C3_rom_ && address >= 0xc300 && address < 0xc400) { - card_number = -1; - } } else { /* Decode the area conventionally used by cards for registers: @@ -693,28 +700,24 @@ template class ConcreteMachine: select = AppleII::Card::IO; } - if(card_number >= 0) { - // If the selected card is a just-in-time card, update the just-in-time cards, - // and then message it specifically. - const bool is_read = isReadOperation(operation); - AppleII::Card *const target = cards_[static_cast(card_number)].get(); - if(target && !is_every_cycle_card(target)) { - update_just_in_time_cards(); - target->perform_bus_operation(select, is_read, address, value); - } - - // Update all the every-cycle cards regardless, but send them a ::None select if they're - // not the one actually selected. - for(const auto &card: every_cycle_cards_) { - card->run_for(Cycles(1), is_stretched_cycle); - card->perform_bus_operation( - (card == target) ? select : AppleII::Card::None, - is_read, address, value); - } - has_updated_cards = true; - } else { - if(isReadOperation(operation)) *value = rom_[address - 0xc100]; + // If the selected card is a just-in-time card, update the just-in-time cards, + // and then message it specifically. + const bool is_read = isReadOperation(operation); + AppleII::Card *const target = cards_[static_cast(card_number)].get(); + if(target && !is_every_cycle_card(target)) { + update_just_in_time_cards(); + target->perform_bus_operation(select, is_read, address, value); } + + // Update all the every-cycle cards regardless, but send them a ::None select if they're + // not the one actually selected. + for(const auto &card: every_cycle_cards_) { + card->run_for(Cycles(1), is_stretched_cycle); + card->perform_bus_operation( + (card == target) ? select : AppleII::Card::None, + is_read, address, value); + } + has_updated_cards = true; } } From 632b37ececed194a66cf77039a0f296af46fea7a Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 29 Jul 2018 10:41:12 -0400 Subject: [PATCH 08/31] Attempts an implementation of auxiliary memory. --- Machines/AppleII/AppleII.cpp | 56 ++++++++++++++++++++++++++++++++---- Machines/AppleII/Video.cpp | 2 +- 2 files changed, 51 insertions(+), 7 deletions(-) diff --git a/Machines/AppleII/AppleII.cpp b/Machines/AppleII/AppleII.cpp index ef6769162..5e5d5b8b2 100644 --- a/Machines/AppleII/AppleII.cpp +++ b/Machines/AppleII/AppleII.cpp @@ -209,6 +209,31 @@ template class ConcreteMachine: // MARK - The IIe's auxiliary RAM controls. bool alternative_zero_page_ = false; + bool read_auxiliary_memory_ = false; + bool write_auxiliary_memory_ = false; + void set_main_paging() { + for(int target = 0x02; target < 0xc0; ++target) { + write_pages_[target] = write_auxiliary_memory_ ? &aux_ram_[target << 8] : &ram_[target << 8]; + read_pages_[target] = read_auxiliary_memory_ ? &aux_ram_[target << 8] : &ram_[target << 8]; + } + + if(video_->get_80_store()) { + int start_page, end_page; + if(video_->get_text()) { + start_page = 0x4; + end_page = 0x8; + } else { + start_page = 0x10; + end_page = 0x20; + } + + bool use_aux_ram = video_->get_page2(); + for(int target = start_page; target < end_page; ++target) { + write_pages_[target] = use_aux_ram ? &aux_ram_[target << 8] : &ram_[target << 8]; + read_pages_[target] = use_aux_ram ? &aux_ram_[target << 8] : &ram_[target << 8]; + } + } + } // MARK - typing std::unique_ptr string_serialiser_; @@ -310,7 +335,7 @@ template class ConcreteMachine: break; case Target::Model::IIe: rom_size += 3840; - rom_names.push_back("apple2e.rom"); + rom_names.push_back("apple2eu.rom"); break; } const auto roms = rom_fetcher("AppleII", rom_names); @@ -501,7 +526,7 @@ template class ConcreteMachine: // Read-only switches. switch(address) { default: -// printf("Unknown read from %04x\n", address); + printf("Unknown (?) read from %04x\n", address); break; case 0xc000: @@ -540,6 +565,10 @@ template class ConcreteMachine: } break; // The IIe-only state reads follow... + case 0xc011: if(is_iie) *value = (*value & 0x7f) | language_card_.bank1 ? 0x80 : 0x00; break; + case 0xc012: if(is_iie) *value = (*value & 0x7f) | language_card_.read ? 0x80 : 0x00; break; + case 0xc013: if(is_iie) *value = (*value & 0x7f) | read_auxiliary_memory_ ? 0x80 : 0x00; break; + case 0xc014: if(is_iie) *value = (*value & 0x7f) | write_auxiliary_memory_ ? 0x80 : 0x00; break; case 0xc015: if(is_iie) *value = (*value & 0x7f) | internal_CX_rom_ ? 0x80 : 0x00; break; case 0xc016: if(is_iie) *value = (*value & 0x7f) | alternative_zero_page_ ? 0x80 : 0x00; break; case 0xc017: if(is_iie) *value = (*value & 0x7f) | slot_C3_rom_ ? 0x80 : 0x00; break; @@ -555,10 +584,21 @@ template class ConcreteMachine: } else { // Write-only switches. All IIe as currently implemented. if(is_iie) { -// printf("w %04x\n", address); + if(address >= 0xc000 && address < 0xc100) printf("w %04x\n", address); switch(address) { default: -// printf("Unknown write to %04x\n", address); + printf("Unknown (?) write to %04x\n", address); + break; + + case 0xc002: + case 0xc003: + read_auxiliary_memory_ = !!(address&1); + set_main_paging(); + break; + case 0xc004: + case 0xc005: + write_auxiliary_memory_ = !!(address&1); + set_main_paging(); break; case 0xc006: @@ -621,8 +661,12 @@ template class ConcreteMachine: case 0xc051: update_video(); video_->set_text(true); break; case 0xc052: update_video(); video_->set_mixed(false); break; case 0xc053: update_video(); video_->set_mixed(true); break; - case 0xc054: update_video(); video_->set_page2(false); break; - case 0xc055: update_video(); video_->set_page2(true); break; + case 0xc054: + case 0xc055: + update_video(); + video_->set_page2(!!(address&1)); + set_main_paging(); + break; case 0xc056: update_video(); video_->set_high_resolution(false); break; case 0xc057: update_video(); video_->set_high_resolution(true); break; diff --git a/Machines/AppleII/Video.cpp b/Machines/AppleII/Video.cpp index e5dde24e2..65c44ee20 100644 --- a/Machines/AppleII/Video.cpp +++ b/Machines/AppleII/Video.cpp @@ -59,7 +59,7 @@ bool VideoBase::get_80_store() { } void VideoBase::set_page2(bool page2) { - page2_ = page2;; + page2_ = page2; } bool VideoBase::get_page2() { From 5aa0b177208e19a90b34ee61035e0a15e9caaac5 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 29 Jul 2018 23:02:27 -0400 Subject: [PATCH 09/31] Improves IIe paging further. --- Machines/AppleII/AppleII.cpp | 26 +++++++++++++------------- Machines/AppleII/Video.hpp | 6 +++--- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/Machines/AppleII/AppleII.cpp b/Machines/AppleII/AppleII.cpp index 5e5d5b8b2..4b8739d78 100644 --- a/Machines/AppleII/AppleII.cpp +++ b/Machines/AppleII/AppleII.cpp @@ -57,17 +57,17 @@ template class ConcreteMachine: private: struct VideoBusHandler : public AppleII::Video::BusHandler { public: - VideoBusHandler(uint8_t *ram) : ram_(ram) {} + VideoBusHandler(uint8_t *ram, uint8_t *aux_ram) : ram_(ram), aux_ram_(aux_ram) {} uint8_t perform_read(uint16_t address) { return ram_[address]; } uint16_t perform_aux_read(uint16_t address) { - return static_cast(ram_[address] | (ram_[address] << 8)); + return static_cast(ram_[address] | (aux_ram_[address] << 8)); } private: - uint8_t *ram_; + uint8_t *ram_, *aux_ram_; }; CPU::MOS6502::Processor m6502_; @@ -212,12 +212,14 @@ template class ConcreteMachine: bool read_auxiliary_memory_ = false; bool write_auxiliary_memory_ = false; void set_main_paging() { + printf("80store: %d; page2:%d; rdaux: %d; wraux:%d\n", video_->get_80_store(), video_->get_page2(), read_auxiliary_memory_, write_auxiliary_memory_); + bool store_80 = video_->get_80_store(); for(int target = 0x02; target < 0xc0; ++target) { - write_pages_[target] = write_auxiliary_memory_ ? &aux_ram_[target << 8] : &ram_[target << 8]; - read_pages_[target] = read_auxiliary_memory_ ? &aux_ram_[target << 8] : &ram_[target << 8]; + write_pages_[target] = !store_80 && write_auxiliary_memory_ ? &aux_ram_[target << 8] : &ram_[target << 8]; + read_pages_[target] = !store_80 && read_auxiliary_memory_ ? &aux_ram_[target << 8] : &ram_[target << 8]; } - if(video_->get_80_store()) { + if(store_80) { int start_page, end_page; if(video_->get_text()) { start_page = 0x4; @@ -294,7 +296,7 @@ template class ConcreteMachine: public: ConcreteMachine(const Analyser::Static::AppleII::Target &target, const ROMMachine::ROMFetcher &rom_fetcher): m6502_(*this), - video_bus_handler_(ram_), + video_bus_handler_(ram_, aux_ram_), audio_toggle_(audio_queue_), speaker_(audio_toggle_) { // The system's master clock rate. @@ -420,10 +422,6 @@ template class ConcreteMachine: if(isReadOperation(operation)) *value = read_pages_[address >> 8][address & 0xff]; else if(write_pages_[address >> 8]) write_pages_[address >> 8][address & 0xff] = *value; -// if(!isReadOperation(operation) && address >= 0xd000) { -// printf("%02x -> %04x (%04x)\n", *value, address, &write_pages_[address >> 8][address & 0xff] - ram_); -// } - if(should_load_quickly_) { // Check for a prima facie entry into RWTS. if(operation == CPU::MOS6502::BusOperation::ReadOpcode && address == 0xb7b5) { @@ -619,7 +617,10 @@ template class ConcreteMachine: case 0xc00d: video_->set_80_columns(!!(address&1)); break; case 0xc000: - case 0xc001: video_->set_80_store(!!(address&1)); break; + case 0xc001: + video_->set_80_store(!!(address&1)); + set_main_paging(); + break; case 0xc05e: case 0xc05f: video_->set_double_high_resolution(!(address&1)); break; @@ -883,4 +884,3 @@ Machine *Machine::AppleII(const Analyser::Static::Target *target, const ROMMachi } Machine::~Machine() {} - diff --git a/Machines/AppleII/Video.hpp b/Machines/AppleII/Video.hpp index f453d162f..cf3212254 100644 --- a/Machines/AppleII/Video.hpp +++ b/Machines/AppleII/Video.hpp @@ -295,13 +295,13 @@ template class Video: public VideoBase { for(int c = column_; c < pixel_end; ++c) { const uint16_t characters = bus_handler_.perform_aux_read(static_cast(text_address + c)); const std::size_t character_addresses[2] = { - static_cast(((characters & 0x3f) << 3) + pixel_row), static_cast((((characters >> 8) & 0x3f) << 3) + pixel_row), + static_cast(((characters & 0x3f) << 3) + pixel_row), }; const uint8_t character_patterns[2] = { - static_cast(character_rom_[character_addresses[0]] ^ inverses[(characters >> 6) & 3]), - static_cast(character_rom_[character_addresses[1]] ^ inverses[(characters >> 14) & 3]), + static_cast(character_rom_[character_addresses[0]] ^ inverses[(characters >> 14) & 3]), + static_cast(character_rom_[character_addresses[1]] ^ inverses[(characters >> 6) & 3]), }; // The character ROM is output MSB to LSB rather than LSB to MSB. From ecb26e328109af601460add7ae2aa1d9ee2babe3 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 30 Jul 2018 19:54:25 -0400 Subject: [PATCH 10/31] Corrections: slot_C3_rom_ works the other way around; 80STORE doesn't affect most of RAM but does always affect the text screen. Also factored out `set_zero_page_paging` for consistency. --- Machines/AppleII/AppleII.cpp | 61 ++++++++++++++++++++---------------- 1 file changed, 34 insertions(+), 27 deletions(-) diff --git a/Machines/AppleII/AppleII.cpp b/Machines/AppleII/AppleII.cpp index 4b8739d78..3ff5e7b0a 100644 --- a/Machines/AppleII/AppleII.cpp +++ b/Machines/AppleII/AppleII.cpp @@ -203,36 +203,46 @@ template class ConcreteMachine: for(int c = 0xc1; c < 0xd0; ++c) { read_pages_[c] = internal_CX_rom_ ? &rom_[static_cast(c << 8) - 0xc100] : nullptr; } - if(slot_C3_rom_) read_pages_[0xc3] = &rom_[0xc300 - 0xc100]; + + if(!internal_CX_rom_) { + if(!slot_C3_rom_) read_pages_[0xc3] = &rom_[0xc300 - 0xc100]; + } } // MARK - The IIe's auxiliary RAM controls. bool alternative_zero_page_ = false; + void set_zero_page_paging() { + if(alternative_zero_page_) { + read_pages_[0] = aux_ram_; + } else { + read_pages_[0] = ram_; + } + read_pages_[1] = read_pages_[0] + 256; + write_pages_[0] = read_pages_[0]; + write_pages_[1] = read_pages_[1]; + } + bool read_auxiliary_memory_ = false; bool write_auxiliary_memory_ = false; void set_main_paging() { - printf("80store: %d; page2:%d; rdaux: %d; wraux:%d\n", video_->get_80_store(), video_->get_page2(), read_auxiliary_memory_, write_auxiliary_memory_); - bool store_80 = video_->get_80_store(); for(int target = 0x02; target < 0xc0; ++target) { - write_pages_[target] = !store_80 && write_auxiliary_memory_ ? &aux_ram_[target << 8] : &ram_[target << 8]; - read_pages_[target] = !store_80 && read_auxiliary_memory_ ? &aux_ram_[target << 8] : &ram_[target << 8]; + read_pages_[target] = read_auxiliary_memory_ ? &aux_ram_[target << 8] : &ram_[target << 8]; + write_pages_[target] = write_auxiliary_memory_ ? &aux_ram_[target << 8] : &ram_[target << 8]; } - if(store_80) { - int start_page, end_page; - if(video_->get_text()) { - start_page = 0x4; - end_page = 0x8; - } else { - start_page = 0x10; - end_page = 0x20; + if(video_->get_80_store()) { + bool use_aux_ram = video_->get_page2(); + for(int target = 0x04; target < 0x08; ++target) { + read_pages_[target] = use_aux_ram ? &aux_ram_[target << 8] : &ram_[target << 8]; + write_pages_[target] = use_aux_ram ? &aux_ram_[target << 8] : &ram_[target << 8]; } - bool use_aux_ram = video_->get_page2(); - for(int target = start_page; target < end_page; ++target) { - write_pages_[target] = use_aux_ram ? &aux_ram_[target << 8] : &ram_[target << 8]; - read_pages_[target] = use_aux_ram ? &aux_ram_[target << 8] : &ram_[target << 8]; + if(!video_->get_text()) { + for(int target = 0x10; target < 0x20; ++target) { + read_pages_[target] = use_aux_ram ? &aux_ram_[target << 8] : &ram_[target << 8]; + write_pages_[target] = use_aux_ram ? &aux_ram_[target << 8] : &ram_[target << 8]; + } } } } @@ -630,14 +640,7 @@ template class ConcreteMachine: // The alternative zero page setting affects both bank 0 and any RAM // that's paged as though it were on a language card. alternative_zero_page_ = !!(address&1); - if(alternative_zero_page_) { - read_pages_[0] = aux_ram_; - } else { - read_pages_[0] = ram_; - } - read_pages_[1] = read_pages_[0] + 256; - write_pages_[0] = read_pages_[0]; - write_pages_[1] = read_pages_[1]; + set_zero_page_paging(); set_language_card_paging(); break; } @@ -658,8 +661,12 @@ template class ConcreteMachine: } break; /* Read-write switches. */ - case 0xc050: update_video(); video_->set_text(false); break; - case 0xc051: update_video(); video_->set_text(true); break; + case 0xc050: + case 0xc051: + update_video(); + video_->set_text(!!(address&1)); + set_main_paging(); + break; case 0xc052: update_video(); video_->set_mixed(false); break; case 0xc053: update_video(); video_->set_mixed(true); break; case 0xc054: From e9aaa5bbdf13263208e03c399e85d2cc92e9b97c Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 30 Jul 2018 22:23:48 -0400 Subject: [PATCH 11/31] Factors out the page-mapping function. For one less potential source of failure. --- Machines/AppleII/AppleII.cpp | 64 +++++++++++++++++------------------- 1 file changed, 31 insertions(+), 33 deletions(-) diff --git a/Machines/AppleII/AppleII.cpp b/Machines/AppleII/AppleII.cpp index 3ff5e7b0a..faf019e09 100644 --- a/Machines/AppleII/AppleII.cpp +++ b/Machines/AppleII/AppleII.cpp @@ -173,6 +173,15 @@ template class ConcreteMachine: */ uint8_t *read_pages_[256]; // each is a pointer to the 256-block of memory the CPU should read when accessing that page of memory uint8_t *write_pages_[256]; // as per read_pages_, but this is where the CPU should write. If a pointer is nullptr, don't write. + void page(int start, int end, uint8_t *read, uint8_t *write) { + for(int position = start; position < end; ++position) { + read_pages_[position] = read; + if(read) read += 256; + + write_pages_[position] = write; + if(write) write += 256; + } + } // MARK: - The language card. struct { @@ -186,12 +195,13 @@ template class ConcreteMachine: uint8_t *const ram = alternative_zero_page_ ? aux_ram_ : ram_; uint8_t *const rom = is_iie ? &rom_[3840] : rom_.data(); - for(int target = 0xd0; target < 0x100; ++target) { - uint8_t *const ram_page = &ram[(target << 8) - ((target < 0xe0 && language_card_.bank1) ? 0x1000 : 0x0000)]; - uint8_t *const rom_page = &rom[(target << 8) - 0xd000]; - write_pages_[target] = has_language_card_ && !language_card_.write ? ram_page : nullptr; - read_pages_[target] = has_language_card_ && language_card_.read ? ram_page : rom_page; - } + page(0xd0, 0xe0, + language_card_.read ? &ram[language_card_.bank1 ? 0xd000 : 0xc000] : rom, + language_card_.write ? nullptr : &ram[language_card_.bank1 ? 0xd000 : 0xc000]); + + page(0xe0, 0x100, + language_card_.read ? &ram[0xe000] : &rom[0x1000], + language_card_.write ? nullptr : &ram[0xe000]); } // MARK - The IIe's ROM controls. @@ -200,16 +210,13 @@ template class ConcreteMachine: // bool internal_c8_rom_ = false; void set_card_paging() { - for(int c = 0xc1; c < 0xd0; ++c) { - read_pages_[c] = internal_CX_rom_ ? &rom_[static_cast(c << 8) - 0xc100] : nullptr; - } + page(0xc1, 0xd0, internal_CX_rom_ ? rom_.data() : nullptr, nullptr); if(!internal_CX_rom_) { if(!slot_C3_rom_) read_pages_[0xc3] = &rom_[0xc300 - 0xc100]; } } - // MARK - The IIe's auxiliary RAM controls. bool alternative_zero_page_ = false; void set_zero_page_paging() { @@ -226,23 +233,20 @@ template class ConcreteMachine: bool read_auxiliary_memory_ = false; bool write_auxiliary_memory_ = false; void set_main_paging() { - for(int target = 0x02; target < 0xc0; ++target) { - read_pages_[target] = read_auxiliary_memory_ ? &aux_ram_[target << 8] : &ram_[target << 8]; - write_pages_[target] = write_auxiliary_memory_ ? &aux_ram_[target << 8] : &ram_[target << 8]; - } + page(0x02, 0xc0, + read_auxiliary_memory_ ? &aux_ram_[0x0200] : &ram_[0x0200], + write_auxiliary_memory_ ? &aux_ram_[0x0200] : &ram_[0x0200]); - if(video_->get_80_store()) { + if(video_ && video_->get_80_store()) { bool use_aux_ram = video_->get_page2(); - for(int target = 0x04; target < 0x08; ++target) { - read_pages_[target] = use_aux_ram ? &aux_ram_[target << 8] : &ram_[target << 8]; - write_pages_[target] = use_aux_ram ? &aux_ram_[target << 8] : &ram_[target << 8]; - } + page(0x04, 0x08, + use_aux_ram ? &aux_ram_[0x0400] : &ram_[0x0400], + use_aux_ram ? &aux_ram_[0x0400] : &ram_[0x0400]); if(!video_->get_text()) { - for(int target = 0x10; target < 0x20; ++target) { - read_pages_[target] = use_aux_ram ? &aux_ram_[target << 8] : &ram_[target << 8]; - write_pages_[target] = use_aux_ram ? &aux_ram_[target << 8] : &ram_[target << 8]; - } + page(0x10, 0x20, + use_aux_ram ? &aux_ram_[0x1000] : &ram_[0x1000], + use_aux_ram ? &aux_ram_[0x1000] : &ram_[0x1000]); } } } @@ -347,7 +351,7 @@ template class ConcreteMachine: break; case Target::Model::IIe: rom_size += 3840; - rom_names.push_back("apple2eu.rom"); + rom_names.push_back("apple2e.rom"); break; } const auto roms = rom_fetcher("AppleII", rom_names); @@ -368,19 +372,13 @@ template class ConcreteMachine: install_card(6, new AppleII::DiskIICard(rom_fetcher, target.disk_controller == Target::DiskController::SixteenSector)); } - // Set up the block that will provide CX ROM access on a IIe. -// cx_rom_block_.read_pointer = rom_.data(); - // Set up the default memory blocks. On a II or II+ these values will never change. // On a IIe they'll be affected by selection of auxiliary RAM. - for(int c = 0; c < 0xc0; ++c) { - read_pages_[c] = write_pages_[c] = &ram_[c << 8]; - } + set_main_paging(); + set_zero_page_paging(); // Set the whole card area to initially backed by nothing. - for(int c = 0xc0; c < 0xd0; ++c) { - read_pages_[c] = write_pages_[c] = nullptr; - } + page(0xc0, 0xd0, nullptr, nullptr); // Set proper values for the language card/ROM area. set_language_card_paging(); From a533d09fe7d637f3bd30dd4c3093278f3b96e6a6 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 30 Jul 2018 23:07:34 -0400 Subject: [PATCH 12/31] Sets the IIe as the default model. --- Analyser/Static/AppleII/Target.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Analyser/Static/AppleII/Target.hpp b/Analyser/Static/AppleII/Target.hpp index adc4e3b5a..25c4e78cf 100644 --- a/Analyser/Static/AppleII/Target.hpp +++ b/Analyser/Static/AppleII/Target.hpp @@ -27,7 +27,7 @@ struct Target: public ::Analyser::Static::Target { ThirteenSector }; - Model model = Model::IIplus; + Model model = Model::IIe; DiskController disk_controller = DiskController::None; }; From 16ccbdefd66e4a5f3e466de8fbaec5ecd7334b11 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 30 Jul 2018 23:08:22 -0400 Subject: [PATCH 13/31] Of course, | has higher precedence than ?. Classic! --- Machines/AppleII/AppleII.cpp | 39 ++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/Machines/AppleII/AppleII.cpp b/Machines/AppleII/AppleII.cpp index faf019e09..8ca3b4e63 100644 --- a/Machines/AppleII/AppleII.cpp +++ b/Machines/AppleII/AppleII.cpp @@ -351,7 +351,7 @@ template class ConcreteMachine: break; case Target::Model::IIe: rom_size += 3840; - rom_names.push_back("apple2e.rom"); + rom_names.push_back("apple2eu.rom"); break; } const auto roms = rom_fetcher("AppleII", rom_names); @@ -532,7 +532,7 @@ template class ConcreteMachine: // Read-only switches. switch(address) { default: - printf("Unknown (?) read from %04x\n", address); +// printf("Unknown (?) read from %04x\n", address); break; case 0xc000: @@ -571,29 +571,30 @@ template class ConcreteMachine: } break; // The IIe-only state reads follow... - case 0xc011: if(is_iie) *value = (*value & 0x7f) | language_card_.bank1 ? 0x80 : 0x00; break; - case 0xc012: if(is_iie) *value = (*value & 0x7f) | language_card_.read ? 0x80 : 0x00; break; - case 0xc013: if(is_iie) *value = (*value & 0x7f) | read_auxiliary_memory_ ? 0x80 : 0x00; break; - case 0xc014: if(is_iie) *value = (*value & 0x7f) | write_auxiliary_memory_ ? 0x80 : 0x00; break; - case 0xc015: if(is_iie) *value = (*value & 0x7f) | internal_CX_rom_ ? 0x80 : 0x00; break; - case 0xc016: if(is_iie) *value = (*value & 0x7f) | alternative_zero_page_ ? 0x80 : 0x00; break; - case 0xc017: if(is_iie) *value = (*value & 0x7f) | slot_C3_rom_ ? 0x80 : 0x00; break; - case 0xc018: if(is_iie) *value = (*value & 0x7f) | video_->get_80_store() ? 0x80 : 0x00; break; - case 0xc01a: if(is_iie) *value = (*value & 0x7f) | video_->get_text() ? 0x80 : 0x00; break; - case 0xc01b: if(is_iie) *value = (*value & 0x7f) | video_->get_mixed() ? 0x80 : 0x00; break; - case 0xc01c: if(is_iie) *value = (*value & 0x7f) | video_->get_page2() ? 0x80 : 0x00; break; - case 0xc01d: if(is_iie) *value = (*value & 0x7f) | video_->get_high_resolution() ? 0x80 : 0x00; break; - case 0xc01e: if(is_iie) *value = (*value & 0x7f) | video_->get_alternative_character_set() ? 0x80 : 0x00; break; - case 0xc01f: if(is_iie) *value = (*value & 0x7f) | video_->get_80_columns() ? 0x80 : 0x00; break; - case 0xc07f: if(is_iie) *value = (*value & 0x7f) | video_->get_double_high_resolution() ? 0x80 : 0x00; break; + case 0xc011: if(is_iie) *value = (*value & 0x7f) | (language_card_.bank1 ? 0x80 : 0x00); break; + case 0xc012: if(is_iie) *value = (*value & 0x7f) | (language_card_.read ? 0x80 : 0x00); break; + case 0xc013: if(is_iie) *value = (*value & 0x7f) | (read_auxiliary_memory_ ? 0x80 : 0x00); break; + case 0xc014: if(is_iie) *value = (*value & 0x7f) | (write_auxiliary_memory_ ? 0x80 : 0x00); break; + case 0xc015: if(is_iie) *value = (*value & 0x7f) | (internal_CX_rom_ ? 0x80 : 0x00); break; + case 0xc016: if(is_iie) *value = (*value & 0x7f) | (alternative_zero_page_ ? 0x80 : 0x00); break; + case 0xc017: if(is_iie) *value = (*value & 0x7f) | (slot_C3_rom_ ? 0x80 : 0x00); break; + case 0xc018: if(is_iie) *value = (*value & 0x7f) | (video_->get_80_store() ? 0x80 : 0x00); break; + // TODO: c019 to read vertical blanking + case 0xc01a: if(is_iie) *value = (*value & 0x7f) | (video_->get_text() ? 0x80 : 0x00); break; + case 0xc01b: if(is_iie) *value = (*value & 0x7f) | (video_->get_mixed() ? 0x80 : 0x00); break; + case 0xc01c: if(is_iie) *value = (*value & 0x7f) | (video_->get_page2() ? 0x80 : 0x00); break; + case 0xc01d: if(is_iie) *value = (*value & 0x7f) | (video_->get_high_resolution() ? 0x80 : 0x00); break; + case 0xc01e: if(is_iie) *value = (*value & 0x7f) | (video_->get_alternative_character_set() ? 0x80 : 0x00); break; + case 0xc01f: if(is_iie) *value = (*value & 0x7f) | (video_->get_80_columns() ? 0x80 : 0x00); break; + case 0xc07f: if(is_iie) *value = (*value & 0x7f) | (video_->get_double_high_resolution() ? 0x80 : 0x00); break; } } else { // Write-only switches. All IIe as currently implemented. if(is_iie) { - if(address >= 0xc000 && address < 0xc100) printf("w %04x\n", address); +// if(address >= 0xc000 && address < 0xc100) printf("w %04x\n", address); switch(address) { default: - printf("Unknown (?) write to %04x\n", address); +// printf("Unknown (?) write to %04x\n", address); break; case 0xc002: From 3f566833425c6cbbe45608b83e46d4ea2ae89b3c Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 30 Jul 2018 23:08:45 -0400 Subject: [PATCH 14/31] Fixes order of deserialisation between auxiliary and base RAM. --- Machines/AppleII/Video.hpp | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/Machines/AppleII/Video.hpp b/Machines/AppleII/Video.hpp index cf3212254..c27958a9d 100644 --- a/Machines/AppleII/Video.hpp +++ b/Machines/AppleII/Video.hpp @@ -385,21 +385,21 @@ template class Video: public VideoBase { for(int c = column_; c < pixel_end; ++c) { const uint16_t graphic = bus_handler_.perform_aux_read(static_cast(graphics_address + c)); - pixel_pointer_[0] = graphic & 0x01; - pixel_pointer_[1] = graphic & 0x02; - pixel_pointer_[2] = graphic & 0x04; - pixel_pointer_[3] = graphic & 0x08; - pixel_pointer_[4] = graphic & 0x10; - pixel_pointer_[5] = graphic & 0x20; - pixel_pointer_[6] = graphic & 0x40; - pixel_pointer_[7] = (graphic >> 8) & 0x01; - pixel_pointer_[8] = (graphic >> 8) & 0x02; - pixel_pointer_[9] = (graphic >> 8) & 0x04; - pixel_pointer_[10] = (graphic >> 8) & 0x08; - pixel_pointer_[11] = (graphic >> 8) & 0x10; - pixel_pointer_[12] = (graphic >> 8) & 0x20; - pixel_pointer_[13] = (graphic >> 8) & 0x40; - graphics_carry_ = (graphic >> 8) & 0x40; + pixel_pointer_[0] = (graphic >> 8) & 0x01; + pixel_pointer_[1] = (graphic >> 8) & 0x02; + pixel_pointer_[2] = (graphic >> 8) & 0x04; + pixel_pointer_[3] = (graphic >> 8) & 0x08; + pixel_pointer_[4] = (graphic >> 8) & 0x10; + pixel_pointer_[5] = (graphic >> 8) & 0x20; + pixel_pointer_[6] = (graphic >> 8) & 0x40; + pixel_pointer_[7] = graphic & 0x01; + pixel_pointer_[8] = graphic & 0x02; + pixel_pointer_[9] = graphic & 0x04; + pixel_pointer_[10] = graphic & 0x08; + pixel_pointer_[11] = graphic & 0x10; + pixel_pointer_[12] = graphic & 0x20; + pixel_pointer_[13] = graphic & 0x40; + graphics_carry_ = graphic & 0x40; pixel_pointer_ += 14; } } break; From 5810f9b3f903cf5b2a6f499f6a6a28b9607b8341 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 30 Jul 2018 23:23:18 -0400 Subject: [PATCH 15/31] Fixes high resolution address range and switching logic. --- Machines/AppleII/AppleII.cpp | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/Machines/AppleII/AppleII.cpp b/Machines/AppleII/AppleII.cpp index 8ca3b4e63..47b607992 100644 --- a/Machines/AppleII/AppleII.cpp +++ b/Machines/AppleII/AppleII.cpp @@ -243,10 +243,10 @@ template class ConcreteMachine: use_aux_ram ? &aux_ram_[0x0400] : &ram_[0x0400], use_aux_ram ? &aux_ram_[0x0400] : &ram_[0x0400]); - if(!video_->get_text()) { - page(0x10, 0x20, - use_aux_ram ? &aux_ram_[0x1000] : &ram_[0x1000], - use_aux_ram ? &aux_ram_[0x1000] : &ram_[0x1000]); + if(video_->get_high_resolution()) { + page(0x20, 0x40, + use_aux_ram ? &aux_ram_[0x2000] : &ram_[0x2000], + use_aux_ram ? &aux_ram_[0x2000] : &ram_[0x2000]); } } } @@ -664,7 +664,6 @@ template class ConcreteMachine: case 0xc051: update_video(); video_->set_text(!!(address&1)); - set_main_paging(); break; case 0xc052: update_video(); video_->set_mixed(false); break; case 0xc053: update_video(); video_->set_mixed(true); break; @@ -674,8 +673,12 @@ template class ConcreteMachine: video_->set_page2(!!(address&1)); set_main_paging(); break; - case 0xc056: update_video(); video_->set_high_resolution(false); break; - case 0xc057: update_video(); video_->set_high_resolution(true); break; + case 0xc056: + case 0xc057: + update_video(); + video_->set_high_resolution(!!(address&1)); + set_main_paging(); + break; case 0xc010: keyboard_input_ &= 0x7f; From c773d3501a4dba0a2e4bf72a72d450403e5c9b87 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 31 Jul 2018 19:00:46 -0400 Subject: [PATCH 16/31] Implements the INTC8ROM switch. Finally causing the Zellyn tests to pass! Is this nightmare behind me? --- Machines/AppleII/AppleII.cpp | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/Machines/AppleII/AppleII.cpp b/Machines/AppleII/AppleII.cpp index 47b607992..bf64b872a 100644 --- a/Machines/AppleII/AppleII.cpp +++ b/Machines/AppleII/AppleII.cpp @@ -207,14 +207,16 @@ template class ConcreteMachine: // MARK - The IIe's ROM controls. bool internal_CX_rom_ = false; bool slot_C3_rom_ = false; -// bool internal_c8_rom_ = false; + bool internal_c8_rom_ = false; void set_card_paging() { - page(0xc1, 0xd0, internal_CX_rom_ ? rom_.data() : nullptr, nullptr); + page(0xc1, 0xd8, internal_CX_rom_ ? rom_.data() : nullptr, nullptr); if(!internal_CX_rom_) { if(!slot_C3_rom_) read_pages_[0xc3] = &rom_[0xc300 - 0xc100]; } + + page(0xc8, 0xd0, (internal_CX_rom_ || internal_c8_rom_) ? &rom_[0xc800 - 0xc100] : nullptr, nullptr); } // MARK - The IIe's auxiliary RAM controls. @@ -430,6 +432,16 @@ template class ConcreteMachine: if(isReadOperation(operation)) *value = read_pages_[address >> 8][address & 0xff]; else if(write_pages_[address >> 8]) write_pages_[address >> 8][address & 0xff] = *value; + if(is_iie && address >= 0xc300 && address < 0xd000) { + bool internal_c8_rom = internal_c8_rom_; + internal_c8_rom |= ((address >> 8) == 0xc3) && !slot_C3_rom_; + internal_c8_rom &= (address != 0xcfff); + if(internal_c8_rom != internal_c8_rom_) { + internal_c8_rom_ = internal_c8_rom; + set_card_paging(); + } + } + if(should_load_quickly_) { // Check for a prima facie entry into RWTS. if(operation == CPU::MOS6502::BusOperation::ReadOpcode && address == 0xb7b5) { From a36f37d24056cbcb14d1514994cc9719645eec5b Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 31 Jul 2018 21:29:51 -0400 Subject: [PATCH 17/31] Introduces a 1/14th delay in output of double high res. --- Machines/AppleII/Video.hpp | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/Machines/AppleII/Video.hpp b/Machines/AppleII/Video.hpp index c27958a9d..5e5d3b4af 100644 --- a/Machines/AppleII/Video.hpp +++ b/Machines/AppleII/Video.hpp @@ -385,20 +385,20 @@ template class Video: public VideoBase { for(int c = column_; c < pixel_end; ++c) { const uint16_t graphic = bus_handler_.perform_aux_read(static_cast(graphics_address + c)); - pixel_pointer_[0] = (graphic >> 8) & 0x01; - pixel_pointer_[1] = (graphic >> 8) & 0x02; - pixel_pointer_[2] = (graphic >> 8) & 0x04; - pixel_pointer_[3] = (graphic >> 8) & 0x08; - pixel_pointer_[4] = (graphic >> 8) & 0x10; - pixel_pointer_[5] = (graphic >> 8) & 0x20; - pixel_pointer_[6] = (graphic >> 8) & 0x40; - pixel_pointer_[7] = graphic & 0x01; - pixel_pointer_[8] = graphic & 0x02; - pixel_pointer_[9] = graphic & 0x04; - pixel_pointer_[10] = graphic & 0x08; - pixel_pointer_[11] = graphic & 0x10; - pixel_pointer_[12] = graphic & 0x20; - pixel_pointer_[13] = graphic & 0x40; + pixel_pointer_[0] = graphics_carry_; + pixel_pointer_[1] = (graphic >> 8) & 0x01; + pixel_pointer_[2] = (graphic >> 8) & 0x02; + pixel_pointer_[3] = (graphic >> 8) & 0x04; + pixel_pointer_[4] = (graphic >> 8) & 0x08; + pixel_pointer_[5] = (graphic >> 8) & 0x10; + pixel_pointer_[6] = (graphic >> 8) & 0x20; + pixel_pointer_[7] = (graphic >> 8) & 0x40; + pixel_pointer_[8] = graphic & 0x01; + pixel_pointer_[9] = graphic & 0x02; + pixel_pointer_[10] = graphic & 0x04; + pixel_pointer_[11] = graphic & 0x08; + pixel_pointer_[12] = graphic & 0x10; + pixel_pointer_[13] = graphic & 0x20; graphics_carry_ = graphic & 0x40; pixel_pointer_ += 14; } From c91eaaf8dacdfaa5398434c114db6e184364c8db Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 31 Jul 2018 21:45:09 -0400 Subject: [PATCH 18/31] Takes a stab at double low-res graphics. --- Machines/AppleII/Video.hpp | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/Machines/AppleII/Video.hpp b/Machines/AppleII/Video.hpp index 5e5d3b4af..630e077f8 100644 --- a/Machines/AppleII/Video.hpp +++ b/Machines/AppleII/Video.hpp @@ -159,7 +159,7 @@ class VideoBase { // State affecting logical state. int row_ = 0, column_ = 0, flash_ = 0; - // An + // Enumerates all Apple II and IIe display modes. enum class GraphicsMode { LowRes, DoubleLowRes, @@ -324,7 +324,38 @@ template class Video: public VideoBase { } } break; - case GraphicsMode::DoubleLowRes: + case GraphicsMode::DoubleLowRes: { + const int row_shift = (row_&4); + for(int c = column_; c < pixel_end; ++c) { + const uint16_t nibble = (bus_handler_.perform_aux_read(static_cast(text_address + c)) >> row_shift) & 0xf0f; + + if(c&1) { + pixel_pointer_[0] = pixel_pointer_[4] = (nibble >> 8) & 4; + pixel_pointer_[1] = pixel_pointer_[5] = (nibble >> 8) & 8; + pixel_pointer_[2] = pixel_pointer_[6] = (nibble >> 8) & 1; + pixel_pointer_[3] = (nibble >> 8) & 2; + + pixel_pointer_[8] = pixel_pointer_[12] = nibble & 4; + pixel_pointer_[9] = pixel_pointer_[13] = nibble & 8; + pixel_pointer_[10] = nibble & 1; + pixel_pointer_[7] = pixel_pointer_[11] = nibble & 2; + graphics_carry_ = nibble & 8; + } else { + pixel_pointer_[0] = pixel_pointer_[4] = (nibble >> 8) & 1; + pixel_pointer_[1] = pixel_pointer_[5] = (nibble >> 8) & 2; + pixel_pointer_[2] = pixel_pointer_[6] = (nibble >> 8) & 4; + pixel_pointer_[3] = (nibble >> 8) & 8; + + pixel_pointer_[8] = pixel_pointer_[12] = nibble & 1; + pixel_pointer_[9] = pixel_pointer_[13] = nibble & 2; + pixel_pointer_[10] = nibble & 4; + pixel_pointer_[7] = pixel_pointer_[11] = nibble & 8; + graphics_carry_ = nibble & 2; + } + pixel_pointer_ += 14; + } + } break; + case GraphicsMode::LowRes: { const int row_shift = (row_&4); // TODO: decompose into two loops, possibly. From 98bb5bd9f1f6a147c2161fd52c023074340f9313 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 31 Jul 2018 23:01:11 -0400 Subject: [PATCH 19/31] Ensures flux bits are observable for two cycles rather than one; it should be 1us. --- Components/DiskII/DiskII.cpp | 8 +++++++- Components/DiskII/DiskII.hpp | 1 + 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Components/DiskII/DiskII.cpp b/Components/DiskII/DiskII.cpp index d02afdb37..b80c68417 100644 --- a/Components/DiskII/DiskII.cpp +++ b/Components/DiskII/DiskII.cpp @@ -73,13 +73,18 @@ void DiskII::select_drive(int drive) { drives_[active_drive_].set_motor_on(motor_is_enabled_); } +// The read pulse is controlled by a special IC that outputs a 1us pulse for every field reversal on the disk. + void DiskII::run_for(const Cycles cycles) { if(preferred_clocking() == ClockingHint::Preference::None) return; int integer_cycles = cycles.as_int(); while(integer_cycles--) { const int address = (state_ & 0xf0) | inputs_ | ((shift_register_&0x80) >> 6); - inputs_ |= input_flux; + if(flux_duration_) { + --flux_duration_; + if(!flux_duration_) inputs_ |= input_flux; + } state_ = state_machine_[static_cast(address)]; switch(state_ & 0xf) { default: shift_register_ = 0; break; // clear @@ -200,6 +205,7 @@ void DiskII::set_disk(const std::shared_ptr &disk, int driv void DiskII::process_event(const Storage::Disk::Track::Event &event) { if(event.type == Storage::Disk::Track::Event::FluxTransition) { inputs_ &= ~input_flux; + flux_duration_ = 2; // Upon detection of a flux transition, the flux flag should stay set for 1us. Emulate that as two cycles. decide_clocking_preference(); } } diff --git a/Components/DiskII/DiskII.hpp b/Components/DiskII/DiskII.hpp index ccb3ce491..e95682101 100644 --- a/Components/DiskII/DiskII.hpp +++ b/Components/DiskII/DiskII.hpp @@ -121,6 +121,7 @@ class DiskII: ClockingHint::Preference clocking_preference_ = ClockingHint::Preference::RealTime; uint8_t data_input_ = 0; + int flux_duration_ = 0; }; } From 74a2f717b3458036b8aea59dd9d78571007e409e Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 1 Aug 2018 18:52:42 -0400 Subject: [PATCH 20/31] Turns down the composite signal amplitude a little, to help colour distinctness. --- Machines/AppleII/Video.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Machines/AppleII/Video.cpp b/Machines/AppleII/Video.cpp index 65c44ee20..80fdb9361 100644 --- a/Machines/AppleII/Video.cpp +++ b/Machines/AppleII/Video.cpp @@ -18,7 +18,7 @@ VideoBase::VideoBase() : crt_->set_composite_sampling_function( "float composite_sample(usampler2D sampler, vec2 coordinate, vec2 icoordinate, float phase, float amplitude)" "{" - "return texture(sampler, coordinate).r;" + "return clamp(texture(sampler, coordinate).r, 0.0, 0.7);" "}"); // Show only the centre 75% of the TV frame. From 78c71374273c95660bee88f4d1ef842b4e8d9fe5 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 3 Aug 2018 21:11:22 -0400 Subject: [PATCH 21/31] Avoids observer communication if motor status hasn't changed. --- Storage/Disk/Drive.cpp | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/Storage/Disk/Drive.cpp b/Storage/Disk/Drive.cpp index cce6362b3..99fba1182 100644 --- a/Storage/Disk/Drive.cpp +++ b/Storage/Disk/Drive.cpp @@ -117,20 +117,22 @@ bool Drive::get_is_ready() { } void Drive::set_motor_on(bool motor_is_on) { - motor_is_on_ = motor_is_on; + if(motor_is_on_ != motor_is_on) { + motor_is_on_ = motor_is_on; - if(observer_) { - observer_->set_drive_motor_status(drive_name_, motor_is_on_); - if(announce_motor_led_) { - observer_->set_led_status(drive_name_, motor_is_on_); + if(observer_) { + observer_->set_drive_motor_status(drive_name_, motor_is_on_); + if(announce_motor_led_) { + observer_->set_led_status(drive_name_, motor_is_on_); + } } - } - if(!motor_is_on) { - ready_index_count_ = 0; - if(disk_) disk_->flush_tracks(); + if(!motor_is_on) { + ready_index_count_ = 0; + if(disk_) disk_->flush_tracks(); + } + update_clocking_observer(); } - update_clocking_observer(); } bool Drive::get_motor_on() { From 70c4d6b9b31a260a41ff10490c3b8b76f0d18886 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 3 Aug 2018 21:13:18 -0400 Subject: [PATCH 22/31] Adds a one second delay between controller and drive motor off. --- Components/DiskII/DiskII.cpp | 16 ++++++++++++++-- Components/DiskII/DiskII.hpp | 1 + 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/Components/DiskII/DiskII.cpp b/Components/DiskII/DiskII.cpp index b80c68417..e915b61a7 100644 --- a/Components/DiskII/DiskII.cpp +++ b/Components/DiskII/DiskII.cpp @@ -120,6 +120,15 @@ void DiskII::run_for(const Cycles cycles) { if(!drive_is_sleeping_[1]) drives_[1].run_for(Cycles(1)); } + // Per comp.sys.apple2.programmer there is a delay between the controller + // motor switch being flipped and the drive motor actually switching off. + // This models that, accepting overrun as a risk. + if(motor_off_time_ >= 0) { + motor_off_time_ -= cycles.as_int(); + if(motor_off_time_ < 0) { + set_control(Control::Motor, false); + } + } decide_clocking_preference(); } @@ -238,9 +247,12 @@ int DiskII::read_address(int address) { case 0x8: shift_register_ = 0; - set_control(Control::Motor, false); + motor_off_time_ = clock_rate_; + break; + case 0x9: + set_control(Control::Motor, true); + motor_off_time_ = -1; break; - case 0x9: set_control(Control::Motor, true); break; case 0xa: select_drive(0); break; case 0xb: select_drive(1); break; diff --git a/Components/DiskII/DiskII.hpp b/Components/DiskII/DiskII.hpp index e95682101..dfd5b364a 100644 --- a/Components/DiskII/DiskII.hpp +++ b/Components/DiskII/DiskII.hpp @@ -109,6 +109,7 @@ class DiskII: int stepper_mask_ = 0; int stepper_position_ = 0; + int motor_off_time_ = -1; bool is_write_protected(); std::array state_machine_; From c4f86cc324bac01cfa0715e38eeb071ec83a67d5 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 3 Aug 2018 21:20:21 -0400 Subject: [PATCH 23/31] The Disk II now being its proper speed, withdraws the quickload option. --- Machines/AppleII/AppleII.cpp | 158 ++++---------------------- Machines/AppleII/AppleII.hpp | 3 - Machines/Utility/MachineForTarget.cpp | 1 - 3 files changed, 22 insertions(+), 140 deletions(-) diff --git a/Machines/AppleII/AppleII.cpp b/Machines/AppleII/AppleII.cpp index bf64b872a..ef4b36177 100644 --- a/Machines/AppleII/AppleII.cpp +++ b/Machines/AppleII/AppleII.cpp @@ -27,27 +27,17 @@ #include "../../Analyser/Static/AppleII/Target.hpp" #include "../../ClockReceiver/ForceInline.hpp" -#include "../../Configurable/Configurable.hpp" -#include "../../Storage/Disk/Track/TrackSerialiser.hpp" -#include "../../Storage/Disk/Encodings/AppleGCR/SegmentParser.hpp" #include #include #include -std::vector> AppleII::get_options() { - std::vector> options; - options.emplace_back(new Configurable::BooleanOption("Accelerate DOS 3.3", "quickload")); - return options; -} - namespace { template class ConcreteMachine: public CRTMachine::Machine, public MediaTarget::Machine, public KeyboardMachine::Machine, - public Configurable::Device, public CPU::MOS6502::BusHandler, public Inputs::Keyboard, public AppleII::Machine, @@ -256,9 +246,6 @@ template class ConcreteMachine: // MARK - typing std::unique_ptr string_serialiser_; - // MARK - quick loading - bool should_load_quickly_ = false; - // MARK - joysticks class Joystick: public Inputs::ConcreteJoystick { public: @@ -441,85 +428,6 @@ template class ConcreteMachine: set_card_paging(); } } - - if(should_load_quickly_) { - // Check for a prima facie entry into RWTS. - if(operation == CPU::MOS6502::BusOperation::ReadOpcode && address == 0xb7b5) { - // Grab the IO control block address for inspection. - uint16_t io_control_block_address = - static_cast( - (m6502_.get_value_of_register(CPU::MOS6502::Register::A) << 8) | - m6502_.get_value_of_register(CPU::MOS6502::Register::Y) - ); - - // Verify that this is table type one, for execution on card six, - // against drive 1 or 2, and that the command is either a seek or a sector read. - if( - ram_[io_control_block_address+0x00] == 0x01 && - ram_[io_control_block_address+0x01] == 0x60 && - ram_[io_control_block_address+0x02] > 0 && ram_[io_control_block_address+0x02] < 3 && - ram_[io_control_block_address+0x0c] < 2 - ) { - const uint8_t iob_track = ram_[io_control_block_address+4]; - const uint8_t iob_sector = ram_[io_control_block_address+5]; - const uint8_t iob_drive = ram_[io_control_block_address+2] - 1; - - // Get the track identified and store the new head position. - auto track = diskii_card()->get_drive(iob_drive).step_to(Storage::Disk::HeadPosition(iob_track)); - - // DOS 3.3 keeps the current track (unspecified drive) in 0x478; the current track for drive 1 and drive 2 - // is also kept in that Disk II card's screen hole. - ram_[0x478] = iob_track; - if(ram_[io_control_block_address+0x02] == 1) { - ram_[0x47e] = iob_track; - } else { - ram_[0x4fe] = iob_track; - } - - // Check whether this is a read, not merely a seek. - if(ram_[io_control_block_address+0x0c] == 1) { - // Apple the DOS 3.3 formula to map the requested logical sector to a physical sector. - const int physical_sector = (iob_sector == 15) ? 15 : ((iob_sector * 13) % 15); - - // Parse the entire track. TODO: cache these. - auto sector_map = Storage::Encodings::AppleGCR::sectors_from_segment( - Storage::Disk::track_serialisation(*track, Storage::Time(1, 50000))); - - bool found_sector = false; - for(const auto &pair: sector_map) { - if(pair.second.address.sector == physical_sector) { - found_sector = true; - - // Copy the sector contents to their destination. - uint16_t target = static_cast( - ram_[io_control_block_address+8] | - (ram_[io_control_block_address+9] << 8) - ); - - for(size_t c = 0; c < 256; ++c) { - ram_[target] = pair.second.data[c]; - ++target; - } - - // Set no error encountered. - ram_[io_control_block_address + 0xd] = 0; - break; - } - } - - if(found_sector) { - // Set no error in the flags register too, and RTS. - m6502_.set_value_of_register(CPU::MOS6502::Register::Flags, m6502_.get_value_of_register(CPU::MOS6502::Register::Flags) & ~1); - *value = 0x60; - } - } else { - // No error encountered; RTS. - m6502_.set_value_of_register(CPU::MOS6502::Register::Flags, m6502_.get_value_of_register(CPU::MOS6502::Register::Flags) & ~1); - *value = 0x60; - } - } - } - } } else { // Assume a vapour read unless it turns out otherwise; this is a little // wasteful but works for now. @@ -609,11 +517,18 @@ template class ConcreteMachine: // printf("Unknown (?) write to %04x\n", address); break; + case 0xc000: + case 0xc001: + video_->set_80_store(!!(address&1)); + set_main_paging(); + break; + case 0xc002: case 0xc003: read_auxiliary_memory_ = !!(address&1); set_main_paging(); break; + case 0xc004: case 0xc005: write_auxiliary_memory_ = !!(address&1); @@ -625,26 +540,6 @@ template class ConcreteMachine: internal_CX_rom_ = !!(address&1); set_card_paging(); break; - case 0xc00a: - case 0xc00b: - slot_C3_rom_ = !!(address&1); - set_card_paging(); - break; - - case 0xc00e: - case 0xc00f: video_->set_alternative_character_set(!!(address&1)); break; - - case 0xc00c: - case 0xc00d: video_->set_80_columns(!!(address&1)); break; - - case 0xc000: - case 0xc001: - video_->set_80_store(!!(address&1)); - set_main_paging(); - break; - - case 0xc05e: - case 0xc05f: video_->set_double_high_resolution(!(address&1)); break; case 0xc008: case 0xc009: @@ -654,6 +549,21 @@ template class ConcreteMachine: set_zero_page_paging(); set_language_card_paging(); break; + + case 0xc00a: + case 0xc00b: + slot_C3_rom_ = !!(address&1); + set_card_paging(); + break; + + case 0xc00c: + case 0xc00d: video_->set_80_columns(!!(address&1)); break; + + case 0xc00e: + case 0xc00f: video_->set_alternative_character_set(!!(address&1)); break; + + case 0xc05e: + case 0xc05f: video_->set_double_high_resolution(!(address&1)); break; } } } @@ -860,30 +770,6 @@ template class ConcreteMachine: } } - // MARK: Options - std::vector> get_options() override { - return AppleII::get_options(); - } - - void set_selections(const Configurable::SelectionSet &selections_by_option) override { - bool quickload; - if(Configurable::get_quick_load_tape(selections_by_option, quickload)) { - should_load_quickly_ = quickload; - } - } - - Configurable::SelectionSet get_accurate_selections() override { - Configurable::SelectionSet selection_set; - Configurable::append_quick_load_tape_selection(selection_set, false); - return selection_set; - } - - Configurable::SelectionSet get_user_friendly_selections() override { - Configurable::SelectionSet selection_set; - Configurable::append_quick_load_tape_selection(selection_set, true); - return selection_set; - } - // MARK: JoystickMachine std::vector> &get_joysticks() override { return joysticks_; diff --git a/Machines/AppleII/AppleII.hpp b/Machines/AppleII/AppleII.hpp index c680b3163..b0b8180aa 100644 --- a/Machines/AppleII/AppleII.hpp +++ b/Machines/AppleII/AppleII.hpp @@ -18,9 +18,6 @@ namespace AppleII { -/// @returns The options available for an Apple II. -std::vector> get_options(); - class Machine { public: virtual ~Machine(); diff --git a/Machines/Utility/MachineForTarget.cpp b/Machines/Utility/MachineForTarget.cpp index b6c2105a2..4d61e701e 100644 --- a/Machines/Utility/MachineForTarget.cpp +++ b/Machines/Utility/MachineForTarget.cpp @@ -130,7 +130,6 @@ std::map>> Machin std::map>> options; options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::AmstradCPC), AmstradCPC::get_options())); - options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::AppleII), AppleII::get_options())); options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::Electron), Electron::get_options())); options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::MSX), MSX::get_options())); options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::Oric), Oric::get_options())); From 94503ed771f648e2fd9514db566439479e01d8c2 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 4 Aug 2018 12:37:55 -0400 Subject: [PATCH 24/31] Disables the macOS Apple II options panel, since it now has no options. --- .../Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm b/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm index 29f15a930..b804d2c59 100644 --- a/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm +++ b/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm @@ -188,7 +188,7 @@ static Analyser::Static::ZX8081::Target::MemoryModel ZX8081MemoryModelFromSize(K - (NSString *)optionsPanelNibName { switch(_targets.front()->machine) { case Analyser::Machine::AmstradCPC: return @"CompositeOptions"; - case Analyser::Machine::AppleII: return @"AppleIIOptions"; +// case Analyser::Machine::AppleII: return @"AppleIIOptions"; case Analyser::Machine::Atari2600: return @"Atari2600Options"; case Analyser::Machine::Electron: return @"QuickLoadCompositeOptions"; case Analyser::Machine::MSX: return @"QuickLoadCompositeOptions"; From e97cc40a2c19224c7afaeb45196fdef298fdb140 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 4 Aug 2018 12:44:58 -0400 Subject: [PATCH 25/31] Corrects typo in Cx-page ROM paging. --- Machines/AppleII/AppleII.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Machines/AppleII/AppleII.cpp b/Machines/AppleII/AppleII.cpp index ef4b36177..a3690c17d 100644 --- a/Machines/AppleII/AppleII.cpp +++ b/Machines/AppleII/AppleII.cpp @@ -200,7 +200,7 @@ template class ConcreteMachine: bool internal_c8_rom_ = false; void set_card_paging() { - page(0xc1, 0xd8, internal_CX_rom_ ? rom_.data() : nullptr, nullptr); + page(0xc1, 0xc8, internal_CX_rom_ ? rom_.data() : nullptr, nullptr); if(!internal_CX_rom_) { if(!slot_C3_rom_) read_pages_[0xc3] = &rom_[0xc300 - 0xc100]; From 558b96bc054ecf2d73a47fbd0a81dc5283258949 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 4 Aug 2018 16:52:29 -0400 Subject: [PATCH 26/31] Corrects IIe text display. --- Machines/AppleII/AppleII.cpp | 9 ++++++--- Machines/AppleII/Video.cpp | 14 ++++++++++++++ Machines/AppleII/Video.hpp | 14 +++++++++----- ROMImages/AppleII/readme.txt | 1 + 4 files changed, 30 insertions(+), 8 deletions(-) diff --git a/Machines/AppleII/AppleII.cpp b/Machines/AppleII/AppleII.cpp index a3690c17d..0f079f1f0 100644 --- a/Machines/AppleII/AppleII.cpp +++ b/Machines/AppleII/AppleII.cpp @@ -62,7 +62,7 @@ template class ConcreteMachine: CPU::MOS6502::Processor m6502_; VideoBusHandler video_bus_handler_; - std::unique_ptr> video_; + std::unique_ptr> video_; int cycles_into_current_line_ = 0; Cycles cycles_since_video_update_; @@ -329,17 +329,20 @@ template class ConcreteMachine: // Pick the required ROMs. using Target = Analyser::Static::AppleII::Target; - std::vector rom_names = {"apple2-character.rom"}; + std::vector rom_names; size_t rom_size = 12*1024; switch(target.model) { default: + rom_names.push_back("apple2-character.rom"); rom_names.push_back("apple2o.rom"); break; case Target::Model::IIplus: + rom_names.push_back("apple2-character.rom"); rom_names.push_back("apple2.rom"); break; case Target::Model::IIe: rom_size += 3840; + rom_names.push_back("apple2eu-character.rom"); rom_names.push_back("apple2eu.rom"); break; } @@ -380,7 +383,7 @@ template class ConcreteMachine: } void setup_output(float aspect_ratio) override { - video_.reset(new AppleII::Video::Video(video_bus_handler_)); + video_.reset(new AppleII::Video::Video(video_bus_handler_)); video_->set_character_rom(character_rom_); } diff --git a/Machines/AppleII/Video.cpp b/Machines/AppleII/Video.cpp index 80fdb9361..9cbfc7302 100644 --- a/Machines/AppleII/Video.cpp +++ b/Machines/AppleII/Video.cpp @@ -100,4 +100,18 @@ bool VideoBase::get_double_high_resolution() { void VideoBase::set_character_rom(const std::vector &character_rom) { character_rom_ = character_rom; + + // Flip all character contents based on the second line of the $ graphic. + if(character_rom_[0x121] == 0x3c || character_rom_[0x122] == 0x3c) { + for(auto &graphic : character_rom_) { + graphic = + ((graphic & 0x01) ? 0x40 : 0x00) | + ((graphic & 0x02) ? 0x20 : 0x00) | + ((graphic & 0x04) ? 0x10 : 0x00) | + ((graphic & 0x08) ? 0x08 : 0x00) | + ((graphic & 0x10) ? 0x04 : 0x00) | + ((graphic & 0x20) ? 0x02 : 0x00) | + ((graphic & 0x40) ? 0x01 : 0x00); + } + } } diff --git a/Machines/AppleII/Video.hpp b/Machines/AppleII/Video.hpp index 630e077f8..2a2771d1b 100644 --- a/Machines/AppleII/Video.hpp +++ b/Machines/AppleII/Video.hpp @@ -191,7 +191,7 @@ class VideoBase { std::vector character_rom_; }; -template class Video: public VideoBase { +template class Video: public VideoBase { public: /// Constructs an instance of the video feed; a CRT is also created. Video(BusHandler &bus_handler) : @@ -262,15 +262,19 @@ template class Video: public VideoBase { case GraphicsMode::Text: { const uint8_t inverses[] = { 0xff, - static_cast((flash_ / flash_length) * 0xff), + alternative_character_set_ ? static_cast(0xff) : static_cast((flash_ / flash_length) * 0xff), 0x00, 0x00 }; + const uint8_t masks[] = { + alternative_character_set_ ? static_cast(0x7f) : static_cast(0x3f), + is_iie ? 0x7f : 0x3f, + }; for(int c = column_; c < pixel_end; ++c) { const uint8_t character = bus_handler_.perform_read(static_cast(text_address + c)); - const std::size_t character_address = static_cast(((character & 0x3f) << 3) + pixel_row); - - const uint8_t character_pattern = character_rom_[character_address] ^ inverses[character >> 6]; + const uint8_t xor_mask = inverses[character >> 6]; + const std::size_t character_address = static_cast(((character & masks[character >> 7]) << 3) + pixel_row); + const uint8_t character_pattern = character_rom_[character_address] ^ xor_mask; // The character ROM is output MSB to LSB rather than LSB to MSB. pixel_pointer_[0] = character_pattern & 0x40; diff --git a/ROMImages/AppleII/readme.txt b/ROMImages/AppleII/readme.txt index c6af4423d..0cb8c1131 100644 --- a/ROMImages/AppleII/readme.txt +++ b/ROMImages/AppleII/readme.txt @@ -8,5 +8,6 @@ apple2e.rom — a file at least 15.75kb big, in which the final 12kb is the main apple2eu.rom — as per apple2e.rom, but for the Unenhanced Apple II. apple2-character.rom — a 2kb image of the Apple IIe's character ROM. +apple2eu-character.rom — a 4kb image of the Unenhanced IIe's character ROM. Apologies for the wackiness around "at least xkb big", it's to allow for use of files such as those on ftp.apple.asimov.net, which tend to be a bunch of other things, then the system ROM. \ No newline at end of file From 3aeb4213fe9464b98519615e4a203d277017de26 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 4 Aug 2018 17:57:02 -0400 Subject: [PATCH 27/31] Implements the C010 read value. --- Machines/AppleII/AppleII.cpp | 45 ++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/Machines/AppleII/AppleII.cpp b/Machines/AppleII/AppleII.cpp index 0f079f1f0..6f6bba106 100644 --- a/Machines/AppleII/AppleII.cpp +++ b/Machines/AppleII/AppleII.cpp @@ -85,6 +85,7 @@ template class ConcreteMachine: std::vector rom_; std::vector character_rom_; uint8_t keyboard_input_ = 0x00; + bool key_is_down_ = false; Concurrency::DeferringAsyncTaskQueue audio_queue_; Audio::Toggle audio_toggle_; @@ -149,17 +150,12 @@ template class ConcreteMachine: 2000 to 4000 : the graphics screen, which can be configured to write to auxiliary RAM c100 to d000 : can be used to page an additional 3.75kb of ROM, replacing the IO area c300 to c400 : can contain the same 256-byte segment of the ROM as if the whole IO area were switched, but while leaving cards visible in the rest + c800 to d000 : can contain ROM separately from the region below c800 If dealt with as individual blocks in the inner loop, that would therefore imply mapping - an address to one of 12 potential pageable zones. So I've gone reductive and surrendered + an address to one of 13 potential pageable zones. So I've gone reductive and surrendered to paging every 6502 page of memory independently. It makes the paging events more expensive, - but hopefully is clear. - - Those 12 block, for the record: - - 0000 to 0200; 0200 to 0400; 0400 to 0800; 0800 to 2000; - 2000 to 4000; 4000 to c000; c000 to c100; c100 to c300; - c300 to c400; c400 to d000; d000 to e000; e000+ + but hopefully more clear. */ uint8_t *read_pages_[256]; // each is a pointer to the 256-block of memory the CPU should read when accessing that page of memory uint8_t *write_pages_[256]; // as per read_pages_, but this is where the CPU should write. If a pointer is nullptr, don't write. @@ -614,8 +610,7 @@ template class ConcreteMachine: // On the IIe, reading C010 returns additional key info. if(is_iie && isReadOperation(operation)) { - // TODO! - *value = 0; + *value = (key_is_down_ ? 0x80 : 0x00) | (keyboard_input_ & 0x7f); } break; @@ -732,20 +727,26 @@ template class ConcreteMachine: return; } - if(is_pressed) { - // If no ASCII value is supplied, look for a few special cases. - if(!value) { - switch(key) { - case Key::Left: value = 0x08; break; - case Key::Right: value = 0x15; break; - case Key::Down: value = 0x0a; break; - case Key::Up: value = 0x0b; break; - case Key::BackSpace: value = 0x7f; break; - default: break; - } + // If no ASCII value is supplied, look for a few special cases. + if(!value) { + switch(key) { + case Key::Left: value = 0x08; break; + case Key::Right: value = 0x15; break; + case Key::Down: value = 0x0a; break; + case Key::Up: value = 0x0b; break; + case Key::BackSpace: value = 0x7f; break; + default: return; } + } - keyboard_input_ = static_cast(toupper(value) | 0x80); + value = static_cast(toupper(value)); + if(is_pressed) { + keyboard_input_ = static_cast(value | 0x80); + key_is_down_ = true; + } else { + if((keyboard_input_ & 0x7f) == value) { + key_is_down_ = false; + } } } From 086596c28efb3e55383f77df0396470e1dce6044 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 4 Aug 2018 19:17:04 -0400 Subject: [PATCH 28/31] Adds reading of vertical blank and implements the full IIe keyboard logic. i.e. there are now two Apple keys, and shift isn't assumed. --- Machines/AppleII/AppleII.cpp | 71 ++++++++++++++++++++++-------------- Machines/AppleII/Video.hpp | 17 +++++++++ 2 files changed, 60 insertions(+), 28 deletions(-) diff --git a/Machines/AppleII/AppleII.cpp b/Machines/AppleII/AppleII.cpp index 6f6bba106..73c38ac53 100644 --- a/Machines/AppleII/AppleII.cpp +++ b/Machines/AppleII/AppleII.cpp @@ -292,6 +292,10 @@ template class ConcreteMachine: return static_cast(joysticks_[channel >> 1].get())->axes[channel & 1] < analogue_charge_ + analogue_biases_[channel]; } + // The IIe has three keys that are wired directly to the same input as the joystick buttons. + bool open_apple_is_pressed_ = false; + bool closed_apple_is_pressed_ = false; + public: ConcreteMachine(const Analyser::Static::AppleII::Target &target, const ROMMachine::ROMFetcher &rom_fetcher): m6502_(*this), @@ -450,9 +454,7 @@ template class ConcreteMachine: if(isReadOperation(operation)) { // Read-only switches. switch(address) { - default: -// printf("Unknown (?) read from %04x\n", address); - break; + default: break; case 0xc000: if(string_serialiser_) { @@ -464,12 +466,18 @@ template class ConcreteMachine: case 0xc061: // Switch input 0. *value &= 0x7f; - if(static_cast(joysticks_[0].get())->buttons[0] || static_cast(joysticks_[1].get())->buttons[2]) + if( + static_cast(joysticks_[0].get())->buttons[0] || static_cast(joysticks_[1].get())->buttons[2] || + (is_iie && open_apple_is_pressed_) + ) *value |= 0x80; break; case 0xc062: // Switch input 1. *value &= 0x7f; - if(static_cast(joysticks_[0].get())->buttons[1] || static_cast(joysticks_[1].get())->buttons[1]) + if( + static_cast(joysticks_[0].get())->buttons[1] || static_cast(joysticks_[1].get())->buttons[1] || + (is_iie && closed_apple_is_pressed_) + ) *value |= 0x80; break; case 0xc063: // Switch input 2. @@ -490,31 +498,28 @@ template class ConcreteMachine: } break; // The IIe-only state reads follow... - case 0xc011: if(is_iie) *value = (*value & 0x7f) | (language_card_.bank1 ? 0x80 : 0x00); break; - case 0xc012: if(is_iie) *value = (*value & 0x7f) | (language_card_.read ? 0x80 : 0x00); break; - case 0xc013: if(is_iie) *value = (*value & 0x7f) | (read_auxiliary_memory_ ? 0x80 : 0x00); break; - case 0xc014: if(is_iie) *value = (*value & 0x7f) | (write_auxiliary_memory_ ? 0x80 : 0x00); break; - case 0xc015: if(is_iie) *value = (*value & 0x7f) | (internal_CX_rom_ ? 0x80 : 0x00); break; - case 0xc016: if(is_iie) *value = (*value & 0x7f) | (alternative_zero_page_ ? 0x80 : 0x00); break; - case 0xc017: if(is_iie) *value = (*value & 0x7f) | (slot_C3_rom_ ? 0x80 : 0x00); break; - case 0xc018: if(is_iie) *value = (*value & 0x7f) | (video_->get_80_store() ? 0x80 : 0x00); break; - // TODO: c019 to read vertical blanking - case 0xc01a: if(is_iie) *value = (*value & 0x7f) | (video_->get_text() ? 0x80 : 0x00); break; - case 0xc01b: if(is_iie) *value = (*value & 0x7f) | (video_->get_mixed() ? 0x80 : 0x00); break; - case 0xc01c: if(is_iie) *value = (*value & 0x7f) | (video_->get_page2() ? 0x80 : 0x00); break; - case 0xc01d: if(is_iie) *value = (*value & 0x7f) | (video_->get_high_resolution() ? 0x80 : 0x00); break; - case 0xc01e: if(is_iie) *value = (*value & 0x7f) | (video_->get_alternative_character_set() ? 0x80 : 0x00); break; - case 0xc01f: if(is_iie) *value = (*value & 0x7f) | (video_->get_80_columns() ? 0x80 : 0x00); break; - case 0xc07f: if(is_iie) *value = (*value & 0x7f) | (video_->get_double_high_resolution() ? 0x80 : 0x00); break; + case 0xc011: if(is_iie) *value = (*value & 0x7f) | (language_card_.bank1 ? 0x80 : 0x00); break; + case 0xc012: if(is_iie) *value = (*value & 0x7f) | (language_card_.read ? 0x80 : 0x00); break; + case 0xc013: if(is_iie) *value = (*value & 0x7f) | (read_auxiliary_memory_ ? 0x80 : 0x00); break; + case 0xc014: if(is_iie) *value = (*value & 0x7f) | (write_auxiliary_memory_ ? 0x80 : 0x00); break; + case 0xc015: if(is_iie) *value = (*value & 0x7f) | (internal_CX_rom_ ? 0x80 : 0x00); break; + case 0xc016: if(is_iie) *value = (*value & 0x7f) | (alternative_zero_page_ ? 0x80 : 0x00); break; + case 0xc017: if(is_iie) *value = (*value & 0x7f) | (slot_C3_rom_ ? 0x80 : 0x00); break; + case 0xc018: if(is_iie) *value = (*value & 0x7f) | (video_->get_80_store() ? 0x80 : 0x00); break; + case 0xc019: if(is_iie) *value = (*value & 0x7f) | (video_->get_is_vertical_blank(cycles_since_video_update_) ? 0x80 : 0x00); break; + case 0xc01a: if(is_iie) *value = (*value & 0x7f) | (video_->get_text() ? 0x80 : 0x00); break; + case 0xc01b: if(is_iie) *value = (*value & 0x7f) | (video_->get_mixed() ? 0x80 : 0x00); break; + case 0xc01c: if(is_iie) *value = (*value & 0x7f) | (video_->get_page2() ? 0x80 : 0x00); break; + case 0xc01d: if(is_iie) *value = (*value & 0x7f) | (video_->get_high_resolution() ? 0x80 : 0x00); break; + case 0xc01e: if(is_iie) *value = (*value & 0x7f) | (video_->get_alternative_character_set() ? 0x80 : 0x00); break; + case 0xc01f: if(is_iie) *value = (*value & 0x7f) | (video_->get_80_columns() ? 0x80 : 0x00); break; + case 0xc07f: if(is_iie) *value = (*value & 0x7f) | (video_->get_double_high_resolution() ? 0x80 : 0x00); break; } } else { // Write-only switches. All IIe as currently implemented. if(is_iie) { -// if(address >= 0xc000 && address < 0xc100) printf("w %04x\n", address); switch(address) { - default: -// printf("Unknown (?) write to %04x\n", address); - break; + default: break; case 0xc000: case 0xc001: @@ -722,8 +727,16 @@ template class ConcreteMachine: } void set_key_pressed(Key key, char value, bool is_pressed) override { - if(key == Key::F12) { - m6502_.set_reset_line(is_pressed); + switch(key) { + default: break; + case Key::F12: + m6502_.set_reset_line(is_pressed); + return; + case Key::LeftMeta: case Key::LeftOption: + open_apple_is_pressed_ = is_pressed; + return; + case Key::RightMeta: case Key::RightOption: + closed_apple_is_pressed_ = is_pressed; return; } @@ -739,7 +752,9 @@ template class ConcreteMachine: } } - value = static_cast(toupper(value)); + // Prior to the IIe, the keyboard could produce uppercase only. + if(!is_iie) value = static_cast(toupper(value)); + if(is_pressed) { keyboard_input_ = static_cast(value | 0x80); key_is_down_ = true; diff --git a/Machines/AppleII/Video.hpp b/Machines/AppleII/Video.hpp index 2a2771d1b..097a7709f 100644 --- a/Machines/AppleII/Video.hpp +++ b/Machines/AppleII/Video.hpp @@ -536,6 +536,23 @@ template class Video: public VideoBase { return bus_handler_.perform_read(read_address); } + /*! + @returns @c true if the display will be within vertical blank at now + @c offset; @c false otherwise. + */ + bool get_is_vertical_blank(Cycles offset) { + // Map that backwards from the internal pixels-at-start generation to pixels-at-end + // (so what was column 0 is now column 25). + int mapped_column = column_ + offset.as_int(); + + // Map that backwards from the internal pixels-at-start generation to pixels-at-end + // (so what was column 0 is now column 25). + mapped_column += 25; + + // Apply carry into the row counter and test it for location. + int mapped_row = row_ + (mapped_column / 65); + return (mapped_row % 262) >= 192; + } + private: GraphicsMode graphics_mode(int row) { if(text_) return columns_80_ ? GraphicsMode::DoubleText : GraphicsMode::Text; From 099d66804ed11c8345b3c4e38d68b763ad87b7d7 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 4 Aug 2018 19:29:34 -0400 Subject: [PATCH 29/31] Makes colour burst phase explicit. --- Machines/AppleII/Video.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Machines/AppleII/Video.hpp b/Machines/AppleII/Video.hpp index 097a7709f..92d94bdb4 100644 --- a/Machines/AppleII/Video.hpp +++ b/Machines/AppleII/Video.hpp @@ -472,7 +472,7 @@ template class Video: public VideoBase { const int colour_burst_start = std::max(first_sync_column + sync_length + 1, column_); const int colour_burst_end = std::min(first_sync_column + sync_length + 4, ending_column); if(colour_burst_end > colour_burst_start) { - crt_->output_default_colour_burst(static_cast(colour_burst_end - colour_burst_start) * 14); + crt_->output_colour_burst(static_cast(colour_burst_end - colour_burst_start) * 14, 128); } second_blank_start = std::max(first_sync_column + 7, column_); From 85fb4773b0d9d0a85cbb58bff404a15627dfa581 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 4 Aug 2018 20:31:37 -0400 Subject: [PATCH 30/31] Tweaks Apple key mapping and implements `reset_all_keys`. --- Machines/AppleII/AppleII.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Machines/AppleII/AppleII.cpp b/Machines/AppleII/AppleII.cpp index 73c38ac53..afd557cb2 100644 --- a/Machines/AppleII/AppleII.cpp +++ b/Machines/AppleII/AppleII.cpp @@ -726,16 +726,20 @@ template class ConcreteMachine: m6502_.run_for(cycles); } + void reset_all_keys() override { + open_apple_is_pressed_ = closed_apple_is_pressed_ = key_is_down_ = false; + } + void set_key_pressed(Key key, char value, bool is_pressed) override { switch(key) { default: break; case Key::F12: m6502_.set_reset_line(is_pressed); return; - case Key::LeftMeta: case Key::LeftOption: + case Key::LeftOption: open_apple_is_pressed_ = is_pressed; return; - case Key::RightMeta: case Key::RightOption: + case Key::RightOption: closed_apple_is_pressed_ = is_pressed; return; } From 26624d7652b2a0b6cc2c6613f8c828785c069cda Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 4 Aug 2018 20:57:02 -0400 Subject: [PATCH 31/31] Fixes vertical blank signal; it should be the other way around. --- Machines/AppleII/AppleII.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Machines/AppleII/AppleII.cpp b/Machines/AppleII/AppleII.cpp index afd557cb2..c8228009e 100644 --- a/Machines/AppleII/AppleII.cpp +++ b/Machines/AppleII/AppleII.cpp @@ -506,7 +506,7 @@ template class ConcreteMachine: case 0xc016: if(is_iie) *value = (*value & 0x7f) | (alternative_zero_page_ ? 0x80 : 0x00); break; case 0xc017: if(is_iie) *value = (*value & 0x7f) | (slot_C3_rom_ ? 0x80 : 0x00); break; case 0xc018: if(is_iie) *value = (*value & 0x7f) | (video_->get_80_store() ? 0x80 : 0x00); break; - case 0xc019: if(is_iie) *value = (*value & 0x7f) | (video_->get_is_vertical_blank(cycles_since_video_update_) ? 0x80 : 0x00); break; + case 0xc019: if(is_iie) *value = (*value & 0x7f) | (video_->get_is_vertical_blank(cycles_since_video_update_) ? 0x00 : 0x80); break; case 0xc01a: if(is_iie) *value = (*value & 0x7f) | (video_->get_text() ? 0x80 : 0x00); break; case 0xc01b: if(is_iie) *value = (*value & 0x7f) | (video_->get_mixed() ? 0x80 : 0x00); break; case 0xc01c: if(is_iie) *value = (*value & 0x7f) | (video_->get_page2() ? 0x80 : 0x00); break;