1
0
mirror of https://github.com/TomHarte/CLK.git synced 2025-09-23 11:24:29 +00:00

Compare commits

...

48 Commits

Author SHA1 Message Date
Thomas Harte
ca226e4295 Merge branch 'master' into New6845 2025-09-22 13:28:33 -04:00
Thomas Harte
9261939f62 Switch to working PC for testing. 2025-09-22 13:24:35 -04:00
Thomas Harte
0349931953 Shuffle declare order. 2025-09-22 13:21:48 -04:00
Thomas Harte
d612a385d2 Dig in further on types. 2025-09-22 13:20:10 -04:00
Thomas Harte
ed4f299d55 Start formalising types. 2025-09-22 13:09:30 -04:00
Thomas Harte
7cef789d41 Merge branch 'master' into New6845 2025-09-22 12:47:32 -04:00
Thomas Harte
66bfb86d42 Introduce SizedCounter as start of CRTC reworking. 2025-09-22 12:46:39 -04:00
Thomas Harte
c4a5bc12ef Merge pull request #1572 from TomHarte/BBCADFS
Support ADFS, sideways RAM.
2025-09-20 23:27:15 -04:00
Thomas Harte
557631f6ba Support ADFS, sideways RAM. 2025-09-20 22:33:08 -04:00
Thomas Harte
362ffaff7f Merge pull request #1571 from TomHarte/RandomPauses
Correct uPD7002 interrupt wiring and behaviour.
2025-09-20 22:08:13 -04:00
Thomas Harte
fb5ef200fb Correct uPD7002 interrupt wiring. 2025-09-20 21:51:19 -04:00
Thomas Harte
5e78ac3af5 Adjust keyboard map slightly. 2025-09-20 21:35:01 -04:00
Thomas Harte
719a090b34 Retain bit 2. 2025-09-20 20:06:28 -04:00
Thomas Harte
3af85da6e0 Adjust conversion bits in status. 2025-09-20 19:52:47 -04:00
Thomas Harte
8fd62aa525 Disable interrupt at start of conversion. 2025-09-20 19:49:16 -04:00
Thomas Harte
40747f51bd Disable ADC interrupt, experimentally. 2025-09-20 17:41:22 -04:00
Thomas Harte
f3cef6bd73 Merge pull request #1570 from TomHarte/BBCCursor
Add BBC cursor.
2025-09-20 08:42:53 -04:00
Thomas Harte
eef0ee8180 Support cursor to end of row. 2025-09-20 08:27:58 -04:00
Thomas Harte
503e974375 Restrict cursor to visible area, fix width. 2025-09-20 08:15:02 -04:00
Thomas Harte
c959f2fee5 Attempt to show the hardware cursor. 2025-09-20 07:54:37 -04:00
Thomas Harte
7d5e434cba Merge pull request #1569 from TomHarte/BBCActivityIndicators
Add activity indicators.
2025-09-19 23:51:18 -04:00
Thomas Harte
2720bcdf18 Retrench to static inline const. 2025-09-19 23:40:30 -04:00
Thomas Harte
c513b7262b Hit up two further strings for constexpr. 2025-09-19 23:37:11 -04:00
Thomas Harte
57a795df96 Add keyboard LEDs. 2025-09-19 23:34:51 -04:00
Thomas Harte
6bdd9e4543 Add drive activity indicators. 2025-09-19 23:26:50 -04:00
Thomas Harte
ede3def37f Merge pull request #1568 from TomHarte/BBC1770
Add 1770 DFS support.
2025-09-19 23:20:29 -04:00
Thomas Harte
87d9022280 Collapse operations. 2025-09-19 23:03:11 -04:00
Thomas Harte
ff0ba7d48b Reduce logging again. 2025-09-19 22:59:58 -04:00
Thomas Harte
b49c47425f Set I flag on NMI and reset. 2025-09-19 22:59:37 -04:00
Thomas Harte
3916ba1a42 This intermittently succeeds. Doubling down on investigation. 2025-09-19 20:33:02 -04:00
Thomas Harte
0b3d22b97c Take a swing and a miss at alternative documentation interpretations. 2025-09-19 19:59:12 -04:00
Thomas Harte
9b8b0f2023 Attempt to introduce a DFS ROM and WD1770. 2025-09-19 10:38:22 -04:00
Thomas Harte
06e0d17be0 Merge pull request #1567 from TomHarte/AllPixelModes
Perform proper pixel generation in all bitmap modes.
2025-09-18 21:54:24 -04:00
Thomas Harte
239c485f3c An underclock will do. 2025-09-18 21:35:08 -04:00
Thomas Harte
5e5fdda0ca Correct audio. 2025-09-18 21:33:25 -04:00
Thomas Harte
4b2dddf3c6 Remove stale TODO. 2025-09-18 21:21:51 -04:00
Thomas Harte
c99ec745ca Remove dead logging. 2025-09-18 21:20:27 -04:00
Thomas Harte
1ec2e455ec Support flash, mixed modes. 2025-09-18 21:19:33 -04:00
Thomas Harte
69304737c6 Switch red and blue. 2025-09-18 17:53:58 -04:00
Thomas Harte
fe91670127 Pull count outside loop, simplify state machine. 2025-09-18 17:50:46 -04:00
Thomas Harte
7a59f94f3d Install more realistic pixel pipeline. 2025-09-18 17:46:09 -04:00
Thomas Harte
4efe3a333d Merge pull request #1566 from TomHarte/BBCADC
Add the BBC's ADC.
2025-09-18 12:39:34 -04:00
Thomas Harte
421bf28582 Add comments, correct address decoding. 2025-09-18 12:27:13 -04:00
Thomas Harte
4c49ffe3d1 Attmept full ADC implementation. 2025-09-18 12:21:25 -04:00
Thomas Harte
26b1ef247b Add calls to ADB. 2025-09-17 23:11:48 -04:00
Thomas Harte
3aafba707a Use more efficient means for blank lines. 2025-09-17 22:33:59 -04:00
Thomas Harte
ae774e88fa Add header for ADC. 2025-09-17 21:42:42 -04:00
Thomas Harte
ff56dd53cf Remove dead code. 2025-09-17 21:42:33 -04:00
27 changed files with 649 additions and 214 deletions

View File

@@ -38,12 +38,19 @@ private:
struct BBCMicroTarget: public ::Analyser::Static::Target, public Reflection::StructImpl<BBCMicroTarget> { struct BBCMicroTarget: public ::Analyser::Static::Target, public Reflection::StructImpl<BBCMicroTarget> {
std::string loading_command; std::string loading_command;
bool has_1770dfs = false;
bool has_adfs = false;
bool has_sideways_ram = false;
BBCMicroTarget() : Analyser::Static::Target(Machine::BBCMicro) {} BBCMicroTarget() : Analyser::Static::Target(Machine::BBCMicro) {}
private: private:
friend Reflection::StructImpl<BBCMicroTarget>; friend Reflection::StructImpl<BBCMicroTarget>;
void declare_fields() {} void declare_fields() {
DeclareField(has_1770dfs);
DeclareField(has_adfs);
DeclareField(has_sideways_ram);
}
}; };
struct ArchimedesTarget: public ::Analyser::Static::Target, public Reflection::StructImpl<ArchimedesTarget> { struct ArchimedesTarget: public ::Analyser::Static::Target, public Reflection::StructImpl<ArchimedesTarget> {

View File

@@ -114,6 +114,7 @@ uint8_t WD1770::read(const int address) {
update_status([] (Status &status) { update_status([] (Status &status) {
status.data_request = false; status.data_request = false;
}); });
// Logger::info().append("Returned data %02x; [drq:%d]", data_, status_.data_request);
return data_; return data_;
} }
} }
@@ -486,8 +487,12 @@ void WD1770::posit_event(const int new_event_type) {
WAIT_FOR_EVENT(Event::Token); WAIT_FOR_EVENT(Event::Token);
if(get_latest_token().type != Token::Byte) goto type2_read_byte; if(get_latest_token().type != Token::Byte) goto type2_read_byte;
data_ = get_latest_token().byte_value; data_ = get_latest_token().byte_value;
// Logger::info().append("Posting %02x", data_);
update_status([] (Status &status) { update_status([] (Status &status) {
status.lost_data |= status.data_request; status.lost_data |= status.data_request;
// if(status.lost_data) {
// Logger::info().append("Lost data");
// }
status.data_request = true; status.data_request = true;
}); });
distance_into_section_++; distance_into_section_++;

View File

@@ -9,23 +9,38 @@
#pragma once #pragma once
#include "ClockReceiver/ClockReceiver.hpp" #include "ClockReceiver/ClockReceiver.hpp"
#include "Numeric/SizedCounter.hpp"
#include <cstdint> #include <cstdint>
#include <cstdio> #include <cstdio>
//
// WARNING: code is in flux. I'm attempting to use hoglet's FPGA implementation at
// https://github.com/hoglet67/BeebFpga/blob/master/src/common/mc6845.vhd as an authoritative guide to proper behaviour,
// having found his Electron ULA to be excellent. This is starting by mapping various bits of internal state here
// to hoglet's equivalents; cf. comments.
//
namespace Motorola::CRTC { namespace Motorola::CRTC {
using RefreshAddress = Numeric::SizedCounter<14>;
using LineAddress = Numeric::SizedCounter<5>;
using SyncCounter = Numeric::SizedCounter<4>;
using CharacterAddress = Numeric::SizedCounter<8>;
using RowAddress = Numeric::SizedCounter<7>;
struct BusState { struct BusState {
bool display_enable = false; bool display_enable = false;
bool hsync = false; bool hsync = false;
bool vsync = false; bool vsync = false;
bool cursor = false; bool cursor = false;
uint16_t refresh_address = 0; RefreshAddress refresh; // ma_i
uint16_t row_address = 0; LineAddress row;
// Not strictly part of the bus state; provided because the partition between 6845 and bus handler // Not strictly part of the bus state; provided because the partition between 6845 and bus handler
// doesn't quite hold up in some emulated systems where the two are integrated and share more state. // doesn't quite hold up in some emulated systems where the two are integrated and share more state.
int field_count = 0; int field_count = 0; // field_counter
}; };
class BusHandler { class BusHandler {
@@ -39,14 +54,8 @@ enum class Personality {
UM6845R, // Type 1 in CPC parlance. Status register, fixed-length VSYNC. UM6845R, // Type 1 in CPC parlance. Status register, fixed-length VSYNC.
MC6845, // Type 2. No status register, fixed-length VSYNC, no zero-length HSYNC. MC6845, // Type 2. No status register, fixed-length VSYNC, no zero-length HSYNC.
AMS40226, // Type 3. Status is get register, fixed-length VSYNC, no zero-length HSYNC. AMS40226, // Type 3. Status is get register, fixed-length VSYNC, no zero-length HSYNC.
EGA, // Extended EGA-style CRTC; uses 16-bit addressing throughout.
}; };
constexpr bool is_egavga(const Personality p) {
return p >= Personality::EGA;
}
// https://www.pcjs.org/blog/2018/03/20/ advises that "the behavior of bits 5 and 6 [of register 10, the cursor start // https://www.pcjs.org/blog/2018/03/20/ advises that "the behavior of bits 5 and 6 [of register 10, the cursor start
// register is really card specific". // register is really card specific".
// //
@@ -56,8 +65,6 @@ enum class CursorType {
None, None,
/// MDA style: 00 => symmetric blinking; 01 or 10 => no blinking; 11 => short on, long off. /// MDA style: 00 => symmetric blinking; 01 or 10 => no blinking; 11 => short on, long off.
MDA, MDA,
/// EGA style: ignore the bits completely.
EGA,
}; };
// TODO UM6845R and R12/R13; see http://www.cpcwiki.eu/index.php/CRTC#CRTC_Differences // TODO UM6845R and R12/R13; see http://www.cpcwiki.eu/index.php/CRTC#CRTC_Differences
@@ -90,16 +97,6 @@ public:
} }
void set_register(const uint8_t value) { void set_register(const uint8_t value) {
static constexpr bool is_ega = is_egavga(personality);
const auto load_low = [value](uint16_t &target) {
target = (target & 0xff00) | value;
};
const auto load_high = [value](uint16_t &target) {
static constexpr uint8_t mask = RefreshMask >> 8;
target = uint16_t((target & 0x00ff) | ((value & mask) << 8));
};
switch(selected_register_) { switch(selected_register_) {
case 0: layout_.horizontal.total = value; break; case 0: layout_.horizontal.total = value; break;
case 1: layout_.horizontal.displayed = value; break; case 1: layout_.horizontal.displayed = value; break;
@@ -140,10 +137,10 @@ public:
case 11: case 11:
layout_.vertical.end_cursor = value & 0x1f; layout_.vertical.end_cursor = value & 0x1f;
break; break;
case 12: load_high(layout_.start_address); break; case 12: layout_.start_address.template load<8>(value); break;
case 13: load_low(layout_.start_address); break; case 13: layout_.start_address.template load<0>(value); break;
case 14: load_high(layout_.cursor_address); break; case 14: layout_.cursor_address.template load<8>(value); break;
case 15: load_low(layout_.cursor_address); break; case 15: layout_.cursor_address.template load<0>(value); break;
} }
static constexpr uint8_t masks[] = { static constexpr uint8_t masks[] = {
@@ -154,11 +151,11 @@ public:
// EGA: b0b4: end of horizontal blank; // EGA: b0b4: end of horizontal blank;
// b5b6: "Number of character clocks to delay start of display after Horizontal Total has been reached." // b5b6: "Number of character clocks to delay start of display after Horizontal Total has been reached."
is_ega ? 0xff : 0x7f, // Start horizontal retrace. 0x7f, // Start horizontal retrace.
0x1f, 0x7f, 0x7f, 0x1f, 0x7f, 0x7f,
0xff, 0x1f, 0x7f, 0x1f, 0xff, 0x1f, 0x7f, 0x1f,
uint8_t(RefreshMask >> 8), uint8_t(RefreshMask), uint8_t(RefreshAddress::Mask >> 8), uint8_t(RefreshAddress::Mask),
uint8_t(RefreshMask >> 8), uint8_t(RefreshMask), uint8_t(RefreshAddress::Mask >> 8), uint8_t(RefreshAddress::Mask),
}; };
if(selected_register_ < 16) { if(selected_register_ < 16) {
@@ -170,8 +167,8 @@ public:
} }
void trigger_light_pen() { void trigger_light_pen() {
registers_[17] = bus_state_.refresh_address & 0xff; registers_[17] = bus_state_.refresh.get() & 0xff;
registers_[16] = bus_state_.refresh_address >> 8; registers_[16] = bus_state_.refresh.get() >> 8;
status_ |= 0x40; status_ |= 0x40;
} }
@@ -184,7 +181,7 @@ public:
// Do bus work. // Do bus work.
bus_state_.cursor = is_cursor_line_ && bus_state_.cursor = is_cursor_line_ &&
bus_state_.refresh_address == layout_.cursor_address; bus_state_.refresh == layout_.cursor_address;
bus_state_.display_enable = character_is_visible_ && line_is_visible_; bus_state_.display_enable = character_is_visible_ && line_is_visible_;
bus_handler_.perform_bus_cycle(bus_state_); bus_handler_.perform_bus_cycle(bus_state_);
@@ -192,10 +189,8 @@ public:
// Shared, stateless signals. // Shared, stateless signals.
// //
const bool character_total_hit = character_counter_ == layout_.horizontal.total; const bool character_total_hit = character_counter_ == layout_.horizontal.total;
const uint8_t lines_per_row = const auto lines_per_row = layout_.end_row();
layout_.interlace_mode_ == InterlaceMode::InterlaceSyncAndVideo ? const bool row_end_hit = bus_state_.row == lines_per_row && !is_in_adjustment_period_;
layout_.vertical.end_row & ~1 : layout_.vertical.end_row;
const bool row_end_hit = bus_state_.row_address == lines_per_row && !is_in_adjustment_period_;
const bool was_eof = eof_latched_; const bool was_eof = eof_latched_;
const bool new_frame = const bool new_frame =
character_total_hit && was_eof && character_total_hit && was_eof &&
@@ -269,7 +264,7 @@ public:
(!odd_field_ && !character_counter_) || (!odd_field_ && !character_counter_) ||
(odd_field_ && character_counter_ == (layout_.horizontal.total >> 1)); (odd_field_ && character_counter_ == (layout_.horizontal.total >> 1));
if(vsync_horizontal) { if(vsync_horizontal) {
if((row_counter_ == layout_.vertical.start_sync && !bus_state_.row_address) || bus_state_.vsync) { if((row_counter_ == layout_.vertical.start_sync && !bus_state_.row) || bus_state_.vsync) {
bus_state_.vsync = true; bus_state_.vsync = true;
vsync_counter_ = (vsync_counter_ + 1) & 0xf; vsync_counter_ = (vsync_counter_ + 1) & 0xf;
} else { } else {
@@ -285,14 +280,14 @@ public:
// Row address. // Row address.
if(character_total_hit) { if(character_total_hit) {
if(was_eof) { if(was_eof) {
bus_state_.row_address = 0; bus_state_.row = 0;
eof_latched_ = eom_latched_ = false; eof_latched_ = eom_latched_ = false;
} else if(row_end_hit) { } else if(row_end_hit) {
bus_state_.row_address = 0; bus_state_.row = 0;
} else if(layout_.interlace_mode_ == InterlaceMode::InterlaceSyncAndVideo) { } else if(layout_.interlace_mode_ == InterlaceMode::InterlaceSyncAndVideo) {
bus_state_.row_address = (bus_state_.row_address + 2) & ~1 & 31; bus_state_.row += 2;
} else { } else {
bus_state_.row_address = (bus_state_.row_address + 1) & 31; ++bus_state_.row;
} }
} }
@@ -316,12 +311,18 @@ public:
++bus_state_.field_count; ++bus_state_.field_count;
} }
// Cursor. // Cursor.
if constexpr (cursor_type != CursorType::None) { if constexpr (cursor_type != CursorType::None) {
// Check for cursor enable. // Check for cursor enable.
is_cursor_line_ |= bus_state_.row_address == layout_.vertical.start_cursor; is_cursor_line_ |= bus_state_.row == layout_.vertical.start_cursor;
is_cursor_line_ &= bus_state_.row_address != layout_.vertical.end_cursor; is_cursor_line_ &= !(
(bus_state_.row == layout_.vertical.end_cursor) ||
(
character_total_hit &&
row_end_hit &&
layout_.vertical.end_cursor == (lines_per_row + LineAddress(1))
)
);
switch(cursor_type) { switch(cursor_type) {
// MDA-style blinking. // MDA-style blinking.
@@ -344,17 +345,17 @@ public:
// //
if(new_frame) { if(new_frame) {
bus_state_.refresh_address = layout_.start_address; bus_state_.refresh = layout_.start_address;
} else if(character_total_hit) { } else if(character_total_hit) {
bus_state_.refresh_address = line_address_; bus_state_.refresh = line_address_;
} else { } else {
bus_state_.refresh_address = (bus_state_.refresh_address + 1) & RefreshMask; ++bus_state_.refresh;
} }
if(new_frame) { if(new_frame) {
line_address_ = layout_.start_address; line_address_ = layout_.start_address;
} else if(character_counter_ == layout_.horizontal.displayed && row_end_hit) { } else if(character_counter_ == layout_.horizontal.displayed && row_end_hit) {
line_address_ = bus_state_.refresh_address; line_address_ = bus_state_.refresh;
} }
} }
} }
@@ -364,8 +365,6 @@ public:
} }
private: private:
static constexpr uint16_t RefreshMask = (personality >= Personality::EGA) ? 0xffff : 0x3fff;
BusHandlerT &bus_handler_; BusHandlerT &bus_handler_;
BusState bus_state_; BusState bus_state_;
@@ -377,56 +376,60 @@ private:
enum class BlinkMode { enum class BlinkMode {
// TODO. // TODO.
}; };
// Comments on the right provide the corresponding signal name in hoglet's VHDL implementation.
struct { struct {
struct { struct {
uint8_t total; CharacterAddress total; // r00_h_total
uint8_t displayed; CharacterAddress displayed; // r01_h_displayed
uint8_t start_sync; CharacterAddress start_sync; // r02_h_sync_pos
uint8_t sync_width; SyncCounter sync_width; // r03_h_sync_width
} horizontal; } horizontal;
struct { struct {
uint8_t total; RowAddress total; // r04_v_total
uint8_t displayed; RowAddress displayed; // r06_v_displayed
uint8_t start_sync; RowAddress start_sync; // r07_v_sync_pos
uint8_t sync_lines; SyncCounter sync_lines; // r03_v_sync_width
uint8_t adjust; LineAddress adjust; // r05_v_total_adj
uint8_t end_row; LineAddress end_row; // r09_max_scanline_addr
uint8_t start_cursor; LineAddress start_cursor; // r10_cursor_start
uint8_t end_cursor; LineAddress end_cursor; // r11_cursor_end
} vertical; } vertical;
InterlaceMode interlace_mode_ = InterlaceMode::Off; InterlaceMode interlace_mode_ = InterlaceMode::Off; // r08_interlace
uint8_t end_row() const { LineAddress end_row() const {
return interlace_mode_ == InterlaceMode::InterlaceSyncAndVideo ? vertical.end_row & ~1 : vertical.end_row; return interlace_mode_ == InterlaceMode::InterlaceSyncAndVideo ?
vertical.end_row & ~1 : vertical.end_row;
} }
uint16_t start_address; RefreshAddress start_address; // r12_start_addr_h + r13_start_addr_l
uint16_t cursor_address; RefreshAddress cursor_address; // r14_cursor_h + r15_cursor_l
uint16_t light_pen_address; RefreshAddress light_pen_address; // r16_light_pen_h + r17_light_pen_l
uint8_t cursor_flags; uint8_t cursor_flags; // r10_cursor_mode
} layout_; } layout_;
uint8_t registers_[18]{}; uint8_t registers_[18]{};
uint8_t dummy_register_ = 0; uint8_t dummy_register_ = 0;
int selected_register_ = 0; int selected_register_ = 0;
uint8_t character_counter_ = 0; CharacterAddress character_counter_ = 0; // h_counter
uint32_t character_reset_history_ = 0; uint32_t character_reset_history_ = 0;
uint8_t row_counter_ = 0, next_row_counter_ = 0; RowAddress row_counter_ = 0; // row_counter
RowAddress next_row_counter_ = 0; // row_counter_next
uint8_t adjustment_counter_ = 0; uint8_t adjustment_counter_ = 0;
bool character_is_visible_ = false; bool character_is_visible_ = false; // h_display
bool line_is_visible_ = false; bool line_is_visible_ = false; // v_display
bool is_first_scanline_ = false; bool is_first_scanline_ = false;
bool is_cursor_line_ = false; bool is_cursor_line_ = false;
int hsync_counter_ = 0; SyncCounter hsync_counter_; // h_sync_counter
int vsync_counter_ = 0; SyncCounter vsync_counter_; // v_sync_counter
bool is_in_adjustment_period_ = false; bool is_in_adjustment_period_ = false;
uint16_t line_address_ = 0; RefreshAddress line_address_;
uint8_t status_ = 0; uint8_t status_ = 0;
int display_skew_mask_ = 1; int display_skew_mask_ = 1;
@@ -434,8 +437,17 @@ private:
bool eof_latched_ = false; bool eof_latched_ = false;
bool eom_latched_ = false; bool eom_latched_ = false;
uint16_t next_row_address_ = 0; bool odd_field_ = false; // odd_field
bool odd_field_ = false;
// line_counter
// line_counter_next
// hs
// vs
// vs_hit
// vs_hit_last
// vs_even
// vs_odd
}; };
} }

View File

@@ -0,0 +1,81 @@
//
// uPD7002.cpp
// Clock Signal
//
// Created by Thomas Harte on 17/09/2025.
// Copyright © 2025 Thomas Harte. All rights reserved.
//
#include "uPD7002.hpp"
using namespace NEC;
uPD7002::uPD7002(const HalfCycles clock_rate) {
// Per the BBC AUG: "8 bit conversions typically take 4 ms to complete whereas 10 bit
// conversions typically take 10 ms to complete".
fast_period_ = clock_rate / 250;
slow_period_ = clock_rate / 100;
}
void uPD7002::run_for(const HalfCycles count) {
if(!conversion_time_remaining_) {
return;
}
if(count >= conversion_time_remaining_) {
conversion_time_remaining_ = HalfCycles(0);
result_ = uint16_t(inputs_[channel_] * 65535.0f) & (high_precision_ ? 0xfff0 : 0xff00);
set_interrupt(true);
return;
}
conversion_time_remaining_ -= count;
}
bool uPD7002::interrupt() const {
return interrupt_;
}
void uPD7002::write(const uint16_t address, const uint8_t value) {
const auto local_address = address & 3;
if(!local_address) {
channel_ = value & 0b0000'0011;
spare_ = value & 0b0000'0100;
high_precision_ = value & 0b0000'1000;
conversion_time_remaining_ = high_precision_ ? slow_period_ : fast_period_;
set_interrupt(false);
return;
}
}
uint8_t uPD7002::read(const uint16_t address) {
switch(address & 3) {
default:
case 0: return status();
case 1:
set_interrupt(false);
return uint8_t(result_ >> 8);
case 2: return uint8_t(result_);
case 3: return 0xff;
}
}
uint8_t uPD7002::status() const {
return
channel_ |
spare_ |
(high_precision_ ? 0x08 : 0) |
((result_ >> 14) & 0x30) |
(conversion_time_remaining_ > HalfCycles(0) ? 0x00 : 0x40) |
(interrupt_ ? 0x00 : 0x80);
}
void uPD7002::set_delegate(Delegate *const delegate) {
delegate_ = delegate;
}
void uPD7002::set_interrupt(const bool value) {
if(interrupt_ == value) return;
interrupt_ = value;
if(delegate_) delegate_->did_change_interrupt_status(*this);
}

View File

@@ -0,0 +1,51 @@
//
// uPD7002.hpp
// Clock Signal
//
// Created by Thomas Harte on 17/09/2025.
// Copyright © 2025 Thomas Harte. All rights reserved.
//
#include "ClockReceiver/ClockReceiver.hpp"
namespace NEC {
class uPD7002 {
public:
/// Constructs a PD7002 that will receive @c run_for updates at the specified clock rate.
uPD7002(HalfCycles clock_rate);
void run_for(HalfCycles);
/// @returns The current state of the interrupt line.
bool interrupt() const;
/// Defines a mean for an observer to receive notifications upon updates to the interrupt line.
struct Delegate {
virtual void did_change_interrupt_status(uPD7002 &) = 0;
};
void set_delegate(Delegate *);
void write(uint16_t address, uint8_t value);
uint8_t read(uint16_t address);
/// Sets the floating point value, which should be in the range [0.0, 1.0], for the signal currently
/// being supplied to @c channel.
void set_input(int channel, float value);
private:
float inputs_[4]{};
uint16_t result_ = 0;
bool interrupt_ = false;
uint8_t channel_ = 0, spare_ = 0;
bool high_precision_ = false;
HalfCycles conversion_time_remaining_{};
HalfCycles fast_period_, slow_period_;
uint8_t status() const;
void set_interrupt(bool);
Delegate *delegate_ = nullptr;
};
}

View File

@@ -7,7 +7,8 @@
// //
#include "BBCMicro.hpp" #include "BBCMicro.hpp"
#include "Keyboard.hpp"
#include "Activity/Source.hpp"
#include "Machines/MachineTypes.hpp" #include "Machines/MachineTypes.hpp"
#include "Machines/Utility/MemoryFuzzer.hpp" #include "Machines/Utility/MemoryFuzzer.hpp"
@@ -18,6 +19,10 @@
#include "Components/6845/CRTC6845.hpp" #include "Components/6845/CRTC6845.hpp"
#include "Components/SN76489/SN76489.hpp" #include "Components/SN76489/SN76489.hpp"
#include "Components/6850/6850.hpp" #include "Components/6850/6850.hpp"
#include "Components/uPD7002/uPD7002.hpp"
// TODO: factor this more appropriately.
#include "Machines/Acorn/Electron/Plus3.hpp"
#include "Analyser/Static/Acorn/Target.hpp" #include "Analyser/Static/Acorn/Target.hpp"
#include "Outputs/Log.hpp" #include "Outputs/Log.hpp"
@@ -26,6 +31,8 @@
#include "Outputs/Speaker/Implementation/LowpassSpeaker.hpp" #include "Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
#include "Concurrency/AsyncTaskQueue.hpp" #include "Concurrency/AsyncTaskQueue.hpp"
#include "Keyboard.hpp"
#include <algorithm> #include <algorithm>
#include <array> #include <array>
#include <bitset> #include <bitset>
@@ -42,10 +49,10 @@ using Logger = Log::Logger<Log::Source::BBCMicro>;
*/ */
struct Audio { struct Audio {
Audio() : Audio() :
sn76489_(TI::SN76489::Personality::SN76489, audio_queue_), sn76489_(TI::SN76489::Personality::SN76489, audio_queue_, 2),
speaker_(sn76489_) speaker_(sn76489_)
{ {
// I'm *VERY* unsure about this. // Combined with the additional divider specified above, implies this chip is clocked at 4Mhz.
speaker_.set_input_rate(2'000'000.0f); speaker_.set_input_rate(2'000'000.0f);
} }
@@ -54,16 +61,16 @@ struct Audio {
} }
TI::SN76489 *operator ->() { TI::SN76489 *operator ->() {
flush(); speaker_.run_for(audio_queue_, time_since_update_.flush<Cycles>());
return &sn76489_; return &sn76489_;
} }
void operator +=(const HalfCycles duration) { void operator +=(const Cycles duration) {
speaker_.run_for(audio_queue_, time_since_update_.flush<Cycles>());
time_since_update_ += duration; time_since_update_ += duration;
} }
void flush() { void flush() {
speaker_.run_for(audio_queue_, time_since_update_.flush<Cycles>());
audio_queue_.perform(); audio_queue_.perform();
} }
@@ -75,7 +82,7 @@ private:
Concurrency::AsyncTaskQueue<false> audio_queue_; Concurrency::AsyncTaskQueue<false> audio_queue_;
TI::SN76489 sn76489_; TI::SN76489 sn76489_;
Outputs::Speaker::PullLowpass<TI::SN76489> speaker_; Outputs::Speaker::PullLowpass<TI::SN76489> speaker_;
HalfCycles time_since_update_; Cycles time_since_update_;
}; };
/*! /*!
@@ -157,7 +164,18 @@ struct SystemVIAPortHandler: public MOS::MOS6522::IRQDelegatePortHandler {
// Update keyboard LEDs. // Update keyboard LEDs.
if(mask >= 0x40) { if(mask >= 0x40) {
Logger::info().append("CAPS: %d SHIFT: %d", bool(latch_ & 0x40), bool(latch_ & 0x40)); const bool new_caps = latch_ & 0x80;
const bool new_shift = latch_ & 0x40;
if(new_caps != caps_led_state_) {
caps_led_state_ = new_caps;
activity_observer_->set_led_status(caps_led, caps_led_state_);
}
if(new_shift != shift_led_state_) {
shift_led_state_ = new_shift;
activity_observer_->set_led_status(shift_led, shift_led_state_);
}
} }
} }
@@ -199,6 +217,18 @@ struct SystemVIAPortHandler: public MOS::MOS6522::IRQDelegatePortHandler {
keyboard_scan_column_ = ending_column; keyboard_scan_column_ = ending_column;
} }
void set_activity_observer(Activity::Observer *const observer) {
activity_observer_ = observer;
if(activity_observer_) {
activity_observer_->register_led(caps_led, Activity::Observer::LEDPresentation::Persistent);
activity_observer_->register_led(shift_led, Activity::Observer::LEDPresentation::Persistent);
activity_observer_->set_led_status(caps_led, caps_led_state_);
activity_observer_->set_led_status(shift_led, shift_led_state_);
}
}
private: private:
uint8_t latch_ = 0; uint8_t latch_ = 0;
enum LatchFlags: uint8_t { enum LatchFlags: uint8_t {
@@ -241,6 +271,12 @@ private:
via_.set_control_line_input<MOS::MOS6522::Port::A, MOS::MOS6522::Line::Two>(state); via_.set_control_line_input<MOS::MOS6522::Port::A, MOS::MOS6522::Line::Two>(state);
} }
static inline const std::string caps_led = "CAPS";
static inline const std::string shift_led = "SHIFT";
bool caps_led_state_ = false;
bool shift_led_state_ = false;
Activity::Observer *activity_observer_ = nullptr;
}; };
/*! /*!
@@ -255,16 +291,27 @@ public:
void set_palette(const uint8_t value) { void set_palette(const uint8_t value) {
const auto index = value >> 4; const auto index = value >> 4;
Logger::info().append("Palette entry %d set to %x", index, value & 0xf); palette_[index] = uint8_t(
7 ^ (
((value & 0b100) >> 2) |
((value & 0b001) << 2) |
(value & 0b010)
)
);
flash_flags_[size_t(index)] = value & 0b1000;
} }
void set_control(const uint8_t value) { void set_control(const uint8_t value) {
Logger::info().append("Video control set to %x", value); crtc_clock_multiplier_ = (value & 0x10) ? 1 : 2;
cycle_length_ = (value & 0x10) ? 8 : 16;
Logger::info().append("TODO: video control => flash %d", bool(value & 0x01)); active_collation_.pixels_per_clock = 1 << ((value >> 2) & 0x03);
Logger::info().append("TODO: video control => teletext %d", bool(value & 0x02)); active_collation_.is_teletext = value & 0x02;
Logger::info().append("TODO: video control => columns %d", (value >> 2) & 0x03); if(active_collation_.is_teletext) {
Logger::info().append("TODO: video control => cursor segment %d%d%d", bool(value & 0x80), bool(value & 0x40), bool(value & 0x20)); Logger::error().append("TODO: video control => teletext %d", bool(value & 0x02));
}
flash_mask_ = value & 0x01 ? 7 : 0;
cursor_mask_ = value & 0b1110'0000;
} }
/*! /*!
@@ -274,41 +321,40 @@ public:
void perform_bus_cycle(const Motorola::CRTC::BusState &state) { void perform_bus_cycle(const Motorola::CRTC::BusState &state) {
system_via_.set_control_line_input<MOS::MOS6522::Port::A, MOS::MOS6522::Line::One>(state.vsync); system_via_.set_control_line_input<MOS::MOS6522::Port::A, MOS::MOS6522::Line::One>(state.vsync);
// bool print = false;
// uint16_t start_address = 0x7c00;
// int rows = 24;
// if(print) {
// for(int y = 0; y < rows; y++) {
// for(int x = 0; x < 40; x++) {
// printf("%c", ram_[start_address + y*40 + x]);
// }
// printf("\n");
// }
// }
// Count cycles since horizontal sync to insert a colour burst. // Count cycles since horizontal sync to insert a colour burst.
if(state.hsync) { if(state.hsync) {
++cycles_into_hsync_; ++cycles_into_hsync_;
} else { } else {
cycles_into_hsync_ = 0; cycles_into_hsync_ = 0;
} }
const bool is_colour_burst = (cycles_into_hsync_ >= 5 && cycles_into_hsync_ < 9); const bool is_colour_burst = cycles_into_hsync_ >= 5 && cycles_into_hsync_ < 9;
// Sync is taken to override pixels, and is combined as a simple OR. // Sync is taken to override pixels, and is combined as a simple OR.
const bool is_sync = state.hsync || state.vsync; const bool is_sync = state.hsync || state.vsync;
const bool is_blank = !is_sync && state.hsync;
// Check for a cursor leading edge.
cursor_shifter_ >>= 4;
if(state.cursor != previous_cursor_enabled_) {
if(state.cursor && state.display_enable) { // TODO: should I have to test display enable here? Or should
// the CRTC already have factored that in?
cursor_shifter_ =
((cursor_mask_ & 0x80) ? 0x0007 : 0) |
((cursor_mask_ & 0x40) ? 0x0070 : 0) |
((cursor_mask_ & 0x20) ? 0x7700 : 0);
}
previous_cursor_enabled_ = state.cursor;
}
OutputMode output_mode; OutputMode output_mode;
const bool should_fetch = state.display_enable && !(state.row.get() & 8);
if(is_sync) { if(is_sync) {
output_mode = OutputMode::Sync; output_mode = OutputMode::Sync;
} else if(is_colour_burst) { } else if(is_colour_burst) {
output_mode = OutputMode::ColourBurst; output_mode = OutputMode::ColourBurst;
} else if(is_blank) { } else if(should_fetch || cursor_shifter_) {
output_mode = OutputMode::Blank;
} else if(state.display_enable) {
output_mode = OutputMode::Pixels; output_mode = OutputMode::Pixels;
} else { } else {
output_mode = OutputMode::Border; output_mode = OutputMode::Blank;
} }
// If a transition between sync/border/pixels just occurred, flush whatever was // If a transition between sync/border/pixels just occurred, flush whatever was
@@ -319,13 +365,8 @@ public:
default: default:
case OutputMode::Blank: crt_.output_blank(cycles_); break; case OutputMode::Blank: crt_.output_blank(cycles_); break;
case OutputMode::Sync: crt_.output_sync(cycles_); break; case OutputMode::Sync: crt_.output_sync(cycles_); break;
case OutputMode::Border: crt_.output_blank(cycles_); break;
case OutputMode::ColourBurst: crt_.output_default_colour_burst(cycles_); break; case OutputMode::ColourBurst: crt_.output_default_colour_burst(cycles_); break;
case OutputMode::Pixels: case OutputMode::Pixels: flush_pixels(); break;
crt_.output_data(cycles_, pixels_);
pixel_pointer_ = pixel_data_ = nullptr;
pixels_ = 0;
break;
} }
} }
@@ -334,57 +375,49 @@ public:
} }
// Increment cycles since state changed. // Increment cycles since state changed.
cycles_ += cycle_length_; cycles_ += crtc_clock_multiplier_ << 3;
// Collect some more pixels if output is ongoing. // Collect some more pixels if output is ongoing.
if(previous_output_mode_ == OutputMode::Pixels) { if(previous_output_mode_ == OutputMode::Pixels) {
// Flush the current buffer pixel if full; the CRTC allows many different display
// widths so it's not necessarily possible to predict the correct number in advance
// and using the upper bound could lead to inefficient behaviour.
if(pixel_data_ && (pixels_collected() == 320 || active_collation_ != previous_collation_)) {
flush_pixels();
cycles_ = 0;
}
previous_collation_ = active_collation_;
if(!pixel_data_) { if(!pixel_data_) {
pixel_pointer_ = pixel_data_ = crt_.begin_data(320, 8); pixel_pointer_ = pixel_data_ = crt_.begin_data(320, 8);
} }
if(pixel_pointer_) { if(pixel_pointer_) {
uint16_t address; uint16_t address;
if(state.refresh_address & (1 << 13)) { if(state.refresh.get() & (1 << 13)) {
// Teletext address generation mode. // Teletext address generation mode.
address = uint16_t( address = uint16_t(
0x3c00 | 0x3c00 |
((state.refresh_address & 0x800) << 3) | ((state.refresh.get() & 0x800) << 3) |
(state.refresh_address & 0x3ff) (state.refresh.get() & 0x3ff)
); );
// TODO: wraparound? // TODO: wraparound? Does that happen on Mode 7?
} else { } else {
address = uint16_t((state.refresh_address << 3) | (state.row_address & 7)); address = uint16_t((state.refresh.get() << 3) | (state.row.get() & 7));
if(address & 0x8000) { if(address & 0x8000) {
address = (address + video_base_) & 0x7fff; address = (address + video_base_) & 0x7fff;
} }
} }
// Hard coded from here for Mode 0! // Hard coded: pixel mode!
if(state.row_address & 8) { pixel_shifter_ = should_fetch ? ram_[address] : 0;
std::fill(pixel_pointer_, pixel_pointer_+8, 0); switch(crtc_clock_multiplier_ * active_collation_.pixels_per_clock) {
} else { case 1: shift_pixels<1>(cursor_shifter_ & 7); break;
const auto source = ram_[address]; case 2: shift_pixels<2>(cursor_shifter_ & 7); break;
pixel_pointer_[0] = (source & 0x80) ? 0xff : 0x00; case 4: shift_pixels<4>(cursor_shifter_ & 7); break;
pixel_pointer_[1] = (source & 0x40) ? 0xff : 0x00; case 8: shift_pixels<8>(cursor_shifter_ & 7); break;
pixel_pointer_[2] = (source & 0x20) ? 0xff : 0x00; case 16: shift_pixels<16>(cursor_shifter_ & 7); break;
pixel_pointer_[3] = (source & 0x10) ? 0xff : 0x00; default: break;
pixel_pointer_[4] = (source & 0x08) ? 0xff : 0x00;
pixel_pointer_[5] = (source & 0x04) ? 0xff : 0x00;
pixel_pointer_[6] = (source & 0x02) ? 0xff : 0x00;
pixel_pointer_[7] = (source & 0x01) ? 0xff : 0x00;
}
pixel_pointer_ += 8;
pixels_ += 8;
// Flush the current buffer pixel if full; the CRTC allows many different display
// widths so it's not necessarily possible to predict the correct number in advance
// and using the upper bound could lead to inefficient behaviour.
if(pixels_ == 320) {
crt_.output_data(cycles_, pixels_);
pixel_pointer_ = pixel_data_ = nullptr;
cycles_ = 0;
pixels_ = 0;
} }
} }
} }
@@ -410,23 +443,61 @@ public:
return crt_.get_display_type(); return crt_.get_display_type();
} }
private: private:
enum class OutputMode { enum class OutputMode {
Sync, Sync,
Blank, Blank,
ColourBurst, ColourBurst,
Border,
Pixels Pixels
} previous_output_mode_ = OutputMode::Sync; };
struct PixelCollation {
int pixels_per_clock;
bool is_teletext;
bool operator !=(const PixelCollation &rhs) {
if(is_teletext && rhs.is_teletext) return false;
return pixels_per_clock != rhs.pixels_per_clock;
}
};
OutputMode previous_output_mode_ = OutputMode::Sync;
int cycles_ = 0; int cycles_ = 0;
int cycles_into_hsync_ = 0; int cycles_into_hsync_ = 0;
int cycle_length_ = 8;
Outputs::CRT::CRT crt_; Outputs::CRT::CRT crt_;
uint8_t *pixel_data_ = nullptr, *pixel_pointer_ = nullptr; uint8_t *pixel_data_ = nullptr, *pixel_pointer_ = nullptr;
size_t pixels_; size_t pixels_collected() const {
return size_t(pixel_pointer_ - pixel_data_);
}
void flush_pixels() {
crt_.output_data(cycles_, pixels_collected());
pixel_pointer_ = pixel_data_ = nullptr;
}
PixelCollation previous_collation_;
uint8_t palette_[16];
std::bitset<16> flash_flags_;
uint8_t flash_mask_ = 0;
int crtc_clock_multiplier_ = 1;
PixelCollation active_collation_;
uint8_t pixel_shifter_ = 0;
uint8_t cursor_mask_ = 0;
uint32_t cursor_shifter_ = 0;
bool previous_cursor_enabled_ = false;
template <int count> void shift_pixels(const uint8_t cursor_mask) {
for(int c = 0; c < count; c++) {
const uint8_t colour =
((pixel_shifter_ & 0x80) >> 4) |
((pixel_shifter_ & 0x20) >> 3) |
((pixel_shifter_ & 0x08) >> 2) |
((pixel_shifter_ & 0x02) >> 1);
pixel_shifter_ <<= 1;
*pixel_pointer_++ = palette_[colour] ^ (flash_flags_[colour] ? flash_mask_ : 0x00) ^ cursor_mask;
}
}
const uint8_t *const ram_ = nullptr; const uint8_t *const ram_ = nullptr;
SystemVIA &system_via_; SystemVIA &system_via_;
@@ -434,16 +505,21 @@ private:
using CRTC = Motorola::CRTC::CRTC6845< using CRTC = Motorola::CRTC::CRTC6845<
CRTCBusHandler, CRTCBusHandler,
Motorola::CRTC::Personality::HD6845S, Motorola::CRTC::Personality::HD6845S,
Motorola::CRTC::CursorType::None>; Motorola::CRTC::CursorType::MDA>;
} }
template <bool has_1770>
class ConcreteMachine: class ConcreteMachine:
public Activity::Source,
public Machine, public Machine,
public MachineTypes::AudioProducer, public MachineTypes::AudioProducer,
public MachineTypes::MappedKeyboardMachine, public MachineTypes::MappedKeyboardMachine,
public MachineTypes::MediaTarget,
public MachineTypes::ScanProducer, public MachineTypes::ScanProducer,
public MachineTypes::TimedMachine, public MachineTypes::TimedMachine,
public MOS::MOS6522::IRQDelegatePortHandler::Delegate public MOS::MOS6522::IRQDelegatePortHandler::Delegate,
public NEC::uPD7002::Delegate,
public WD::WD1770::Delegate
{ {
public: public:
ConcreteMachine( ConcreteMachine(
@@ -456,17 +532,27 @@ public:
system_via_(system_via_port_handler_), system_via_(system_via_port_handler_),
crtc_bus_handler_(ram_.data(), system_via_), crtc_bus_handler_(ram_.data(), system_via_),
crtc_(crtc_bus_handler_), crtc_(crtc_bus_handler_),
acia_(HalfCycles(2'000'000)) // TODO: look up real ACIA clock rate. acia_(HalfCycles(2'000'000)), // TODO: look up real ACIA clock rate.
adc_(HalfCycles(2'000'000))
{ {
set_clock_rate(2'000'000); set_clock_rate(2'000'000);
system_via_port_handler_.set_interrupt_delegate(this); system_via_port_handler_.set_interrupt_delegate(this);
user_via_port_handler_.set_interrupt_delegate(this); user_via_port_handler_.set_interrupt_delegate(this);
adc_.set_delegate(this);
// Grab ROMs. // Grab ROMs.
using Request = ::ROM::Request; using Request = ::ROM::Request;
using Name = ::ROM::Name; using Name = ::ROM::Name;
const auto request = Request(Name::AcornBASICII) && Request(Name::BBCMicroMOS12);
auto request = Request(Name::AcornBASICII) && Request(Name::BBCMicroMOS12);
if(target.has_1770dfs) {
request = request && Request(Name::BBCMicroDFS226);
}
if(target.has_adfs) {
request = request && Request(Name::BBCMicroADFS130);
}
auto roms = rom_fetcher(request); auto roms = rom_fetcher(request);
if(!request.validate(roms)) { if(!request.validate(roms)) {
throw ROMMachine::Error::MissingROMs; throw ROMMachine::Error::MissingROMs;
@@ -475,8 +561,27 @@ public:
const auto os_data = roms.find(Name::BBCMicroMOS12)->second; const auto os_data = roms.find(Name::BBCMicroMOS12)->second;
std::copy(os_data.begin(), os_data.end(), os_.begin()); std::copy(os_data.begin(), os_data.end(), os_.begin());
// Put BASIC in pole position.
install_sideways(15, roms.find(Name::AcornBASICII)->second, false); install_sideways(15, roms.find(Name::AcornBASICII)->second, false);
// Install filing systems: put the DFS before the ADFS because it's more common on the BBC.
size_t fs_slot = 14;
if(target.has_1770dfs) {
install_sideways(fs_slot--, roms.find(Name::BBCMicroDFS226)->second, false);
}
if(target.has_adfs) {
install_sideways(fs_slot--, roms.find(Name::BBCMicroADFS130)->second, false);
}
// Throw sideways RAM into all unused slots.
if(target.has_sideways_ram) {
for(size_t c = 0; c < 16; c++) {
if(!rom_inserted_[c]) {
rom_inserted_[c] = rom_write_masks_[c] = true;
}
}
}
// Setup fixed parts of memory map. // Setup fixed parts of memory map.
page(0, &ram_[0], true); page(0, &ram_[0], true);
page(1, &ram_[16384], true); page(1, &ram_[16384], true);
@@ -484,7 +589,11 @@ public:
page(3, os_.data(), false); page(3, os_.data(), false);
Memory::Fuzz(ram_); Memory::Fuzz(ram_);
(void)target; if constexpr (has_1770) {
wd1770_.set_delegate(this);
}
insert_media(target.media);
} }
// MARK: - 6502 bus. // MARK: - 6502 bus.
@@ -516,7 +625,7 @@ public:
}; };
// Determine whether this access hits the 1Mhz bus; if so then apply appropriate penalty, and update phase. // Determine whether this access hits the 1Mhz bus; if so then apply appropriate penalty, and update phase.
const auto duration = is_1mhz(address) ? Cycles(2 + (phase_&1)) : Cycles(1); const auto duration = Cycles(is_1mhz(address) ? 2 + (phase_&1) : 1);
phase_ += duration.as<int>(); phase_ += duration.as<int>();
@@ -524,7 +633,6 @@ public:
// 1Mhz devices. // 1Mhz devices.
// //
const auto half_cycles = HalfCycles(duration.as_integral()); const auto half_cycles = HalfCycles(duration.as_integral());
audio_ += half_cycles;
system_via_.run_for(half_cycles); system_via_.run_for(half_cycles);
system_via_port_handler_.advance_keyboard_scan(half_cycles); system_via_port_handler_.advance_keyboard_scan(half_cycles);
user_via_.run_for(half_cycles); user_via_.run_for(half_cycles);
@@ -533,7 +641,7 @@ public:
// //
// 2Mhz devices. // 2Mhz devices.
// //
// TODO: if CRTC clock is 1Mhz, adapt. audio_ += duration;
if(crtc_2mhz_) { if(crtc_2mhz_) {
crtc_.run_for(duration); crtc_.run_for(duration);
} else { } else {
@@ -541,8 +649,14 @@ public:
const auto cycles = (phase_ >> 1) - ((phase_ - duration.as<int>()) >> 1); const auto cycles = (phase_ >> 1) - ((phase_ - duration.as<int>()) >> 1);
crtc_.run_for(Cycles(cycles)); crtc_.run_for(Cycles(cycles));
} }
adc_.run_for(duration);
if constexpr (has_1770) {
// The WD1770 is nominally clocked at 8Mhz.
wd1770_.run_for(duration * 4);
}
// //
// Questionably-clocked devices. // Questionably-clocked devices.
// //
@@ -552,7 +666,6 @@ public:
// //
// Check for an IO access; if found then perform that and exit. // Check for an IO access; if found then perform that and exit.
// //
// static bool log = false;
if(address >= 0xfc00 && address < 0xff00) { if(address >= 0xfc00 && address < 0xff00) {
if(address >= 0xfe40 && address < 0xfe60) { if(address >= 0xfe40 && address < 0xfe60) {
if(is_read(operation)) { if(is_read(operation)) {
@@ -615,6 +728,27 @@ public:
// Logger::info().append("ACIA write: %02x", *value); // Logger::info().append("ACIA write: %02x", *value);
acia_.write(address, *value); acia_.write(address, *value);
} }
} else if(address >= 0xfec0 && address < 0xfee0) {
if(is_read(operation)) {
*value = adc_.read(address);
} else {
adc_.write(address, *value);
}
} else if(has_1770 && address >= 0xfe80 && address < 0xfe88) {
switch(address) {
case 0xfe80:
if(!is_read(operation)) {
wd1770_.set_control_register(*value);
}
break;
default:
if(is_read(operation)) {
*value = wd1770_.read(address);
} else {
wd1770_.write(address, *value);
}
break;
}
} }
else { else {
Logger::error() Logger::error()
@@ -627,14 +761,6 @@ public:
// //
// ROM or RAM access. // ROM or RAM access.
// //
// if(operation == CPU::MOS6502Esque::BusOperation::ReadOpcode) {
// log |= address == 0xc4c0;
//
// if(log) {
// printf("%04x\n", address);
// }
// }
if(is_read(operation)) { if(is_read(operation)) {
// TODO: probably don't do this with this condition? See how it compiles. If it's a CMOV somehow, no problem. // TODO: probably don't do this with this condition? See how it compiles. If it's a CMOV somehow, no problem.
if((address >> 14) == 2 && !sideways_read_mask_) { if((address >> 14) == 2 && !sideways_read_mask_) {
@@ -645,10 +771,6 @@ public:
} else { } else {
if(memory_write_masks_[address >> 14]) { if(memory_write_masks_[address >> 14]) {
memory_[address >> 14][address] = *value; memory_[address >> 14][address] = *value;
if(address >= 0x7c00 && *value) {
Logger::info().append("Output character: %c", *value);
}
} }
} }
@@ -656,6 +778,14 @@ public:
} }
private: private:
// MARK: - Activity::Source.
void set_activity_observer(Activity::Observer *const observer) override {
if(has_1770) {
wd1770_.set_activity_observer(observer);
}
system_via_port_handler_.set_activity_observer(observer);
}
// MARK: - AudioProducer. // MARK: - AudioProducer.
Outputs::Speaker::Speaker *get_speaker() override { Outputs::Speaker::Speaker *get_speaker() override {
return audio_.speaker(); return audio_.speaker();
@@ -700,6 +830,20 @@ private:
update_irq_line(); update_irq_line();
} }
// MARK: - uPD7002::Delegate.
void did_change_interrupt_status(NEC::uPD7002 &) override {
system_via_.set_control_line_input<MOS::MOS6522::Port::B, MOS::MOS6522::Line::One>(adc_.interrupt());
}
// MARK: - MediaTarget.
bool insert_media(const Analyser::Static::Media &media) override {
if(!media.disks.empty() && has_1770) {
wd1770_.set_disk(media.disks.front(), 0);
}
return !media.disks.empty();
}
// MARK: - Clock phase. // MARK: - Clock phase.
int phase_ = 0; int phase_ = 0;
@@ -755,6 +899,14 @@ private:
bool crtc_2mhz_ = true; bool crtc_2mhz_ = true;
Motorola::ACIA::ACIA acia_; Motorola::ACIA::ACIA acia_;
NEC::uPD7002 adc_;
// MARK: - WD1770.
Electron::Plus3 wd1770_;
void wd1770_did_change_output(WD::WD1770 &) override {
m6502_.set_nmi_line(wd1770_.get_interrupt_request_line() || wd1770_.get_data_request_line());
}
}; };
} }
@@ -767,5 +919,9 @@ std::unique_ptr<Machine> Machine::BBCMicro(
) { ) {
using Target = Analyser::Static::Acorn::BBCMicroTarget; using Target = Analyser::Static::Acorn::BBCMicroTarget;
const Target *const acorn_target = dynamic_cast<const Target *>(target); const Target *const acorn_target = dynamic_cast<const Target *>(target);
return std::make_unique<BBCMicro::ConcreteMachine>(*acorn_target, rom_fetcher); if(acorn_target->has_1770dfs || acorn_target->has_adfs) {
return std::make_unique<BBCMicro::ConcreteMachine<true>>(*acorn_target, rom_fetcher);
} else {
return std::make_unique<BBCMicro::ConcreteMachine<false>>(*acorn_target, rom_fetcher);
}
} }

View File

@@ -52,11 +52,13 @@ private:
{Key::Hyphen, 0x17}, {Key::Hyphen, 0x17},
{Key::Equals, 0x18}, {Key::Equals, 0x18},
{Key::Quote, 0x69}, {Key::BackTick, 0x69},
{Key::OpenSquareBracket, 0x38}, {Key::OpenSquareBracket, 0x38},
{Key::CloseSquareBracket, 0x58}, {Key::CloseSquareBracket, 0x58},
{Key::Semicolon, 0x48},
{Key::Semicolon, 0x57},
{Key::Quote, 0x48},
{Key::Enter, 0x49}, {Key::Enter, 0x49},
{Key::Backspace, 0x59}, {Key::Backspace, 0x59},

View File

@@ -778,7 +778,7 @@ private:
bool speaker_is_enabled_ = false; bool speaker_is_enabled_ = false;
// MARK: - Caps Lock status and the activity observer. // MARK: - Caps Lock status and the activity observer.
const std::string caps_led = "CAPS"; static inline const std::string caps_led = "CAPS";
bool caps_led_state_ = false; bool caps_led_state_ = false;
Activity::Observer *activity_observer_ = nullptr; Activity::Observer *activity_observer_ = nullptr;
}; };

View File

@@ -36,6 +36,7 @@ void Plus3::set_control_register(uint8_t control) {
// bit 1 => enable or disable drive 2 // bit 1 => enable or disable drive 2
// bit 2 => side select // bit 2 => side select
// bit 3 => single density select // bit 3 => single density select
// bit 5 => reset?
uint8_t changes = control ^ last_control_; uint8_t changes = control ^ last_control_;
last_control_ = control; last_control_ = control;

View File

@@ -275,7 +275,7 @@ private:
Mouse &mouse_; Mouse &mouse_;
Joystick *joystick_ = nullptr; Joystick *joystick_ = nullptr;
Activity::Observer *observer_ = nullptr; Activity::Observer *observer_ = nullptr;
inline static const std::string led_name = "Power"; static inline const std::string led_name = "Power";
} cia_a_handler_; } cia_a_handler_;
class CIABHandler: public MOS::MOS6526::PortHandler { class CIABHandler: public MOS::MOS6526::PortHandler {

View File

@@ -251,9 +251,9 @@ public:
// ... so form the real access address. // ... so form the real access address.
const uint16_t address = const uint16_t address =
uint16_t( uint16_t(
((state.refresh_address & 0x3ff) << 1) | ((state.refresh.get() & 0x3ff) << 1) |
((state.row_address & 0x7) << 11) | ((state.row.get() & 0x7) << 11) |
((state.refresh_address & 0x3000) << 2) ((state.refresh.get() & 0x3000) << 2)
); );
// Fetch two bytes and translate into pixels. Guaranteed: the mode can change only at // Fetch two bytes and translate into pixels. Guaranteed: the mode can change only at

View File

@@ -258,8 +258,8 @@ private:
// //
// Meanwhile, row address is used as a substitute 14th address line. // Meanwhile, row address is used as a substitute 14th address line.
const auto base_address = const auto base_address =
((state.refresh_address & 0xfff) << 1) + ((state.refresh.get() & 0xfff) << 1) +
((state.row_address & 1) << 13); ((state.row.get() & 1) << 13);
const uint8_t bitmap[] = { const uint8_t bitmap[] = {
ram[base_address], ram[base_address],
ram[base_address + 1], ram[base_address + 1],
@@ -295,9 +295,9 @@ private:
} }
void serialise_text(const Motorola::CRTC::BusState &state) { void serialise_text(const Motorola::CRTC::BusState &state) {
const uint8_t attributes = ram[((state.refresh_address << 1) + 1) & 0x3fff]; const uint8_t attributes = ram[((state.refresh.get() << 1) + 1) & 0x3fff];
const uint8_t glyph = ram[((state.refresh_address << 1) + 0) & 0x3fff]; const uint8_t glyph = ram[((state.refresh.get() << 1) + 0) & 0x3fff];
const uint8_t row = font[(glyph * 8) + state.row_address]; const uint8_t row = font[(glyph * 8) + state.row.get()];
uint8_t colours[2] = { rgb(attributes >> 4), rgbi(attributes) }; uint8_t colours[2] = { rgb(attributes >> 4), rgbi(attributes) };

View File

@@ -158,9 +158,9 @@ private:
pixel_pointer[4] = pixel_pointer[5] = pixel_pointer[6] = pixel_pointer[7] = pixel_pointer[4] = pixel_pointer[5] = pixel_pointer[6] = pixel_pointer[7] =
pixel_pointer[8] = low_intensity; pixel_pointer[8] = low_intensity;
} else { } else {
const uint8_t attributes = ram[((state.refresh_address << 1) + 1) & 0xfff]; const uint8_t attributes = ram[((state.refresh.get() << 1) + 1) & 0xfff];
const uint8_t glyph = ram[((state.refresh_address << 1) + 0) & 0xfff]; const uint8_t glyph = ram[((state.refresh.get() << 1) + 0) & 0xfff];
uint8_t row = font[(glyph * 14) + state.row_address]; uint8_t row = font[(glyph * 14) + state.row.get()];
const uint8_t intensity = (attributes & 0x08) ? high_intensity : low_intensity; const uint8_t intensity = (attributes & 0x08) ? high_intensity : low_intensity;
uint8_t blank = off; uint8_t blank = off;
@@ -183,7 +183,7 @@ private:
blank = (blank == off) ? intensity : off; blank = (blank == off) ? intensity : off;
} }
if(((attributes & 7) == 1) && state.row_address == 13) { if(((attributes & 7) == 1) && state.row.get() == 13) {
// Draw as underline. // Draw as underline.
std::fill(pixel_pointer, pixel_pointer + 9, intensity); std::fill(pixel_pointer, pixel_pointer + 9, intensity);
} else { } else {

View File

@@ -1130,7 +1130,7 @@ using namespace PCCompatible;
namespace { namespace {
#ifndef NDEBUG #ifndef NDEBUG
static constexpr bool ForceAT = true; static constexpr bool ForceAT = false;//true;
#else #else
static constexpr bool ForceAT = false; static constexpr bool ForceAT = false;
#endif #endif

View File

@@ -429,6 +429,22 @@ const std::vector<Description> &Description::all_roms() {
16_kb, 16_kb,
0x3c14fc70u 0x3c14fc70u
}, },
{
BBCMicroDFS226,
"BBCMicro",
"the Acorn 1770 DFS 2.26 ROM",
"dfs-2.26.rom",
16_kb,
0x5ae33e94u
},
{
BBCMicroADFS130,
"BBCMicro",
"the Acorn ADFS 1.30 ROM",
"adfs-1.30.rom",
16_kb,
0xd3855588u
},
// //
// ColecoVision. // ColecoVision.

View File

@@ -82,6 +82,8 @@ enum Name {
// BBC Micro. // BBC Micro.
BBCMicroMOS12, BBCMicroMOS12,
BBCMicroDFS226,
BBCMicroADFS130,
// ColecoVision. // ColecoVision.
ColecoVisionBIOS, ColecoVisionBIOS,

85
Numeric/SizedCounter.hpp Normal file
View File

@@ -0,0 +1,85 @@
//
// SizedCounter.hpp
// Clock Signal
//
// Created by Thomas Harte on 22/09/2025.
// Copyright © 2025 Thomas Harte. All rights reserved.
//
#pragma once
#include "Sizes.hpp"
namespace Numeric {
/*!
Provides a counter that is strictly limited to the requested of bits but attempts otherwise
to act like a standard C++ numeric type.
*/
template <int bits>
struct SizedCounter {
using IntT = MinIntForValue<1 << bits>::type;
inline static constexpr IntT Mask = (1 << bits) - 1;
constexpr SizedCounter(const IntT start_value) noexcept : counter_(start_value & Mask) {}
SizedCounter() = default;
IntT get() const {
return counter_;
}
SizedCounter operator +(const SizedCounter offset) const {
return SizedCounter<bits>(counter_ + offset.counter_);
}
SizedCounter operator &(const SizedCounter offset) const {
return SizedCounter<bits>(counter_ & offset.counter_);
}
SizedCounter operator >>(const int shift) const {
return SizedCounter<bits>(counter_ >> shift);
}
SizedCounter operator <<(const int shift) const {
return SizedCounter<bits>(counter_ << shift);
}
SizedCounter &operator ++(int) {
++(*this);
return *this;
}
SizedCounter &operator ++() {
counter_ = (counter_ + 1) & Mask;
return *this;
}
SizedCounter &operator +=(const IntT rhs) {
counter_ = (counter_ + rhs) & Mask;
return *this;
}
bool operator!() const {
return !counter_;
}
auto operator <=>(const SizedCounter &) const = default;
/// Replaces the bits in the range [begin, end) with those in the low-order bits of @c vlaue.
template <int begin, int end>
void load(const MinIntForValue<1 << (end - begin)>::type value) {
const auto mask = (1 << end) - (1 << begin);
counter_ &= ~mask;
counter_ |= IntT((value << begin) & mask);
}
template <int begin, typename IntT>
void load(const IntT value) {
load<begin, begin + sizeof(IntT)*8>(value);
}
private:
IntT counter_{};
};
}

View File

@@ -360,6 +360,9 @@
4B4518A51F75FD1C00926311 /* SSD.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518991F75FD1B00926311 /* SSD.cpp */; }; 4B4518A51F75FD1C00926311 /* SSD.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518991F75FD1B00926311 /* SSD.cpp */; };
4B47770B268FBE4D005C2340 /* FAT.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B477709268FBE4D005C2340 /* FAT.cpp */; }; 4B47770B268FBE4D005C2340 /* FAT.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B477709268FBE4D005C2340 /* FAT.cpp */; };
4B47770D26900685005C2340 /* EnterpriseDaveTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B47770C26900685005C2340 /* EnterpriseDaveTests.mm */; }; 4B47770D26900685005C2340 /* EnterpriseDaveTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B47770C26900685005C2340 /* EnterpriseDaveTests.mm */; };
4B47F3B62E7BAB94005D4DEC /* uPD7002.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B47F3B52E7BAB94005D4DEC /* uPD7002.cpp */; };
4B47F3B72E7BAB94005D4DEC /* uPD7002.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B47F3B52E7BAB94005D4DEC /* uPD7002.cpp */; };
4B47F3B82E7BAB94005D4DEC /* uPD7002.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B47F3B52E7BAB94005D4DEC /* uPD7002.cpp */; };
4B47F6C5241C87A100ED06F7 /* Struct.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B47F6C4241C87A100ED06F7 /* Struct.cpp */; }; 4B47F6C5241C87A100ED06F7 /* Struct.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B47F6C4241C87A100ED06F7 /* Struct.cpp */; };
4B47F6C6241C87A100ED06F7 /* Struct.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B47F6C4241C87A100ED06F7 /* Struct.cpp */; }; 4B47F6C6241C87A100ED06F7 /* Struct.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B47F6C4241C87A100ED06F7 /* Struct.cpp */; };
4B49F0A923346F7A0045E6A6 /* MacintoshOptions.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B49F0A723346F7A0045E6A6 /* MacintoshOptions.xib */; }; 4B49F0A923346F7A0045E6A6 /* MacintoshOptions.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B49F0A723346F7A0045E6A6 /* MacintoshOptions.xib */; };
@@ -1597,6 +1600,8 @@
4B477709268FBE4D005C2340 /* FAT.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = FAT.cpp; path = Parsers/FAT.cpp; sourceTree = "<group>"; }; 4B477709268FBE4D005C2340 /* FAT.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = FAT.cpp; path = Parsers/FAT.cpp; sourceTree = "<group>"; };
4B47770A268FBE4D005C2340 /* FAT.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = FAT.hpp; path = Parsers/FAT.hpp; sourceTree = "<group>"; }; 4B47770A268FBE4D005C2340 /* FAT.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = FAT.hpp; path = Parsers/FAT.hpp; sourceTree = "<group>"; };
4B47770C26900685005C2340 /* EnterpriseDaveTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = EnterpriseDaveTests.mm; sourceTree = "<group>"; }; 4B47770C26900685005C2340 /* EnterpriseDaveTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = EnterpriseDaveTests.mm; sourceTree = "<group>"; };
4B47F3B32E7B9A14005D4DEC /* uPD7002.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = uPD7002.hpp; sourceTree = "<group>"; };
4B47F3B52E7BAB94005D4DEC /* uPD7002.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = uPD7002.cpp; sourceTree = "<group>"; };
4B47F6C4241C87A100ED06F7 /* Struct.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Struct.cpp; sourceTree = "<group>"; }; 4B47F6C4241C87A100ED06F7 /* Struct.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Struct.cpp; sourceTree = "<group>"; };
4B49F0A823346F7A0045E6A6 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/MacintoshOptions.xib"; sourceTree = SOURCE_ROOT; }; 4B49F0A823346F7A0045E6A6 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/MacintoshOptions.xib"; sourceTree = SOURCE_ROOT; };
4B4A762E1DB1A3FA007AAE2E /* AY38910.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = AY38910.cpp; sourceTree = "<group>"; }; 4B4A762E1DB1A3FA007AAE2E /* AY38910.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = AY38910.cpp; sourceTree = "<group>"; };
@@ -1789,6 +1794,7 @@
4B8805FA1DCFF807003085B1 /* Oric.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Oric.hpp; path = Parsers/Oric.hpp; sourceTree = "<group>"; }; 4B8805FA1DCFF807003085B1 /* Oric.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Oric.hpp; path = Parsers/Oric.hpp; sourceTree = "<group>"; };
4B882F582C2F9C6900D84031 /* CPCShakerTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CPCShakerTests.mm; sourceTree = "<group>"; }; 4B882F582C2F9C6900D84031 /* CPCShakerTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CPCShakerTests.mm; sourceTree = "<group>"; };
4B882F5A2C2F9C7700D84031 /* Shaker */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Shaker; sourceTree = "<group>"; }; 4B882F5A2C2F9C7700D84031 /* Shaker */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Shaker; sourceTree = "<group>"; };
4B88559C2E8185BF00E251DD /* SizedCounter.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = SizedCounter.hpp; sourceTree = "<group>"; };
4B89449220194A47007DE474 /* CSStaticAnalyser+TargetVector.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "CSStaticAnalyser+TargetVector.h"; path = "StaticAnalyser/CSStaticAnalyser+TargetVector.h"; sourceTree = "<group>"; }; 4B89449220194A47007DE474 /* CSStaticAnalyser+TargetVector.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "CSStaticAnalyser+TargetVector.h"; path = "StaticAnalyser/CSStaticAnalyser+TargetVector.h"; sourceTree = "<group>"; };
4B8944E4201967B4007DE474 /* ConfidenceSummary.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ConfidenceSummary.hpp; sourceTree = "<group>"; }; 4B8944E4201967B4007DE474 /* ConfidenceSummary.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ConfidenceSummary.hpp; sourceTree = "<group>"; };
4B8944E5201967B4007DE474 /* ConfidenceSource.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ConfidenceSource.hpp; sourceTree = "<group>"; }; 4B8944E5201967B4007DE474 /* ConfidenceSource.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ConfidenceSource.hpp; sourceTree = "<group>"; };
@@ -3316,6 +3322,15 @@
path = Formats; path = Formats;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
4B47F3B42E7B9A14005D4DEC /* uPD7002 */ = {
isa = PBXGroup;
children = (
4B47F3B52E7BAB94005D4DEC /* uPD7002.cpp */,
4B47F3B32E7B9A14005D4DEC /* uPD7002.hpp */,
);
path = uPD7002;
sourceTree = "<group>";
};
4B4A762D1DB1A35C007AAE2E /* AY38910 */ = { 4B4A762D1DB1A35C007AAE2E /* AY38910 */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@@ -3727,15 +3742,16 @@
children = ( children = (
4B43984129674943006B0BFC /* BitReverse.hpp */, 4B43984129674943006B0BFC /* BitReverse.hpp */,
4BD155312716362A00410C6E /* BitSpread.hpp */, 4BD155312716362A00410C6E /* BitSpread.hpp */,
4B0B239F2D658C9400153879 /* BitStream.hpp */,
4281572E2AA0334300E16AA1 /* Carry.hpp */, 4281572E2AA0334300E16AA1 /* Carry.hpp */,
4B7BA03E23D55E7900B98D9E /* CRC.hpp */, 4B7BA03E23D55E7900B98D9E /* CRC.hpp */,
4B7BA03F23D55E7900B98D9E /* LFSR.hpp */, 4B7BA03F23D55E7900B98D9E /* LFSR.hpp */,
4B66E1A8297719270057ED0F /* NumericCoder.hpp */, 4B66E1A8297719270057ED0F /* NumericCoder.hpp */,
4BB5B995281B1D3E00522DA9 /* RegisterSizes.hpp */, 4BB5B995281B1D3E00522DA9 /* RegisterSizes.hpp */,
4B88559C2E8185BF00E251DD /* SizedCounter.hpp */,
4BFEA2F12682A90200EBF94C /* Sizes.hpp */, 4BFEA2F12682A90200EBF94C /* Sizes.hpp */,
4BD9713A2BFD7E7100C907AA /* StringSimilarity.hpp */, 4BD9713A2BFD7E7100C907AA /* StringSimilarity.hpp */,
4B0329202D0A78DC00C51EB5 /* UpperBound.hpp */, 4B0329202D0A78DC00C51EB5 /* UpperBound.hpp */,
4B0B239F2D658C9400153879 /* BitStream.hpp */,
); );
name = Numeric; name = Numeric;
path = ../../Numeric; path = ../../Numeric;
@@ -5075,6 +5091,7 @@
4BF0BC6E2973318E00CCA2B5 /* RP5C01 */, 4BF0BC6E2973318E00CCA2B5 /* RP5C01 */,
4B0ACBFF237756EC008902D0 /* Serial */, 4B0ACBFF237756EC008902D0 /* Serial */,
4BB0A6582044FD3000FB3688 /* SN76489 */, 4BB0A6582044FD3000FB3688 /* SN76489 */,
4B47F3B42E7B9A14005D4DEC /* uPD7002 */,
); );
name = Components; name = Components;
path = ../../Components; path = ../../Components;
@@ -6064,6 +6081,7 @@
4BB505832B962DDF0031C43C /* Keyboard.cpp in Sources */, 4BB505832B962DDF0031C43C /* Keyboard.cpp in Sources */,
4BF0BC69297108D600CCA2B5 /* MemorySlotHandler.cpp in Sources */, 4BF0BC69297108D600CCA2B5 /* MemorySlotHandler.cpp in Sources */,
4B894521201967B4007DE474 /* StaticAnalyser.cpp in Sources */, 4B894521201967B4007DE474 /* StaticAnalyser.cpp in Sources */,
4B47F3B62E7BAB94005D4DEC /* uPD7002.cpp in Sources */,
4B8318B522D3E548006DB630 /* Macintosh.cpp in Sources */, 4B8318B522D3E548006DB630 /* Macintosh.cpp in Sources */,
4B8DF4FA254E36AE00F3433C /* Video.cpp in Sources */, 4B8DF4FA254E36AE00F3433C /* Video.cpp in Sources */,
4B7BA03123C2B19C00B98D9E /* Jasmin.cpp in Sources */, 4B7BA03123C2B19C00B98D9E /* Jasmin.cpp in Sources */,
@@ -6434,6 +6452,7 @@
4B89452C201967B4007DE474 /* Tape.cpp in Sources */, 4B89452C201967B4007DE474 /* Tape.cpp in Sources */,
4B448E811F1C45A00009ABD6 /* TZX.cpp in Sources */, 4B448E811F1C45A00009ABD6 /* TZX.cpp in Sources */,
4BBFE83D21015D9C00BF1C40 /* CSJoystickManager.m in Sources */, 4BBFE83D21015D9C00BF1C40 /* CSJoystickManager.m in Sources */,
4B47F3B72E7BAB94005D4DEC /* uPD7002.cpp in Sources */,
4BEBFB512002DB30000708CC /* DiskROM.cpp in Sources */, 4BEBFB512002DB30000708CC /* DiskROM.cpp in Sources */,
4B89451C201967B4007DE474 /* Disk.cpp in Sources */, 4B89451C201967B4007DE474 /* Disk.cpp in Sources */,
4B302184208A550100773308 /* DiskII.cpp in Sources */, 4B302184208A550100773308 /* DiskII.cpp in Sources */,
@@ -6792,6 +6811,7 @@
4BDA8235261E8E000021AA19 /* Z80ContentionTests.mm in Sources */, 4BDA8235261E8E000021AA19 /* Z80ContentionTests.mm in Sources */,
4B882F592C2F9C6A00D84031 /* CPCShakerTests.mm in Sources */, 4B882F592C2F9C6A00D84031 /* CPCShakerTests.mm in Sources */,
4B7752C328217F720073E2C5 /* Z80.cpp in Sources */, 4B7752C328217F720073E2C5 /* Z80.cpp in Sources */,
4B47F3B82E7BAB94005D4DEC /* uPD7002.cpp in Sources */,
4B06AACE2C645EEC0034D014 /* PCCompatible.cpp in Sources */, 4B06AACE2C645EEC0034D014 /* PCCompatible.cpp in Sources */,
4B06AB072C6461160034D014 /* StaticAnalyser.cpp in Sources */, 4B06AB072C6461160034D014 /* StaticAnalyser.cpp in Sources */,
4B778F1A23A5ED320000D260 /* Video.cpp in Sources */, 4B778F1A23A5ED320000D260 /* Video.cpp in Sources */,

View File

@@ -67,7 +67,6 @@
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
enableASanStackUseAfterReturn = "YES" enableASanStackUseAfterReturn = "YES"
disableMainThreadChecker = "YES" disableMainThreadChecker = "YES"
disablePerformanceAntipatternChecker = "YES"
launchStyle = "0" launchStyle = "0"
useCustomWorkingDirectory = "NO" useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO" ignoresPersistentStateOnLaunch = "NO"
@@ -75,7 +74,8 @@
migratedStopOnEveryIssue = "YES" migratedStopOnEveryIssue = "YES"
debugServiceExtension = "internal" debugServiceExtension = "internal"
enableGPUValidationMode = "1" enableGPUValidationMode = "1"
allowLocationSimulation = "NO"> allowLocationSimulation = "NO"
disablePerformanceAntipatternChecker = "YES">
<BuildableProductRunnable <BuildableProductRunnable
runnableDebuggingMode = "0"> runnableDebuggingMode = "0">
<BuildableReference <BuildableReference

View File

@@ -164,14 +164,9 @@
self = [super init]; self = [super init];
if(self) { if(self) {
auto target = std::make_unique<Analyser::Static::Acorn::BBCMicroTarget>(); auto target = std::make_unique<Analyser::Static::Acorn::BBCMicroTarget>();
// TODO. target->has_1770dfs = dfs;
(void)dfs; target->has_adfs = adfs;
(void)adfs; target->has_sideways_ram = sidewaysRAM;
(void)sidewaysRAM;
// target->has_dfs = dfs;
// target->has_pres_adfs = adfs;
// target->has_ap6_rom = ap6;
// target->has_sideways_ram = sidewaysRAM;
_targets.push_back(std::move(target)); _targets.push_back(std::move(target));
} }
return self; return self;

View File

@@ -78,6 +78,7 @@ SOURCES += \
$$SRC/Components/RP5C01/*.cpp \ $$SRC/Components/RP5C01/*.cpp \
$$SRC/Components/Serial/*.cpp \ $$SRC/Components/Serial/*.cpp \
$$SRC/Components/SN76489/*.cpp \ $$SRC/Components/SN76489/*.cpp \
$$SRC/Components/uPD7002/*.cpp \
\ \
$$SRC/Inputs/*.cpp \ $$SRC/Inputs/*.cpp \
\ \
@@ -213,6 +214,7 @@ HEADERS += \
$$SRC/Components/RP5C01/*.hpp \ $$SRC/Components/RP5C01/*.hpp \
$$SRC/Components/Serial/*.hpp \ $$SRC/Components/Serial/*.hpp \
$$SRC/Components/SN76489/*.hpp \ $$SRC/Components/SN76489/*.hpp \
$$SRC/Components/uPD7002/*.hpp \
\ \
$$SRC/Concurrency/*.hpp \ $$SRC/Concurrency/*.hpp \
\ \

View File

@@ -62,6 +62,7 @@ SOURCES += glob.glob('../../Components/OPx/*.cpp')
SOURCES += glob.glob('../../Components/RP5C01/*.cpp') SOURCES += glob.glob('../../Components/RP5C01/*.cpp')
SOURCES += glob.glob('../../Components/SN76489/*.cpp') SOURCES += glob.glob('../../Components/SN76489/*.cpp')
SOURCES += glob.glob('../../Components/Serial/*.cpp') SOURCES += glob.glob('../../Components/Serial/*.cpp')
SOURCES += glob.glob('../../Components/uPD7002/*.cpp')
SOURCES += glob.glob('../../Configurable/*.cpp') SOURCES += glob.glob('../../Configurable/*.cpp')

View File

@@ -194,13 +194,10 @@ void Processor<personality, T, uses_ready_line>::run_for(const Cycles cycles) {
case OperationRSTPickVector: next_address_.full = 0xfffc; continue; case OperationRSTPickVector: next_address_.full = 0xfffc; continue;
case CycleReadVectorLow: read_mem(pc_.halves.low, next_address_.full); break; case CycleReadVectorLow: read_mem(pc_.halves.low, next_address_.full); break;
case CycleReadVectorHigh: read_mem(pc_.halves.high, next_address_.full+1); break; case CycleReadVectorHigh: read_mem(pc_.halves.high, next_address_.full+1); break;
case OperationSetIRQFlags: case OperationSetInterruptFlags:
flags_.inverse_interrupt = 0; flags_.inverse_interrupt = 0;
if(is_65c02(personality)) flags_.decimal = 0; if(is_65c02(personality)) flags_.decimal = 0;
continue; continue;
case OperationSetNMIRSTFlags:
if(is_65c02(personality)) flags_.decimal = 0;
continue;
case CyclePullPCL: s_++; read_mem(pc_.halves.low, s_ | 0x100); break; case CyclePullPCL: s_++; read_mem(pc_.halves.low, s_ | 0x100); break;
case CyclePullPCH: s_++; read_mem(pc_.halves.high, s_ | 0x100); break; case CyclePullPCH: s_++; read_mem(pc_.halves.high, s_ | 0x100); break;

View File

@@ -77,7 +77,7 @@ using namespace CPU::MOS6502;
ProcessorStorage::ProcessorStorage(Personality personality) { ProcessorStorage::ProcessorStorage(Personality personality) {
const InstructionList operations_6502[] = { const InstructionList operations_6502[] = {
/* 0x00 BRK */ Program(CycleIncPCPushPCH, CyclePushPCL, OperationBRKPickVector, OperationSetOperandFromFlagsWithBRKSet, CyclePushOperand, OperationSetIRQFlags, CycleReadVectorLow, CycleReadVectorHigh), /* 0x00 BRK */ Program(CycleIncPCPushPCH, CyclePushPCL, OperationBRKPickVector, OperationSetOperandFromFlagsWithBRKSet, CyclePushOperand, OperationSetInterruptFlags, CycleReadVectorLow, CycleReadVectorHigh),
/* 0x01 ORA x, ind */ IndexedIndirectRead(OperationORA), /* 0x01 ORA x, ind */ IndexedIndirectRead(OperationORA),
/* 0x02 JAM */ JAM, /* 0x03 ASO x, ind */ IndexedIndirectReadModifyWrite(OperationASO), /* 0x02 JAM */ JAM, /* 0x03 ASO x, ind */ IndexedIndirectReadModifyWrite(OperationASO),
/* 0x04 NOP zpg */ ZeroNop(), /* 0x05 ORA zpg */ ZeroRead(OperationORA), /* 0x04 NOP zpg */ ZeroNop(), /* 0x05 ORA zpg */ ZeroRead(OperationORA),
@@ -228,7 +228,7 @@ ProcessorStorage::ProcessorStorage(Personality personality) {
CycleNoWritePush, CycleNoWritePush,
OperationRSTPickVector, OperationRSTPickVector,
CycleNoWritePush, CycleNoWritePush,
OperationSetNMIRSTFlags, OperationSetInterruptFlags,
CycleReadVectorLow, CycleReadVectorLow,
CycleReadVectorHigh CycleReadVectorHigh
), ),
@@ -242,7 +242,7 @@ ProcessorStorage::ProcessorStorage(Personality personality) {
OperationBRKPickVector, OperationBRKPickVector,
OperationSetOperandFromFlags, OperationSetOperandFromFlags,
CyclePushOperand, CyclePushOperand,
OperationSetIRQFlags, OperationSetInterruptFlags,
CycleReadVectorLow, CycleReadVectorLow,
CycleReadVectorHigh CycleReadVectorHigh
), ),
@@ -256,7 +256,7 @@ ProcessorStorage::ProcessorStorage(Personality personality) {
OperationNMIPickVector, OperationNMIPickVector,
OperationSetOperandFromFlags, OperationSetOperandFromFlags,
CyclePushOperand, CyclePushOperand,
OperationSetNMIRSTFlags, OperationSetInterruptFlags,
CycleReadVectorLow, CycleReadVectorLow,
CycleReadVectorHigh CycleReadVectorHigh
), ),

View File

@@ -35,8 +35,7 @@ class ProcessorStorage {
CyclePushY, // pushes Y to the stack CyclePushY, // pushes Y to the stack
CyclePushOperand, // pushes operand_ to the stack CyclePushOperand, // pushes operand_ to the stack
OperationSetIRQFlags, // 6502: sets I; 65C02: sets I and resets D OperationSetInterruptFlags, // 6502: sets I; 65C02: sets I and resets D
OperationSetNMIRSTFlags, // 6502: no-op. 65C02: resets D
OperationBRKPickVector, // 65C02: sets next_address_ to the BRK vector location; 6502: as 65C02 if no NMI is pending; otherwise sets next_address_ to the NMI address and resets the internal NMI-pending flag OperationBRKPickVector, // 65C02: sets next_address_ to the BRK vector location; 6502: as 65C02 if no NMI is pending; otherwise sets next_address_ to the NMI address and resets the internal NMI-pending flag
OperationNMIPickVector, // sets next_address_ to the NMI vector OperationNMIPickVector, // sets next_address_ to the NMI vector

View File

@@ -3,3 +3,5 @@ ROM files would ordinarily go here; the copyright status of these is uncertain s
Expected files: Expected files:
os12.rom os12.rom
dfs-2.26.rom
adfs-1.30.rom

View File

@@ -60,6 +60,7 @@ set(CLK_SOURCES
Components/RP5C01/RP5C01.cpp Components/RP5C01/RP5C01.cpp
Components/SN76489/SN76489.cpp Components/SN76489/SN76489.cpp
Components/Serial/Line.cpp Components/Serial/Line.cpp
Components/uPD7002/uPD7002.cpp
Inputs/Keyboard.cpp Inputs/Keyboard.cpp