From 949cfcfa6906dfad041068596e27779bc561a389 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 9 Dec 2024 17:31:00 -0500 Subject: [PATCH 001/103] Load ROMs. --- Machines/Commodore/Plus4/Plus4.cpp | 14 ++++++++++++-- Machines/Utility/ROMCatalogue.hpp | 4 ++-- ROMImages/Plus4/readme.txt | 12 ++++++++++++ 3 files changed, 26 insertions(+), 4 deletions(-) create mode 100644 ROMImages/Plus4/readme.txt diff --git a/Machines/Commodore/Plus4/Plus4.cpp b/Machines/Commodore/Plus4/Plus4.cpp index 7832c35d7f..a3506bb1f9 100644 --- a/Machines/Commodore/Plus4/Plus4.cpp +++ b/Machines/Commodore/Plus4/Plus4.cpp @@ -19,11 +19,17 @@ namespace { class ConcreteMachine: public MachineTypes::TimedMachine, public MachineTypes::ScanProducer, + public MachineTypes::MediaTarget, public Machine { public: ConcreteMachine(const Analyser::Static::Commodore::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) { - (void)target; - (void)rom_fetcher; + const ROM::Request request = ROM::Request(ROM::Name::Plus4BASIC) && ROM::Request(ROM::Name::Plus4KernelPALv5); + auto roms = rom_fetcher(request); + if(!request.validate(roms)) { + throw ROMMachine::Error::MissingROMs; + } + + insert_media(target.media); } private: @@ -36,6 +42,10 @@ class ConcreteMachine: void run_for(const Cycles) final { } + + bool insert_media(const Analyser::Static::Media &) final { + return true; + } }; } diff --git a/Machines/Utility/ROMCatalogue.hpp b/Machines/Utility/ROMCatalogue.hpp index 67ffe2423e..d366e0d2e9 100644 --- a/Machines/Utility/ROMCatalogue.hpp +++ b/Machines/Utility/ROMCatalogue.hpp @@ -256,7 +256,7 @@ struct Request { bool empty(); /// @returns what remains of this ROM request given that everything in @c map has been found. - Request subtract(const ROM::Map &map) const; + Request subtract(const Map &map) const; enum class ListType { Any, All, Single @@ -303,7 +303,7 @@ struct Request { const std::function &exit_list, const std::function &add_item ) const; - bool subtract(const ROM::Map &map); + bool subtract(const Map &map); void sort() { // Don't do a full sort, but move anything optional to the back. // This makes them print more nicely; it's a human-facing tweak only. diff --git a/ROMImages/Plus4/readme.txt b/ROMImages/Plus4/readme.txt new file mode 100644 index 0000000000..0a42cb1852 --- /dev/null +++ b/ROMImages/Plus4/readme.txt @@ -0,0 +1,12 @@ +ROM files would ordinarily go here; the copyright status of these is uncertain so they have not been included in this repository. + +Expected files: + +At least one of: +kernal.318004-03.bin — the PAL kernel, v3; +kernal.318004-04.bin — the PAL kernel, v4; and +kernal.318004-05.bin — the PAL kernel, v5. + + +And: +basic.318006-01.bin — the BASIC 3.5 ROM. From cbde504057feff18f7aa0e4fd5da9e3b07a373ed Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 9 Dec 2024 17:46:31 -0500 Subject: [PATCH 002/103] Add a memory map of sorts and a 6502. --- Machines/Commodore/Plus4/Plus4.cpp | 77 ++++++++++++++++++++++++++++-- 1 file changed, 74 insertions(+), 3 deletions(-) diff --git a/Machines/Commodore/Plus4/Plus4.cpp b/Machines/Commodore/Plus4/Plus4.cpp index a3506bb1f9..5cdb47c84b 100644 --- a/Machines/Commodore/Plus4/Plus4.cpp +++ b/Machines/Commodore/Plus4/Plus4.cpp @@ -10,29 +10,94 @@ #include "../../MachineTypes.hpp" +#include "../../../Processors/6502/6502.hpp" + #include "../../../Analyser/Static/Commodore/Target.hpp" using namespace Commodore::Plus4; namespace { +template +class Pager { +public: + DataT read(AddressT address) { + return read_[address >> Shift][address]; + } + DataT &write(AddressT address) { + return write_[address >> Shift][address]; + } + + template + void page(const uint8_t *read, uint8_t *write) { + write_[slot] = write - (slot << Shift); + read_[slot] = read - (slot << Shift); + } + +private: + std::array write_{}; + std::array read_{}; + + static constexpr auto AddressBits = sizeof(AddressT) * 8; + static constexpr auto PageSize = (1 << AddressBits) / NumPages; + static_assert(!(PageSize & (PageSize - 1)), "Pages must be a power of two in size"); + + static constexpr int ln2(int value) { + int result = 0; + while(value != 1) { + value >>= 1; + ++result; + } + return result; + } + static constexpr auto Shift = ln2(PageSize); +}; + class ConcreteMachine: + public CPU::MOS6502::BusHandler, public MachineTypes::TimedMachine, public MachineTypes::ScanProducer, public MachineTypes::MediaTarget, public Machine { public: - ConcreteMachine(const Analyser::Static::Commodore::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) { - const ROM::Request request = ROM::Request(ROM::Name::Plus4BASIC) && ROM::Request(ROM::Name::Plus4KernelPALv5); + ConcreteMachine(const Analyser::Static::Commodore::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) : + m6502_(*this) + { + const auto kernel = ROM::Name::Plus4KernelPALv5; + const auto basic = ROM::Name::Plus4BASIC; + + const ROM::Request request = ROM::Request(basic) && ROM::Request(kernel); auto roms = rom_fetcher(request); if(!request.validate(roms)) { throw ROMMachine::Error::MissingROMs; } + kernel_ = roms.find(kernel)->second; + basic_ = roms.find(basic)->second; + + map_.page<0>(ram_.data(), ram_.data()); + map_.page<1>(ram_.data() + 16*1024, ram_.data() + 16*1024); + map_.page<2>(basic_.data(), ram_.data() + 32*1024); + map_.page<3>(kernel_.data(), ram_.data() + 48*1024); + insert_media(target.media); } + Cycles perform_bus_operation( + const CPU::MOS6502::BusOperation operation, + const uint16_t address, + uint8_t *const value + ) { + (void)operation; + (void)address; + (void)value; + + return Cycles(1); + } + private: + CPU::MOS6502::Processor m6502_; + void set_scan_target(Outputs::Display::ScanTarget *const) final { } @@ -40,12 +105,18 @@ class ConcreteMachine: return {}; } - void run_for(const Cycles) final { + void run_for(const Cycles cycles) final { + m6502_.run_for(cycles); } bool insert_media(const Analyser::Static::Media &) final { return true; } + + Pager map_; + std::array ram_; + std::vector kernel_; + std::vector basic_; }; } From 064c4b43129f85d180e9219c4fa5d93464315757 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 9 Dec 2024 22:22:20 -0500 Subject: [PATCH 003/103] Add some logging. --- Machines/Commodore/Plus4/Plus4.cpp | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/Machines/Commodore/Plus4/Plus4.cpp b/Machines/Commodore/Plus4/Plus4.cpp index 5cdb47c84b..a526087849 100644 --- a/Machines/Commodore/Plus4/Plus4.cpp +++ b/Machines/Commodore/Plus4/Plus4.cpp @@ -63,6 +63,12 @@ class ConcreteMachine: ConcreteMachine(const Analyser::Static::Commodore::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) : m6502_(*this) { + // PAL: 8'867'240 divided by 5 or 4? + // NTSC: 7'159'090? + // i.e. colour subcarriers multiplied by two? + + set_clock_rate(8'867'240); // TODO. + const auto kernel = ROM::Name::Plus4KernelPALv5; const auto basic = ROM::Name::Plus4BASIC; @@ -88,11 +94,24 @@ class ConcreteMachine: const uint16_t address, uint8_t *const value ) { - (void)operation; - (void)address; - (void)value; +// printf("%04x\n", address); + + if(address >= 0xfd00 && address < 0xff40) { + if(isReadOperation(operation)) { + printf("TODO: TED read @ %04x\n", address); + } else { + printf("TODO: TED write of %02x @ %04x\n", *value, address); + } + return Cycles(5); + } + + if(isReadOperation(operation)) { + *value = map_.read(address); + } else { + map_.write(address) = *value; + } - return Cycles(1); + return Cycles(5); } private: From 6b7edac6e4acfceb5d436efe06353e131f5abd7e Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 10 Dec 2024 18:04:10 -0500 Subject: [PATCH 004/103] Add timers. --- Machines/Commodore/Plus4/Plus4.cpp | 107 ++++++++++++++++++++++++++++- 1 file changed, 104 insertions(+), 3 deletions(-) diff --git a/Machines/Commodore/Plus4/Plus4.cpp b/Machines/Commodore/Plus4/Plus4.cpp index a526087849..d14d5dbdbd 100644 --- a/Machines/Commodore/Plus4/Plus4.cpp +++ b/Machines/Commodore/Plus4/Plus4.cpp @@ -53,6 +53,73 @@ class Pager { static constexpr auto Shift = ln2(PageSize); }; +class Timers { +public: + template + void write(const uint8_t value) { + const auto load_low = [&](uint16_t &target) { + target = uint16_t((target & 0xff00) | (value << 0)); + }; + const auto load_high = [&](uint16_t &target) { + target = uint16_t((target & 0x00ff) | (value << 8)); + }; + + constexpr auto timer = offset >> 1; + paused_[timer] = !(offset & 1); + if constexpr (offset & 1) { + load_high(timers_[timer]); + if(!timer) { + load_high(timer0_reload_); + } + } else { + load_low(timers_[timer]); + if(!timer) { + load_low(timer0_reload_); + } + } + } + + template + uint8_t read() { + constexpr auto timer = offset >> 1; + if constexpr (offset & 1) { + return uint8_t(timers_[timer] >> 8); + } else { + return uint8_t(timers_[timer] >> 0); + } + } + + void tick(int count) { + // Quick hack here; do better than stepping through one at a time. + while(count--) { + decrement<0>(); + decrement<1>(); + decrement<2>(); + } + } + +private: + template + void decrement() { + if(paused_[timer]) return; + + // Check for reload. + if(!timer && !timers_[timer]) { + timers_[timer] = timer0_reload_; + } + + -- timers_[timer]; + + // Check for interrupt. + if(!timers_[timer]) { + } + } + + uint16_t timers_[3]{}; + uint16_t timer0_reload_ = 0xffff; + bool paused_[3]{}; +}; + class ConcreteMachine: public CPU::MOS6502::BusHandler, public MachineTypes::TimedMachine, @@ -94,7 +161,38 @@ class ConcreteMachine: const uint16_t address, uint8_t *const value ) { -// printf("%04x\n", address); + // TODO: calculate length of this bus operation. + const auto length = Cycles(5); + + // Update other subsystems. + // TODO: timers decrement at a 894 KHz rate for NTSC television systems, 884 KHZ for PAL systems. + // Probably a function of the speed register? + timers_subcycles_ += length; + const auto timers_cycles = timers_subcycles_.divide(Cycles(5)); + timers_.tick(timers_cycles.as()); + + // Perform actual access. + if(address >= 0xff00 && address < 0xff40) { + if(isReadOperation(operation)) { + switch(address) { + case 0xff00: *value = timers_.read<0>(); break; + case 0xff01: *value = timers_.read<1>(); break; + case 0xff02: *value = timers_.read<2>(); break; + case 0xff03: *value = timers_.read<3>(); break; + case 0xff04: *value = timers_.read<4>(); break; + case 0xff05: *value = timers_.read<5>(); break; + } + } else { + switch(address) { + case 0xff00: timers_.write<0>(*value); break; + case 0xff01: timers_.write<1>(*value); break; + case 0xff02: timers_.write<2>(*value); break; + case 0xff03: timers_.write<3>(*value); break; + case 0xff04: timers_.write<4>(*value); break; + case 0xff05: timers_.write<5>(*value); break; + } + } + } if(address >= 0xfd00 && address < 0xff40) { if(isReadOperation(operation)) { @@ -102,7 +200,7 @@ class ConcreteMachine: } else { printf("TODO: TED write of %02x @ %04x\n", *value, address); } - return Cycles(5); + return length; } if(isReadOperation(operation)) { @@ -111,7 +209,7 @@ class ConcreteMachine: map_.write(address) = *value; } - return Cycles(5); + return length; } private: @@ -136,6 +234,9 @@ class ConcreteMachine: std::array ram_; std::vector kernel_; std::vector basic_; + + Cycles timers_subcycles_; + Timers timers_; }; } From 891d5c2066b0c5610e08263789c69a52e9873be0 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 10 Dec 2024 18:07:07 -0500 Subject: [PATCH 005/103] Separate out TED calls, to aid with logging. --- Machines/Commodore/Plus4/Plus4.cpp | 36 +++++++++++++++++------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/Machines/Commodore/Plus4/Plus4.cpp b/Machines/Commodore/Plus4/Plus4.cpp index d14d5dbdbd..77aa25ef7d 100644 --- a/Machines/Commodore/Plus4/Plus4.cpp +++ b/Machines/Commodore/Plus4/Plus4.cpp @@ -172,7 +172,20 @@ class ConcreteMachine: timers_.tick(timers_cycles.as()); // Perform actual access. - if(address >= 0xff00 && address < 0xff40) { + if(address < 0xfd00 || address >= 0xff40) { + if(isReadOperation(operation)) { + *value = map_.read(address); + } else { + map_.write(address) = *value; + } + } else if(address < 0xff00) { + // Miscellaneous hardware. All TODO. +// if(isReadOperation(operation)) { +// printf("TODO: read @ %04x\n", address); +// } else { +// printf("TODO: write of %02x @ %04x\n", *value, address); +// } + } else { if(isReadOperation(operation)) { switch(address) { case 0xff00: *value = timers_.read<0>(); break; @@ -181,6 +194,9 @@ class ConcreteMachine: case 0xff03: *value = timers_.read<3>(); break; case 0xff04: *value = timers_.read<4>(); break; case 0xff05: *value = timers_.read<5>(); break; + + default: + printf("TODO: TED read at %04x\n", address); } } else { switch(address) { @@ -190,23 +206,11 @@ class ConcreteMachine: case 0xff03: timers_.write<3>(*value); break; case 0xff04: timers_.write<4>(*value); break; case 0xff05: timers_.write<5>(*value); break; - } - } - } - if(address >= 0xfd00 && address < 0xff40) { - if(isReadOperation(operation)) { - printf("TODO: TED read @ %04x\n", address); - } else { - printf("TODO: TED write of %02x @ %04x\n", *value, address); + default: + printf("TODO: TED write at %04x\n", address); + } } - return length; - } - - if(isReadOperation(operation)) { - *value = map_.read(address); - } else { - map_.write(address) = *value; } return length; From 38325741dedc6148c6aa331065cb599ea804e3fd Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 10 Dec 2024 21:28:46 -0500 Subject: [PATCH 006/103] Forward address information to a video stub. --- Machines/Commodore/Plus4/Plus4.cpp | 14 +++- Machines/Commodore/Plus4/Video.hpp | 81 +++++++++++++++++++ .../Clock Signal.xcodeproj/project.pbxproj | 2 + 3 files changed, 95 insertions(+), 2 deletions(-) create mode 100644 Machines/Commodore/Plus4/Video.hpp diff --git a/Machines/Commodore/Plus4/Plus4.cpp b/Machines/Commodore/Plus4/Plus4.cpp index 77aa25ef7d..e638a3806d 100644 --- a/Machines/Commodore/Plus4/Plus4.cpp +++ b/Machines/Commodore/Plus4/Plus4.cpp @@ -8,10 +8,10 @@ #include "Plus4.hpp" -#include "../../MachineTypes.hpp" +#include "Video.hpp" +#include "../../MachineTypes.hpp" #include "../../../Processors/6502/6502.hpp" - #include "../../../Analyser/Static/Commodore/Target.hpp" using namespace Commodore::Plus4; @@ -207,6 +207,15 @@ class ConcreteMachine: case 0xff04: timers_.write<4>(*value); break; case 0xff05: timers_.write<5>(*value); break; + case 0xff06: video_.write<0xff06>(*value); break; + case 0xff07: video_.write<0xff07>(*value); break; + case 0xff0c: video_.write<0xff0c>(*value); break; + case 0xff0d: video_.write<0xff0d>(*value); break; + case 0xff12: video_.write<0xff12>(*value); break; + case 0xff14: video_.write<0xff14>(*value); break; + case 0xff1a: video_.write<0xff1a>(*value); break; + case 0xff1b: video_.write<0xff1b>(*value); break; + default: printf("TODO: TED write at %04x\n", address); } @@ -241,6 +250,7 @@ class ConcreteMachine: Cycles timers_subcycles_; Timers timers_; + Video video_; }; } diff --git a/Machines/Commodore/Plus4/Video.hpp b/Machines/Commodore/Plus4/Video.hpp new file mode 100644 index 0000000000..86b9f1f6f4 --- /dev/null +++ b/Machines/Commodore/Plus4/Video.hpp @@ -0,0 +1,81 @@ +// +// Video.hpp +// Clock Signal +// +// Created by Thomas Harte on 10/12/2024. +// Copyright © 2024 Thomas Harte. All rights reserved. +// + +#pragma once + +namespace Commodore::Plus4 { + +struct Video { +public: + template + void write(const uint8_t value) { + const auto load_high10 = [&](uint16_t &target) { + target = uint16_t( + (target & 0x00ff) | ((value & 0x3) << 8) + ); + }; + const auto load_low8 = [&](uint16_t &target) { + target = uint16_t( + (target & 0xff00) | value + ); + }; + + + switch(address) { + case 0xff06: + extended_colour_mode_ = value & 0x40; + bitmap_mode_ = value & 0x20; + display_enable_ = value & 0x10; + rows_25_ = value & 8; + y_scroll_ = value & 7; + break; + + case 0xff07: + characters_256_ = value & 0x80; + is_ntsc_ = value & 0x40; + ted_off_ = value & 0x20; + multicolour_mode_ = value & 0x10; + columns_40_ = value & 8; + x_scroll_ = value & 7; + break; + + case 0xff12: + character_generator_address_ = uint16_t((value & 0xfc) << 8); + break; + case 0xff14: + screen_memory_address_ = uint16_t((value & 0xf8) << 8); + break; + + case 0xff0c: load_high10(cursor_address_); break; + case 0xff0d: load_low8(cursor_address_); break; + case 0xff1a: load_high10(character_row_address_); break; + case 0xff1b: load_low8(character_row_address_); break; + } + } + +private: + bool extended_colour_mode_ = false; + bool bitmap_mode_ = false; + bool display_enable_ = false; + bool rows_25_ = false; + int y_scroll_ = 0; + + bool characters_256_ = false; + bool is_ntsc_ = false; + bool ted_off_ = false; + bool multicolour_mode_ = false; + bool columns_40_ = false; + int x_scroll_ = 0; + + uint16_t cursor_address_ = 0; + uint16_t character_row_address_ = 0; + uint16_t character_generator_address_ = 0; + uint16_t screen_memory_address_ = 0; +}; + +} diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 5fd7733d7d..f723807c1e 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -1296,6 +1296,7 @@ 42EB812C2B47008D00429AF4 /* MemoryMap.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = MemoryMap.cpp; sourceTree = ""; }; 4B018B88211930DE002A3937 /* 65C02_extended_opcodes_test.bin */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; name = 65C02_extended_opcodes_test.bin; path = "Klaus Dormann/65C02_extended_opcodes_test.bin"; sourceTree = ""; }; 4B01A6871F22F0DB001FD6E3 /* Z80MemptrTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Z80MemptrTests.swift; sourceTree = ""; }; + 4B03291F2D0923E300C51EB5 /* Video.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Video.hpp; sourceTree = ""; }; 4B0333AD2094081A0050B93D /* AppleDSK.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = AppleDSK.cpp; sourceTree = ""; }; 4B0333AE2094081A0050B93D /* AppleDSK.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = AppleDSK.hpp; sourceTree = ""; }; 4B046DC31CFE651500E9E45E /* ScanProducer.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ScanProducer.hpp; sourceTree = ""; }; @@ -3393,6 +3394,7 @@ children = ( 4B596EAF2D037E8800FBF4B1 /* Plus4.hpp */, 4B596EB02D037E8800FBF4B1 /* Plus4.cpp */, + 4B03291F2D0923E300C51EB5 /* Video.hpp */, ); path = Plus4; sourceTree = ""; From aed8f8efa8ee64afa4da9f2e117caad678aecc6a Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 10 Dec 2024 22:56:14 -0500 Subject: [PATCH 007/103] Transcribe some timing numbers. --- Machines/Commodore/Plus4/Plus4.cpp | 8 ++-- Machines/Commodore/Plus4/Video.hpp | 66 +++++++++++++++++++++++++++++- 2 files changed, 70 insertions(+), 4 deletions(-) diff --git a/Machines/Commodore/Plus4/Plus4.cpp b/Machines/Commodore/Plus4/Plus4.cpp index e638a3806d..277cacb64b 100644 --- a/Machines/Commodore/Plus4/Plus4.cpp +++ b/Machines/Commodore/Plus4/Plus4.cpp @@ -130,11 +130,11 @@ class ConcreteMachine: ConcreteMachine(const Analyser::Static::Commodore::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) : m6502_(*this) { - // PAL: 8'867'240 divided by 5 or 4? - // NTSC: 7'159'090? + // PAL: 8867240 divided by 5 or 4? + // NTSC: 7159090? // i.e. colour subcarriers multiplied by two? - set_clock_rate(8'867'240); // TODO. + set_clock_rate(7159090); // TODO. const auto kernel = ROM::Name::Plus4KernelPALv5; const auto basic = ROM::Name::Plus4BASIC; @@ -171,6 +171,8 @@ class ConcreteMachine: const auto timers_cycles = timers_subcycles_.divide(Cycles(5)); timers_.tick(timers_cycles.as()); + video_.run_for(length); + // Perform actual access. if(address < 0xfd00 || address >= 0xff40) { if(isReadOperation(operation)) { diff --git a/Machines/Commodore/Plus4/Video.hpp b/Machines/Commodore/Plus4/Video.hpp index 86b9f1f6f4..b1260ed3dc 100644 --- a/Machines/Commodore/Plus4/Video.hpp +++ b/Machines/Commodore/Plus4/Video.hpp @@ -25,7 +25,6 @@ struct Video { ); }; - switch(address) { case 0xff06: extended_colour_mode_ = value & 0x40; @@ -58,6 +57,68 @@ struct Video { } } + // Outer clock is [NTSC or PAL] colour subcarrier * 2. + // + // 65 cycles = 64µs? + // 65*262*60 = 1021800 + // + // In an NTSC television system. 262 raster lines are produced (0 to 261), 312 for PAL (0−311). + // + // An interrupt is generated 8 cycles before the character window. For a 25 row display, the visible + // raster lines are from 4 to 203. + // + // The horizontal position register counts 456 dots, 0 to 455. + void run_for(Cycles) { + // Timing: + // + // 456 cycles/line; + // if in PAL mode, divide input clock by 1.25 (?); + // see page 34 of plus4_tech.pdf for event times. + + // Horizontal events: + // + // 3: 38-column screen start + // 288: external fetch window end, refresh single clock start, increment character position end + // 290: latch character position to reload + // 296: character window end, character window single clock end, increment refresh start + // 304: video shift register end + // 307: 38-column screen stop + // 315: 40-column screen end + // 328: refresh single clock end + // 336: increment blink, increment refresh end + // 344: horizontal blanking start + // 358: horizontal sync start + // 376: increment vertical line + // 384: burst start, end of screen — clear vertical line, vertical sub and character reload registers + // 390: horizontal sync end + // 408: burst end + // 400: external fetch window start + // 416: horizontal blanking end + // 424: increment character position reload + // 432: character window start, character window single clock start, increment character position start + // 440: video shift register start + // 451: 40-column screen start + + // Vertical events: + // + // 0: attribute fetch start + // 4: vertical screen window start (25 lines) + // 8: vertical screen window start (24 lines) + // 203: attribute fetch end + // 200: vertical screen window stop (24 lines) + // 204: frame window stop, vertical screen window stop (25 lines) + // 226: NTSC vertical blank start + // 229: NTSC vsync start + // 232: NTSC vsync end + // 244: NTSC vertical blank end + // 251: PAL vertical blank start + // 254: PAL vsync start + // 257: PAL vsync end + // 261: end of screen NTSC + // 269: PAL vertical blank end + // 311: end of screen PAL + } + private: bool extended_colour_mode_ = false; bool bitmap_mode_ = false; @@ -76,6 +137,9 @@ struct Video { uint16_t character_row_address_ = 0; uint16_t character_generator_address_ = 0; uint16_t screen_memory_address_ = 0; + + int horizontal_counter_ = 0; + int vertical_counter_ = 0; }; } From 84d178c0ca6b5b3e6b43a2e9dc24502db97d441b Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 11 Dec 2024 17:32:51 -0500 Subject: [PATCH 008/103] Transcribe event times into [mostly] non-action. --- Machines/Commodore/Plus4/Video.hpp | 173 ++++++++++++++++++++++------- 1 file changed, 130 insertions(+), 43 deletions(-) diff --git a/Machines/Commodore/Plus4/Video.hpp b/Machines/Commodore/Plus4/Video.hpp index b1260ed3dc..33a6819624 100644 --- a/Machines/Commodore/Plus4/Video.hpp +++ b/Machines/Commodore/Plus4/Video.hpp @@ -68,55 +68,142 @@ struct Video { // raster lines are from 4 to 203. // // The horizontal position register counts 456 dots, 0 to 455. - void run_for(Cycles) { + void run_for(Cycles cycles) { // Timing: // // 456 cycles/line; // if in PAL mode, divide input clock by 1.25 (?); // see page 34 of plus4_tech.pdf for event times. - // Horizontal events: - // - // 3: 38-column screen start - // 288: external fetch window end, refresh single clock start, increment character position end - // 290: latch character position to reload - // 296: character window end, character window single clock end, increment refresh start - // 304: video shift register end - // 307: 38-column screen stop - // 315: 40-column screen end - // 328: refresh single clock end - // 336: increment blink, increment refresh end - // 344: horizontal blanking start - // 358: horizontal sync start - // 376: increment vertical line - // 384: burst start, end of screen — clear vertical line, vertical sub and character reload registers - // 390: horizontal sync end - // 408: burst end - // 400: external fetch window start - // 416: horizontal blanking end - // 424: increment character position reload - // 432: character window start, character window single clock start, increment character position start - // 440: video shift register start - // 451: 40-column screen start - - // Vertical events: - // - // 0: attribute fetch start - // 4: vertical screen window start (25 lines) - // 8: vertical screen window start (24 lines) - // 203: attribute fetch end - // 200: vertical screen window stop (24 lines) - // 204: frame window stop, vertical screen window stop (25 lines) - // 226: NTSC vertical blank start - // 229: NTSC vsync start - // 232: NTSC vsync end - // 244: NTSC vertical blank end - // 251: PAL vertical blank start - // 254: PAL vsync start - // 257: PAL vsync end - // 261: end of screen NTSC - // 269: PAL vertical blank end - // 311: end of screen PAL + auto ticks_remaining = cycles.as() * 8; + while(ticks_remaining--) { + ++horizontal_counter_; + switch(horizontal_counter_) { + case 3: // 38-column screen start. + break; + + case 288: // External fetch window end, refresh single clock start, increment character position end. + break; + + case 290: // Latch character position to reload. + break; + + case 296: // Character window end, character window single clock end, increment refresh start. + break; + + case 304: // Video shift register end. + break; + + case 307: // 38-column screen stop. + break; + + case 315: // 40-column screen end. + break; + + case 328: // Refresh single clock end. + break; + + case 336: // Increment blink, increment refresh end. + break; + + case 344: // Horizontal blanking start. + break; + + case 358: // Horizontal sync start. + break; + + case 376: // Increment vertical line. + ++vertical_counter_; + break; + + case 384: // Burst start, end of screen — clear vertical line, vertical sub and character reload registers. + break; + + case 390: // Horizontal sync end. + break; + + case 408: // Burst end. + break; + + case 400: // External fetch window start. + break; + + case 416: // Horizontal blanking end. + break; + + case 424: // Increment character position reload. + break; + + case 432: // Character window start, character window single clock start, increment character position start. + break; + + case 440: // Video shift register start. + break; + + case 451: // 40-column screen start. + break; + + case 465: // Wraparound. + horizontal_counter_ = 0; + break; + } + + const auto attribute_fetch_start = []{}; + switch(vertical_counter_) { + case 261: // End of screen NTSC. [and hence 0: Attribute fetch start]. + if(is_ntsc_) { + vertical_counter_ = 0; + attribute_fetch_start(); + } + break; + + case 311: // End of screen PAL. [and hence 0: Attribute fetch start]. + if(!is_ntsc_) { + vertical_counter_ = 0; + attribute_fetch_start(); + } + break; + + case 4: // Vertical screen window start (25 lines). + break; + + case 8: // Vertical screen window start (24 lines). + break; + + case 200: // Vertical screen window stop (24 lines). + break; + + case 203: // Attribute fetch end. + break; + + case 204: // Vertical screen window stop (25 lines). + break; + + case 226: // NTSC vertical blank start. + break; + + case 229: // NTSC vsync start. + break; + + case 232: // NTSC vsync end. + break; + + case 244: // NTSC vertical blank end. + break; + + case 251: // PAL vertical blank start. + break; + + case 254: // PAL vsync start. + break; + + case 257: // PAL vsync end. + break; + + case 269: // PAL vertical blank end. + break; + } + } } private: From 389ba95e5a603aff88b4ff70add4a7c13056caf8 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 11 Dec 2024 21:30:58 -0500 Subject: [PATCH 009/103] Template out the usual repetitive stuff of segment finding. --- Machines/Commodore/Plus4/Video.hpp | 118 ++++++++++-------- Numeric/UpperBound.hpp | 50 ++++++++ .../Clock Signal.xcodeproj/project.pbxproj | 4 +- 3 files changed, 118 insertions(+), 54 deletions(-) create mode 100644 Numeric/UpperBound.hpp diff --git a/Machines/Commodore/Plus4/Video.hpp b/Machines/Commodore/Plus4/Video.hpp index 33a6819624..944ad8262e 100644 --- a/Machines/Commodore/Plus4/Video.hpp +++ b/Machines/Commodore/Plus4/Video.hpp @@ -8,6 +8,8 @@ #pragma once +#include "../../../Numeric/UpperBound.hpp" + namespace Commodore::Plus4 { struct Video { @@ -76,131 +78,141 @@ struct Video { // see page 34 of plus4_tech.pdf for event times. auto ticks_remaining = cycles.as() * 8; - while(ticks_remaining--) { - ++horizontal_counter_; - switch(horizontal_counter_) { - case 3: // 38-column screen start. + while(ticks_remaining) { + // Test vertical first; this will catch both any programmed change that has occurred outside + // of the loop and any change to the vertical counter that occurs during the horizontal runs. + const auto attribute_fetch_start = []{}; + switch(vertical_counter_) { + case 261: // End of screen NTSC. [and hence 0: Attribute fetch start]. + if(is_ntsc_) { + vertical_counter_ = 0; + attribute_fetch_start(); + } break; - case 288: // External fetch window end, refresh single clock start, increment character position end. + case 311: // End of screen PAL. [and hence 0: Attribute fetch start]. + if(!is_ntsc_) { + vertical_counter_ = 0; + attribute_fetch_start(); + } break; - case 290: // Latch character position to reload. + case 4: // Vertical screen window start (25 lines). break; - case 296: // Character window end, character window single clock end, increment refresh start. + case 8: // Vertical screen window start (24 lines). break; - case 304: // Video shift register end. + case 200: // Vertical screen window stop (24 lines). break; - case 307: // 38-column screen stop. + case 203: // Attribute fetch end. break; - case 315: // 40-column screen end. + case 204: // Vertical screen window stop (25 lines). break; - case 328: // Refresh single clock end. + case 226: // NTSC vertical blank start. break; - case 336: // Increment blink, increment refresh end. + case 229: // NTSC vsync start. break; - case 344: // Horizontal blanking start. + case 232: // NTSC vsync end. break; - case 358: // Horizontal sync start. + case 244: // NTSC vertical blank end. break; - case 376: // Increment vertical line. - ++vertical_counter_; + case 251: // PAL vertical blank start. break; - case 384: // Burst start, end of screen — clear vertical line, vertical sub and character reload registers. + case 254: // PAL vsync start. break; - case 390: // Horizontal sync end. + case 257: // PAL vsync end. break; - case 408: // Burst end. + case 269: // PAL vertical blank end. break; + } - case 400: // External fetch window start. + const auto next = Numeric::upper_bound< + 0, 3, 288, 290, 296, 304, 307, 315, 328, 336, 344, 358, 376, 384, 390, 400, 416, 424, 432, 440, 451, 465 + >(horizontal_counter_); + const auto period = std::min(next - horizontal_counter_, ticks_remaining); + +// printf("From %d next is %d\n", horizontal_counter_, next); + + horizontal_counter_ += period; + ticks_remaining -= period; + switch(horizontal_counter_) { + case 3: // 38-column screen start. break; - case 416: // Horizontal blanking end. + case 288: // External fetch window end, refresh single clock start, increment character position end. break; - case 424: // Increment character position reload. + case 290: // Latch character position to reload. break; - case 432: // Character window start, character window single clock start, increment character position start. + case 296: // Character window end, character window single clock end, increment refresh start. break; - case 440: // Video shift register start. + case 304: // Video shift register end. break; - case 451: // 40-column screen start. + case 307: // 38-column screen stop. break; - case 465: // Wraparound. - horizontal_counter_ = 0; + case 315: // 40-column screen end. break; - } - const auto attribute_fetch_start = []{}; - switch(vertical_counter_) { - case 261: // End of screen NTSC. [and hence 0: Attribute fetch start]. - if(is_ntsc_) { - vertical_counter_ = 0; - attribute_fetch_start(); - } + case 328: // Refresh single clock end. break; - case 311: // End of screen PAL. [and hence 0: Attribute fetch start]. - if(!is_ntsc_) { - vertical_counter_ = 0; - attribute_fetch_start(); - } + case 336: // Increment blink, increment refresh end. break; - case 4: // Vertical screen window start (25 lines). + case 344: // Horizontal blanking start. break; - case 8: // Vertical screen window start (24 lines). + case 358: // Horizontal sync start. break; - case 200: // Vertical screen window stop (24 lines). + case 376: // Increment vertical line. + ++vertical_counter_; break; - case 203: // Attribute fetch end. + case 384: // Burst start, end of screen — clear vertical line, vertical sub and character reload registers. break; - case 204: // Vertical screen window stop (25 lines). + case 390: // Horizontal sync end. break; - case 226: // NTSC vertical blank start. + case 400: // External fetch window start. break; - case 229: // NTSC vsync start. + case 408: // Burst end. break; - case 232: // NTSC vsync end. + case 416: // Horizontal blanking end. break; - case 244: // NTSC vertical blank end. + case 424: // Increment character position reload. break; - case 251: // PAL vertical blank start. + case 432: // Character window start, character window single clock start, increment character position start. break; - case 254: // PAL vsync start. + case 440: // Video shift register start. break; - case 257: // PAL vsync end. + case 451: // 40-column screen start. break; - case 269: // PAL vertical blank end. + case 465: // Wraparound. + horizontal_counter_ = 0; break; } } diff --git a/Numeric/UpperBound.hpp b/Numeric/UpperBound.hpp new file mode 100644 index 0000000000..f00d08a80d --- /dev/null +++ b/Numeric/UpperBound.hpp @@ -0,0 +1,50 @@ +// +// UpperBound.hpp +// Clock Signal +// +// Created by Thomas Harte on 11/12/2024. +// Copyright © 2024 Thomas Harte. All rights reserved. +// + +#pragma once + +namespace Numeric { + +/// @returns The element that is `index - offset` into the list given by the rest of the +/// variadic arguments or the final element if `offset` is out of bounds. +/// +/// E.g. @c at_index<0, 3, 5, 6, 7, 8, 9>() returns the `3 - 0` = 4th element from the +/// list 5, 6, 7, 8, 9, i.e. 8. +template +int at_index() { + if constexpr (origin == index || sizeof...(Args) == 0) { + return T; + } else { + return at_index(); + } +} + +/// @returns The result of binary searching for the first thing in the range `[left, right)` within +/// the other template arguments that is strictly greater than @c location. +template +int upper_bound_bounded(int location) { + if constexpr (left + 1 == right) { + return at_index<0, left+1, Args...>(); + } + + constexpr auto midpoint = (left + right) >> 1; + if(location >= at_index<0, midpoint, Args...>()) { + return upper_bound_bounded(location); + } else { + return upper_bound_bounded(location); + } +} + +/// @returns The result of binary searching for the first thing in the template arguments +/// is strictly greater than @c location. +template +int upper_bound(int location) { + return upper_bound_bounded<0, sizeof...(Args), Args...>(location); +} + +} diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index f723807c1e..c6f89df4bd 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -1297,6 +1297,7 @@ 4B018B88211930DE002A3937 /* 65C02_extended_opcodes_test.bin */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; name = 65C02_extended_opcodes_test.bin; path = "Klaus Dormann/65C02_extended_opcodes_test.bin"; sourceTree = ""; }; 4B01A6871F22F0DB001FD6E3 /* Z80MemptrTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Z80MemptrTests.swift; sourceTree = ""; }; 4B03291F2D0923E300C51EB5 /* Video.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Video.hpp; sourceTree = ""; }; + 4B0329202D0A78DC00C51EB5 /* UpperBound.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = UpperBound.hpp; sourceTree = ""; }; 4B0333AD2094081A0050B93D /* AppleDSK.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = AppleDSK.cpp; sourceTree = ""; }; 4B0333AE2094081A0050B93D /* AppleDSK.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = AppleDSK.hpp; sourceTree = ""; }; 4B046DC31CFE651500E9E45E /* ScanProducer.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ScanProducer.hpp; sourceTree = ""; }; @@ -3637,13 +3638,14 @@ children = ( 4B43984129674943006B0BFC /* BitReverse.hpp */, 4BD155312716362A00410C6E /* BitSpread.hpp */, + 4281572E2AA0334300E16AA1 /* Carry.hpp */, 4B7BA03E23D55E7900B98D9E /* CRC.hpp */, 4B7BA03F23D55E7900B98D9E /* LFSR.hpp */, 4B66E1A8297719270057ED0F /* NumericCoder.hpp */, 4BB5B995281B1D3E00522DA9 /* RegisterSizes.hpp */, 4BFEA2F12682A90200EBF94C /* Sizes.hpp */, - 4281572E2AA0334300E16AA1 /* Carry.hpp */, 4BD9713A2BFD7E7100C907AA /* StringSimilarity.hpp */, + 4B0329202D0A78DC00C51EB5 /* UpperBound.hpp */, ); name = Numeric; path = ../../Numeric; From 0eab6146fcb174725c8a6a26e19c198f0b8bfdcd Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 11 Dec 2024 21:38:32 -0500 Subject: [PATCH 010/103] Introduce a CRT. --- Machines/Commodore/Plus4/Plus4.cpp | 5 +++-- Machines/Commodore/Plus4/Video.hpp | 13 +++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/Machines/Commodore/Plus4/Plus4.cpp b/Machines/Commodore/Plus4/Plus4.cpp index 277cacb64b..17392510c4 100644 --- a/Machines/Commodore/Plus4/Plus4.cpp +++ b/Machines/Commodore/Plus4/Plus4.cpp @@ -230,11 +230,12 @@ class ConcreteMachine: private: CPU::MOS6502::Processor m6502_; - void set_scan_target(Outputs::Display::ScanTarget *const) final { + void set_scan_target(Outputs::Display::ScanTarget *const target) final { + video_.set_scan_target(target); } Outputs::Display::ScanStatus get_scaled_scan_status() const final { - return {}; + return video_.get_scaled_scan_status(); } void run_for(const Cycles cycles) final { diff --git a/Machines/Commodore/Plus4/Video.hpp b/Machines/Commodore/Plus4/Video.hpp index 944ad8262e..3e4cf8f273 100644 --- a/Machines/Commodore/Plus4/Video.hpp +++ b/Machines/Commodore/Plus4/Video.hpp @@ -9,11 +9,14 @@ #pragma once #include "../../../Numeric/UpperBound.hpp" +#include "../../../Outputs/CRT/CRT.hpp" namespace Commodore::Plus4 { struct Video { public: + Video() : crt_(465, 1, Outputs::Display::Type::PAL50, Outputs::Display::InputDataType::Luminance8Phase8) {} + template void write(const uint8_t value) { const auto load_high10 = [&](uint16_t &target) { @@ -218,7 +221,17 @@ struct Video { } } + void set_scan_target(Outputs::Display::ScanTarget *const target) { + crt_.set_scan_target(target); + } + + Outputs::Display::ScanStatus get_scaled_scan_status() const { + return crt_.get_scaled_scan_status(); + } + private: + Outputs::CRT::CRT crt_; + bool extended_colour_mode_ = false; bool bitmap_mode_ = false; bool display_enable_ = false; From a487619578af8795209d1fe0807cde16f0efb9af Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 11 Dec 2024 21:54:03 -0500 Subject: [PATCH 011/103] Track basic frame events. --- Machines/Commodore/Plus4/Video.hpp | 100 ++++++++++++++++++----------- 1 file changed, 63 insertions(+), 37 deletions(-) diff --git a/Machines/Commodore/Plus4/Video.hpp b/Machines/Commodore/Plus4/Video.hpp index 3e4cf8f273..b012e59b5b 100644 --- a/Machines/Commodore/Plus4/Video.hpp +++ b/Machines/Commodore/Plus4/Video.hpp @@ -82,8 +82,10 @@ struct Video { auto ticks_remaining = cycles.as() * 8; while(ticks_remaining) { + // // Test vertical first; this will catch both any programmed change that has occurred outside // of the loop and any change to the vertical counter that occurs during the horizontal runs. + // const auto attribute_fetch_start = []{}; switch(vertical_counter_) { case 261: // End of screen NTSC. [and hence 0: Attribute fetch start]. @@ -115,29 +117,15 @@ struct Video { case 204: // Vertical screen window stop (25 lines). break; - case 226: // NTSC vertical blank start. - break; - - case 229: // NTSC vsync start. - break; - - case 232: // NTSC vsync end. - break; - - case 244: // NTSC vertical blank end. - break; + case 226: if(is_ntsc_) vertical_blank_ = true; break; // NTSC vertical blank start. + case 229: if(is_ntsc_) vertical_sync_ = true; break; // NTSC vsync start. + case 232: if(is_ntsc_) vertical_sync_ = false; break; // NTSC vsync end. + case 244: if(is_ntsc_) vertical_blank_ = false; break; // NTSC vertical blank end. - case 251: // PAL vertical blank start. - break; - - case 254: // PAL vsync start. - break; - - case 257: // PAL vsync end. - break; - - case 269: // PAL vertical blank end. - break; + case 251: if(!is_ntsc_) vertical_blank_ = true; break; // PAL vertical blank start. + case 254: if(!is_ntsc_) vertical_sync_ = true; break; // PAL vsync start. + case 257: if(!is_ntsc_) vertical_sync_ = false; break; // PAL vsync end. + case 269: if(!is_ntsc_) vertical_blank_ = false; break; // PAL vertical blank end. } const auto next = Numeric::upper_bound< @@ -145,8 +133,36 @@ struct Video { >(horizontal_counter_); const auto period = std::min(next - horizontal_counter_, ticks_remaining); -// printf("From %d next is %d\n", horizontal_counter_, next); + // + // TODO: output. + // + OutputState state; + if(vertical_sync_ || horizontal_sync_) { + state = OutputState::Sync; + } else if(vertical_blank_ || horizontal_blank_) { + state = horizontal_burst_ ? OutputState::Burst : OutputState::Blank; + } else { + state = OutputState::Border; + // TODO: pixels when? + } + + + if(state != output_state_) { + switch(output_state_) { + case OutputState::Blank: crt_.output_blank(time_in_state_); break; + case OutputState::Sync: crt_.output_sync(time_in_state_); break; + case OutputState::Burst: crt_.output_default_colour_burst(time_in_state_); break; + case OutputState::Border: crt_.output_level(time_in_state_, 0x8080); break; + } + time_in_state_ = 0; + } + output_state_ = state; + time_in_state_ += period; + + // + // Advance for current period. + // horizontal_counter_ += period; ticks_remaining -= period; switch(horizontal_counter_) { @@ -177,31 +193,24 @@ struct Video { case 336: // Increment blink, increment refresh end. break; - case 344: // Horizontal blanking start. - break; - - case 358: // Horizontal sync start. - break; - case 376: // Increment vertical line. ++vertical_counter_; break; case 384: // Burst start, end of screen — clear vertical line, vertical sub and character reload registers. + horizontal_burst_ = true; + // TODO: rest. break; - case 390: // Horizontal sync end. - break; + case 344: horizontal_blank_ = true; break; // Horizontal blanking start. + case 358: horizontal_sync_ = true; break; // Horizontal sync start. + case 390: horizontal_sync_ = false; break; // Horizontal sync end. + case 408: horizontal_burst_ = false; break; // Burst end. + case 416: horizontal_blank_ = false; break; // Horizontal blanking end. case 400: // External fetch window start. break; - case 408: // Burst end. - break; - - case 416: // Horizontal blanking end. - break; - case 424: // Increment character position reload. break; @@ -232,6 +241,7 @@ struct Video { private: Outputs::CRT::CRT crt_; + // Programmable values. bool extended_colour_mode_ = false; bool bitmap_mode_ = false; bool display_enable_ = false; @@ -250,8 +260,24 @@ struct Video { uint16_t character_generator_address_ = 0; uint16_t screen_memory_address_ = 0; + // Field position. int horizontal_counter_ = 0; int vertical_counter_ = 0; + + // Running state. + bool vertical_blank_ = false; + bool vertical_sync_ = false; + bool horizontal_blank_ = false; + bool horizontal_sync_ = false; + bool horizontal_burst_ = false; + + enum class OutputState { + Blank, + Sync, + Burst, + Border, + } output_state_ = OutputState::Blank; + int time_in_state_ = 0; }; } From 8854ffddee7b3cbe25a9293266d97cdcc0d02abf Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 11 Dec 2024 21:57:31 -0500 Subject: [PATCH 012/103] Include possible clock divider. --- Machines/Commodore/Plus4/Video.hpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Machines/Commodore/Plus4/Video.hpp b/Machines/Commodore/Plus4/Video.hpp index b012e59b5b..ba3798937b 100644 --- a/Machines/Commodore/Plus4/Video.hpp +++ b/Machines/Commodore/Plus4/Video.hpp @@ -80,7 +80,8 @@ struct Video { // if in PAL mode, divide input clock by 1.25 (?); // see page 34 of plus4_tech.pdf for event times. - auto ticks_remaining = cycles.as() * 8; + subcycles_ += cycles * 4; + auto ticks_remaining = subcycles_.divide(is_ntsc_ ? Cycles(4) : Cycles(5)).as(); while(ticks_remaining) { // // Test vertical first; this will catch both any programmed change that has occurred outside @@ -240,6 +241,7 @@ struct Video { private: Outputs::CRT::CRT crt_; + Cycles subcycles_; // Programmable values. bool extended_colour_mode_ = false; From f7750af3d00a032a2043a103ddea1e6fc5408d6a Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 11 Dec 2024 22:32:14 -0500 Subject: [PATCH 013/103] Provide bus visibility to video; mark vertical portion of display. --- Machines/Commodore/Plus4/Pager.hpp | 50 +++++++++++++++++++ Machines/Commodore/Plus4/Plus4.cpp | 41 ++------------- Machines/Commodore/Plus4/Video.hpp | 35 +++++++------ .../Clock Signal.xcodeproj/project.pbxproj | 4 +- 4 files changed, 77 insertions(+), 53 deletions(-) create mode 100644 Machines/Commodore/Plus4/Pager.hpp diff --git a/Machines/Commodore/Plus4/Pager.hpp b/Machines/Commodore/Plus4/Pager.hpp new file mode 100644 index 0000000000..6a2efea7f8 --- /dev/null +++ b/Machines/Commodore/Plus4/Pager.hpp @@ -0,0 +1,50 @@ +// +// Pager.hpp +// Clock Signal +// +// Created by Thomas Harte on 11/12/2024. +// Copyright © 2024 Thomas Harte. All rights reserved. +// + +#pragma once + +template +class Pager { +public: + DataT read(AddressT address) { + return read_[address >> Shift][address]; + } + DataT &write(AddressT address) { + return write_[address >> Shift][address]; + } + + template + void page(const uint8_t *read, uint8_t *write) { + write_[slot] = write - (slot << Shift); + read_[slot] = read - (slot << Shift); + } + +private: + std::array write_{}; + std::array read_{}; + + static constexpr auto AddressBits = sizeof(AddressT) * 8; + static constexpr auto PageSize = (1 << AddressBits) / NumPages; + static_assert(!(PageSize & (PageSize - 1)), "Pages must be a power of two in size"); + + static constexpr int ln2(int value) { + int result = 0; + while(value != 1) { + value >>= 1; + ++result; + } + return result; + } + static constexpr auto Shift = ln2(PageSize); +}; + +namespace Commodore::Plus4 { + +using Pager = Pager; + +} diff --git a/Machines/Commodore/Plus4/Plus4.cpp b/Machines/Commodore/Plus4/Plus4.cpp index 17392510c4..79e072a2e9 100644 --- a/Machines/Commodore/Plus4/Plus4.cpp +++ b/Machines/Commodore/Plus4/Plus4.cpp @@ -8,6 +8,7 @@ #include "Plus4.hpp" +#include "Pager.hpp" #include "Video.hpp" #include "../../MachineTypes.hpp" @@ -18,41 +19,6 @@ using namespace Commodore::Plus4; namespace { -template -class Pager { -public: - DataT read(AddressT address) { - return read_[address >> Shift][address]; - } - DataT &write(AddressT address) { - return write_[address >> Shift][address]; - } - - template - void page(const uint8_t *read, uint8_t *write) { - write_[slot] = write - (slot << Shift); - read_[slot] = read - (slot << Shift); - } - -private: - std::array write_{}; - std::array read_{}; - - static constexpr auto AddressBits = sizeof(AddressT) * 8; - static constexpr auto PageSize = (1 << AddressBits) / NumPages; - static_assert(!(PageSize & (PageSize - 1)), "Pages must be a power of two in size"); - - static constexpr int ln2(int value) { - int result = 0; - while(value != 1) { - value >>= 1; - ++result; - } - return result; - } - static constexpr auto Shift = ln2(PageSize); -}; - class Timers { public: template @@ -128,7 +94,8 @@ class ConcreteMachine: public Machine { public: ConcreteMachine(const Analyser::Static::Commodore::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) : - m6502_(*this) + m6502_(*this), + video_(map_) { // PAL: 8867240 divided by 5 or 4? // NTSC: 7159090? @@ -246,7 +213,7 @@ class ConcreteMachine: return true; } - Pager map_; + Commodore::Plus4::Pager map_; std::array ram_; std::vector kernel_; std::vector basic_; diff --git a/Machines/Commodore/Plus4/Video.hpp b/Machines/Commodore/Plus4/Video.hpp index ba3798937b..b158958c08 100644 --- a/Machines/Commodore/Plus4/Video.hpp +++ b/Machines/Commodore/Plus4/Video.hpp @@ -8,6 +8,8 @@ #pragma once +#include "Pager.hpp" + #include "../../../Numeric/UpperBound.hpp" #include "../../../Outputs/CRT/CRT.hpp" @@ -15,7 +17,9 @@ namespace Commodore::Plus4 { struct Video { public: - Video() : crt_(465, 1, Outputs::Display::Type::PAL50, Outputs::Display::InputDataType::Luminance8Phase8) {} + Video(const Commodore::Plus4::Pager &pager) : + crt_(465, 1, Outputs::Display::Type::PAL50, Outputs::Display::InputDataType::Luminance8Phase8), + pager_(pager) {} template void write(const uint8_t value) { @@ -60,6 +64,8 @@ struct Video { case 0xff1a: load_high10(character_row_address_); break; case 0xff1b: load_low8(character_row_address_); break; } + +// printf("bitmap:%d c256:%d ntsc:%d 40col:%d; base:%04x\n", bitmap_mode_, characters_256_, is_ntsc_, columns_40_, screen_memory_address_); } // Outer clock is [NTSC or PAL] colour subcarrier * 2. @@ -103,20 +109,14 @@ struct Video { } break; - case 4: // Vertical screen window start (25 lines). - break; - - case 8: // Vertical screen window start (24 lines). - break; - - case 200: // Vertical screen window stop (24 lines). - break; - case 203: // Attribute fetch end. break; - case 204: // Vertical screen window stop (25 lines). - break; + + case 4: if(rows_25_) vertical_window_ = true; break; // Vertical screen window start (25 lines). + case 204: if(rows_25_) vertical_window_ = false; break; // Vertical screen window stop (25 lines). + case 8: if(!rows_25_) vertical_window_ = true; break; // Vertical screen window start (24 lines). + case 200: if(!rows_25_) vertical_window_ = false; break; // Vertical screen window stop (24 lines). case 226: if(is_ntsc_) vertical_blank_ = true; break; // NTSC vertical blank start. case 229: if(is_ntsc_) vertical_sync_ = true; break; // NTSC vsync start. @@ -136,7 +136,7 @@ struct Video { // - // TODO: output. + // Output. // OutputState state; if(vertical_sync_ || horizontal_sync_) { @@ -144,8 +144,8 @@ struct Video { } else if(vertical_blank_ || horizontal_blank_) { state = horizontal_burst_ ? OutputState::Burst : OutputState::Blank; } else { - state = OutputState::Border; - // TODO: pixels when? + state = vertical_window_ ? OutputState::Pixels : OutputState::Border; + // TODO: pixels when? Like, for real? } @@ -155,6 +155,7 @@ struct Video { case OutputState::Sync: crt_.output_sync(time_in_state_); break; case OutputState::Burst: crt_.output_default_colour_burst(time_in_state_); break; case OutputState::Border: crt_.output_level(time_in_state_, 0x8080); break; + case OutputState::Pixels: crt_.output_level(time_in_state_, 0xff80); break; } time_in_state_ = 0; } @@ -269,6 +270,7 @@ struct Video { // Running state. bool vertical_blank_ = false; bool vertical_sync_ = false; + bool vertical_window_ = false; bool horizontal_blank_ = false; bool horizontal_sync_ = false; bool horizontal_burst_ = false; @@ -278,8 +280,11 @@ struct Video { Sync, Burst, Border, + Pixels, } output_state_ = OutputState::Blank; int time_in_state_ = 0; + + const Commodore::Plus4::Pager &pager_; }; } diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index c6f89df4bd..354ca63ea6 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -1298,6 +1298,7 @@ 4B01A6871F22F0DB001FD6E3 /* Z80MemptrTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Z80MemptrTests.swift; sourceTree = ""; }; 4B03291F2D0923E300C51EB5 /* Video.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Video.hpp; sourceTree = ""; }; 4B0329202D0A78DC00C51EB5 /* UpperBound.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = UpperBound.hpp; sourceTree = ""; }; + 4B0329212D0A8C4700C51EB5 /* Pager.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Pager.hpp; sourceTree = ""; }; 4B0333AD2094081A0050B93D /* AppleDSK.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = AppleDSK.cpp; sourceTree = ""; }; 4B0333AE2094081A0050B93D /* AppleDSK.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = AppleDSK.hpp; sourceTree = ""; }; 4B046DC31CFE651500E9E45E /* ScanProducer.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ScanProducer.hpp; sourceTree = ""; }; @@ -3393,8 +3394,9 @@ 4B596EB12D037E8800FBF4B1 /* Plus4 */ = { isa = PBXGroup; children = ( - 4B596EAF2D037E8800FBF4B1 /* Plus4.hpp */, 4B596EB02D037E8800FBF4B1 /* Plus4.cpp */, + 4B0329212D0A8C4700C51EB5 /* Pager.hpp */, + 4B596EAF2D037E8800FBF4B1 /* Plus4.hpp */, 4B03291F2D0923E300C51EB5 /* Video.hpp */, ); path = Plus4; From 41c6ed7c5a6f407b28fcc6f538f74ed838de9042 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 12 Dec 2024 17:33:11 -0500 Subject: [PATCH 014/103] Further restrict 'active' area of the display. --- Machines/Commodore/Plus4/Video.hpp | 46 +++++++++++++++--------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/Machines/Commodore/Plus4/Video.hpp b/Machines/Commodore/Plus4/Video.hpp index b158958c08..40eed84f60 100644 --- a/Machines/Commodore/Plus4/Video.hpp +++ b/Machines/Commodore/Plus4/Video.hpp @@ -112,7 +112,6 @@ struct Video { case 203: // Attribute fetch end. break; - case 4: if(rows_25_) vertical_window_ = true; break; // Vertical screen window start (25 lines). case 204: if(rows_25_) vertical_window_ = false; break; // Vertical screen window stop (25 lines). case 8: if(!rows_25_) vertical_window_ = true; break; // Vertical screen window start (24 lines). @@ -134,7 +133,6 @@ struct Video { >(horizontal_counter_); const auto period = std::min(next - horizontal_counter_, ticks_remaining); - // // Output. // @@ -144,11 +142,9 @@ struct Video { } else if(vertical_blank_ || horizontal_blank_) { state = horizontal_burst_ ? OutputState::Burst : OutputState::Blank; } else { - state = vertical_window_ ? OutputState::Pixels : OutputState::Border; - // TODO: pixels when? Like, for real? + state = vertical_window_ && output_pixels_ ? OutputState::Pixels : OutputState::Border; } - if(state != output_state_) { switch(output_state_) { case OutputState::Blank: crt_.output_blank(time_in_state_); break; @@ -168,28 +164,35 @@ struct Video { horizontal_counter_ += period; ticks_remaining -= period; switch(horizontal_counter_) { - case 3: // 38-column screen start. - break; - case 288: // External fetch window end, refresh single clock start, increment character position end. + // TODO: release RDY if it was held. + // TODO: increment character position end. + refresh_ = true; break; - case 290: // Latch character position to reload. + case 400: // External fetch window start. + // TODO: set RDY line if this is an appropriate row. break; + case 290: line_character_address_ = character_address_; break; // Latch character position to reload. + case 296: // Character window end, character window single clock end, increment refresh start. + fetch_characters_ = false; + break; + case 432: // Character window start, character window single clock start, increment character position start. + fetch_characters_ = true; break; case 304: // Video shift register end. break; - case 307: // 38-column screen stop. - break; - - case 315: // 40-column screen end. - break; + case 3: if(!columns_40_) output_pixels_ = true; break; // 38-column screen start. + case 307: if(!columns_40_) output_pixels_ = false; break; // 38-column screen stop. + case 451: if(columns_40_) output_pixels_ = true; break; // 40-column screen start. + case 315: if(columns_40_) output_pixels_ = false; break; // 40-column screen end. case 328: // Refresh single clock end. + refresh_ = false; break; case 336: // Increment blink, increment refresh end. @@ -210,21 +213,12 @@ struct Video { case 408: horizontal_burst_ = false; break; // Burst end. case 416: horizontal_blank_ = false; break; // Horizontal blanking end. - case 400: // External fetch window start. - break; - case 424: // Increment character position reload. break; - case 432: // Character window start, character window single clock start, increment character position start. - break; - case 440: // Video shift register start. break; - case 451: // 40-column screen start. - break; - case 465: // Wraparound. horizontal_counter_ = 0; break; @@ -275,6 +269,12 @@ struct Video { bool horizontal_sync_ = false; bool horizontal_burst_ = false; + uint16_t character_address_ = 0; + uint16_t line_character_address_ = 0; + bool fetch_characters_; + bool output_pixels_; + bool refresh_ = false; + enum class OutputState { Blank, Sync, From 1d07b8238ca582b8c6cc1896b3a6891ecf52f4c5 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 12 Dec 2024 17:36:44 -0500 Subject: [PATCH 015/103] Add a crop rectangle. --- Machines/Commodore/Plus4/Video.hpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Machines/Commodore/Plus4/Video.hpp b/Machines/Commodore/Plus4/Video.hpp index 40eed84f60..bc3035b2f1 100644 --- a/Machines/Commodore/Plus4/Video.hpp +++ b/Machines/Commodore/Plus4/Video.hpp @@ -19,7 +19,11 @@ struct Video { public: Video(const Commodore::Plus4::Pager &pager) : crt_(465, 1, Outputs::Display::Type::PAL50, Outputs::Display::InputDataType::Luminance8Phase8), - pager_(pager) {} + pager_(pager) + { + // TODO: perfect crop. + crt_.set_visible_area(Outputs::Display::Rect(0.075f, 0.065f, 0.85f, 0.85f)); + } template void write(const uint8_t value) { From ed766c74e690f9146e48a1eee44fb7f1c5218291 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 12 Dec 2024 21:17:28 -0500 Subject: [PATCH 016/103] Add some paging. --- Machines/Commodore/Plus4/Pager.hpp | 20 ++++++++++++++++---- Machines/Commodore/Plus4/Plus4.cpp | 17 +++++++++++++---- Machines/Commodore/Plus4/Video.hpp | 25 ++++++++++++++++++++----- 3 files changed, 49 insertions(+), 13 deletions(-) diff --git a/Machines/Commodore/Plus4/Pager.hpp b/Machines/Commodore/Plus4/Pager.hpp index 6a2efea7f8..c033f1b583 100644 --- a/Machines/Commodore/Plus4/Pager.hpp +++ b/Machines/Commodore/Plus4/Pager.hpp @@ -8,6 +8,12 @@ #pragma once +enum PagerSide { + Read = 1, + Write = 2, + ReadWrite = Read | Write, +}; + template class Pager { public: @@ -18,10 +24,16 @@ class Pager { return write_[address >> Shift][address]; } - template - void page(const uint8_t *read, uint8_t *write) { - write_[slot] = write - (slot << Shift); - read_[slot] = read - (slot << Shift); + template + void page(uint8_t *data) { + static_assert(!(start % PageSize), "Start address must be a multiple of the page size"); + static_assert(!(length % PageSize), "Data length must be a multiple of the page size"); + + for(size_t slot = start >> Shift; slot < (start + length) >> Shift; slot++) { + if constexpr (side & PagerSide::Write) write_[slot] = data - (slot << Shift); + if constexpr (side & PagerSide::Read) read_[slot] = data - (slot << Shift); + data += PageSize; + } } private: diff --git a/Machines/Commodore/Plus4/Plus4.cpp b/Machines/Commodore/Plus4/Plus4.cpp index 79e072a2e9..7c4f0086ab 100644 --- a/Machines/Commodore/Plus4/Plus4.cpp +++ b/Machines/Commodore/Plus4/Plus4.cpp @@ -115,10 +115,8 @@ class ConcreteMachine: kernel_ = roms.find(kernel)->second; basic_ = roms.find(basic)->second; - map_.page<0>(ram_.data(), ram_.data()); - map_.page<1>(ram_.data() + 16*1024, ram_.data() + 16*1024); - map_.page<2>(basic_.data(), ram_.data() + 32*1024); - map_.page<3>(kernel_.data(), ram_.data() + 48*1024); + map_.page(ram_.data()); + page_rom(); insert_media(target.media); } @@ -185,6 +183,9 @@ class ConcreteMachine: case 0xff1a: video_.write<0xff1a>(*value); break; case 0xff1b: video_.write<0xff1b>(*value); break; + case 0xff3e: page_rom(); break; + case 0xff3f: page_ram(); break; + default: printf("TODO: TED write at %04x\n", address); } @@ -197,6 +198,14 @@ class ConcreteMachine: private: CPU::MOS6502::Processor m6502_; + void page_rom() { + map_.page(basic_.data()); + map_.page(kernel_.data()); + } + void page_ram() { + map_.page(&ram_[0x8000]); + } + void set_scan_target(Outputs::Display::ScanTarget *const target) final { video_.set_scan_target(target); } diff --git a/Machines/Commodore/Plus4/Video.hpp b/Machines/Commodore/Plus4/Video.hpp index bc3035b2f1..41c0fce149 100644 --- a/Machines/Commodore/Plus4/Video.hpp +++ b/Machines/Commodore/Plus4/Video.hpp @@ -13,6 +13,8 @@ #include "../../../Numeric/UpperBound.hpp" #include "../../../Outputs/CRT/CRT.hpp" +#include + namespace Commodore::Plus4 { struct Video { @@ -67,6 +69,17 @@ struct Video { case 0xff0d: load_low8(cursor_address_); break; case 0xff1a: load_high10(character_row_address_); break; case 0xff1b: load_low8(character_row_address_); break; + + case 0xff15: case 0xff16: case 0xff17: case 0xff18: case 0xff19: { + const uint8_t luminance = uint8_t( + ((value & 0x70) << 1) | ((value & 0x70) >> 2) | ((value & 0x70) >> 5) + ); + background_[value - 0xff15] = uint16_t( + luminance + ); + + printf("%02x -> %04x\n", value, address); + } break; } // printf("bitmap:%d c256:%d ntsc:%d 40col:%d; base:%04x\n", bitmap_mode_, characters_256_, is_ntsc_, columns_40_, screen_memory_address_); @@ -151,11 +164,11 @@ struct Video { if(state != output_state_) { switch(output_state_) { - case OutputState::Blank: crt_.output_blank(time_in_state_); break; - case OutputState::Sync: crt_.output_sync(time_in_state_); break; - case OutputState::Burst: crt_.output_default_colour_burst(time_in_state_); break; - case OutputState::Border: crt_.output_level(time_in_state_, 0x8080); break; - case OutputState::Pixels: crt_.output_level(time_in_state_, 0xff80); break; + case OutputState::Blank: crt_.output_blank(time_in_state_); break; + case OutputState::Sync: crt_.output_sync(time_in_state_); break; + case OutputState::Burst: crt_.output_default_colour_burst(time_in_state_); break; + case OutputState::Border: crt_.output_level(time_in_state_, background_[4]); break; + case OutputState::Pixels: crt_.output_level(time_in_state_, background_[0]); break; } time_in_state_ = 0; } @@ -288,6 +301,8 @@ struct Video { } output_state_ = OutputState::Blank; int time_in_state_ = 0; + std::array background_{}; + const Commodore::Plus4::Pager &pager_; }; From 58b464bdfc7d1a733a59f9c7586313c834d9d02a Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 12 Dec 2024 22:07:51 -0500 Subject: [PATCH 017/103] Attempt to add interrupts. --- Machines/Commodore/Plus4/Interrupts.hpp | 68 +++++++++++++++++++ Machines/Commodore/Plus4/Plus4.cpp | 57 ++++++++++++++-- Machines/Commodore/Plus4/Video.hpp | 32 ++++++++- .../Clock Signal.xcodeproj/project.pbxproj | 2 + 4 files changed, 149 insertions(+), 10 deletions(-) create mode 100644 Machines/Commodore/Plus4/Interrupts.hpp diff --git a/Machines/Commodore/Plus4/Interrupts.hpp b/Machines/Commodore/Plus4/Interrupts.hpp new file mode 100644 index 0000000000..66e9f16824 --- /dev/null +++ b/Machines/Commodore/Plus4/Interrupts.hpp @@ -0,0 +1,68 @@ +// +// Interrupts.hpp +// Clock Signal +// +// Created by Thomas Harte on 12/12/2024. +// Copyright © 2024 Thomas Harte. All rights reserved. +// + +#pragma once + +#include "../../../Processors/6502/6502.hpp" + +namespace Commodore::Plus4 { + +struct Interrupts { +public: + struct Delegate { + virtual void set_irq_line(bool) = 0; + }; + + Interrupts(Delegate &delegate) : delegate_(delegate) {} + + enum Flag { + Timer3 = 0x40, + Timer2 = 0x10, + Timer1 = 0x08, + Raster = 0x02, + }; + + uint8_t status() const { + return status_ | ((status_ & mask_) ? 0x80 : 0x00); + } + + uint8_t mask() const { + return mask_; + } + + void set_status(const uint8_t status) { + status_ = status & 0x7f; + update_output(); + } + + void apply(const uint8_t interrupt) { + status_ |= interrupt; + update_output(); + } + + void set_mask(const uint8_t mask) { + mask_ = mask; + update_output(); + } + +private: + void update_output() { + const bool set = status_ & mask_; + if(set != last_set_) { + delegate_.set_irq_line(set); + last_set_ = set; + } + } + + Delegate &delegate_; + uint8_t status_; + uint8_t mask_; + bool last_set_ = false; +}; + +} diff --git a/Machines/Commodore/Plus4/Plus4.cpp b/Machines/Commodore/Plus4/Plus4.cpp index 7c4f0086ab..30c14c36a0 100644 --- a/Machines/Commodore/Plus4/Plus4.cpp +++ b/Machines/Commodore/Plus4/Plus4.cpp @@ -8,6 +8,7 @@ #include "Plus4.hpp" +#include "Interrupts.hpp" #include "Pager.hpp" #include "Video.hpp" @@ -21,6 +22,8 @@ namespace { class Timers { public: + Timers(Interrupts &interrupts) : interrupts_(interrupts) {} + template void write(const uint8_t value) { const auto load_low = [&](uint16_t &target) { @@ -78,16 +81,24 @@ class Timers { // Check for interrupt. if(!timers_[timer]) { + switch(timer) { + case 0: interrupts_.apply(Interrupts::Flag::Timer1); break; + case 1: interrupts_.apply(Interrupts::Flag::Timer2); break; + case 2: interrupts_.apply(Interrupts::Flag::Timer3); break; + } } } uint16_t timers_[3]{}; uint16_t timer0_reload_ = 0xffff; bool paused_[3]{}; + + Interrupts &interrupts_; }; class ConcreteMachine: public CPU::MOS6502::BusHandler, + public Interrupts::Delegate, public MachineTypes::TimedMachine, public MachineTypes::ScanProducer, public MachineTypes::MediaTarget, @@ -95,7 +106,9 @@ class ConcreteMachine: public: ConcreteMachine(const Analyser::Static::Commodore::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) : m6502_(*this), - video_(map_) + interrupts_(*this), + timers_(interrupts_), + video_(map_, interrupts_) { // PAL: 8867240 divided by 5 or 4? // NTSC: 7159090? @@ -155,12 +168,24 @@ class ConcreteMachine: } else { if(isReadOperation(operation)) { switch(address) { - case 0xff00: *value = timers_.read<0>(); break; - case 0xff01: *value = timers_.read<1>(); break; - case 0xff02: *value = timers_.read<2>(); break; - case 0xff03: *value = timers_.read<3>(); break; - case 0xff04: *value = timers_.read<4>(); break; - case 0xff05: *value = timers_.read<5>(); break; + case 0xff00: *value = timers_.read<0>(); break; + case 0xff01: *value = timers_.read<1 >(); break; + case 0xff02: *value = timers_.read<2>(); break; + case 0xff03: *value = timers_.read<3>(); break; + case 0xff04: *value = timers_.read<4>(); break; + case 0xff05: *value = timers_.read<5>(); break; + + case 0xff08: + // TODO: keyboard. + *value = 0xff; + break; + + case 0xff09: *value = interrupts_.status(); break; + case 0xff0a: *value = interrupts_.mask(); break; + + case 0xff0b: *value = video_.read<0xff0b>(); break; + case 0xff1c: *value = video_.read<0xff1c>(); break; + case 0xff1d: *value = video_.read<0xff1d>(); break; default: printf("TODO: TED read at %04x\n", address); @@ -174,6 +199,19 @@ class ConcreteMachine: case 0xff04: timers_.write<4>(*value); break; case 0xff05: timers_.write<5>(*value); break; + case 0xff08: + // TODO: keyboard. + break; + + case 0xff09: + interrupts_.set_status(*value); + break; + case 0xff0a: + interrupts_.set_mask(*value); + video_.write<0xff0a>(*value); + break; + case 0xff0b: video_.write<0xff0b>(*value); break; + case 0xff06: video_.write<0xff06>(*value); break; case 0xff07: video_.write<0xff07>(*value); break; case 0xff0c: video_.write<0xff0c>(*value); break; @@ -198,6 +236,10 @@ class ConcreteMachine: private: CPU::MOS6502::Processor m6502_; + void set_irq_line(bool active) override { + m6502_.set_irq_line(active); + } + void page_rom() { map_.page(basic_.data()); map_.page(kernel_.data()); @@ -227,6 +269,7 @@ class ConcreteMachine: std::vector kernel_; std::vector basic_; + Interrupts interrupts_; Cycles timers_subcycles_; Timers timers_; Video video_; diff --git a/Machines/Commodore/Plus4/Video.hpp b/Machines/Commodore/Plus4/Video.hpp index 41c0fce149..0e79f13f6c 100644 --- a/Machines/Commodore/Plus4/Video.hpp +++ b/Machines/Commodore/Plus4/Video.hpp @@ -8,6 +8,7 @@ #pragma once +#include "Interrupts.hpp" #include "Pager.hpp" #include "../../../Numeric/UpperBound.hpp" @@ -19,14 +20,26 @@ namespace Commodore::Plus4 { struct Video { public: - Video(const Commodore::Plus4::Pager &pager) : + Video(const Commodore::Plus4::Pager &pager, Interrupts &interrupts) : crt_(465, 1, Outputs::Display::Type::PAL50, Outputs::Display::InputDataType::Luminance8Phase8), - pager_(pager) + pager_(pager), + interrupts_(interrupts) { // TODO: perfect crop. crt_.set_visible_area(Outputs::Display::Rect(0.075f, 0.065f, 0.85f, 0.85f)); } + template + uint8_t read() const { + switch(address) { + case 0xff0b: return uint8_t(raster_interrupt_); + case 0xff1c: return uint8_t(vertical_counter_ >> 8); + case 0xff0d: return uint8_t(vertical_counter_); + } + + return 0xff; + } + template void write(const uint8_t value) { const auto load_high10 = [&](uint16_t &target) { @@ -65,6 +78,13 @@ struct Video { screen_memory_address_ = uint16_t((value & 0xf8) << 8); break; + case 0xff0a: + raster_interrupt_ = (raster_interrupt_ & 0x00ff) | ((value & 1) << 8); + break; + case 0xff0b: + raster_interrupt_ = (raster_interrupt_ & 0xff00) | value; + break; + case 0xff0c: load_high10(cursor_address_); break; case 0xff0d: load_low8(cursor_address_); break; case 0xff1a: load_high10(character_row_address_); break; @@ -144,6 +164,9 @@ struct Video { case 257: if(!is_ntsc_) vertical_sync_ = false; break; // PAL vsync end. case 269: if(!is_ntsc_) vertical_blank_ = false; break; // PAL vertical blank end. } + if(raster_interrupt_ == vertical_counter_) { + interrupts_.apply(Interrupts::Flag::Raster); + } const auto next = Numeric::upper_bound< 0, 3, 288, 290, 296, 304, 307, 315, 328, 336, 344, 358, 376, 384, 390, 400, 416, 424, 432, 440, 451, 465 @@ -216,7 +239,7 @@ struct Video { break; case 376: // Increment vertical line. - ++vertical_counter_; + vertical_counter_ = (vertical_counter_ + 1) & 0x1ff; break; case 384: // Burst start, end of screen — clear vertical line, vertical sub and character reload registers. @@ -274,6 +297,8 @@ struct Video { uint16_t character_generator_address_ = 0; uint16_t screen_memory_address_ = 0; + int raster_interrupt_ = 0x1ff; + // Field position. int horizontal_counter_ = 0; int vertical_counter_ = 0; @@ -304,6 +329,7 @@ struct Video { std::array background_{}; const Commodore::Plus4::Pager &pager_; + Interrupts &interrupts_; }; } diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 354ca63ea6..4d30204e7d 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -1299,6 +1299,7 @@ 4B03291F2D0923E300C51EB5 /* Video.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Video.hpp; sourceTree = ""; }; 4B0329202D0A78DC00C51EB5 /* UpperBound.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = UpperBound.hpp; sourceTree = ""; }; 4B0329212D0A8C4700C51EB5 /* Pager.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Pager.hpp; sourceTree = ""; }; + 4B0329222D0BD32500C51EB5 /* Interrupts.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Interrupts.hpp; sourceTree = ""; }; 4B0333AD2094081A0050B93D /* AppleDSK.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = AppleDSK.cpp; sourceTree = ""; }; 4B0333AE2094081A0050B93D /* AppleDSK.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = AppleDSK.hpp; sourceTree = ""; }; 4B046DC31CFE651500E9E45E /* ScanProducer.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ScanProducer.hpp; sourceTree = ""; }; @@ -3395,6 +3396,7 @@ isa = PBXGroup; children = ( 4B596EB02D037E8800FBF4B1 /* Plus4.cpp */, + 4B0329222D0BD32500C51EB5 /* Interrupts.hpp */, 4B0329212D0A8C4700C51EB5 /* Pager.hpp */, 4B596EAF2D037E8800FBF4B1 /* Plus4.hpp */, 4B03291F2D0923E300C51EB5 /* Video.hpp */, From c2fc26089e184d8374f18e7423a55dfdb24d4f35 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 12 Dec 2024 22:15:15 -0500 Subject: [PATCH 018/103] Add background colour reading, fix writing. --- Machines/Commodore/Plus4/Plus4.cpp | 12 ++++++++++++ Machines/Commodore/Plus4/Video.hpp | 12 +++++++++--- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/Machines/Commodore/Plus4/Plus4.cpp b/Machines/Commodore/Plus4/Plus4.cpp index 30c14c36a0..65d2d70c6e 100644 --- a/Machines/Commodore/Plus4/Plus4.cpp +++ b/Machines/Commodore/Plus4/Plus4.cpp @@ -187,6 +187,12 @@ class ConcreteMachine: case 0xff1c: *value = video_.read<0xff1c>(); break; case 0xff1d: *value = video_.read<0xff1d>(); break; + case 0xff15: *value = video_.read<0xff15>(); break; + case 0xff16: *value = video_.read<0xff16>(); break; + case 0xff17: *value = video_.read<0xff17>(); break; + case 0xff18: *value = video_.read<0xff18>(); break; + case 0xff19: *value = video_.read<0xff19>(); break; + default: printf("TODO: TED read at %04x\n", address); } @@ -221,6 +227,12 @@ class ConcreteMachine: case 0xff1a: video_.write<0xff1a>(*value); break; case 0xff1b: video_.write<0xff1b>(*value); break; + case 0xff15: video_.write<0xff15>(*value); break; + case 0xff16: video_.write<0xff16>(*value); break; + case 0xff17: video_.write<0xff17>(*value); break; + case 0xff18: video_.write<0xff18>(*value); break; + case 0xff19: video_.write<0xff19>(*value); break; + case 0xff3e: page_rom(); break; case 0xff3f: page_ram(); break; diff --git a/Machines/Commodore/Plus4/Video.hpp b/Machines/Commodore/Plus4/Video.hpp index 0e79f13f6c..766cf1aa50 100644 --- a/Machines/Commodore/Plus4/Video.hpp +++ b/Machines/Commodore/Plus4/Video.hpp @@ -34,7 +34,10 @@ struct Video { switch(address) { case 0xff0b: return uint8_t(raster_interrupt_); case 0xff1c: return uint8_t(vertical_counter_ >> 8); - case 0xff0d: return uint8_t(vertical_counter_); + case 0xff1d: return uint8_t(vertical_counter_); + + case 0xff15: case 0xff16: case 0xff17: case 0xff18: case 0xff19: + return raw_background_[size_t(address - 0xff15)]; } return 0xff; @@ -91,11 +94,13 @@ struct Video { case 0xff1b: load_low8(character_row_address_); break; case 0xff15: case 0xff16: case 0xff17: case 0xff18: case 0xff19: { + raw_background_[size_t(address - 0xff15)] = value; + const uint8_t luminance = uint8_t( ((value & 0x70) << 1) | ((value & 0x70) >> 2) | ((value & 0x70) >> 5) ); - background_[value - 0xff15] = uint16_t( - luminance + background_[size_t(address - 0xff15)] = uint16_t( + luminance | 0xff00 ); printf("%02x -> %04x\n", value, address); @@ -327,6 +332,7 @@ struct Video { int time_in_state_ = 0; std::array background_{}; + std::array raw_background_{}; const Commodore::Plus4::Pager &pager_; Interrupts &interrupts_; From 663acd3810c0a1c46bfc8bc0b031e9a07bb007bc Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 12 Dec 2024 22:35:50 -0500 Subject: [PATCH 019/103] Map initial border colour, white and black. --- Machines/Commodore/Plus4/Video.hpp | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/Machines/Commodore/Plus4/Video.hpp b/Machines/Commodore/Plus4/Video.hpp index 766cf1aa50..8efe2dbb0e 100644 --- a/Machines/Commodore/Plus4/Video.hpp +++ b/Machines/Commodore/Plus4/Video.hpp @@ -96,11 +96,27 @@ struct Video { case 0xff15: case 0xff16: case 0xff17: case 0xff18: case 0xff19: { raw_background_[size_t(address - 0xff15)] = value; - const uint8_t luminance = uint8_t( + const uint8_t luminance = (value & 0x0f) ? uint8_t( ((value & 0x70) << 1) | ((value & 0x70) >> 2) | ((value & 0x70) >> 5) - ); + ) : 0; + const auto chrominance = uint8_t([&] { + switch(value & 0x0f) { + default: + printf("Unmapped colour: %d\n", value & 0x0f); + [[fallthrough]]; + case 0: + case 1: return 0xff; + + // The following have been eyeballed. +// case 5: return 3; +// case 7: return 3; +// case 11: return 3; + case 14: return 5; + } + }()); + background_[size_t(address - 0xff15)] = uint16_t( - luminance | 0xff00 + luminance | (chrominance << 8) ); printf("%02x -> %04x\n", value, address); From 363ad7342a67df8c93ca257a90aad1b71323ea7c Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 12 Dec 2024 22:59:20 -0500 Subject: [PATCH 020/103] Add memory fuzzing, some text output. --- Machines/Commodore/Plus4/Plus4.cpp | 63 ++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/Machines/Commodore/Plus4/Plus4.cpp b/Machines/Commodore/Plus4/Plus4.cpp index 65d2d70c6e..da5cbf94c9 100644 --- a/Machines/Commodore/Plus4/Plus4.cpp +++ b/Machines/Commodore/Plus4/Plus4.cpp @@ -13,6 +13,7 @@ #include "Video.hpp" #include "../../MachineTypes.hpp" +#include "../../Utility/MemoryFuzzer.hpp" #include "../../../Processors/6502/6502.hpp" #include "../../../Analyser/Static/Commodore/Target.hpp" @@ -128,6 +129,7 @@ class ConcreteMachine: kernel_ = roms.find(kernel)->second; basic_ = roms.find(basic)->second; + Memory::Fuzz(ram_); map_.page(ram_.data()); page_rom(); @@ -269,7 +271,68 @@ class ConcreteMachine: } void run_for(const Cycles cycles) final { + static bool log = false; m6502_.run_for(cycles); + + if(!log) return; + for(size_t y = 0; y < 25; y++) { + for(size_t x = 0; x < 40; x++) { + const auto c = ram_[0xc00 + x + (y * 40)]; + printf("%c", [&] { + switch(c) { + case 0x00: return '@'; + case 0x01: return 'A'; + case 0x02: return 'B'; + case 0x03: return 'C'; + case 0x04: return 'D'; + case 0x05: return 'E'; + case 0x06: return 'F'; + case 0x07: return 'G'; + case 0x08: return 'H'; + case 0x09: return 'I'; + case 0x0a: return 'J'; + case 0x0b: return 'K'; + case 0x0c: return 'L'; + case 0x0d: return 'M'; + case 0x0e: return 'N'; + case 0x0f: return 'O'; + case 0x10: return 'P'; + case 0x11: return 'Q'; + case 0x12: return 'R'; + case 0x13: return 'S'; + case 0x14: return 'T'; + case 0x15: return 'U'; + case 0x16: return 'V'; + case 0x17: return 'W'; + case 0x18: return 'X'; + case 0x19: return 'Y'; + case 0x1a: return 'Z'; + case 0x1b: return '['; + case 0x1c: return '\\'; + case 0x1d: return ']'; + case 0x1e: return '?'; + case 0x1f: return '?'; + case 0x20: return ' '; + + case 0x2e: return '.'; + case 0x30: return '0'; + case 0x31: return '1'; + case 0x32: return '2'; + case 0x33: return '3'; + case 0x34: return '4'; + case 0x35: return '5'; + case 0x36: return '6'; + case 0x37: return '7'; + case 0x38: return '8'; + case 0x39: return '9'; + + default: return '?'; + } + }()); + } + printf("\n"); + } + printf("\n"); } bool insert_media(const Analyser::Static::Media &) final { From ff92bdb3247b4ef8fef63f0860aef852196dc967 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 13 Dec 2024 13:31:15 -0500 Subject: [PATCH 021/103] Add buffer for pixels, output _something_. --- Machines/Commodore/Plus4/Plus4.cpp | 9 ++++++--- Machines/Commodore/Plus4/Video.hpp | 29 +++++++++++++++++++++++++++-- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/Machines/Commodore/Plus4/Plus4.cpp b/Machines/Commodore/Plus4/Plus4.cpp index da5cbf94c9..f23d1a968f 100644 --- a/Machines/Commodore/Plus4/Plus4.cpp +++ b/Machines/Commodore/Plus4/Plus4.cpp @@ -115,7 +115,7 @@ class ConcreteMachine: // NTSC: 7159090? // i.e. colour subcarriers multiplied by two? - set_clock_rate(7159090); // TODO. + set_clock_rate(8867240); // TODO. const auto kernel = ROM::Name::Plus4KernelPALv5; const auto basic = ROM::Name::Plus4BASIC; @@ -141,8 +141,9 @@ class ConcreteMachine: const uint16_t address, uint8_t *const value ) { - // TODO: calculate length of this bus operation. - const auto length = Cycles(5); + // Determine from the TED video subsystem the length of this clock cycle as perceived by the 6502, + // relative to the master clock. + const auto length = video_.cycle_length(operation == CPU::MOS6502::BusOperation::Ready); // Update other subsystems. // TODO: timers decrement at a 894 KHz rate for NTSC television systems, 884 KHZ for PAL systems. @@ -179,6 +180,7 @@ class ConcreteMachine: case 0xff08: // TODO: keyboard. + // For now: all keys unpressed. *value = 0xff; break; @@ -255,6 +257,7 @@ class ConcreteMachine: } void page_rom() { + // TODO: allow other ROM selection. And no ROM? map_.page(basic_.data()); map_.page(kernel_.data()); } diff --git a/Machines/Commodore/Plus4/Video.hpp b/Machines/Commodore/Plus4/Video.hpp index 8efe2dbb0e..c09c2262af 100644 --- a/Machines/Commodore/Plus4/Video.hpp +++ b/Machines/Commodore/Plus4/Video.hpp @@ -126,6 +126,18 @@ struct Video { // printf("bitmap:%d c256:%d ntsc:%d 40col:%d; base:%04x\n", bitmap_mode_, characters_256_, is_ntsc_, columns_40_, screen_memory_address_); } + Cycles cycle_length([[maybe_unused]] bool is_ready) const { + // TODO: the complete test is more than this. + // TODO: if this is a RDY cycle, can reply with time until end-of-RDY. + const bool is_long_cycle = refresh_; + + if(is_ntsc_) { + return is_long_cycle ? Cycles(8) : Cycles(4); + } else { + return is_long_cycle ? Cycles(10) : Cycles(5); + } + } + // Outer clock is [NTSC or PAL] colour subcarrier * 2. // // 65 cycles = 64µs? @@ -212,13 +224,25 @@ struct Video { case OutputState::Sync: crt_.output_sync(time_in_state_); break; case OutputState::Burst: crt_.output_default_colour_burst(time_in_state_); break; case OutputState::Border: crt_.output_level(time_in_state_, background_[4]); break; - case OutputState::Pixels: crt_.output_level(time_in_state_, background_[0]); break; + case OutputState::Pixels: crt_.output_data(time_in_state_, time_in_state_); break; } time_in_state_ = 0; + + output_state_ = state; + if(output_state_ == OutputState::Pixels) { + pixels_ = reinterpret_cast(crt_.begin_data(384, 2)); + } } - output_state_ = state; time_in_state_ += period; + // Output pixels. TODO: properly. + if(pixels_) { + for(int c = 0; c < period; c++) { + pixels_[c] = ((c + horizontal_counter_) & 1) ? 0x0000 : 0xffff; + } + pixels_ += period; + } + // // Advance for current period. // @@ -346,6 +370,7 @@ struct Video { Pixels, } output_state_ = OutputState::Blank; int time_in_state_ = 0; + uint16_t *pixels_ = nullptr; std::array background_{}; std::array raw_background_{}; From 1d9c3fb827d5f8d17111e2f7b0c59ecfe9053ecc Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 13 Dec 2024 13:56:12 -0500 Subject: [PATCH 022/103] Hack in some text output. --- Machines/Commodore/Plus4/Pager.hpp | 2 +- Machines/Commodore/Plus4/Plus4.cpp | 62 +----------------------------- Machines/Commodore/Plus4/Video.hpp | 17 ++++++-- 3 files changed, 16 insertions(+), 65 deletions(-) diff --git a/Machines/Commodore/Plus4/Pager.hpp b/Machines/Commodore/Plus4/Pager.hpp index c033f1b583..1bb17789bf 100644 --- a/Machines/Commodore/Plus4/Pager.hpp +++ b/Machines/Commodore/Plus4/Pager.hpp @@ -17,7 +17,7 @@ enum PagerSide { template class Pager { public: - DataT read(AddressT address) { + DataT read(AddressT address) const { return read_[address >> Shift][address]; } DataT &write(AddressT address) { diff --git a/Machines/Commodore/Plus4/Plus4.cpp b/Machines/Commodore/Plus4/Plus4.cpp index f23d1a968f..99895e18ef 100644 --- a/Machines/Commodore/Plus4/Plus4.cpp +++ b/Machines/Commodore/Plus4/Plus4.cpp @@ -227,6 +227,7 @@ class ConcreteMachine: case 0xff0c: video_.write<0xff0c>(*value); break; case 0xff0d: video_.write<0xff0d>(*value); break; case 0xff12: video_.write<0xff12>(*value); break; + case 0xff13: video_.write<0xff13>(*value); break; case 0xff14: video_.write<0xff14>(*value); break; case 0xff1a: video_.write<0xff1a>(*value); break; case 0xff1b: video_.write<0xff1b>(*value); break; @@ -274,68 +275,7 @@ class ConcreteMachine: } void run_for(const Cycles cycles) final { - static bool log = false; m6502_.run_for(cycles); - - if(!log) return; - for(size_t y = 0; y < 25; y++) { - for(size_t x = 0; x < 40; x++) { - const auto c = ram_[0xc00 + x + (y * 40)]; - printf("%c", [&] { - switch(c) { - case 0x00: return '@'; - case 0x01: return 'A'; - case 0x02: return 'B'; - case 0x03: return 'C'; - case 0x04: return 'D'; - case 0x05: return 'E'; - case 0x06: return 'F'; - case 0x07: return 'G'; - case 0x08: return 'H'; - case 0x09: return 'I'; - case 0x0a: return 'J'; - case 0x0b: return 'K'; - case 0x0c: return 'L'; - case 0x0d: return 'M'; - case 0x0e: return 'N'; - case 0x0f: return 'O'; - case 0x10: return 'P'; - case 0x11: return 'Q'; - case 0x12: return 'R'; - case 0x13: return 'S'; - case 0x14: return 'T'; - case 0x15: return 'U'; - case 0x16: return 'V'; - case 0x17: return 'W'; - case 0x18: return 'X'; - case 0x19: return 'Y'; - case 0x1a: return 'Z'; - case 0x1b: return '['; - case 0x1c: return '\\'; - case 0x1d: return ']'; - case 0x1e: return '?'; - case 0x1f: return '?'; - case 0x20: return ' '; - - case 0x2e: return '.'; - case 0x30: return '0'; - case 0x31: return '1'; - case 0x32: return '2'; - case 0x33: return '3'; - case 0x34: return '4'; - case 0x35: return '5'; - case 0x36: return '6'; - case 0x37: return '7'; - case 0x38: return '8'; - case 0x39: return '9'; - - default: return '?'; - } - }()); - } - printf("\n"); - } - printf("\n"); } bool insert_media(const Analyser::Static::Media &) final { diff --git a/Machines/Commodore/Plus4/Video.hpp b/Machines/Commodore/Plus4/Video.hpp index c09c2262af..d9c22418b8 100644 --- a/Machines/Commodore/Plus4/Video.hpp +++ b/Machines/Commodore/Plus4/Video.hpp @@ -75,6 +75,9 @@ struct Video { break; case 0xff12: +// bitmap_base_ = uint16_t((value & 0x3c) << 10); + break; + case 0xff13: character_generator_address_ = uint16_t((value & 0xfc) << 8); break; case 0xff14: @@ -233,15 +236,23 @@ struct Video { pixels_ = reinterpret_cast(crt_.begin_data(384, 2)); } } - time_in_state_ += period; - // Output pixels. TODO: properly. + // Output pixels. + // TODO: properly. THIS HACKS IN TEXT OUTPUT. IT IS NOT CORRECT. NOT AS TO TIMING, NOT AS TO CONTENT. if(pixels_) { for(int c = 0; c < period; c++) { - pixels_[c] = ((c + horizontal_counter_) & 1) ? 0x0000 : 0xffff; + const auto pixel = time_in_state_ + c; + const auto row = vertical_counter_ - 4; + + const auto index = (row >> 3) * 40 + (pixel >> 3); + const uint8_t character = pager_.read(uint16_t(index + screen_memory_address_ + 0x400)); + const uint8_t glyph = pager_.read(character_generator_address_ + character * 8 + (row & 7)); + + pixels_[c] = glyph & (0x80 >> (pixel & 7)) ? 0xff00 : 0xffff; } pixels_ += period; } + time_in_state_ += period; // // Advance for current period. From 83a9ef772a1a23ec20693aa1f23d6d156f0a9e70 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 13 Dec 2024 14:02:33 -0500 Subject: [PATCH 023/103] Add TODO explaining all currently-unhandled writes. --- Machines/Commodore/Plus4/Plus4.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Machines/Commodore/Plus4/Plus4.cpp b/Machines/Commodore/Plus4/Plus4.cpp index 99895e18ef..931ebba7ba 100644 --- a/Machines/Commodore/Plus4/Plus4.cpp +++ b/Machines/Commodore/Plus4/Plus4.cpp @@ -241,6 +241,8 @@ class ConcreteMachine: case 0xff3e: page_rom(); break; case 0xff3f: page_ram(); break; + // TODO: audio is 0xff10, 0xff11, 0xff0e, 0xff0f and shares 0xff18. + default: printf("TODO: TED write at %04x\n", address); } From 1b5d446635dbec9cb609a271346f1959482c5a36 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 13 Dec 2024 17:22:05 -0500 Subject: [PATCH 024/103] Fix: writes to interrupt status _clear_. --- Machines/Commodore/Plus4/Interrupts.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Machines/Commodore/Plus4/Interrupts.hpp b/Machines/Commodore/Plus4/Interrupts.hpp index 66e9f16824..8cd50f483f 100644 --- a/Machines/Commodore/Plus4/Interrupts.hpp +++ b/Machines/Commodore/Plus4/Interrupts.hpp @@ -36,7 +36,7 @@ struct Interrupts { } void set_status(const uint8_t status) { - status_ = status & 0x7f; + status_ &= ~status; update_output(); } From 1628af2ffc5622d40f93ce7aabd7f414bd1cc8df Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 13 Dec 2024 17:56:47 -0500 Subject: [PATCH 025/103] Provide a stuck down key 'a'. --- Machines/Commodore/Plus4/Plus4.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/Machines/Commodore/Plus4/Plus4.cpp b/Machines/Commodore/Plus4/Plus4.cpp index 931ebba7ba..0a782adc18 100644 --- a/Machines/Commodore/Plus4/Plus4.cpp +++ b/Machines/Commodore/Plus4/Plus4.cpp @@ -178,11 +178,7 @@ class ConcreteMachine: case 0xff04: *value = timers_.read<4>(); break; case 0xff05: *value = timers_.read<5>(); break; - case 0xff08: - // TODO: keyboard. - // For now: all keys unpressed. - *value = 0xff; - break; + case 0xff08: *value = keyboard_latch_; break; case 0xff09: *value = interrupts_.status(); break; case 0xff0a: *value = interrupts_.mask(); break; @@ -210,7 +206,12 @@ class ConcreteMachine: case 0xff05: timers_.write<5>(*value); break; case 0xff08: - // TODO: keyboard. + printf("Keyboard: %02x\n", *value); + + keyboard_latch_ = (*value & 2) ? 0xff : 0xfb; + // TODO: keyboard. Low bits on the data bus select keyboard lines. + // Pressed keys drain to 0. + // TODO: possibly fd30 imputes joystick inputs here too? break; case 0xff09: @@ -289,6 +290,8 @@ class ConcreteMachine: std::vector kernel_; std::vector basic_; + uint8_t keyboard_latch_ = 0xff; + Interrupts interrupts_; Cycles timers_subcycles_; Timers timers_; From a1f6e93e22a93a169690806ad656fee3ef6feb3d Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 13 Dec 2024 21:24:11 -0500 Subject: [PATCH 026/103] Add most of the keyboard. --- Machines/Commodore/Plus4/Keyboard.cpp | 130 ++++++++++++++++++++++++++ Machines/Commodore/Plus4/Keyboard.hpp | 76 +++++++++++++++ Machines/Commodore/Plus4/Plus4.cpp | 41 ++++++-- 3 files changed, 237 insertions(+), 10 deletions(-) create mode 100644 Machines/Commodore/Plus4/Keyboard.cpp create mode 100644 Machines/Commodore/Plus4/Keyboard.hpp diff --git a/Machines/Commodore/Plus4/Keyboard.cpp b/Machines/Commodore/Plus4/Keyboard.cpp new file mode 100644 index 0000000000..ca48ef1bb2 --- /dev/null +++ b/Machines/Commodore/Plus4/Keyboard.cpp @@ -0,0 +1,130 @@ +// +// Keyboard.cpp +// Clock Signal +// +// Created by Thomas Harte on 13/12/2024. +// Copyright 2024 Thomas Harte. All rights reserved. +// + +#include "Keyboard.hpp" + +using namespace Commodore::Plus4; + +uint16_t KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) const { +#define BIND(source, dest) case Inputs::Keyboard::Key::source: return Commodore::Plus4::dest + switch(key) { + default: break; + + BIND(k0, k0); BIND(k1, k1); BIND(k2, k2); BIND(k3, k3); BIND(k4, k4); + BIND(k5, k5); BIND(k6, k6); BIND(k7, k7); BIND(k8, k8); BIND(k9, k9); + BIND(Q, Q); BIND(W, W); BIND(E, E); BIND(R, R); BIND(T, T); + BIND(Y, Y); BIND(U, U); BIND(I, I); BIND(O, O); BIND(P, P); + BIND(A, A); BIND(S, S); BIND(D, D); BIND(F, F); BIND(G, G); + BIND(H, H); BIND(J, J); BIND(K, K); BIND(L, L); + BIND(Z, Z); BIND(X, X); BIND(C, C); BIND(V, V); + BIND(B, B); BIND(N, N); BIND(M, M); + + BIND(Backspace, InsDel); + BIND(Escape, Escape); + BIND(F1, F1_F4); + BIND(F2, F2_F5); + BIND(F3, F3_F6); + BIND(F4, Help_F7); + BIND(Enter, Return); + BIND(Space, Space); + + BIND(Up, Up); BIND(Down, Down); BIND(Left, Left); BIND(Right, Right); + + BIND(LeftShift, Shift); BIND(RightShift, Shift); + BIND(LeftControl, Control); BIND(RightControl, Control); + BIND(LeftOption, Commodore); BIND(RightOption, Commodore); + + BIND(FullStop, FullStop); BIND(Comma, Comma); + BIND(Semicolon, Semicolon); BIND(Quote, Colon); + BIND(Equals, Equals); BIND(ForwardSlash, Slash); + + // TODO: + // At + // GBP + // Asterisk + // Clear_Home + // Run_Stop + } +#undef BIND + return MachineTypes::MappedKeyboardMachine::KeyNotMapped; +} + +//const uint16_t *CharacterMapper::sequence_for_character(char character) const { +//#define KEYS(...) {__VA_ARGS__, MachineTypes::MappedKeyboardMachine::KeyEndSequence} +//#define SHIFT(...) {KeyLShift, __VA_ARGS__, MachineTypes::MappedKeyboardMachine::KeyEndSequence} +//#define X {MachineTypes::MappedKeyboardMachine::KeyNotMapped} +// static KeySequence key_sequences[] = { +// /* NUL */ X, /* SOH */ X, +// /* STX */ X, /* ETX */ X, +// /* EOT */ X, /* ENQ */ X, +// /* ACK */ X, /* BEL */ X, +// /* BS */ KEYS(KeyDelete), /* HT */ X, +// /* LF */ KEYS(KeyReturn), /* VT */ X, +// /* FF */ X, /* CR */ X, +// /* SO */ X, /* SI */ X, +// /* DLE */ X, /* DC1 */ X, +// /* DC2 */ X, /* DC3 */ X, +// /* DC4 */ X, /* NAK */ X, +// /* SYN */ X, /* ETB */ X, +// /* CAN */ X, /* EM */ X, +// /* SUB */ X, /* ESC */ X, +// /* FS */ X, /* GS */ X, +// /* RS */ X, /* US */ X, +// /* space */ KEYS(KeySpace), /* ! */ SHIFT(Key1), +// /* " */ SHIFT(Key2), /* # */ SHIFT(Key3), +// /* $ */ SHIFT(Key4), /* % */ SHIFT(Key5), +// /* & */ SHIFT(Key6), /* ' */ SHIFT(Key7), +// /* ( */ SHIFT(Key8), /* ) */ SHIFT(Key9), +// /* * */ KEYS(KeyAsterisk), /* + */ KEYS(KeyPlus), +// /* , */ KEYS(KeyComma), /* - */ KEYS(KeyDash), +// /* . */ KEYS(KeyFullStop), /* / */ KEYS(KeySlash), +// /* 0 */ KEYS(Key0), /* 1 */ KEYS(Key1), +// /* 2 */ KEYS(Key2), /* 3 */ KEYS(Key3), +// /* 4 */ KEYS(Key4), /* 5 */ KEYS(Key5), +// /* 6 */ KEYS(Key6), /* 7 */ KEYS(Key7), +// /* 8 */ KEYS(Key8), /* 9 */ KEYS(Key9), +// /* : */ KEYS(KeyColon), /* ; */ KEYS(KeySemicolon), +// /* < */ SHIFT(KeyComma), /* = */ KEYS(KeyEquals), +// /* > */ SHIFT(KeyFullStop), /* ? */ SHIFT(KeySlash), +// /* @ */ KEYS(KeyAt), /* A */ KEYS(KeyA), +// /* B */ KEYS(KeyB), /* C */ KEYS(KeyC), +// /* D */ KEYS(KeyD), /* E */ KEYS(KeyE), +// /* F */ KEYS(KeyF), /* G */ KEYS(KeyG), +// /* H */ KEYS(KeyH), /* I */ KEYS(KeyI), +// /* J */ KEYS(KeyJ), /* K */ KEYS(KeyK), +// /* L */ KEYS(KeyL), /* M */ KEYS(KeyM), +// /* N */ KEYS(KeyN), /* O */ KEYS(KeyO), +// /* P */ KEYS(KeyP), /* Q */ KEYS(KeyQ), +// /* R */ KEYS(KeyR), /* S */ KEYS(KeyS), +// /* T */ KEYS(KeyT), /* U */ KEYS(KeyU), +// /* V */ KEYS(KeyV), /* W */ KEYS(KeyW), +// /* X */ KEYS(KeyX), /* Y */ KEYS(KeyY), +// /* Z */ KEYS(KeyZ), /* [ */ SHIFT(KeyColon), +// /* \ */ X, /* ] */ SHIFT(KeySemicolon), +// /* ^ */ X, /* _ */ X, +// /* ` */ X, /* a */ KEYS(KeyA), +// /* b */ KEYS(KeyB), /* c */ KEYS(KeyC), +// /* d */ KEYS(KeyD), /* e */ KEYS(KeyE), +// /* f */ KEYS(KeyF), /* g */ KEYS(KeyG), +// /* h */ KEYS(KeyH), /* i */ KEYS(KeyI), +// /* j */ KEYS(KeyJ), /* k */ KEYS(KeyK), +// /* l */ KEYS(KeyL), /* m */ KEYS(KeyM), +// /* n */ KEYS(KeyN), /* o */ KEYS(KeyO), +// /* p */ KEYS(KeyP), /* q */ KEYS(KeyQ), +// /* r */ KEYS(KeyR), /* s */ KEYS(KeyS), +// /* t */ KEYS(KeyT), /* u */ KEYS(KeyU), +// /* v */ KEYS(KeyV), /* w */ KEYS(KeyW), +// /* x */ KEYS(KeyX), /* y */ KEYS(KeyY), +// /* z */ KEYS(KeyZ) +// }; +//#undef KEYS +//#undef SHIFT +//#undef X +// +// return table_lookup_sequence_for_character(key_sequences, character); +//} diff --git a/Machines/Commodore/Plus4/Keyboard.hpp b/Machines/Commodore/Plus4/Keyboard.hpp new file mode 100644 index 0000000000..1519976f4e --- /dev/null +++ b/Machines/Commodore/Plus4/Keyboard.hpp @@ -0,0 +1,76 @@ +// +// Keyboard.hpp +// Clock Signal +// +// Created by Thomas Harte on 13/12/2024. +// Copyright 2024 Thomas Harte. All rights reserved. +// + +#pragma once + +#include "../../KeyboardMachine.hpp" +#include "../../Utility/Typer.hpp" + +namespace Commodore::Plus4 { + +static constexpr uint16_t key(const int line, const int mask) { + return uint16_t((mask << 3) | line); +} +static constexpr size_t line(const uint16_t key) { + return key & 7; +} +static constexpr uint8_t mask(const uint16_t key) { + return uint8_t(key >> 3); +} + +enum Key: uint16_t { + InsDel = key(0, 0x01), Return = key(0, 0x02), + GBP = key(0, 0x04), Help_F7 = key(0, 0x08), + F1_F4 = key(0, 0x10), F2_F5 = key(0, 0x20), + F3_F6 = key(0, 0x40), At = key(0, 0x80), + + k3 = key(1, 0x01), W = key(1, 0x02), + A = key(1, 0x04), k4 = key(1, 0x08), + Z = key(1, 0x10), S = key(1, 0x20), + E = key(1, 0x40), Shift = key(1, 0x80), + + k5 = key(2, 0x01), R = key(2, 0x02), + D = key(2, 0x04), k6 = key(2, 0x08), + C = key(2, 0x10), F = key(2, 0x20), + T = key(2, 0x40), X = key(2, 0x80), + + k7 = key(3, 0x01), Y = key(3, 0x02), + G = key(3, 0x04), k8 = key(3, 0x08), + B = key(3, 0x10), H = key(3, 0x20), + U = key(3, 0x40), V = key(3, 0x80), + + k9 = key(4, 0x01), I = key(4, 0x02), + J = key(4, 0x04), k0 = key(4, 0x08), + M = key(4, 0x10), K = key(4, 0x20), + O = key(4, 0x40), N = key(4, 0x80), + + Down = key(5, 0x01), P = key(5, 0x02), + L = key(5, 0x04), Up = key(5, 0x08), + FullStop = key(5, 0x10), Colon = key(5, 0x20), + Minus = key(5, 0x40), Comma = key(5, 0x80), + + Left = key(6, 0x01), Asterisk = key(6, 0x02), + Semicolon = key(6, 0x04), Right = key(6, 0x08), + Escape = key(6, 0x10), Equals = key(6, 0x20), + Plus = key(6, 0x40), Slash = key(6, 0x80), + + k1 = key(7, 0x01), Clear_Home = key(7, 0x02), + Control = key(7, 0x04), k2 = key(7, 0x08), + Space = key(7, 0x10), Commodore = key(7, 0x20), + Q = key(7, 0x40), Run_Stop = key(7, 0x80), +}; + +struct KeyboardMapper: public MachineTypes::MappedKeyboardMachine::KeyboardMapper { + uint16_t mapped_key_for_key(Inputs::Keyboard::Key key) const final; +}; + +//struct CharacterMapper: public ::Utility::CharacterMapper { +// const uint16_t *sequence_for_character(char character) const final; +//}; + +} diff --git a/Machines/Commodore/Plus4/Plus4.cpp b/Machines/Commodore/Plus4/Plus4.cpp index 0a782adc18..2bafcb074f 100644 --- a/Machines/Commodore/Plus4/Plus4.cpp +++ b/Machines/Commodore/Plus4/Plus4.cpp @@ -9,6 +9,7 @@ #include "Plus4.hpp" #include "Interrupts.hpp" +#include "Keyboard.hpp" #include "Pager.hpp" #include "Video.hpp" @@ -100,6 +101,7 @@ class Timers { class ConcreteMachine: public CPU::MOS6502::BusHandler, public Interrupts::Delegate, + public MachineTypes::MappedKeyboardMachine, public MachineTypes::TimedMachine, public MachineTypes::ScanProducer, public MachineTypes::MediaTarget, @@ -205,14 +207,18 @@ class ConcreteMachine: case 0xff04: timers_.write<4>(*value); break; case 0xff05: timers_.write<5>(*value); break; - case 0xff08: - printf("Keyboard: %02x\n", *value); - - keyboard_latch_ = (*value & 2) ? 0xff : 0xfb; - // TODO: keyboard. Low bits on the data bus select keyboard lines. - // Pressed keys drain to 0. - // TODO: possibly fd30 imputes joystick inputs here too? - break; + case 0xff08: { + keyboard_latch_ = ~( + ((*value & 0x01) ? 0x00 : key_states_[0]) | + ((*value & 0x02) ? 0x00 : key_states_[1]) | + ((*value & 0x04) ? 0x00 : key_states_[2]) | + ((*value & 0x08) ? 0x00 : key_states_[3]) | + ((*value & 0x10) ? 0x00 : key_states_[4]) | + ((*value & 0x20) ? 0x00 : key_states_[5]) | + ((*value & 0x40) ? 0x00 : key_states_[6]) | + ((*value & 0x80) ? 0x00 : key_states_[7]) + ); + } break; case 0xff09: interrupts_.set_status(*value); @@ -290,12 +296,27 @@ class ConcreteMachine: std::vector kernel_; std::vector basic_; - uint8_t keyboard_latch_ = 0xff; - Interrupts interrupts_; Cycles timers_subcycles_; Timers timers_; Video video_; + + // MARK: - MappedKeyboardMachine. + MappedKeyboardMachine::KeyboardMapper *get_keyboard_mapper() override { + static Commodore::Plus4::KeyboardMapper keyboard_mapper_; + return &keyboard_mapper_; + } + + void set_key_state(uint16_t key, bool is_pressed) override { + if(is_pressed) { + key_states_[line(key)] |= mask(key); + } else { + key_states_[line(key)] &= ~mask(key); + } + } + + std::array key_states_{}; + uint8_t keyboard_latch_ = 0xff; }; } From 700b848f26ebcbc7a08db8825613f57976578114 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 13 Dec 2024 21:25:01 -0500 Subject: [PATCH 027/103] Add overt `nil`s to aid with debugging. --- OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj | 10 ++++++++++ .../xcshareddata/xcschemes/Clock Signal.xcscheme | 2 +- OSBindings/Mac/Clock Signal/ScanTarget/CSScanTarget.mm | 7 ++++++- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 4d30204e7d..7eb396ec91 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -595,6 +595,9 @@ 4B7F188F2154825E00388727 /* MasterSystem.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7F188C2154825D00388727 /* MasterSystem.cpp */; }; 4B7F1897215486A200388727 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7F1896215486A100388727 /* StaticAnalyser.cpp */; }; 4B7F1898215486A200388727 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7F1896215486A100388727 /* StaticAnalyser.cpp */; }; + 4B8058032D0D0B55007EAD46 /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8058022D0D0B55007EAD46 /* Keyboard.cpp */; }; + 4B8058042D0D0B55007EAD46 /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8058022D0D0B55007EAD46 /* Keyboard.cpp */; }; + 4B8058052D0D0B55007EAD46 /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8058022D0D0B55007EAD46 /* Keyboard.cpp */; }; 4B80CD6F2568A82C00176FCC /* DiskIIDrive.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B80CD6D2568A82600176FCC /* DiskIIDrive.cpp */; }; 4B80CD76256CA16400176FCC /* 2MG.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B80CD74256CA15E00176FCC /* 2MG.cpp */; }; 4B80CD77256CA16600176FCC /* 2MG.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B80CD74256CA15E00176FCC /* 2MG.cpp */; }; @@ -1714,6 +1717,8 @@ 4B7F1895215486A100388727 /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = StaticAnalyser.hpp; sourceTree = ""; }; 4B7F1896215486A100388727 /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StaticAnalyser.cpp; sourceTree = ""; }; 4B80214322EE7C3E00068002 /* JustInTime.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = JustInTime.hpp; sourceTree = ""; }; + 4B8058012D0D0B55007EAD46 /* Keyboard.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Keyboard.hpp; sourceTree = ""; }; + 4B8058022D0D0B55007EAD46 /* Keyboard.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Keyboard.cpp; sourceTree = ""; }; 4B80CD6D2568A82600176FCC /* DiskIIDrive.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DiskIIDrive.cpp; sourceTree = ""; }; 4B80CD6E2568A82900176FCC /* DiskIIDrive.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = DiskIIDrive.hpp; sourceTree = ""; }; 4B80CD74256CA15E00176FCC /* 2MG.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = 2MG.cpp; sourceTree = ""; }; @@ -3395,8 +3400,10 @@ 4B596EB12D037E8800FBF4B1 /* Plus4 */ = { isa = PBXGroup; children = ( + 4B8058022D0D0B55007EAD46 /* Keyboard.cpp */, 4B596EB02D037E8800FBF4B1 /* Plus4.cpp */, 4B0329222D0BD32500C51EB5 /* Interrupts.hpp */, + 4B8058012D0D0B55007EAD46 /* Keyboard.hpp */, 4B0329212D0A8C4700C51EB5 /* Pager.hpp */, 4B596EAF2D037E8800FBF4B1 /* Plus4.hpp */, 4B03291F2D0923E300C51EB5 /* Video.hpp */, @@ -6104,6 +6111,7 @@ 4B8DD3872634D37E00B3C866 /* SNA.cpp in Sources */, 4BB505792B962DDF0031C43C /* SoundGenerator.cpp in Sources */, 4B055AA91FAE85EF0060FFFF /* CommodoreGCR.cpp in Sources */, + 4B8058042D0D0B55007EAD46 /* Keyboard.cpp in Sources */, 4B055ADB1FAE9B460060FFFF /* 6560.cpp in Sources */, 4B17B58C20A8A9D9007CCA8F /* StringSerialiser.cpp in Sources */, 4B055AA01FAE85DA0060FFFF /* MFMSectorDump.cpp in Sources */, @@ -6370,6 +6378,7 @@ 4BB4BFAD22A33DE50069048D /* DriveSpeedAccumulator.cpp in Sources */, 4BC080D926A25ADA00D03FD8 /* Amiga.cpp in Sources */, 4B2B3A4B1F9B8FA70062DABF /* Typer.cpp in Sources */, + 4B8058052D0D0B55007EAD46 /* Keyboard.cpp in Sources */, 4B4518821F75E91A00926311 /* PCMSegment.cpp in Sources */, 4B0DA67B282DCDF100C12F17 /* Instruction.cpp in Sources */, 4B74CF812312FA9C00500CE8 /* HFV.cpp in Sources */, @@ -6704,6 +6713,7 @@ 4BD4A8D01E077FD20020D856 /* PCMTrackTests.mm in Sources */, 4B778F2123A5EDD50000D260 /* TrackSerialiser.cpp in Sources */, 4B049CDD1DA3C82F00322067 /* BCDTest.swift in Sources */, + 4B8058032D0D0B55007EAD46 /* Keyboard.cpp in Sources */, 4B06AADF2C645F830034D014 /* Video.cpp in Sources */, 4BC6237226F94BCB00F83DFE /* MintermTests.mm in Sources */, 4B7752BF28217F250073E2C5 /* Sprites.cpp in Sources */, diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme b/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme index 1dec5b8a5d..2ab9f42e46 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme +++ b/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme @@ -74,7 +74,7 @@ debugDocumentVersioning = "YES" migratedStopOnEveryIssue = "YES" debugServiceExtension = "internal" - enableGPUShaderValidationMode = "2" + enableGPUValidationMode = "1" allowLocationSimulation = "NO"> diff --git a/OSBindings/Mac/Clock Signal/ScanTarget/CSScanTarget.mm b/OSBindings/Mac/Clock Signal/ScanTarget/CSScanTarget.mm index 94b1c96f6f..9f43ff1fc2 100644 --- a/OSBindings/Mac/Clock Signal/ScanTarget/CSScanTarget.mm +++ b/OSBindings/Mac/Clock Signal/ScanTarget/CSScanTarget.mm @@ -390,6 +390,7 @@ - (void)updateSizeBuffers { [encoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4]; [encoder endEncoding]; + encoder = nil; [commandBuffer commit]; return commandBuffer; @@ -885,6 +886,7 @@ - (void)outputFrom:(size_t)start to:(size_t)end commandBuffer:(id)commandBuffer { @@ -901,11 +903,12 @@ - (void)outputFrameCleanerToCommandBuffer:(id)commandBuffer { [encoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4]; [encoder endEncoding]; + encoder = nil; } - (void)composeOutputArea:(const BufferingScanTarget::OutputArea &)outputArea commandBuffer:(id)commandBuffer { // Output all scans to the composition buffer. - const id encoder = [commandBuffer renderCommandEncoderWithDescriptor:_compositionRenderPass]; + id encoder = [commandBuffer renderCommandEncoderWithDescriptor:_compositionRenderPass]; [encoder setRenderPipelineState:_composePipeline]; [encoder setVertexBuffer:_scansBuffer offset:0 atIndex:0]; @@ -919,6 +922,7 @@ - (void)composeOutputArea:(const BufferingScanTarget::OutputArea &)outputArea co RangePerform(outputArea.start.scan, outputArea.end.scan, NumBufferedScans, OutputScans); #undef OutputScans [encoder endEncoding]; + encoder = nil; } - (id)bufferForOffset:(size_t)offset { @@ -1141,6 +1145,7 @@ - (void)drawInMTKView:(nonnull MTKView *)view { [encoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4]; [encoder endEncoding]; + encoder = nil; [commandBuffer presentDrawable:view.currentDrawable]; [commandBuffer addCompletedHandler:^(id _Nonnull) { From f41b54de21f095ead70c9a224b10a865752423d9 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 13 Dec 2024 22:25:23 -0500 Subject: [PATCH 028/103] Make a close-enough guess at chrominances. --- Machines/Commodore/Plus4/Plus4.cpp | 24 +++++++++++++--- Machines/Commodore/Plus4/Video.hpp | 44 ++++++++++++++---------------- 2 files changed, 41 insertions(+), 27 deletions(-) diff --git a/Machines/Commodore/Plus4/Plus4.cpp b/Machines/Commodore/Plus4/Plus4.cpp index 2bafcb074f..9c18940e5d 100644 --- a/Machines/Commodore/Plus4/Plus4.cpp +++ b/Machines/Commodore/Plus4/Plus4.cpp @@ -185,10 +185,16 @@ class ConcreteMachine: case 0xff09: *value = interrupts_.status(); break; case 0xff0a: *value = interrupts_.mask(); break; + case 0xff06: *value = video_.read<0xff06>(); break; + case 0xff07: *value = video_.read<0xff07>(); break; case 0xff0b: *value = video_.read<0xff0b>(); break; case 0xff1c: *value = video_.read<0xff1c>(); break; case 0xff1d: *value = video_.read<0xff1d>(); break; + case 0xff12: *value = ff12_; break; + case 0xff13: *value = ff13_ | (rom_is_paged_ ? 1 : 0); break; + + case 0xff14: *value = video_.read<0xff14>(); break; case 0xff15: *value = video_.read<0xff15>(); break; case 0xff16: *value = video_.read<0xff16>(); break; case 0xff17: *value = video_.read<0xff17>(); break; @@ -207,7 +213,7 @@ class ConcreteMachine: case 0xff04: timers_.write<4>(*value); break; case 0xff05: timers_.write<5>(*value); break; - case 0xff08: { + case 0xff08: keyboard_latch_ = ~( ((*value & 0x01) ? 0x00 : key_states_[0]) | ((*value & 0x02) ? 0x00 : key_states_[1]) | @@ -218,7 +224,7 @@ class ConcreteMachine: ((*value & 0x40) ? 0x00 : key_states_[6]) | ((*value & 0x80) ? 0x00 : key_states_[7]) ); - } break; + break; case 0xff09: interrupts_.set_status(*value); @@ -233,8 +239,14 @@ class ConcreteMachine: case 0xff07: video_.write<0xff07>(*value); break; case 0xff0c: video_.write<0xff0c>(*value); break; case 0xff0d: video_.write<0xff0d>(*value); break; - case 0xff12: video_.write<0xff12>(*value); break; - case 0xff13: video_.write<0xff13>(*value); break; + case 0xff12: + ff12_ = *value & 0x3f; + video_.write<0xff12>(*value); + break; + case 0xff13: + ff13_ = *value & 0xfe; + video_.write<0xff13>(*value); + break; case 0xff14: video_.write<0xff14>(*value); break; case 0xff1a: video_.write<0xff1a>(*value); break; case 0xff1b: video_.write<0xff1b>(*value); break; @@ -270,10 +282,13 @@ class ConcreteMachine: // TODO: allow other ROM selection. And no ROM? map_.page(basic_.data()); map_.page(kernel_.data()); + rom_is_paged_ = true; } void page_ram() { map_.page(&ram_[0x8000]); + rom_is_paged_ = false; } + bool rom_is_paged_ = false; void set_scan_target(Outputs::Display::ScanTarget *const target) final { video_.set_scan_target(target); @@ -295,6 +310,7 @@ class ConcreteMachine: std::array ram_; std::vector kernel_; std::vector basic_; + uint8_t ff12_, ff13_; Interrupts interrupts_; Cycles timers_subcycles_; diff --git a/Machines/Commodore/Plus4/Video.hpp b/Machines/Commodore/Plus4/Video.hpp index d9c22418b8..4db2632220 100644 --- a/Machines/Commodore/Plus4/Video.hpp +++ b/Machines/Commodore/Plus4/Video.hpp @@ -32,9 +32,12 @@ struct Video { template uint8_t read() const { switch(address) { + case 0xff06: return ff06_; + case 0xff07: return ff07_; case 0xff0b: return uint8_t(raster_interrupt_); case 0xff1c: return uint8_t(vertical_counter_ >> 8); case 0xff1d: return uint8_t(vertical_counter_); + case 0xff14: return uint8_t((screen_memory_address_ >> 8) & 0xf8); case 0xff15: case 0xff16: case 0xff17: case 0xff18: case 0xff19: return raw_background_[size_t(address - 0xff15)]; @@ -58,6 +61,7 @@ struct Video { switch(address) { case 0xff06: + ff06_ = value; extended_colour_mode_ = value & 0x40; bitmap_mode_ = value & 0x20; display_enable_ = value & 0x10; @@ -66,6 +70,7 @@ struct Video { break; case 0xff07: + ff07_ = value; characters_256_ = value & 0x80; is_ntsc_ = value & 0x40; ted_off_ = value & 0x20; @@ -79,6 +84,7 @@ struct Video { break; case 0xff13: character_generator_address_ = uint16_t((value & 0xfc) << 8); + single_clock_ = value & 0x02; break; case 0xff14: screen_memory_address_ = uint16_t((value & 0xf8) << 8); @@ -102,37 +108,17 @@ struct Video { const uint8_t luminance = (value & 0x0f) ? uint8_t( ((value & 0x70) << 1) | ((value & 0x70) >> 2) | ((value & 0x70) >> 5) ) : 0; - const auto chrominance = uint8_t([&] { - switch(value & 0x0f) { - default: - printf("Unmapped colour: %d\n", value & 0x0f); - [[fallthrough]]; - case 0: - case 1: return 0xff; - - // The following have been eyeballed. -// case 5: return 3; -// case 7: return 3; -// case 11: return 3; - case 14: return 5; - } - }()); - background_[size_t(address - 0xff15)] = uint16_t( - luminance | (chrominance << 8) + luminance | (chrominances[value & 0x0f] << 8) ); - - printf("%02x -> %04x\n", value, address); } break; } - -// printf("bitmap:%d c256:%d ntsc:%d 40col:%d; base:%04x\n", bitmap_mode_, characters_256_, is_ntsc_, columns_40_, screen_memory_address_); } Cycles cycle_length([[maybe_unused]] bool is_ready) const { // TODO: the complete test is more than this. // TODO: if this is a RDY cycle, can reply with time until end-of-RDY. - const bool is_long_cycle = refresh_; + const bool is_long_cycle = single_clock_ || refresh_; if(is_ntsc_) { return is_long_cycle ? Cycles(8) : Cycles(4); @@ -227,7 +213,7 @@ struct Video { case OutputState::Sync: crt_.output_sync(time_in_state_); break; case OutputState::Burst: crt_.output_default_colour_burst(time_in_state_); break; case OutputState::Border: crt_.output_level(time_in_state_, background_[4]); break; - case OutputState::Pixels: crt_.output_data(time_in_state_, time_in_state_); break; + case OutputState::Pixels: crt_.output_data(time_in_state_, size_t(time_in_state_)); break; } time_in_state_ = 0; @@ -366,12 +352,15 @@ struct Video { bool horizontal_blank_ = false; bool horizontal_sync_ = false; bool horizontal_burst_ = false; + uint8_t ff06_; uint16_t character_address_ = 0; uint16_t line_character_address_ = 0; bool fetch_characters_; bool output_pixels_; bool refresh_ = false; + bool single_clock_ = false; + uint8_t ff07_; enum class OutputState { Blank, @@ -388,6 +377,15 @@ struct Video { const Commodore::Plus4::Pager &pager_; Interrupts &interrupts_; + + // The following aren't accurate; they're eyeballed to be close enough for now in PAL. + static constexpr uint8_t chrominances[] = { + 0xff, 0xff, + 90, 23, 105, 59, + 14, 69, 83, 78, + 50, 96, 32, 9, + 5, 41, + }; }; } From 589903c43cb03ae91e4952712952f8ee195a0dc0 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 13 Dec 2024 23:14:00 -0500 Subject: [PATCH 029/103] Add safety rail. --- Machines/Commodore/Plus4/Plus4.cpp | 2 +- Machines/Commodore/Plus4/Video.hpp | 10 ++++++++-- Numeric/UpperBound.hpp | 14 +++++++++++++- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/Machines/Commodore/Plus4/Plus4.cpp b/Machines/Commodore/Plus4/Plus4.cpp index 9c18940e5d..3e72799702 100644 --- a/Machines/Commodore/Plus4/Plus4.cpp +++ b/Machines/Commodore/Plus4/Plus4.cpp @@ -174,7 +174,7 @@ class ConcreteMachine: if(isReadOperation(operation)) { switch(address) { case 0xff00: *value = timers_.read<0>(); break; - case 0xff01: *value = timers_.read<1 >(); break; + case 0xff01: *value = timers_.read<1>(); break; case 0xff02: *value = timers_.read<2>(); break; case 0xff03: *value = timers_.read<3>(); break; case 0xff04: *value = timers_.read<4>(); break; diff --git a/Machines/Commodore/Plus4/Video.hpp b/Machines/Commodore/Plus4/Video.hpp index 4db2632220..ba91923e2d 100644 --- a/Machines/Commodore/Plus4/Video.hpp +++ b/Machines/Commodore/Plus4/Video.hpp @@ -191,7 +191,7 @@ struct Video { } const auto next = Numeric::upper_bound< - 0, 3, 288, 290, 296, 304, 307, 315, 328, 336, 344, 358, 376, 384, 390, 400, 416, 424, 432, 440, 451, 465 + 0, 3, 288, 290, 296, 304, 307, 315, 328, 336, 344, 358, 368, 376, 384, 390, 400, 416, 424, 432, 440, 451, 465 >(horizontal_counter_); const auto period = std::min(next - horizontal_counter_, ticks_remaining); @@ -295,7 +295,13 @@ struct Video { case 408: horizontal_burst_ = false; break; // Burst end. case 416: horizontal_blank_ = false; break; // Horizontal blanking end. - case 424: // Increment character position reload. + case 368: + if(raster_interrupt_ == vertical_counter_) { + interrupts_.apply(Interrupts::Flag::Raster); + } + break; + + case 424: // Increment character position reload; also interrput time. break; case 440: // Video shift register start. diff --git a/Numeric/UpperBound.hpp b/Numeric/UpperBound.hpp index f00d08a80d..bc735ca5e1 100644 --- a/Numeric/UpperBound.hpp +++ b/Numeric/UpperBound.hpp @@ -16,7 +16,7 @@ namespace Numeric { /// E.g. @c at_index<0, 3, 5, 6, 7, 8, 9>() returns the `3 - 0` = 4th element from the /// list 5, 6, 7, 8, 9, i.e. 8. template -int at_index() { +constexpr int at_index() { if constexpr (origin == index || sizeof...(Args) == 0) { return T; } else { @@ -40,10 +40,22 @@ int upper_bound_bounded(int location) { } } +template +constexpr int is_ordered() { + if constexpr (sizeof...(Args) == index + 1) { + return true; + } else { + return + (at_index<0, index, Args...>() < at_index<0, index+1, Args...>()) && + is_ordered(); + } +} + /// @returns The result of binary searching for the first thing in the template arguments /// is strictly greater than @c location. template int upper_bound(int location) { + static_assert(is_ordered<0, Args...>(), "Template arguments must be in ascending order."); return upper_bound_bounded<0, sizeof...(Args), Args...>(location); } From 3e93004db6d8623f21b5890456551d18c018afc8 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 14 Dec 2024 11:55:10 -0500 Subject: [PATCH 030/103] Double nominal clock, to hit normative values. --- Machines/Commodore/Plus4/Plus4.cpp | 6 +++--- Machines/Commodore/Plus4/Video.hpp | 19 +++++++++++++------ 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/Machines/Commodore/Plus4/Plus4.cpp b/Machines/Commodore/Plus4/Plus4.cpp index 3e72799702..d95cad8052 100644 --- a/Machines/Commodore/Plus4/Plus4.cpp +++ b/Machines/Commodore/Plus4/Plus4.cpp @@ -113,11 +113,11 @@ class ConcreteMachine: timers_(interrupts_), video_(map_, interrupts_) { - // PAL: 8867240 divided by 5 or 4? - // NTSC: 7159090? + // PAL: 17,734,480 Mhz divided by 5 or 4? + // NTSC: 14,318,180 Mhz // i.e. colour subcarriers multiplied by two? - set_clock_rate(8867240); // TODO. + set_clock_rate(17'734'480); const auto kernel = ROM::Name::Plus4KernelPALv5; const auto basic = ROM::Name::Plus4BASIC; diff --git a/Machines/Commodore/Plus4/Video.hpp b/Machines/Commodore/Plus4/Video.hpp index ba91923e2d..07701db58e 100644 --- a/Machines/Commodore/Plus4/Video.hpp +++ b/Machines/Commodore/Plus4/Video.hpp @@ -121,9 +121,9 @@ struct Video { const bool is_long_cycle = single_clock_ || refresh_; if(is_ntsc_) { - return is_long_cycle ? Cycles(8) : Cycles(4); + return is_long_cycle ? Cycles(16) : Cycles(8); } else { - return is_long_cycle ? Cycles(10) : Cycles(5); + return is_long_cycle ? Cycles(20) : Cycles(10); } } @@ -141,11 +141,18 @@ struct Video { void run_for(Cycles cycles) { // Timing: // - // 456 cycles/line; - // if in PAL mode, divide input clock by 1.25 (?); - // see page 34 of plus4_tech.pdf for event times. + // Input clock is at 17.7Mhz PAL or 14.38Mhz NTSC. i.e. each is four times the colour subcarrier. + // + // In PAL mode, divide by 5 and multiply by 2 to get the internal pixel clock. + // + // In NTSC mode just dividing by 2 would do to get the pixel clock but in practice that's implemented as + // a divide by 4 and a multiply by 2 to keep it similar to the PAL code. + // + // That gives close enough to 456 pixel clocks per line in both systems so the TED just rolls with that. + + // See page 34 of plus4_tech.pdf for event times. - subcycles_ += cycles * 4; + subcycles_ += cycles * 2; auto ticks_remaining = subcycles_.divide(is_ntsc_ ? Cycles(4) : Cycles(5)).as(); while(ticks_remaining) { // From 702b6d65671eb20d3db24601dcecabd62777b26b Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 14 Dec 2024 11:56:13 -0500 Subject: [PATCH 031/103] Add extra note to self. --- Machines/Commodore/Plus4/Video.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Machines/Commodore/Plus4/Video.hpp b/Machines/Commodore/Plus4/Video.hpp index 07701db58e..37f851467d 100644 --- a/Machines/Commodore/Plus4/Video.hpp +++ b/Machines/Commodore/Plus4/Video.hpp @@ -175,7 +175,8 @@ struct Video { } break; - case 203: // Attribute fetch end. + case 203: // Attribute fetch end. But I think this might be fairly nominal, assuming attribute fetches + // are triggered by testing against y scroll. break; case 4: if(rows_25_) vertical_window_ = true; break; // Vertical screen window start (25 lines). From b76104d14507bce0cf9e9dfdb99b0b907e43fdbb Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 14 Dec 2024 22:06:18 -0500 Subject: [PATCH 032/103] Start edging towards proper video timing. --- Machines/Commodore/Plus4/Interrupts.hpp | 15 ++++++++++----- Machines/Commodore/Plus4/Plus4.cpp | 23 ++++++++++++++++++----- Machines/Commodore/Plus4/Video.hpp | 10 ++++++++++ 3 files changed, 38 insertions(+), 10 deletions(-) diff --git a/Machines/Commodore/Plus4/Interrupts.hpp b/Machines/Commodore/Plus4/Interrupts.hpp index 8cd50f483f..a493a3449a 100644 --- a/Machines/Commodore/Plus4/Interrupts.hpp +++ b/Machines/Commodore/Plus4/Interrupts.hpp @@ -12,13 +12,18 @@ namespace Commodore::Plus4 { +struct BusController { + virtual void set_irq_line(bool) = 0; + virtual void set_ready_line(bool) = 0; +}; + struct Interrupts { public: - struct Delegate { - virtual void set_irq_line(bool) = 0; - }; + Interrupts(BusController &delegate) : delegate_(delegate) {} + BusController &bus() { + return delegate_; + } - Interrupts(Delegate &delegate) : delegate_(delegate) {} enum Flag { Timer3 = 0x40, @@ -59,7 +64,7 @@ struct Interrupts { } } - Delegate &delegate_; + BusController &delegate_; uint8_t status_; uint8_t mask_; bool last_set_ = false; diff --git a/Machines/Commodore/Plus4/Plus4.cpp b/Machines/Commodore/Plus4/Plus4.cpp index d95cad8052..bb5dfdf3ab 100644 --- a/Machines/Commodore/Plus4/Plus4.cpp +++ b/Machines/Commodore/Plus4/Plus4.cpp @@ -99,8 +99,8 @@ class Timers { }; class ConcreteMachine: + public BusController, public CPU::MOS6502::BusHandler, - public Interrupts::Delegate, public MachineTypes::MappedKeyboardMachine, public MachineTypes::TimedMachine, public MachineTypes::ScanProducer, @@ -148,16 +148,26 @@ class ConcreteMachine: const auto length = video_.cycle_length(operation == CPU::MOS6502::BusOperation::Ready); // Update other subsystems. - // TODO: timers decrement at a 894 KHz rate for NTSC television systems, 884 KHZ for PAL systems. - // Probably a function of the speed register? timers_subcycles_ += length; - const auto timers_cycles = timers_subcycles_.divide(Cycles(5)); + const auto timers_cycles = timers_subcycles_.divide(video_.timer_cycle_length()); timers_.tick(timers_cycles.as()); video_.run_for(length); // Perform actual access. - if(address < 0xfd00 || address >= 0xff40) { + if(address < 0x0002) { + // TODO: 0x0000: data directions for parallel IO; 1 = output. + // TODO: 0x0001: + // b7 = serial data in; + // b6 = serial clock in and cassette write; + // b5 = [unconnected]; + // b4 = cassette read; + // b3 = cassette motor, 1 = off; + // b2 = serial ATN out; + // b1 = serial clock out and cassette write; + // b0 = serial data out. +// printf("%04x: %02x %c\n", address, *value, isReadOperation(operation) ? 'r' : 'w'); + } else if(address < 0xfd00 || address >= 0xff40) { if(isReadOperation(operation)) { *value = map_.read(address); } else { @@ -277,6 +287,9 @@ class ConcreteMachine: void set_irq_line(bool active) override { m6502_.set_irq_line(active); } + void set_ready_line(bool active) override { + m6502_.set_ready_line(active); + } void page_rom() { // TODO: allow other ROM selection. And no ROM? diff --git a/Machines/Commodore/Plus4/Video.hpp b/Machines/Commodore/Plus4/Video.hpp index 37f851467d..5a4b322912 100644 --- a/Machines/Commodore/Plus4/Video.hpp +++ b/Machines/Commodore/Plus4/Video.hpp @@ -127,6 +127,10 @@ struct Video { } } + Cycles timer_cycle_length() const { + return is_ntsc_ ? Cycles(16) : Cycles(20); + } + // Outer clock is [NTSC or PAL] colour subcarrier * 2. // // 65 cycles = 64µs? @@ -400,6 +404,12 @@ struct Video { 50, 96, 32, 9, 5, 41, }; + + enum class FetchPhase { + Waiting, + FetchingCharacters, + FetchingAttributs, + } fetch_phase_ = FetchPhase::Waiting; }; } From 3d7e016b42158fce1e2d1e2a373519f9e921bbbe Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 15 Dec 2024 08:46:07 -0500 Subject: [PATCH 033/103] Name horizontal events. --- Machines/Commodore/Plus4/Video.hpp | 87 +++++++++++++++++++++--------- 1 file changed, 61 insertions(+), 26 deletions(-) diff --git a/Machines/Commodore/Plus4/Video.hpp b/Machines/Commodore/Plus4/Video.hpp index 5a4b322912..14efd9dcf2 100644 --- a/Machines/Commodore/Plus4/Video.hpp +++ b/Machines/Commodore/Plus4/Video.hpp @@ -202,8 +202,46 @@ struct Video { interrupts_.apply(Interrupts::Flag::Raster); } + // List of events. + enum HorizontalEvent { + Begin38Columns = 3, + EndExternalFetchWindow = 288, + LatchCharacterPosition = 290, + EndCharacterFetchWindow = 296, + EndVideoShiftRegister = 304, + End38Columns = 307, + End40Columns = 315, + EndRefresh = 328, + IncrementBlink = 336, + BeginBlank = 344, + BeginSync = 358, + TestRasterInterrupt = 368, + IncrementVerticalLine = 376, + BeginBurst = 384, + EndSync = 390, + BeginExternalFetchWindow = 400, + EndBurst = 408, + EndBlank = 416, + IncrementCharacterPositionReload = 424, + BeginCharacterFetchWindow = 432, + BeginVideoShiftRegister = 440, + Begin40Columns = 451, + EndOfLine = 456, + }; + const auto next = Numeric::upper_bound< - 0, 3, 288, 290, 296, 304, 307, 315, 328, 336, 344, 358, 368, 376, 384, 390, 400, 416, 424, 432, 440, 451, 465 + 0, Begin38Columns, + EndExternalFetchWindow, LatchCharacterPosition, + EndCharacterFetchWindow, EndVideoShiftRegister, + End38Columns, End40Columns, + EndRefresh, IncrementBlink, + BeginBlank, BeginSync, + TestRasterInterrupt, IncrementVerticalLine, + BeginBurst, EndSync, + BeginExternalFetchWindow, EndBurst, + EndBlank, IncrementCharacterPositionReload, + BeginCharacterFetchWindow, BeginVideoShiftRegister, + Begin40Columns, EndOfLine >(horizontal_counter_); const auto period = std::min(next - horizontal_counter_, ticks_remaining); @@ -258,68 +296,65 @@ struct Video { horizontal_counter_ += period; ticks_remaining -= period; switch(horizontal_counter_) { - case 288: // External fetch window end, refresh single clock start, increment character position end. + case EndExternalFetchWindow: // TODO: release RDY if it was held. // TODO: increment character position end. refresh_ = true; break; - case 400: // External fetch window start. + case BeginExternalFetchWindow: // TODO: set RDY line if this is an appropriate row. break; - case 290: line_character_address_ = character_address_; break; // Latch character position to reload. + case LatchCharacterPosition: line_character_address_ = character_address_; break; - case 296: // Character window end, character window single clock end, increment refresh start. + case EndCharacterFetchWindow: fetch_characters_ = false; break; - case 432: // Character window start, character window single clock start, increment character position start. + case BeginCharacterFetchWindow: fetch_characters_ = true; break; - case 304: // Video shift register end. - break; - - case 3: if(!columns_40_) output_pixels_ = true; break; // 38-column screen start. - case 307: if(!columns_40_) output_pixels_ = false; break; // 38-column screen stop. - case 451: if(columns_40_) output_pixels_ = true; break; // 40-column screen start. - case 315: if(columns_40_) output_pixels_ = false; break; // 40-column screen end. + case Begin38Columns: if(!columns_40_) output_pixels_ = true; break; + case End38Columns: if(!columns_40_) output_pixels_ = false; break; + case Begin40Columns: if(columns_40_) output_pixels_ = true; break; + case End40Columns: if(columns_40_) output_pixels_ = false; break; - case 328: // Refresh single clock end. + case EndRefresh: refresh_ = false; break; - case 336: // Increment blink, increment refresh end. + case IncrementBlink: break; - case 376: // Increment vertical line. + case IncrementVerticalLine: vertical_counter_ = (vertical_counter_ + 1) & 0x1ff; break; - case 384: // Burst start, end of screen — clear vertical line, vertical sub and character reload registers. + case BeginBurst: horizontal_burst_ = true; // TODO: rest. break; - case 344: horizontal_blank_ = true; break; // Horizontal blanking start. - case 358: horizontal_sync_ = true; break; // Horizontal sync start. - case 390: horizontal_sync_ = false; break; // Horizontal sync end. - case 408: horizontal_burst_ = false; break; // Burst end. - case 416: horizontal_blank_ = false; break; // Horizontal blanking end. + case BeginBlank: horizontal_blank_ = true; break; + case BeginSync: horizontal_sync_ = true; break; + case EndSync: horizontal_sync_ = false; break; + case EndBurst: horizontal_burst_ = false; break; + case EndBlank: horizontal_blank_ = false; break; - case 368: + case TestRasterInterrupt: if(raster_interrupt_ == vertical_counter_) { interrupts_.apply(Interrupts::Flag::Raster); } break; - case 424: // Increment character position reload; also interrput time. + case IncrementCharacterPositionReload: break; - case 440: // Video shift register start. + case BeginVideoShiftRegister: break; - case 465: // Wraparound. + case EndOfLine: horizontal_counter_ = 0; break; } From 709f350d60c7938c9af4da2aa1c54403d278928c Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 16 Dec 2024 07:18:07 -0500 Subject: [PATCH 034/103] Begin usage of RDY. --- Machines/Commodore/Plus4/Plus4.cpp | 4 ++ Machines/Commodore/Plus4/Video.hpp | 103 ++++++++++++++++++----------- 2 files changed, 70 insertions(+), 37 deletions(-) diff --git a/Machines/Commodore/Plus4/Plus4.cpp b/Machines/Commodore/Plus4/Plus4.cpp index bb5dfdf3ab..5a4832b28c 100644 --- a/Machines/Commodore/Plus4/Plus4.cpp +++ b/Machines/Commodore/Plus4/Plus4.cpp @@ -154,6 +154,10 @@ class ConcreteMachine: video_.run_for(length); + if(operation == CPU::MOS6502::BusOperation::Ready) { + return length; + } + // Perform actual access. if(address < 0x0002) { // TODO: 0x0000: data directions for parallel IO; 1 = output. diff --git a/Machines/Commodore/Plus4/Video.hpp b/Machines/Commodore/Plus4/Video.hpp index 14efd9dcf2..83e39d81ff 100644 --- a/Machines/Commodore/Plus4/Video.hpp +++ b/Machines/Commodore/Plus4/Video.hpp @@ -116,9 +116,12 @@ struct Video { } Cycles cycle_length([[maybe_unused]] bool is_ready) const { - // TODO: the complete test is more than this. - // TODO: if this is a RDY cycle, can reply with time until end-of-RDY. - const bool is_long_cycle = single_clock_ || refresh_; + if(is_ready) { +// return +// Cycles(EndCharacterFetchWindow - horizontal_counter_ + EndOfLine) * is_ntsc_ ? Cycles(4) : Cycles(5) / 2; + } + + const bool is_long_cycle = single_clock_ || refresh_ || external_fetch_; if(is_ntsc_) { return is_long_cycle ? Cycles(16) : Cycles(8); @@ -202,33 +205,6 @@ struct Video { interrupts_.apply(Interrupts::Flag::Raster); } - // List of events. - enum HorizontalEvent { - Begin38Columns = 3, - EndExternalFetchWindow = 288, - LatchCharacterPosition = 290, - EndCharacterFetchWindow = 296, - EndVideoShiftRegister = 304, - End38Columns = 307, - End40Columns = 315, - EndRefresh = 328, - IncrementBlink = 336, - BeginBlank = 344, - BeginSync = 358, - TestRasterInterrupt = 368, - IncrementVerticalLine = 376, - BeginBurst = 384, - EndSync = 390, - BeginExternalFetchWindow = 400, - EndBurst = 408, - EndBlank = 416, - IncrementCharacterPositionReload = 424, - BeginCharacterFetchWindow = 432, - BeginVideoShiftRegister = 440, - Begin40Columns = 451, - EndOfLine = 456, - }; - const auto next = Numeric::upper_bound< 0, Begin38Columns, EndExternalFetchWindow, LatchCharacterPosition, @@ -238,7 +214,7 @@ struct Video { BeginBlank, BeginSync, TestRasterInterrupt, IncrementVerticalLine, BeginBurst, EndSync, - BeginExternalFetchWindow, EndBurst, + BeginExternalFetchWindow, //EndBurst, EndBlank, IncrementCharacterPositionReload, BeginCharacterFetchWindow, BeginVideoShiftRegister, Begin40Columns, EndOfLine @@ -255,6 +231,10 @@ struct Video { state = horizontal_burst_ ? OutputState::Burst : OutputState::Blank; } else { state = vertical_window_ && output_pixels_ ? OutputState::Pixels : OutputState::Border; + +// if(output_pixels_) { +// printf("%d -> %d\n", horizontal_counter_, horizontal_counter_ + period); +// } } if(state != output_state_) { @@ -303,13 +283,34 @@ struct Video { break; case BeginExternalFetchWindow: - // TODO: set RDY line if this is an appropriate row. + external_fetch_ = true; + switch(fetch_phase_) { + case FetchPhase::Waiting: + // TODO: the < 200 is obviously phoney baloney. Figure out what the actual condition is here. + if(vertical_counter_ < 200 && (vertical_counter_&7) == y_scroll_ && vertical_window_) { + fetch_phase_ = FetchPhase::FetchingCharacters; + } + break; + case FetchPhase::FetchingCharacters: + fetch_phase_ = FetchPhase::FetchingAttributes; + break; + case FetchPhase::FetchingAttributes: + fetch_phase_ = FetchPhase::Waiting; + break; + } + interrupts_.bus().set_ready_line(fetch_phase_ != FetchPhase::Waiting); + + horizontal_burst_ = false; break; - case LatchCharacterPosition: line_character_address_ = character_address_; break; + case LatchCharacterPosition: + line_character_address_ = character_address_; + break; case EndCharacterFetchWindow: fetch_characters_ = false; + external_fetch_ = false; + interrupts_.bus().set_ready_line(false); break; case BeginCharacterFetchWindow: fetch_characters_ = true; @@ -339,7 +340,7 @@ struct Video { case BeginBlank: horizontal_blank_ = true; break; case BeginSync: horizontal_sync_ = true; break; case EndSync: horizontal_sync_ = false; break; - case EndBurst: horizontal_burst_ = false; break; +// case EndBurst: horizontal_burst_ = false; break; case EndBlank: horizontal_blank_ = false; break; case TestRasterInterrupt: @@ -409,8 +410,9 @@ struct Video { uint16_t character_address_ = 0; uint16_t line_character_address_ = 0; - bool fetch_characters_; - bool output_pixels_; + bool fetch_characters_ = false; + bool external_fetch_ = false; + bool output_pixels_ = false; bool refresh_ = false; bool single_clock_ = false; uint8_t ff07_; @@ -443,8 +445,35 @@ struct Video { enum class FetchPhase { Waiting, FetchingCharacters, - FetchingAttributs, + FetchingAttributes, } fetch_phase_ = FetchPhase::Waiting; + + // List of events. + enum HorizontalEvent { + Begin38Columns = 3, + EndExternalFetchWindow = 288, + LatchCharacterPosition = 290, + EndCharacterFetchWindow = 300, // 296 + EndVideoShiftRegister = 304, + End38Columns = 307, + End40Columns = 315, + EndRefresh = 328, + IncrementBlink = 336, + BeginBlank = 344, + BeginSync = 358, + TestRasterInterrupt = 368, + IncrementVerticalLine = 376, + BeginBurst = 384, + EndSync = 390, + BeginExternalFetchWindow = 408, // 400, +// EndBurst = 408, + EndBlank = 416, + IncrementCharacterPositionReload = 424, + BeginCharacterFetchWindow = 436, // 432, + BeginVideoShiftRegister = 440, + Begin40Columns = 451, + EndOfLine = 456, + }; }; } From 9d5c10d4409382f8f4b74915cf4eb6c6f56133d6 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 16 Dec 2024 22:11:06 -0500 Subject: [PATCH 035/103] Edge towards realistic video collection. --- Machines/Commodore/Plus4/Video.hpp | 46 ++++++++++++++++++++++++++---- 1 file changed, 41 insertions(+), 5 deletions(-) diff --git a/Machines/Commodore/Plus4/Video.hpp b/Machines/Commodore/Plus4/Video.hpp index 83e39d81ff..c12972fc86 100644 --- a/Machines/Commodore/Plus4/Video.hpp +++ b/Machines/Commodore/Plus4/Video.hpp @@ -166,7 +166,9 @@ struct Video { // Test vertical first; this will catch both any programmed change that has occurred outside // of the loop and any change to the vertical counter that occurs during the horizontal runs. // - const auto attribute_fetch_start = []{}; + const auto attribute_fetch_start = [&] { + character_address_ = 0; + }; switch(vertical_counter_) { case 261: // End of screen NTSC. [and hence 0: Attribute fetch start]. if(is_ntsc_) { @@ -221,6 +223,31 @@ struct Video { >(horizontal_counter_); const auto period = std::min(next - horizontal_counter_, ticks_remaining); + // + // Fetch as appropriate. + // + if(fetch_characters_) { + const int start = fetch_count_ >> 3; + const int end = (fetch_count_ + period) >> 3; + fetch_count_ += period; + + auto &line = lines_[line_pointer_]; +// uint8_t attributes[40]; +// uint8_t characters[40]; +// } lines_[2]; +// int line_pointer_ = 0; + + for(int x = start; x < end; x++) { +// line.attributes[x] = pager_.read(character_generator_address_ + x * 8 + (row & 7)); +// line.characters[x] = pager_.read(uint16_t(line_character_address_ + x + screen_memory_address_ + 0x400)); + } + +// switch(fetch_phase_) { +// case FetchPh +// } + } + + // // Output. // @@ -231,10 +258,6 @@ struct Video { state = horizontal_burst_ ? OutputState::Burst : OutputState::Blank; } else { state = vertical_window_ && output_pixels_ ? OutputState::Pixels : OutputState::Border; - -// if(output_pixels_) { -// printf("%d -> %d\n", horizontal_counter_, horizontal_counter_ + period); -// } } if(state != output_state_) { @@ -286,6 +309,8 @@ struct Video { external_fetch_ = true; switch(fetch_phase_) { case FetchPhase::Waiting: + ++character_line_; + // TODO: the < 200 is obviously phoney baloney. Figure out what the actual condition is here. if(vertical_counter_ < 200 && (vertical_counter_&7) == y_scroll_ && vertical_window_) { fetch_phase_ = FetchPhase::FetchingCharacters; @@ -293,6 +318,7 @@ struct Video { break; case FetchPhase::FetchingCharacters: fetch_phase_ = FetchPhase::FetchingAttributes; + character_line_ = 0; break; case FetchPhase::FetchingAttributes: fetch_phase_ = FetchPhase::Waiting; @@ -314,6 +340,7 @@ struct Video { break; case BeginCharacterFetchWindow: fetch_characters_ = true; + fetch_count_ = 0; break; case Begin38Columns: if(!columns_40_) output_pixels_ = true; break; @@ -447,6 +474,15 @@ struct Video { FetchingCharacters, FetchingAttributes, } fetch_phase_ = FetchPhase::Waiting; + int character_line_ = 0; + int fetch_count_ = 0; + + struct LineBuffer { + uint8_t attributes[40]; + uint8_t characters[40]; + } lines_[2]; + uint8_t bitmap_[40]; + int line_pointer_ = 0; // List of events. enum HorizontalEvent { From 2240ada5dbcd21865b10d957ce0e3f8d0b695ef8 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 16 Dec 2024 22:11:49 -0500 Subject: [PATCH 036/103] Avoid going out of bounds below. --- Analyser/Static/Commodore/StaticAnalyser.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Analyser/Static/Commodore/StaticAnalyser.cpp b/Analyser/Static/Commodore/StaticAnalyser.cpp index f077348886..52c3bb73bc 100644 --- a/Analyser/Static/Commodore/StaticAnalyser.cpp +++ b/Analyser/Static/Commodore/StaticAnalyser.cpp @@ -108,7 +108,7 @@ std::optional analyse(const File &file) { while(true) { // Analysis has failed if there isn't at least one complete BASIC line from here. // Fall back on guessing the start address as a machine code entrypoint. - if(size_t(line_address - file.starting_address) + 5 >= file.data.size()) { + if(size_t(line_address - file.starting_address) + 5 >= file.data.size() || line_address < file.starting_address) { analysis.machine_code_addresses.push_back(file.starting_address); break; } From 5acdf3956646bfe16e37d81fa912ad22075d0476 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 16 Dec 2024 22:12:12 -0500 Subject: [PATCH 037/103] Use a normative, unique_ptr-based cache. --- Analyser/Static/Commodore/Disk.cpp | 32 ++++++++++++++++-------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/Analyser/Static/Commodore/Disk.cpp b/Analyser/Static/Commodore/Disk.cpp index 94d3a3b520..ef78d1ddb6 100644 --- a/Analyser/Static/Commodore/Disk.cpp +++ b/Analyser/Static/Commodore/Disk.cpp @@ -14,6 +14,7 @@ #include #include #include +#include using namespace Analyser::Static::Commodore; @@ -37,7 +38,7 @@ class CommodoreGCRParser: public Storage::Disk::Controller { @returns a sector if one was found; @c nullptr otherwise. */ - std::shared_ptr sector(const uint8_t track, const uint8_t sector) { + const Sector *sector(const uint8_t track, const uint8_t sector) { int difference = int(track) - int(track_); track_ = track; @@ -68,7 +69,7 @@ class CommodoreGCRParser: public Storage::Disk::Controller { int index_count_; int bit_count_; uint8_t track_; - std::shared_ptr sector_cache_[65536]; + std::unordered_map> sector_cache_; void process_input_bit(const int value) override { shift_register_ = ((shift_register_ << 1) | unsigned(value)) & 0x3ff; @@ -111,23 +112,24 @@ class CommodoreGCRParser: public Storage::Disk::Controller { index_count_++; } - std::shared_ptr get_sector(const uint8_t sector) { + const Sector *get_sector(const uint8_t sector) { const uint16_t sector_address = uint16_t((track_ << 8) | sector); - if(sector_cache_[sector_address]) return sector_cache_[sector_address]; + auto existing = sector_cache_.find(sector_address); + if(existing != sector_cache_.end()) return existing->second.get(); - const std::shared_ptr first_sector = get_next_sector(); + const auto first_sector = get_next_sector(); if(!first_sector) return first_sector; if(first_sector->sector == sector) return first_sector; - while(1) { - const std::shared_ptr next_sector = get_next_sector(); + while(true) { + const auto next_sector = get_next_sector(); if(next_sector->sector == first_sector->sector) return nullptr; if(next_sector->sector == sector) return next_sector; } } - std::shared_ptr get_next_sector() { - auto sector = std::make_shared(); + const Sector *get_next_sector() { + auto sector = std::make_unique(); const int max_index_count = index_count_ + 2; while(index_count_ < max_index_count) { @@ -160,8 +162,8 @@ class CommodoreGCRParser: public Storage::Disk::Controller { if(checksum == get_next_byte()) { uint16_t sector_address = uint16_t((sector->track << 8) | sector->sector); - sector_cache_[sector_address] = sector; - return sector; + auto pair = sector_cache_.emplace(sector_address, std::move(sector)); + return pair.first->second.get(); } } @@ -171,8 +173,8 @@ class CommodoreGCRParser: public Storage::Disk::Controller { std::vector Analyser::Static::Commodore::GetFiles(const std::shared_ptr &disk) { std::vector files; - auto parser = std::make_unique(); - parser->set_disk(disk); + CommodoreGCRParser parser; + parser.set_disk(disk); // Assemble directory. std::vector directory; @@ -180,7 +182,7 @@ std::vector Analyser::Static::Commodore::GetFiles(const std::shared_ptrsector(next_track, next_sector); + const auto sector = parser.sector(next_track, next_sector); if(!sector) break; directory.insert(directory.end(), sector->data.begin(), sector->data.end()); next_track = sector->data[0]; @@ -222,7 +224,7 @@ std::vector Analyser::Static::Commodore::GetFiles(const std::shared_ptrsector(next_track, next_sector); + const auto sector = parser.sector(next_track, next_sector); if(!sector) break; next_track = sector->data[0]; From 15583e7975fb81e64d21c80234a703bb12819623 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 16 Dec 2024 22:29:23 -0500 Subject: [PATCH 038/103] Avoid risk of unbounded memory consumption. --- Analyser/Static/Commodore/Disk.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Analyser/Static/Commodore/Disk.cpp b/Analyser/Static/Commodore/Disk.cpp index ef78d1ddb6..2d0f0381be 100644 --- a/Analyser/Static/Commodore/Disk.cpp +++ b/Analyser/Static/Commodore/Disk.cpp @@ -181,13 +181,21 @@ std::vector Analyser::Static::Commodore::GetFiles(const std::shared_ptr> visited; while(true) { + // Don't be fooled by disks that are encoded with a looping directory. + const auto key = std::make_pair(next_track, next_sector); + if(visited.find(key) != visited.end()) break; + visited.insert(key); + + // Append sector to directory and follow next link. const auto sector = parser.sector(next_track, next_sector); if(!sector) break; directory.insert(directory.end(), sector->data.begin(), sector->data.end()); next_track = sector->data[0]; next_sector = sector->data[1]; + // Check for end-of-directory. if(!next_track) break; } From c8fdde4c5e231d670e5a8adace296aca8529372a Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 17 Dec 2024 07:08:04 -0500 Subject: [PATCH 039/103] Clarify clock rates. --- Machines/Commodore/Plus4/Plus4.cpp | 9 ++++----- .../xcshareddata/xcschemes/Clock Signal.xcscheme | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/Machines/Commodore/Plus4/Plus4.cpp b/Machines/Commodore/Plus4/Plus4.cpp index 5a4832b28c..37915c62a5 100644 --- a/Machines/Commodore/Plus4/Plus4.cpp +++ b/Machines/Commodore/Plus4/Plus4.cpp @@ -98,6 +98,9 @@ class Timers { Interrupts &interrupts_; }; +static constexpr auto NTSCclock = 14'318'180; // i.e. colour subcarrier * 4. +static constexpr auto PALclock = 17'734'448; // i.e. very close to colour subcarrier * 4 — only about 0.1% off. + class ConcreteMachine: public BusController, public CPU::MOS6502::BusHandler, @@ -113,11 +116,7 @@ class ConcreteMachine: timers_(interrupts_), video_(map_, interrupts_) { - // PAL: 17,734,480 Mhz divided by 5 or 4? - // NTSC: 14,318,180 Mhz - // i.e. colour subcarriers multiplied by two? - - set_clock_rate(17'734'480); + set_clock_rate(PALclock); const auto kernel = ROM::Name::Plus4KernelPALv5; const auto basic = ROM::Name::Plus4BASIC; diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme b/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme index 2ab9f42e46..91f9702069 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme +++ b/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme @@ -23,7 +23,7 @@ Date: Wed, 18 Dec 2024 07:04:17 -0500 Subject: [PATCH 040/103] Add Keyboard. --- cmake/CLK_SOURCES.cmake | 1 + 1 file changed, 1 insertion(+) diff --git a/cmake/CLK_SOURCES.cmake b/cmake/CLK_SOURCES.cmake index 6e2a770e57..59fb865357 100644 --- a/cmake/CLK_SOURCES.cmake +++ b/cmake/CLK_SOURCES.cmake @@ -118,6 +118,7 @@ set(CLK_SOURCES Machines/Atari/ST/Video.cpp Machines/ColecoVision/ColecoVision.cpp Machines/Commodore/1540/Implementation/C1540.cpp + Machines/Commodore/Plus4/Keyboard.cpp Machines/Commodore/Plus4/Plus4.cpp Machines/Commodore/SerialBus.cpp Machines/Commodore/Vic-20/Keyboard.cpp From 81398d58a231675fc63d4dd6caaeb6d4b6aa92ab Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 18 Dec 2024 20:53:03 -0500 Subject: [PATCH 041/103] Improve `get_rect_for_area`, use in C16. --- Machines/Atari/ST/Video.cpp | 5 ++- Machines/Commodore/Plus4/Video.hpp | 9 ++++- Outputs/CRT/CRT.cpp | 58 ++++++++++++++++++++---------- 3 files changed, 51 insertions(+), 21 deletions(-) diff --git a/Machines/Atari/ST/Video.cpp b/Machines/Atari/ST/Video.cpp index 6fb41f39c8..90720c9768 100644 --- a/Machines/Atari/ST/Video.cpp +++ b/Machines/Atari/ST/Video.cpp @@ -127,7 +127,10 @@ Video::Video() : // Show a total of 260 lines; a little short for PAL but a compromise between that and the ST's // usual output height of 200 lines. - crt_.set_visible_area(crt_.get_rect_for_area(33, 260, 440, 1700, 4.0f / 3.0f)); + crt_.set_visible_area(crt_.get_rect_for_area( + 33, 260, + 480, 1280, + 4.0f / 3.0f)); } void Video::set_ram(uint16_t *ram, size_t size) { diff --git a/Machines/Commodore/Plus4/Video.hpp b/Machines/Commodore/Plus4/Video.hpp index c12972fc86..c075d984d0 100644 --- a/Machines/Commodore/Plus4/Video.hpp +++ b/Machines/Commodore/Plus4/Video.hpp @@ -26,7 +26,14 @@ struct Video { interrupts_(interrupts) { // TODO: perfect crop. - crt_.set_visible_area(Outputs::Display::Rect(0.075f, 0.065f, 0.85f, 0.85f)); +// crt_.set_visible_area(Outputs::Display::Rect(0.075f - 0.00877193f, 0.065f, 0.85f, 0.85f)); + crt_.set_visible_area(crt_.get_rect_for_area( + 311 - 257 - 4, + 208 + 8, + Begin40Columns - BeginSync - 8, + End40Columns + EndOfLine - Begin40Columns + 16, + 4.0f / 3.0f + )); } template diff --git a/Outputs/CRT/CRT.cpp b/Outputs/CRT/CRT.cpp index 16a001d6de..75f31fe269 100644 --- a/Outputs/CRT/CRT.cpp +++ b/Outputs/CRT/CRT.cpp @@ -8,10 +8,10 @@ #include "CRT.hpp" -#include -#include #include #include +#include +#include using namespace Outputs::CRT; @@ -458,40 +458,58 @@ void CRT::output_data(int number_of_cycles, size_t number_of_samples) { // MARK: - Getters. -Outputs::Display::Rect CRT::get_rect_for_area(int first_line_after_sync, int number_of_lines, int first_cycle_after_sync, int number_of_cycles, float aspect_ratio) const { +Outputs::Display::Rect CRT::get_rect_for_area( + int first_line_after_sync, + int number_of_lines, + int first_cycle_after_sync, + int number_of_cycles, + float aspect_ratio +) const { + assert(number_of_cycles > 0); + assert(number_of_lines > 0); + assert(first_line_after_sync >= 0); + assert(first_cycle_after_sync >= 0); + + // Scale up x coordinates and add a little extra leeway to y. first_cycle_after_sync *= time_multiplier_; number_of_cycles *= time_multiplier_; first_line_after_sync -= 2; number_of_lines += 4; - // determine prima facie x extent + // Determine prima facie x extent. const int horizontal_period = horizontal_flywheel_->get_standard_period(); const int horizontal_scan_period = horizontal_flywheel_->get_scan_period(); const int horizontal_retrace_period = horizontal_period - horizontal_scan_period; - // make sure that the requested range is visible - if(int(first_cycle_after_sync) < horizontal_retrace_period) first_cycle_after_sync = int(horizontal_retrace_period); - if(int(first_cycle_after_sync + number_of_cycles) > horizontal_scan_period) number_of_cycles = int(horizontal_scan_period - int(first_cycle_after_sync)); + // Ensure requested range is within visible region. + first_cycle_after_sync = std::max(horizontal_retrace_period, first_cycle_after_sync); + number_of_cycles = std::min(horizontal_period - first_cycle_after_sync, number_of_cycles); - float start_x = float(int(first_cycle_after_sync) - horizontal_retrace_period) / float(horizontal_scan_period); + float start_x = float(first_cycle_after_sync - horizontal_retrace_period) / float(horizontal_scan_period); float width = float(number_of_cycles) / float(horizontal_scan_period); - // determine prima facie y extent + // Determine prima facie y extent. const int vertical_period = vertical_flywheel_->get_standard_period(); const int vertical_scan_period = vertical_flywheel_->get_scan_period(); const int vertical_retrace_period = vertical_period - vertical_scan_period; - // make sure that the requested range is visible -// if(int(first_line_after_sync) * horizontal_period < vertical_retrace_period) -// first_line_after_sync = (vertical_retrace_period + horizontal_period - 1) / horizontal_period; -// if((first_line_after_sync + number_of_lines) * horizontal_period > vertical_scan_period) -// number_of_lines = int(horizontal_scan_period - int(first_cycle_after_sync)); - - float start_y = float((int(first_line_after_sync) * horizontal_period) - vertical_retrace_period) / float(vertical_scan_period); - float height = float(int(number_of_lines) * horizontal_period) / vertical_scan_period; - - // adjust to ensure aspect ratio is correct + // Ensure range is visible. + first_line_after_sync = std::max( + first_line_after_sync * horizontal_period, + vertical_retrace_period + ) / horizontal_period; + number_of_lines = std::min( + vertical_period - first_line_after_sync * horizontal_period, + number_of_lines * horizontal_period + ) / horizontal_period; + + float start_y = + float(first_line_after_sync * horizontal_period - vertical_retrace_period) / + float(vertical_scan_period); + float height = float(number_of_lines * horizontal_period) / vertical_scan_period; + + // Pick a zoom that includes the entire requested visible area given the aspect ratio constraints. const float adjusted_aspect_ratio = (3.0f*aspect_ratio / 4.0f); const float ideal_width = height * adjusted_aspect_ratio; if(ideal_width > width) { @@ -503,6 +521,8 @@ Outputs::Display::Rect CRT::get_rect_for_area(int first_line_after_sync, int num height = ideal_height; } + // TODO: apply absolute clipping constraints now. + return Outputs::Display::Rect(start_x, start_y, width, height); } From 096f48be331b96dac3885d26dd728e0bae8041c4 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 18 Dec 2024 21:48:03 -0500 Subject: [PATCH 042/103] Fix top line of cursor, add pretend cursor, page video separately. --- Machines/Commodore/Plus4/Plus4.cpp | 29 ++++++++++--- Machines/Commodore/Plus4/Video.hpp | 66 +++++++++++++++++++----------- 2 files changed, 64 insertions(+), 31 deletions(-) diff --git a/Machines/Commodore/Plus4/Plus4.cpp b/Machines/Commodore/Plus4/Plus4.cpp index 37915c62a5..7c3e5f70d4 100644 --- a/Machines/Commodore/Plus4/Plus4.cpp +++ b/Machines/Commodore/Plus4/Plus4.cpp @@ -114,7 +114,7 @@ class ConcreteMachine: m6502_(*this), interrupts_(*this), timers_(interrupts_), - video_(map_, interrupts_) + video_(video_map_, interrupts_) { set_clock_rate(PALclock); @@ -132,7 +132,9 @@ class ConcreteMachine: Memory::Fuzz(ram_); map_.page(ram_.data()); - page_rom(); + page_cpu_rom(); + + video_map_.page(ram_.data()); insert_media(target.media); } @@ -255,6 +257,12 @@ class ConcreteMachine: case 0xff12: ff12_ = *value & 0x3f; video_.write<0xff12>(*value); + + if((*value & 4)) { + page_video_rom(); + } else { + page_video_ram(); + } break; case 0xff13: ff13_ = *value & 0xfe; @@ -270,8 +278,8 @@ class ConcreteMachine: case 0xff18: video_.write<0xff18>(*value); break; case 0xff19: video_.write<0xff19>(*value); break; - case 0xff3e: page_rom(); break; - case 0xff3f: page_ram(); break; + case 0xff3e: page_cpu_rom(); break; + case 0xff3f: page_cpu_ram(); break; // TODO: audio is 0xff10, 0xff11, 0xff0e, 0xff0f and shares 0xff18. @@ -294,13 +302,21 @@ class ConcreteMachine: m6502_.set_ready_line(active); } - void page_rom() { + void page_video_rom() { + video_map_.page(basic_.data()); + video_map_.page(kernel_.data()); + } + void page_video_ram() { + video_map_.page(&ram_[0x8000]); + } + + void page_cpu_rom() { // TODO: allow other ROM selection. And no ROM? map_.page(basic_.data()); map_.page(kernel_.data()); rom_is_paged_ = true; } - void page_ram() { + void page_cpu_ram() { map_.page(&ram_[0x8000]); rom_is_paged_ = false; } @@ -323,6 +339,7 @@ class ConcreteMachine: } Commodore::Plus4::Pager map_; + Commodore::Plus4::Pager video_map_; std::array ram_; std::vector kernel_; std::vector basic_; diff --git a/Machines/Commodore/Plus4/Video.hpp b/Machines/Commodore/Plus4/Video.hpp index c075d984d0..90d156ae71 100644 --- a/Machines/Commodore/Plus4/Video.hpp +++ b/Machines/Commodore/Plus4/Video.hpp @@ -25,8 +25,6 @@ struct Video { pager_(pager), interrupts_(interrupts) { - // TODO: perfect crop. -// crt_.set_visible_area(Outputs::Display::Rect(0.075f - 0.00877193f, 0.065f, 0.85f, 0.85f)); crt_.set_visible_area(crt_.get_rect_for_area( 311 - 257 - 4, 208 + 8, @@ -87,7 +85,7 @@ struct Video { break; case 0xff12: -// bitmap_base_ = uint16_t((value & 0x3c) << 10); + bitmap_base_ = uint16_t((value & 0x3c) << 10); break; case 0xff13: character_generator_address_ = uint16_t((value & 0xfc) << 8); @@ -175,6 +173,7 @@ struct Video { // const auto attribute_fetch_start = [&] { character_address_ = 0; + fetch_phase_ = FetchPhase::Waiting; }; switch(vertical_counter_) { case 261: // End of screen NTSC. [and hence 0: Attribute fetch start]. @@ -239,19 +238,39 @@ struct Video { fetch_count_ += period; auto &line = lines_[line_pointer_]; -// uint8_t attributes[40]; -// uint8_t characters[40]; -// } lines_[2]; -// int line_pointer_ = 0; - for(int x = start; x < end; x++) { -// line.attributes[x] = pager_.read(character_generator_address_ + x * 8 + (row & 7)); -// line.characters[x] = pager_.read(uint16_t(line_character_address_ + x + screen_memory_address_ + 0x400)); + const auto offset = (line.characters[x] << 3) + (character_line_ & 7); + bitmap_[x] = pager_.read(uint16_t( + character_generator_address_ + + offset + )); } -// switch(fetch_phase_) { -// case FetchPh -// } + switch(fetch_phase_) { + default: break; + case FetchPhase::FetchingCharacters: { + auto &character_line = lines_[line_pointer_ ^ 1]; + for(int x = start; x < end; x++) { + character_line.characters[x] = pager_.read( + screen_memory_address_ + 0x400 + + uint16_t(character_address_ + x) + ); + + // TEST of cursor position. + if(character_address_ + x == cursor_address_) { + character_line.characters[x] = ']'; + } + } + } break; + case FetchPhase::FetchingAttributes: + for(int x = start; x < end; x++) { + line.attributes[x] = pager_.read( + screen_memory_address_ + + uint16_t(character_address_ + x) + ); + } + break; + } } @@ -284,16 +303,11 @@ struct Video { } // Output pixels. - // TODO: properly. THIS HACKS IN TEXT OUTPUT. IT IS NOT CORRECT. NOT AS TO TIMING, NOT AS TO CONTENT. + // TODO: properly. Accounting for mode, attributes, etc. if(pixels_) { for(int c = 0; c < period; c++) { const auto pixel = time_in_state_ + c; - const auto row = vertical_counter_ - 4; - - const auto index = (row >> 3) * 40 + (pixel >> 3); - const uint8_t character = pager_.read(uint16_t(index + screen_memory_address_ + 0x400)); - const uint8_t glyph = pager_.read(character_generator_address_ + character * 8 + (row & 7)); - + const uint8_t glyph = bitmap_[pixel >> 3]; pixels_[c] = glyph & (0x80 >> (pixel & 7)) ? 0xff00 : 0xffff; } pixels_ += period; @@ -314,21 +328,24 @@ struct Video { case BeginExternalFetchWindow: external_fetch_ = true; + ++character_line_; + + // At this point fetch_phase_ is whichever phase is **ending**. switch(fetch_phase_) { case FetchPhase::Waiting: - ++character_line_; - // TODO: the < 200 is obviously phoney baloney. Figure out what the actual condition is here. - if(vertical_counter_ < 200 && (vertical_counter_&7) == y_scroll_ && vertical_window_) { + if(vertical_counter_ < 200 && (vertical_counter_&7) == y_scroll_) { fetch_phase_ = FetchPhase::FetchingCharacters; } break; case FetchPhase::FetchingCharacters: fetch_phase_ = FetchPhase::FetchingAttributes; character_line_ = 0; + line_pointer_ ^= 1; break; case FetchPhase::FetchingAttributes: fetch_phase_ = FetchPhase::Waiting; + character_address_ += 40; break; } interrupts_.bus().set_ready_line(fetch_phase_ != FetchPhase::Waiting); @@ -337,7 +354,6 @@ struct Video { break; case LatchCharacterPosition: - line_character_address_ = character_address_; break; case EndCharacterFetchWindow: @@ -426,6 +442,7 @@ struct Video { uint16_t character_row_address_ = 0; uint16_t character_generator_address_ = 0; uint16_t screen_memory_address_ = 0; + uint16_t bitmap_base_ = 0; int raster_interrupt_ = 0x1ff; @@ -443,7 +460,6 @@ struct Video { uint8_t ff06_; uint16_t character_address_ = 0; - uint16_t line_character_address_ = 0; bool fetch_characters_ = false; bool external_fetch_ = false; bool output_pixels_ = false; From 4f93dc0adfe4b03337be3d72116a4c08a646755d Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 18 Dec 2024 22:02:05 -0500 Subject: [PATCH 043/103] Support attribute bytes. --- Machines/Commodore/Plus4/Video.hpp | 49 +++++++++++++++++------------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/Machines/Commodore/Plus4/Video.hpp b/Machines/Commodore/Plus4/Video.hpp index 90d156ae71..9fff1e6b44 100644 --- a/Machines/Commodore/Plus4/Video.hpp +++ b/Machines/Commodore/Plus4/Video.hpp @@ -107,16 +107,10 @@ struct Video { case 0xff1a: load_high10(character_row_address_); break; case 0xff1b: load_low8(character_row_address_); break; - case 0xff15: case 0xff16: case 0xff17: case 0xff18: case 0xff19: { + case 0xff15: case 0xff16: case 0xff17: case 0xff18: case 0xff19: raw_background_[size_t(address - 0xff15)] = value; - - const uint8_t luminance = (value & 0x0f) ? uint8_t( - ((value & 0x70) << 1) | ((value & 0x70) >> 2) | ((value & 0x70) >> 5) - ) : 0; - background_[size_t(address - 0xff15)] = uint16_t( - luminance | (chrominances[value & 0x0f] << 8) - ); - } break; + background_[size_t(address - 0xff15)] = colour(value); + break; } } @@ -257,7 +251,7 @@ struct Video { ); // TEST of cursor position. - if(character_address_ + x == cursor_address_) { + if(character_address_ + x == cursor_address_ && (blink_ & 32)) { character_line.characters[x] = ']'; } } @@ -308,7 +302,9 @@ struct Video { for(int c = 0; c < period; c++) { const auto pixel = time_in_state_ + c; const uint8_t glyph = bitmap_[pixel >> 3]; - pixels_[c] = glyph & (0x80 >> (pixel & 7)) ? 0xff00 : 0xffff; + pixels_[c] = glyph & (0x80 >> (pixel & 7)) ? + colour(lines_[line_pointer_].attributes[pixel >> 3]) : + background_[0]; } pixels_ += period; } @@ -376,6 +372,7 @@ struct Video { break; case IncrementBlink: + blink_ += vertical_counter_ == 0; break; case IncrementVerticalLine: @@ -457,7 +454,8 @@ struct Video { bool horizontal_blank_ = false; bool horizontal_sync_ = false; bool horizontal_burst_ = false; - uint8_t ff06_; + uint8_t ff06_ = 0; + int blink_ = 0; uint16_t character_address_ = 0; bool fetch_characters_ = false; @@ -465,7 +463,7 @@ struct Video { bool output_pixels_ = false; bool refresh_ = false; bool single_clock_ = false; - uint8_t ff07_; + uint8_t ff07_ = 0; enum class OutputState { Blank, @@ -483,14 +481,23 @@ struct Video { const Commodore::Plus4::Pager &pager_; Interrupts &interrupts_; - // The following aren't accurate; they're eyeballed to be close enough for now in PAL. - static constexpr uint8_t chrominances[] = { - 0xff, 0xff, - 90, 23, 105, 59, - 14, 69, 83, 78, - 50, 96, 32, 9, - 5, 41, - }; + uint16_t colour(uint8_t value) const { + // The following aren't accurate; they're eyeballed to be close enough for now in PAL. + static constexpr uint8_t chrominances[] = { + 0xff, 0xff, + 90, 23, 105, 59, + 14, 69, 83, 78, + 50, 96, 32, 9, + 5, 41, + }; + + const uint8_t luminance = (value & 0x0f) ? uint8_t( + ((value & 0x70) << 1) | ((value & 0x70) >> 2) | ((value & 0x70) >> 5) + ) : 0; + return uint16_t( + luminance | (chrominances[value & 0x0f] << 8) + ); + } enum class FetchPhase { Waiting, From b08cc9cb49347cf5d78f08b6425c1896f72c4d75 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 18 Dec 2024 22:07:37 -0500 Subject: [PATCH 044/103] Use attributes, attempt real cursor. --- Machines/Commodore/Plus4/Video.hpp | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/Machines/Commodore/Plus4/Video.hpp b/Machines/Commodore/Plus4/Video.hpp index 9fff1e6b44..e63f23d937 100644 --- a/Machines/Commodore/Plus4/Video.hpp +++ b/Machines/Commodore/Plus4/Video.hpp @@ -249,19 +249,15 @@ struct Video { screen_memory_address_ + 0x400 + uint16_t(character_address_ + x) ); - - // TEST of cursor position. - if(character_address_ + x == cursor_address_ && (blink_ & 32)) { - character_line.characters[x] = ']'; - } } } break; case FetchPhase::FetchingAttributes: for(int x = start; x < end; x++) { - line.attributes[x] = pager_.read( + line.attributes[x].colour = colour(pager_.read( screen_memory_address_ + uint16_t(character_address_ + x) - ); + )); + line.attributes[x].cursor_mask = character_address_ + x == cursor_address_ && (blink_ & 32) ? 0xff : 0x00; } break; } @@ -301,9 +297,11 @@ struct Video { if(pixels_) { for(int c = 0; c < period; c++) { const auto pixel = time_in_state_ + c; - const uint8_t glyph = bitmap_[pixel >> 3]; + const auto column = pixel >> 3; + const uint8_t glyph = bitmap_[column] ^ lines_[line_pointer_].attributes[column].cursor_mask; + pixels_[c] = glyph & (0x80 >> (pixel & 7)) ? - colour(lines_[line_pointer_].attributes[pixel >> 3]) : + lines_[line_pointer_].attributes[pixel >> 3].colour : background_[0]; } pixels_ += period; @@ -508,7 +506,10 @@ struct Video { int fetch_count_ = 0; struct LineBuffer { - uint8_t attributes[40]; + struct Attribute { + uint16_t colour; + uint8_t cursor_mask; + } attributes[40]; uint8_t characters[40]; } lines_[2]; uint8_t bitmap_[40]; From c8ad8c79bd8ec5697641125fb339167a648d4e66 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 18 Dec 2024 22:14:57 -0500 Subject: [PATCH 045/103] Factor in x_scroll_. --- Machines/Commodore/Plus4/Video.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Machines/Commodore/Plus4/Video.hpp b/Machines/Commodore/Plus4/Video.hpp index e63f23d937..a1f5b083a5 100644 --- a/Machines/Commodore/Plus4/Video.hpp +++ b/Machines/Commodore/Plus4/Video.hpp @@ -295,11 +295,11 @@ struct Video { // Output pixels. // TODO: properly. Accounting for mode, attributes, etc. if(pixels_) { + const auto first_pixel = ((horizontal_counter_ + EndOfLine - Begin40Columns) - x_scroll_) % EndOfLine; for(int c = 0; c < period; c++) { - const auto pixel = time_in_state_ + c; + const auto pixel = first_pixel + c; const auto column = pixel >> 3; const uint8_t glyph = bitmap_[column] ^ lines_[line_pointer_].attributes[column].cursor_mask; - pixels_[c] = glyph & (0x80 >> (pixel & 7)) ? lines_[line_pointer_].attributes[pixel >> 3].colour : background_[0]; From e6523f3ec1ceea12019a28b234dc0eefe06e4799 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 20 Dec 2024 09:17:40 -0500 Subject: [PATCH 046/103] Shuffle colour conversion moment; move ownership of clock rate. --- Machines/Commodore/Plus4/Plus4.cpp | 5 +---- Machines/Commodore/Plus4/Video.hpp | 33 ++++++++++++++++++++---------- 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/Machines/Commodore/Plus4/Plus4.cpp b/Machines/Commodore/Plus4/Plus4.cpp index 7c3e5f70d4..b4639de512 100644 --- a/Machines/Commodore/Plus4/Plus4.cpp +++ b/Machines/Commodore/Plus4/Plus4.cpp @@ -98,9 +98,6 @@ class Timers { Interrupts &interrupts_; }; -static constexpr auto NTSCclock = 14'318'180; // i.e. colour subcarrier * 4. -static constexpr auto PALclock = 17'734'448; // i.e. very close to colour subcarrier * 4 — only about 0.1% off. - class ConcreteMachine: public BusController, public CPU::MOS6502::BusHandler, @@ -116,7 +113,7 @@ class ConcreteMachine: timers_(interrupts_), video_(video_map_, interrupts_) { - set_clock_rate(PALclock); + set_clock_rate(clock_rate(false)); const auto kernel = ROM::Name::Plus4KernelPALv5; const auto basic = ROM::Name::Plus4BASIC; diff --git a/Machines/Commodore/Plus4/Video.hpp b/Machines/Commodore/Plus4/Video.hpp index a1f5b083a5..b63a0dd722 100644 --- a/Machines/Commodore/Plus4/Video.hpp +++ b/Machines/Commodore/Plus4/Video.hpp @@ -18,6 +18,12 @@ namespace Commodore::Plus4 { +constexpr double clock_rate(bool is_ntsc) { + return is_ntsc ? + 14'318'180 : // i.e. colour subcarrier * 4. + 17'734'448; // i.e. very close to colour subcarrier * 4 — only about 0.1% off. +} + struct Video { public: Video(const Commodore::Plus4::Pager &pager, Interrupts &interrupts) : @@ -253,10 +259,10 @@ struct Video { } break; case FetchPhase::FetchingAttributes: for(int x = start; x < end; x++) { - line.attributes[x].colour = colour(pager_.read( + line.attributes[x].colour = pager_.read( screen_memory_address_ + uint16_t(character_address_ + x) - )); + ); line.attributes[x].cursor_mask = character_address_ + x == cursor_address_ && (blink_ & 32) ? 0xff : 0x00; } break; @@ -296,12 +302,13 @@ struct Video { // TODO: properly. Accounting for mode, attributes, etc. if(pixels_) { const auto first_pixel = ((horizontal_counter_ + EndOfLine - Begin40Columns) - x_scroll_) % EndOfLine; - for(int c = 0; c < period; c++) { - const auto pixel = first_pixel + c; + const auto last_pixel = ((horizontal_counter_ + EndOfLine - Begin40Columns) - x_scroll_ + period) % EndOfLine; + + for(int pixel = first_pixel; pixel < last_pixel; pixel++) { const auto column = pixel >> 3; const uint8_t glyph = bitmap_[column] ^ lines_[line_pointer_].attributes[column].cursor_mask; - pixels_[c] = glyph & (0x80 >> (pixel & 7)) ? - lines_[line_pointer_].attributes[pixel >> 3].colour : + pixels_[pixel] = glyph & (0x80 >> (pixel & 7)) ? + colour(lines_[line_pointer_].attributes[pixel >> 3].colour) : background_[0]; } pixels_ += period; @@ -479,7 +486,7 @@ struct Video { const Commodore::Plus4::Pager &pager_; Interrupts &interrupts_; - uint16_t colour(uint8_t value) const { + uint16_t colour(uint8_t chrominance, uint8_t luminance) const { // The following aren't accurate; they're eyeballed to be close enough for now in PAL. static constexpr uint8_t chrominances[] = { 0xff, 0xff, @@ -489,14 +496,18 @@ struct Video { 5, 41, }; - const uint8_t luminance = (value & 0x0f) ? uint8_t( - ((value & 0x70) << 1) | ((value & 0x70) >> 2) | ((value & 0x70) >> 5) + luminance = chrominance ? uint8_t( + (luminance << 5) | (luminance << 2) | (luminance >> 1) ) : 0; return uint16_t( - luminance | (chrominances[value & 0x0f] << 8) + luminance | (chrominances[chrominance] << 8) ); } + uint16_t colour(uint8_t value) const { + return colour(value & 0x0f, (value >> 4) & 7); + } + enum class FetchPhase { Waiting, FetchingCharacters, @@ -507,7 +518,7 @@ struct Video { struct LineBuffer { struct Attribute { - uint16_t colour; + uint8_t colour; uint8_t cursor_mask; } attributes[40]; uint8_t characters[40]; From 4bb53ed6ba15af8aaeeb179eae3a38c9afd061d9 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 26 Dec 2024 22:33:09 -0500 Subject: [PATCH 047/103] Start trending towards an FPGA-inspired implementation. --- Machines/Commodore/Plus4/Video.hpp | 659 ++++++++++++++++++----------- 1 file changed, 411 insertions(+), 248 deletions(-) diff --git a/Machines/Commodore/Plus4/Video.hpp b/Machines/Commodore/Plus4/Video.hpp index b63a0dd722..3a76550660 100644 --- a/Machines/Commodore/Plus4/Video.hpp +++ b/Machines/Commodore/Plus4/Video.hpp @@ -18,6 +18,14 @@ namespace Commodore::Plus4 { +namespace { + +void tprintf([[maybe_unused]] const char *str) { +// printf("%s", str); +} + +} + constexpr double clock_rate(bool is_ntsc) { return is_ntsc ? 14'318'180 : // i.e. colour subcarrier * 4. @@ -34,8 +42,8 @@ struct Video { crt_.set_visible_area(crt_.get_rect_for_area( 311 - 257 - 4, 208 + 8, - Begin40Columns - BeginSync - 8, - End40Columns + EndOfLine - Begin40Columns + 16, + int(HorizontalEvent::Begin40Columns) - int(HorizontalEvent::BeginSync) + int(HorizontalEvent::ScheduleCounterReset) + 1 - 8, + int(HorizontalEvent::End40Columns) - int(HorizontalEvent::Begin40Columns) + 16, 4.0f / 3.0f )); } @@ -48,7 +56,7 @@ struct Video { case 0xff0b: return uint8_t(raster_interrupt_); case 0xff1c: return uint8_t(vertical_counter_ >> 8); case 0xff1d: return uint8_t(vertical_counter_); - case 0xff14: return uint8_t((screen_memory_address_ >> 8) & 0xf8); + case 0xff14: return uint8_t((video_matrix_base_ >> 8) & 0xf8); case 0xff15: case 0xff16: case 0xff17: case 0xff18: case 0xff19: return raw_background_[size_t(address - 0xff15)]; @@ -94,11 +102,11 @@ struct Video { bitmap_base_ = uint16_t((value & 0x3c) << 10); break; case 0xff13: - character_generator_address_ = uint16_t((value & 0xfc) << 8); + character_base_ = uint16_t((value & 0xfc) << 8); single_clock_ = value & 0x02; break; case 0xff14: - screen_memory_address_ = uint16_t((value & 0xf8) << 8); + video_matrix_base_ = uint16_t((value & 0xf8) << 8); break; case 0xff0a: @@ -108,10 +116,10 @@ struct Video { raster_interrupt_ = (raster_interrupt_ & 0xff00) | value; break; - case 0xff0c: load_high10(cursor_address_); break; - case 0xff0d: load_low8(cursor_address_); break; - case 0xff1a: load_high10(character_row_address_); break; - case 0xff1b: load_low8(character_row_address_); break; + case 0xff0c: load_high10(cursor_position_); break; + case 0xff0d: load_low8(cursor_position_); break; + case 0xff1a: load_high10(character_position_reload_); break; + case 0xff1b: load_low8(character_position_reload_); break; case 0xff15: case 0xff16: case 0xff17: case 0xff18: case 0xff19: raw_background_[size_t(address - 0xff15)] = value; @@ -162,113 +170,65 @@ struct Video { // // That gives close enough to 456 pixel clocks per line in both systems so the TED just rolls with that. - // See page 34 of plus4_tech.pdf for event times. - subcycles_ += cycles * 2; auto ticks_remaining = subcycles_.divide(is_ntsc_ ? Cycles(4) : Cycles(5)).as(); while(ticks_remaining) { - // - // Test vertical first; this will catch both any programmed change that has occurred outside - // of the loop and any change to the vertical counter that occurs during the horizontal runs. - // - const auto attribute_fetch_start = [&] { - character_address_ = 0; - fetch_phase_ = FetchPhase::Waiting; - }; - switch(vertical_counter_) { - case 261: // End of screen NTSC. [and hence 0: Attribute fetch start]. - if(is_ntsc_) { - vertical_counter_ = 0; - attribute_fetch_start(); - } - break; - - case 311: // End of screen PAL. [and hence 0: Attribute fetch start]. - if(!is_ntsc_) { - vertical_counter_ = 0; - attribute_fetch_start(); - } - break; - - case 203: // Attribute fetch end. But I think this might be fairly nominal, assuming attribute fetches - // are triggered by testing against y scroll. - break; - - case 4: if(rows_25_) vertical_window_ = true; break; // Vertical screen window start (25 lines). - case 204: if(rows_25_) vertical_window_ = false; break; // Vertical screen window stop (25 lines). - case 8: if(!rows_25_) vertical_window_ = true; break; // Vertical screen window start (24 lines). - case 200: if(!rows_25_) vertical_window_ = false; break; // Vertical screen window stop (24 lines). - - case 226: if(is_ntsc_) vertical_blank_ = true; break; // NTSC vertical blank start. - case 229: if(is_ntsc_) vertical_sync_ = true; break; // NTSC vsync start. - case 232: if(is_ntsc_) vertical_sync_ = false; break; // NTSC vsync end. - case 244: if(is_ntsc_) vertical_blank_ = false; break; // NTSC vertical blank end. - - case 251: if(!is_ntsc_) vertical_blank_ = true; break; // PAL vertical blank start. - case 254: if(!is_ntsc_) vertical_sync_ = true; break; // PAL vsync start. - case 257: if(!is_ntsc_) vertical_sync_ = false; break; // PAL vsync end. - case 269: if(!is_ntsc_) vertical_blank_ = false; break; // PAL vertical blank end. - } + // Test for raster interrupt. if(raster_interrupt_ == vertical_counter_) { - interrupts_.apply(Interrupts::Flag::Raster); + if(!raster_interrupt_done_) { + raster_interrupt_done_ = true; + interrupts_.apply(Interrupts::Flag::Raster); + } + } else { + raster_interrupt_done_ = false; } - const auto next = Numeric::upper_bound< - 0, Begin38Columns, - EndExternalFetchWindow, LatchCharacterPosition, - EndCharacterFetchWindow, EndVideoShiftRegister, - End38Columns, End40Columns, - EndRefresh, IncrementBlink, - BeginBlank, BeginSync, - TestRasterInterrupt, IncrementVerticalLine, - BeginBurst, EndSync, - BeginExternalFetchWindow, //EndBurst, - EndBlank, IncrementCharacterPositionReload, - BeginCharacterFetchWindow, BeginVideoShiftRegister, - Begin40Columns, EndOfLine - >(horizontal_counter_); - const auto period = std::min(next - horizontal_counter_, ticks_remaining); - // - // Fetch as appropriate. + // Compute time to run for in this step based upon: + // (i) timer-linked events; + // (ii) deferred events; and + // (iii) ticks remaining. // - if(fetch_characters_) { - const int start = fetch_count_ >> 3; - const int end = (fetch_count_ + period) >> 3; - fetch_count_ += period; - - auto &line = lines_[line_pointer_]; - for(int x = start; x < end; x++) { - const auto offset = (line.characters[x] << 3) + (character_line_ & 7); - bitmap_[x] = pager_.read(uint16_t( - character_generator_address_ + - offset - )); + const auto next = Numeric::upper_bound< + int(HorizontalEvent::Begin40Columns), int(HorizontalEvent::Begin38Columns), + int(HorizontalEvent::LatchCharacterPosition), int(HorizontalEvent::DMAWindowEnd), + int(HorizontalEvent::EndExternalFetchWindow), int(HorizontalEvent::EndCharacterFetchWindow), + int(HorizontalEvent::End38Columns), int(HorizontalEvent::End40Columns), + int(HorizontalEvent::EndRefresh), int(HorizontalEvent::IncrementFlashCounter), + int(HorizontalEvent::BeginBlank), int(HorizontalEvent::BeginSync), + int(HorizontalEvent::VerticalSubActive), int(HorizontalEvent::EndOfScreen), + int(HorizontalEvent::EndSync), int(HorizontalEvent::IncrementVerticalSub), + int(HorizontalEvent::BeginExternalFetchClock), int(HorizontalEvent::BeginAttributeFetch), + int(HorizontalEvent::EndBlank), int(HorizontalEvent::IncrementVideoCounter), + int(HorizontalEvent::BeginShiftRegister), int(HorizontalEvent::ScheduleCounterReset), + int(HorizontalEvent::CounterOverflow) + >(horizontal_counter_); + const auto period = [&] { + auto period = std::min(next - horizontal_counter_, ticks_remaining); + if(delayed_events_) { + period = std::min(period, __builtin_ctzll(delayed_events_) / DelayEventSize); + // TODO: switch to std::countr_zero upon adoption of C++20. } + return period; + }(); - switch(fetch_phase_) { - default: break; - case FetchPhase::FetchingCharacters: { - auto &character_line = lines_[line_pointer_ ^ 1]; - for(int x = start; x < end; x++) { - character_line.characters[x] = pager_.read( - screen_memory_address_ + 0x400 + - uint16_t(character_address_ + x) - ); - } - } break; - case FetchPhase::FetchingAttributes: - for(int x = start; x < end; x++) { - line.attributes[x].colour = pager_.read( - screen_memory_address_ + - uint16_t(character_address_ + x) - ); - line.attributes[x].cursor_mask = character_address_ + x == cursor_address_ && (blink_ & 32) ? 0xff : 0x00; - } - break; - } + // Update vertical state. + if(rows_25_) { + if(video_line_ == 4) vertical_screen_ = true; + else if(video_line_ == 204) vertical_screen_ = false; + } else { + if(video_line_ == 8) vertical_screen_ = true; + else if(video_line_ == 200) vertical_screen_ = false; } + character_fetch_ |= bad_line2_; + if(video_line_ == vblank_start()) vertical_blank_ = true; + else if(video_line_ == vblank_stop()) vertical_blank_ = false; + else if(video_line_ == 0 && display_enable_) enable_display_ = true; + else if(video_line_ == 204) { + enable_display_ = false; + character_fetch_ = false; + } // // Output. @@ -277,12 +237,15 @@ struct Video { if(vertical_sync_ || horizontal_sync_) { state = OutputState::Sync; } else if(vertical_blank_ || horizontal_blank_) { - state = horizontal_burst_ ? OutputState::Burst : OutputState::Blank; +// state = horizontal_burst_ ? OutputState::Burst : OutputState::Blank; + state = OutputState::Blank; } else { - state = vertical_window_ && output_pixels_ ? OutputState::Pixels : OutputState::Border; + const bool pixel_screen = columns_40_ ? wide_screen_ : narrow_screen_; + state = enable_display_ && pixel_screen ? OutputState::Pixels : OutputState::Border; } - if(state != output_state_) { + static constexpr auto PixelAllocationSize = 320; + if(state != output_state_ || (state == OutputState::Pixels && time_in_state_ == PixelAllocationSize)) { switch(output_state_) { case OutputState::Blank: crt_.output_blank(time_in_state_); break; case OutputState::Sync: crt_.output_sync(time_in_state_); break; @@ -294,123 +257,244 @@ struct Video { output_state_ = state; if(output_state_ == OutputState::Pixels) { - pixels_ = reinterpret_cast(crt_.begin_data(384, 2)); + pixels_ = reinterpret_cast(crt_.begin_data(PixelAllocationSize)); } } - // Output pixels. - // TODO: properly. Accounting for mode, attributes, etc. - if(pixels_) { - const auto first_pixel = ((horizontal_counter_ + EndOfLine - Begin40Columns) - x_scroll_) % EndOfLine; - const auto last_pixel = ((horizontal_counter_ + EndOfLine - Begin40Columns) - x_scroll_ + period) % EndOfLine; - - for(int pixel = first_pixel; pixel < last_pixel; pixel++) { - const auto column = pixel >> 3; - const uint8_t glyph = bitmap_[column] ^ lines_[line_pointer_].attributes[column].cursor_mask; - pixels_[pixel] = glyph & (0x80 >> (pixel & 7)) ? - colour(lines_[line_pointer_].attributes[pixel >> 3].colour) : - background_[0]; + // Get count of 'single_cycle_end's in FPGATED parlance. + const int start_window = horizontal_counter_ >> 3; + const int end_window = (horizontal_counter_ + period) >> 3; + const int window_count = end_window - start_window; + + // Advance DMA state machine. + for(int cycle = 0; cycle < window_count; cycle++) { + const auto is_active = [&] { return dma_window_ && (bad_line2_ || bad_line()); }; + switch(dma_state_) { + case DMAState::IDLE: + if(is_active()) { + dma_state_ = DMAState::THALT1; + } + break; + case DMAState::THALT1: + case DMAState::THALT2: + case DMAState::THALT3: + if(is_active()) { + dma_state_ = DMAState(int(dma_state_) + 1); + } else { + dma_state_ = DMAState::IDLE; + } + break; + case DMAState::TDMA: + if(!is_active()) { + dma_state_ = DMAState::IDLE; + } + break; + } + if(increment_character_position_) { + ++character_position_; + } + if(video_shift_) { + waiting_character_ = current_character_; + current_character_ = next_character_; + + waiting_attribute_ = current_attribute_; + current_attribute_ = next_attribute_; + + tprintf("s"); + } + if(increment_video_counter_) { + ++video_counter_; + shifter_.advance(); + next_attribute_ = shifter_.read<1>(); + next_character_ = shifter_.read<0>(); + tprintf("+"); + } + + if(dma_state_ == DMAState::TDMA) { + const uint16_t address = video_matrix_base_ + video_counter_; + if(bad_line2_) { + shifter_.write<1>(pager_.read(address)); + tprintf("c"); + } else { + shifter_.write<0>(pager_.read(address + 0x400)); + tprintf("a"); + } + } + + if(increment_character_position_ && character_fetch_) { + tprintf("p"); } - pixels_ += period; + if(wide_screen_ || narrow_screen_) { + tprintf("."); + } + + if(state == OutputState::Pixels && pixels_) { + const auto pixel_address = shifter_.read<0>(); + const auto pixels = + pager_.read(uint16_t(character_base_ + (pixel_address * 8) + vertical_sub_count_)); + + const auto attribute = shifter_.read<1>(); + const uint16_t colours[] = { background_[0], colour(attribute) }; + + pixels_[0] = (pixels & 0x80) ? colours[1] : colours[0]; + pixels_[1] = (pixels & 0x40) ? colours[1] : colours[0]; + pixels_[2] = (pixels & 0x20) ? colours[1] : colours[0]; + pixels_[3] = (pixels & 0x10) ? colours[1] : colours[0]; + pixels_[4] = (pixels & 0x08) ? colours[1] : colours[0]; + pixels_[5] = (pixels & 0x04) ? colours[1] : colours[0]; + pixels_[6] = (pixels & 0x02) ? colours[1] : colours[0]; + pixels_[7] = (pixels & 0x01) ? colours[1] : colours[0]; + + pixels_ += 8; + } + + tprintf("|"); } time_in_state_ += period; // - // Advance for current period. + // Advance for current period, then check for... // horizontal_counter_ += period; - ticks_remaining -= period; - switch(horizontal_counter_) { - case EndExternalFetchWindow: - // TODO: release RDY if it was held. - // TODO: increment character position end. - refresh_ = true; - break; - case BeginExternalFetchWindow: - external_fetch_ = true; - ++character_line_; - - // At this point fetch_phase_ is whichever phase is **ending**. - switch(fetch_phase_) { - case FetchPhase::Waiting: - // TODO: the < 200 is obviously phoney baloney. Figure out what the actual condition is here. - if(vertical_counter_ < 200 && (vertical_counter_&7) == y_scroll_) { - fetch_phase_ = FetchPhase::FetchingCharacters; - } - break; - case FetchPhase::FetchingCharacters: - fetch_phase_ = FetchPhase::FetchingAttributes; - character_line_ = 0; - line_pointer_ ^= 1; - break; - case FetchPhase::FetchingAttributes: - fetch_phase_ = FetchPhase::Waiting; - character_address_ += 40; - break; + // ... deferred events, and... + if(delayed_events_) { + delayed_events_ >>= period * DelayEventSize; + + if(delayed_events_ & uint64_t(DelayedEvent::Latch)) { + if(char_pos_latch_ && vertical_sub_active_) { + character_position_reload_ = character_position_; } - interrupts_.bus().set_ready_line(fetch_phase_ != FetchPhase::Waiting); + char_pos_latch_ = vertical_sub_count_ == 6; + if(char_pos_latch_ && enable_display_) { + video_counter_reload_ = video_counter_; + } + } - horizontal_burst_ = false; - break; + if(delayed_events_ & uint64_t(DelayedEvent::Flash)) { + if(vertical_counter_ == 205) { + ++flash_count_; + } + } - case LatchCharacterPosition: - break; + if(delayed_events_ & uint64_t(DelayedEvent::IncrementVerticalLine)) { + vertical_counter_ = next_vertical_counter_; + bad_line2_ = bad_line(); + } - case EndCharacterFetchWindow: - fetch_characters_ = false; - external_fetch_ = false; - interrupts_.bus().set_ready_line(false); + if(delayed_events_ & uint64_t(DelayedEvent::IncrementVerticalSub)) { + if(!video_line_) { + vertical_sub_count_ = 7; // TODO: should be between cycle 0xc8 and 0xca? + } else if(display_enable_ && vertical_sub_active_) { + vertical_sub_count_ = (vertical_sub_count_ + 1) & 7; + } + } + + if(delayed_events_ & uint64_t(DelayedEvent::CounterReset)) { + horizontal_counter_ = 0; + } + + delayed_events_ &= ~uint64_t(DelayedEvent::Mask); + } + + // ... timer-linked events. + switch(HorizontalEvent(horizontal_counter_)) { + case HorizontalEvent::CounterOverflow: + horizontal_counter_ = 0; + [[fallthrough]]; + case HorizontalEvent::Begin40Columns: + if(vertical_screen_ && enable_display_) wide_screen_ = true; break; - case BeginCharacterFetchWindow: - fetch_characters_ = true; - fetch_count_ = 0; + case HorizontalEvent::End40Columns: + if(vertical_screen_ && enable_display_) wide_screen_ = false; + break; + case HorizontalEvent::Begin38Columns: + if(vertical_screen_ && enable_display_) narrow_screen_ = true; + break; + case HorizontalEvent::End38Columns: + if(vertical_screen_ && enable_display_) narrow_screen_ = false; + video_shift_ = false; + break; + case HorizontalEvent::DMAWindowEnd: dma_window_ = false; break; + case HorizontalEvent::EndRefresh: refresh_ = false; break; + case HorizontalEvent::EndCharacterFetchWindow: character_window_ = false; break; + case HorizontalEvent::BeginBlank: horizontal_blank_ = true; break; + case HorizontalEvent::BeginSync: horizontal_sync_ = true; break; + case HorizontalEvent::EndSync: horizontal_sync_ = false; break; + + case HorizontalEvent::LatchCharacterPosition: schedule<8>(DelayedEvent::Latch); break; + case HorizontalEvent::IncrementFlashCounter: schedule<4>(DelayedEvent::Flash); break; + case HorizontalEvent::EndOfScreen: + schedule<8>(DelayedEvent::IncrementVerticalLine); + next_vertical_counter_ = video_line_ == eos() ? 0 : ((vertical_counter_ + 1) & 511); break; - case Begin38Columns: if(!columns_40_) output_pixels_ = true; break; - case End38Columns: if(!columns_40_) output_pixels_ = false; break; - case Begin40Columns: if(columns_40_) output_pixels_ = true; break; - case End40Columns: if(columns_40_) output_pixels_ = false; break; - - case EndRefresh: - refresh_ = false; + case HorizontalEvent::EndExternalFetchWindow: + external_fetch_ = false; + increment_character_position_ = false; + if(enable_display_) increment_video_counter_ = false; + refresh_ = true; break; - case IncrementBlink: - blink_ += vertical_counter_ == 0; + case HorizontalEvent::VerticalSubActive: + if(bad_line()) { + vertical_sub_active_ = true; + } else if(!enable_display_) { + vertical_sub_active_ = false; + } break; - case IncrementVerticalLine: - vertical_counter_ = (vertical_counter_ + 1) & 0x1ff; + case HorizontalEvent::IncrementVerticalSub: + schedule<8>(DelayedEvent::IncrementVerticalSub); + video_line_ = vertical_counter_; + character_position_ = 0; + + if(video_line_ == eos()) { + character_position_reload_ = 0; + video_counter_reload_ = 0; + } break; - case BeginBurst: - horizontal_burst_ = true; - // TODO: rest. + case HorizontalEvent::ScheduleCounterReset: + schedule<1>(DelayedEvent::CounterReset); break; - case BeginBlank: horizontal_blank_ = true; break; - case BeginSync: horizontal_sync_ = true; break; - case EndSync: horizontal_sync_ = false; break; -// case EndBurst: horizontal_burst_ = false; break; - case EndBlank: horizontal_blank_ = false; break; + case HorizontalEvent::BeginExternalFetchClock: + external_fetch_ = true; - case TestRasterInterrupt: - if(raster_interrupt_ == vertical_counter_) { - interrupts_.apply(Interrupts::Flag::Raster); + if(video_line_ == vs_start()) { + vertical_sync_ = true; + } else if(video_line_ == vs_stop()) { + vertical_sync_ = false; } break; - case IncrementCharacterPositionReload: + case HorizontalEvent::BeginAttributeFetch: + dma_window_ = true; break; - case BeginVideoShiftRegister: + case HorizontalEvent::EndBlank: + horizontal_blank_ = false; break; - case EndOfLine: - horizontal_counter_ = 0; + case HorizontalEvent::IncrementVideoCounter: + increment_character_position_ = true; + if(enable_display_) increment_video_counter_ = true; + + if(enable_display_ && vertical_sub_active_) { + character_position_ = character_position_reload_; + } + video_counter_ = video_counter_reload_; + break; + + case HorizontalEvent::BeginShiftRegister: + character_window_ = video_shift_ = enable_display_; + next_pixels_ = 0; break; } + + ticks_remaining -= period; + if(!horizontal_counter_) tprintf("\n"); } } @@ -440,35 +524,79 @@ struct Video { bool columns_40_ = false; int x_scroll_ = 0; - uint16_t cursor_address_ = 0; - uint16_t character_row_address_ = 0; - uint16_t character_generator_address_ = 0; - uint16_t screen_memory_address_ = 0; + uint16_t cursor_position_ = 0; + uint16_t character_base_ = 0; + uint16_t video_matrix_base_ = 0; uint16_t bitmap_base_ = 0; int raster_interrupt_ = 0x1ff; + bool raster_interrupt_done_ = false; + bool single_clock_ = false; + + // FF06 and FF07 are easier to return if read by just keeping undecoded copies of, not reconstituting. + uint8_t ff06_ = 0; + uint8_t ff07_ = 0; // Field position. int horizontal_counter_ = 0; + int vertical_counter_ = 0; + int next_vertical_counter_ = 0; + int video_line_ = 0; + + int eos() const { return is_ntsc_ ? 261 : 311; } + int vs_start() const { return is_ntsc_ ? 229 : 254; } + int vs_stop() const { return is_ntsc_ ? 232 : 257; } + int vblank_start() const { return is_ntsc_ ? 226 : 251; } + int vblank_stop() const { return is_ntsc_ ? 244 : 269; } + + bool attribute_fetch_line() const { + return video_line_ >= 0 && video_line_ < 203; + } + bool bad_line() const { + return enable_display_ && attribute_fetch_line() && ((video_line_ & 7) == y_scroll_); + } + + // Running state that's exposed. + uint16_t character_position_reload_ = 0; + uint16_t character_position_ = 0; // Actually kept as fixed point in this implementation, i.e. effectively + // a pixel count. // Running state. - bool vertical_blank_ = false; - bool vertical_sync_ = false; - bool vertical_window_ = false; + bool wide_screen_ = false; + bool narrow_screen_ = false; + + int vertical_sub_count_ = 0; + + bool char_pos_latch_ = false; + + bool increment_character_position_ = false; + bool increment_video_counter_ = false; + bool refresh_ = false; + bool character_window_ = false; bool horizontal_blank_ = false; bool horizontal_sync_ = false; - bool horizontal_burst_ = false; - uint8_t ff06_ = 0; - int blink_ = 0; + bool enable_display_ = false; + bool vertical_sub_active_ = false; // Indicates the the 3-bit row counter is active. + bool video_shift_ = false; // Indicates that the shift register is shifting. + + bool dma_window_ = false; // Indicates when RDY might be asserted. + bool external_fetch_ = false; // Covers the entire region during which the CPU is slowed down + // to single-clock speed to allow for CPU-interleaved fetches. + bool bad_line2_ = false; // High for the second (i.e. character-fetch) badline. + // Cf. bad_line() which indicates the first (i.e. attribute-fetch) badline. + bool character_fetch_ = false; // High for the entire region of a frame during which characters might be + // fetched, i.e. from the first bad_line2_ until the end of the visible area. - uint16_t character_address_ = 0; - bool fetch_characters_ = false; - bool external_fetch_ = false; - bool output_pixels_ = false; - bool refresh_ = false; - bool single_clock_ = false; - uint8_t ff07_ = 0; + bool vertical_sync_ = false; + bool vertical_screen_ = false; + bool vertical_blank_ = false; + + int flash_count_ = 0; + + uint16_t video_counter_ = 0; + uint16_t video_counter_reload_ = 0; + uint16_t next_pixels_ = 0; enum class OutputState { Blank, @@ -508,50 +636,85 @@ struct Video { return colour(value & 0x0f, (value >> 4) & 7); } - enum class FetchPhase { - Waiting, - FetchingCharacters, - FetchingAttributes, - } fetch_phase_ = FetchPhase::Waiting; - int character_line_ = 0; - int fetch_count_ = 0; - - struct LineBuffer { - struct Attribute { - uint8_t colour; - uint8_t cursor_mask; - } attributes[40]; - uint8_t characters[40]; - } lines_[2]; - uint8_t bitmap_[40]; - int line_pointer_ = 0; - - // List of events. - enum HorizontalEvent { - Begin38Columns = 3, - EndExternalFetchWindow = 288, - LatchCharacterPosition = 290, - EndCharacterFetchWindow = 300, // 296 - EndVideoShiftRegister = 304, - End38Columns = 307, - End40Columns = 315, - EndRefresh = 328, - IncrementBlink = 336, - BeginBlank = 344, - BeginSync = 358, - TestRasterInterrupt = 368, - IncrementVerticalLine = 376, - BeginBurst = 384, - EndSync = 390, - BeginExternalFetchWindow = 408, // 400, -// EndBurst = 408, - EndBlank = 416, - IncrementCharacterPositionReload = 424, - BeginCharacterFetchWindow = 436, // 432, - BeginVideoShiftRegister = 440, - Begin40Columns = 451, - EndOfLine = 456, + /// Maintains two 320-bit shift registers, one for attributes and one for characters. + /// Values come out of here and go through another 16-bit shift register before eventually reaching the display. + struct ShiftLine { + public: + template + uint8_t read() const { + return data_[channel][cursor_]; + } + template + void write(uint8_t value) { + data_[channel][(cursor_ + 1) % 40] = value; + } + void advance() { + ++cursor_; + if(cursor_ == 40) cursor_ = 0; + } + + private: + uint8_t data_[2][40]; + int cursor_ = 0; + }; + ShiftLine shifter_; + uint8_t next_attribute_, next_character_; + uint8_t current_attribute_, current_character_; + uint8_t waiting_attribute_, waiting_character_; + + // List of counter-triggered events. + enum class HorizontalEvent: unsigned int { + Begin40Columns = 0, + Begin38Columns = 8, + LatchCharacterPosition = 288, + DMAWindowEnd = 295, + EndExternalFetchWindow = 296, + EndCharacterFetchWindow = 304, + End38Columns = 312, + End40Columns = 320, + EndRefresh = 336, + IncrementFlashCounter = 348, + BeginBlank = 353, + BeginSync = 359, + VerticalSubActive = 380, + EndOfScreen = 384, + EndSync = 391, + IncrementVerticalSub = 392, + BeginExternalFetchClock = 400, + BeginAttributeFetch = 407, + EndBlank = 423, + IncrementVideoCounter = 432, + BeginShiftRegister = 440, + ScheduleCounterReset = 455, + CounterOverflow = 512, + }; + + // List of events that occur at a certain latency. + enum class DelayedEvent { + Latch = 0x01, + Flash = 0x02, + IncrementVerticalSub = 0x04, + IncrementVerticalLine = 0x08, + CounterReset = 0x10, + UpdateDMAState = 0x20, + + Mask = CounterReset | IncrementVerticalLine | IncrementVerticalSub | Flash | Latch, }; + static constexpr int DelayEventSize = 6; + uint64_t delayed_events_ = 0; + + /// Scheudles @c event to occur after @c latency pixel-clock cycles. + template + void schedule(DelayedEvent event) { + static_assert(latency <= sizeof(delayed_events_) * 8 / DelayEventSize); + delayed_events_ |= uint64_t(event) << (DelayEventSize * latency); + } + + // DMA states. + enum class DMAState { + IDLE, + THALT1, THALT2, THALT3, TDMA, + } dma_state_ = DMAState::IDLE; }; } From 6b90de539e3f3b9924f9b70281bfe45bd3d091ab Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 26 Dec 2024 22:39:16 -0500 Subject: [PATCH 048/103] Add data delays. --- Machines/Commodore/Plus4/Video.hpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/Machines/Commodore/Plus4/Video.hpp b/Machines/Commodore/Plus4/Video.hpp index 3a76550660..7ae972b3e9 100644 --- a/Machines/Commodore/Plus4/Video.hpp +++ b/Machines/Commodore/Plus4/Video.hpp @@ -304,9 +304,15 @@ struct Video { } if(increment_video_counter_) { ++video_counter_; - shifter_.advance(); + + // TODO: I've not fully persuaded myself yet that these shouldn't be the other side of the advance, + // subject to another latch in the pipeline. I need to figure out whether pixel[char/attr] are + // artefacts of the FPGA version not being able to dump 8 pixels simultaneously or act as extra + // delay. They'll probably need to come back either way once I observe x_scroll_. next_attribute_ = shifter_.read<1>(); next_character_ = shifter_.read<0>(); + + shifter_.advance(); tprintf("+"); } @@ -329,11 +335,11 @@ struct Video { } if(state == OutputState::Pixels && pixels_) { - const auto pixel_address = shifter_.read<0>(); + const auto pixel_address = waiting_character_; const auto pixels = pager_.read(uint16_t(character_base_ + (pixel_address * 8) + vertical_sub_count_)); - const auto attribute = shifter_.read<1>(); + const auto attribute = waiting_attribute_; const uint16_t colours[] = { background_[0], colour(attribute) }; pixels_[0] = (pixels & 0x80) ? colours[1] : colours[0]; @@ -661,6 +667,7 @@ struct Video { uint8_t next_attribute_, next_character_; uint8_t current_attribute_, current_character_; uint8_t waiting_attribute_, waiting_character_; + uint8_t pixel_attribute_, pixel_character_; // List of counter-triggered events. enum class HorizontalEvent: unsigned int { From 863b09c39be865334c0748ba813980d53e004694 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 27 Dec 2024 09:14:23 -0500 Subject: [PATCH 049/103] Increase key bindings. --- Machines/Commodore/Plus4/Keyboard.cpp | 10 +++++++--- Machines/Commodore/Plus4/Keyboard.hpp | 6 ++++++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/Machines/Commodore/Plus4/Keyboard.cpp b/Machines/Commodore/Plus4/Keyboard.cpp index ca48ef1bb2..871422a754 100644 --- a/Machines/Commodore/Plus4/Keyboard.cpp +++ b/Machines/Commodore/Plus4/Keyboard.cpp @@ -43,12 +43,16 @@ uint16_t KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) const { BIND(Semicolon, Semicolon); BIND(Quote, Colon); BIND(Equals, Equals); BIND(ForwardSlash, Slash); + BIND(OpenSquareBracket, At); + BIND(CloseSquareBracket, Plus); + BIND(Backslash, Clear_Home); + + BIND(F11, Clear_Home); + BIND(F12, Run_Stop); + // TODO: - // At // GBP // Asterisk - // Clear_Home - // Run_Stop } #undef BIND return MachineTypes::MappedKeyboardMachine::KeyNotMapped; diff --git a/Machines/Commodore/Plus4/Keyboard.hpp b/Machines/Commodore/Plus4/Keyboard.hpp index 1519976f4e..850c1bdaa0 100644 --- a/Machines/Commodore/Plus4/Keyboard.hpp +++ b/Machines/Commodore/Plus4/Keyboard.hpp @@ -63,6 +63,12 @@ enum Key: uint16_t { Control = key(7, 0x04), k2 = key(7, 0x08), Space = key(7, 0x10), Commodore = key(7, 0x20), Q = key(7, 0x40), Run_Stop = key(7, 0x80), + + // Bonus virtual keys: + F4 = 0xfe00, + F5 = 0xfe01, + F6 = 0xfe02, + F7 = 0xfe03, }; struct KeyboardMapper: public MachineTypes::MappedKeyboardMachine::KeyboardMapper { From 9670d5f4deef184b690429f578056ba34a8fb2d0 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 27 Dec 2024 09:20:12 -0500 Subject: [PATCH 050/103] Add cursor, proving things generally to be off by one. --- Machines/Commodore/Plus4/Video.hpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/Machines/Commodore/Plus4/Video.hpp b/Machines/Commodore/Plus4/Video.hpp index 7ae972b3e9..c14ad05de6 100644 --- a/Machines/Commodore/Plus4/Video.hpp +++ b/Machines/Commodore/Plus4/Video.hpp @@ -300,6 +300,9 @@ struct Video { waiting_attribute_ = current_attribute_; current_attribute_ = next_attribute_; + waiting_cursor_ = current_cursor_; + current_cursor_ = next_cursor_; + tprintf("s"); } if(increment_video_counter_) { @@ -311,6 +314,9 @@ struct Video { // delay. They'll probably need to come back either way once I observe x_scroll_. next_attribute_ = shifter_.read<1>(); next_character_ = shifter_.read<0>(); + next_cursor_ = + (!cursor_position_ && !character_position_) || + ((character_position_ == cursor_position_) && vertical_sub_active_); shifter_.advance(); tprintf("+"); @@ -336,8 +342,10 @@ struct Video { if(state == OutputState::Pixels && pixels_) { const auto pixel_address = waiting_character_; - const auto pixels = - pager_.read(uint16_t(character_base_ + (pixel_address * 8) + vertical_sub_count_)); + const uint8_t flash_mask = waiting_cursor_ && (flash_count_ & 0x10) ? 0xff : 0x00; + const auto pixels = pager_.read(uint16_t( + character_base_ + (pixel_address << 3) + vertical_sub_count_ + )) ^ flash_mask; const auto attribute = waiting_attribute_; const uint16_t colours[] = { background_[0], colour(attribute) }; @@ -669,6 +677,8 @@ struct Video { uint8_t waiting_attribute_, waiting_character_; uint8_t pixel_attribute_, pixel_character_; + bool next_cursor_, current_cursor_, waiting_cursor_; + // List of counter-triggered events. enum class HorizontalEvent: unsigned int { Begin40Columns = 0, From e19fe5d0e22bfd72f035c1be5620cb7c498f0e3e Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 27 Dec 2024 09:25:36 -0500 Subject: [PATCH 051/103] Reintroduce colour burst. --- Machines/Commodore/Plus4/Video.hpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Machines/Commodore/Plus4/Video.hpp b/Machines/Commodore/Plus4/Video.hpp index c14ad05de6..f089ef68a0 100644 --- a/Machines/Commodore/Plus4/Video.hpp +++ b/Machines/Commodore/Plus4/Video.hpp @@ -237,8 +237,7 @@ struct Video { if(vertical_sync_ || horizontal_sync_) { state = OutputState::Sync; } else if(vertical_blank_ || horizontal_blank_) { -// state = horizontal_burst_ ? OutputState::Burst : OutputState::Blank; - state = OutputState::Blank; + state = horizontal_burst_ ? OutputState::Burst : OutputState::Blank; } else { const bool pixel_screen = columns_40_ ? wide_screen_ : narrow_screen_; state = enable_display_ && pixel_screen ? OutputState::Pixels : OutputState::Border; @@ -441,6 +440,7 @@ struct Video { case HorizontalEvent::EndOfScreen: schedule<8>(DelayedEvent::IncrementVerticalLine); next_vertical_counter_ = video_line_ == eos() ? 0 : ((vertical_counter_ + 1) & 511); + horizontal_burst_ = true; break; case HorizontalEvent::EndExternalFetchWindow: @@ -485,6 +485,8 @@ struct Video { case HorizontalEvent::BeginAttributeFetch: dma_window_ = true; + horizontal_burst_ = false; // Should be 1 cycle later, if the data sheet is completely accurate. + // Though all other timings work on the assumption that it isn't. break; case HorizontalEvent::EndBlank: @@ -590,6 +592,7 @@ struct Video { bool character_window_ = false; bool horizontal_blank_ = false; bool horizontal_sync_ = false; + bool horizontal_burst_ = false; bool enable_display_ = false; bool vertical_sub_active_ = false; // Indicates the the 3-bit row counter is active. bool video_shift_ = false; // Indicates that the shift register is shifting. From ff12bbbdb61d8034030e61486d35509c6d2eda9d Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 27 Dec 2024 17:10:25 -0500 Subject: [PATCH 052/103] Simplify delay state. --- Machines/Commodore/Plus4/Video.hpp | 94 ++++++++++++++---------------- 1 file changed, 44 insertions(+), 50 deletions(-) diff --git a/Machines/Commodore/Plus4/Video.hpp b/Machines/Commodore/Plus4/Video.hpp index f089ef68a0..2ccc7c6f27 100644 --- a/Machines/Commodore/Plus4/Video.hpp +++ b/Machines/Commodore/Plus4/Video.hpp @@ -18,14 +18,6 @@ namespace Commodore::Plus4 { -namespace { - -void tprintf([[maybe_unused]] const char *str) { -// printf("%s", str); -} - -} - constexpr double clock_rate(bool is_ntsc) { return is_ntsc ? 14'318'180 : // i.e. colour subcarrier * 4. @@ -289,64 +281,53 @@ struct Video { } break; } - if(increment_character_position_) { - ++character_position_; - } if(video_shift_) { - waiting_character_ = current_character_; - current_character_ = next_character_; - - waiting_attribute_ = current_attribute_; - current_attribute_ = next_attribute_; - - waiting_cursor_ = current_cursor_; - current_cursor_ = next_cursor_; - - tprintf("s"); + next_attribute_.advance(); + next_character_.advance(); + next_cursor_.advance(); } if(increment_video_counter_) { - ++video_counter_; - // TODO: I've not fully persuaded myself yet that these shouldn't be the other side of the advance, // subject to another latch in the pipeline. I need to figure out whether pixel[char/attr] are // artefacts of the FPGA version not being able to dump 8 pixels simultaneously or act as extra // delay. They'll probably need to come back either way once I observe x_scroll_. - next_attribute_ = shifter_.read<1>(); - next_character_ = shifter_.read<0>(); - next_cursor_ = - (!cursor_position_ && !character_position_) || - ((character_position_ == cursor_position_) && vertical_sub_active_); + // + // Current thinking: these should probably go afterwards, with an equivalent of pixelshiftreg + // adding further delay? + next_attribute_.write(shifter_.read<1>()); + next_character_.write(shifter_.read<0>()); + next_cursor_.write( + (flash_count_ & 0x10) && + ( + (!cursor_position_ && !character_position_) || + ((character_position_ == cursor_position_) && vertical_sub_active_) + ) + ? 0xff : 0x00 + ); shifter_.advance(); - tprintf("+"); + ++video_counter_; + } + if(increment_character_position_ && character_fetch_) { + ++character_position_; } if(dma_state_ == DMAState::TDMA) { const uint16_t address = video_matrix_base_ + video_counter_; if(bad_line2_) { shifter_.write<1>(pager_.read(address)); - tprintf("c"); } else { shifter_.write<0>(pager_.read(address + 0x400)); - tprintf("a"); } } - if(increment_character_position_ && character_fetch_) { - tprintf("p"); - } - if(wide_screen_ || narrow_screen_) { - tprintf("."); - } - if(state == OutputState::Pixels && pixels_) { - const auto pixel_address = waiting_character_; - const uint8_t flash_mask = waiting_cursor_ && (flash_count_ & 0x10) ? 0xff : 0x00; + const auto pixel_address = next_character_.read(); const auto pixels = pager_.read(uint16_t( character_base_ + (pixel_address << 3) + vertical_sub_count_ - )) ^ flash_mask; + )) ^ next_cursor_.read(); - const auto attribute = waiting_attribute_; + const auto attribute = next_attribute_.read(); const uint16_t colours[] = { background_[0], colour(attribute) }; pixels_[0] = (pixels & 0x80) ? colours[1] : colours[0]; @@ -360,8 +341,6 @@ struct Video { pixels_ += 8; } - - tprintf("|"); } time_in_state_ += period; @@ -510,7 +489,6 @@ struct Video { } ticks_remaining -= period; - if(!horizontal_counter_) tprintf("\n"); } } @@ -675,12 +653,28 @@ struct Video { int cursor_ = 0; }; ShiftLine shifter_; - uint8_t next_attribute_, next_character_; - uint8_t current_attribute_, current_character_; - uint8_t waiting_attribute_, waiting_character_; - uint8_t pixel_attribute_, pixel_character_; - bool next_cursor_, current_cursor_, waiting_cursor_; + /// Maintains a single 32-bit shift register, which shifts in whole-byte increments with + /// a template-provided delay time. + template + struct ShiftRegister { + public: + uint8_t read() const { + return uint8_t(data_); + } + void write(uint8_t value) { + data_ |= uint32_t(value) << (cycles_delay * 8); + } + void advance() { + data_ >>= 8; + } + + private: + uint32_t data_; + }; + ShiftRegister<2> next_attribute_; + ShiftRegister<2> next_character_; + ShiftRegister<3> next_cursor_; // List of counter-triggered events. enum class HorizontalEvent: unsigned int { From 2cfd2ff624c87ae73af1c269501f8b507b7838f1 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 27 Dec 2024 20:59:07 -0500 Subject: [PATCH 053/103] Start adding tape player. --- Machines/Commodore/Plus4/Plus4.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Machines/Commodore/Plus4/Plus4.cpp b/Machines/Commodore/Plus4/Plus4.cpp index b4639de512..2041d2fd3d 100644 --- a/Machines/Commodore/Plus4/Plus4.cpp +++ b/Machines/Commodore/Plus4/Plus4.cpp @@ -18,6 +18,8 @@ #include "../../../Processors/6502/6502.hpp" #include "../../../Analyser/Static/Commodore/Target.hpp" +#include "../../../Storage/Tape/Tape.hpp" + using namespace Commodore::Plus4; namespace { @@ -111,7 +113,8 @@ class ConcreteMachine: m6502_(*this), interrupts_(*this), timers_(interrupts_), - video_(video_map_, interrupts_) + video_(video_map_, interrupts_), + tape_player_(1'000'000) { set_clock_rate(clock_rate(false)); @@ -363,6 +366,8 @@ class ConcreteMachine: std::array key_states_{}; uint8_t keyboard_latch_ = 0xff; + + Storage::Tape::BinaryTapePlayer tape_player_; }; } From 01aeb466642aecddff07dddfc37bd42a1ddf807e Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 27 Dec 2024 20:59:24 -0500 Subject: [PATCH 054/103] Bump version. --- .../Base.lproj/MachinePicker.xib | 56 +++++++++---------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/OSBindings/Mac/Clock Signal/MachinePicker/Base.lproj/MachinePicker.xib b/OSBindings/Mac/Clock Signal/MachinePicker/Base.lproj/MachinePicker.xib index 30f4828938..9d19a159ae 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 @@ - + - + @@ -18,7 +18,7 @@ - + @@ -125,7 +125,7 @@ Gw - + @@ -155,7 +155,7 @@ Gw - + @@ -192,7 +192,7 @@ Gw - + @@ -246,7 +246,7 @@ Gw - + @@ -261,7 +261,7 @@ Gw - + @@ -321,7 +321,7 @@ Gw - + @@ -335,7 +335,7 @@ Gw - + @@ -400,7 +400,7 @@ Gw - + @@ -481,7 +481,7 @@ Gw - + @@ -495,7 +495,7 @@ Gw - + @@ -508,7 +508,7 @@ Gw - + @@ -522,7 +522,7 @@ Gw - + @@ -537,7 +537,7 @@ Gw - + @@ -636,7 +636,7 @@ Gw - + @@ -674,7 +674,7 @@ Gw - + @@ -704,7 +704,7 @@ Gw - + @@ -759,7 +759,7 @@ Gw - + @@ -773,7 +773,7 @@ Gw - + @@ -818,7 +818,7 @@ Gw - + @@ -847,7 +847,7 @@ Gw - + @@ -881,7 +881,7 @@ Gw - + @@ -897,7 +897,7 @@ Gw - + @@ -958,7 +958,7 @@ Gw - + @@ -1006,7 +1006,7 @@ Gw - + @@ -1044,7 +1044,7 @@ Gw - + From 80f5d7c7350970665c4b189dae87a66c140a9115 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 27 Dec 2024 21:19:41 -0500 Subject: [PATCH 055/103] Attempt full tape input. --- Machines/Commodore/Plus4/Keyboard.cpp | 2 +- Machines/Commodore/Plus4/Plus4.cpp | 44 ++++++++++++++++++++++++--- Machines/Commodore/Plus4/Video.hpp | 2 +- 3 files changed, 41 insertions(+), 7 deletions(-) diff --git a/Machines/Commodore/Plus4/Keyboard.cpp b/Machines/Commodore/Plus4/Keyboard.cpp index 871422a754..1158c25b26 100644 --- a/Machines/Commodore/Plus4/Keyboard.cpp +++ b/Machines/Commodore/Plus4/Keyboard.cpp @@ -46,13 +46,13 @@ uint16_t KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) const { BIND(OpenSquareBracket, At); BIND(CloseSquareBracket, Plus); BIND(Backslash, Clear_Home); + BIND(BackTick, Asterisk); BIND(F11, Clear_Home); BIND(F12, Run_Stop); // TODO: // GBP - // Asterisk } #undef BIND return MachineTypes::MappedKeyboardMachine::KeyNotMapped; diff --git a/Machines/Commodore/Plus4/Plus4.cpp b/Machines/Commodore/Plus4/Plus4.cpp index 2041d2fd3d..24343474e7 100644 --- a/Machines/Commodore/Plus4/Plus4.cpp +++ b/Machines/Commodore/Plus4/Plus4.cpp @@ -113,10 +113,12 @@ class ConcreteMachine: m6502_(*this), interrupts_(*this), timers_(interrupts_), - video_(video_map_, interrupts_), - tape_player_(1'000'000) + video_(video_map_, interrupts_) { - set_clock_rate(clock_rate(false)); + const auto clock = clock_rate(false); + media_divider_ = Cycles(clock); + set_clock_rate(clock); + tape_player_ = std::make_unique(clock); const auto kernel = ROM::Name::Plus4KernelPALv5; const auto basic = ROM::Name::Plus4BASIC; @@ -137,6 +139,7 @@ class ConcreteMachine: video_map_.page(ram_.data()); insert_media(target.media); + printf("Loading command is: %s\n", target.loading_command.c_str()); } Cycles perform_bus_operation( @@ -154,6 +157,7 @@ class ConcreteMachine: timers_.tick(timers_cycles.as()); video_.run_for(length); + tape_player_->run_for(length); if(operation == CPU::MOS6502::BusOperation::Ready) { return length; @@ -171,6 +175,30 @@ class ConcreteMachine: // b2 = serial ATN out; // b1 = serial clock out and cassette write; // b0 = serial data out. + + if(isReadOperation(operation)) { + if(address) { + *value = io_direction_; +// printf("Read data direction: %02x\n", *value); + } else { + const uint8_t all_inputs = tape_player_->input() ? 0x10 : 0x00; + *value = + (io_direction_ & io_output_) | + (~io_direction_ & all_inputs); + printf("Read input: %02x\n", *value); + } + } else { + if(address) { + io_direction_ = *value; +// printf("Set data direction: %02x\n", *value); + } else { + io_output_ = *value; + printf("Output: %02x\n", *value); + tape_player_->set_motor_control(!(*value & 0x08)); +// tape_player_->set_motor_control(*value & 0x08); + } + } + // printf("%04x: %02x %c\n", address, *value, isReadOperation(operation) ? 'r' : 'w'); } else if(address < 0xfd00 || address >= 0xff40) { if(isReadOperation(operation)) { @@ -334,7 +362,11 @@ class ConcreteMachine: m6502_.run_for(cycles); } - bool insert_media(const Analyser::Static::Media &) final { + bool insert_media(const Analyser::Static::Media &media) final { + if(!media.tapes.empty()) { + tape_player_->set_tape(media.tapes[0]); + } + return true; } @@ -367,7 +399,9 @@ class ConcreteMachine: std::array key_states_{}; uint8_t keyboard_latch_ = 0xff; - Storage::Tape::BinaryTapePlayer tape_player_; + Cycles media_divider_; + std::unique_ptr tape_player_; + uint8_t io_direction_ = 0x00, io_output_ = 0x00; }; } diff --git a/Machines/Commodore/Plus4/Video.hpp b/Machines/Commodore/Plus4/Video.hpp index 2ccc7c6f27..300737b611 100644 --- a/Machines/Commodore/Plus4/Video.hpp +++ b/Machines/Commodore/Plus4/Video.hpp @@ -18,7 +18,7 @@ namespace Commodore::Plus4 { -constexpr double clock_rate(bool is_ntsc) { +constexpr int clock_rate(bool is_ntsc) { return is_ntsc ? 14'318'180 : // i.e. colour subcarrier * 4. 17'734'448; // i.e. very close to colour subcarrier * 4 — only about 0.1% off. From b4b216de847439e4f719e6bdc377ab6bcd47e08f Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 27 Dec 2024 21:43:41 -0500 Subject: [PATCH 056/103] Attempt press-play feedback. --- Machines/Commodore/Plus4/Plus4.cpp | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/Machines/Commodore/Plus4/Plus4.cpp b/Machines/Commodore/Plus4/Plus4.cpp index 24343474e7..b550d52571 100644 --- a/Machines/Commodore/Plus4/Plus4.cpp +++ b/Machines/Commodore/Plus4/Plus4.cpp @@ -177,25 +177,25 @@ class ConcreteMachine: // b0 = serial data out. if(isReadOperation(operation)) { - if(address) { + if(!address) { *value = io_direction_; // printf("Read data direction: %02x\n", *value); } else { const uint8_t all_inputs = tape_player_->input() ? 0x10 : 0x00; *value = (io_direction_ & io_output_) | - (~io_direction_ & all_inputs); - printf("Read input: %02x\n", *value); + ((~io_direction_) & all_inputs); +// printf("Read input: %02x\n", *value); } } else { - if(address) { + if(!address) { io_direction_ = *value; // printf("Set data direction: %02x\n", *value); } else { io_output_ = *value; - printf("Output: %02x\n", *value); - tape_player_->set_motor_control(!(*value & 0x08)); +// printf("Output: %02x\n", *value); // tape_player_->set_motor_control(*value & 0x08); + tape_player_->set_motor_control(!(*value & 0x08)); } } @@ -208,11 +208,12 @@ class ConcreteMachine: } } else if(address < 0xff00) { // Miscellaneous hardware. All TODO. -// if(isReadOperation(operation)) { + if(isReadOperation(operation)) { // printf("TODO: read @ %04x\n", address); -// } else { + *value = 0x00; + } else { // printf("TODO: write of %02x @ %04x\n", *value, address); -// } + } } else { if(isReadOperation(operation)) { switch(address) { From c92b0dc8866579b1a3acee56152e78db93ed4854 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 28 Dec 2024 20:11:27 -0500 Subject: [PATCH 057/103] Start normalising Commodore serial bus interface. --- Machines/Commodore/SerialBus.cpp | 2 +- Machines/Commodore/SerialBus.hpp | 226 ++++++++++++++++--------------- 2 files changed, 118 insertions(+), 110 deletions(-) diff --git a/Machines/Commodore/SerialBus.cpp b/Machines/Commodore/SerialBus.cpp index ddab8c6a2e..79eef817b7 100644 --- a/Machines/Commodore/SerialBus.cpp +++ b/Machines/Commodore/SerialBus.cpp @@ -13,7 +13,7 @@ using namespace Commodore::Serial; -const char *::Commodore::Serial::StringForLine(Line line) { +const char *::Commodore::Serial::to_string(Line line) { switch(line) { case ServiceRequest: return "Service request"; case Attention: return "Attention"; diff --git a/Machines/Commodore/SerialBus.hpp b/Machines/Commodore/SerialBus.hpp index 0b4640c7bb..25dabd522e 100644 --- a/Machines/Commodore/SerialBus.hpp +++ b/Machines/Commodore/SerialBus.hpp @@ -14,115 +14,123 @@ namespace Commodore::Serial { - enum Line { - ServiceRequest = 0, - Attention, - Clock, - Data, - Reset - }; - - enum LineLevel: bool { - High = true, - Low = false - }; - - class Port; - class Bus; - - /*! - Returns a C string giving a human-readable name for the supplied line. - */ - const char *StringForLine(Line line); - - /*! - Calls both of the necessary methods to (i) set @c bus as the target for @c port outputs; and - (ii) add @c port as one of the targets to which @c bus propagates line changes. - */ - void AttachPortAndBus(std::shared_ptr port, std::shared_ptr bus); - - /*! - A serial bus is responsible for retaining a weakly-held collection of attached ports and for deciding the - current bus levels based upon the net result of each port's output, and for communicating changes in bus - levels to every port. - */ - class Bus { - public: - Bus() : line_levels_{High, High, High, High, High} {} - - /*! - Adds the supplied port to the bus. - */ - void add_port(std::shared_ptr port); - - /*! - Communicates to the bus that one of its attached port has changed its output level for the given line. - The bus will therefore recalculate bus state and propagate as necessary. - */ - void set_line_output_did_change(Line line); - - private: - LineLevel line_levels_[5]; - std::vector> ports_; - }; - - /*! - A serial port is an endpoint on a serial bus; this class provides a direct setter for current line outputs and - expects to be subclassed in order for specific port-housing devices to deal with input. - */ - class Port { - public: - Port() : line_levels_{High, High, High, High, High} {} - virtual ~Port() = default; - - /*! - Sets the current level of an output line on this serial port. - */ - void set_output(Line line, LineLevel level) { - if(line_levels_[line] != level) { - line_levels_[line] = level; - std::shared_ptr bus = serial_bus_.lock(); - if(bus) bus->set_line_output_did_change(line); - } +enum class Line { + ServiceRequest = 0, + Attention, + Clock, + Data, + Reset +}; +const char *to_string(Line line); + +enum class LineLevel: bool { + High = true, + Low = false +}; +const char *to_string(LineLevel line); + +class Port; +class Bus; + +/*! + Calls both of the necessary methods to (i) set @c bus as the target for @c port outputs; and + (ii) add @c port as one of the targets to which @c bus propagates line changes. +*/ +void attach(Port &port, Bus &bus); + +/*! + A serial bus is responsible for retaining a weakly-held collection of attached ports and for deciding the + current bus levels based upon the net result of each port's output, and for communicating changes in bus + levels to every port. +*/ +class Bus { + public: + Bus() : line_levels_{ + LineLevel::High, + LineLevel::High, + LineLevel::High, + LineLevel::High, + LineLevel::High + } {} + + /*! + Adds the supplied port to the bus. + */ + void add_port(Port &port); + + /*! + Communicates to the bus that one of its attached port has changed its output level for the given line. + The bus will therefore recalculate bus state and propagate as necessary. + */ + void set_line_output_did_change(Line line); + + private: + LineLevel line_levels_[5]; + std::vector ports_; +}; + +/*! + A serial port is an endpoint on a serial bus; this class provides a direct setter for current line outputs and + expects to be subclassed in order for specific port-housing devices to deal with input. +*/ +class Port { + public: + Port() : line_levels_{ + LineLevel::High, + LineLevel::High, + LineLevel::High, + LineLevel::High, + LineLevel::High + } {} + virtual ~Port() = default; + + /*! + Sets the current level of an output line on this serial port. + */ + void set_output(Line line, LineLevel level) { + if(line_levels_[size_t(line)] != level) { + line_levels_[size_t(line)] = level; + if(serial_bus_) serial_bus_->set_line_output_did_change(line); } - - /*! - Gets the previously set level of an output line. - */ - LineLevel get_output(Line line) { - return line_levels_[line]; - } - - /*! - Called by the bus to signal a change in any input line level. Subclasses should implement this. - */ - virtual void set_input(Line line, LineLevel value) = 0; - - /*! - Sets the supplied serial bus as that to which line levels will be communicated. - */ - inline void set_serial_bus(std::shared_ptr serial_bus) { - serial_bus_ = serial_bus; - } - - private: - std::weak_ptr serial_bus_; - LineLevel line_levels_[5]; - }; - - /*! - A debugging port, which makes some attempt to log bus activity. Incomplete. TODO: complete. - */ - class DebugPort: public Port { - public: - void set_input(Line line, LineLevel value); - - DebugPort() : incoming_count_(0) {} - - private: - uint8_t incoming_byte_; - int incoming_count_; - LineLevel input_levels_[5]; - }; + } + + /*! + Gets the previously set level of an output line. + */ + LineLevel get_output(Line line) { + return line_levels_[size_t(line)]; + } + + /*! + Called by the bus to signal a change in any input line level. Subclasses should implement this. + */ + virtual void set_input(Line line, LineLevel value) = 0; + + /*! + Sets the supplied serial bus as that to which line levels will be communicated. + */ + inline void set_bus(Bus &serial_bus) { + serial_bus_ = &serial_bus; + } + + private: + Bus *serial_bus_ = nullptr; + LineLevel line_levels_[5]; +}; + +/*! + A debugging port, which makes some attempt to log bus activity. Incomplete. TODO: complete. +*/ +class DebugPort: public Port { + public: + void set_input(Line line, LineLevel value); + + DebugPort() : incoming_count_(0) {} + + private: + uint8_t incoming_byte_; + int incoming_count_; + LineLevel input_levels_[5]; +}; } From 6f638805f78481651bc4c3f023d8b4bf99307745 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 28 Dec 2024 20:52:31 -0500 Subject: [PATCH 058/103] Further eliminate std::shared_ptr connections. --- Machines/Commodore/1540/C1540.hpp | 22 +-- .../Commodore/1540/Implementation/C1540.cpp | 48 +++--- .../1540/Implementation/C1540Base.hpp | 40 ++--- Machines/Commodore/Plus4/Interrupts.hpp | 1 - Machines/Commodore/Plus4/Plus4.cpp | 12 +- Machines/Commodore/SerialBus.cpp | 70 +++++---- Machines/Commodore/SerialBus.hpp | 144 +++++++++--------- Machines/Commodore/Vic-20/Vic20.cpp | 79 +++++----- 8 files changed, 203 insertions(+), 213 deletions(-) diff --git a/Machines/Commodore/1540/C1540.hpp b/Machines/Commodore/1540/C1540.hpp index 47f8cf7261..a5a815c628 100644 --- a/Machines/Commodore/1540/C1540.hpp +++ b/Machines/Commodore/1540/C1540.hpp @@ -36,20 +36,20 @@ namespace Commodore::C1540 { Provides an emulation of the C1540. */ class Machine final: public MachineBase { - public: - static ROM::Request rom_request(Personality personality); - Machine(Personality personality, const ROM::Map &roms); +public: + static ROM::Request rom_request(Personality personality); + Machine(Personality personality, const ROM::Map &roms); - /*! - Sets the serial bus to which this drive should attach itself. - */ - void set_serial_bus(std::shared_ptr<::Commodore::Serial::Bus> serial_bus); + /*! + Sets the serial bus to which this drive should attach itself. + */ + void set_serial_bus(Commodore::Serial::Bus &); - /// Advances time. - void run_for(const Cycles cycles); + /// Advances time. + void run_for(Cycles); - /// Inserts @c disk into the drive. - void set_disk(std::shared_ptr disk); + /// Inserts @c disk into the drive. + void set_disk(std::shared_ptr disk); }; } diff --git a/Machines/Commodore/1540/Implementation/C1540.cpp b/Machines/Commodore/1540/Implementation/C1540.cpp index 6ec47298d6..9a1d8ef95b 100644 --- a/Machines/Commodore/1540/Implementation/C1540.cpp +++ b/Machines/Commodore/1540/Implementation/C1540.cpp @@ -27,16 +27,15 @@ ROM::Request Machine::rom_request(Personality personality) { MachineBase::MachineBase(Personality personality, const ROM::Map &roms) : Storage::Disk::Controller(1000000), m6502_(*this), - serial_port_VIA_port_handler_(new SerialPortVIA(serial_port_VIA_)), - serial_port_(new SerialPort), + serial_port_VIA_port_handler_(serial_port_VIA_), drive_VIA_(drive_VIA_port_handler_), - serial_port_VIA_(*serial_port_VIA_port_handler_) { + serial_port_VIA_(serial_port_VIA_port_handler_) { // attach the serial port to its VIA and vice versa - serial_port_->set_serial_port_via(serial_port_VIA_port_handler_); - serial_port_VIA_port_handler_->set_serial_port(serial_port_); + serial_port_.set_serial_port_via(serial_port_VIA_port_handler_); + serial_port_VIA_port_handler_.set_serial_port(serial_port_); // set this instance as the delegate to receive interrupt requests from both VIAs - serial_port_VIA_port_handler_->set_interrupt_delegate(this); + serial_port_VIA_port_handler_.set_interrupt_delegate(this); drive_VIA_port_handler_.set_interrupt_delegate(this); drive_VIA_port_handler_.set_delegate(this); @@ -64,8 +63,8 @@ MachineBase::MachineBase(Personality personality, const ROM::Map &roms) : Machine::Machine(Personality personality, const ROM::Map &roms) : MachineBase(personality, roms) {} -void Machine::set_serial_bus(std::shared_ptr<::Commodore::Serial::Bus> serial_bus) { - Commodore::Serial::AttachPortAndBus(serial_port_, serial_bus); +void Machine::set_serial_bus(Commodore::Serial::Bus &serial_bus) { + Commodore::Serial::attach(serial_port_, serial_bus); } Cycles MachineBase::perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { @@ -174,14 +173,11 @@ uint8_t SerialPortVIA::get_port_input(MOS::MOS6522::Port port) { void SerialPortVIA::set_port_output(MOS::MOS6522::Port port, uint8_t value, uint8_t) { if(port) { - std::shared_ptr<::Commodore::Serial::Port> serialPort = serial_port_.lock(); - if(serialPort) { - attention_acknowledge_level_ = !(value&0x10); - data_level_output_ = (value&0x02); + attention_acknowledge_level_ = !(value&0x10); + data_level_output_ = (value&0x02); - serialPort->set_output(::Commodore::Serial::Line::Clock, ::Commodore::Serial::LineLevel(!(value&0x08))); - update_data_line(); - } + serial_port_->set_output(::Commodore::Serial::Line::Clock, ::Commodore::Serial::LineLevel(!(value&0x08))); + update_data_line(); } } @@ -199,17 +195,14 @@ void SerialPortVIA::set_serial_line_state(::Commodore::Serial::Line line, bool v } } -void SerialPortVIA::set_serial_port(const std::shared_ptr<::Commodore::Serial::Port> &serialPort) { - serial_port_ = serialPort; +void SerialPortVIA::set_serial_port(Commodore::Serial::Port &port) { + serial_port_ = &port; } void SerialPortVIA::update_data_line() { - std::shared_ptr<::Commodore::Serial::Port> serialPort = serial_port_.lock(); - if(serialPort) { - // "ATN (Attention) is an input on pin 3 of P2 and P3 that is sensed at PB7 and CA1 of UC3 after being inverted by UA1" - serialPort->set_output(::Commodore::Serial::Line::Data, - ::Commodore::Serial::LineLevel(!data_level_output_ && (attention_level_input_ != attention_acknowledge_level_))); - } + // "ATN (Attention) is an input on pin 3 of P2 and P3 that is sensed at PB7 and CA1 of UC3 after being inverted by UA1" + serial_port_->set_output(::Commodore::Serial::Line::Data, + Serial::LineLevel(!data_level_output_ && (attention_level_input_ != attention_acknowledge_level_))); } // MARK: - DriveVIA @@ -281,11 +274,10 @@ void DriveVIA::set_activity_observer(Activity::Observer *observer) { // MARK: - SerialPort -void SerialPort::set_input(::Commodore::Serial::Line line, ::Commodore::Serial::LineLevel level) { - std::shared_ptr serialPortVIA = serial_port_VIA_.lock(); - if(serialPortVIA) serialPortVIA->set_serial_line_state(line, bool(level)); +void SerialPort::set_input(Serial::Line line, Serial::LineLevel level) { + serial_port_VIA_->set_serial_line_state(line, bool(level)); } -void SerialPort::set_serial_port_via(const std::shared_ptr &serialPortVIA) { - serial_port_VIA_ = serialPortVIA; +void SerialPort::set_serial_port_via(SerialPortVIA &serialPortVIA) { + serial_port_VIA_ = &serialPortVIA; } diff --git a/Machines/Commodore/1540/Implementation/C1540Base.hpp b/Machines/Commodore/1540/Implementation/C1540Base.hpp index 774afb0a55..91f5da8179 100644 --- a/Machines/Commodore/1540/Implementation/C1540Base.hpp +++ b/Machines/Commodore/1540/Implementation/C1540Base.hpp @@ -46,12 +46,12 @@ class SerialPortVIA: public MOS::MOS6522::IRQDelegatePortHandler { void set_port_output(MOS::MOS6522::Port, uint8_t value, uint8_t mask); void set_serial_line_state(::Commodore::Serial::Line, bool); - void set_serial_port(const std::shared_ptr<::Commodore::Serial::Port> &); + void set_serial_port(Commodore::Serial::Port &); private: MOS::MOS6522::MOS6522 &via_; uint8_t port_b_ = 0x0; - std::weak_ptr<::Commodore::Serial::Port> serial_port_; + Commodore::Serial::Port *serial_port_ = nullptr; bool attention_acknowledge_level_ = false; bool attention_level_input_ = true; bool data_level_output_ = false; @@ -84,7 +84,7 @@ class DriveVIA: public MOS::MOS6522::IRQDelegatePortHandler { }; void set_delegate(Delegate *); - uint8_t get_port_input(MOS::MOS6522::Port port); + uint8_t get_port_input(MOS::MOS6522::Port); void set_sync_detected(bool); void set_data_input(uint8_t); @@ -95,7 +95,7 @@ class DriveVIA: public MOS::MOS6522::IRQDelegatePortHandler { void set_port_output(MOS::MOS6522::Port, uint8_t value, uint8_t direction_mask); - void set_activity_observer(Activity::Observer *observer); + void set_activity_observer(Activity::Observer *); private: uint8_t port_b_ = 0xff, port_a_ = 0xff; @@ -111,11 +111,11 @@ class DriveVIA: public MOS::MOS6522::IRQDelegatePortHandler { */ class SerialPort : public ::Commodore::Serial::Port { public: - void set_input(::Commodore::Serial::Line, ::Commodore::Serial::LineLevel); - void set_serial_port_via(const std::shared_ptr &); + void set_input(Commodore::Serial::Line, Commodore::Serial::LineLevel); + void set_serial_port_via(SerialPortVIA &); private: - std::weak_ptr serial_port_VIA_; + SerialPortVIA *serial_port_VIA_ = nullptr; }; class MachineBase: @@ -125,37 +125,37 @@ class MachineBase: public Storage::Disk::Controller { public: - MachineBase(Personality personality, const ROM::Map &roms); + MachineBase(Personality, const ROM::Map &); + + /// Attaches the activity observer to this C1540. + void set_activity_observer(Activity::Observer *); // to satisfy CPU::MOS6502::Processor - Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value); + Cycles perform_bus_operation(CPU::MOS6502::BusOperation, uint16_t address, uint8_t *value); +protected: // to satisfy MOS::MOS6522::Delegate - virtual void mos6522_did_change_interrupt_status(void *mos6522); + void mos6522_did_change_interrupt_status(void *mos6522) override; // to satisfy DriveVIA::Delegate - void drive_via_did_step_head(void *driveVIA, int direction); - void drive_via_did_set_data_density(void *driveVIA, int density); + void drive_via_did_step_head(void *driveVIA, int direction) override; + void drive_via_did_set_data_density(void *driveVIA, int density) override; - /// Attaches the activity observer to this C1540. - void set_activity_observer(Activity::Observer *observer); - -protected: CPU::MOS6502::Processor m6502_; uint8_t ram_[0x800]; uint8_t rom_[0x4000]; - std::shared_ptr serial_port_VIA_port_handler_; - std::shared_ptr serial_port_; + SerialPortVIA serial_port_VIA_port_handler_; + SerialPort serial_port_; DriveVIA drive_VIA_port_handler_; MOS::MOS6522::MOS6522 drive_VIA_; MOS::MOS6522::MOS6522 serial_port_VIA_; int shift_register_ = 0, bit_window_offset_; - virtual void process_input_bit(int value); - virtual void process_index_hole(); + void process_input_bit(int value) override; + void process_index_hole() override; }; } diff --git a/Machines/Commodore/Plus4/Interrupts.hpp b/Machines/Commodore/Plus4/Interrupts.hpp index a493a3449a..5eb2b408e7 100644 --- a/Machines/Commodore/Plus4/Interrupts.hpp +++ b/Machines/Commodore/Plus4/Interrupts.hpp @@ -24,7 +24,6 @@ struct Interrupts { return delegate_; } - enum Flag { Timer3 = 0x40, Timer2 = 0x10, diff --git a/Machines/Commodore/Plus4/Plus4.cpp b/Machines/Commodore/Plus4/Plus4.cpp index b550d52571..5d0ea1ddb7 100644 --- a/Machines/Commodore/Plus4/Plus4.cpp +++ b/Machines/Commodore/Plus4/Plus4.cpp @@ -181,11 +181,11 @@ class ConcreteMachine: *value = io_direction_; // printf("Read data direction: %02x\n", *value); } else { - const uint8_t all_inputs = tape_player_->input() ? 0x10 : 0x00; + const uint8_t all_inputs = (tape_player_->input() ? 0x00 : 0x10) | 0xe0; *value = (io_direction_ & io_output_) | ((~io_direction_) & all_inputs); -// printf("Read input: %02x\n", *value); + printf("[%04x] In: %02x\n", m6502_.value_of(CPU::MOS6502::Register::ProgramCounter), *value); } } else { if(!address) { @@ -193,7 +193,7 @@ class ConcreteMachine: // printf("Set data direction: %02x\n", *value); } else { io_output_ = *value; -// printf("Output: %02x\n", *value); + printf("[%04x] Out: %02x\n", m6502_.value_of(CPU::MOS6502::Register::ProgramCounter), *value); // tape_player_->set_motor_control(*value & 0x08); tape_player_->set_motor_control(!(*value & 0x08)); } @@ -210,7 +210,11 @@ class ConcreteMachine: // Miscellaneous hardware. All TODO. if(isReadOperation(operation)) { // printf("TODO: read @ %04x\n", address); - *value = 0x00; + if((address & 0xfff0) == 0xfd10) { + *value = 0xff ^ 0x4; // Seems to detect the play button. + } else { + *value = 0xff; + } } else { // printf("TODO: write of %02x @ %04x\n", *value, address); } diff --git a/Machines/Commodore/SerialBus.cpp b/Machines/Commodore/SerialBus.cpp index 79eef817b7..54bf1d9377 100644 --- a/Machines/Commodore/SerialBus.cpp +++ b/Machines/Commodore/SerialBus.cpp @@ -15,50 +15,53 @@ using namespace Commodore::Serial; const char *::Commodore::Serial::to_string(Line line) { switch(line) { - case ServiceRequest: return "Service request"; - case Attention: return "Attention"; - case Clock: return "Clock"; - case Data: return "Data"; - case Reset: return "Reset"; + case Line::ServiceRequest: return "Service request"; + case Line::Attention: return "Attention"; + case Line::Clock: return "Clock"; + case Line::Data: return "Data"; + case Line::Reset: return "Reset"; default: return nullptr; } } -void ::Commodore::Serial::AttachPortAndBus(std::shared_ptr port, std::shared_ptr bus) { - port->set_serial_bus(bus); - bus->add_port(port); +const char *::Commodore::Serial::to_string(LineLevel level) { + switch(level) { + case LineLevel::High : return "high"; + case LineLevel::Low : return "low"; + default: return nullptr; + } } -void Bus::add_port(std::shared_ptr port) { - ports_.push_back(port); - for(int line = int(ServiceRequest); line <= int(Reset); line++) { - // the addition of a new device may change the line output... +void ::Commodore::Serial::attach(Port &port, Bus &bus) { + port.set_bus(bus); + bus.add_port(port); +} + +void Bus::add_port(Port &port) { + ports_.push_back(&port); + for(int line = int(Line::ServiceRequest); line <= int(Line::Reset); line++) { + // The addition of a new device may change the line output... set_line_output_did_change(Line(line)); - // ... but the new device will need to be told the current state regardless - port->set_input(Line(line), line_levels_[line]); + // ... but the new device will need to be told the current state regardless. + port.set_input(Line(line), line_levels_[line]); } } void Bus::set_line_output_did_change(Line line) { - // i.e. I believe these lines to be open collector - LineLevel new_line_level = High; - for(std::weak_ptr port : ports_) { - std::shared_ptr locked_port = port.lock(); - if(locked_port) { - new_line_level = (LineLevel)(bool(new_line_level) & bool(locked_port->get_output(line))); - } + // Treat lines as open collector. + auto new_line_level = LineLevel::High; + for(auto port : ports_) { + new_line_level = LineLevel(bool(new_line_level) & bool(port->get_output(line))); } - // post an update only if one occurred - if(new_line_level != line_levels_[line]) { - line_levels_[line] = new_line_level; + // Post an update only if one occurred. + const auto index = size_t(line); + if(new_line_level != line_levels_[index]) { + line_levels_[index] = new_line_level; - for(std::weak_ptr port : ports_) { - std::shared_ptr locked_port = port.lock(); - if(locked_port) { - locked_port->set_input(line, new_line_level); - } + for(auto port : ports_) { + port->set_input(line, new_line_level); } } } @@ -66,14 +69,15 @@ void Bus::set_line_output_did_change(Line line) { // MARK: - The debug port void DebugPort::set_input(Line line, LineLevel value) { - input_levels_[line] = value; + const auto index = size_t(line); + input_levels_[index] = value; - std::cout << "[Bus] " << StringForLine(line) << " is " << (value ? "high" : "low"); + std::cout << "[Bus] " << to_string(line) << " is " << to_string(value); if(!incoming_count_) { - incoming_count_ = (!input_levels_[Line::Clock] && !input_levels_[Line::Data]) ? 8 : 0; + incoming_count_ = (!input_levels_[size_t(Line::Clock)] && !input_levels_[size_t(Line::Data)]) ? 8 : 0; } else { if(line == Line::Clock && value) { - incoming_byte_ = (incoming_byte_ >> 1) | (input_levels_[Line::Data] ? 0x80 : 0x00); + incoming_byte_ = (incoming_byte_ >> 1) | (input_levels_[size_t(Line::Data)] ? 0x80 : 0x00); } incoming_count_--; if(incoming_count_ == 0) std::cout << "[Bus] Observed value " << std::hex << int(incoming_byte_); diff --git a/Machines/Commodore/SerialBus.hpp b/Machines/Commodore/SerialBus.hpp index 25dabd522e..c8bdfd4e43 100644 --- a/Machines/Commodore/SerialBus.hpp +++ b/Machines/Commodore/SerialBus.hpp @@ -23,7 +23,7 @@ enum class Line { }; const char *to_string(Line line); -enum class LineLevel: bool { +enum LineLevel: bool { High = true, Low = false }; @@ -44,29 +44,29 @@ void attach(Port &port, Bus &bus); levels to every port. */ class Bus { - public: - Bus() : line_levels_{ - LineLevel::High, - LineLevel::High, - LineLevel::High, - LineLevel::High, - LineLevel::High - } {} - - /*! - Adds the supplied port to the bus. - */ - void add_port(Port &port); - - /*! - Communicates to the bus that one of its attached port has changed its output level for the given line. - The bus will therefore recalculate bus state and propagate as necessary. - */ - void set_line_output_did_change(Line line); - - private: - LineLevel line_levels_[5]; - std::vector ports_; +public: + Bus() : line_levels_{ + LineLevel::High, + LineLevel::High, + LineLevel::High, + LineLevel::High, + LineLevel::High + } {} + + /*! + Adds the supplied port to the bus. + */ + void add_port(Port &port); + + /*! + Communicates to the bus that one of its attached port has changed its output level for the given line. + The bus will therefore recalculate bus state and propagate as necessary. + */ + void set_line_output_did_change(Line line); + +private: + LineLevel line_levels_[5]; + std::vector ports_; }; /*! @@ -74,63 +74,63 @@ class Bus { expects to be subclassed in order for specific port-housing devices to deal with input. */ class Port { - public: - Port() : line_levels_{ - LineLevel::High, - LineLevel::High, - LineLevel::High, - LineLevel::High, - LineLevel::High - } {} - virtual ~Port() = default; - - /*! - Sets the current level of an output line on this serial port. - */ - void set_output(Line line, LineLevel level) { - if(line_levels_[size_t(line)] != level) { - line_levels_[size_t(line)] = level; - if(serial_bus_) serial_bus_->set_line_output_did_change(line); - } +public: + Port() : line_levels_{ + LineLevel::High, + LineLevel::High, + LineLevel::High, + LineLevel::High, + LineLevel::High + } {} + virtual ~Port() = default; + + /*! + Sets the current level of an output line on this serial port. + */ + void set_output(Line line, LineLevel level) { + if(line_levels_[size_t(line)] != level) { + line_levels_[size_t(line)] = level; + if(serial_bus_) serial_bus_->set_line_output_did_change(line); } - - /*! - Gets the previously set level of an output line. - */ - LineLevel get_output(Line line) { - return line_levels_[size_t(line)]; - } - - /*! - Called by the bus to signal a change in any input line level. Subclasses should implement this. - */ - virtual void set_input(Line line, LineLevel value) = 0; - - /*! - Sets the supplied serial bus as that to which line levels will be communicated. - */ - inline void set_bus(Bus &serial_bus) { - serial_bus_ = &serial_bus; - } - - private: - Bus *serial_bus_ = nullptr; - LineLevel line_levels_[5]; + } + + /*! + Gets the previously set level of an output line. + */ + LineLevel get_output(Line line) { + return line_levels_[size_t(line)]; + } + + /*! + Called by the bus to signal a change in any input line level. Subclasses should implement this. + */ + virtual void set_input(Line line, LineLevel value) = 0; + + /*! + Sets the supplied serial bus as that to which line levels will be communicated. + */ + inline void set_bus(Bus &serial_bus) { + serial_bus_ = &serial_bus; + } + +private: + Bus *serial_bus_ = nullptr; + LineLevel line_levels_[5]; }; /*! A debugging port, which makes some attempt to log bus activity. Incomplete. TODO: complete. */ class DebugPort: public Port { - public: - void set_input(Line line, LineLevel value); +public: + void set_input(Line line, LineLevel value); - DebugPort() : incoming_count_(0) {} + DebugPort() : incoming_count_(0) {} - private: - uint8_t incoming_byte_; - int incoming_count_; - LineLevel input_levels_[5]; +private: + uint8_t incoming_byte_; + int incoming_count_; + LineLevel input_levels_[5]; }; } diff --git a/Machines/Commodore/Vic-20/Vic20.cpp b/Machines/Commodore/Vic-20/Vic20.cpp index e7f9e9eb92..1a6982345a 100644 --- a/Machines/Commodore/Vic-20/Vic20.cpp +++ b/Machines/Commodore/Vic-20/Vic20.cpp @@ -105,14 +105,13 @@ class UserPortVIA: public MOS::MOS6522::IRQDelegatePortHandler { void set_port_output(const MOS::MOS6522::Port port, const uint8_t value, uint8_t) { // Line 7 of port A is inverted and output as serial ATN. if(!port) { - std::shared_ptr<::Commodore::Serial::Port> serialPort = serial_port_.lock(); - if(serialPort) serialPort->set_output(::Commodore::Serial::Line::Attention, (::Commodore::Serial::LineLevel)!(value&0x80)); + serial_port_->set_output(Serial::Line::Attention, Serial::LineLevel(!(value&0x80))); } } /// Sets @serial_port as this VIA's connection to the serial bus. - void set_serial_port(std::shared_ptr<::Commodore::Serial::Port> serial_port) { - serial_port_ = serial_port; + void set_serial_port(Serial::Port &serial_port) { + serial_port_ = &serial_port; } /// Sets @tape as the tape player connected to this VIA. @@ -122,7 +121,7 @@ class UserPortVIA: public MOS::MOS6522::IRQDelegatePortHandler { private: uint8_t port_a_; - std::weak_ptr<::Commodore::Serial::Port> serial_port_; + Serial::Port *serial_port_ = nullptr; std::shared_ptr tape_; }; @@ -171,14 +170,11 @@ class KeyboardVIA: public MOS::MOS6522::IRQDelegatePortHandler { /// Called by the 6522 to set control line output. Which affects the serial port. void set_control_line_output(const MOS::MOS6522::Port port, const MOS::MOS6522::Line line, const bool value) { if(line == MOS::MOS6522::Line::Two) { - std::shared_ptr<::Commodore::Serial::Port> serialPort = serial_port_.lock(); - if(serialPort) { - // CB2 is inverted to become serial data; CA2 is inverted to become serial clock - if(port == MOS::MOS6522::Port::A) - serialPort->set_output(::Commodore::Serial::Line::Clock, (::Commodore::Serial::LineLevel)!value); - else - serialPort->set_output(::Commodore::Serial::Line::Data, (::Commodore::Serial::LineLevel)!value); - } + // CB2 is inverted to become serial data; CA2 is inverted to become serial clock + if(port == MOS::MOS6522::Port::A) + serial_port_->set_output(Serial::Line::Clock, Serial::LineLevel(!value)); + else + serial_port_->set_output(Serial::Line::Data, Serial::LineLevel(!value)); } } @@ -190,15 +186,15 @@ class KeyboardVIA: public MOS::MOS6522::IRQDelegatePortHandler { } /// Sets the serial port to which this VIA is connected. - void set_serial_port(std::shared_ptr<::Commodore::Serial::Port> serialPort) { - serial_port_ = serialPort; + void set_serial_port(Serial::Port &port) { + serial_port_ = &port; } private: uint8_t port_b_; uint8_t columns_[8]; uint8_t activation_mask_; - std::weak_ptr<::Commodore::Serial::Port> serial_port_; + Serial::Port *serial_port_ = nullptr; }; /*! @@ -208,17 +204,16 @@ class SerialPort : public ::Commodore::Serial::Port { public: /// Receives an input change from the base serial port class, and communicates it to the user-port VIA. void set_input(const ::Commodore::Serial::Line line, const ::Commodore::Serial::LineLevel level) { - std::shared_ptr userPortVIA = user_port_via_.lock(); - if(userPortVIA) userPortVIA->set_serial_line_state(line, bool(level)); + if(user_port_via_) user_port_via_->set_serial_line_state(line, bool(level)); } /// Sets the user-port VIA with which this serial port communicates. - void set_user_port_via(const std::shared_ptr userPortVIA) { - user_port_via_ = userPortVIA; + void set_user_port_via(UserPortVIA &via) { + user_port_via_ = &via; } private: - std::weak_ptr user_port_via_; + UserPortVIA *user_port_via_ = nullptr; }; /*! @@ -293,32 +288,28 @@ class ConcreteMachine: ConcreteMachine(const Analyser::Static::Commodore::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) : m6502_(*this), mos6560_(mos6560_bus_handler_), - user_port_via_port_handler_(new UserPortVIA), - keyboard_via_port_handler_(new KeyboardVIA), - serial_port_(new SerialPort), - serial_bus_(new ::Commodore::Serial::Bus), - user_port_via_(*user_port_via_port_handler_), - keyboard_via_(*keyboard_via_port_handler_), + user_port_via_(user_port_via_port_handler_), + keyboard_via_(keyboard_via_port_handler_), tape_(new Storage::Tape::BinaryTapePlayer(1022727)) { // communicate the tape to the user-port VIA - user_port_via_port_handler_->set_tape(tape_); + user_port_via_port_handler_.set_tape(tape_); // wire up the serial bus and serial port - Commodore::Serial::AttachPortAndBus(serial_port_, serial_bus_); + Commodore::Serial::attach(serial_port_, serial_bus_); // wire up 6522s and serial port - user_port_via_port_handler_->set_serial_port(serial_port_); - keyboard_via_port_handler_->set_serial_port(serial_port_); - serial_port_->set_user_port_via(user_port_via_port_handler_); + user_port_via_port_handler_.set_serial_port(serial_port_); + keyboard_via_port_handler_.set_serial_port(serial_port_); + serial_port_.set_user_port_via(user_port_via_port_handler_); // wire up the 6522s, tape and machine - user_port_via_port_handler_->set_interrupt_delegate(this); - keyboard_via_port_handler_->set_interrupt_delegate(this); + user_port_via_port_handler_.set_interrupt_delegate(this); + keyboard_via_port_handler_.set_interrupt_delegate(this); tape_->set_delegate(this); tape_->set_clocking_hint_observer(this); // Install a joystick. - joysticks_.emplace_back(new Joystick(*user_port_via_port_handler_, *keyboard_via_port_handler_)); + joysticks_.emplace_back(new Joystick(user_port_via_port_handler_, keyboard_via_port_handler_)); ROM::Request request(ROM::Name::Vic20BASIC); ROM::Name kernel, character; @@ -472,7 +463,7 @@ class ConcreteMachine: void set_key_state(const uint16_t key, const bool is_pressed) final { if(key < 0xfff0) { - keyboard_via_port_handler_->set_key_state(key, is_pressed); + keyboard_via_port_handler_.set_key_state(key, is_pressed); } else { switch(key) { case KeyRestore: @@ -480,8 +471,8 @@ class ConcreteMachine: break; #define ShiftedMap(source, target) \ case source: \ - keyboard_via_port_handler_->set_key_state(KeyLShift, is_pressed); \ - keyboard_via_port_handler_->set_key_state(target, is_pressed); \ + keyboard_via_port_handler_.set_key_state(KeyLShift, is_pressed); \ + keyboard_via_port_handler_.set_key_state(target, is_pressed); \ break; ShiftedMap(KeyUp, KeyDown); @@ -496,7 +487,7 @@ class ConcreteMachine: } void clear_all_keys() final { - keyboard_via_port_handler_->clear_all_keys(); + keyboard_via_port_handler_.clear_all_keys(); } const std::vector> &get_joysticks() final { @@ -741,10 +732,10 @@ class ConcreteMachine: Cycles cycles_since_mos6560_update_; Vic6560BusHandler mos6560_bus_handler_; MOS::MOS6560::MOS6560 mos6560_; - std::shared_ptr user_port_via_port_handler_; - std::shared_ptr keyboard_via_port_handler_; - std::shared_ptr serial_port_; - std::shared_ptr<::Commodore::Serial::Bus> serial_bus_; + UserPortVIA user_port_via_port_handler_; + KeyboardVIA keyboard_via_port_handler_; + SerialPort serial_port_; + Serial::Bus serial_bus_; MOS::MOS6522::MOS6522 user_port_via_; MOS::MOS6522::MOS6522 keyboard_via_; @@ -760,7 +751,7 @@ class ConcreteMachine: } // Disk - std::shared_ptr<::Commodore::C1540::Machine> c1540_; + std::unique_ptr<::Commodore::C1540::Machine> c1540_; }; } From 570f1caa8f0efb51fe49f8c3fc404ddd967f311d Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 28 Dec 2024 21:49:04 -0500 Subject: [PATCH 059/103] Attempt also to integrate a C1541. --- Machines/Commodore/Plus4/Plus4.cpp | 69 ++++++++++++++++++++++++----- Machines/Commodore/SerialBus.hpp | 6 +-- Machines/Commodore/Vic-20/Vic20.cpp | 2 +- 3 files changed, 63 insertions(+), 14 deletions(-) diff --git a/Machines/Commodore/Plus4/Plus4.cpp b/Machines/Commodore/Plus4/Plus4.cpp index 5d0ea1ddb7..3c5a3b59a1 100644 --- a/Machines/Commodore/Plus4/Plus4.cpp +++ b/Machines/Commodore/Plus4/Plus4.cpp @@ -19,6 +19,8 @@ #include "../../../Analyser/Static/Commodore/Target.hpp" #include "../../../Storage/Tape/Tape.hpp" +#include "../SerialBus.hpp" +#include "../1540/C1540.hpp" using namespace Commodore::Plus4; @@ -100,6 +102,20 @@ class Timers { Interrupts &interrupts_; }; +class SerialPort: public Commodore::Serial::Port { +public: + void set_input(Commodore::Serial::Line line, Commodore::Serial::LineLevel value) override { + levels_[size_t(line)] = value; + } + + Commodore::Serial::LineLevel level(Commodore::Serial::Line line) const { + return levels_[size_t(line)]; + } + +private: + Commodore::Serial::LineLevel levels_[5]; +}; + class ConcreteMachine: public BusController, public CPU::MOS6502::BusHandler, @@ -118,12 +134,17 @@ class ConcreteMachine: const auto clock = clock_rate(false); media_divider_ = Cycles(clock); set_clock_rate(clock); - tape_player_ = std::make_unique(clock); + + // TODO: decide whether to attach a 1541 for real. + const bool has_c1541 = true; const auto kernel = ROM::Name::Plus4KernelPALv5; const auto basic = ROM::Name::Plus4BASIC; + ROM::Request request = ROM::Request(basic) && ROM::Request(kernel); + if(has_c1541) { + request = request && Commodore::C1540::Machine::rom_request(Commodore::C1540::Personality::C1541); + } - const ROM::Request request = ROM::Request(basic) && ROM::Request(kernel); auto roms = rom_fetcher(request); if(!request.validate(roms)) { throw ROMMachine::Error::MissingROMs; @@ -138,6 +159,14 @@ class ConcreteMachine: video_map_.page(ram_.data()); + if(has_c1541) { + c1541_ = std::make_unique(Commodore::C1540::Personality::C1541, roms); + c1541_->set_serial_bus(serial_bus_); + serial_port_.set_bus(serial_bus_); + } + + tape_player_ = std::make_unique(clock); + insert_media(target.media); printf("Loading command is: %s\n", target.loading_command.c_str()); } @@ -159,6 +188,11 @@ class ConcreteMachine: video_.run_for(length); tape_player_->run_for(length); + if(c1541_) { + c1541_cycles_ += length; + c1541_->run_for(c1541_cycles_.divide(media_divider_)); + } + if(operation == CPU::MOS6502::BusOperation::Ready) { return length; } @@ -179,9 +213,11 @@ class ConcreteMachine: if(isReadOperation(operation)) { if(!address) { *value = io_direction_; -// printf("Read data direction: %02x\n", *value); } else { - const uint8_t all_inputs = (tape_player_->input() ? 0x00 : 0x10) | 0xe0; + const uint8_t all_inputs = + (tape_player_->input() ? 0x00 : 0x10) | + (serial_port_.level(Commodore::Serial::Line::Data) ? 0x80 : 0x00) | + (serial_port_.level(Commodore::Serial::Line::Clock) ? 0x40 : 0x00); *value = (io_direction_ & io_output_) | ((~io_direction_) & all_inputs); @@ -190,13 +226,18 @@ class ConcreteMachine: } else { if(!address) { io_direction_ = *value; -// printf("Set data direction: %02x\n", *value); } else { io_output_ = *value; - printf("[%04x] Out: %02x\n", m6502_.value_of(CPU::MOS6502::Register::ProgramCounter), *value); -// tape_player_->set_motor_control(*value & 0x08); - tape_player_->set_motor_control(!(*value & 0x08)); } + + const auto output = io_output_ & ~io_direction_; + tape_player_->set_motor_control(!(output & 0x08)); + serial_port_.set_output( + Commodore::Serial::Line::Data, Commodore::Serial::LineLevel(~output & 0x01)); + serial_port_.set_output( + Commodore::Serial::Line::Clock, Commodore::Serial::LineLevel(~output & 0x02)); + serial_port_.set_output( + Commodore::Serial::Line::Attention, Commodore::Serial::LineLevel(~output & 0x04)); } // printf("%04x: %02x %c\n", address, *value, isReadOperation(operation) ? 'r' : 'w'); @@ -372,6 +413,10 @@ class ConcreteMachine: tape_player_->set_tape(media.tapes[0]); } + if(!media.disks.empty() && c1541_) { + c1541_->set_disk(media.disks[0]); + } + return true; } @@ -404,12 +449,16 @@ class ConcreteMachine: std::array key_states_{}; uint8_t keyboard_latch_ = 0xff; - Cycles media_divider_; + Cycles media_divider_, c1541_cycles_; + std::unique_ptr c1541_; + Commodore::Serial::Bus serial_bus_; + SerialPort serial_port_; + std::unique_ptr tape_player_; uint8_t io_direction_ = 0x00, io_output_ = 0x00; }; - } + std::unique_ptr Machine::Plus4( const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher diff --git a/Machines/Commodore/SerialBus.hpp b/Machines/Commodore/SerialBus.hpp index c8bdfd4e43..40be0f4b2d 100644 --- a/Machines/Commodore/SerialBus.hpp +++ b/Machines/Commodore/SerialBus.hpp @@ -56,13 +56,13 @@ class Bus { /*! Adds the supplied port to the bus. */ - void add_port(Port &port); + void add_port(Port &); /*! Communicates to the bus that one of its attached port has changed its output level for the given line. The bus will therefore recalculate bus state and propagate as necessary. */ - void set_line_output_did_change(Line line); + void set_line_output_did_change(Line); private: LineLevel line_levels_[5]; @@ -97,7 +97,7 @@ class Port { /*! Gets the previously set level of an output line. */ - LineLevel get_output(Line line) { + LineLevel get_output(Line line) const { return line_levels_[size_t(line)]; } diff --git a/Machines/Commodore/Vic-20/Vic20.cpp b/Machines/Commodore/Vic-20/Vic20.cpp index 1a6982345a..42f6a13f24 100644 --- a/Machines/Commodore/Vic-20/Vic20.cpp +++ b/Machines/Commodore/Vic-20/Vic20.cpp @@ -352,7 +352,7 @@ class ConcreteMachine: if(target.has_c1540) { // construct the 1540 - c1540_ = std::make_unique<::Commodore::C1540::Machine>(Commodore::C1540::Personality::C1540, roms); + c1540_ = std::make_unique(C1540::Personality::C1540, roms); // attach it to the serial bus c1540_->set_serial_bus(serial_bus_); From da6efe52ff90e62e80f1113d644bab803007be36 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 28 Dec 2024 22:22:28 -0500 Subject: [PATCH 060/103] Accept implicit bool, eliminate rom name repetition. --- .../Commodore/1540/Implementation/C1540.cpp | 37 +++++++++---------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/Machines/Commodore/1540/Implementation/C1540.cpp b/Machines/Commodore/1540/Implementation/C1540.cpp index 9a1d8ef95b..ee7038f854 100644 --- a/Machines/Commodore/1540/Implementation/C1540.cpp +++ b/Machines/Commodore/1540/Implementation/C1540.cpp @@ -16,13 +16,19 @@ using namespace Commodore::C1540; -ROM::Request Machine::rom_request(Personality personality) { +namespace { +ROM::Name rom_name(Personality personality) { switch(personality) { default: - case Personality::C1540: return ROM::Request(ROM::Name::Commodore1540); - case Personality::C1541: return ROM::Request(ROM::Name::Commodore1541); + case Personality::C1540: return ROM::Name::Commodore1540; + case Personality::C1541: return ROM::Name::Commodore1541; } } +} + +ROM::Request Machine::rom_request(Personality personality) { + return ROM::Request(rom_name(personality)); +} MachineBase::MachineBase(Personality personality, const ROM::Map &roms) : Storage::Disk::Controller(1000000), @@ -30,34 +36,27 @@ MachineBase::MachineBase(Personality personality, const ROM::Map &roms) : serial_port_VIA_port_handler_(serial_port_VIA_), drive_VIA_(drive_VIA_port_handler_), serial_port_VIA_(serial_port_VIA_port_handler_) { - // attach the serial port to its VIA and vice versa + // Attach the serial port to its VIA and vice versa. serial_port_.set_serial_port_via(serial_port_VIA_port_handler_); serial_port_VIA_port_handler_.set_serial_port(serial_port_); - // set this instance as the delegate to receive interrupt requests from both VIAs + // Set this instance as the delegate to receive interrupt requests from both VIAs. serial_port_VIA_port_handler_.set_interrupt_delegate(this); drive_VIA_port_handler_.set_interrupt_delegate(this); drive_VIA_port_handler_.set_delegate(this); - // set a bit rate + // Set a bit rate. set_expected_bit_length(Storage::Encodings::CommodoreGCR::length_of_a_bit_in_time_zone(3)); - // attach the only drive there is + // Attach the only drive there is. emplace_drive(1000000, 300, 2); set_drive(1); - ROM::Name rom_name; - switch(personality) { - default: - case Personality::C1540: rom_name = ROM::Name::Commodore1540; break; - case Personality::C1541: rom_name = ROM::Name::Commodore1541; break; - } - - auto rom = roms.find(rom_name); + const auto rom = roms.find(rom_name(personality)); if(rom == roms.end()) { throw ROMMachine::Error::MissingROMs; } - std::memcpy(rom_, roms.find(rom_name)->second.data(), std::min(sizeof(rom_), roms.find(rom_name)->second.size())); + std::memcpy(rom_, rom->second.data(), std::min(sizeof(rom_), rom->second.size())); } Machine::Machine(Personality personality, const ROM::Map &roms) : @@ -242,7 +241,7 @@ void DriveVIA::set_port_output(MOS::MOS6522::Port port, uint8_t value, uint8_t) if(port) { if(previous_port_b_output_ != value) { // record drive motor state - drive_motor_ = !!(value&4); + drive_motor_ = value&4; // check for a head step int step_difference = ((value&3) - (previous_port_b_output_&3))&3; @@ -257,7 +256,7 @@ void DriveVIA::set_port_output(MOS::MOS6522::Port port, uint8_t value, uint8_t) } // post the LED status - if(observer_) observer_->set_led_status("Drive", !!(value&8)); + if(observer_) observer_->set_led_status("Drive", value&8); previous_port_b_output_ = value; } @@ -268,7 +267,7 @@ void DriveVIA::set_activity_observer(Activity::Observer *observer) { observer_ = observer; if(observer) { observer->register_led("Drive"); - observer->set_led_status("Drive", !!(previous_port_b_output_&8)); + observer->set_led_status("Drive", previous_port_b_output_&8); } } From 34938e8c6231fc76c4d4495d37e060938106869c Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 28 Dec 2024 23:07:01 -0500 Subject: [PATCH 061/103] Fully connect serial port. --- Analyser/Static/Commodore/StaticAnalyser.cpp | 4 ---- .../Commodore/1540/Implementation/C1540.cpp | 16 +++++++-------- Machines/Commodore/Plus4/Plus4.cpp | 20 +++++++++++-------- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/Analyser/Static/Commodore/StaticAnalyser.cpp b/Analyser/Static/Commodore/StaticAnalyser.cpp index 52c3bb73bc..066a0c105e 100644 --- a/Analyser/Static/Commodore/StaticAnalyser.cpp +++ b/Analyser/Static/Commodore/StaticAnalyser.cpp @@ -209,10 +209,6 @@ Analyser::Static::TargetList Analyser::Static::Commodore::GetTargets( // Inspect discovered files to try to divine machine and memory model. auto vic_memory_model = Target::MemoryModel::Unexpanded; - if(files.size() > 1) { - printf(""); - } - auto it = files.begin(); while(it != files.end()) { const auto &file = *it; diff --git a/Machines/Commodore/1540/Implementation/C1540.cpp b/Machines/Commodore/1540/Implementation/C1540.cpp index ee7038f854..6a1b066c16 100644 --- a/Machines/Commodore/1540/Implementation/C1540.cpp +++ b/Machines/Commodore/1540/Implementation/C1540.cpp @@ -153,11 +153,11 @@ void MachineBase::process_index_hole() {} // MARK: - Drive VIA delegate -void MachineBase::drive_via_did_step_head(void *, int direction) { +void MachineBase::drive_via_did_step_head(void *, const int direction) { get_drive().step(Storage::Disk::HeadPosition(direction, 2)); } -void MachineBase::drive_via_did_set_data_density(void *, int density) { +void MachineBase::drive_via_did_set_data_density(void *, const int density) { set_expected_bit_length(Storage::Encodings::CommodoreGCR::length_of_a_bit_in_time_zone(unsigned(density))); } @@ -240,22 +240,22 @@ void DriveVIA::set_control_line_output(MOS::MOS6522::Port port, MOS::MOS6522::Li void DriveVIA::set_port_output(MOS::MOS6522::Port port, uint8_t value, uint8_t) { if(port) { if(previous_port_b_output_ != value) { - // record drive motor state + // Record drive motor state. drive_motor_ = value&4; - // check for a head step - int step_difference = ((value&3) - (previous_port_b_output_&3))&3; + // Check for a head step. + const int step_difference = ((value&3) - (previous_port_b_output_&3))&3; if(step_difference) { if(delegate_) delegate_->drive_via_did_step_head(this, (step_difference == 1) ? 1 : -1); } - // check for a change in density - int density_difference = (previous_port_b_output_^value) & (3 << 5); + // Check for a change in density. + const int density_difference = (previous_port_b_output_^value) & (3 << 5); if(density_difference && delegate_) { delegate_->drive_via_did_set_data_density(this, (value >> 5)&3); } - // post the LED status + // Post the LED status. if(observer_) observer_->set_led_status("Drive", value&8); previous_port_b_output_ = value; diff --git a/Machines/Commodore/Plus4/Plus4.cpp b/Machines/Commodore/Plus4/Plus4.cpp index 3c5a3b59a1..81b9953c85 100644 --- a/Machines/Commodore/Plus4/Plus4.cpp +++ b/Machines/Commodore/Plus4/Plus4.cpp @@ -162,7 +162,7 @@ class ConcreteMachine: if(has_c1541) { c1541_ = std::make_unique(Commodore::C1540::Personality::C1541, roms); c1541_->set_serial_bus(serial_bus_); - serial_port_.set_bus(serial_bus_); + Commodore::Serial::attach(serial_port_, serial_bus_); } tape_player_ = std::make_unique(clock); @@ -215,29 +215,33 @@ class ConcreteMachine: *value = io_direction_; } else { const uint8_t all_inputs = - (tape_player_->input() ? 0x00 : 0x10) | + (tape_player_->input() ? 0x10 : 0x00) | (serial_port_.level(Commodore::Serial::Line::Data) ? 0x80 : 0x00) | (serial_port_.level(Commodore::Serial::Line::Clock) ? 0x40 : 0x00); *value = (io_direction_ & io_output_) | ((~io_direction_) & all_inputs); - printf("[%04x] In: %02x\n", m6502_.value_of(CPU::MOS6502::Register::ProgramCounter), *value); +// printf("[%04x] In: %02x\n", m6502_.value_of(CPU::MOS6502::Register::ProgramCounter), *value); +// printf("Input: %02x\n", *value); } } else { if(!address) { io_direction_ = *value; +// printf("Direction: %02x\n", io_direction_); } else { io_output_ = *value; +// printf("'Output': %02x\n", io_output_); } - const auto output = io_output_ & ~io_direction_; - tape_player_->set_motor_control(!(output & 0x08)); + const auto output = io_output_ | ~io_direction_; +// printf("Visible output: %02x\n\n", uint8_t(output)); + tape_player_->set_motor_control(~output & 0x08); serial_port_.set_output( - Commodore::Serial::Line::Data, Commodore::Serial::LineLevel(~output & 0x01)); + Commodore::Serial::Line::Data, Commodore::Serial::LineLevel(output & 0x01)); serial_port_.set_output( - Commodore::Serial::Line::Clock, Commodore::Serial::LineLevel(~output & 0x02)); + Commodore::Serial::Line::Clock, Commodore::Serial::LineLevel(output & 0x02)); serial_port_.set_output( - Commodore::Serial::Line::Attention, Commodore::Serial::LineLevel(~output & 0x04)); + Commodore::Serial::Line::Attention, Commodore::Serial::LineLevel(output & 0x04)); } // printf("%04x: %02x %c\n", address, *value, isReadOperation(operation) ? 'r' : 'w'); From 918a8c5f8b8ef0abf24285822737b65972d5d16d Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 29 Dec 2024 08:41:37 -0500 Subject: [PATCH 062/103] Fix 1541 clocking, invert levels again. --- Machines/Commodore/Plus4/Plus4.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Machines/Commodore/Plus4/Plus4.cpp b/Machines/Commodore/Plus4/Plus4.cpp index 81b9953c85..2a337ba43a 100644 --- a/Machines/Commodore/Plus4/Plus4.cpp +++ b/Machines/Commodore/Plus4/Plus4.cpp @@ -189,7 +189,7 @@ class ConcreteMachine: tape_player_->run_for(length); if(c1541_) { - c1541_cycles_ += length; + c1541_cycles_ += length * Cycles(1'000'000); c1541_->run_for(c1541_cycles_.divide(media_divider_)); } @@ -237,11 +237,11 @@ class ConcreteMachine: // printf("Visible output: %02x\n\n", uint8_t(output)); tape_player_->set_motor_control(~output & 0x08); serial_port_.set_output( - Commodore::Serial::Line::Data, Commodore::Serial::LineLevel(output & 0x01)); + Commodore::Serial::Line::Data, Commodore::Serial::LineLevel(~output & 0x01)); serial_port_.set_output( - Commodore::Serial::Line::Clock, Commodore::Serial::LineLevel(output & 0x02)); + Commodore::Serial::Line::Clock, Commodore::Serial::LineLevel(~output & 0x02)); serial_port_.set_output( - Commodore::Serial::Line::Attention, Commodore::Serial::LineLevel(output & 0x04)); + Commodore::Serial::Line::Attention, Commodore::Serial::LineLevel(~output & 0x04)); } // printf("%04x: %02x %c\n", address, *value, isReadOperation(operation) ? 'r' : 'w'); From 0e663e1da822ddfe7ee67952d34fde4eb90ed3e7 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 29 Dec 2024 21:30:53 -0500 Subject: [PATCH 063/103] Add C1541 activity indicator. --- Machines/Commodore/Plus4/Plus4.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Machines/Commodore/Plus4/Plus4.cpp b/Machines/Commodore/Plus4/Plus4.cpp index 2a337ba43a..0c28cd3c2c 100644 --- a/Machines/Commodore/Plus4/Plus4.cpp +++ b/Machines/Commodore/Plus4/Plus4.cpp @@ -117,6 +117,7 @@ class SerialPort: public Commodore::Serial::Port { }; class ConcreteMachine: + public Activity::Source, public BusController, public CPU::MOS6502::BusHandler, public MachineTypes::MappedKeyboardMachine, @@ -371,6 +372,10 @@ class ConcreteMachine: } private: + void set_activity_observer(Activity::Observer *const observer) final { + if(c1541_) c1541_->set_activity_observer(observer); + } + CPU::MOS6502::Processor m6502_; void set_irq_line(bool active) override { From cb98297bb57ff4b03d6147136a39369688593079 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 29 Dec 2024 22:14:48 -0500 Subject: [PATCH 064/103] Add `const`s. --- Storage/Tape/Tape.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Storage/Tape/Tape.cpp b/Storage/Tape/Tape.cpp index e8f97b745b..ac157dffcc 100644 --- a/Storage/Tape/Tape.cpp +++ b/Storage/Tape/Tape.cpp @@ -12,7 +12,7 @@ using namespace Storage::Tape; // MARK: - Lifecycle -TapePlayer::TapePlayer(int input_clock_rate) : +TapePlayer::TapePlayer(const int input_clock_rate) : TimedEventLoop(input_clock_rate) {} @@ -129,7 +129,7 @@ void TapePlayer::process_next_event() { // MARK: - Binary Player -BinaryTapePlayer::BinaryTapePlayer(int input_clock_rate) : +BinaryTapePlayer::BinaryTapePlayer(const int input_clock_rate) : TapePlayer(input_clock_rate) {} @@ -138,7 +138,7 @@ ClockingHint::Preference BinaryTapePlayer::preferred_clocking() const { return TapePlayer::preferred_clocking(); } -void BinaryTapePlayer::set_motor_control(bool enabled) { +void BinaryTapePlayer::set_motor_control(const bool enabled) { if(motor_is_running_ != enabled) { motor_is_running_ = enabled; update_clocking_observer(); @@ -149,7 +149,7 @@ void BinaryTapePlayer::set_motor_control(bool enabled) { } } -void BinaryTapePlayer::set_activity_observer(Activity::Observer *observer) { +void BinaryTapePlayer::set_activity_observer(Activity::Observer *const observer) { observer_ = observer; if(observer) { observer->register_led("Tape motor"); @@ -173,7 +173,7 @@ void BinaryTapePlayer::run_for(const Cycles cycles) { if(motor_is_running_) TapePlayer::run_for(cycles); } -void BinaryTapePlayer::set_delegate(Delegate *delegate) { +void BinaryTapePlayer::set_delegate(Delegate *const delegate) { delegate_ = delegate; } From 33c2353107eff7e4344efd293ee9125a7d53a848 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 29 Dec 2024 22:21:02 -0500 Subject: [PATCH 065/103] Alter motor control detection. --- Machines/Commodore/Plus4/Plus4.cpp | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/Machines/Commodore/Plus4/Plus4.cpp b/Machines/Commodore/Plus4/Plus4.cpp index 0c28cd3c2c..39d5978214 100644 --- a/Machines/Commodore/Plus4/Plus4.cpp +++ b/Machines/Commodore/Plus4/Plus4.cpp @@ -200,8 +200,8 @@ class ConcreteMachine: // Perform actual access. if(address < 0x0002) { - // TODO: 0x0000: data directions for parallel IO; 1 = output. - // TODO: 0x0001: + // 0x0000: data directions for parallel IO; 1 = output. + // 0x0001: // b7 = serial data in; // b6 = serial clock in and cassette write; // b5 = [unconnected]; @@ -221,22 +221,17 @@ class ConcreteMachine: (serial_port_.level(Commodore::Serial::Line::Clock) ? 0x40 : 0x00); *value = (io_direction_ & io_output_) | - ((~io_direction_) & all_inputs); -// printf("[%04x] In: %02x\n", m6502_.value_of(CPU::MOS6502::Register::ProgramCounter), *value); -// printf("Input: %02x\n", *value); + (~io_direction_ & all_inputs); } } else { if(!address) { io_direction_ = *value; -// printf("Direction: %02x\n", io_direction_); } else { io_output_ = *value; -// printf("'Output': %02x\n", io_output_); } const auto output = io_output_ | ~io_direction_; -// printf("Visible output: %02x\n\n", uint8_t(output)); - tape_player_->set_motor_control(~output & 0x08); +// tape_player_->set_motor_control(~output & 0x08); serial_port_.set_output( Commodore::Serial::Line::Data, Commodore::Serial::LineLevel(~output & 0x01)); serial_port_.set_output( @@ -257,6 +252,7 @@ class ConcreteMachine: if(isReadOperation(operation)) { // printf("TODO: read @ %04x\n", address); if((address & 0xfff0) == 0xfd10) { + tape_player_->set_motor_control(true); *value = 0xff ^ 0x4; // Seems to detect the play button. } else { *value = 0xff; @@ -372,12 +368,12 @@ class ConcreteMachine: } private: + CPU::MOS6502::Processor m6502_; + void set_activity_observer(Activity::Observer *const observer) final { if(c1541_) c1541_->set_activity_observer(observer); } - CPU::MOS6502::Processor m6502_; - void set_irq_line(bool active) override { m6502_.set_irq_line(active); } From 0005229c1ee62abdd73c5313a6937f2ec9d9333a Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 29 Dec 2024 22:21:38 -0500 Subject: [PATCH 066/103] Improve header. --- Machines/Enterprise/Enterprise.cpp | 1 + Machines/PCCompatible/PCCompatible.cpp | 2 ++ Storage/Tape/Tape.hpp | 9 ++++----- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/Machines/Enterprise/Enterprise.cpp b/Machines/Enterprise/Enterprise.cpp index 325559d30b..b675a6a841 100644 --- a/Machines/Enterprise/Enterprise.cpp +++ b/Machines/Enterprise/Enterprise.cpp @@ -16,6 +16,7 @@ #include "../MachineTypes.hpp" #include "../Utility/Typer.hpp" +#include "../../Activity/Source.hpp" #include "../../Analyser/Static/Enterprise/Target.hpp" #include "../../ClockReceiver/JustInTime.hpp" #include "../../Outputs/Log.hpp" diff --git a/Machines/PCCompatible/PCCompatible.cpp b/Machines/PCCompatible/PCCompatible.cpp index 490b70dec8..c9c74ac362 100644 --- a/Machines/PCCompatible/PCCompatible.cpp +++ b/Machines/PCCompatible/PCCompatible.cpp @@ -17,6 +17,8 @@ #include "PIT.hpp" #include "RTC.hpp" +#include "../../Activity/Source.hpp" + #include "../../InstructionSets/x86/Decoder.hpp" #include "../../InstructionSets/x86/Flags.hpp" #include "../../InstructionSets/x86/Instruction.hpp" diff --git a/Storage/Tape/Tape.hpp b/Storage/Tape/Tape.hpp index 8771d577d9..ce7006e41c 100644 --- a/Storage/Tape/Tape.hpp +++ b/Storage/Tape/Tape.hpp @@ -13,11 +13,10 @@ #include "../TimedEventLoop.hpp" -#include "../../Activity/Source.hpp" +#include "../../Activity/Observer.hpp" #include "../TargetPlatforms.hpp" #include -#include namespace Storage::Tape { @@ -112,7 +111,7 @@ class TapePlayer: public TimedEventLoop, public ClockingHint::Source { bool has_tape() const; std::shared_ptr tape(); - void run_for(const Cycles cycles); + void run_for(Cycles); void run_for_input_pulse(); ClockingHint::Preference preferred_clocking() const override; @@ -148,12 +147,12 @@ class BinaryTapePlayer : public TapePlayer { void set_tape_output(bool); bool input() const; - void run_for(const Cycles cycles); + void run_for(Cycles); struct Delegate { virtual void tape_did_change_input(BinaryTapePlayer *) = 0; }; - void set_delegate(Delegate *delegate); + void set_delegate(Delegate *); ClockingHint::Preference preferred_clocking() const final; From 0751c518033176a1384f3a820584364be0e92f72 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 29 Dec 2024 22:34:32 -0500 Subject: [PATCH 067/103] Dispatch warning. --- OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/xcschemes/Clock Signal Kiosk.xcscheme | 2 +- .../xcshareddata/xcschemes/Clock Signal.xcscheme | 2 +- .../xcshareddata/xcschemes/Clock SignalTests.xcscheme | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 7eb396ec91..95e450767b 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -5483,7 +5483,7 @@ attributes = { BuildIndependentTargetsInParallel = YES; LastSwiftUpdateCheck = 0700; - LastUpgradeCheck = 1610; + LastUpgradeCheck = 1620; ORGANIZATIONNAME = "Thomas Harte"; TargetAttributes = { 4B055A691FAE763F0060FFFF = { diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal Kiosk.xcscheme b/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal Kiosk.xcscheme index 9bce73717a..eeb93d124b 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal Kiosk.xcscheme +++ b/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal Kiosk.xcscheme @@ -1,6 +1,6 @@ Date: Sun, 29 Dec 2024 22:36:14 -0500 Subject: [PATCH 068/103] Add missing header. --- Analyser/Static/Commodore/StaticAnalyser.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Analyser/Static/Commodore/StaticAnalyser.cpp b/Analyser/Static/Commodore/StaticAnalyser.cpp index 066a0c105e..094a9d4f56 100644 --- a/Analyser/Static/Commodore/StaticAnalyser.cpp +++ b/Analyser/Static/Commodore/StaticAnalyser.cpp @@ -20,6 +20,7 @@ #include #include +#include #include #include From 24b281d62565084a60bfef511988dda118b05dc8 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 30 Dec 2024 09:14:27 -0500 Subject: [PATCH 069/103] Use namespace; attempt to avoid false characters. --- Machines/Commodore/Plus4/Plus4.cpp | 38 ++++++++++++++---------------- Machines/Commodore/Plus4/Video.hpp | 6 +++-- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/Machines/Commodore/Plus4/Plus4.cpp b/Machines/Commodore/Plus4/Plus4.cpp index 39d5978214..fa90953534 100644 --- a/Machines/Commodore/Plus4/Plus4.cpp +++ b/Machines/Commodore/Plus4/Plus4.cpp @@ -22,6 +22,7 @@ #include "../SerialBus.hpp" #include "../1540/C1540.hpp" +using namespace Commodore; using namespace Commodore::Plus4; namespace { @@ -102,18 +103,18 @@ class Timers { Interrupts &interrupts_; }; -class SerialPort: public Commodore::Serial::Port { +class SerialPort: public Serial::Port { public: - void set_input(Commodore::Serial::Line line, Commodore::Serial::LineLevel value) override { + void set_input(const Serial::Line line, const Serial::LineLevel value) override { levels_[size_t(line)] = value; } - Commodore::Serial::LineLevel level(Commodore::Serial::Line line) const { + Serial::LineLevel level(const Serial::Line line) const { return levels_[size_t(line)]; } private: - Commodore::Serial::LineLevel levels_[5]; + Serial::LineLevel levels_[5]; }; class ConcreteMachine: @@ -143,7 +144,7 @@ class ConcreteMachine: const auto basic = ROM::Name::Plus4BASIC; ROM::Request request = ROM::Request(basic) && ROM::Request(kernel); if(has_c1541) { - request = request && Commodore::C1540::Machine::rom_request(Commodore::C1540::Personality::C1541); + request = request && C1540::Machine::rom_request(C1540::Personality::C1541); } auto roms = rom_fetcher(request); @@ -161,9 +162,9 @@ class ConcreteMachine: video_map_.page(ram_.data()); if(has_c1541) { - c1541_ = std::make_unique(Commodore::C1540::Personality::C1541, roms); + c1541_ = std::make_unique(C1540::Personality::C1541, roms); c1541_->set_serial_bus(serial_bus_); - Commodore::Serial::attach(serial_port_, serial_bus_); + Serial::attach(serial_port_, serial_bus_); } tape_player_ = std::make_unique(clock); @@ -217,8 +218,8 @@ class ConcreteMachine: } else { const uint8_t all_inputs = (tape_player_->input() ? 0x10 : 0x00) | - (serial_port_.level(Commodore::Serial::Line::Data) ? 0x80 : 0x00) | - (serial_port_.level(Commodore::Serial::Line::Clock) ? 0x40 : 0x00); + (serial_port_.level(Serial::Line::Data) ? 0x80 : 0x00) | + (serial_port_.level(Serial::Line::Clock) ? 0x40 : 0x00); *value = (io_direction_ & io_output_) | (~io_direction_ & all_inputs); @@ -232,12 +233,9 @@ class ConcreteMachine: const auto output = io_output_ | ~io_direction_; // tape_player_->set_motor_control(~output & 0x08); - serial_port_.set_output( - Commodore::Serial::Line::Data, Commodore::Serial::LineLevel(~output & 0x01)); - serial_port_.set_output( - Commodore::Serial::Line::Clock, Commodore::Serial::LineLevel(~output & 0x02)); - serial_port_.set_output( - Commodore::Serial::Line::Attention, Commodore::Serial::LineLevel(~output & 0x04)); + serial_port_.set_output(Serial::Line::Data, Serial::LineLevel(~output & 0x01)); + serial_port_.set_output(Serial::Line::Clock, Serial::LineLevel(~output & 0x02)); + serial_port_.set_output(Serial::Line::Attention, Serial::LineLevel(~output & 0x04)); } // printf("%04x: %02x %c\n", address, *value, isReadOperation(operation) ? 'r' : 'w'); @@ -425,8 +423,8 @@ class ConcreteMachine: return true; } - Commodore::Plus4::Pager map_; - Commodore::Plus4::Pager video_map_; + Plus4::Pager map_; + Plus4::Pager video_map_; std::array ram_; std::vector kernel_; std::vector basic_; @@ -439,7 +437,7 @@ class ConcreteMachine: // MARK: - MappedKeyboardMachine. MappedKeyboardMachine::KeyboardMapper *get_keyboard_mapper() override { - static Commodore::Plus4::KeyboardMapper keyboard_mapper_; + static Plus4::KeyboardMapper keyboard_mapper_; return &keyboard_mapper_; } @@ -455,8 +453,8 @@ class ConcreteMachine: uint8_t keyboard_latch_ = 0xff; Cycles media_divider_, c1541_cycles_; - std::unique_ptr c1541_; - Commodore::Serial::Bus serial_bus_; + std::unique_ptr c1541_; + Serial::Bus serial_bus_; SerialPort serial_port_; std::unique_ptr tape_player_; diff --git a/Machines/Commodore/Plus4/Video.hpp b/Machines/Commodore/Plus4/Video.hpp index 300737b611..40ff1c5588 100644 --- a/Machines/Commodore/Plus4/Video.hpp +++ b/Machines/Commodore/Plus4/Video.hpp @@ -295,7 +295,7 @@ struct Video { // Current thinking: these should probably go afterwards, with an equivalent of pixelshiftreg // adding further delay? next_attribute_.write(shifter_.read<1>()); - next_character_.write(shifter_.read<0>()); + if(!bad_line()) next_character_.write(shifter_.read<0>()); next_cursor_.write( (flash_count_ & 0x10) && ( @@ -317,7 +317,9 @@ struct Video { if(bad_line2_) { shifter_.write<1>(pager_.read(address)); } else { - shifter_.write<0>(pager_.read(address + 0x400)); + const auto data = pager_.read(address + 0x400); + shifter_.write<0>(data); + if(bad_line()) next_character_.write(data); } } From d1eca5dc214bbb7b7f6f09808dc91fd5df996d49 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 30 Dec 2024 21:29:36 -0500 Subject: [PATCH 070/103] Expand mask. --- Machines/Commodore/Plus4/Video.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Machines/Commodore/Plus4/Video.hpp b/Machines/Commodore/Plus4/Video.hpp index 40ff1c5588..eb5209d588 100644 --- a/Machines/Commodore/Plus4/Video.hpp +++ b/Machines/Commodore/Plus4/Video.hpp @@ -714,7 +714,7 @@ struct Video { CounterReset = 0x10, UpdateDMAState = 0x20, - Mask = CounterReset | IncrementVerticalLine | IncrementVerticalSub | Flash | Latch, + Mask = CounterReset | IncrementVerticalLine | IncrementVerticalSub | Flash | Latch | UpdateDMAState, }; static constexpr int DelayEventSize = 6; uint64_t delayed_events_ = 0; From 60856b974b6d494efefd13cb9a8b063fa2448f47 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 30 Dec 2024 22:56:29 -0500 Subject: [PATCH 071/103] Add wiring for audio. --- Machines/Commodore/Plus4/Audio.hpp | 87 +++++++++++++++++++ Machines/Commodore/Plus4/Plus4.cpp | 52 ++++++++++- .../Clock Signal.xcodeproj/project.pbxproj | 2 + 3 files changed, 140 insertions(+), 1 deletion(-) create mode 100644 Machines/Commodore/Plus4/Audio.hpp diff --git a/Machines/Commodore/Plus4/Audio.hpp b/Machines/Commodore/Plus4/Audio.hpp new file mode 100644 index 0000000000..4bec24df92 --- /dev/null +++ b/Machines/Commodore/Plus4/Audio.hpp @@ -0,0 +1,87 @@ +// +// Audio.hpp +// Clock Signal +// +// Created by Thomas Harte on 30/12/2024. +// Copyright © 2024 Thomas Harte. All rights reserved. +// + +#pragma once + +#include "../../../Outputs/Speaker/Implementation/BufferSource.hpp" +#include "../../../Concurrency/AsyncTaskQueue.hpp" + +namespace Commodore::Plus4 { + +// PAL: / 160 +// NTSC: / 128 + +// 111860.78125 = NTSC +// 110840.46875 = PAL + +class Audio: public Outputs::Speaker::BufferSource { +public: + Audio(Concurrency::AsyncTaskQueue &audio_queue) : + audio_queue_(audio_queue) {} + + template + void apply_samples(std::size_t size, Outputs::Speaker::MonoSample *const target) { + for(size_t c = 0; c < size; c++) { + + Outputs::Speaker::apply( + target[c], + Outputs::Speaker::MonoSample( + ((r_ + c) & 128) ? external_volume_ : -external_volume_ + )); + } + r_ += size; + } + + void set_sample_volume_range(const std::int16_t range) { + external_volume_ = range; + } + + bool is_zero_level() const { + return !(sound1_on_ || sound2_on_ || sound2_noise_on_) || !volume_; + } + + template void set_frequency_low(uint8_t value) { + audio_queue_.enqueue([this, value] { + frequencies_[channel] = (frequencies_[channel] & 0xff00) | value; + }); + } + + template void set_frequency_high(uint8_t value) { + audio_queue_.enqueue([this, value] { + frequencies_[channel] = (frequencies_[channel] & 0x00ff) | ((value&3) << 8); + }); + } + + void set_constrol(uint8_t value) { + audio_queue_.enqueue([this, value] { + volume_ = value & 0xf; + sound1_on_ = value & 0x10; + sound2_on_ = value & 0x20; + sound2_noise_on_ = value & 0x40; + sound_dc_ = value & 0x80; + }); + } + +private: + // Calling-thread state. + Concurrency::AsyncTaskQueue &audio_queue_; + + // Audio-thread state. + int16_t external_volume_ = 0; + int frequencies_[2]{}; + + bool sound1_on_ = false; + bool sound2_on_ = false; + bool sound2_noise_on_ = false; + bool sound_dc_ = false; + uint8_t volume_ = 0; + + int r_ = 0; +}; + +} diff --git a/Machines/Commodore/Plus4/Plus4.cpp b/Machines/Commodore/Plus4/Plus4.cpp index fa90953534..9b12523dae 100644 --- a/Machines/Commodore/Plus4/Plus4.cpp +++ b/Machines/Commodore/Plus4/Plus4.cpp @@ -8,6 +8,7 @@ #include "Plus4.hpp" +#include "Audio.hpp" #include "Interrupts.hpp" #include "Keyboard.hpp" #include "Pager.hpp" @@ -17,6 +18,7 @@ #include "../../Utility/MemoryFuzzer.hpp" #include "../../../Processors/6502/6502.hpp" #include "../../../Analyser/Static/Commodore/Target.hpp" +#include "../../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp" #include "../../../Storage/Tape/Tape.hpp" #include "../SerialBus.hpp" @@ -121,6 +123,7 @@ class ConcreteMachine: public Activity::Source, public BusController, public CPU::MOS6502::BusHandler, + public MachineTypes::AudioProducer, public MachineTypes::MappedKeyboardMachine, public MachineTypes::TimedMachine, public MachineTypes::ScanProducer, @@ -131,11 +134,14 @@ class ConcreteMachine: m6502_(*this), interrupts_(*this), timers_(interrupts_), - video_(video_map_, interrupts_) + video_(video_map_, interrupts_), + audio_(audio_queue_), + speaker_(audio_) { const auto clock = clock_rate(false); media_divider_ = Cycles(clock); set_clock_rate(clock); + speaker_.set_input_rate(float(clock) / 32.0f); // TODO: decide whether to attach a 1541 for real. const bool has_c1541 = true; @@ -173,6 +179,10 @@ class ConcreteMachine: printf("Loading command is: %s\n", target.loading_command.c_str()); } + ~ConcreteMachine() { + audio_queue_.flush(); + } + Cycles perform_bus_operation( const CPU::MOS6502::BusOperation operation, const uint16_t address, @@ -195,6 +205,8 @@ class ConcreteMachine: c1541_->run_for(c1541_cycles_.divide(media_divider_)); } + time_since_audio_update_ += length; + if(operation == CPU::MOS6502::BusOperation::Ready) { return length; } @@ -327,6 +339,22 @@ class ConcreteMachine: case 0xff07: video_.write<0xff07>(*value); break; case 0xff0c: video_.write<0xff0c>(*value); break; case 0xff0d: video_.write<0xff0d>(*value); break; + case 0xff0e: + update_audio(); + audio_.set_frequency_low<0>(*value); + break; + case 0xff0f: + update_audio(); + audio_.set_frequency_low<1>(*value); + break; + case 0xff10: + update_audio(); + audio_.set_frequency_high<1>(*value); + break; + case 0xff11: + update_audio(); + audio_.set_constrol(*value); + break; case 0xff12: ff12_ = *value & 0x3f; video_.write<0xff12>(*value); @@ -336,6 +364,9 @@ class ConcreteMachine: } else { page_video_ram(); } + + update_audio(); + audio_.set_frequency_high<0>(*value); break; case 0xff13: ff13_ = *value & 0xfe; @@ -368,6 +399,10 @@ class ConcreteMachine: private: CPU::MOS6502::Processor m6502_; + Outputs::Speaker::Speaker *get_speaker() override { + return &speaker_; + } + void set_activity_observer(Activity::Observer *const observer) final { if(c1541_) c1541_->set_activity_observer(observer); } @@ -411,6 +446,13 @@ class ConcreteMachine: m6502_.run_for(cycles); } + void flush_output(int outputs) override { + if(outputs & Output::Audio) { + update_audio(); + audio_queue_.perform(); + } + } + bool insert_media(const Analyser::Static::Media &media) final { if(!media.tapes.empty()) { tape_player_->set_tape(media.tapes[0]); @@ -435,6 +477,14 @@ class ConcreteMachine: Timers timers_; Video video_; + Concurrency::AsyncTaskQueue audio_queue_; + Audio audio_; + Cycles time_since_audio_update_; + Outputs::Speaker::PullLowpass