1
0
mirror of https://github.com/TomHarte/CLK.git synced 2025-08-08 14:25:05 +00:00

Merge pull request #1112 from TomHarte/VDPs

Clean up TMS9918-related code.
This commit is contained in:
Thomas Harte
2023-01-10 13:40:47 -05:00
committed by GitHub
16 changed files with 2284 additions and 1859 deletions

View File

@@ -48,7 +48,9 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
} }
}; };
#define is_master_system(v) v >= Analyser::Static::Sega::Target::Model::MasterSystem constexpr bool is_master_system(Analyser::Static::Sega::Target::Model model) {
return model >= Analyser::Static::Sega::Target::Model::MasterSystem;
}
} }
} }

File diff suppressed because it is too large Load Diff

View File

@@ -12,13 +12,38 @@
#include "../../Outputs/CRT/CRT.hpp" #include "../../Outputs/CRT/CRT.hpp"
#include "../../ClockReceiver/ClockReceiver.hpp" #include "../../ClockReceiver/ClockReceiver.hpp"
#include "Implementation/9918Base.hpp"
#include <cstdint> #include <cstdint>
namespace TI { namespace TI {
namespace TMS { namespace TMS {
enum Personality {
TMS9918A, // includes the 9928 and 9929; set TV standard and output device as desired.
V9938,
V9958,
// Sega extensions.
SMSVDP,
SMS2VDP,
GGVDP,
MDVDP,
};
enum class TVStandard {
/*! i.e. 50Hz output at around 312.5 lines/field */
PAL,
/*! i.e. 60Hz output at around 262.5 lines/field */
NTSC
};
}
}
#include "Implementation/9918Base.hpp"
namespace TI {
namespace TMS {
/*! /*!
Provides emulation of the TMS9918a, TMS9928 and TMS9929. Likely in the future to be the Provides emulation of the TMS9918a, TMS9928 and TMS9929. Likely in the future to be the
vessel for emulation of sufficiently close derivatives, such as the Master System VDP. vessel for emulation of sufficiently close derivatives, such as the Master System VDP.
@@ -30,13 +55,10 @@ namespace TMS {
These chips have only one non-on-demand interaction with the outside world: an interrupt line. These chips have only one non-on-demand interaction with the outside world: an interrupt line.
See get_time_until_interrupt and get_interrupt_line for asynchronous operation options. See get_time_until_interrupt and get_interrupt_line for asynchronous operation options.
*/ */
class TMS9918: public Base { template <Personality personality> class TMS9918: private Base<personality> {
public: public:
/*! /*! Constructs an instance of the VDP that behaves according to the templated personality. */
Constructs an instance of the drive controller that behaves according to personality @c p. TMS9918();
@param p The type of controller to emulate.
*/
TMS9918(Personality p);
/*! Sets the TV standard for this TMS, if that is hard-coded in hardware. */ /*! Sets the TV standard for this TMS, if that is hard-coded in hardware. */
void set_tv_standard(TVStandard standard); void set_tv_standard(TVStandard standard);
@@ -44,18 +66,24 @@ class TMS9918: public Base {
/*! Sets the scan target this TMS will post content to. */ /*! Sets the scan target this TMS will post content to. */
void set_scan_target(Outputs::Display::ScanTarget *); void set_scan_target(Outputs::Display::ScanTarget *);
/// Gets the current scan status. /*! Gets the current scan status. */
Outputs::Display::ScanStatus get_scaled_scan_status() const; Outputs::Display::ScanStatus get_scaled_scan_status() const;
/*! Sets the type of display the CRT will request. */ /*! Sets the type of CRT display. */
void set_display_type(Outputs::Display::DisplayType); void set_display_type(Outputs::Display::DisplayType);
/*! Gets the type of display the CRT will request. */ /*! Gets the type of CRT display. */
Outputs::Display::DisplayType get_display_type() const; Outputs::Display::DisplayType get_display_type() const;
/*! /*!
Runs the VCP for the number of cycles indicate; it is an implicit assumption of the code Runs the VDP for the number of cycles indicate; the input clock rate is implicitly assumed.
that the input clock rate is 3579545 Hz, the NTSC colour clock rate.
For everything except the Mega Drive VDP:
* the input clock rate should be 3579545 Hz, the NTSC colour clock rate.
For the Mega Drive:
* the input clock rate should be around 7.6MHz; 15/7ths of the NTSC colour
clock rate for NTSC output and 12/7ths of the PAL colour clock rate for PAL output.
*/ */
void run_for(const HalfCycles cycles); void run_for(const HalfCycles cycles);
@@ -65,11 +93,11 @@ class TMS9918: public Base {
/*! Gets a register value. */ /*! Gets a register value. */
uint8_t read(int address); uint8_t read(int address);
/*! Gets the current scan line; provided by the Master System only. */ /*! Gets the current scan line; provided by the Sega VDPs only. */
uint8_t get_current_line(); uint8_t get_current_line() const;
/*! Gets the current latched horizontal counter; provided by the Master System only. */ /*! Gets the current latched horizontal counter; provided by the Sega VDPs only. */
uint8_t get_latched_horizontal_counter(); uint8_t get_latched_horizontal_counter() const;
/*! Latches the current horizontal counter. */ /*! Latches the current horizontal counter. */
void latch_horizontal_counter(); void latch_horizontal_counter();
@@ -81,7 +109,7 @@ class TMS9918: public Base {
If get_interrupt_line is true now of if get_interrupt_line would If get_interrupt_line is true now of if get_interrupt_line would
never return true, returns HalfCycles::max(). never return true, returns HalfCycles::max().
*/ */
HalfCycles get_next_sequence_point(); HalfCycles get_next_sequence_point() const;
/*! /*!
Returns the amount of time until the nominated line interrupt position is Returns the amount of time until the nominated line interrupt position is
@@ -96,7 +124,7 @@ class TMS9918: public Base {
/*! /*!
@returns @c true if the interrupt line is currently active; @c false otherwise. @returns @c true if the interrupt line is currently active; @c false otherwise.
*/ */
bool get_interrupt_line(); bool get_interrupt_line() const;
}; };
} }

View File

@@ -0,0 +1,809 @@
//
// 9918.cpp
// Clock Signal
//
// Created by Thomas Harte on 25/11/2017.
// Copyright 2017 Thomas Harte. All rights reserved.
//
#include "../9918.hpp"
#include <cassert>
#include <cstring>
#include <cstdlib>
#include "../../../Outputs/Log.hpp"
using namespace TI::TMS;
namespace {
// 342 internal cycles are 228/227.5ths of a line, so 341.25 cycles should be a whole
// line. Therefore multiply everything by four, but set line length to 1365 rather than 342*4 = 1368.
constexpr unsigned int CRTCyclesPerLine = 1365;
constexpr unsigned int CRTCyclesDivider = 4;
}
template <Personality personality>
Base<personality>::Base() :
crt_(CRTCyclesPerLine, CRTCyclesDivider, Outputs::Display::Type::NTSC60, Outputs::Display::InputDataType::Red8Green8Blue8) {
// Unimaginatively, this class just passes RGB through to the shader. Investigation is needed
// into whether there's a more natural form. It feels unlikely given the diversity of chips modelled.
if constexpr (is_sega_vdp(personality)) {
mode_timing_.line_interrupt_position = 64;
mode_timing_.end_of_frame_interrupt_position.column = 63;
mode_timing_.end_of_frame_interrupt_position.row = 193;
}
// Establish that output is delayed after reading by `output_lag` cycles; start
// at a random position.
read_pointer_.row = rand() % 262;
read_pointer_.column = rand() % (Timing<personality>::CyclesPerLine - output_lag);
write_pointer_.row = read_pointer_.row;
write_pointer_.column = read_pointer_.column + output_lag;
}
template <Personality personality>
TMS9918<personality>::TMS9918() {
this->crt_.set_display_type(Outputs::Display::DisplayType::RGB);
this->crt_.set_visible_area(Outputs::Display::Rect(0.07f, 0.0375f, 0.875f, 0.875f));
// The TMS remains in-phase with the NTSC colour clock; this is an empirical measurement
// intended to produce the correct relationship between the hard edges between pixels and
// the colour clock. It was eyeballed rather than derived from any knowledge of the TMS
// colour burst generator because I've yet to find any.
this->crt_.set_immediate_default_phase(0.85f);
}
template <Personality personality>
void TMS9918<personality>::set_tv_standard(TVStandard standard) {
this->tv_standard_ = standard;
switch(standard) {
case TVStandard::PAL:
this->mode_timing_.total_lines = 313;
this->mode_timing_.first_vsync_line = 253;
this->crt_.set_new_display_type(CRTCyclesPerLine, Outputs::Display::Type::PAL50);
break;
default:
this->mode_timing_.total_lines = 262;
this->mode_timing_.first_vsync_line = 227;
this->crt_.set_new_display_type(CRTCyclesPerLine, Outputs::Display::Type::NTSC60);
break;
}
}
template <Personality personality>
void TMS9918<personality>::set_scan_target(Outputs::Display::ScanTarget *scan_target) {
this->crt_.set_scan_target(scan_target);
}
template <Personality personality>
Outputs::Display::ScanStatus TMS9918<personality>::get_scaled_scan_status() const {
// The input was scaled by 3/4 to convert half cycles to internal ticks,
// so undo that and also allow for: (i) the multiply by 4 that it takes
// to reach the CRT; and (ii) the fact that the half-cycles value was scaled,
// and this should really reply in whole cycles.
return this->crt_.get_scaled_scan_status() * (4.0f / (3.0f * 8.0f));
}
template <Personality personality>
void TMS9918<personality>::set_display_type(Outputs::Display::DisplayType display_type) {
this->crt_.set_display_type(display_type);
}
template <Personality personality>
Outputs::Display::DisplayType TMS9918<personality>::get_display_type() const {
return this->crt_.get_display_type();
}
void LineBuffer::reset_sprite_collection() {
sprites_stopped = false;
active_sprite_slot = 0;
for(int c = 0; c < 8; ++c) {
active_sprites[c].shift_position = 0;
}
}
template <Personality personality>
void Base<personality>::posit_sprite(LineBuffer &buffer, int sprite_number, int sprite_position, int screen_row) {
if(!(status_ & StatusSpriteOverflow)) {
status_ = uint8_t((status_ & ~0x1f) | (sprite_number & 0x1f));
}
if(buffer.sprites_stopped) return;
// A sprite Y of 208 means "don't scan the list any further".
if(mode_timing_.allow_sprite_terminator && sprite_position == mode_timing_.sprite_terminator) {
buffer.sprites_stopped = true;
return;
}
const int sprite_row = (((screen_row + 1) % mode_timing_.total_lines) - ((sprite_position + 1) & 255)) & 255;
if(sprite_row < 0 || sprite_row >= sprite_height_) return;
if(buffer.active_sprite_slot == mode_timing_.maximum_visible_sprites) {
status_ |= StatusSpriteOverflow;
return;
}
LineBuffer::ActiveSprite &sprite = buffer.active_sprites[buffer.active_sprite_slot];
sprite.index = sprite_number;
sprite.row = sprite_row >> (sprites_magnified_ ? 1 : 0);
++buffer.active_sprite_slot;
}
template <Personality personality>
void TMS9918<personality>::run_for(const HalfCycles cycles) {
// As specific as I've been able to get:
// Scanline time is always 228 cycles.
// PAL output is 313 lines total. NTSC output is 262 lines total.
// Interrupt is signalled upon entering the lower border.
// Convert 456 clocked half cycles per line to 342 internal cycles per line;
// the internal clock is 1.5 times the nominal 3.579545 Mhz that I've advertised
// for this part. So multiply by three quarters.
const int int_cycles = this->clock_converter_.to_internal(cycles.as<int>());
if(!int_cycles) return;
// There are two intertwined processes here, 'writing' (which means writing to the
// line buffers, i.e. it's everything to do with collecting a line) and 'reading'
// (which means reading from the line buffers and generating video).
int write_cycles_pool = int_cycles;
int read_cycles_pool = int_cycles;
while(write_cycles_pool || read_cycles_pool) {
#ifndef NDEBUG
LineBufferPointer backup = this->read_pointer_;
#endif
if(write_cycles_pool) {
// Determine how much writing to do.
const int write_cycles = std::min(
Timing<personality>::CyclesPerLine - this->write_pointer_.column,
write_cycles_pool
);
const int end_column = this->write_pointer_.column + write_cycles;
LineBuffer &line_buffer = this->line_buffers_[this->write_pointer_.row];
// Determine what this does to any enqueued VRAM access.
this->minimum_access_column_ = this->write_pointer_.column + this->cycles_until_access_;
this->cycles_until_access_ -= write_cycles;
// ---------------------------------------
// Latch scrolling position, if necessary.
// ---------------------------------------
if constexpr (is_sega_vdp(personality)) {
if(this->write_pointer_.column < 61 && end_column >= 61) {
if(!this->write_pointer_.row) {
this->master_system_.latched_vertical_scroll = this->master_system_.vertical_scroll;
if(this->master_system_.mode4_enable) {
this->mode_timing_.pixel_lines = 192;
if(this->mode2_enable_ && this->mode1_enable_) this->mode_timing_.pixel_lines = 224;
if(this->mode2_enable_ && this->mode3_enable_) this->mode_timing_.pixel_lines = 240;
this->mode_timing_.allow_sprite_terminator = this->mode_timing_.pixel_lines == 192;
this->mode_timing_.first_vsync_line = (this->mode_timing_.total_lines + this->mode_timing_.pixel_lines) >> 1;
this->mode_timing_.end_of_frame_interrupt_position.row = this->mode_timing_.pixel_lines + 1;
}
}
line_buffer.latched_horizontal_scroll = this->master_system_.horizontal_scroll;
}
}
// ------------------------
// Perform memory accesses.
// ------------------------
#define fetch(function, clock) \
const int first_window = from_internal<personality, clock>(this->write_pointer_.column);\
const int final_window = from_internal<personality, clock>(end_column); \
if(first_window == final_window) break; \
if(final_window != clock_rate<personality, clock>()) { \
function<true>(first_window, final_window); \
} else { \
function<false>(first_window, final_window); \
}
switch(line_buffer.line_mode) {
case LineMode::Text: { fetch(this->template fetch_tms_text, Clock::TMSMemoryWindow); } break;
case LineMode::Character: { fetch(this->template fetch_tms_character, Clock::TMSMemoryWindow); } break;
case LineMode::SMS: { fetch(this->template fetch_sms, Clock::TMSMemoryWindow); } break;
case LineMode::Refresh: { fetch(this->template fetch_tms_refresh, Clock::TMSMemoryWindow); } break;
}
#undef fetch
// -------------------------------
// Check for interrupt conditions.
// -------------------------------
if(this->write_pointer_.column < this->mode_timing_.line_interrupt_position && end_column >= this->mode_timing_.line_interrupt_position) {
// The Sega VDP offers a decrementing counter for triggering line interrupts;
// it is reloaded either when it overflows or upon every non-pixel line after the first.
// It is otherwise decremented.
if constexpr (is_sega_vdp(personality)) {
if(this->write_pointer_.row >= 0 && this->write_pointer_.row <= this->mode_timing_.pixel_lines) {
--this->line_interrupt_counter;
if(this->line_interrupt_counter == 0xff) {
this->line_interrupt_pending_ = true;
this->line_interrupt_counter = this->line_interrupt_target;
}
} else {
this->line_interrupt_counter = this->line_interrupt_target;
}
}
// TODO: the V9938 provides line interrupts from direct specification of the target line.
// So life is easy.
}
if(
this->write_pointer_.row == this->mode_timing_.end_of_frame_interrupt_position.row &&
this->write_pointer_.column < this->mode_timing_.end_of_frame_interrupt_position.column &&
end_column >= this->mode_timing_.end_of_frame_interrupt_position.column
) {
this->status_ |= StatusInterrupt;
}
// -------------
// Advance time.
// -------------
this->write_pointer_.column = end_column;
write_cycles_pool -= write_cycles;
if(this->write_pointer_.column == Timing<personality>::CyclesPerLine) {
this->write_pointer_.column = 0;
this->write_pointer_.row = (this->write_pointer_.row + 1) % this->mode_timing_.total_lines;
LineBuffer &next_line_buffer = this->line_buffers_[this->write_pointer_.row];
// Establish the current screen output mode, which will be captured as a
// line mode momentarily.
this->screen_mode_ = this->current_screen_mode();
// Based on the output mode, pick a line mode.
next_line_buffer.first_pixel_output_column = Timing<personality>::FirstPixelCycle;
next_line_buffer.next_border_column = Timing<personality>::CyclesPerLine;
next_line_buffer.pixel_count = 256;
this->mode_timing_.maximum_visible_sprites = 4;
switch(this->screen_mode_) {
case ScreenMode::Text:
next_line_buffer.line_mode = LineMode::Text;
next_line_buffer.first_pixel_output_column = Timing<personality>::FirstTextCycle;
next_line_buffer.next_border_column = Timing<personality>::LastTextCycle;
next_line_buffer.pixel_count = 240;
break;
case ScreenMode::SMSMode4:
next_line_buffer.line_mode = LineMode::SMS;
this->mode_timing_.maximum_visible_sprites = 8;
break;
default:
next_line_buffer.line_mode = LineMode::Character;
break;
}
if(
(this->screen_mode_ == ScreenMode::Blank) ||
(this->write_pointer_.row >= this->mode_timing_.pixel_lines && this->write_pointer_.row != this->mode_timing_.total_lines-1))
next_line_buffer.line_mode = LineMode::Refresh;
}
}
#ifndef NDEBUG
assert(backup.row == this->read_pointer_.row && backup.column == this->read_pointer_.column);
backup = this->write_pointer_;
#endif
if(read_cycles_pool) {
// Determine how much time has passed in the remainder of this line, and proceed.
const int target_read_cycles = std::min(
Timing<personality>::CyclesPerLine - this->read_pointer_.column,
read_cycles_pool
);
int read_cycles_performed = 0;
uint32_t next_cram_value = 0;
while(read_cycles_performed < target_read_cycles) {
int read_cycles = target_read_cycles - read_cycles_performed;
if(!read_cycles) continue;
// Grab the next CRAM dot value and schedule a break in output if applicable.
const uint32_t cram_value = next_cram_value;
if constexpr (is_sega_vdp(personality)) {
next_cram_value = 0;
if(!this->upcoming_cram_dots_.empty() && this->upcoming_cram_dots_.front().location.row == this->read_pointer_.row) {
int time_until_dot = this->upcoming_cram_dots_.front().location.column - this->read_pointer_.column;
if(time_until_dot < read_cycles) {
read_cycles = time_until_dot;
next_cram_value = this->upcoming_cram_dots_.front().value;
this->upcoming_cram_dots_.erase(this->upcoming_cram_dots_.begin());
}
}
}
read_cycles_performed += read_cycles;
const int end_column = this->read_pointer_.column + read_cycles;
LineBuffer &line_buffer = this->line_buffers_[this->read_pointer_.row];
// --------------------
// Output video stream.
// --------------------
#define crt_convert(action, time) this->crt_.action(from_internal<personality, Clock::CRT>(time))
#define output_sync(x) crt_convert(output_sync, x)
#define output_blank(x) crt_convert(output_blank, x)
#define output_default_colour_burst(x) crt_convert(output_default_colour_burst, x)
#define intersect(left, right, code) { \
const int start = std::max(this->read_pointer_.column, left); \
const int end = std::min(end_column, right); \
if(end > start) {\
code;\
}\
}
#define border(left, right) intersect(left, right, this->output_border(end - start, cram_value))
if(line_buffer.line_mode == LineMode::Refresh || this->read_pointer_.row > this->mode_timing_.pixel_lines) {
if(
this->read_pointer_.row >= this->mode_timing_.first_vsync_line &&
this->read_pointer_.row < this->mode_timing_.first_vsync_line + 4
) {
// Vertical sync.
// TODO: the Mega Drive supports interlaced video, I think?
if(end_column == Timing<personality>::CyclesPerLine) {
output_sync(Timing<personality>::CyclesPerLine);
}
} else {
// Right border.
border(0, Timing<personality>::EndOfRightBorder);
// Blanking region: output the entire sequence when the cursor
// crosses the start-of-border point.
if(
this->read_pointer_.column < Timing<personality>::StartOfLeftBorder &&
end_column >= Timing<personality>::StartOfLeftBorder
) {
output_blank(Timing<personality>::StartOfSync - Timing<personality>::EndOfRightBorder);
output_sync(Timing<personality>::EndOfSync - Timing<personality>::StartOfSync);
output_blank(Timing<personality>::StartOfColourBurst - Timing<personality>::EndOfSync);
output_default_colour_burst(Timing<personality>::EndOfColourBurst - Timing<personality>::StartOfColourBurst);
output_blank(Timing<personality>::StartOfLeftBorder - Timing<personality>::EndOfColourBurst);
}
// Border colour for the rest of the line.
border(Timing<personality>::StartOfLeftBorder, Timing<personality>::CyclesPerLine);
}
} else {
// Right border.
border(0, Timing<personality>::EndOfRightBorder);
// Blanking region.
if(
this->read_pointer_.column < Timing<personality>::StartOfLeftBorder &&
end_column >= Timing<personality>::StartOfLeftBorder
) {
output_blank(Timing<personality>::StartOfSync - Timing<personality>::EndOfRightBorder);
output_sync(Timing<personality>::EndOfSync - Timing<personality>::StartOfSync);
output_blank(Timing<personality>::StartOfColourBurst - Timing<personality>::EndOfSync);
output_default_colour_burst(Timing<personality>::EndOfColourBurst - Timing<personality>::StartOfColourBurst);
output_blank(Timing<personality>::StartOfLeftBorder - Timing<personality>::EndOfColourBurst);
}
// Left border.
border(Timing<personality>::StartOfLeftBorder, line_buffer.first_pixel_output_column);
#define draw(function, clock) { \
const int relative_start = from_internal<personality, clock>(start - line_buffer.first_pixel_output_column); \
const int relative_end = from_internal<personality, clock>(end - line_buffer.first_pixel_output_column); \
if(relative_start == relative_end) break; \
this->function; }
// Pixel region.
intersect(
line_buffer.first_pixel_output_column,
line_buffer.next_border_column,
if(!this->asked_for_write_area_) {
this->asked_for_write_area_ = true;
this->pixel_origin_ = this->pixel_target_ = reinterpret_cast<uint32_t *>(
this->crt_.begin_data(line_buffer.pixel_count)
);
}
if(this->pixel_target_) {
switch(line_buffer.line_mode) {
case LineMode::SMS: draw(draw_sms(relative_start, relative_end, cram_value), Clock::TMSPixel); break;
case LineMode::Character: draw(draw_tms_character(relative_start, relative_end), Clock::TMSPixel); break;
case LineMode::Text: draw(draw_tms_text(relative_start, relative_end), Clock::TMSPixel); break;
case LineMode::Refresh: break; /* Dealt with elsewhere. */
}
}
if(end == line_buffer.next_border_column) {
const int length = line_buffer.next_border_column - line_buffer.first_pixel_output_column;
this->crt_.output_data(from_internal<personality, Clock::CRT>(length), line_buffer.pixel_count);
this->pixel_origin_ = this->pixel_target_ = nullptr;
this->asked_for_write_area_ = false;
}
);
#undef draw
// Additional right border, if called for.
if(line_buffer.next_border_column != Timing<personality>::CyclesPerLine) {
border(line_buffer.next_border_column, Timing<personality>::CyclesPerLine);
}
}
#undef border
#undef intersect
#undef crt_convert
#undef output_sync
#undef output_blank
#undef output_default_colour_burst
// -------------
// Advance time.
// -------------
this->read_pointer_.column = end_column;
}
read_cycles_pool -= target_read_cycles;
if(this->read_pointer_.column == Timing<personality>::CyclesPerLine) {
this->read_pointer_.column = 0;
this->read_pointer_.row = (this->read_pointer_.row + 1) % this->mode_timing_.total_lines;
}
}
assert(backup.row == this->write_pointer_.row && backup.column == this->write_pointer_.column);
}
}
template <Personality personality>
void Base<personality>::output_border(int cycles, [[maybe_unused]] uint32_t cram_dot) {
cycles = from_internal<personality, Clock::CRT>(cycles);
const uint32_t border_colour =
is_sega_vdp(personality) ?
master_system_.colour_ram[16 + background_colour_] :
palette[background_colour_];
if constexpr (is_sega_vdp(personality)) {
if(cram_dot) {
uint32_t *const pixel_target = reinterpret_cast<uint32_t *>(crt_.begin_data(1));
if(pixel_target) {
*pixel_target = border_colour | cram_dot;
}
// Four CRT cycles is one pixel width, so this doesn't need clock conversion.
// TODO: on the Mega Drive it may be only 3 colour cycles, depending on mode.
crt_.output_level(4);
cycles -= 4;
}
}
if(!cycles) {
return;
}
// If the border colour is 0, that can be communicated
// more efficiently as an explicit blank.
if(border_colour) {
uint32_t *const pixel_target = reinterpret_cast<uint32_t *>(crt_.begin_data(1));
if(pixel_target) {
*pixel_target = border_colour;
}
crt_.output_level(cycles);
} else {
crt_.output_blank(cycles);
}
}
template <Personality personality>
void TMS9918<personality>::write(int address, uint8_t value) {
// Writes to address 0 are writes to the video RAM. Store
// the value and return.
if(!(address & 1)) {
this->write_phase_ = false;
// Enqueue the write to occur at the next available slot.
this->read_ahead_buffer_ = value;
this->queued_access_ = MemoryAccess::Write;
this->cycles_until_access_ = Timing<personality>::VRAMAccessDelay;
return;
}
// Writes to address 1 are performed in pairs; if this is the
// low byte of a value, store it and wait for the high byte.
if(!this->write_phase_) {
this->low_write_ = value;
this->write_phase_ = true;
// The initial write should half update the access pointer.
this->ram_pointer_ = (this->ram_pointer_ & 0xff00) | this->low_write_;
return;
}
// The RAM pointer is always set on a second write, regardless of
// whether the caller is intending to enqueue a VDP operation.
this->ram_pointer_ = (this->ram_pointer_ & 0x00ff) | uint16_t(value << 8);
this->write_phase_ = false;
if(value & 0x80) {
if constexpr (is_sega_vdp(personality)) {
if(value & 0x40) {
this->master_system_.cram_is_selected = true;
return;
}
value &= 0xf;
} else {
value &= 0x7;
}
// This is a write to a register.
switch(value) {
case 0:
if constexpr (is_sega_vdp(personality)) {
this->master_system_.vertical_scroll_lock = this->low_write_ & 0x80;
this->master_system_.horizontal_scroll_lock = this->low_write_ & 0x40;
this->master_system_.hide_left_column = this->low_write_ & 0x20;
this->enable_line_interrupts_ = this->low_write_ & 0x10;
this->master_system_.shift_sprites_8px_left = this->low_write_ & 0x08;
this->master_system_.mode4_enable = this->low_write_ & 0x04;
}
this->mode2_enable_ = this->low_write_ & 0x02;
break;
case 1:
this->blank_display_ = !(this->low_write_ & 0x40);
this->generate_interrupts_ = this->low_write_ & 0x20;
this->mode1_enable_ = this->low_write_ & 0x10;
this->mode3_enable_ = this->low_write_ & 0x08;
this->sprites_16x16_ = this->low_write_ & 0x02;
this->sprites_magnified_ = this->low_write_ & 0x01;
this->sprite_height_ = 8;
if(this->sprites_16x16_) this->sprite_height_ <<= 1;
if(this->sprites_magnified_) this->sprite_height_ <<= 1;
break;
case 2:
this->pattern_name_address_ = size_t((this->low_write_ & 0xf) << 10) | 0x3ff;
this->master_system_.pattern_name_address = this->pattern_name_address_ | ((personality == TMS::SMSVDP) ? 0x000 : 0x400);
break;
case 3:
this->colour_table_address_ = size_t(this->low_write_ << 6) | 0x3f;
break;
case 4:
this->pattern_generator_table_address_ = size_t((this->low_write_ & 0x07) << 11) | 0x7ff;
break;
case 5:
this->sprite_attribute_table_address_ = size_t((this->low_write_ & 0x7f) << 7) | 0x7f;
this->master_system_.sprite_attribute_table_address = this->sprite_attribute_table_address_ | ((personality == TMS::SMSVDP) ? 0x00 : 0x80);
break;
case 6:
this->sprite_generator_table_address_ = size_t((this->low_write_ & 0x07) << 11) | 0x7ff;
this->master_system_.sprite_generator_table_address = this->sprite_generator_table_address_ | ((personality == TMS::SMSVDP) ? 0x0000 : 0x1800);
break;
case 7:
this->text_colour_ = this->low_write_ >> 4;
this->background_colour_ = this->low_write_ & 0xf;
break;
case 8:
if constexpr (is_sega_vdp(personality)) {
this->master_system_.horizontal_scroll = this->low_write_;
}
break;
case 9:
if constexpr (is_sega_vdp(personality)) {
this->master_system_.vertical_scroll = this->low_write_;
}
break;
case 10:
if constexpr (is_sega_vdp(personality)) {
this->line_interrupt_target = this->low_write_;
}
break;
default:
LOG("Unknown TMS write: " << int(this->low_write_) << " to " << int(value));
break;
}
} else {
// This is an access via the RAM pointer.
if(!(value & 0x40)) {
// A read request is enqueued upon setting the address; conversely a write
// won't be enqueued unless and until some actual data is supplied.
this->queued_access_ = MemoryAccess::Read;
this->cycles_until_access_ = Timing<personality>::VRAMAccessDelay;
}
this->master_system_.cram_is_selected = false;
}
}
template <Personality personality>
uint8_t TMS9918<personality>::get_current_line() const {
// Determine the row to return.
constexpr int row_change_position = 63; // This is the proper Master System value; substitute if any other VDPs turn out to have this functionality.
int source_row =
(this->write_pointer_.column < row_change_position)
? (this->write_pointer_.row + this->mode_timing_.total_lines - 1) % this->mode_timing_.total_lines
: this->write_pointer_.row;
if(this->tv_standard_ == TVStandard::NTSC) {
if(this->mode_timing_.pixel_lines == 240) {
// NTSC 256x240: 00-FF, 00-06
} else if(this->mode_timing_.pixel_lines == 224) {
// NTSC 256x224: 00-EA, E5-FF
if(source_row >= 0xeb) source_row -= 6;
} else {
// NTSC 256x192: 00-DA, D5-FF
if(source_row >= 0xdb) source_row -= 6;
}
} else {
if(this->mode_timing_.pixel_lines == 240) {
// PAL 256x240: 00-FF, 00-0A, D2-FF
if(source_row >= 267) source_row -= 0x39;
} else if(this->mode_timing_.pixel_lines == 224) {
// PAL 256x224: 00-FF, 00-02, CA-FF
if(source_row >= 259) source_row -= 0x39;
} else {
// PAL 256x192: 00-F2, BA-FF
if(source_row >= 0xf3) source_row -= 0x39;
}
}
return uint8_t(source_row);
}
template <Personality personality>
uint8_t TMS9918<personality>::read(int address) {
this->write_phase_ = false;
// Reads from address 0 read video RAM, via the read-ahead buffer.
if(!(address & 1)) {
// Enqueue the write to occur at the next available slot.
const uint8_t result = this->read_ahead_buffer_;
this->queued_access_ = MemoryAccess::Read;
return result;
}
// Reads from address 1 get the status register.
const uint8_t result = this->status_;
this->status_ &= ~(StatusInterrupt | StatusSpriteOverflow | StatusSpriteCollision);
this->line_interrupt_pending_ = false;
return result;
}
template <Personality personality>
HalfCycles TMS9918<personality>::get_next_sequence_point() const {
if(!this->generate_interrupts_ && !this->enable_line_interrupts_) return HalfCycles::max();
if(get_interrupt_line()) return HalfCycles::max();
// Calculate the amount of time until the next end-of-frame interrupt.
const int frame_length = Timing<personality>::CyclesPerLine * this->mode_timing_.total_lines;
int time_until_frame_interrupt =
(
((this->mode_timing_.end_of_frame_interrupt_position.row * Timing<personality>::CyclesPerLine) + this->mode_timing_.end_of_frame_interrupt_position.column + frame_length) -
((this->write_pointer_.row * Timing<personality>::CyclesPerLine) + this->write_pointer_.column)
) % frame_length;
if(!time_until_frame_interrupt) time_until_frame_interrupt = frame_length;
if(!this->enable_line_interrupts_) {
return this->clock_converter_.half_cycles_before_internal_cycles(time_until_frame_interrupt);
}
// Calculate when the next line interrupt will occur.
int next_line_interrupt_row = -1;
int cycles_to_next_interrupt_threshold = this->mode_timing_.line_interrupt_position - this->write_pointer_.column;
int line_of_next_interrupt_threshold = this->write_pointer_.row;
if(cycles_to_next_interrupt_threshold <= 0) {
cycles_to_next_interrupt_threshold += Timing<personality>::CyclesPerLine;
++line_of_next_interrupt_threshold;
}
if constexpr (is_sega_vdp(personality)) {
// If there is still time for a line interrupt this frame, that'll be it;
// otherwise it'll be on the next frame, supposing there's ever time for
// it at all.
if(line_of_next_interrupt_threshold + this->line_interrupt_counter <= this->mode_timing_.pixel_lines) {
next_line_interrupt_row = line_of_next_interrupt_threshold + this->line_interrupt_counter;
} else {
if(this->line_interrupt_target <= this->mode_timing_.pixel_lines)
next_line_interrupt_row = this->mode_timing_.total_lines + this->line_interrupt_target;
}
}
// If there's actually no interrupt upcoming, despite being enabled, either return
// the frame end interrupt or no interrupt pending as appropriate.
if(next_line_interrupt_row == -1) {
return this->generate_interrupts_ ?
this->clock_converter_.half_cycles_before_internal_cycles(time_until_frame_interrupt) :
HalfCycles::max();
}
// Figure out the number of internal cycles until the next line interrupt, which is the amount
// of time to the next tick over and then next_line_interrupt_row - row_ lines further.
const int local_cycles_until_line_interrupt = cycles_to_next_interrupt_threshold + (next_line_interrupt_row - line_of_next_interrupt_threshold) * Timing<personality>::CyclesPerLine;
if(!this->generate_interrupts_) return this->clock_converter_.half_cycles_before_internal_cycles(local_cycles_until_line_interrupt);
// Return whichever interrupt is closer.
return this->clock_converter_.half_cycles_before_internal_cycles(std::min(local_cycles_until_line_interrupt, time_until_frame_interrupt));
}
template <Personality personality>
HalfCycles TMS9918<personality>::get_time_until_line(int line) {
if(line < 0) line += this->mode_timing_.total_lines;
int cycles_to_next_interrupt_threshold = this->mode_timing_.line_interrupt_position - this->write_pointer_.column;
int line_of_next_interrupt_threshold = this->write_pointer_.row;
if(cycles_to_next_interrupt_threshold <= 0) {
cycles_to_next_interrupt_threshold += Timing<personality>::CyclesPerLine;
++line_of_next_interrupt_threshold;
}
if(line_of_next_interrupt_threshold > line) {
line += this->mode_timing_.total_lines;
}
return this->clock_converter_.half_cycles_before_internal_cycles(cycles_to_next_interrupt_threshold + (line - line_of_next_interrupt_threshold)*Timing<personality>::CyclesPerLine);
}
template <Personality personality>
bool TMS9918<personality>::get_interrupt_line() const {
return
((this->status_ & StatusInterrupt) && this->generate_interrupts_) ||
(this->enable_line_interrupts_ && this->line_interrupt_pending_);
}
// TODO: [potentially] remove Master System timing assumptions in latch and get_latched below.
template <Personality personality>uint8_t TMS9918<personality>::get_latched_horizontal_counter() const {
// Translate from internal numbering, which puts pixel output
// in the final 256 pixels of 342, to the public numbering,
// which counts the 256 pixels as items 0255, starts
// counting at -48, and returns only the top 8 bits of the number.
int public_counter = this->latched_column_ - (342 - 256);
if(public_counter < -46) public_counter += 342;
return uint8_t(public_counter >> 1);
}
template <Personality personality>
void TMS9918<personality>::latch_horizontal_counter() {
this->latched_column_ = this->write_pointer_.column;
}
template class TI::TMS::TMS9918<Personality::TMS9918A>;
template class TI::TMS::TMS9918<Personality::V9938>;
template class TI::TMS::TMS9918<Personality::V9958>;
template class TI::TMS::TMS9918<Personality::SMSVDP>;
template class TI::TMS::TMS9918<Personality::SMS2VDP>;
template class TI::TMS::TMS9918<Personality::GGVDP>;
template class TI::TMS::TMS9918<Personality::MDVDP>;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,180 @@
//
// ClockConverter.hpp
// Clock Signal
//
// Created by Thomas Harte on 01/01/2023.
// Copyright © 2023 Thomas Harte. All rights reserved.
//
#ifndef ClockConverter_hpp
#define ClockConverter_hpp
#include "../9918.hpp"
#include "PersonalityTraits.hpp"
namespace TI {
namespace TMS {
enum class Clock {
Internal,
TMSPixel,
TMSMemoryWindow,
CRT
};
template <Personality personality, Clock clk> constexpr int clock_rate() {
static_assert(
is_classic_vdp(personality) ||
is_yamaha_vdp(personality) ||
(personality == Personality::MDVDP)
);
switch(clk) {
case Clock::TMSPixel: return 342;
case Clock::TMSMemoryWindow: return 171;
case Clock::CRT: return 1368;
case Clock::Internal:
if constexpr (is_classic_vdp(personality)) {
return 342;
} else if constexpr (is_yamaha_vdp(personality)) {
return 1368;
} else if constexpr (personality == Personality::MDVDP) {
return 3420;
}
}
}
template <Personality personality, Clock clock> constexpr int to_internal(int length) {
return length * clock_rate<personality, Clock::Internal>() / clock_rate<personality, clock>();
}
template <Personality personality, Clock clock> constexpr int from_internal(int length) {
return length * clock_rate<personality, clock>() / clock_rate<personality, Clock::Internal>();
}
/// Provides default timing measurements that duplicate the layout of a TMS9928's line,
/// scaled to the clock rate specified.
template <Personality personality> struct StandardTiming {
/// The total number of internal cycles per line of output.
constexpr static int CyclesPerLine = clock_rate<personality, Clock::Internal>();
/// The number of internal cycles that must elapse between a request to read or write and
/// it becoming a candidate for action.
constexpr static int VRAMAccessDelay = 6;
/// The first internal cycle at which pixels will be output in any mode other than text.
/// Pixels implicitly run from here to the end of the line.
constexpr static int FirstPixelCycle = 86 * CyclesPerLine / 342;
/// The first internal cycle at which pixels will be output text mode.
constexpr static int FirstTextCycle = 94 * CyclesPerLine / 342;
/// The final internal cycle at which pixels will be output text mode.
constexpr static int LastTextCycle = 334 * CyclesPerLine / 342;
// For the below, the fixed portion of line layout is:
//
// [0, EndOfRightBorder): right border colour
// [EndOfRightBorder, StartOfSync): blank
// [StartOfSync, EndOfSync): sync
// [EndOfSync, StartOfColourBurst): blank
// [StartOfColourBurst, EndOfColourBurst): the colour burst
// [EndOfColourBurst, StartOfLeftBorder): blank
//
// The region from StartOfLeftBorder until the end is then filled with
// some combination of pixels and more border, depending on the vertical
// position of this line and the current screen mode.
constexpr static int EndOfRightBorder = 15 * CyclesPerLine / 342;
constexpr static int StartOfSync = 23 * CyclesPerLine / 342;
constexpr static int EndOfSync = 49 * CyclesPerLine / 342;
constexpr static int StartOfColourBurst = 51 * CyclesPerLine / 342;
constexpr static int EndOfColourBurst = 65 * CyclesPerLine / 342;
constexpr static int StartOfLeftBorder = 73 * CyclesPerLine / 342;
};
/// Provides concrete, specific timing for the nominated personality.
template <Personality personality> struct Timing: public StandardTiming<personality> {};
/*!
Provides a [potentially-]stateful conversion between the external and internal clocks.
Unlike the other clock conversions, this one may be non-integral, requiring that
an error term be tracked.
*/
template <Personality personality> class ClockConverter {
public:
/*!
Given that another @c source external **half-cycles** has occurred,
indicates how many complete internal **cycles** have additionally elapsed
since the last call to @c to_internal.
E.g. for the TMS, @c source will count 456 ticks per line, and the internal clock
runs at 342 ticks per line, so the proper conversion is to multiply by 3/4.
*/
int to_internal(int source) {
switch(personality) {
// Default behaviour is to apply a multiplication by 3/4;
// this is correct for the TMS and Sega VDPs other than the Mega Drive.
default: {
const int result = source * 3 + cycles_error_;
cycles_error_ = result & 3;
return result >> 2;
}
// The two Yamaha chips have an internal clock that is four times
// as fast as the TMS, therefore a stateless translation is possible.
case Personality::V9938:
case Personality::V9958:
return source * 3;
// The Mega Drive runs at 3420 master clocks per line, which is then
// divided by 4 or 5 depending on other state. That's 7 times the
// rate provided to the CPU; given that the input is in half-cycles
// the proper multiplier is therefore 3.5.
case Personality::MDVDP: {
const int result = source * 7 + cycles_error_;
cycles_error_ = result & 1;
return result >> 1;
}
}
}
/*!
Provides the number of external cycles that need to begin from now in order to
get at least @c internal_cycles into the future.
*/
HalfCycles half_cycles_before_internal_cycles(int internal_cycles) const {
// Logic here correlates with multipliers as per @c to_internal.
switch(personality) {
default:
// Relative to the external clock multiplied by 3, it will definitely take this
// many cycles to complete a further (internal_cycles - 1) after the current one.
internal_cycles = (internal_cycles - 1) << 2;
// It will also be necessary to complete the current one.
internal_cycles += 4 - cycles_error_;
// Round up to get the first external cycle after
// the number of internal_cycles has elapsed.
return HalfCycles((internal_cycles + 2) / 3);
case Personality::V9938:
case Personality::V9958:
return HalfCycles((internal_cycles + 2) / 3);
case Personality::MDVDP:
internal_cycles = (internal_cycles - 1) << 1;
internal_cycles += 2 - cycles_error_;
return HalfCycles((internal_cycles + 6) / 7);
}
}
private:
// Holds current residue in conversion from the external to
// internal clock.
int cycles_error_ = 0;
};
}
}
#endif /* ClockConverter_hpp */

View File

@@ -0,0 +1,293 @@
//
// Draw.hpp
// Clock Signal
//
// Created by Thomas Harte on 05/01/2023.
// Copyright © 2023 Thomas Harte. All rights reserved.
//
#ifndef Draw_hpp
#define Draw_hpp
// MARK: - TMS9918
template <Personality personality>
void Base<personality>::draw_tms_character(int start, int end) {
LineBuffer &line_buffer = line_buffers_[read_pointer_.row];
// Paint the background tiles.
const int pixels_left = end - start;
if(this->screen_mode_ == ScreenMode::MultiColour) {
for(int c = start; c < end; ++c) {
pixel_target_[c] = palette[
(line_buffer.patterns[c >> 3][0] >> (((c & 4)^4))) & 15
];
}
} else {
const int shift = start & 7;
int byte_column = start >> 3;
int length = std::min(pixels_left, 8 - shift);
int pattern = Numeric::bit_reverse(line_buffer.patterns[byte_column][0]) >> shift;
uint8_t colour = line_buffer.patterns[byte_column][1];
uint32_t colours[2] = {
palette[(colour & 15) ? (colour & 15) : background_colour_],
palette[(colour >> 4) ? (colour >> 4) : background_colour_]
};
int background_pixels_left = pixels_left;
while(true) {
background_pixels_left -= length;
for(int c = 0; c < length; ++c) {
pixel_target_[c] = colours[pattern&0x01];
pattern >>= 1;
}
pixel_target_ += length;
if(!background_pixels_left) break;
length = std::min(8, background_pixels_left);
byte_column++;
pattern = Numeric::bit_reverse(line_buffer.patterns[byte_column][0]);
colour = line_buffer.patterns[byte_column][1];
colours[0] = palette[(colour & 15) ? (colour & 15) : background_colour_];
colours[1] = palette[(colour >> 4) ? (colour >> 4) : background_colour_];
}
}
// Paint sprites and check for collisions, but only if at least one sprite is active
// on this line.
if(line_buffer.active_sprite_slot) {
const int shift_advance = sprites_magnified_ ? 1 : 2;
// If this is the start of the line clip any part of any sprites that is off to the left.
if(!start) {
for(int index = 0; index < line_buffer.active_sprite_slot; ++index) {
LineBuffer::ActiveSprite &sprite = line_buffer.active_sprites[index];
if(sprite.x < 0) sprite.shift_position -= shift_advance * sprite.x;
}
}
int sprite_buffer[256];
int sprite_collision = 0;
memset(&sprite_buffer[start], 0, size_t(end - start)*sizeof(sprite_buffer[0]));
constexpr uint32_t sprite_colour_selection_masks[2] = {0x00000000, 0xffffffff};
constexpr int colour_masks[16] = {0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
// Draw all sprites into the sprite buffer.
const int shifter_target = sprites_16x16_ ? 32 : 16;
for(int index = line_buffer.active_sprite_slot - 1; index >= 0; --index) {
LineBuffer::ActiveSprite &sprite = line_buffer.active_sprites[index];
if(sprite.shift_position < shifter_target) {
const int pixel_start = std::max(start, sprite.x);
for(int c = pixel_start; c < end && sprite.shift_position < shifter_target; ++c) {
const int shift = (sprite.shift_position >> 1) ^ 7;
int sprite_colour = (sprite.image[shift >> 3] >> (shift & 7)) & 1;
// A colision is detected regardless of sprite colour ...
sprite_collision |= sprite_buffer[c] & sprite_colour;
sprite_buffer[c] |= sprite_colour;
// ... but a sprite with the transparent colour won't actually be visible.
sprite_colour &= colour_masks[sprite.image[2]&15];
pixel_origin_[c] =
(pixel_origin_[c] & sprite_colour_selection_masks[sprite_colour^1]) |
(palette[sprite.image[2]&15] & sprite_colour_selection_masks[sprite_colour]);
sprite.shift_position += shift_advance;
}
}
}
status_ |= sprite_collision << StatusSpriteCollisionShift;
}
}
template <Personality personality>
void Base<personality>::draw_tms_text(int start, int end) {
LineBuffer &line_buffer = line_buffers_[read_pointer_.row];
const uint32_t colours[2] = { palette[background_colour_], palette[text_colour_] };
const int shift = start % 6;
int byte_column = start / 6;
int pattern = Numeric::bit_reverse(line_buffer.patterns[byte_column][0]) >> shift;
int pixels_left = end - start;
int length = std::min(pixels_left, 6 - shift);
while(true) {
pixels_left -= length;
for(int c = 0; c < length; ++c) {
pixel_target_[c] = colours[pattern&0x01];
pattern >>= 1;
}
pixel_target_ += length;
if(!pixels_left) break;
length = std::min(6, pixels_left);
byte_column++;
pattern = Numeric::bit_reverse(line_buffer.patterns[byte_column][0]);
}
}
// MARK: - Master System
template <Personality personality>
void Base<personality>::draw_sms(int start, int end, uint32_t cram_dot) {
LineBuffer &line_buffer = line_buffers_[read_pointer_.row];
int colour_buffer[256];
/*
Add extra border for any pixels that fall before the fine scroll.
*/
int tile_start = start, tile_end = end;
int tile_offset = start;
if(read_pointer_.row >= 16 || !master_system_.horizontal_scroll_lock) {
for(int c = start; c < (line_buffer.latched_horizontal_scroll & 7); ++c) {
colour_buffer[c] = 16 + background_colour_;
++tile_offset;
}
// Remove the border area from that to which tiles will be drawn.
tile_start = std::max(start - (line_buffer.latched_horizontal_scroll & 7), 0);
tile_end = std::max(end - (line_buffer.latched_horizontal_scroll & 7), 0);
}
uint32_t pattern;
uint8_t *const pattern_index = reinterpret_cast<uint8_t *>(&pattern);
/*
Add background tiles; these will fill the colour_buffer with values in which
the low five bits are a palette index, and bit six is set if this tile has
priority over sprites.
*/
if(tile_start < end) {
const int shift = tile_start & 7;
int byte_column = tile_start >> 3;
int pixels_left = tile_end - tile_start;
int length = std::min(pixels_left, 8 - shift);
pattern = *reinterpret_cast<const uint32_t *>(line_buffer.patterns[byte_column]);
if(line_buffer.names[byte_column].flags&2)
pattern >>= shift;
else
pattern <<= shift;
while(true) {
const int palette_offset = (line_buffer.names[byte_column].flags&0x18) << 1;
if(line_buffer.names[byte_column].flags&2) {
for(int c = 0; c < length; ++c) {
colour_buffer[tile_offset] =
((pattern_index[3] & 0x01) << 3) |
((pattern_index[2] & 0x01) << 2) |
((pattern_index[1] & 0x01) << 1) |
((pattern_index[0] & 0x01) << 0) |
palette_offset;
++tile_offset;
pattern >>= 1;
}
} else {
for(int c = 0; c < length; ++c) {
colour_buffer[tile_offset] =
((pattern_index[3] & 0x80) >> 4) |
((pattern_index[2] & 0x80) >> 5) |
((pattern_index[1] & 0x80) >> 6) |
((pattern_index[0] & 0x80) >> 7) |
palette_offset;
++tile_offset;
pattern <<= 1;
}
}
pixels_left -= length;
if(!pixels_left) break;
length = std::min(8, pixels_left);
byte_column++;
pattern = *reinterpret_cast<const uint32_t *>(line_buffer.patterns[byte_column]);
}
}
/*
Apply sprites (if any).
*/
if(line_buffer.active_sprite_slot) {
const int shift_advance = sprites_magnified_ ? 1 : 2;
// If this is the start of the line clip any part of any sprites that is off to the left.
if(!start) {
for(int index = 0; index < line_buffer.active_sprite_slot; ++index) {
LineBuffer::ActiveSprite &sprite = line_buffer.active_sprites[index];
if(sprite.x < 0) sprite.shift_position -= shift_advance * sprite.x;
}
}
int sprite_buffer[256];
int sprite_collision = 0;
memset(&sprite_buffer[start], 0, size_t(end - start)*sizeof(sprite_buffer[0]));
// Draw all sprites into the sprite buffer.
for(int index = line_buffer.active_sprite_slot - 1; index >= 0; --index) {
LineBuffer::ActiveSprite &sprite = line_buffer.active_sprites[index];
if(sprite.shift_position < 16) {
const int pixel_start = std::max(start, sprite.x);
// TODO: it feels like the work below should be simplifiable;
// the double shift in particular, and hopefully the variable shift.
for(int c = pixel_start; c < end && sprite.shift_position < 16; ++c) {
const int shift = (sprite.shift_position >> 1);
const int sprite_colour =
(((sprite.image[3] << shift) & 0x80) >> 4) |
(((sprite.image[2] << shift) & 0x80) >> 5) |
(((sprite.image[1] << shift) & 0x80) >> 6) |
(((sprite.image[0] << shift) & 0x80) >> 7);
if(sprite_colour) {
sprite_collision |= sprite_buffer[c];
sprite_buffer[c] = sprite_colour | 0x10;
}
sprite.shift_position += shift_advance;
}
}
}
// Draw the sprite buffer onto the colour buffer, wherever the tile map doesn't have
// priority (or is transparent).
for(int c = start; c < end; ++c) {
if(
sprite_buffer[c] &&
(!(colour_buffer[c]&0x20) || !(colour_buffer[c]&0xf))
) colour_buffer[c] = sprite_buffer[c];
}
if(sprite_collision)
status_ |= StatusSpriteCollision;
}
// Map from the 32-colour buffer to real output pixels, applying the specific CRAM dot if any.
pixel_target_[start] = master_system_.colour_ram[colour_buffer[start] & 0x1f] | cram_dot;
for(int c = start+1; c < end; ++c) {
pixel_target_[c] = master_system_.colour_ram[colour_buffer[c] & 0x1f];
}
// If the VDP is set to hide the left column and this is the final call that'll come
// this line, hide it.
if(end == 256) {
if(master_system_.hide_left_column) {
pixel_origin_[0] = pixel_origin_[1] = pixel_origin_[2] = pixel_origin_[3] =
pixel_origin_[4] = pixel_origin_[5] = pixel_origin_[6] = pixel_origin_[7] =
master_system_.colour_ram[16 + background_colour_];
}
}
}
// MARK: - Yamaha
// TODO.
// MARK: - Mega Drive
// TODO.
#endif /* Draw_hpp */

View File

@@ -0,0 +1,497 @@
//
// Fetch.hpp
// Clock Signal
//
// Created by Thomas Harte on 01/01/2023.
// Copyright © 2023 Thomas Harte. All rights reserved.
//
#ifndef Fetch_hpp
#define Fetch_hpp
/*
Fetching routines follow below; they obey the following rules:
1) input is a start position and an end position; they should perform the proper
operations for the period: start <= time < end.
2) times are measured relative to a 172-cycles-per-line clock (so: they directly
count access windows on the TMS and Master System).
3) time 0 is the beginning of the access window immediately after the last pattern/data
block fetch that would contribute to this line, in a normal 32-column mode. So:
* it's cycle 309 on Mattias' TMS diagram;
* it's cycle 1238 on his V9938 diagram;
* it's after the last background render block in Mask of Destiny's Master System timing diagram.
That division point was selected, albeit arbitrarily, because it puts all the tile
fetches for a single line into the same [0, 171] period.
4) all of these functions are templated with a `use_end` parameter. That will be true if
end is < 172, false otherwise. So functions can use it to eliminate should-exit-not checks,
for the more usual path of execution.
Provided for the benefit of the methods below:
* the function external_slot(), which will perform any pending VRAM read/write.
* the macros slot(n) and external_slot(n) which can be used to schedule those things inside a
switch(start)-based implementation.
All functions should just spool data to intermediary storage. This is because for most VDPs there is
a decoupling between fetch pattern and output pattern, and it's neater to keep the same division
for the exceptions.
*/
#define slot(n) \
if(use_end && end == n) return; \
[[fallthrough]]; \
case n
#define external_slot(n) \
slot(n): do_external_slot(to_internal<personality, Clock::TMSMemoryWindow>(n));
#define external_slots_2(n) \
external_slot(n); \
external_slot(n+1);
#define external_slots_4(n) \
external_slots_2(n); \
external_slots_2(n+2);
#define external_slots_8(n) \
external_slots_4(n); \
external_slots_4(n+4);
#define external_slots_16(n) \
external_slots_8(n); \
external_slots_8(n+8);
#define external_slots_32(n) \
external_slots_16(n); \
external_slots_16(n+16);
// MARK: - TMS9918
template <Personality personality>
template<bool use_end> void Base<personality>::fetch_tms_refresh(int start, int end) {
#define refresh(location) \
slot(location): \
external_slot(location+1);
#define refreshes_2(location) \
refresh(location); \
refresh(location+2);
#define refreshes_4(location) \
refreshes_2(location); \
refreshes_2(location+4);
#define refreshes_8(location) \
refreshes_4(location); \
refreshes_4(location+8);
switch(start) {
default: assert(false);
/* 44 external slots */
external_slots_32(0)
external_slots_8(32)
external_slots_4(40)
/* 64 refresh/external slot pairs (= 128 windows) */
refreshes_8(44);
refreshes_8(60);
refreshes_8(76);
refreshes_8(92);
refreshes_8(108);
refreshes_8(124);
refreshes_8(140);
refreshes_8(156);
return;
}
#undef refreshes_8
#undef refreshes_4
#undef refreshes_2
#undef refresh
}
template <Personality personality>
template<bool use_end> void Base<personality>::fetch_tms_text(int start, int end) {
#define fetch_tile_name(location, column) slot(location): line_buffer.names[column].offset = ram_[row_base + column];
#define fetch_tile_pattern(location, column) slot(location): line_buffer.patterns[column][0] = ram_[row_offset + size_t(line_buffer.names[column].offset << 3)];
#define fetch_column(location, column) \
fetch_tile_name(location, column); \
external_slot(location+1); \
fetch_tile_pattern(location+2, column);
#define fetch_columns_2(location, column) \
fetch_column(location, column); \
fetch_column(location+3, column+1);
#define fetch_columns_4(location, column) \
fetch_columns_2(location, column); \
fetch_columns_2(location+6, column+2);
#define fetch_columns_8(location, column) \
fetch_columns_4(location, column); \
fetch_columns_4(location+12, column+4);
LineBuffer &line_buffer = line_buffers_[write_pointer_.row];
const size_t row_base = pattern_name_address_ & (0x3c00 | size_t(write_pointer_.row >> 3) * 40);
const size_t row_offset = pattern_generator_table_address_ & (0x3800 | (write_pointer_.row & 7));
switch(start) {
default: assert(false);
/* 47 external slots (= 47 windows) */
external_slots_32(0)
external_slots_8(32)
external_slots_4(40)
external_slots_2(44)
external_slot(46)
/* 40 column fetches (= 120 windows) */
fetch_columns_8(47, 0);
fetch_columns_8(71, 8);
fetch_columns_8(95, 16);
fetch_columns_8(119, 24);
fetch_columns_8(143, 32);
/* 5 more external slots */
external_slots_4(167);
external_slot(171);
return;
}
#undef fetch_columns_8
#undef fetch_columns_4
#undef fetch_columns_2
#undef fetch_column
#undef fetch_tile_pattern
#undef fetch_tile_name
}
template <Personality personality>
template<bool use_end> void Base<personality>::fetch_tms_character(int start, int end) {
#define sprite_fetch_coordinates(location, sprite) \
slot(location): \
slot(location+1): \
line_buffer.active_sprites[sprite].x = \
ram_[\
sprite_attribute_table_address_ & size_t(0x3f81 | (line_buffer.active_sprites[sprite].index << 2))\
];
// This implementation doesn't refetch Y; it's unclear to me
// whether it's refetched.
#define sprite_fetch_graphics(location, sprite) \
slot(location): \
slot(location+1): \
slot(location+2): \
slot(location+3): {\
const uint8_t name = ram_[\
sprite_attribute_table_address_ & size_t(0x3f82 | (line_buffer.active_sprites[sprite].index << 2))\
] & (sprites_16x16_ ? ~3 : ~0);\
line_buffer.active_sprites[sprite].image[2] = ram_[\
sprite_attribute_table_address_ & size_t(0x3f83 | (line_buffer.active_sprites[sprite].index << 2))\
];\
line_buffer.active_sprites[sprite].x -= (line_buffer.active_sprites[sprite].image[2] & 0x80) >> 2;\
const size_t graphic_location = sprite_generator_table_address_ & size_t(0x3800 | (name << 3) | line_buffer.active_sprites[sprite].row); \
line_buffer.active_sprites[sprite].image[0] = ram_[graphic_location];\
line_buffer.active_sprites[sprite].image[1] = ram_[graphic_location+16];\
}
#define sprite_fetch_block(location, sprite) \
sprite_fetch_coordinates(location, sprite) \
sprite_fetch_graphics(location+2, sprite)
#define sprite_y_read(location, sprite) \
slot(location): posit_sprite(sprite_selection_buffer, sprite, ram_[sprite_attribute_table_address_ & (((sprite) << 2) | 0x3f80)], write_pointer_.row);
#define fetch_tile_name(column) line_buffer.names[column].offset = ram_[(row_base + column) & 0x3fff];
#define fetch_tile(column) {\
line_buffer.patterns[column][1] = ram_[(colour_base + size_t((line_buffer.names[column].offset << 3) >> colour_name_shift)) & 0x3fff]; \
line_buffer.patterns[column][0] = ram_[(pattern_base + size_t(line_buffer.names[column].offset << 3)) & 0x3fff]; \
}
#define background_fetch_block(location, column, sprite) \
slot(location): fetch_tile_name(column) \
external_slot(location+1); \
slot(location+2): \
slot(location+3): fetch_tile(column) \
slot(location+4): fetch_tile_name(column+1) \
sprite_y_read(location+5, sprite); \
slot(location+6): \
slot(location+7): fetch_tile(column+1) \
slot(location+8): fetch_tile_name(column+2) \
sprite_y_read(location+9, sprite+1); \
slot(location+10): \
slot(location+11): fetch_tile(column+2) \
slot(location+12): fetch_tile_name(column+3) \
sprite_y_read(location+13, sprite+2); \
slot(location+14): \
slot(location+15): fetch_tile(column+3)
LineBuffer &line_buffer = line_buffers_[write_pointer_.row];
LineBuffer &sprite_selection_buffer = line_buffers_[(write_pointer_.row + 1) % mode_timing_.total_lines];
const size_t row_base = pattern_name_address_ & (size_t((write_pointer_.row << 2)&~31) | 0x3c00);
size_t pattern_base = pattern_generator_table_address_;
size_t colour_base = colour_table_address_;
int colour_name_shift = 6;
if(screen_mode_ == ScreenMode::Graphics) {
// If this is high resolution mode, allow the row number to affect the pattern and colour addresses.
pattern_base &= size_t(0x2000 | ((write_pointer_.row & 0xc0) << 5));
colour_base &= size_t(0x2000 | ((write_pointer_.row & 0xc0) << 5));
colour_base += size_t(write_pointer_.row & 7);
colour_name_shift = 0;
} else {
colour_base &= size_t(0xffc0);
pattern_base &= size_t(0x3800);
}
if(screen_mode_ == ScreenMode::MultiColour) {
pattern_base += size_t((write_pointer_.row >> 2) & 7);
} else {
pattern_base += size_t(write_pointer_.row & 7);
}
switch(start) {
default: assert(false);
external_slots_2(0);
sprite_fetch_block(2, 0);
sprite_fetch_block(8, 1);
sprite_fetch_coordinates(14, 2);
external_slots_4(16);
external_slot(20);
sprite_fetch_graphics(21, 2);
sprite_fetch_block(25, 3);
slot(31):
sprite_selection_buffer.reset_sprite_collection();
do_external_slot(to_internal<personality, Clock::TMSMemoryWindow>(31));
external_slots_2(32);
external_slot(34);
sprite_y_read(35, 0);
sprite_y_read(36, 1);
sprite_y_read(37, 2);
sprite_y_read(38, 3);
sprite_y_read(39, 4);
sprite_y_read(40, 5);
sprite_y_read(41, 6);
sprite_y_read(42, 7);
background_fetch_block(43, 0, 8);
background_fetch_block(59, 4, 11);
background_fetch_block(75, 8, 14);
background_fetch_block(91, 12, 17);
background_fetch_block(107, 16, 20);
background_fetch_block(123, 20, 23);
background_fetch_block(139, 24, 26);
background_fetch_block(155, 28, 29);
return;
}
#undef background_fetch_block
#undef fetch_tile
#undef fetch_tile_name
#undef sprite_y_read
#undef sprite_fetch_block
#undef sprite_fetch_graphics
#undef sprite_fetch_coordinates
}
// MARK: - Master System
template <Personality personality>
template<bool use_end> void Base<personality>::fetch_sms(int start, int end) {
#define sprite_fetch(sprite) {\
line_buffer.active_sprites[sprite].x = \
ram_[\
master_system_.sprite_attribute_table_address & size_t(0x3f80 | (line_buffer.active_sprites[sprite].index << 1))\
] - (master_system_.shift_sprites_8px_left ? 8 : 0); \
const uint8_t name = ram_[\
master_system_.sprite_attribute_table_address & size_t(0x3f81 | (line_buffer.active_sprites[sprite].index << 1))\
] & (sprites_16x16_ ? ~1 : ~0);\
const size_t graphic_location = master_system_.sprite_generator_table_address & size_t(0x2000 | (name << 5) | (line_buffer.active_sprites[sprite].row << 2)); \
line_buffer.active_sprites[sprite].image[0] = ram_[graphic_location]; \
line_buffer.active_sprites[sprite].image[1] = ram_[graphic_location+1]; \
line_buffer.active_sprites[sprite].image[2] = ram_[graphic_location+2]; \
line_buffer.active_sprites[sprite].image[3] = ram_[graphic_location+3]; \
}
#define sprite_fetch_block(location, sprite) \
slot(location): \
slot(location+1): \
slot(location+2): \
slot(location+3): \
slot(location+4): \
slot(location+5): \
sprite_fetch(sprite);\
sprite_fetch(sprite+1);
#define sprite_y_read(location, sprite) \
slot(location): \
posit_sprite(sprite_selection_buffer, sprite, ram_[master_system_.sprite_attribute_table_address & ((sprite) | 0x3f00)], write_pointer_.row); \
posit_sprite(sprite_selection_buffer, sprite+1, ram_[master_system_.sprite_attribute_table_address & ((sprite + 1) | 0x3f00)], write_pointer_.row); \
#define fetch_tile_name(column, row_info) {\
const size_t scrolled_column = (column - horizontal_offset) & 0x1f;\
const size_t address = row_info.pattern_address_base + (scrolled_column << 1); \
line_buffer.names[column].flags = ram_[address+1]; \
line_buffer.names[column].offset = size_t( \
(((line_buffer.names[column].flags&1) << 8) | ram_[address]) << 5 \
) + row_info.sub_row[(line_buffer.names[column].flags&4) >> 2]; \
}
#define fetch_tile(column) \
line_buffer.patterns[column][0] = ram_[line_buffer.names[column].offset]; \
line_buffer.patterns[column][1] = ram_[line_buffer.names[column].offset+1]; \
line_buffer.patterns[column][2] = ram_[line_buffer.names[column].offset+2]; \
line_buffer.patterns[column][3] = ram_[line_buffer.names[column].offset+3];
#define background_fetch_block(location, column, sprite, row_info) \
slot(location): fetch_tile_name(column, row_info) \
external_slot(location+1); \
slot(location+2): \
slot(location+3): \
slot(location+4): \
fetch_tile(column) \
fetch_tile_name(column+1, row_info) \
sprite_y_read(location+5, sprite); \
slot(location+6): \
slot(location+7): \
slot(location+8): \
fetch_tile(column+1) \
fetch_tile_name(column+2, row_info) \
sprite_y_read(location+9, sprite+2); \
slot(location+10): \
slot(location+11): \
slot(location+12): \
fetch_tile(column+2) \
fetch_tile_name(column+3, row_info) \
sprite_y_read(location+13, sprite+4); \
slot(location+14): \
slot(location+15): fetch_tile(column+3)
// Determine the coarse horizontal scrolling offset; this isn't applied on the first two lines if the programmer has requested it.
LineBuffer &line_buffer = line_buffers_[write_pointer_.row];
LineBuffer &sprite_selection_buffer = line_buffers_[(write_pointer_.row + 1) % mode_timing_.total_lines];
const int horizontal_offset = (write_pointer_.row >= 16 || !master_system_.horizontal_scroll_lock) ? (line_buffer.latched_horizontal_scroll >> 3) : 0;
// Limit address bits in use if this is a SMS2 mode.
const bool is_tall_mode = mode_timing_.pixel_lines != 192;
const size_t pattern_name_address = master_system_.pattern_name_address | (is_tall_mode ? 0x800 : 0);
const size_t pattern_name_offset = is_tall_mode ? 0x100 : 0;
// Determine row info for the screen both (i) if vertical scrolling is applied; and (ii) if it isn't.
// The programmer can opt out of applying vertical scrolling to the right-hand portion of the display.
const int scrolled_row = (write_pointer_.row + master_system_.latched_vertical_scroll) % (is_tall_mode ? 256 : 224);
struct RowInfo {
size_t pattern_address_base;
size_t sub_row[2];
};
const RowInfo scrolled_row_info = {
(pattern_name_address & size_t(((scrolled_row & ~7) << 3) | 0x3800)) - pattern_name_offset,
{size_t((scrolled_row & 7) << 2), 28 ^ size_t((scrolled_row & 7) << 2)}
};
RowInfo row_info;
if(master_system_.vertical_scroll_lock) {
row_info.pattern_address_base = (pattern_name_address & size_t(((write_pointer_.row & ~7) << 3) | 0x3800)) - pattern_name_offset;
row_info.sub_row[0] = size_t((write_pointer_.row & 7) << 2);
row_info.sub_row[1] = 28 ^ size_t((write_pointer_.row & 7) << 2);
} else row_info = scrolled_row_info;
// ... and do the actual fetching, which follows this routine:
switch(start) {
default: assert(false);
sprite_fetch_block(0, 0);
sprite_fetch_block(6, 2);
external_slots_4(12);
external_slot(16);
sprite_fetch_block(17, 4);
sprite_fetch_block(23, 6);
slot(29):
sprite_selection_buffer.reset_sprite_collection();
do_external_slot(to_internal<personality, Clock::TMSMemoryWindow>(29));
external_slot(30);
sprite_y_read(31, 0);
sprite_y_read(32, 2);
sprite_y_read(33, 4);
sprite_y_read(34, 6);
sprite_y_read(35, 8);
sprite_y_read(36, 10);
sprite_y_read(37, 12);
sprite_y_read(38, 14);
background_fetch_block(39, 0, 16, scrolled_row_info);
background_fetch_block(55, 4, 22, scrolled_row_info);
background_fetch_block(71, 8, 28, scrolled_row_info);
background_fetch_block(87, 12, 34, scrolled_row_info);
background_fetch_block(103, 16, 40, scrolled_row_info);
background_fetch_block(119, 20, 46, scrolled_row_info);
background_fetch_block(135, 24, 52, row_info);
background_fetch_block(151, 28, 58, row_info);
external_slots_4(167);
return;
}
#undef background_fetch_block
#undef fetch_tile
#undef fetch_tile_name
#undef sprite_y_read
#undef sprite_fetch_block
#undef sprite_fetch
}
// MARK: - Yamaha
// TODO.
template <Personality personality>
template<bool use_end> void Base<personality>::fetch_yamaha_refresh(int start, int end) {
(void)start;
(void)end;
}
template <Personality personality>
template<bool use_end> void Base<personality>::fetch_yamaha_no_sprites(int start, int end) {
(void)start;
(void)end;
}
template <Personality personality>
template<bool use_end> void Base<personality>::fetch_yamaha_sprites(int start, int end) {
(void)start;
(void)end;
}
// MARK: - Mega Drive
// TODO.
#undef external_slot
#undef slot
#endif /* Fetch_hpp */

View File

@@ -0,0 +1,49 @@
//
// PersonalityTraits.hpp
// Clock Signal
//
// Created by Thomas Harte on 06/01/2023.
// Copyright © 2023 Thomas Harte. All rights reserved.
//
#ifndef PersonalityTraits_hpp
#define PersonalityTraits_hpp
namespace TI {
namespace TMS {
// Genus determinants for the various personalityes.
constexpr bool is_sega_vdp(Personality p) {
return p >= Personality::SMSVDP;
}
constexpr bool is_yamaha_vdp(Personality p) {
return p == Personality::V9938 || p == Personality::V9958;
}
// i.e. one with the original internal timings.
constexpr bool is_classic_vdp(Personality p) {
return
p == Personality::TMS9918A ||
p == Personality::SMSVDP ||
p == Personality::SMS2VDP ||
p == Personality::GGVDP;
}
constexpr size_t memory_size(Personality p) {
switch(p) {
case TI::TMS::TMS9918A:
case TI::TMS::SMSVDP:
case TI::TMS::SMS2VDP:
case TI::TMS::GGVDP: return 16 * 1024;
case TI::TMS::MDVDP: return 64 * 1024;
case TI::TMS::V9938: return 128 * 1024;
case TI::TMS::V9958: return 192 * 1024;
}
}
}
}
#endif /* PersonalityTraits_hpp */

View File

@@ -117,7 +117,6 @@ class ConcreteMachine:
public: public:
ConcreteMachine(const Analyser::Static::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) : ConcreteMachine(const Analyser::Static::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) :
z80_(*this), z80_(*this),
vdp_(TI::TMS::TMS9918A),
sn76489_(TI::SN76489::Personality::SN76489, audio_queue_, sn76489_divider), sn76489_(TI::SN76489::Personality::SN76489, audio_queue_, sn76489_divider),
ay_(GI::AY38910::Personality::AY38910, audio_queue_), ay_(GI::AY38910::Personality::AY38910, audio_queue_),
mixer_(sn76489_, ay_), mixer_(sn76489_, ay_),
@@ -379,7 +378,7 @@ class ConcreteMachine:
} }
CPU::Z80::Processor<ConcreteMachine, false, false> z80_; CPU::Z80::Processor<ConcreteMachine, false, false> z80_;
JustInTimeActor<TI::TMS::TMS9918> vdp_; JustInTimeActor<TI::TMS::TMS9918<TI::TMS::Personality::TMS9918A>> vdp_;
Concurrency::AsyncTaskQueue<false> audio_queue_; Concurrency::AsyncTaskQueue<false> audio_queue_;
TI::SN76489 sn76489_; TI::SN76489 sn76489_;

View File

@@ -146,7 +146,6 @@ class ConcreteMachine:
ConcreteMachine(const Target &target, const ROMMachine::ROMFetcher &rom_fetcher): ConcreteMachine(const Target &target, const ROMMachine::ROMFetcher &rom_fetcher):
z80_(*this), z80_(*this),
vdp_(TI::TMS::TMS9918A),
i8255_(i8255_port_handler_), i8255_(i8255_port_handler_),
ay_(GI::AY38910::Personality::AY38910, audio_queue_), ay_(GI::AY38910::Personality::AY38910, audio_queue_),
audio_toggle_(audio_queue_), audio_toggle_(audio_queue_),
@@ -744,7 +743,7 @@ class ConcreteMachine:
}; };
CPU::Z80::Processor<ConcreteMachine, false, false> z80_; CPU::Z80::Processor<ConcreteMachine, false, false> z80_;
JustInTimeActor<TI::TMS::TMS9918> vdp_; JustInTimeActor<TI::TMS::TMS9918<TI::TMS::Personality::TMS9918A>> vdp_;
Intel::i8255::i8255<i8255PortHandler> i8255_; Intel::i8255::i8255<i8255PortHandler> i8255_;
Concurrency::AsyncTaskQueue<false> audio_queue_; Concurrency::AsyncTaskQueue<false> audio_queue_;

View File

@@ -29,6 +29,7 @@
#include "../../Analyser/Static/Sega/Target.hpp" #include "../../Analyser/Static/Sega/Target.hpp"
#include <algorithm> #include <algorithm>
#include <cassert>
#include <iostream> #include <iostream>
namespace { namespace {
@@ -77,7 +78,7 @@ class Joystick: public Inputs::ConcreteJoystick {
uint8_t state_ = 0xff; uint8_t state_ = 0xff;
}; };
class ConcreteMachine: template <Analyser::Static::Sega::Target::Model model> class ConcreteMachine:
public Machine, public Machine,
public CPU::Z80::BusHandler, public CPU::Z80::BusHandler,
public MachineTypes::TimedMachine, public MachineTypes::TimedMachine,
@@ -90,11 +91,9 @@ class ConcreteMachine:
public: public:
ConcreteMachine(const Analyser::Static::Sega::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) : ConcreteMachine(const Analyser::Static::Sega::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) :
model_(target.model),
region_(target.region), region_(target.region),
paging_scheme_(target.paging_scheme), paging_scheme_(target.paging_scheme),
z80_(*this), z80_(*this),
vdp_(tms_personality_for_model(target.model)),
sn76489_( sn76489_(
(target.model == Target::Model::SG1000) ? TI::SN76489::Personality::SN76489 : TI::SN76489::Personality::SMS, (target.model == Target::Model::SG1000) ? TI::SN76489::Personality::SN76489 : TI::SN76489::Personality::SMS,
audio_queue_, audio_queue_,
@@ -159,7 +158,7 @@ class ConcreteMachine:
page_cartridge(); page_cartridge();
// Map RAM. // Map RAM.
if(is_master_system(model_)) { if constexpr (is_master_system(model)) {
map(read_pointers_, ram_, 8*1024, 0xc000, 0x10000); map(read_pointers_, ram_, 8*1024, 0xc000, 0x10000);
map(write_pointers_, ram_, 8*1024, 0xc000, 0x10000); map(write_pointers_, ram_, 8*1024, 0xc000, 0x10000);
} else { } else {
@@ -311,7 +310,7 @@ class ConcreteMachine:
case CPU::Z80::PartialMachineCycle::Output: case CPU::Z80::PartialMachineCycle::Output:
switch(address & 0xc1) { switch(address & 0xc1) {
case 0x00: // i.e. even ports less than 0x40. case 0x00: // i.e. even ports less than 0x40.
if(is_master_system(model_)) { if constexpr (is_master_system(model)) {
// TODO: Obey the RAM enable. // TODO: Obey the RAM enable.
LOG("Memory control: " << PADHEX(2) << memory_control_); LOG("Memory control: " << PADHEX(2) << memory_control_);
memory_control_ = *cycle.value; memory_control_ = *cycle.value;
@@ -431,7 +430,7 @@ class ConcreteMachine:
} }
private: private:
static TI::TMS::Personality tms_personality_for_model(Analyser::Static::Sega::Target::Model model) { static constexpr TI::TMS::Personality tms_personality() {
switch(model) { switch(model) {
default: default:
case Target::Model::SG1000: return TI::TMS::TMS9918A; case Target::Model::SG1000: return TI::TMS::TMS9918A;
@@ -481,11 +480,10 @@ class ConcreteMachine:
} }
using Target = Analyser::Static::Sega::Target; using Target = Analyser::Static::Sega::Target;
const Target::Model model_;
const Target::Region region_; const Target::Region region_;
const Target::PagingScheme paging_scheme_; const Target::PagingScheme paging_scheme_;
CPU::Z80::Processor<ConcreteMachine, false, false> z80_; CPU::Z80::Processor<ConcreteMachine, false, false> z80_;
JustInTimeActor<TI::TMS::TMS9918> vdp_; JustInTimeActor<TI::TMS::TMS9918<tms_personality()>> vdp_;
Concurrency::AsyncTaskQueue<false> audio_queue_; Concurrency::AsyncTaskQueue<false> audio_queue_;
TI::SN76489 sn76489_; TI::SN76489 sn76489_;
@@ -559,7 +557,14 @@ using namespace Sega::MasterSystem;
Machine *Machine::MasterSystem(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) { Machine *Machine::MasterSystem(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) {
using Target = Analyser::Static::Sega::Target; using Target = Analyser::Static::Sega::Target;
const Target *const sega_target = dynamic_cast<const Target *>(target); const Target *const sega_target = dynamic_cast<const Target *>(target);
return new ConcreteMachine(*sega_target, rom_fetcher);
switch(sega_target->model) {
case Target::Model::SG1000: return new ConcreteMachine<Target::Model::SG1000>(*sega_target, rom_fetcher);
case Target::Model::MasterSystem: return new ConcreteMachine<Target::Model::MasterSystem>(*sega_target, rom_fetcher);
case Target::Model::MasterSystem2: return new ConcreteMachine<Target::Model::MasterSystem2>(*sega_target, rom_fetcher);
default:
assert(false);
}
} }
Machine::~Machine() {} Machine::~Machine() {}

62
Numeric/BitReverse.hpp Normal file
View File

@@ -0,0 +1,62 @@
//
// BitReverse.hpp
// Clock Signal
//
// Created by Thomas Harte on 05/01/2023.
// Copyright © 2023 Thomas Harte. All rights reserved.
//
#ifndef BitReverse_hpp
#define BitReverse_hpp
#include <cstdint>
namespace Numeric {
/// @returns @c source with the order of its bits reversed. E.g. if @c IntT is @c uint8_t then
/// the reverse of bit pattern abcd efgh is hgfd dcba.
template <typename IntT> constexpr IntT bit_reverse(IntT source);
// The single-byte specialisation uses a lookup table.
template<> constexpr uint8_t bit_reverse<uint8_t>(uint8_t source) {
struct ReverseTable {
static constexpr std::array<uint8_t, 256> reverse_table() {
std::array<uint8_t, 256> map{};
for(std::size_t c = 0; c < 256; ++c) {
map[c] = uint8_t(
((c & 0x80) >> 7) |
((c & 0x40) >> 5) |
((c & 0x20) >> 3) |
((c & 0x10) >> 1) |
((c & 0x08) << 1) |
((c & 0x04) << 3) |
((c & 0x02) << 5) |
((c & 0x01) << 7)
);
}
return map;
}
};
const std::array<uint8_t, 256> map = ReverseTable::reverse_table();
return map[source];
}
// All other versions just call the byte-level reverse the appropriate number of times.
template <typename IntT> constexpr IntT bit_reverse(IntT source) {
IntT result;
uint8_t *src = reinterpret_cast<uint8_t *>(&source);
uint8_t *dest = reinterpret_cast<uint8_t *>(&result) + sizeof(result) - 1;
for(size_t c = 0; c < sizeof(source); c++) {
*dest = bit_reverse(*src);
++src;
--dest;
}
return result;
}
}
#endif /* BitReverse_hpp */

View File

@@ -137,8 +137,6 @@
4B0E04EA1FC9E5DA00F43484 /* CAS.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0E04E81FC9E5DA00F43484 /* CAS.cpp */; }; 4B0E04EA1FC9E5DA00F43484 /* CAS.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0E04E81FC9E5DA00F43484 /* CAS.cpp */; };
4B0E04EB1FC9E78800F43484 /* CAS.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0E04E81FC9E5DA00F43484 /* CAS.cpp */; }; 4B0E04EB1FC9E78800F43484 /* CAS.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0E04E81FC9E5DA00F43484 /* CAS.cpp */; };
4B0E04F11FC9EA9500F43484 /* MSX.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B79A4FF1FC913C900EEDAD5 /* MSX.cpp */; }; 4B0E04F11FC9EA9500F43484 /* MSX.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B79A4FF1FC913C900EEDAD5 /* MSX.cpp */; };
4B0E04FA1FC9FA3100F43484 /* 9918.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0E04F91FC9FA3100F43484 /* 9918.cpp */; };
4B0E04FB1FC9FA3100F43484 /* 9918.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0E04F91FC9FA3100F43484 /* 9918.cpp */; };
4B0E61071FF34737002A9DBD /* MSX.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0E61051FF34737002A9DBD /* MSX.cpp */; }; 4B0E61071FF34737002A9DBD /* MSX.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0E61051FF34737002A9DBD /* MSX.cpp */; };
4B0F1BB22602645900B85C66 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0F1BB02602645900B85C66 /* StaticAnalyser.cpp */; }; 4B0F1BB22602645900B85C66 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0F1BB02602645900B85C66 /* StaticAnalyser.cpp */; };
4B0F1BB32602645900B85C66 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0F1BB02602645900B85C66 /* StaticAnalyser.cpp */; }; 4B0F1BB32602645900B85C66 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0F1BB02602645900B85C66 /* StaticAnalyser.cpp */; };
@@ -229,6 +227,9 @@
4B3F76B925A1635300178AEC /* PowerPCDecoderTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B3F76B825A1635300178AEC /* PowerPCDecoderTests.mm */; }; 4B3F76B925A1635300178AEC /* PowerPCDecoderTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B3F76B825A1635300178AEC /* PowerPCDecoderTests.mm */; };
4B3FCC40201EC24200960631 /* MultiMachine.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B3FCC3F201EC24200960631 /* MultiMachine.cpp */; }; 4B3FCC40201EC24200960631 /* MultiMachine.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B3FCC3F201EC24200960631 /* MultiMachine.cpp */; };
4B3FE75E1F3CF68B00448EE4 /* CPM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B3FE75C1F3CF68B00448EE4 /* CPM.cpp */; }; 4B3FE75E1F3CF68B00448EE4 /* CPM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B3FE75C1F3CF68B00448EE4 /* CPM.cpp */; };
4B43983929620FC7006B0BFC /* 9918.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B43983829620FB1006B0BFC /* 9918.cpp */; };
4B43983A29620FC8006B0BFC /* 9918.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B43983829620FB1006B0BFC /* 9918.cpp */; };
4B43983B29620FC9006B0BFC /* 9918.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B43983829620FB1006B0BFC /* 9918.cpp */; };
4B448E811F1C45A00009ABD6 /* TZX.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B448E7F1F1C45A00009ABD6 /* TZX.cpp */; }; 4B448E811F1C45A00009ABD6 /* TZX.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B448E7F1F1C45A00009ABD6 /* TZX.cpp */; };
4B448E841F1C4C480009ABD6 /* PulseQueuedTape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B448E821F1C4C480009ABD6 /* PulseQueuedTape.cpp */; }; 4B448E841F1C4C480009ABD6 /* PulseQueuedTape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B448E821F1C4C480009ABD6 /* PulseQueuedTape.cpp */; };
4B44EBF51DC987AF00A7820C /* AllSuiteA.bin in Resources */ = {isa = PBXBuildFile; fileRef = 4B44EBF41DC987AE00A7820C /* AllSuiteA.bin */; }; 4B44EBF51DC987AF00A7820C /* AllSuiteA.bin in Resources */ = {isa = PBXBuildFile; fileRef = 4B44EBF41DC987AE00A7820C /* AllSuiteA.bin */; };
@@ -352,7 +353,6 @@
4B7752C228217F5C0073E2C5 /* Spectrum.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B5D5C9525F56FC7001B4623 /* Spectrum.cpp */; }; 4B7752C228217F5C0073E2C5 /* Spectrum.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B5D5C9525F56FC7001B4623 /* Spectrum.cpp */; };
4B7752C328217F720073E2C5 /* Z80.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8DD39526360DDF00B3C866 /* Z80.cpp */; }; 4B7752C328217F720073E2C5 /* Z80.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8DD39526360DDF00B3C866 /* Z80.cpp */; };
4B778EF023A5D68C0000D260 /* 68000Storage.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BFF1D3822337B0300838EA1 /* 68000Storage.cpp */; }; 4B778EF023A5D68C0000D260 /* 68000Storage.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BFF1D3822337B0300838EA1 /* 68000Storage.cpp */; };
4B778EF123A5D6B50000D260 /* 9918.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0E04F91FC9FA3100F43484 /* 9918.cpp */; };
4B778EF323A5DB230000D260 /* PCMSegment.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518731F75E91800926311 /* PCMSegment.cpp */; }; 4B778EF323A5DB230000D260 /* PCMSegment.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518731F75E91800926311 /* PCMSegment.cpp */; };
4B778EF423A5DB3A0000D260 /* C1540.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8334941F5E25B60097E338 /* C1540.cpp */; }; 4B778EF423A5DB3A0000D260 /* C1540.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8334941F5E25B60097E338 /* C1540.cpp */; };
4B778EF523A5DB440000D260 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B894517201967B4007DE474 /* StaticAnalyser.cpp */; }; 4B778EF523A5DB440000D260 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B894517201967B4007DE474 /* StaticAnalyser.cpp */; };
@@ -1179,7 +1179,6 @@
4B0E04E81FC9E5DA00F43484 /* CAS.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = CAS.cpp; sourceTree = "<group>"; }; 4B0E04E81FC9E5DA00F43484 /* CAS.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = CAS.cpp; sourceTree = "<group>"; };
4B0E04E91FC9E5DA00F43484 /* CAS.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = CAS.hpp; sourceTree = "<group>"; }; 4B0E04E91FC9E5DA00F43484 /* CAS.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = CAS.hpp; sourceTree = "<group>"; };
4B0E04F81FC9FA3000F43484 /* 9918.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 9918.hpp; sourceTree = "<group>"; }; 4B0E04F81FC9FA3000F43484 /* 9918.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 9918.hpp; sourceTree = "<group>"; };
4B0E04F91FC9FA3100F43484 /* 9918.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = 9918.cpp; sourceTree = "<group>"; };
4B0E61051FF34737002A9DBD /* MSX.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = MSX.cpp; path = Parsers/MSX.cpp; sourceTree = "<group>"; }; 4B0E61051FF34737002A9DBD /* MSX.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = MSX.cpp; path = Parsers/MSX.cpp; sourceTree = "<group>"; };
4B0E61061FF34737002A9DBD /* MSX.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = MSX.hpp; path = Parsers/MSX.hpp; sourceTree = "<group>"; }; 4B0E61061FF34737002A9DBD /* MSX.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = MSX.hpp; path = Parsers/MSX.hpp; sourceTree = "<group>"; };
4B0F1BB02602645900B85C66 /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StaticAnalyser.cpp; sourceTree = "<group>"; }; 4B0F1BB02602645900B85C66 /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StaticAnalyser.cpp; sourceTree = "<group>"; };
@@ -1247,6 +1246,7 @@
4B228CDA24DA41880077EF25 /* ScanTarget.metal */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.metal; path = ScanTarget.metal; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.metal; }; 4B228CDA24DA41880077EF25 /* ScanTarget.metal */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.metal; path = ScanTarget.metal; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.metal; };
4B24095A1C45DF85004DA684 /* Stepper.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Stepper.hpp; sourceTree = "<group>"; }; 4B24095A1C45DF85004DA684 /* Stepper.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Stepper.hpp; sourceTree = "<group>"; };
4B2530F3244E6773007980BF /* fm.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = fm.json; sourceTree = "<group>"; }; 4B2530F3244E6773007980BF /* fm.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = fm.json; sourceTree = "<group>"; };
4B262BFF29691F55002EC0F7 /* PersonalityTraits.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = PersonalityTraits.hpp; sourceTree = "<group>"; };
4B2A332C1DB86821002876E3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/OricOptions.xib"; sourceTree = SOURCE_ROOT; }; 4B2A332C1DB86821002876E3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/OricOptions.xib"; sourceTree = SOURCE_ROOT; };
4B2A53901D117D36003C6002 /* CSAudioQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSAudioQueue.h; sourceTree = "<group>"; }; 4B2A53901D117D36003C6002 /* CSAudioQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSAudioQueue.h; sourceTree = "<group>"; };
4B2A53911D117D36003C6002 /* CSAudioQueue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CSAudioQueue.m; sourceTree = "<group>"; }; 4B2A53911D117D36003C6002 /* CSAudioQueue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CSAudioQueue.m; sourceTree = "<group>"; };
@@ -1317,6 +1317,11 @@
4B3FCC3F201EC24200960631 /* MultiMachine.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MultiMachine.cpp; sourceTree = "<group>"; }; 4B3FCC3F201EC24200960631 /* MultiMachine.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MultiMachine.cpp; sourceTree = "<group>"; };
4B3FE75C1F3CF68B00448EE4 /* CPM.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CPM.cpp; path = Parsers/CPM.cpp; sourceTree = "<group>"; }; 4B3FE75C1F3CF68B00448EE4 /* CPM.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CPM.cpp; path = Parsers/CPM.cpp; sourceTree = "<group>"; };
4B3FE75D1F3CF68B00448EE4 /* CPM.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = CPM.hpp; path = Parsers/CPM.hpp; sourceTree = "<group>"; }; 4B3FE75D1F3CF68B00448EE4 /* CPM.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = CPM.hpp; path = Parsers/CPM.hpp; sourceTree = "<group>"; };
4B43983829620FB1006B0BFC /* 9918.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = 9918.cpp; sourceTree = "<group>"; };
4B43983C29621024006B0BFC /* ClockConverter.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ClockConverter.hpp; sourceTree = "<group>"; };
4B43983E29628538006B0BFC /* Fetch.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Fetch.hpp; sourceTree = "<group>"; };
4B43983F2967459B006B0BFC /* Draw.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Draw.hpp; sourceTree = "<group>"; };
4B43984129674943006B0BFC /* BitReverse.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = BitReverse.hpp; sourceTree = "<group>"; };
4B448E7F1F1C45A00009ABD6 /* TZX.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TZX.cpp; sourceTree = "<group>"; }; 4B448E7F1F1C45A00009ABD6 /* TZX.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TZX.cpp; sourceTree = "<group>"; };
4B448E801F1C45A00009ABD6 /* TZX.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TZX.hpp; sourceTree = "<group>"; }; 4B448E801F1C45A00009ABD6 /* TZX.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TZX.hpp; sourceTree = "<group>"; };
4B448E821F1C4C480009ABD6 /* PulseQueuedTape.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PulseQueuedTape.cpp; sourceTree = "<group>"; }; 4B448E821F1C4C480009ABD6 /* PulseQueuedTape.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PulseQueuedTape.cpp; sourceTree = "<group>"; };
@@ -2398,7 +2403,6 @@
4B0E04F71FC9F2C800F43484 /* 9918 */ = { 4B0E04F71FC9F2C800F43484 /* 9918 */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
4B0E04F91FC9FA3100F43484 /* 9918.cpp */,
4B0E04F81FC9FA3000F43484 /* 9918.hpp */, 4B0E04F81FC9FA3000F43484 /* 9918.hpp */,
4BD388431FE34E060042B588 /* Implementation */, 4BD388431FE34E060042B588 /* Implementation */,
); );
@@ -3288,6 +3292,7 @@
4B7BA03C23D55E7900B98D9E /* Numeric */ = { 4B7BA03C23D55E7900B98D9E /* Numeric */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
4B43984129674943006B0BFC /* BitReverse.hpp */,
4BD155312716362A00410C6E /* BitSpread.hpp */, 4BD155312716362A00410C6E /* BitSpread.hpp */,
4B7BA03E23D55E7900B98D9E /* CRC.hpp */, 4B7BA03E23D55E7900B98D9E /* CRC.hpp */,
4B7BA03F23D55E7900B98D9E /* LFSR.hpp */, 4B7BA03F23D55E7900B98D9E /* LFSR.hpp */,
@@ -4733,7 +4738,12 @@
4BD388431FE34E060042B588 /* Implementation */ = { 4BD388431FE34E060042B588 /* Implementation */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
4B43983829620FB1006B0BFC /* 9918.cpp */,
4BD388411FE34E010042B588 /* 9918Base.hpp */, 4BD388411FE34E010042B588 /* 9918Base.hpp */,
4B43983C29621024006B0BFC /* ClockConverter.hpp */,
4B43983F2967459B006B0BFC /* Draw.hpp */,
4B43983E29628538006B0BFC /* Fetch.hpp */,
4B262BFF29691F55002EC0F7 /* PersonalityTraits.hpp */,
); );
path = Implementation; path = Implementation;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -5519,7 +5529,6 @@
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
4B0E04FB1FC9FA3100F43484 /* 9918.cpp in Sources */,
4B1B88C9202E469400B67DFF /* MultiJoystickMachine.cpp in Sources */, 4B1B88C9202E469400B67DFF /* MultiJoystickMachine.cpp in Sources */,
4BCE1DF225D4C3FA00AE7A2B /* Bus.cpp in Sources */, 4BCE1DF225D4C3FA00AE7A2B /* Bus.cpp in Sources */,
4BC080DA26A25ADA00D03FD8 /* Amiga.cpp in Sources */, 4BC080DA26A25ADA00D03FD8 /* Amiga.cpp in Sources */,
@@ -5529,6 +5538,7 @@
4B1B5900246E19FD009C171E /* State.cpp in Sources */, 4B1B5900246E19FD009C171E /* State.cpp in Sources */,
4BC57CDA2436A62900FBC404 /* State.cpp in Sources */, 4BC57CDA2436A62900FBC404 /* State.cpp in Sources */,
4B055ACB1FAE9AFB0060FFFF /* SerialBus.cpp in Sources */, 4B055ACB1FAE9AFB0060FFFF /* SerialBus.cpp in Sources */,
4B43983B29620FC9006B0BFC /* 9918.cpp in Sources */,
4B8318B122D3E53A006DB630 /* DiskIICard.cpp in Sources */, 4B8318B122D3E53A006DB630 /* DiskIICard.cpp in Sources */,
4B055A9B1FAE85DA0060FFFF /* AcornADF.cpp in Sources */, 4B055A9B1FAE85DA0060FFFF /* AcornADF.cpp in Sources */,
4B0E04F11FC9EA9500F43484 /* MSX.cpp in Sources */, 4B0E04F11FC9EA9500F43484 /* MSX.cpp in Sources */,
@@ -5821,6 +5831,7 @@
4BC080CA26A238CC00D03FD8 /* AmigaADF.cpp in Sources */, 4BC080CA26A238CC00D03FD8 /* AmigaADF.cpp in Sources */,
4B1A1B1E27320FBC00119335 /* Disk.cpp in Sources */, 4B1A1B1E27320FBC00119335 /* Disk.cpp in Sources */,
4B92E26A234AE35100CD6D1B /* MFP68901.cpp in Sources */, 4B92E26A234AE35100CD6D1B /* MFP68901.cpp in Sources */,
4B43983929620FC7006B0BFC /* 9918.cpp in Sources */,
4B051C97266EF5F600CA44E8 /* CSAppleII.mm in Sources */, 4B051C97266EF5F600CA44E8 /* CSAppleII.mm in Sources */,
4B0ACC2A23775819008902D0 /* Video.cpp in Sources */, 4B0ACC2A23775819008902D0 /* Video.cpp in Sources */,
4B54C0BF1F8D8F450050900F /* Keyboard.cpp in Sources */, 4B54C0BF1F8D8F450050900F /* Keyboard.cpp in Sources */,
@@ -5927,7 +5938,6 @@
4B17B58B20A8A9D9007CCA8F /* StringSerialiser.cpp in Sources */, 4B17B58B20A8A9D9007CCA8F /* StringSerialiser.cpp in Sources */,
4B2E2D9D1C3A070400138695 /* Electron.cpp in Sources */, 4B2E2D9D1C3A070400138695 /* Electron.cpp in Sources */,
4B051CA826781D6500CA44E8 /* StaticAnalyser.cpp in Sources */, 4B051CA826781D6500CA44E8 /* StaticAnalyser.cpp in Sources */,
4B0E04FA1FC9FA3100F43484 /* 9918.cpp in Sources */,
4B69FB3D1C4D908A00B5F0AA /* Tape.cpp in Sources */, 4B69FB3D1C4D908A00B5F0AA /* Tape.cpp in Sources */,
4B4518841F75E91A00926311 /* UnformattedTrack.cpp in Sources */, 4B4518841F75E91A00926311 /* UnformattedTrack.cpp in Sources */,
4B65086022F4CF8D009C1100 /* Keyboard.cpp in Sources */, 4B65086022F4CF8D009C1100 /* Keyboard.cpp in Sources */,
@@ -6123,6 +6133,7 @@
4BE34438238389E10058E78F /* AtariSTVideoTests.mm in Sources */, 4BE34438238389E10058E78F /* AtariSTVideoTests.mm in Sources */,
4BEF6AAC1D35D1C400E73575 /* DPLLTests.swift in Sources */, 4BEF6AAC1D35D1C400E73575 /* DPLLTests.swift in Sources */,
4BE76CF922641ED400ACD6FA /* QLTests.mm in Sources */, 4BE76CF922641ED400ACD6FA /* QLTests.mm in Sources */,
4B43983A29620FC8006B0BFC /* 9918.cpp in Sources */,
4B778F0923A5EC150000D260 /* OricTAP.cpp in Sources */, 4B778F0923A5EC150000D260 /* OricTAP.cpp in Sources */,
4B3BA0CF1D318B44005DD7A7 /* MOS6522Bridge.mm in Sources */, 4B3BA0CF1D318B44005DD7A7 /* MOS6522Bridge.mm in Sources */,
4B778F6023A5F3460000D260 /* Disk.cpp in Sources */, 4B778F6023A5F3460000D260 /* Disk.cpp in Sources */,
@@ -6138,7 +6149,6 @@
4B0DA67D282DCDF300C12F17 /* Instruction.cpp in Sources */, 4B0DA67D282DCDF300C12F17 /* Instruction.cpp in Sources */,
4BFCA12B1ECBE7C400AC40C1 /* ZexallTests.swift in Sources */, 4BFCA12B1ECBE7C400AC40C1 /* ZexallTests.swift in Sources */,
4B778F2223A5EDDD0000D260 /* PulseQueuedTape.cpp in Sources */, 4B778F2223A5EDDD0000D260 /* PulseQueuedTape.cpp in Sources */,
4B778EF123A5D6B50000D260 /* 9918.cpp in Sources */,
4B051CB3267D3FF800CA44E8 /* EnterpriseNickTests.mm in Sources */, 4B051CB3267D3FF800CA44E8 /* EnterpriseNickTests.mm in Sources */,
4B9D0C4D22C7DA1A00DE1AD3 /* 68000ControlFlowTests.mm in Sources */, 4B9D0C4D22C7DA1A00DE1AD3 /* 68000ControlFlowTests.mm in Sources */,
4BB2A9AF1E13367E001A5C23 /* CRCTests.mm in Sources */, 4BB2A9AF1E13367E001A5C23 /* CRCTests.mm in Sources */,

View File

@@ -19,8 +19,10 @@
[super setUp]; [super setUp];
} }
using VDP = TI::TMS::TMS9918<TI::TMS::Personality::SMSVDP>;
- (void)testLineInterrupt { - (void)testLineInterrupt {
TI::TMS::TMS9918 vdp(TI::TMS::Personality::SMSVDP); VDP vdp;
// Disable end-of-frame interrupts, enable line interrupts. // Disable end-of-frame interrupts, enable line interrupts.
vdp.write(1, 0x00); vdp.write(1, 0x00);
@@ -63,7 +65,7 @@
} }
- (void)testFirstLineInterrupt { - (void)testFirstLineInterrupt {
TI::TMS::TMS9918 vdp(TI::TMS::Personality::SMSVDP); VDP vdp;
// Disable end-of-frame interrupts, enable line interrupts, set an interrupt to occur every line. // Disable end-of-frame interrupts, enable line interrupts, set an interrupt to occur every line.
vdp.write(1, 0x00); vdp.write(1, 0x00);
@@ -96,7 +98,7 @@
} }
- (void)testInterruptPrediction { - (void)testInterruptPrediction {
TI::TMS::TMS9918 vdp(TI::TMS::Personality::SMSVDP); VDP vdp;
for(int c = 0; c < 256; ++c) { for(int c = 0; c < 256; ++c) {
for(int with_eof = (c < 192) ? 0 : 1; with_eof < 2; ++with_eof) { for(int with_eof = (c < 192) ? 0 : 1; with_eof < 2; ++with_eof) {
@@ -144,7 +146,7 @@
} }
- (void)testTimeUntilLine { - (void)testTimeUntilLine {
TI::TMS::TMS9918 vdp(TI::TMS::Personality::SMSVDP); VDP vdp;
auto time_until_line = vdp.get_time_until_line(-1).as_integral(); auto time_until_line = vdp.get_time_until_line(-1).as_integral();
for(int c = 0; c < 262*228*5; ++c) { for(int c = 0; c < 262*228*5; ++c) {

View File

@@ -65,6 +65,7 @@ SOURCES += \
$$SRC/Components/8272/*.cpp \ $$SRC/Components/8272/*.cpp \
$$SRC/Components/8530/*.cpp \ $$SRC/Components/8530/*.cpp \
$$SRC/Components/9918/*.cpp \ $$SRC/Components/9918/*.cpp \
$$SRC/Components/9918/Implementation/*.cpp \
$$SRC/Components/AudioToggle/*.cpp \ $$SRC/Components/AudioToggle/*.cpp \
$$SRC/Components/AY38910/*.cpp \ $$SRC/Components/AY38910/*.cpp \
$$SRC/Components/DiskII/*.cpp \ $$SRC/Components/DiskII/*.cpp \