Merge pull request #1727 from TomHarte/MoreMO5

Further improve 6809; get MO5 to interactive BASIC.
This commit is contained in:
Thomas Harte authored and GitHub committed 2026-03-22 23:33:16 -04:00
commit f5b73c0bf4
13 files changed
+908 -75

No files matched your search

+187
View File
@@ -0,0 +1,187 @@
//
// 6821.hpp
// Clock Signal
//
// Created by Thomas Harte on 19/03/2026.
// Copyright © 2026 Thomas Harte. All rights reserved.
//
#ifndef _821_hpp
#define _821_hpp
namespace Motorola::MC6821 {
enum class Port {
A = 0,
B = 1,
};
enum class IRQ {
A,
B,
};
enum class Control {
CA1, CA2,
CB1, CB2,
};
// TODO: CB2/CA2 output.
template <typename PortHandlerT, int RS0Mask = 1, int RS1Mask = 2>
class MC6821 {
public:
MC6821(PortHandlerT &port_handler) : port_handler_(port_handler) {}
template <int address>
void write(const uint8_t value) {
static constexpr int port = bool(address & RS1Mask);
if constexpr (address & RS0Mask) {
static constexpr auto IRQMask = IRQ1 | IRQ2;
ports_[port].control = (ports_[port].control & IRQMask) | (value & ~IRQMask);
} else {
if(ports_[port].control & Flag::DataVisible) {
ports_[port].data = value;
} else {
ports_[port].direction = value;
}
const uint8_t output = ports_[port].output();
if(output != ports_[port].previous_output) {
ports_[port].previous_output = output;
port_handler_.template output<Port(port)>(output);
}
}
}
template <int address>
uint8_t read() {
static constexpr int port = bool(address & RS1Mask);
if constexpr(address & RS0Mask) {
const auto result = ports_[port].control;
ports_[port].clear_irq();
update_interrupts();
return result;
} else {
if(ports_[port].control & Flag::DataVisible) {
return ports_[port].input(port_handler_.template input<Port(port)>());
} else {
return ports_[port].direction;
}
}
}
void refresh() const {
refresh<Port::A>();
refresh<Port::B>();
}
template <Control control>
void set(const bool value) {
// Reject anything that isn't a change.
static constexpr int input = int(control);
if(inputs_[input] == value) {
return;
}
inputs_[input] = value;
port_handler_.template observe<control>(value);
static constexpr bool is_irq2 = control == Control::CB2 || control == Control::CA2; // 0 = IRQ1; 1 = IRQ2.
static constexpr int port = control == Control::CB2 || control == Control::CB1; // 0 = port A; 1 = port B.
// Reject any set to CB2 if it's in output mode.
if constexpr (is_irq2) {
if(ports_[port].control & Flag::C2IsOutput) {
return;
}
}
// Test whether change was in the triggering direction; if so update control bit and check for change
// to interrupt output.
static constexpr int direction = is_irq2 ? IRQ2Direction : IRQ1Direction;
if(value != bool(ports_[port].control & direction)) {
ports_[port].apply_irq(is_irq2 ? Flag::IRQ2 : Flag::IRQ1);
update_interrupts();
}
}
private:
PortHandlerT &port_handler_;
bool inputs_[4]{};
enum Flag: uint8_t {
DataVisible = 0b0000'0100,
C2IsOutput = 0b0010'0000,
IRQ1 = 0b1000'0000,
IRQ1Direction = 0b0000'0010,
EnableIRQ1 = 0b0000'0001,
IRQ2 = 0b0100'0000,
IRQ2Direction = 0b0001'0000,
EnableIRQ2 = 0b0000'1000,
};
struct {
uint8_t control = 0;
uint8_t data = 0;
uint8_t direction = 0; // Per bit: 0 = input; 1 = output.
void apply_irq(const uint8_t mask) {
control |= mask;
}
void clear_irq() {
control &= ~(IRQ1 | IRQ2);
}
bool irq() const {
static constexpr auto IRQ1Value = EnableIRQ1 | IRQ1;
static constexpr auto IRQ1Mask = IRQ1Value;
static constexpr auto IRQ2Value = EnableIRQ2 | IRQ2;
static constexpr auto IRQ2Mask = IRQ2Value | C2IsOutput;
return
((control & IRQ1Mask) == IRQ1Value) ||
((control & IRQ2Mask) == IRQ2Value);
}
mutable uint8_t previous_output = 0x00;
uint8_t output() const {
return data | ~direction;
}
uint8_t input(const uint8_t bus) const {
return (data & direction) | (bus & ~direction);
}
} ports_[2];
template <Port port>
void refresh() const {
static constexpr auto index = int(port);
const uint8_t output = ports_[index].output();
ports_[index].previous_output = output;
port_handler_.template output<port>(output);
}
bool irqa_ = false, irqb_ = false;
void update_interrupts() {
const bool irqa = ports_[0].irq();
const bool irqb = ports_[1].irq();
if(irqa_ != irqa) {
irqa_ = irqa;
port_handler_.template set<IRQ::A>(irqa);
}
if(irqb_ != irqb) {
irqb_ = irqb;
port_handler_.template set<IRQ::B>(irqb);
}
}
};
};
#endif /* _821_hpp */
+78
View File
@@ -0,0 +1,78 @@
//
// Keyboard.hpp
// Clock Signal
//
// Created by Thomas Harte on 22/03/2026.
// Copyright © 2026 Thomas Harte. All rights reserved.
//
#pragma once
#include "Machines/KeyboardMachine.hpp"
namespace MO5 {
enum Key: uint16_t {
};
struct KeyboardMapper: public MachineTypes::MappedKeyboardMachine::KeyboardMapper {
uint16_t mapped_key_for_key(const Inputs::Keyboard::Key key) const {
switch(key) {
default: return MachineTypes::MappedKeyboardMachine::KeyNotMapped;
using enum Inputs::Keyboard::Key;
case k0: return 0x1e; case k1: return 0x2f;
case k2: return 0x27; case k3: return 0x1f;
case k4: return 0x17; case k5: return 0x0f;
case k6: return 0x07; case k7: return 0x06;
case k8: return 0x0e; case k9: return 0x16;
case Q: return 0x2d; case W: return 0x25;
case E: return 0x1d; case R: return 0x15;
case T: return 0x0d; case Y: return 0x05;
case U: return 0x04; case I: return 0x0c;
case O: return 0x14; case P: return 0x1c;
case A: return 0x2b; case S: return 0x23;
case D: return 0x1b; case F: return 0x13;
case G: return 0x0b; case H: return 0x03;
case J: return 0x02; case K: return 0x0a;
case L: return 0x12; case Z: return 0x30;
case X: return 0x28; case C: return 0x32;
case V: return 0x2a; case B: return 0x22;
case N: return 0x00; case M: return 0x1a;
case FullStop: return 0x10;
case Comma: return 0x08;
case Hyphen: return 0x26;
case Equals: return 0x2e; // +
case OpenSquareBracket: return 0x18; // @
case Semicolon: return 0x1a; // M
case CloseSquareBracket:
case Quote: return 0x2c; // Asterisk.
case Space: return 0x20;
case Enter: return 0x34;
case Backspace: return 0x36;
case Tab: return 0x37;
case Up: return 0x31; case Down: return 0x21;
case Left: return 0x29; case Right: return 0x19;
case LeftShift:
case RightShift: return 0x38;
case LeftControl:
case RightControl: return 0x35;
case LeftOption:
case RightOption: return 0x33;
case LeftMeta:
case RightMeta: return 0x39;
// TODO: INS and EFF from the lower right, which I think are 0x09 and 0x11?
// And where the heck is forward slash?
}
}
};
}
+222 -17
View File
@@ -8,22 +8,41 @@
#include "MO5.hpp"
#include "Video.hpp"
#include "Machines/MachineTypes.hpp"
#include "Machines/Utility/MemoryFuzzer.hpp"
#include "Processors/6809/6809.hpp"
#include "Components/6821/6821.hpp"
#include "ClockReceiver/JustInTime.hpp"
#include "Components/AudioToggle/AudioToggle.hpp"
#include "Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
#include "Outputs/Speaker/Implementation/BufferSource.hpp"
#include "Keyboard.hpp"
using namespace Thomson::MO5;
namespace {
struct ConcreteMachine:
public MachineTypes::AudioProducer,
public MachineTypes::MappedKeyboardMachine,
public MachineTypes::TimedMachine,
public MachineTypes::ScanProducer,
public Machine
{
ConcreteMachine(const Analyser::Static::Target &, const ROMMachine::ROMFetcher &rom_fetcher) :
m6809_(*this)
m6809_(*this),
system_pia_port_handler_(*this),
system_pia_(system_pia_port_handler_),
video_(video_page(true), video_page(false)),
audio_toggle_(audio_queue_),
speaker_(audio_toggle_)
{
set_clock_rate(1'000'000);
speaker_.set_input_rate(1'000'000.0f);
const auto request = ROM::Request(ROM::Name::ThomasonMO5v11);
auto roms = rom_fetcher(request);
@@ -33,10 +52,26 @@ struct ConcreteMachine:
const auto &rom = roms.find(ROM::Name::ThomasonMO5v11)->second;
std::copy_n(rom.begin(), rom.size(), rom_.begin());
Memory::Fuzz(ram_);
system_pia_.refresh();
}
void run_for(const Cycles cycles) final {
m6809_.run_for(cycles);
~ConcreteMachine() {
audio_queue_.lock_flush();
}
template <
int address,
CPU::M6809::ReadWrite read_write,
typename ComponentT
>
static void access(ComponentT &component, CPU::M6809::data_t<read_write> value) {
if constexpr (CPU::M6809::is_read(read_write)) {
value = component.template read<address>();
} else {
component.template write<address>(value);
}
}
template <
@@ -49,21 +84,49 @@ struct ConcreteMachine:
const AddressT address,
CPU::M6809::data_t<read_write> value
) {
if constexpr (read_write != CPU::M6809::ReadWrite::NoData) {
if constexpr (CPU::M6809::is_read(read_write)) {
if(address >= 0xc000) {
value = rom_[address - 0xc000];
} else {
value = ram_[address];
if(video_ += m6809_.duration(bus_phase)) {
system_pia_.set<Motorola::MC6821::Control::CB1>(video_.last_valid()->irq());
}
time_since_audio_update_ += m6809_.duration(bus_phase);
if constexpr (read_write == CPU::M6809::ReadWrite::NoData) {
return Cycles(0);
} else {
if(address >= 0xa7c0 && address < 0xa800) {
switch(address) {
case 0xa7c0: access<0xa7c0, read_write>(system_pia_, value); break;
case 0xa7c1: access<0xa7c1, read_write>(system_pia_, value); break;
case 0xa7c2: access<0xa7c2, read_write>(system_pia_, value); break;
case 0xa7c3: access<0xa7c3, read_write>(system_pia_, value); break;
default:
if constexpr (CPU::M6809::is_read(read_write)) {
value = 0xff;
printf("Unhandled read at %04x\n", +address);
} else {
printf("Unhandled write: %02x -> %04x\n", +value, +address);
}
break;
}
} else {
ram_[address] = value;
printf("%04x: RAM <- 0x%02x [S: %04x]\n", +address, value, m6809_.registers().s);
if constexpr (CPU::M6809::is_read(read_write)) {
if(address < 0x2000) {
value = start_pointer_[address];
} else if(address >= 0xc000) {
value = rom_[address - 0xc000];
} else {
value = ram_[address];
}
} else {
if(address < 0x2000) {
if(address < 40*200) video_.flush();
start_pointer_[address] = value;
} else {
ram_[address] = value;
}
}
}
}
// TODO: the lowest 8kb of memory can actually be paged, so the linear representation above isn't accurate.
return Cycles(0);
}
@@ -75,16 +138,158 @@ private:
};
CPU::M6809::Processor<M6809Traits> m6809_;
std::array<uint8_t, 0xf000 + 0x2000> ram_;
std::array<uint8_t, 0x10000 + 0x2000> ram_;
std::array<uint8_t, 0x4000> rom_;
uint8_t *start_pointer_ = nullptr;
uint8_t *video_page(const bool pixels) {
return &ram_[pixels ? 0 : 0x1'0000];
}
void page_lower(const bool pixels) {
start_pointer_ = video_page(pixels);
}
friend struct SystemPIAPortHandler;
struct SystemPIAPortHandler {
SystemPIAPortHandler(ConcreteMachine &machine) : machine_(machine) {}
template <Motorola::MC6821::Port port>
uint8_t input() {
if constexpr (port == Motorola::MC6821::Port::A) {
// Port A inputs:
// b4: light pen button
// b7: tape input [and 0 = no tape; 1 = tape present]
return 0xff;
}
if constexpr (port == Motorola::MC6821::Port::B) {
// Port B inputs:
// b7: status of key at that position. [0 = pressed?]
return key_states_[key_] ? 0x00 : 0x80;
}
__builtin_unreachable();
}
template <Motorola::MC6821::Port port>
void output(const uint8_t value) {
if constexpr (port == Motorola::MC6821::Port::A) {
// Port A outputs:
// b0 = lower 8kb RAM paging;
// b1–4: border colour;
// b6: tape output
machine_.page_lower(value & 1);
machine_.video_->set_border_colour((value >> 1) & 0xf);
}
if constexpr (port == Motorola::MC6821::Port::B) {
// Port B outputs:
// b0 = 1-bit sound output;
// b1–3 = keyboard column;
// b4–6: keyboard line;
key_ = (value >> 1) & 0b111'111;
machine_.update_audio();
machine_.audio_toggle_.set_output(value & 1);
}
}
template <Motorola::MC6821::IRQ irq>
void set(const bool active) {
if constexpr (irq == Motorola::MC6821::IRQ::A) {
machine_.m6809_.set<CPU::M6809::Line::FIRQ>(active);
}
if constexpr (irq == Motorola::MC6821::IRQ::B) {
machine_.m6809_.set<CPU::M6809::Line::IRQ>(active);
}
}
template <Motorola::MC6821::Control control>
void observe(const bool value) {
// TODO: CA2 is drive motor control, so catch that.
(void)value;
}
// TODO:
//
// CA1: lightpen input
// CA2: drive motor control
// CB1: 50Hz interrupt
// CB2: genlock enable, maybe? Video "encrustation".
void clear_all_keys() {
std::fill(std::begin(key_states_), std::end(key_states_), false);
}
void set_key_state(const uint16_t key, const bool is_pressed) {
key_states_[key] = is_pressed;
}
private:
ConcreteMachine &machine_;
uint8_t key_ = 0;
bool key_states_[0x40]{};
};
SystemPIAPortHandler system_pia_port_handler_;
Motorola::MC6821::MC6821<SystemPIAPortHandler, 2, 1> system_pia_;
JustInTimeActor<Video, Cycles> video_;
// MARK: - AudioProducer.
Concurrency::AsyncTaskQueue<false> audio_queue_;
Audio::Toggle audio_toggle_;
Outputs::Speaker::PullLowpass<Audio::Toggle> speaker_;
Cycles time_since_audio_update_;
void update_audio() {
const auto cycles = time_since_audio_update_.flush();
speaker_.run_for(audio_queue_, cycles);
}
Outputs::Speaker::Speaker *get_speaker() override {
return &speaker_;
}
// MARK: - ScanProducer.
void set_scan_target(Outputs::Display::ScanTarget *) final {
void set_scan_target(Outputs::Display::ScanTarget *const target) final {
video_->set_scan_target(target);
}
Outputs::Display::ScanStatus get_scan_status() const final {
return Outputs::Display::ScanStatus();
Outputs::Display::ScanStatus get_scaled_scan_status() const final {
return video_.last_valid()->get_scaled_scan_status();
}
// MARK: - TimedMachine.
void run_for(const Cycles cycles) final {
m6809_.run_for(cycles);
}
void flush_output(int outputs) final {
if(outputs & Output::Video) {
video_.flush();
}
if(outputs & Output::Audio) {
update_audio();
audio_queue_.perform();
}
}
// MARK: - MappedKeyboardMachine.
MO5::KeyboardMapper keyboard_mapper_;
KeyboardMapper *get_keyboard_mapper() final {
return &keyboard_mapper_;
}
void set_key_state(const uint16_t key, const bool is_pressed) final {
system_pia_port_handler_.set_key_state(key, is_pressed);
}
void clear_all_keys() final {
system_pia_port_handler_.clear_all_keys();
}
};
+150
View File
@@ -0,0 +1,150 @@
//
// Video.cpp
// Clock Signal
//
// Created by Thomas Harte on 20/03/2026.
// Copyright © 2026 Thomas Harte. All rights reserved.
//
#include "Video.hpp"
#include <bit>
using namespace Thomson::MO5;
namespace {
constexpr uint16_t rgb(const uint16_t code) {
#ifdef TARGET_RT_BIG_ENDIAN
return code;
#else
return std::rotl(code, 8);
#endif
}
// Per https://pulkomandy.tk/wiki/doku.php?id=documentations:devices:gate.arrays#video_generation
constexpr uint16_t palette[] = {
rgb(0x000), rgb(0xf55), rgb(0x0f0), rgb(0xff0), rgb(0x55f), rgb(0xf0f), rgb(0x5ff), rgb(0xfff),
rgb(0xaaa), rgb(0xfaa), rgb(0xafa), rgb(0xffa), rgb(0x5af), rgb(0xfaf), rgb(0xaff), rgb(0xfa5),
};
}
Video::Video(const uint8_t *const pixels, const uint8_t *const attributes) :
pixels_(pixels), attributes_(attributes),
crt_(
CyclesPerLine,
1,
Outputs::Display::Type::SECAM,
Outputs::Display::InputDataType::Red4Green4Blue4
) {
crt_.set_fixed_framing([&] {
run_for(Cycles(10'000));
});
crt_.set_display_type(Outputs::Display::DisplayType::RGB);
}
void Video::run_for(const Cycles cycles) {
position_.advance(
cycles.as<int>(),
[&] (const int line, const int begin, const int end) {
if(line >= VerticalSyncLine && line < VerticalSyncLine + VerticalSyncLength) {
vsync_line(begin, end);
} else if(line >= TotalPixelLines) {
border_line(begin, end);
} else {
pixel_line(begin, end);
}
},
[&] {
source_address_ = 0;
}
);
}
void Video::vsync_line(const int line_begin, const int line_end) {
crt_.output_sync(line_end - line_begin);
}
void Video::border_line(const int line_begin, const int line_end) {
static constexpr int EndOfSync = 4;
Numeric::clamp<0, EndOfSync>(line_begin, line_end, [&](const int begin, const int end) {
crt_.output_sync(end - begin);
});
Numeric::clamp<EndOfSync, CyclesPerLine>(line_begin, line_end, [&](const int begin, const int end) {
crt_.output_level(end - begin, border_);
});
}
void Video::pixel_line(const int line_begin, const int line_end) {
// Layout: [sync][border][pixels][border].
static constexpr int EndOfSync = 4;
static constexpr int EndOfLeftBorder = 14;
static constexpr int EndOfPixels = 54;
static constexpr int PixelsPerLine = 320;
Numeric::clamp<0, EndOfSync>(line_begin, line_end, [&](const int begin, const int end) {
crt_.output_sync(end - begin);
});
Numeric::clamp<EndOfSync, EndOfLeftBorder>(line_begin, line_end, [&](const int begin, const int end) {
crt_.output_level(end - begin, border_);
});
Numeric::clamp<EndOfLeftBorder, EndOfPixels>(line_begin, line_end, [&](const int begin, const int end) {
if(begin == EndOfLeftBorder) {
output_ = reinterpret_cast<uint16_t *>(crt_.begin_data(PixelsPerLine));
}
if(output_) {
for(int c = begin; c < end; c++) {
const uint8_t pixels = pixels_[source_address_];
const uint8_t attributes = attributes_[source_address_];
++source_address_;
const uint16_t colours[] = {
palette[attributes & 0xf],
palette[attributes >> 4],
};
output_[0] = colours[(pixels >> 7) & 1];
output_[1] = colours[(pixels >> 6) & 1];
output_[2] = colours[(pixels >> 5) & 1];
output_[3] = colours[(pixels >> 4) & 1];
output_[4] = colours[(pixels >> 3) & 1];
output_[5] = colours[(pixels >> 2) & 1];
output_[6] = colours[(pixels >> 1) & 1];
output_[7] = colours[(pixels >> 0) & 1];
output_ += 8;
}
} else {
source_address_ += end - begin;
}
if(end == EndOfPixels) {
crt_.output_data(EndOfPixels - EndOfLeftBorder, PixelsPerLine);
output_ = nullptr;
}
});
Numeric::clamp<EndOfPixels, CyclesPerLine>(line_begin, line_end, [&](const int begin, const int end) {
crt_.output_level(end - begin, border_);
});
}
Cycles Video::next_sequence_point() const {
// Pulse the interrupt output for 8 cycles, arbitrarily. The real number seems to be undocumented, and it
// doesn't actually make much concrete difference in concrete terms because this is fed into edge-detection via
// the PIA. Knowing the real number would only fix the case where the detected transition is switched to
// trailing-edge during the pulse, which probably doesn't happen in any real software.
//
// Would be nice to know the real number though.
if(position_.absolute() < IRQCycle) return IRQCycle - position_.absolute();
if(position_.absolute() < IRQCycle + IRQLength) return IRQCycle + IRQLength - position_.absolute();
return FrameLength - position_.absolute() + IRQCycle;
}
bool Video::irq() const {
return position_.absolute() >= IRQCycle && position_.absolute() < (IRQCycle + IRQLength);
}
void Video::set_border_colour(const uint8_t colour) {
border_ = palette[colour & 0xf];
}
+88
View File
@@ -0,0 +1,88 @@
//
// Video.hpp
// Clock Signal
//
// Created by Thomas Harte on 20/03/2026.
// Copyright © 2026 Thomas Harte. All rights reserved.
//
#ifndef Video_hpp
#define Video_hpp
#include "ClockReceiver/ClockReceiver.hpp"
#include "Numeric/SegmentCounter.hpp"
#include "Outputs/CRT/CRT.hpp"
namespace Thomson::MO5 {
struct Video {
public:
Video(const uint8_t *pixels, const uint8_t *attributes);
void run_for(Cycles);
Cycles next_sequence_point() const;
void set_border_colour(uint8_t);
bool irq() const;
// MARK: - Standard boilerplate.
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();
}
void set_display_type(const Outputs::Display::DisplayType display_type) {
crt_.set_display_type(display_type);
}
Outputs::Display::DisplayType get_display_type() const {
return crt_.get_display_type();
}
private:
const uint8_t *pixels_ = nullptr;
const uint8_t *attributes_ = nullptr;
Outputs::CRT::CRT crt_;
uint16_t source_address_ = 0;
uint16_t border_ = 0;
uint16_t *output_ = nullptr;
void vsync_line(int, int);
void border_line(int, int);
void pixel_line(int, int);
// Video timing, as far as auto-translate lets me figure it out:
//
// 64 cycles/line;
// 56 lines post signalled vsync, then 200 of video, then 56 more, for 312 total.
//
// Start of vsync is connected to CPU IRQ.
//
// Within a line: ??? Who knows ???
//
// Have rationalised as 4 cycles of sync and the rest as appropriate colours. Via IRQCycle the interrupt can be placed
// arbitrarily within the frame so I think any implementation within a line is valid as long as I place the interrupt
// appropriately. TODO: where is the interrupt placed?
//
static constexpr int CyclesPerLine = 64;
static constexpr int TotalLines = 312;
static constexpr int TotalPixelLines = 200;
static constexpr int VerticalSyncLine = 256;
static constexpr int VerticalSyncLength = 3;
static constexpr int IRQCycle = 256 * CyclesPerLine;
static constexpr int IRQLength = 8;
static constexpr int FrameLength = TotalLines * CyclesPerLine;
Numeric::DividingAccumulator<CyclesPerLine, TotalLines> position_;
};
}
#endif /* Video_hpp */
+59
View File
@@ -0,0 +1,59 @@
//
// SegmentCounter.hpp
// Clock Signal
//
// Created by Thomas Harte on 22/03/2026.
// Copyright © 2026 Thomas Harte. All rights reserved.
//
#include <algorithm>
namespace Numeric {
/*!
A counter that divides its time into n segments, each of a fixed length, then repeats.
Lambdas receive:
(1) calls to advance through segment and regions within them; and
(2) optionally, an announcement each time the counter wraps around.
*/
template <int SegmentLength, int Segments>
struct DividingAccumulator {
template <typename AdvanceFuncT, typename ResetFuncT>
void advance(int count, const AdvanceFuncT &&advance, const ResetFuncT &&reset = []{}) {
while(count > 0) {
const int segment = position_ / SegmentLength;
const int begin = position_ % SegmentLength;
const int length = std::min(SegmentLength - begin, count);
advance(segment, begin, begin + length);
count -= length;
position_ += length;
if(position_ == SegmentLength * Segments) {
position_ = 0;
reset();
}
}
}
/// @returns Position within the range [0, SegmentLength*Segments)
int absolute() const {
return position_;
}
private:
int position_ = 0;
};
/// Calculates the overlap, if any, between [begin, end] and [RangeBegin, RangeEnd] and calls FuncT with a begin and end if the range is non-zero.
template <int RangeBegin, int RangeEnd, typename FuncT>
void clamp(const int begin, const int end, const FuncT &&function) {
const int range_begin = std::max(begin, RangeBegin);
const int range_end = std::min(end, RangeEnd);
if(range_end > range_begin) {
function(range_begin, range_end);
}
}
}
+1
View File
@@ -11,6 +11,7 @@
#include "Sizes.hpp"
#include <concepts>
#include <compare>
namespace Numeric {
@@ -1104,6 +1104,9 @@
4BBB70A8202014E2002FE009 /* MultiProducer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBB70A6202014E2002FE009 /* MultiProducer.cpp */; };
4BBB70A9202014E2002FE009 /* MultiProducer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBB70A6202014E2002FE009 /* MultiProducer.cpp */; };
4BBB77DD2867EBB300D335A1 /* IIgs Memory Map in Resources */ = {isa = PBXBuildFile; fileRef = 4BBB77DC2867EBB300D335A1 /* IIgs Memory Map */; };
4BBC7DC12F6D94E500E71794 /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBC7DC02F6D94E500E71794 /* Video.cpp */; };
4BBC7DC22F6D94E500E71794 /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBC7DC02F6D94E500E71794 /* Video.cpp */; };
4BBC7DC32F6D94E500E71794 /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBC7DC02F6D94E500E71794 /* Video.cpp */; };
4BBC951E1F368D83008F4C34 /* i8272.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBC951C1F368D83008F4C34 /* i8272.cpp */; };
4BBF49AF1ED2880200AB3669 /* FUSETests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF49AE1ED2880200AB3669 /* FUSETests.swift */; };
4BBFBB6C1EE8401E00C01E7A /* ZX8081.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBFBB6A1EE8401E00C01E7A /* ZX8081.cpp */; };
@@ -2376,6 +2379,10 @@
4BBB70A6202014E2002FE009 /* MultiProducer.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = MultiProducer.cpp; sourceTree = "<group>"; };
4BBB70A7202014E2002FE009 /* MultiProducer.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MultiProducer.hpp; sourceTree = "<group>"; };
4BBB77DC2867EBB300D335A1 /* IIgs Memory Map */ = {isa = PBXFileReference; lastKnownFileType = folder; path = "IIgs Memory Map"; sourceTree = "<group>"; };
4BBC7DBF2F6D94E500E71794 /* Video.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Video.hpp; sourceTree = "<group>"; };
4BBC7DC02F6D94E500E71794 /* Video.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Video.cpp; sourceTree = "<group>"; };
4BBC7DC42F70507B00E71794 /* SegmentCounter.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = SegmentCounter.hpp; sourceTree = "<group>"; };
4BBC7DC52F70BAFB00E71794 /* Keyboard.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Keyboard.hpp; sourceTree = "<group>"; };
4BBC951C1F368D83008F4C34 /* i8272.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = i8272.cpp; sourceTree = "<group>"; };
4BBC951D1F368D83008F4C34 /* i8272.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = i8272.hpp; sourceTree = "<group>"; };
4BBF49AE1ED2880200AB3669 /* FUSETests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FUSETests.swift; sourceTree = "<group>"; };
@@ -2531,6 +2538,7 @@
4BDACBEA22FFA5D20045EF7E /* ncr5380.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = ncr5380.cpp; sourceTree = "<group>"; };
4BDACBEB22FFA5D20045EF7E /* ncr5380.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ncr5380.hpp; sourceTree = "<group>"; };
4BDB3D8522833321002D3CEE /* Keyboard.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Keyboard.hpp; sourceTree = "<group>"; };
4BDB734F2F6CA1A200964D69 /* 6821.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = 6821.hpp; sourceTree = "<group>"; };
4BDCC5F81FB27A5E001220C5 /* ROMMachine.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ROMMachine.hpp; sourceTree = "<group>"; };
4BDDBA981EF3451200347E61 /* Z80MachineCycleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Z80MachineCycleTests.swift; sourceTree = "<group>"; };
4BE0151C286A8C8E00EA42E9 /* MemorySwitches.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MemorySwitches.hpp; sourceTree = "<group>"; };
@@ -3880,6 +3888,7 @@
4BFEA2F12682A90200EBF94C /* Sizes.hpp */,
4BD9713A2BFD7E7100C907AA /* StringSimilarity.hpp */,
4B0329202D0A78DC00C51EB5 /* UpperBound.hpp */,
4BBC7DC42F70507B00E71794 /* SegmentCounter.hpp */,
);
name = Numeric;
path = ../../Numeric;
@@ -5276,6 +5285,7 @@
4BC080DD26A481C100D03FD8 /* 6526 */,
4B1E85791D174DEC001EF87D /* 6532 */,
4BC9DF4C1D04691600F44158 /* 6560 */,
4BDB73512F6CA1A200964D69 /* 6821 */,
4BE845221F2FF7F400A5EA22 /* 6845 */,
4BB307B8235001C300457D33 /* 6850 */,
4BD9137C1F3115AC009BCF85 /* 8255 */,
@@ -5331,8 +5341,11 @@
4BCA6FAA2F589C0C000B9537 /* MO5 */ = {
isa = PBXGroup;
children = (
4BCA6FA82F589C0C000B9537 /* MO5.hpp */,
4BCA6FA92F589C0C000B9537 /* MO5.cpp */,
4BBC7DC02F6D94E500E71794 /* Video.cpp */,
4BBC7DC52F70BAFB00E71794 /* Keyboard.hpp */,
4BCA6FA82F589C0C000B9537 /* MO5.hpp */,
4BBC7DBF2F6D94E500E71794 /* Video.hpp */,
);
path = MO5;
sourceTree = "<group>";
@@ -5591,6 +5604,14 @@
path = 5380;
sourceTree = "<group>";
};
4BDB73512F6CA1A200964D69 /* 6821 */ = {
isa = PBXGroup;
children = (
4BDB734F2F6CA1A200964D69 /* 6821.hpp */,
);
path = 6821;
sourceTree = "<group>";
};
4BE211FB253FC80800435408 /* AppleIIgs */ = {
isa = PBXGroup;
children = (
@@ -6460,6 +6481,7 @@
423820452B1A90BE00964EFE /* PCBooter.cpp in Sources */,
4B8318B922D3E56D006DB630 /* MemoryPacker.cpp in Sources */,
4B055ABA1FAE86170060FFFF /* Commodore.cpp in Sources */,
4BBC7DC12F6D94E500E71794 /* Video.cpp in Sources */,
42EB812E2B4700B700429AF4 /* MemoryMap.cpp in Sources */,
4B0ACC2F23775819008902D0 /* TIA.cpp in Sources */,
4B9BE401203A0C0600FFAE60 /* MultiSpeaker.cpp in Sources */,
@@ -6818,6 +6840,7 @@
4B8805F41DCFD22A003085B1 /* Commodore.cpp in Sources */,
4B3FCC40201EC24200960631 /* MultiMachine.cpp in Sources */,
4B0F1BB22602645900B85C66 /* StaticAnalyser.cpp in Sources */,
4BBC7DC32F6D94E500E71794 /* Video.cpp in Sources */,
4B8805F01DCFC99C003085B1 /* Acorn.cpp in Sources */,
4B30512D1D989E2200B4FED8 /* Drive.cpp in Sources */,
4BCE005D227D30CC000CA200 /* MemoryPacker.cpp in Sources */,
@@ -6954,6 +6977,7 @@
4BC5C3E022C994CD00795658 /* 68000MoveTests.mm in Sources */,
4B778F5923A5F2D00000D260 /* Z80.cpp in Sources */,
4BD971392BFC3D9D00C907AA /* ArchimedesStaticAnalyserTests.mm in Sources */,
4BBC7DC22F6D94E500E71794 /* Video.cpp in Sources */,
4B08A2751EE35D56008B7065 /* Z80InterruptTests.swift in Sources */,
4B778F0E23A5EC4F0000D260 /* Tape.cpp in Sources */,
4B778F2D23A5EF190000D260 /* MFMDiskController.cpp in Sources */,
+16 -4
View File
@@ -122,16 +122,28 @@ struct M6809Traits {
// Test set uses the test that either exactly one of NZC is set, or they all are. That's four possibilities.
//
// Documented test is N != C, or Z. That's six possibilities (four with Z set; two more with Z unset but the
// Documented test is N != V, or Z. That's six possibilities (four with Z set; two more with Z unset but the
// other two not matching).
//
// Hence it's seems true that the test set isn't right on this. Or the documented test isn't.
// Not even both being dependent on the same flags, it's clear that the test set and documentation diverge.
case BLE:
case LBLE: return;
case LBLE:
return;
// The test set doesn't branch if both Z and C are set. The documented test is to branch.
case BLS:
case LBLS: return;
case LBLS:
if(
m6809.registers().cc.get<CPU::M6809::ConditionCode::Zero>() &&
m6809.registers().cc.get<CPU::M6809::ConditionCode::Carry>()
) {
return;
}
break;
// The test set implements CLR as a write operation. It's actually a modify.
case CLR:
return;
case EXG: case TFR: {
// The test suite supports only the operands listed below, treating the rest as NOPs.
+1
View File
@@ -19,6 +19,7 @@ namespace Outputs::Display {
enum class Type {
PAL50,
SECAM = PAL50, // Not correct as to composite colour, but correct as to timing.
PAL60,
NTSC60
};
+75 -47
View File
@@ -79,6 +79,16 @@ enum class LIC {
Inactive,
};
enum Vector: uint16_t {
SWI3 = 0xfff2,
SWI2 = 0xfff4,
FIRQ = 0xfff6,
IRQ = 0xfff8,
SWI = 0xfffa,
NMI = 0xfffc,
Reset = 0xfffe,
};
// Missing outputs:
//
// AVMA — advanced VMA, i.e. bus will be accessed in the next cycle.
@@ -198,7 +208,7 @@ struct Processor {
time_ -= Cycles(1); \
time_ -= \
bus_handler_.template perform<BusPhase::FullCycle, ReadWrite::Read, bus_state>(addr, target_); \
goto local_label(skipMRDY); \
goto local_label(skipMRDY); \
} \
\
if constexpr (Traits::uses_mrdy) { \
@@ -355,17 +365,17 @@ struct Processor {
resume_point_ = ResumePoint::ResetSpin;
return;
}
addressed_internal_cycle(Address::Fixed<0xfffe>())
addressed_internal_cycle(Address::Fixed<Vector::Reset>())
if(exceptions_ & (Exception::Halt | Exception::DMABusReq | Exception::Reset)) {
goto reset_spin;
}
addressed_internal_cycle(Address::Fixed<0xfffe>())
addressed_internal_cycle(Address::Fixed<0xfffe>())
addressed_internal_cycle(Address::Fixed<0xfffe>())
addressed_internal_cycle(Address::Fixed<Vector::Reset>())
addressed_internal_cycle(Address::Fixed<Vector::Reset>())
addressed_internal_cycle(Address::Fixed<Vector::Reset>())
read(BusState::InterruptOrResetAcknowledge, Address::Fixed<0xfffe>(), registers_.pc.halves.high);
read(BusState::InterruptOrResetAcknowledge, Address::Fixed<0xffff>(), registers_.pc.halves.low);
read(BusState::InterruptOrResetAcknowledge, Address::Fixed<Vector::Reset>(), registers_.pc.halves.high);
read(BusState::InterruptOrResetAcknowledge, Address::Fixed<Vector::Reset + 1>(), registers_.pc.halves.low);
internal_cycle();
@@ -384,6 +394,7 @@ struct Processor {
-- registers_.reg<R16::S>();
write(BusState::Normal, Literal(registers_.reg<R16::S>()), registers_.reg<R8::CC>());
address_.full = Vector::FIRQ;
goto interrupt_dispatch;
// MARK: - Fetch/decode.
@@ -400,23 +411,31 @@ struct Processor {
}
if(exceptions_) {
// Avoid potential accidental detection of SWIs on the interrupt paths.
operation_.operation = Operation::None;
if(exceptions_ & Exception::Halt) {
goto halt;
}
if(exceptions_ & (Exception::PowerOnReset | Exception::Reset)) {
address_ = Vector::Reset;
goto reset;
}
if(exceptions_ & Exception::NMI) {
address_ = Vector::NMI;
exceptions_ &= ~Exception::NMI;
goto nmi_irq;
}
if(exceptions_ & Exception::FIRQ && !registers_.cc.get<ConditionCode::FIRQMask>()) {
address_ = Vector::FIRQ;
goto firq;
}
if(exceptions_ & Exception::IRQ && !registers_.cc.get<ConditionCode::IRQMask>()) {
address_ = Vector::IRQ;
goto nmi_irq;
}
}
@@ -566,10 +585,20 @@ struct Processor {
case Operation::RTI: goto rti;
case Operation::SWI:
address_ = Vector::SWI;
goto swi_reset;
case Operation::SWI2:
address_ = Vector::SWI2;
goto swi_reset;
case Operation::SWI3:
address_.full = Vector::SWI3;
goto swi_reset;
case Operation::RESET:
goto swi_reset;
address_.full = Vector::Reset;
goto swi_reset;
default: __builtin_unreachable();
}
@@ -741,11 +770,7 @@ struct Processor {
rts:
addressed_internal_cycle(Address::Literal(registers_.pc.full));
read(BusState::Normal, Literal(registers_.reg<R16::S>()), address_.halves.high, ++registers_.reg<R16::S>());
read(BusState::Normal, Literal(registers_.reg<R16::S>()), address_.halves.low, ++registers_.reg<R16::S>());
registers_.reg<R16::PC>() = address_.full;
internal_cycle();
goto fetch_decode;
goto rti_not_entire;
bsr:
read(BusState::Normal, Literal(registers_.pc.full), operand_.halves.low, ++registers_.pc.full);
@@ -767,11 +792,18 @@ struct Processor {
return;
}
if(
exceptions_ & Exception::NMI ||
(exceptions_ & Exception::FIRQ && !registers_.cc.get<ConditionCode::FIRQMask>()) ||
(exceptions_ & Exception::IRQ && !registers_.cc.get<ConditionCode::IRQMask>())
) {
if(exceptions_ & Exception::NMI) {
address_ = Vector::NMI;
goto interrupt_dispatch;
}
if(exceptions_ & Exception::FIRQ && !registers_.cc.get<ConditionCode::FIRQMask>()) {
address_ = Vector::FIRQ;
goto interrupt_dispatch;
}
if(exceptions_ & Exception::IRQ && !registers_.cc.get<ConditionCode::IRQMask>()) {
address_ = Vector::IRQ;
goto interrupt_dispatch;
}
@@ -842,37 +874,24 @@ struct Processor {
}
interrupt_dispatch:
address_.full = [&]() -> uint16_t {
if(exceptions_ & Exception::NMI) {
exceptions_ &= ~Exception::NMI;
return 0xfffc;
}
if(exceptions_ & Exception::FIRQ && !registers_.cc.get<ConditionCode::FIRQMask>()) {
return 0xfff6;
}
if(exceptions_ & Exception::IRQ && !registers_.cc.get<ConditionCode::IRQMask>()) {
return 0xfff8;
}
switch(operation_.operation) {
case Operation::SWI: return 0xfffa;
case Operation::SWI2: return 0xfff4;
case Operation::SWI3: return 0xfff2;
case Operation::RESET: return 0xfffe;
default: __builtin_unreachable();
}
} ();
if(operation_.operation != Operation::SWI2 && operation_.operation != Operation::SWI3) {
registers_.cc.set<ConditionCode::IRQMask>(true);
registers_.cc.set<ConditionCode::FIRQMask>(true);
}
internal_cycle();
read(BusState::Normal, Literal(address_.full), registers_.pc.halves.high, ++address_.full);
read(BusState::Normal, Literal(address_.full), registers_.pc.halves.low, ++address_.full);
read(
BusState::InterruptOrResetAcknowledge,
Literal(address_.full),
registers_.pc.halves.high,
++address_.full
);
read(
BusState::InterruptOrResetAcknowledge,
Literal(address_.full),
registers_.pc.halves.low,
++address_.full
);
internal_cycle();
goto fetch_decode;
@@ -950,11 +969,20 @@ struct Processor {
goto fetch_decode;
}
#undef access_label
#undef attach
#undef join
#undef access
#undef addressing_program
#undef access_program
#undef restore_point
#undef join
#undef attach
#undef local_label
#undef access_label
#undef check_pause
#undef read
#undef write
#undef inactive_bus
#undef internal_cycles
#undef perform_operation
#undef addressed_internal_cycle
}
Registers &registers() {
+5 -6
View File
@@ -130,13 +130,13 @@ struct ConditionCodeRegister {
case NE: return !get<ConditionCode::Zero>();
case EQ: return get<ConditionCode::Zero>();
case LE: return get<ConditionCode::Zero>() || test<InstructionSet::M6809::Condition::LT>();
case LS: return get<ConditionCode::Zero>() || get<ConditionCode::Carry>();
case LE: return get<ConditionCode::Zero>() || test<InstructionSet::M6809::Condition::LT>();
case LT: return get<ConditionCode::Negative>() != get<ConditionCode::Overflow>();
case HI: return !get<ConditionCode::Zero>() && !get<ConditionCode::Carry>();
case GE: return get<ConditionCode::Negative>() == get<ConditionCode::Overflow>();
case GT: return !get<ConditionCode::Zero>() && test<InstructionSet::M6809::Condition::GE>();
case GE: return !test<InstructionSet::M6809::Condition::LT>();
case GT: return !test<InstructionSet::M6809::Condition::LE>();
case HI: return !test<InstructionSet::M6809::Condition::LS>();
}
return false;
}
@@ -347,6 +347,7 @@ struct IndexedAddressDecoder {
}
switch(suffix()) {
using enum FormSuffix;
default: // For invalid suffixes.
case NoOffset: return 0;
case Offset8bit: return 1;
@@ -366,8 +367,6 @@ struct IndexedAddressDecoder {
case Offset16bitFromPC: return 4;
case Extended: return 1;
default:__builtin_unreachable();
}
}
+1
View File
@@ -151,6 +151,7 @@ set(CLK_SOURCES
Machines/Sinclair/ZX8081/ZX8081.cpp
Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp
Machines/Thomson/MO5/MO5.cpp
Machines/Thomson/MO5/Video.cpp
Machines/Utility/MachineForTarget.cpp
Machines/Utility/MemoryFuzzer.cpp
Machines/Utility/MemoryPacker.cpp