Merge pull request #1727 from TomHarte/MoreMO5
Further improve 6809; get MO5 to interactive BASIC.
This commit is contained in:
13 files changed
+908
-75
No files matched your search
@@ -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 */
|
||||
@@ -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
@@ -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();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
@@ -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 */
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 */,
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
@@ -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 ®isters() {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in new issue
Block a user