1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-07-04 18:29:40 +00:00

Merge pull request #315 from TomHarte/MSX

Introduces very provisional MSX 1 emulation
This commit is contained in:
Thomas Harte 2017-12-12 18:30:09 -08:00 committed by GitHub
commit da57df55e8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 1620 additions and 24 deletions

View File

@ -169,13 +169,20 @@ class HalfCycles: public WrappedInt<HalfCycles> {
return Cycles(length_ >> 1);
}
///Flushes the whole cycles in @c this, subtracting that many from the total stored here.
/// Flushes the whole cycles in @c this, subtracting that many from the total stored here.
inline Cycles flush_cycles() {
Cycles result(length_ >> 1);
length_ &= 1;
return result;
}
/// Flushes the half cycles in @c this, returning the number stored and setting this total to zero.
inline HalfCycles flush() {
HalfCycles result(length_);
length_ = 0;
return result;
}
/*!
Severs from @c this the effect of dividing by @c divisor @c this will end up with
the value of @c this modulo @c divisor and @c divided by @c divisor is returned.

View File

@ -76,8 +76,8 @@ template <class T> class i8255 {
private:
void update_outputs() {
port_handler_.set_value(0, outputs_[0]);
port_handler_.set_value(1, outputs_[1]);
if(!(control_ & 0x10)) port_handler_.set_value(0, outputs_[0]);
if(!(control_ & 0x02)) port_handler_.set_value(1, outputs_[1]);
port_handler_.set_value(2, outputs_[2]);
}

618
Components/9918/9918.cpp Normal file
View File

@ -0,0 +1,618 @@
//
// 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>
using namespace TI;
namespace {
const uint32_t palette_pack(uint8_t r, uint8_t g, uint8_t b) {
uint32_t result = 0;
uint8_t *const result_ptr = reinterpret_cast<uint8_t *>(&result);
result_ptr[0] = r;
result_ptr[1] = g;
result_ptr[2] = b;
result_ptr[3] = 0;
return result;
}
const uint32_t palette[16] = {
palette_pack(0, 0, 0),
palette_pack(0, 0, 0),
palette_pack(33, 200, 66),
palette_pack(94, 220, 120),
palette_pack(84, 85, 237),
palette_pack(125, 118, 252),
palette_pack(212, 82, 77),
palette_pack(66, 235, 245),
palette_pack(252, 85, 84),
palette_pack(255, 121, 120),
palette_pack(212, 193, 84),
palette_pack(230, 206, 128),
palette_pack(33, 176, 59),
palette_pack(201, 91, 186),
palette_pack(204, 204, 204),
palette_pack(255, 255, 255)
};
const uint8_t StatusInterrupt = 0x80;
const uint8_t StatusFifthSprite = 0x40;
const int StatusSpriteCollisionShift = 5;
const uint8_t StatusSpriteCollision = 0x20;
}
TMS9918::TMS9918(Personality p) :
// 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.
crt_(new Outputs::CRT::CRT(1365, 4, Outputs::CRT::DisplayType::NTSC60, 4)) {
crt_->set_rgb_sampling_function(
"vec3 rgb_sample(usampler2D sampler, vec2 coordinate, vec2 icoordinate)"
"{"
"return texture(sampler, coordinate).rgb / vec3(255.0);"
"}");
crt_->set_output_device(Outputs::CRT::OutputDevice::Monitor);
crt_->set_visible_area(Outputs::CRT::Rect(0.055f, 0.025f, 0.9f, 0.9f));
}
std::shared_ptr<Outputs::CRT::CRT> TMS9918::get_crt() {
return crt_;
}
void TMS9918::test_sprite(int sprite_number) {
if(!(status_ & StatusFifthSprite)) {
status_ = static_cast<uint8_t>((status_ & ~31) | sprite_number);
}
if(sprites_stopped_)
return;
const int sprite_position = ram_[sprite_attribute_table_address_ + (sprite_number << 2)];
// A sprite Y of 208 means "don't scan the list any further".
if(sprite_position == 208) {
sprites_stopped_ = true;
return;
}
const int sprite_row = (row_ - sprite_position)&255;
if(sprite_row < 0 || sprite_row >= sprite_height_) return;
const int active_sprite_slot = sprite_sets_[active_sprite_set_].active_sprite_slot;
if(active_sprite_slot == 4) {
status_ |= StatusFifthSprite;
return;
}
SpriteSet::ActiveSprite &sprite = sprite_sets_[active_sprite_set_].active_sprites[active_sprite_slot];
sprite.index = sprite_number;
sprite.row = sprite_row >> (sprites_magnified_ ? 1 : 0);
sprite_sets_[active_sprite_set_].active_sprite_slot++;
}
void TMS9918::get_sprite_contents(int field, int cycles_left, int screen_row) {
int sprite_id = field / 6;
field %= 6;
while(true) {
const int cycles_in_sprite = std::min(cycles_left, 6 - field);
cycles_left -= cycles_in_sprite;
const int final_field = cycles_in_sprite + field;
assert(sprite_id < 4);
SpriteSet::ActiveSprite &sprite = sprite_sets_[active_sprite_set_].active_sprites[sprite_id];
if(field < 4) {
std::memcpy(
&sprite.info[field],
&ram_[sprite_attribute_table_address_ + (sprite.index << 2) + field],
static_cast<size_t>(std::min(4, final_field) - field));
}
field = std::min(4, final_field);
const int sprite_offset = sprite.info[2] & ~(sprites_16x16_ ? 3 : 0);
const int sprite_address = sprite_generator_table_address_ + (sprite_offset << 3) + sprite.row;
while(field < final_field) {
sprite.image[field - 4] = ram_[sprite_address + ((field - 4) << 4)];
field++;
}
if(!cycles_left) return;
field = 0;
sprite_id++;
}
}
void TMS9918::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.
// Keep a count of cycles separate from internal counts to avoid
// potential errors mapping back and forth.
half_cycles_into_frame_ = (half_cycles_into_frame_ + cycles) % HalfCycles(frame_lines_ * 228 * 2);
// Convert to 342 cycles per line; the internal clock is 1.5 times the
// nominal 3.579545 Mhz that I've advertised for this part.
int int_cycles = (cycles.as_int() * 3) + cycles_error_;
cycles_error_ = int_cycles & 7;
int_cycles >>= 3;
if(!int_cycles) return;
while(int_cycles) {
// Determine how much time has passed in the remainder of this line, and proceed.
int cycles_left = std::min(342 - column_, int_cycles);
column_ += cycles_left; // column_ is now the column that has been reached in this line.
int_cycles -= cycles_left; // Count down duration to run for.
// ------------------------------
// TODO: memory access slot here.
// ------------------------------
// ------------------------------
// Perform video memory accesses.
// ------------------------------
if(row_ < 192 && !blank_screen_) {
const int access_slot = column_ >> 1; // There are only 171 available memory accesses per line.
switch(line_mode_) {
case LineMode::Text:
access_pointer_ = std::min(30, access_slot);
if(access_pointer_ >= 30 && access_pointer_ < 150) {
const int row_base = pattern_name_address_ + (row_ >> 3) * 40;
const int end = std::min(150, access_slot);
// Pattern names are collected every third window starting from window 30.
const int pattern_names_start = (access_pointer_ - 30 + 2) / 3;
const int pattern_names_end = (end - 30 + 2) / 3;
std::memcpy(&pattern_names_[pattern_names_start], &ram_[row_base + pattern_names_start], static_cast<size_t>(pattern_names_end - pattern_names_start));
// Patterns are collected every third window starting from window 32.
const int pattern_buffer_start = (access_pointer_ - 32 + 2) / 3;
const int pattern_buffer_end = (end - 32 + 2) / 3;
for(int column = pattern_buffer_start; column < pattern_buffer_end; ++column) {
pattern_buffer_[column] = ram_[pattern_generator_table_address_ + (pattern_names_[column] << 3) + (row_ & 7)];
}
}
break;
case LineMode::Character:
// Four access windows: no collection.
if(access_pointer_ < 5)
access_pointer_ = std::min(5, access_slot);
// Then ten access windows are filled with collection of sprite 3 and 4 details.
if(access_pointer_ >= 5 && access_pointer_ < 15) {
int end = std::min(15, access_slot);
get_sprite_contents(access_pointer_ - 5 + 14, end - access_pointer_, row_ - 1);
access_pointer_ = std::min(15, access_slot);
}
// Four more access windows: no collection.
if(access_pointer_ >= 15 && access_pointer_ < 19)
access_pointer_ = std::min(19, access_slot);
// Start new sprite set if this is location 19.
if(access_pointer_ == 19) {
active_sprite_set_ ^= 1;
sprite_sets_[active_sprite_set_].active_sprite_slot = 0;
sprites_stopped_ = false;
}
// Then eight access windows fetch the y position for the first eight sprites.
while(access_pointer_ < 27 && access_pointer_ < access_slot) {
test_sprite(access_pointer_ - 19);
access_pointer_++;
}
// The next 128 access slots are video and sprite collection interleaved.
if(access_pointer_ >= 27 && access_pointer_ < 155) {
int end = std::min(155, access_slot);
int row_base = pattern_name_address_;
int pattern_base = pattern_generator_table_address_;
int colour_base = colour_table_address_;
if(screen_mode_ == 1) {
pattern_base &= 0x2000 | ((row_ & 0xc0) << 5);
colour_base &= 0x2000 | ((row_ & 0xc0) << 5);
}
row_base += (row_ << 2)&~31;
// Pattern names are collected every fourth window starting from window 27.
const int pattern_names_start = (access_pointer_ - 27 + 3) >> 2;
const int pattern_names_end = (end - 27 + 3) >> 2;
std::memcpy(&pattern_names_[pattern_names_start], &ram_[row_base + pattern_names_start], static_cast<size_t>(pattern_names_end - pattern_names_start));
// Colours are collected ever fourth window starting from window 29.
const int colours_start = (access_pointer_ - 29 + 3) >> 2;
const int colours_end = (end - 29 + 3) >> 2;
if(screen_mode_ != 1) {
for(int column = colours_start; column < colours_end; ++column) {
colour_buffer_[column] = ram_[colour_base + (pattern_names_[column] >> 3)];
}
} else {
for(int column = colours_start; column < colours_end; ++column) {
colour_buffer_[column] = ram_[colour_base + (pattern_names_[column] << 3) + (row_ & 7)];
}
}
// Patterns are collected ever fourth window starting from window 30.
const int pattern_buffer_start = (access_pointer_ - 30 + 3) >> 2;
const int pattern_buffer_end = (end - 30 + 3) >> 2;
for(int column = pattern_buffer_start; column < pattern_buffer_end; ++column) {
pattern_buffer_[column] = ram_[pattern_base + (pattern_names_[column] << 3) + (row_ & 7)];
}
// Sprite slots occur in three quarters of ever fourth window starting from window 28.
const int sprite_start = (access_pointer_ - 28 + 3) >> 2;
const int sprite_end = (end - 28 + 3) >> 2;
for(int column = sprite_start; column < sprite_end; ++column) {
if(column&3) {
test_sprite(7 + column - (column >> 2));
}
}
access_pointer_ = std::min(155, access_slot);
}
// Two access windows: no collection.
if(access_pointer_ < 157)
access_pointer_ = std::min(157, access_slot);
// Fourteen access windows: collect initial sprite information.
if(access_pointer_ >= 157 && access_pointer_ < 171) {
int end = std::min(171, access_slot);
get_sprite_contents(access_pointer_ - 157, end - access_pointer_, row_);
access_pointer_ = std::min(171, access_slot);
}
break;
}
}
// --------------------------
// End video memory accesses.
// --------------------------
// --------------------
// Output video stream.
// --------------------
if(row_ < 192 && !blank_screen_) {
// ----------------------
// Output horizontal sync
// ----------------------
if(!output_column_ && column_ >= 26) {
crt_->output_sync(13 * 4);
crt_->output_default_colour_burst(13 * 4);
output_column_ = 26;
}
// -------------------
// Output left border.
// -------------------
if(output_column_ >= 26) {
int pixels_end = std::min(first_pixel_column_, column_);
if(output_column_ < pixels_end) {
output_border(pixels_end - output_column_);
output_column_ = pixels_end;
// Grab a pointer for drawing pixels to, if the moment has arrived.
if(pixels_end == first_pixel_column_) {
pixel_base_ = pixel_target_ = reinterpret_cast<uint32_t *>(crt_->allocate_write_area(static_cast<unsigned int>(first_right_border_column_ - first_pixel_column_)));
}
}
}
// --------------
// Output pixels.
// --------------
if(output_column_ >= first_pixel_column_) {
int pixels_end = std::min(first_right_border_column_, column_);
if(output_column_ < pixels_end) {
switch(line_mode_) {
case LineMode::Text: {
const uint32_t colours[2] = { palette[background_colour_], palette[text_colour_] };
const int shift = (output_column_ - first_pixel_column_) % 6;
int byte_column = (output_column_ - first_pixel_column_) / 6;
int pattern = pattern_buffer_[byte_column] << shift;
int pixels_left = pixels_end - output_column_;
int length = std::min(pixels_left, 6 - shift);
while(true) {
pixels_left -= length;
while(length--) {
*pixel_target_ = colours[(pattern >> 7)&0x01];
pixel_target_++;
pattern <<= 1;
}
if(!pixels_left) break;
length = std::min(6, pixels_left);
byte_column++;
pattern = pattern_buffer_[byte_column];
}
output_column_ = pixels_end;
} break;
case LineMode::Character: {
// If this is the start of the visible area, seed sprite shifter positions.
SpriteSet &sprite_set = sprite_sets_[active_sprite_set_ ^ 1];
if(line_mode_ == LineMode::Character && output_column_ == first_pixel_column_) {
int c = sprite_set.active_sprite_slot;
while(c--) {
SpriteSet::ActiveSprite &sprite = sprite_set.active_sprites[c];
sprite.shift_position = -sprite.info[1];
if(sprite.info[3] & 0x80) {
sprite.shift_position += 32;
if(sprite.shift_position > 0 && !sprites_magnified_)
sprite.shift_position *= 2;
}
}
}
// Paint the background tiles.
const int shift = (output_column_ - first_pixel_column_) & 7;
int byte_column = (output_column_ - first_pixel_column_) >> 3;
const int pixels_left = pixels_end - output_column_;
int length = std::min(pixels_left, 8 - shift);
int pattern = pattern_buffer_[byte_column] << shift;
uint8_t colour = colour_buffer_[byte_column];
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;
while(length--) {
*pixel_target_ = colours[(pattern >> 7)&0x01];
pixel_target_++;
pattern <<= 1;
}
if(!background_pixels_left) break;
length = std::min(8, background_pixels_left);
byte_column++;
pattern = pattern_buffer_[byte_column];
colour = colour_buffer_[byte_column];
colours[0] = palette[(colour & 15) ? (colour & 15) : background_colour_];
colours[1] = palette[(colour >> 4) ? (colour >> 4) : background_colour_];
}
// Paint sprites and check for collisions.
if(sprite_set.active_sprite_slot) {
int sprite_pixels_left = pixels_left;
const int shift_advance = sprites_magnified_ ? 1 : 2;
// const uint32_t sprite_colour_selection_masks[2] = {0x00000000, 0xffffffff};
while(sprite_pixels_left--) {
uint32_t sprite_colour = pixel_base_[output_column_ - first_pixel_column_];
int sprite_mask = 0;
int c = sprite_set.active_sprite_slot;
while(c--) {
SpriteSet::ActiveSprite &sprite = sprite_set.active_sprites[c];
if(sprite.shift_position < 0) {
sprite.shift_position++;
continue;
} else if(sprite.shift_position < 32) {
int mask = sprite.image[sprite.shift_position >> 4] << ((sprite.shift_position&15) >> 1);
mask = (mask >> 7) & 1;
status_ |= (mask & sprite_mask) << StatusSpriteCollisionShift;
sprite_mask |= mask;
sprite.shift_position += shift_advance;
// TODO: can a non-conditional version be found like that commented out below, but
// which accounts for colour 0 being invisible?
// sprite_colour = (sprite_colour & sprite_colour_selection_masks[mask^1]) | (palette[sprite.info[3]&15] & sprite_colour_selection_masks[mask]);
if((sprite.info[3]&15) && mask) {
sprite_colour = palette[sprite.info[3]&15];
}
}
}
pixel_base_[output_column_ - first_pixel_column_] = sprite_colour;
output_column_++;
}
}
output_column_ = pixels_end;
} break;
}
if(output_column_ == first_right_border_column_) {
crt_->output_data(static_cast<unsigned int>(first_right_border_column_ - first_pixel_column_) * 4, 4);
pixel_target_ = nullptr;
}
}
}
// --------------------
// Output right border.
// --------------------
if(output_column_ >= first_right_border_column_) {
output_border(column_ - output_column_);
output_column_ = column_;
}
} else if(row_ >= first_vsync_line_ && row_ < first_vsync_line_+3) {
// Vertical sync.
if(column_ == 342) {
crt_->output_sync(342 * 4);
}
} else {
// Blank.
if(!output_column_ && column_ >= 26) {
crt_->output_sync(13 * 4);
crt_->output_default_colour_burst(13 * 4);
output_column_ = 26;
}
if(output_column_ >= 26) {
output_border(column_ - output_column_);
output_column_ = column_;
}
}
// -----------------
// End video stream.
// -----------------
// -----------------------------------
// Prepare for next line, potentially.
// -----------------------------------
if(column_ == 342) {
access_pointer_ = column_ = output_column_ = 0;
row_ = (row_ + 1) % frame_lines_;
if(row_ == 192) status_ |= StatusInterrupt;
screen_mode_ = next_screen_mode_;
blank_screen_ = next_blank_screen_;
switch(screen_mode_) {
case 2:
line_mode_ = LineMode::Text;
first_pixel_column_ = 69;
first_right_border_column_ = 309;
break;
default:
line_mode_ = LineMode::Character;
first_pixel_column_ = 63;
first_right_border_column_ = 319;
break;
}
}
}
}
void TMS9918::output_border(int cycles) {
pixel_target_ = reinterpret_cast<uint32_t *>(crt_->allocate_write_area(1));
if(pixel_target_) *pixel_target_ = palette[background_colour_];
crt_->output_level(static_cast<unsigned int>(cycles) * 4);
}
// TODO: as a temporary development measure, memory access below is magically instantaneous. Correct that.
void TMS9918::set_register(int address, uint8_t value) {
// Writes to address 0 are writes to the video RAM. Store
// the value and return.
if(!(address & 1)) {
write_phase_ = false;
read_ahead_buffer_ = value;
ram_[ram_pointer_ & 16383] = value;
ram_pointer_++;
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(!write_phase_) {
low_write_ = value;
write_phase_ = true;
return;
}
write_phase_ = false;
if(value & 0x80) {
// This is a write to a register.
switch(value & 7) {
case 0:
next_screen_mode_ = (next_screen_mode_ & 6) | ((low_write_ & 2) >> 1);
// printf("NSM: %02x\n", next_screen_mode_);
break;
case 1:
next_blank_screen_ = !(low_write_ & 0x40);
generate_interrupts_ = !!(low_write_ & 0x20);
next_screen_mode_ = (next_screen_mode_ & 1) | ((low_write_ & 0x18) >> 3);
sprites_16x16_ = !!(low_write_ & 0x02);
sprites_magnified_ = !!(low_write_ & 0x01);
sprite_height_ = 8;
if(sprites_16x16_) sprite_height_ <<= 1;
if(sprites_magnified_) sprite_height_ <<= 1;
// printf("NSM: %02x\n", next_screen_mode_);
break;
case 2:
pattern_name_address_ = static_cast<uint16_t>((low_write_ & 0xf) << 10);
break;
case 3:
colour_table_address_ = static_cast<uint16_t>(low_write_ << 6);
break;
case 4:
pattern_generator_table_address_ = static_cast<uint16_t>((low_write_ & 0x07) << 11);
break;
case 5:
sprite_attribute_table_address_ = static_cast<uint16_t>((low_write_ & 0x7f) << 7);
break;
case 6:
sprite_generator_table_address_ = static_cast<uint16_t>((low_write_ & 0x07) << 11);
break;
case 7:
text_colour_ = low_write_ >> 4;
background_colour_ = low_write_ & 0xf;
break;
}
} else {
// This is a write to the RAM pointer.
ram_pointer_ = static_cast<uint16_t>(low_write_ | (value << 8));
if(!(value & 0x40)) {
// Officially a 'read' set, so perform lookahead.
read_ahead_buffer_ = ram_[ram_pointer_ & 16383];
ram_pointer_++;
}
}
}
uint8_t TMS9918::get_register(int address) {
write_phase_ = false;
// Reads from address 0 read video RAM, via the read-ahead buffer.
if(!(address & 1)) {
uint8_t result = read_ahead_buffer_;
read_ahead_buffer_ = ram_[ram_pointer_ & 16383];
ram_pointer_++;
return result;
}
// Reads from address 1 get the status register.
uint8_t result = status_;
status_ &= ~(StatusInterrupt | StatusFifthSprite | StatusSpriteCollision);
return result;
}
HalfCycles TMS9918::get_time_until_interrupt() {
if(!generate_interrupts_) return HalfCycles(-1);
if(get_interrupt_line()) return HalfCycles(0);
const int half_cycles_per_frame = frame_lines_ * 228 * 2;
int half_cycles_remaining = (192 * 228 * 2 + half_cycles_per_frame - half_cycles_into_frame_.as_int()) % half_cycles_per_frame;
return HalfCycles(half_cycles_remaining);
}
bool TMS9918::get_interrupt_line() {
return (status_ & StatusInterrupt) && generate_interrupts_;
}

135
Components/9918/9918.hpp Normal file
View File

@ -0,0 +1,135 @@
//
// 9918.hpp
// Clock Signal
//
// Created by Thomas Harte on 25/11/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#ifndef _918_hpp
#define _918_hpp
#include "../../Outputs/CRT/CRT.hpp"
#include "../../ClockReceiver/ClockReceiver.hpp"
#include <cstdint>
namespace TI {
class TMS9918 {
public:
enum Personality {
TMS9918A, // includes the 9928A; set TV standard as desired.
};
/*!
Constructs an instance of the drive controller that behaves according to personality @c p.
@param p The type of controller to emulate.
*/
TMS9918(Personality p);
enum TVStandard {
PAL, NTSC
};
void set_tv_standard(TVStandard standard);
std::shared_ptr<Outputs::CRT::CRT> get_crt();
/*!
Runs the VCP for the number of cycles indicate; it is an implicit assumption of the code
that the input clock rate is 3579545 Hz the NTSC colour clock rate.
*/
void run_for(const HalfCycles cycles);
void set_register(int address, uint8_t value);
uint8_t get_register(int address);
/*!
Returns the amount of time until get_interrupt_line would next return true if
there are no interceding calls to set_register or get_register.
If get_interrupt_line is true now, returns zero. If get_interrupt_line would
never return true, returns -1.
*/
HalfCycles get_time_until_interrupt();
/*!
@returns @c true if the interrupt line is currently active; @c false otherwise.
*/
bool get_interrupt_line();
private:
std::shared_ptr<Outputs::CRT::CRT> crt_;
uint8_t ram_[16384];
uint16_t ram_pointer_ = 0;
uint8_t read_ahead_buffer_ = 0;
uint8_t status_ = 0;
bool write_phase_ = false;
uint8_t low_write_ = 0;
// The various register flags.
int next_screen_mode_ = 0, screen_mode_ = 0;
bool next_blank_screen_ = true, blank_screen_ = true;
bool sprites_16x16_ = false;
bool sprites_magnified_ = false;
bool generate_interrupts_ = false;
int sprite_height_ = 8;
uint16_t pattern_name_address_ = 0;
uint16_t colour_table_address_ = 0;
uint16_t pattern_generator_table_address_ = 0;
uint16_t sprite_attribute_table_address_ = 0;
uint16_t sprite_generator_table_address_ = 0;
uint8_t text_colour_ = 0;
uint8_t background_colour_ = 0;
HalfCycles half_cycles_into_frame_;
int column_ = 0, row_ = 0, output_column_ = 0;
int cycles_error_ = 0;
uint32_t *pixel_target_ = nullptr, *pixel_base_ = nullptr;
void output_border(int cycles);
// Vertical timing details.
int frame_lines_ = 262;
int first_vsync_line_ = 227;
// Horizontal selections.
enum class LineMode {
Text,
Character
} line_mode_ = LineMode::Text;
int first_pixel_column_, first_right_border_column_;
uint8_t pattern_names_[40];
uint8_t pattern_buffer_[40];
uint8_t colour_buffer_[40];
struct SpriteSet {
struct ActiveSprite {
int index = 0;
int row = 0;
uint8_t info[4];
uint8_t image[2];
int shift_position = 0;
} active_sprites[4];
int active_sprite_slot = 0;
} sprite_sets_[2];
int active_sprite_set_ = 0;
bool sprites_stopped_ = false;
int access_pointer_ = 0;
inline void test_sprite(int sprite_number);
inline void get_sprite_contents(int start, int cycles, int screen_row);
};
};
#endif /* _918_hpp */

61
Machines/MSX/Keyboard.cpp Normal file
View File

@ -0,0 +1,61 @@
//
// Keyboard.cpp
// Clock Signal
//
// Created by Thomas Harte on 29/11/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#include "Keyboard.hpp"
uint16_t MSX::KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) {
#define BIND(source, dest) case Inputs::Keyboard::Key::source: return MSX::Key::dest
switch(key) {
BIND(k0, Key0); BIND(k1, Key1); BIND(k2, Key2); BIND(k3, Key3); BIND(k4, Key4);
BIND(k5, Key5); BIND(k6, Key6); BIND(k7, Key7); BIND(k8, Key8); BIND(k9, Key9);
BIND(Q, KeyQ); BIND(W, KeyW); BIND(E, KeyE); BIND(R, KeyR); BIND(T, KeyT);
BIND(Y, KeyY); BIND(U, KeyU); BIND(I, KeyI); BIND(O, KeyO); BIND(P, KeyP);
BIND(A, KeyA); BIND(S, KeyS); BIND(D, KeyD); BIND(F, KeyF); BIND(G, KeyG);
BIND(H, KeyH); BIND(J, KeyJ); BIND(K, KeyK); BIND(L, KeyL);
BIND(Z, KeyZ); BIND(X, KeyX); BIND(C, KeyC); BIND(V, KeyV);
BIND(B, KeyB); BIND(N, KeyN); BIND(M, KeyM);
BIND(F1, KeyF1); BIND(F2, KeyF2); BIND(F3, KeyF3); BIND(F4, KeyF4); BIND(F5, KeyF5);
BIND(F12, KeyStop);
BIND(F10, KeyDelete); BIND(F9, KeyInsert); BIND(F8, KeyHome);
BIND(Delete, KeyDelete); BIND(Insert, KeyInsert); BIND(Home, KeyHome);
BIND(Escape, KeyEscape);
BIND(Tab, KeyTab); BIND(CapsLock, KeyCaps);
BIND(LeftControl, KeyControl); BIND(RightControl, KeyControl);
BIND(LeftShift, KeyShift); BIND(RightShift, KeyShift);
BIND(LeftMeta, KeyCode); BIND(RightMeta, KeyGraph);
BIND(LeftOption, KeyCode); BIND(RightOption, KeySelect);
BIND(Semicolon, KeySemicolon);
BIND(Quote, KeyQuote);
BIND(OpenSquareBracket, KeyLeftSquareBracket);
BIND(CloseSquareBracket, KeyRightSquareBracket);
BIND(Hyphen, KeyMinus);
BIND(Equals, KeyEquals);
BIND(Left, KeyLeft);
BIND(Right, KeyRight);
BIND(Up, KeyUp);
BIND(Down, KeyDown);
BIND(FullStop, KeyFullStop);
BIND(Comma, KeyComma);
BIND(ForwardSlash, KeyForwardSlash);
BIND(BackSlash, KeyBackSlash);
BIND(BackTick, KeyGrave);
BIND(Enter, KeyEnter);
BIND(Space, KeySpace);
BIND(BackSpace, KeyBackspace);
default: break;
}
#undef BIND
return KeyboardMachine::Machine::KeyNotMapped;
}

42
Machines/MSX/Keyboard.hpp Normal file
View File

@ -0,0 +1,42 @@
//
// Keyboard.hpp
// Clock Signal
//
// Created by Thomas Harte on 29/11/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#ifndef Machines_MSX_Keyboard_hpp
#define Machines_MSX_Keyboard_hpp
#include "../KeyboardMachine.hpp"
namespace MSX {
enum Key: uint16_t {
#define Line(l, k1, k2, k3, k4, k5, k6, k7, k8) \
k1 = (l << 4) | 0x07, k2 = (l << 4) | 0x06, k3 = (l << 4) | 0x05, k4 = (l << 4) | 0x04,\
k5 = (l << 4) | 0x03, k6 = (l << 4) | 0x02, k7 = (l << 4) | 0x01, k8 = (l << 4) | 0x00,
Line(0, Key7, Key6, Key5, Key4, Key3, Key2, Key1, Key0)
Line(1, KeySemicolon, KeyRightSquareBracket, KeyLeftSquareBracket, KeyBackSlash, KeyEquals, KeyMinus, Key9, Key8)
Line(2, KeyB, KeyA, KeyNA, KeyForwardSlash, KeyFullStop, KeyComma, KeyQuote, KeyGrave)
Line(3, KeyJ, KeyI, KeyH, KeyG, KeyF, KeyE, KeyD, KeyC)
Line(4, KeyR, KeyQ, KeyP, KeyO, KeyN, KeyM, KeyL, KeyK)
Line(5, KeyZ, KeyY, KeyX, KeyW, KeyV, KeyU, KeyT, KeyS)
Line(6, KeyF3, KeyF2, KeyF1, KeyCode, KeyCaps, KeyGraph, KeyControl, KeyShift)
Line(7, KeyEnter, KeySelect, KeyBackspace, KeyStop, KeyTab, KeyEscape, KeyF5, KeyF4)
Line(8, KeyRight, KeyDown, KeyUp, KeyLeft, KeyDelete, KeyInsert, KeyHome, KeySpace)
Line(9, KeyNumpad4, KeyNumpad3, KeyNumpad2, KeyNumpad1, KeyNumpad0, KeyNumpadDivide, KeyNumpadAdd, KeyNumpadMultiply)
Line(10, KeyNumpadDecimal, KeyNumpadComma, KeyNumpadSubtract, KeyNumpad9, KeyNumpad8, KeyNumpad7, KeyNumpad6, KeyNumpad5)
#undef Line
};
struct KeyboardMapper: public KeyboardMachine::Machine::KeyboardMapper {
uint16_t mapped_key_for_key(Inputs::Keyboard::Key key);
};
};
#endif /* Machines_MSX_Keyboard_hpp */

322
Machines/MSX/MSX.cpp Normal file
View File

@ -0,0 +1,322 @@
//
// MSX.cpp
// Clock Signal
//
// Created by Thomas Harte on 24/11/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#include "MSX.hpp"
#include "Keyboard.hpp"
#include "../../Processors/Z80/Z80.hpp"
#include "../../Components/1770/1770.hpp"
#include "../../Components/9918/9918.hpp"
#include "../../Components/8255/i8255.hpp"
#include "../../Components/AY38910/AY38910.hpp"
#include "../CRTMachine.hpp"
#include "../ConfigurationTarget.hpp"
#include "../KeyboardMachine.hpp"
namespace MSX {
struct AYPortHandler: public GI::AY38910::PortHandler {
void set_port_output(bool port_b, uint8_t value) {
// printf("AY port %c output: %02x\n", port_b ? 'b' : 'a', value);
}
uint8_t get_port_input(bool port_b) {
// printf("AY port %c input\n", port_b ? 'b' : 'a');
return 0xff;
}
};
class ConcreteMachine:
public Machine,
public CPU::Z80::BusHandler,
public CRTMachine::Machine,
public ConfigurationTarget::Machine,
public KeyboardMachine::Machine {
public:
ConcreteMachine():
z80_(*this),
i8255_(i8255_port_handler_),
i8255_port_handler_(*this) {
set_clock_rate(3579545);
std::memset(unpopulated_, 0xff, sizeof(unpopulated_));
clear_all_keys();
}
void setup_output(float aspect_ratio) override {
vdp_.reset(new TI::TMS9918(TI::TMS9918::TMS9918A));
ay_.reset(new GI::AY38910::AY38910());
ay_->set_port_handler(&ay_port_handler_);
ay_->set_input_rate(3579545.0f / 2.0f);
}
void close_output() override {
vdp_.reset();
ay_.reset();
}
std::shared_ptr<Outputs::CRT::CRT> get_crt() override {
return vdp_->get_crt();
}
std::shared_ptr<Outputs::Speaker> get_speaker() override {
return ay_;
}
void run_for(const Cycles cycles) override {
z80_.run_for(cycles);
}
void configure_as_target(const StaticAnalyser::Target &target) override {
insert_media(target.media);
}
bool insert_media(const StaticAnalyser::Media &media) override {
if(!media.cartridges.empty()) {
const auto &segment = media.cartridges.front()->get_segments().front();
cartridge_ = segment.data;
// TODO: should clear other page 1 pointers, should allow for paging cartridges, etc.
size_t base = segment.start_address >> 14;
for(size_t c = 0; c < cartridge_.size(); c += 16384) {
memory_slots_[1].read_pointers[(c >> 14) + base] = cartridge_.data() + c;
}
}
return true;
}
void page_memory(uint8_t value) {
// printf("Page: %02x\n", value);
for(size_t c = 0; c < 4; ++c) {
read_pointers_[c] = memory_slots_[value & 3].read_pointers[c];
write_pointers_[c] = memory_slots_[value & 3].write_pointers[c];
value >>= 2;
}
}
HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) {
if(time_until_interrupt_ > 0) {
time_until_interrupt_ -= cycle.length;
if(time_until_interrupt_ <= HalfCycles(0)) {
z80_.set_interrupt_line(true, time_until_interrupt_);
}
}
uint16_t address = cycle.address ? *cycle.address : 0x0000;
switch(cycle.operation) {
case CPU::Z80::PartialMachineCycle::ReadOpcode:
case CPU::Z80::PartialMachineCycle::Read:
*cycle.value = read_pointers_[address >> 14][address & 16383];
break;
case CPU::Z80::PartialMachineCycle::Write:
write_pointers_[address >> 14][address & 16383] = *cycle.value;
break;
case CPU::Z80::PartialMachineCycle::Input:
switch(address & 0xff) {
case 0x98: case 0x99:
vdp_->run_for(time_since_vdp_update_.flush());
*cycle.value = vdp_->get_register(address);
z80_.set_interrupt_line(vdp_->get_interrupt_line());
time_until_interrupt_ = vdp_->get_time_until_interrupt();
break;
case 0xa2:
ay_->run_for(time_since_ay_update_.divide_cycles(Cycles(2)));
ay_->set_control_lines(static_cast<GI::AY38910::ControlLines>(GI::AY38910::BC2 | GI::AY38910::BC1));
*cycle.value = ay_->get_data_output();
ay_->set_control_lines(static_cast<GI::AY38910::ControlLines>(0));
break;
case 0xa8: case 0xa9:
case 0xaa: case 0xab:
*cycle.value = i8255_.get_register(address);
break;
default:
*cycle.value = 0xff;
break;
}
break;
case CPU::Z80::PartialMachineCycle::Output: {
const int port = address & 0xff;
switch(port) {
case 0x98: case 0x99:
vdp_->run_for(time_since_vdp_update_.flush());
vdp_->set_register(address, *cycle.value);
z80_.set_interrupt_line(vdp_->get_interrupt_line());
time_until_interrupt_ = vdp_->get_time_until_interrupt();
break;
case 0xa0: case 0xa1:
ay_->run_for(time_since_ay_update_.divide_cycles(Cycles(2)));
ay_->set_control_lines(static_cast<GI::AY38910::ControlLines>(GI::AY38910::BDIR | GI::AY38910::BC2 | ((port == 0xa0) ? GI::AY38910::BC1 : 0)));
ay_->set_data_input(*cycle.value);
ay_->set_control_lines(static_cast<GI::AY38910::ControlLines>(0));
break;
case 0xa8: case 0xa9:
case 0xaa: case 0xab:
i8255_.set_register(address, *cycle.value);
break;
case 0xfc: case 0xfd: case 0xfe: case 0xff:
// printf("RAM banking %02x: %02x\n", port, *cycle.value);
break;
}
} break;
default: break;
}
// Per the best information I currently have, the MSX inserts an extra cycle into each opcode read,
// but otherwise runs without pause.
HalfCycles addition((cycle.operation == CPU::Z80::PartialMachineCycle::ReadOpcode) ? 2 : 0);
time_since_vdp_update_ += cycle.length + addition;
time_since_ay_update_ += cycle.length + addition;
return addition;
}
void flush() {
vdp_->run_for(time_since_vdp_update_.flush());
ay_->run_for(time_since_ay_update_.divide_cycles(Cycles(2)));
ay_->flush();
}
// Obtains the system ROMs.
bool set_rom_fetcher(const std::function<std::vector<std::unique_ptr<std::vector<uint8_t>>>(const std::string &machine, const std::vector<std::string> &names)> &roms_with_names) override {
auto roms = roms_with_names(
"MSX",
{
"msx.rom"
});
if(!roms[0]) return false;
rom_ = std::move(*roms[0]);
rom_.resize(32768);
for(size_t c = 0; c < 4; ++c) {
for(size_t slot = 0; slot < 3; ++slot) {
memory_slots_[slot].read_pointers[c] = unpopulated_;
memory_slots_[slot].write_pointers[c] = scratch_;
}
memory_slots_[3].read_pointers[c] =
memory_slots_[3].write_pointers[c] = &ram_[c * 16384];
}
memory_slots_[0].read_pointers[0] = rom_.data();
memory_slots_[0].read_pointers[1] = &rom_[16384];
for(size_t c = 0; c < 4; ++c) {
read_pointers_[c] = memory_slots_[0].read_pointers[c];
write_pointers_[c] = memory_slots_[0].write_pointers[c];
}
return true;
}
void set_keyboard_line(int line) {
selected_key_line_ = line;
}
uint8_t read_keyboard() {
return key_states_[selected_key_line_];
}
void clear_all_keys() override {
std::memset(key_states_, 0xff, sizeof(key_states_));
}
void set_key_state(uint16_t key, bool is_pressed) override {
int mask = 1 << (key & 7);
int line = key >> 4;
if(is_pressed) key_states_[line] &= ~mask; else key_states_[line] |= mask;
}
KeyboardMapper &get_keyboard_mapper() override {
return keyboard_mapper_;
}
private:
class i8255PortHandler: public Intel::i8255::PortHandler {
public:
i8255PortHandler(ConcreteMachine &machine) : machine_(machine) {}
void set_value(int port, uint8_t value) {
switch(port) {
case 0: machine_.page_memory(value); break;
case 2:
// TODO:
// b7 keyboard click
// b6 caps lock LED
// b5 audio output
// b4 cassette motor relay
machine_.set_keyboard_line(value & 0xf);
break;
default: printf("What what what what?\n"); break;
}
}
uint8_t get_value(int port) {
if(port == 1) {
return machine_.read_keyboard();
} else printf("What what?\n");
return 0xff;
}
private:
ConcreteMachine &machine_;
};
CPU::Z80::Processor<ConcreteMachine, false, false> z80_;
std::unique_ptr<TI::TMS9918> vdp_;
Intel::i8255::i8255<i8255PortHandler> i8255_;
std::shared_ptr<GI::AY38910::AY38910> ay_;
i8255PortHandler i8255_port_handler_;
AYPortHandler ay_port_handler_;
uint8_t *read_pointers_[4];
uint8_t *write_pointers_[4];
struct MemorySlots {
uint8_t *read_pointers[4];
uint8_t *write_pointers[4];
} memory_slots_[4];
uint8_t ram_[65536];
uint8_t scratch_[16384];
uint8_t unpopulated_[16384];
std::vector<uint8_t> rom_;
std::vector<uint8_t> cartridge_;
HalfCycles time_since_vdp_update_;
HalfCycles time_since_ay_update_;
HalfCycles time_until_interrupt_;
uint8_t key_states_[16];
int selected_key_line_ = 0;
MSX::KeyboardMapper keyboard_mapper_;
};
}
using namespace MSX;
Machine *Machine::MSX() {
return new ConcreteMachine;
}
Machine::~Machine() {}

22
Machines/MSX/MSX.hpp Normal file
View File

@ -0,0 +1,22 @@
//
// MSX.hpp
// Clock Signal
//
// Created by Thomas Harte on 24/11/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#ifndef MSX_hpp
#define MSX_hpp
namespace MSX {
class Machine {
public:
virtual ~Machine();
static Machine *MSX();
};
}
#endif /* MSX_hpp */

View File

@ -12,6 +12,7 @@
#include "../Atari2600/Atari2600.hpp"
#include "../Commodore/Vic-20/Vic20.hpp"
#include "../Electron/Electron.hpp"
#include "../MSX/MSX.hpp"
#include "../Oric/Oric.hpp"
#include "../ZX8081/ZX8081.hpp"
@ -22,6 +23,7 @@
case StaticAnalyser::Target::AmstradCPC: return new TypedDynamicMachine<AmstradCPC::Machine>(AmstradCPC::Machine::AmstradCPC());
case StaticAnalyser::Target::Atari2600: return new TypedDynamicMachine<Atari2600::Machine>(Atari2600::Machine::Atari2600());
case StaticAnalyser::Target::Electron: return new TypedDynamicMachine<Electron::Machine>(Electron::Machine::Electron());
case StaticAnalyser::Target::MSX: return new TypedDynamicMachine<MSX::Machine>(MSX::Machine::MSX());
case StaticAnalyser::Target::Oric: return new TypedDynamicMachine<Oric::Machine>(Oric::Machine::Oric());
case StaticAnalyser::Target::Vic20: return new TypedDynamicMachine<Commodore::Vic20::Machine>(Commodore::Vic20::Machine::Vic20());
case StaticAnalyser::Target::ZX8081: return new TypedDynamicMachine<ZX8081::Machine>(ZX8081::Machine::ZX8081(target));
@ -35,6 +37,7 @@ std::string Machine::ShortNameForTargetMachine(const StaticAnalyser::Target::Mac
case StaticAnalyser::Target::AmstradCPC: return "AmstradCPC";
case StaticAnalyser::Target::Atari2600: return "Atari2600";
case StaticAnalyser::Target::Electron: return "Electron";
case StaticAnalyser::Target::MSX: return "MSX";
case StaticAnalyser::Target::Oric: return "Oric";
case StaticAnalyser::Target::Vic20: return "Vic20";
case StaticAnalyser::Target::ZX8081: return "ZX8081";
@ -48,6 +51,7 @@ std::string Machine::LongNameForTargetMachine(StaticAnalyser::Target::Machine ma
case StaticAnalyser::Target::AmstradCPC: return "Amstrad CPC";
case StaticAnalyser::Target::Atari2600: return "Atari 2600";
case StaticAnalyser::Target::Electron: return "Acorn Electron";
case StaticAnalyser::Target::MSX: return "MSX";
case StaticAnalyser::Target::Oric: return "Oric";
case StaticAnalyser::Target::Vic20: return "Vic 20";
case StaticAnalyser::Target::ZX8081: return "ZX80/81";

View File

@ -128,8 +128,17 @@
4B08A2751EE35D56008B7065 /* Z80InterruptTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B08A2741EE35D56008B7065 /* Z80InterruptTests.swift */; };
4B08A2781EE39306008B7065 /* TestMachine.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B08A2771EE39306008B7065 /* TestMachine.mm */; };
4B0CCC451C62D0B3001CAC5F /* CRT.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0CCC421C62D0B3001CAC5F /* CRT.cpp */; };
4B0E04EA1FC9E5DA00F43484 /* CAS.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0E04E81FC9E5DA00F43484 /* CAS.cpp */; };
4B0E04EB1FC9E78800F43484 /* CAS.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0E04E81FC9E5DA00F43484 /* CAS.cpp */; };
4B0E04EE1FC9E88300F43484 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0E04EC1FC9E88300F43484 /* StaticAnalyser.cpp */; };
4B0E04F11FC9EA9500F43484 /* MSX.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B79A4FF1FC913C900EEDAD5 /* MSX.cpp */; };
4B0E04F21FC9EAA800F43484 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0E04EC1FC9E88300F43484 /* StaticAnalyser.cpp */; };
4B0E04FA1FC9FA3100F43484 /* 9918.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0E04F91FC9FA3100F43484 /* 9918.cpp */; };
4B0E04FB1FC9FA3100F43484 /* 9918.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0E04F91FC9FA3100F43484 /* 9918.cpp */; };
4B121F951E05E66800BFDA12 /* PCMPatchedTrackTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B121F941E05E66800BFDA12 /* PCMPatchedTrackTests.mm */; };
4B121F9B1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B121F9A1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm */; };
4B12C0ED1FCFA98D005BFD93 /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B12C0EB1FCFA98D005BFD93 /* Keyboard.cpp */; };
4B12C0EE1FCFAD1A005BFD93 /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B12C0EB1FCFA98D005BFD93 /* Keyboard.cpp */; };
4B1414601B58885000E04248 /* WolfgangLorenzTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B14145F1B58885000E04248 /* WolfgangLorenzTests.swift */; };
4B1414621B58888700E04248 /* KlausDormannTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B1414611B58888700E04248 /* KlausDormannTests.swift */; };
4B1497881EE4A1DA00CE2596 /* ZX80O81P.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1497861EE4A1DA00CE2596 /* ZX80O81P.cpp */; };
@ -138,6 +147,8 @@
4B1497921EE4B5A800CE2596 /* ZX8081.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1497901EE4B5A800CE2596 /* ZX8081.cpp */; };
4B1497981EE4B97F00CE2596 /* ZX8081Options.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B1497961EE4B97F00CE2596 /* ZX8081Options.xib */; };
4B1558C01F844ECD006E9A97 /* BitReverse.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1558BE1F844ECD006E9A97 /* BitReverse.cpp */; };
4B1BA08A1FD4967800CB4ADA /* CSMSX.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B1BA0891FD4967800CB4ADA /* CSMSX.mm */; };
4B1BA08D1FD498B000CB4ADA /* MSXOptions.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B1BA08B1FD498B000CB4ADA /* MSXOptions.xib */; };
4B1D08061E0F7A1100763741 /* TimeTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B1D08051E0F7A1100763741 /* TimeTests.mm */; };
4B1E85811D176468001EF87D /* 6532Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B1E85801D176468001EF87D /* 6532Tests.swift */; };
4B1EDB451E39A0AC009D6819 /* chip.png in Resources */ = {isa = PBXBuildFile; fileRef = 4B1EDB431E39A0AC009D6819 /* chip.png */; };
@ -221,6 +232,7 @@
4B71368E1F788112008B8ED9 /* Parser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B71368C1F788112008B8ED9 /* Parser.cpp */; };
4B7136911F789C93008B8ED9 /* SegmentParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B71368F1F789C93008B8ED9 /* SegmentParser.cpp */; };
4B7913CC1DFCD80E00175A82 /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7913CA1DFCD80E00175A82 /* Video.cpp */; };
4B79A5011FC913C900EEDAD5 /* MSX.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B79A4FF1FC913C900EEDAD5 /* MSX.cpp */; };
4B79E4441E3AF38600141F11 /* cassette.png in Resources */ = {isa = PBXBuildFile; fileRef = 4B79E4411E3AF38600141F11 /* cassette.png */; };
4B79E4451E3AF38600141F11 /* floppy35.png in Resources */ = {isa = PBXBuildFile; fileRef = 4B79E4421E3AF38600141F11 /* floppy35.png */; };
4B79E4461E3AF38600141F11 /* floppy525.png in Resources */ = {isa = PBXBuildFile; fileRef = 4B79E4431E3AF38600141F11 /* floppy525.png */; };
@ -629,8 +641,16 @@
4B0B6E121C9DBD5D00FFB60D /* CRTConstants.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = CRTConstants.hpp; sourceTree = "<group>"; };
4B0CCC421C62D0B3001CAC5F /* CRT.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CRT.cpp; sourceTree = "<group>"; };
4B0CCC431C62D0B3001CAC5F /* CRT.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CRT.hpp; 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>"; };
4B0E04EC1FC9E88300F43484 /* StaticAnalyser.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = StaticAnalyser.cpp; path = ../../StaticAnalyser/MSX/StaticAnalyser.cpp; sourceTree = "<group>"; };
4B0E04ED1FC9E88300F43484 /* StaticAnalyser.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = StaticAnalyser.hpp; path = ../../StaticAnalyser/MSX/StaticAnalyser.hpp; sourceTree = "<group>"; };
4B0E04F81FC9FA3000F43484 /* 9918.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = 9918.hpp; path = 9918/9918.hpp; sourceTree = "<group>"; };
4B0E04F91FC9FA3100F43484 /* 9918.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = 9918.cpp; path = 9918/9918.cpp; sourceTree = "<group>"; };
4B121F941E05E66800BFDA12 /* PCMPatchedTrackTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = PCMPatchedTrackTests.mm; sourceTree = "<group>"; };
4B121F9A1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = PCMSegmentEventSourceTests.mm; sourceTree = "<group>"; };
4B12C0EB1FCFA98D005BFD93 /* Keyboard.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Keyboard.cpp; path = MSX/Keyboard.cpp; sourceTree = "<group>"; };
4B12C0EC1FCFA98D005BFD93 /* Keyboard.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = Keyboard.hpp; path = MSX/Keyboard.hpp; sourceTree = "<group>"; };
4B1414501B58848C00E04248 /* ClockSignal-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ClockSignal-Bridging-Header.h"; sourceTree = "<group>"; };
4B14145F1B58885000E04248 /* WolfgangLorenzTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WolfgangLorenzTests.swift; sourceTree = "<group>"; };
4B1414611B58888700E04248 /* KlausDormannTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KlausDormannTests.swift; sourceTree = "<group>"; };
@ -645,6 +665,9 @@
4B1497971EE4B97F00CE2596 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/ZX8081Options.xib"; sourceTree = SOURCE_ROOT; };
4B1558BE1F844ECD006E9A97 /* BitReverse.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = BitReverse.cpp; path = Data/BitReverse.cpp; sourceTree = "<group>"; };
4B1558BF1F844ECD006E9A97 /* BitReverse.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = BitReverse.hpp; path = Data/BitReverse.hpp; sourceTree = "<group>"; };
4B1BA0881FD4967700CB4ADA /* CSMSX.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSMSX.h; sourceTree = "<group>"; };
4B1BA0891FD4967800CB4ADA /* CSMSX.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CSMSX.mm; sourceTree = "<group>"; };
4B1BA08C1FD498B000CB4ADA /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/MSXOptions.xib"; sourceTree = SOURCE_ROOT; };
4B1D08051E0F7A1100763741 /* TimeTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = TimeTests.mm; sourceTree = "<group>"; };
4B1E857B1D174DEC001EF87D /* 6532.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 6532.hpp; sourceTree = "<group>"; };
4B1E85801D176468001EF87D /* 6532Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = 6532Tests.swift; sourceTree = "<group>"; };
@ -820,6 +843,8 @@
4B7913CA1DFCD80E00175A82 /* Video.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Video.cpp; path = Electron/Video.cpp; sourceTree = "<group>"; };
4B7913CB1DFCD80E00175A82 /* Video.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Video.hpp; path = Electron/Video.hpp; sourceTree = "<group>"; };
4B79A4FE1FC9082300EEDAD5 /* TypedDynamicMachine.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = TypedDynamicMachine.hpp; sourceTree = "<group>"; };
4B79A4FF1FC913C900EEDAD5 /* MSX.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = MSX.cpp; path = MSX/MSX.cpp; sourceTree = "<group>"; };
4B79A5001FC913C900EEDAD5 /* MSX.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = MSX.hpp; path = MSX/MSX.hpp; sourceTree = "<group>"; };
4B79E4411E3AF38600141F11 /* cassette.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = cassette.png; sourceTree = "<group>"; };
4B79E4421E3AF38600141F11 /* floppy35.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = floppy35.png; sourceTree = "<group>"; };
4B79E4431E3AF38600141F11 /* floppy525.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = floppy525.png; sourceTree = "<group>"; };
@ -1343,6 +1368,24 @@
path = ../../Outputs/CRT;
sourceTree = "<group>";
};
4B0E04F01FC9E89100F43484 /* MSX */ = {
isa = PBXGroup;
children = (
4B0E04EC1FC9E88300F43484 /* StaticAnalyser.cpp */,
4B0E04ED1FC9E88300F43484 /* StaticAnalyser.hpp */,
);
name = MSX;
sourceTree = "<group>";
};
4B0E04F71FC9F2C800F43484 /* 9918 */ = {
isa = PBXGroup;
children = (
4B0E04F91FC9FA3100F43484 /* 9918.cpp */,
4B0E04F81FC9FA3000F43484 /* 9918.hpp */,
);
name = 9918;
sourceTree = "<group>";
};
4B1414561B58879D00E04248 /* 6502 */ = {
isa = PBXGroup;
children = (
@ -1454,6 +1497,7 @@
4B38F34A1F2EC3CA00D9235D /* CSAmstradCPC.h */,
4B2A53991D117D36003C6002 /* CSAtari2600.h */,
4B2A539B1D117D36003C6002 /* CSElectron.h */,
4B1BA0881FD4967700CB4ADA /* CSMSX.h */,
4BCF1FA61DADC5250039D2E7 /* CSOric.h */,
4B2A539D1D117D36003C6002 /* CSVic20.h */,
4B14978D1EE4B4D200CE2596 /* CSZX8081.h */,
@ -1461,6 +1505,7 @@
4B38F34B1F2EC3CA00D9235D /* CSAmstradCPC.mm */,
4B2A539A1D117D36003C6002 /* CSAtari2600.mm */,
4B2A539C1D117D36003C6002 /* CSElectron.mm */,
4B1BA0891FD4967800CB4ADA /* CSMSX.mm */,
4BCF1FA71DADC5250039D2E7 /* CSOric.mm */,
4B2A539E1D117D36003C6002 /* CSVic20.mm */,
4B14978E1EE4B4D200CE2596 /* CSZX8081.mm */,
@ -1752,6 +1797,7 @@
4B8FE2131DA19D5F0090D3CE /* Atari2600Options.xib */,
4B8FE2171DA19D5F0090D3CE /* ElectronOptions.xib */,
4B8FE2151DA19D5F0090D3CE /* MachineDocument.xib */,
4B1BA08B1FD498B000CB4ADA /* MSXOptions.xib */,
4B2A332B1DB86821002876E3 /* OricOptions.xib */,
4B8FE2191DA19D5F0090D3CE /* Vic20Options.xib */,
4B1497961EE4B97F00CE2596 /* ZX8081Options.xib */,
@ -1828,6 +1874,7 @@
4B69FB411C4D941400B5F0AA /* Formats */ = {
isa = PBXGroup;
children = (
4B0E04E81FC9E5DA00F43484 /* CAS.cpp */,
4BC91B811D1F160E00884B76 /* CommodoreTAP.cpp */,
4B3BF5AE1F146264005B6C36 /* CSW.cpp */,
4B59199A1DAC6C46005BB85C /* OricTAP.cpp */,
@ -1835,6 +1882,7 @@
4B69FB421C4D941400B5F0AA /* TapeUEF.cpp */,
4B448E7F1F1C45A00009ABD6 /* TZX.cpp */,
4B1497861EE4A1DA00CE2596 /* ZX80O81P.cpp */,
4B0E04E91FC9E5DA00F43484 /* CAS.hpp */,
4BC91B821D1F160E00884B76 /* CommodoreTAP.hpp */,
4B3BF5AF1F146264005B6C36 /* CSW.hpp */,
4B59199B1DAC6C46005BB85C /* OricTAP.hpp */,
@ -1895,6 +1943,17 @@
name = Z80;
sourceTree = "<group>";
};
4B79A4FC1FC8FF9800EEDAD5 /* MSX */ = {
isa = PBXGroup;
children = (
4B79A4FF1FC913C900EEDAD5 /* MSX.cpp */,
4B79A5001FC913C900EEDAD5 /* MSX.hpp */,
4B12C0EB1FCFA98D005BFD93 /* Keyboard.cpp */,
4B12C0EC1FCFA98D005BFD93 /* Keyboard.hpp */,
);
name = MSX;
sourceTree = "<group>";
};
4B8334881F5DB8470097E338 /* Implementation */ = {
isa = PBXGroup;
children = (
@ -2376,15 +2435,16 @@
isa = PBXGroup;
children = (
4B54C0BB1F8D8E790050900F /* KeyboardMachine.cpp */,
4BDCC5F81FB27A5E001220C5 /* ROMMachine.hpp */,
4BA9C3CF1D8164A9002DDB61 /* ConfigurationTarget.hpp */,
4B046DC31CFE651500E9E45E /* CRTMachine.hpp */,
4B7041271F92C26900735E45 /* JoystickMachine.hpp */,
4B8E4ECD1DCE483D003716C3 /* KeyboardMachine.hpp */,
4BDCC5F81FB27A5E001220C5 /* ROMMachine.hpp */,
4B38F3491F2EC12000D9235D /* AmstradCPC */,
4B2E2D961C3A06EC00138695 /* Atari2600 */,
4B4DC81D1D2C2425003C5BF8 /* Commodore */,
4B2E2D9E1C3A070900138695 /* Electron */,
4B79A4FC1FC8FF9800EEDAD5 /* MSX */,
4BCF1FA51DADC3E10039D2E7 /* Oric */,
4B2B3A461F9B8FA70062DABF /* Utility */,
4B1497931EE4B5AC00CE2596 /* ZX8081 */,
@ -2482,6 +2542,7 @@
4BE845221F2FF7F400A5EA22 /* 6845 */,
4BD9137C1F3115AC009BCF85 /* 8255 */,
4BBC951F1F368D87008F4C34 /* 8272 */,
4B0E04F71FC9F2C800F43484 /* 9918 */,
);
name = Components;
path = ../../Components;
@ -2660,6 +2721,7 @@
4BCF1FAC1DADD41F0039D2E7 /* Oric */,
4B14978C1EE4AC6200CE2596 /* ZX80/81 */,
4B38F3451F2EB41800D9235D /* AmstradCPC */,
4B0E04F01FC9E89100F43484 /* MSX */,
);
name = StaticAnalyser;
sourceTree = "<group>";
@ -2821,6 +2883,7 @@
4B2C45421E3C3896002A2389 /* cartridge.png in Resources */,
4BB73EA91B587A5100552FC2 /* Assets.xcassets in Resources */,
4B79E4451E3AF38600141F11 /* floppy35.png in Resources */,
4B1BA08D1FD498B000CB4ADA /* MSXOptions.xib in Resources */,
4B1EDB451E39A0AC009D6819 /* chip.png in Resources */,
4B2A332D1DB86821002876E3 /* OricOptions.xib in Resources */,
4B8FE21B1DA19D5F0090D3CE /* Atari2600Options.xib in Resources */,
@ -3130,12 +3193,14 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
4B0E04FB1FC9FA3100F43484 /* 9918.cpp in Sources */,
4B055AAA1FAE85F50060FFFF /* CPM.cpp in Sources */,
4B055A9A1FAE85CB0060FFFF /* MFMDiskController.cpp in Sources */,
4B055ACB1FAE9AFB0060FFFF /* SerialBus.cpp in Sources */,
4B055AA41FAE85E50060FFFF /* DigitalPhaseLockedLoop.cpp in Sources */,
4B055AE61FAE9B6F0060FFFF /* OutputShader.cpp in Sources */,
4B055A9B1FAE85DA0060FFFF /* AcornADF.cpp in Sources */,
4B0E04F11FC9EA9500F43484 /* MSX.cpp in Sources */,
4B055AD51FAE9B0B0060FFFF /* Video.cpp in Sources */,
4B055A811FAE853A0060FFFF /* Disk.cpp in Sources */,
4B055AE11FAE9B6F0060FFFF /* ArrayBuilder.cpp in Sources */,
@ -3157,6 +3222,7 @@
4B055AB61FAE860F0060FFFF /* TapeUEF.cpp in Sources */,
4B055A9D1FAE85DA0060FFFF /* D64.cpp in Sources */,
4B055ABB1FAE86170060FFFF /* Oric.cpp in Sources */,
4B12C0EE1FCFAD1A005BFD93 /* Keyboard.cpp in Sources */,
4B055AE81FAE9B7B0060FFFF /* FIRFilter.cpp in Sources */,
4B055A901FAE85A90060FFFF /* TimedEventLoop.cpp in Sources */,
4B055AAB1FAE85FD0060FFFF /* PCMPatchedTrack.cpp in Sources */,
@ -3222,6 +3288,7 @@
4B055ADF1FAE9B4C0060FFFF /* IRQDelegatePortHandler.cpp in Sources */,
4B055AB51FAE860F0060FFFF /* TapePRG.cpp in Sources */,
4B055AE01FAE9B660060FFFF /* CRT.cpp in Sources */,
4B0E04F21FC9EAA800F43484 /* StaticAnalyser.cpp in Sources */,
4B055AD01FAE9B030060FFFF /* Tape.cpp in Sources */,
4B055A961FAE85BB0060FFFF /* Commodore.cpp in Sources */,
4B055ADE1FAE9B4C0060FFFF /* 6522Base.cpp in Sources */,
@ -3234,6 +3301,7 @@
4B07835B1FC11D42001D12BB /* Configurable.cpp in Sources */,
4B055AE71FAE9B6F0060FFFF /* Shader.cpp in Sources */,
4B055AEC1FAE9BA20060FFFF /* Z80Base.cpp in Sources */,
4B0E04EB1FC9E78800F43484 /* CAS.cpp in Sources */,
4B055AE31FAE9B6F0060FFFF /* TextureBuilder.cpp in Sources */,
4B055AB91FAE86170060FFFF /* Acorn.cpp in Sources */,
4B055A931FAE85B50060FFFF /* BinaryDump.cpp in Sources */,
@ -3254,6 +3322,7 @@
4B59199C1DAC6C46005BB85C /* OricTAP.cpp in Sources */,
4BB697CE1D4BA44400248BDF /* CommodoreGCR.cpp in Sources */,
4B7136861F78724F008B8ED9 /* Encoder.cpp in Sources */,
4B0E04EA1FC9E5DA00F43484 /* CAS.cpp in Sources */,
4B58601E1F806AB200AEE2E3 /* MFMSectorDump.cpp in Sources */,
4B448E841F1C4C480009ABD6 /* PulseQueuedTape.cpp in Sources */,
4BD14B111D74627C0088EAD6 /* StaticAnalyser.cpp in Sources */,
@ -3296,6 +3365,7 @@
4B2A332F1DB86869002876E3 /* OricOptionsPanel.swift in Sources */,
4B2B3A4C1F9B8FA70062DABF /* MemoryFuzzer.cpp in Sources */,
4B7913CC1DFCD80E00175A82 /* Video.cpp in Sources */,
4B0E04EE1FC9E88300F43484 /* StaticAnalyser.cpp in Sources */,
4B4518831F75E91A00926311 /* PCMTrack.cpp in Sources */,
4B45189F1F75FD1C00926311 /* AcornADF.cpp in Sources */,
4B2A53A11D117D36003C6002 /* CSAtari2600.mm in Sources */,
@ -3303,6 +3373,7 @@
4B4518A21F75FD1C00926311 /* G64.cpp in Sources */,
4BF829661D8F732B001BAE39 /* Disk.cpp in Sources */,
4B448E811F1C45A00009ABD6 /* TZX.cpp in Sources */,
4B1BA08A1FD4967800CB4ADA /* CSMSX.mm in Sources */,
4BEA52631DF339D7007E74F2 /* Speaker.cpp in Sources */,
4BC5E4921D7ED365008CF980 /* StaticAnalyser.cpp in Sources */,
4BC830D11D6E7C690000A26F /* Tape.cpp in Sources */,
@ -3316,6 +3387,7 @@
4B71368E1F788112008B8ED9 /* Parser.cpp in Sources */,
4BE77A2E1D84ADFB00BC3827 /* File.cpp in Sources */,
4B14978B1EE4AC5E00CE2596 /* StaticAnalyser.cpp in Sources */,
4B12C0ED1FCFA98D005BFD93 /* Keyboard.cpp in Sources */,
4BA0F68E1EEA0E8400E9489E /* ZX8081.cpp in Sources */,
4BD468F71D8DF41D0084958B /* 1770.cpp in Sources */,
4BD3A30B1EE755C800B5B501 /* Video.cpp in Sources */,
@ -3331,6 +3403,7 @@
4B80AD001F85CACA00176895 /* BestEffortUpdater.cpp in Sources */,
4B2E2D9D1C3A070400138695 /* Electron.cpp in Sources */,
4B3940E71DA83C8300427841 /* AsyncTaskQueue.cpp in Sources */,
4B0E04FA1FC9FA3100F43484 /* 9918.cpp in Sources */,
4B69FB3D1C4D908A00B5F0AA /* Tape.cpp in Sources */,
4B4518841F75E91A00926311 /* UnformattedTrack.cpp in Sources */,
4B8FE2291DA1EDDF0090D3CE /* ElectronOptionsPanel.swift in Sources */,
@ -3369,6 +3442,7 @@
4BFE7B871FC39BF100160B38 /* StandardOptions.cpp in Sources */,
4B5FADC01DE3BF2B00AEC565 /* Microdisc.cpp in Sources */,
4B54C0C81F8D91E50050900F /* Keyboard.cpp in Sources */,
4B79A5011FC913C900EEDAD5 /* MSX.cpp in Sources */,
4BEE0A701D72496600532C7B /* PRG.cpp in Sources */,
4B8334861F5DA3780097E338 /* 6502Storage.cpp in Sources */,
4B8FE2271DA1DE2D0090D3CE /* NSBundle+DataResource.m in Sources */,
@ -3457,6 +3531,14 @@
name = ZX8081Options.xib;
sourceTree = "<group>";
};
4B1BA08B1FD498B000CB4ADA /* MSXOptions.xib */ = {
isa = PBXVariantGroup;
children = (
4B1BA08C1FD498B000CB4ADA /* Base */,
);
name = MSXOptions.xib;
sourceTree = "<group>";
};
4B2A332B1DB86821002876E3 /* OricOptions.xib */ = {
isa = PBXVariantGroup;
children = (

View File

@ -0,0 +1,48 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="12121" systemVersion="16F73" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="12121"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="MachineDocument" customModule="Clock_Signal" customModuleProvider="target">
<connections>
<outlet property="optionsPanel" destination="ZW7-Bw-4RP" id="JpE-wG-zRR"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<window title="Options" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" hidesOnDeactivate="YES" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" id="ZW7-Bw-4RP" customClass="MachinePanel" customModule="Clock_Signal" customModuleProvider="target">
<windowStyleMask key="styleMask" titled="YES" closable="YES" utility="YES" nonactivatingPanel="YES" HUD="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="83" y="102" width="200" height="54"/>
<rect key="screenRect" x="0.0" y="0.0" width="1366" height="768"/>
<view key="contentView" id="tpZ-0B-QQu">
<rect key="frame" x="0.0" y="0.0" width="200" height="54"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<button translatesAutoresizingMaskIntoConstraints="NO" id="e1J-pw-zGw">
<rect key="frame" x="18" y="18" width="164" height="18"/>
<buttonCell key="cell" type="check" title="Load Quickly" bezelStyle="regularSquare" imagePosition="left" alignment="left" state="on" inset="2" id="tD6-UB-ESB">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="setFastLoading:" target="ZW7-Bw-4RP" id="JmG-Ks-jSh"/>
</connections>
</button>
</subviews>
<constraints>
<constraint firstItem="e1J-pw-zGw" firstAttribute="leading" secondItem="tpZ-0B-QQu" secondAttribute="leading" constant="20" id="HSD-3d-Bl7"/>
<constraint firstAttribute="trailing" secondItem="e1J-pw-zGw" secondAttribute="trailing" constant="20" id="Q9M-FH-92N"/>
<constraint firstAttribute="bottom" secondItem="e1J-pw-zGw" secondAttribute="bottom" constant="20" id="sdh-oJ-ZIQ"/>
<constraint firstItem="e1J-pw-zGw" firstAttribute="top" secondItem="tpZ-0B-QQu" secondAttribute="top" constant="20" id="ul9-lf-Y3u"/>
</constraints>
</view>
<connections>
<outlet property="fastLoadingButton" destination="e1J-pw-zGw" id="jj7-OZ-mOH"/>
</connections>
<point key="canvasLocation" x="175" y="30"/>
</window>
</objects>
</document>

View File

@ -261,6 +261,20 @@
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>cas</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>cassette</string>
<key>CFBundleTypeName</key>
<string>MSX Tape Image</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
</dict>
</array>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>

View File

@ -17,6 +17,7 @@
#import "CSAmstradCPC.h"
#import "CSAtari2600.h"
#import "CSElectron.h"
#import "CSMSX.h"
#import "CSOric.h"
#import "CSVic20.h"
#import "CSZX8081+Instantiation.h"
@ -45,6 +46,7 @@
case StaticAnalyser::Target::AmstradCPC: return nil;
case StaticAnalyser::Target::Atari2600: return @"Atari2600Options";
case StaticAnalyser::Target::Electron: return @"ElectronOptions";
case StaticAnalyser::Target::MSX: return @"MSXOptions";
case StaticAnalyser::Target::Oric: return @"OricOptions";
case StaticAnalyser::Target::Vic20: return @"Vic20Options";
case StaticAnalyser::Target::ZX8081: return @"ZX8081Options";
@ -57,6 +59,7 @@
case StaticAnalyser::Target::AmstradCPC: return [[CSAmstradCPC alloc] init];
case StaticAnalyser::Target::Atari2600: return [[CSAtari2600 alloc] init];
case StaticAnalyser::Target::Electron: return [[CSElectron alloc] init];
case StaticAnalyser::Target::MSX: return [[CSMSX alloc] init];
case StaticAnalyser::Target::Oric: return [[CSOric alloc] init];
case StaticAnalyser::Target::Vic20: return [[CSVic20 alloc] init];
case StaticAnalyser::Target::ZX8081: return [[CSZX8081 alloc] initWithIntendedTarget:_target];

View File

@ -0,0 +1,15 @@
//
// CSMSX.h
// Clock Signal
//
// Created by Thomas Harte on 03/12/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#import "CSMachine.h"
@interface CSMSX : CSMachine
- (instancetype)init;
@end

View File

@ -0,0 +1,25 @@
//
// CSMSX.m
// Clock Signal
//
// Created by Thomas Harte on 03/12/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#import "CSMSX.h"
#include "MSX.hpp"
#include "TypedDynamicMachine.hpp"
@implementation CSMSX {
Machine::TypedDynamicMachine<MSX::Machine> _msx;
}
- (instancetype)init {
_msx = Machine::TypedDynamicMachine<MSX::Machine>(MSX::Machine::MSX());
return [super initWithMachine:&_msx];
}
- (NSString *)userDefaultsPrefix { return @"MSX"; }
@end

View File

@ -14,6 +14,7 @@ SOURCES += glob.glob('../../Components/1770/*.cpp')
SOURCES += glob.glob('../../Components/6522/Implementation/*.cpp')
SOURCES += glob.glob('../../Components/6560/*.cpp')
SOURCES += glob.glob('../../Components/8272/*.cpp')
SOURCES += glob.glob('../../Components/9918/*.cpp')
SOURCES += glob.glob('../../Components/AY38910/*.cpp')
SOURCES += glob.glob('../../Concurrency/*.cpp')
@ -29,6 +30,7 @@ SOURCES += glob.glob('../../Machines/Commodore/*.cpp')
SOURCES += glob.glob('../../Machines/Commodore/1540/Implementation/*.cpp')
SOURCES += glob.glob('../../Machines/Commodore/Vic-20/*.cpp')
SOURCES += glob.glob('../../Machines/Electron/*.cpp')
SOURCES += glob.glob('../../Machines/MSX/*.cpp')
SOURCES += glob.glob('../../Machines/Oric/*.cpp')
SOURCES += glob.glob('../../Machines/Utility/*.cpp')
SOURCES += glob.glob('../../Machines/ZX8081/*.cpp')
@ -48,6 +50,7 @@ SOURCES += glob.glob('../../StaticAnalyser/AmstradCPC/*.cpp')
SOURCES += glob.glob('../../StaticAnalyser/Atari/*.cpp')
SOURCES += glob.glob('../../StaticAnalyser/Commodore/*.cpp')
SOURCES += glob.glob('../../StaticAnalyser/Disassembler/*.cpp')
SOURCES += glob.glob('../../StaticAnalyser/MSX/*.cpp')
SOURCES += glob.glob('../../StaticAnalyser/Oric/*.cpp')
SOURCES += glob.glob('../../StaticAnalyser/ZX8081/*.cpp')

View File

@ -120,7 +120,7 @@ class ProcessorStorage {
RegisterPair afDash_, bcDash_, deDash_, hlDash_;
RegisterPair ix_, iy_, pc_, sp_;
RegisterPair ir_, refresh_addr_;
bool iff1_, iff2_;
bool iff1_ = false, iff2_ = false;
int interrupt_mode_ = 0;
uint16_t pc_increment_ = 1;
uint8_t sign_result_; // the sign flag is set if the value in sign_result_ is negative

7
ROMImages/MSX/readme.txt Normal file
View File

@ -0,0 +1,7 @@
ROMs for the MSX go here; the copyright status of these is uncertain so they have not been included in this repository.
Expected files:
msx.rom
These names match those offered for download at http://fms.komkon.org/fMSX/ (albeit in lowercase), and the emulator has been tested against those images.

View File

@ -17,8 +17,8 @@ static std::list<std::shared_ptr<Storage::Cartridge::Cartridge>>
AcornCartridgesFrom(const std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges) {
std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> acorn_cartridges;
for(std::shared_ptr<Storage::Cartridge::Cartridge> cartridge : cartridges) {
const std::list<Storage::Cartridge::Cartridge::Segment> &segments = cartridge->get_segments();
for(const auto &cartridge : cartridges) {
const auto &segments = cartridge->get_segments();
// only one mapped item is allowed
if(segments.size() != 1) continue;

View File

@ -189,7 +189,7 @@ void StaticAnalyser::Atari::AddTargets(const Media &media, std::list<Target> &de
// try to figure out the paging scheme
if(!media.cartridges.empty()) {
const std::list<Storage::Cartridge::Cartridge::Segment> &segments = media.cartridges.front()->get_segments();
const auto &segments = media.cartridges.front()->get_segments();
if(segments.size() == 1) {
const Storage::Cartridge::Cartridge::Segment &segment = segments.front();

View File

@ -21,8 +21,8 @@ static std::list<std::shared_ptr<Storage::Cartridge::Cartridge>>
Vic20CartridgesFrom(const std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges) {
std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> vic20_cartridges;
for(std::shared_ptr<Storage::Cartridge::Cartridge> cartridge : cartridges) {
const std::list<Storage::Cartridge::Cartridge::Segment> &segments = cartridge->get_segments();
for(const auto &cartridge : cartridges) {
const auto &segments = cartridge->get_segments();
// only one mapped item is allowed
if(segments.size() != 1) continue;

View File

@ -0,0 +1,73 @@
//
// StaticAnalyser.cpp
// Clock Signal
//
// Created by Thomas Harte on 25/11/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#include "StaticAnalyser.hpp"
/*
DEFB "AB" ; expansion ROM header
DEFW initcode ; start of the init code, 0 if no initcode
DEFW callstat; pointer to CALL statement handler, 0 if no such handler
DEFW device; pointer to expansion device handler, 0 if no such handler
DEFW basic ; pointer to the start of a tokenized basicprogram, 0 if no basicprogram
DEFS 6,0 ; room reserved for future extensions
*/
static std::list<std::shared_ptr<Storage::Cartridge::Cartridge>>
MSXCartridgesFrom(const std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges) {
std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> msx_cartridges;
for(const auto &cartridge : cartridges) {
const auto &segments = cartridge->get_segments();
// Only one mapped item is allowed.
if(segments.size() != 1) continue;
// Which must be a multiple of 16 kb in size.
Storage::Cartridge::Cartridge::Segment segment = segments.front();
const size_t data_size = segment.data.size();
if(data_size < 0x4000 || data_size & 0x3fff) continue;
// Check for a ROM header at address 0; TODO: if it's not found then try 0x4000
// and consider swapping the image.
// Check for the expansion ROM header and the reserved bytes.
if(segment.data[0] != 0x41 || segment.data[1] != 0x42) continue;
bool all_zeroes = true;
for(size_t c = 0; c < 6; ++c) {
if(segment.data[10 + c] != 0) all_zeroes = false;
}
if(!all_zeroes) continue;
// Pick a paging address based on the four pointers.
uint16_t start_address = 0xc000;
for(size_t c = 0; c < 8; c += 2) {
uint16_t code_pointer = static_cast<uint16_t>(segment.data[2 + c] | segment.data[3 + c] << 8);
if(code_pointer) {
start_address = std::min(static_cast<uint16_t>(code_pointer &~ 0x3fff), start_address);
}
}
// That'll do then, but apply the detected start address.
msx_cartridges.emplace_back(new Storage::Cartridge::Cartridge({
Storage::Cartridge::Cartridge::Segment(start_address, segment.data)
}));
}
return msx_cartridges;
}
void StaticAnalyser::MSX::AddTargets(const Media &media, std::list<Target> &destination) {
Target target;
target.media.cartridges = MSXCartridgesFrom(media.cartridges);
if(!target.media.empty()) {
target.machine = Target::MSX;
target.probability = 1.0;
destination.push_back(target);
}
}

View File

@ -0,0 +1,22 @@
//
// StaticAnalyser.hpp
// Clock Signal
//
// Created by Thomas Harte on 25/11/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#ifndef StaticAnalyser_MSX_StaticAnalyser_hpp
#define StaticAnalyser_MSX_StaticAnalyser_hpp
#include "../StaticAnalyser.hpp"
namespace StaticAnalyser {
namespace MSX {
void AddTargets(const Media &media, std::list<Target> &destination);
}
}
#endif /* StaticAnalyser_MSX_StaticAnalyser_hpp */

View File

@ -16,6 +16,7 @@
#include "AmstradCPC/StaticAnalyser.hpp"
#include "Atari/StaticAnalyser.hpp"
#include "Commodore/StaticAnalyser.hpp"
#include "MSX/StaticAnalyser.hpp"
#include "Oric/StaticAnalyser.hpp"
#include "ZX8081/StaticAnalyser.hpp"
@ -33,6 +34,7 @@
#include "../Storage/Disk/DiskImage/Formats/SSD.hpp"
// Tapes
#include "../Storage/Tape/Formats/CAS.hpp"
#include "../Storage/Tape/Formats/CommodoreTAP.hpp"
#include "../Storage/Tape/Formats/CSW.hpp"
#include "../Storage/Tape/Formats/OricTAP.hpp"
@ -83,6 +85,7 @@ static Media GetMediaAndPlatforms(const char *file_name, TargetPlatform::IntType
Format("a26", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Atari2600) // A26
Format("adf", result.disks, Disk::DiskImageHolder<Storage::Disk::AcornADF>, TargetPlatform::Acorn) // ADF
Format("bin", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Atari2600) // BIN
Format("cas", result.tapes, Tape::CAS, TargetPlatform::MSX) // CAS
Format("cdt", result.tapes, Tape::TZX, TargetPlatform::AmstradCPC) // CDT
Format("csw", result.tapes, Tape::CSW, TargetPlatform::AllTape) // CSW
Format("d64", result.disks, Disk::DiskImageHolder<Storage::Disk::D64>, TargetPlatform::Commodore) // D64
@ -107,7 +110,7 @@ static Media GetMediaAndPlatforms(const char *file_name, TargetPlatform::IntType
}
}
Format("rom", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Acorn) // ROM
Format("rom", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Acorn | TargetPlatform::MSX) // ROM
Format("ssd", result.disks, Disk::DiskImageHolder<Storage::Disk::SSD>, TargetPlatform::Acorn) // SSD
Format("tap", result.tapes, Tape::CommodoreTAP, TargetPlatform::Commodore) // TAP (Commodore)
Format("tap", result.tapes, Tape::OricTAP, TargetPlatform::Oric) // TAP (Oric)
@ -145,6 +148,7 @@ std::list<Target> StaticAnalyser::GetTargets(const char *file_name) {
if(potential_platforms & TargetPlatform::AmstradCPC) AmstradCPC::AddTargets(media, targets);
if(potential_platforms & TargetPlatform::Atari2600) Atari::AddTargets(media, targets);
if(potential_platforms & TargetPlatform::Commodore) Commodore::AddTargets(media, targets);
if(potential_platforms & TargetPlatform::MSX) MSX::AddTargets(media, targets);
if(potential_platforms & TargetPlatform::Oric) Oric::AddTargets(media, targets);
if(potential_platforms & TargetPlatform::ZX8081) ZX8081::AddTargets(media, targets, potential_platforms);

View File

@ -59,6 +59,10 @@ struct Media {
std::list<std::shared_ptr<Storage::Disk::Disk>> disks;
std::list<std::shared_ptr<Storage::Tape::Tape>> tapes;
std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> cartridges;
bool empty() const {
return disks.empty() && tapes.empty() && cartridges.empty();
}
};
/*!
@ -70,6 +74,7 @@ struct Target {
AmstradCPC,
Atari2600,
Electron,
MSX,
Oric,
Vic20,
ZX8081

View File

@ -8,4 +8,5 @@
#include "Cartridge.hpp"
const int Storage::Cartridge::Cartridge::Segment::UnknownAddress = -1;
const size_t Storage::Cartridge::Cartridge::Segment::UnknownAddress = static_cast<size_t>(-1);

View File

@ -9,7 +9,6 @@
#ifndef Storage_Cartridge_hpp
#define Storage_Cartridge_hpp
#include <list>
#include <vector>
#include <memory>
@ -29,19 +28,22 @@ namespace Cartridge {
class Cartridge {
public:
struct Segment {
Segment(int start_address, int end_address, std::vector<uint8_t> data) :
Segment(size_t start_address, size_t end_address, std::vector<uint8_t> data) :
start_address(start_address), end_address(end_address), data(std::move(data)) {}
Segment(size_t start_address, std::vector<uint8_t> data) :
Segment(start_address, start_address + data.size(), data) {}
/// Indicates that an address is unknown.
static const int UnknownAddress;
static const size_t UnknownAddress;
/// The initial CPU-exposed starting address for this segment; may be @c UnknownAddress.
int start_address;
size_t start_address;
/*!
The initial CPU-exposed ending address for this segment; may be @c UnknownAddress. Not necessarily equal
to start_address + data_length due to potential paging.
*/
int end_address;
size_t end_address;
/*!
The data contents for this segment. If @c start_address and @c end_address are suppled then
@ -51,11 +53,16 @@ class Cartridge {
std::vector<uint8_t> data;
};
const std::list<Segment> &get_segments() { return segments_; }
const std::vector<Segment> &get_segments() const {
return segments_;
}
virtual ~Cartridge() {}
Cartridge() {}
Cartridge(const std::vector<Segment> &segments) : segments_(segments) {}
protected:
std::list<Segment> segments_;
std::vector<Segment> segments_;
};
}

View File

@ -0,0 +1,28 @@
//
// CAS.cpp
// Clock Signal
//
// Created by Thomas Harte on 25/11/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#include "CAS.hpp"
using namespace Storage::Tape;
CAS::CAS(const char *file_name) :
file_(file_name) {
}
bool CAS::is_at_end() {
return true;
}
void CAS::virtual_reset() {
}
Tape::Pulse CAS::virtual_get_next_pulse() {
Pulse empty_pulse;
return empty_pulse;
}

View File

@ -0,0 +1,47 @@
//
// CAS.hpp
// Clock Signal
//
// Created by Thomas Harte on 25/11/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#ifndef CAS_hpp
#define CAS_hpp
#include "../Tape.hpp"
#include "../../FileHolder.hpp"
namespace Storage {
namespace Tape {
/*!
Provides a @c Tape containing a CAS tape image, which is an MSX byte stream.
*/
class CAS: public Tape {
public:
/*!
Constructs a @c CAS containing content from the file with name @c file_name.
@throws ErrorNotCAS if this file could not be opened and recognised as a valid CAS file.
*/
CAS(const char *file_name);
enum {
ErrorNotCAS
};
// implemented to satisfy @c Tape
bool is_at_end();
private:
Storage::FileHolder file_;
void virtual_reset();
Pulse virtual_get_next_pulse();
};
}
}
#endif /* CAS_hpp */

View File

@ -21,14 +21,15 @@ enum Type: IntType {
BBCModelA = 1 << 6,
BBCModelB = 1 << 7,
Commodore = 1 << 8,
Oric = 1 << 9,
ZX80 = 1 << 10,
ZX81 = 1 << 11,
MSX = 1 << 9,
Oric = 1 << 10,
ZX80 = 1 << 11,
ZX81 = 1 << 12,
Acorn = AcornAtom | AcornElectron | BBCMaster | BBCModelA | BBCModelB,
ZX8081 = ZX80 | ZX81,
AllTape = Acorn | AmstradCPC | Commodore | Oric | ZX80 | ZX81,
AllDisk = Acorn | AmstradCPC | Commodore | Oric,
AllTape = Acorn | AmstradCPC | Commodore | Oric | ZX80 | ZX81 | MSX,
AllDisk = Acorn | AmstradCPC | Commodore | Oric | MSX,
};
class TypeDistinguisher {