mirror of
https://github.com/TomHarte/CLK.git
synced 2024-12-26 09:29:45 +00:00
Merge pull request #553 from TomHarte/MasterSystemVDP
Adds Initial Sega SG1000 and Master System emulation
This commit is contained in:
commit
2ee360e6ba
@ -17,6 +17,7 @@ enum class Machine {
|
||||
Atari2600,
|
||||
ColecoVision,
|
||||
Electron,
|
||||
MasterSystem,
|
||||
MSX,
|
||||
Oric,
|
||||
Vic20,
|
||||
|
@ -23,5 +23,4 @@ TargetList GetTargets(const Media &media, const std::string &file_name, TargetPl
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif /* StaticAnalyser_hpp */
|
||||
|
32
Analyser/Static/Sega/StaticAnalyser.cpp
Normal file
32
Analyser/Static/Sega/StaticAnalyser.cpp
Normal file
@ -0,0 +1,32 @@
|
||||
//
|
||||
// StaticAnalyser.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 20/09/2018.
|
||||
// Copyright © 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "StaticAnalyser.hpp"
|
||||
|
||||
#include "Target.hpp"
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::Sega::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
|
||||
TargetList targets;
|
||||
std::unique_ptr<Target> target(new Target);
|
||||
|
||||
target->machine = Machine::MasterSystem;
|
||||
|
||||
// Files named .sg are treated as for the SG1000; otherwise assume a Master System.
|
||||
if(file_name.size() >= 2 && *(file_name.end() - 2) == 's' && *(file_name.end() - 1) == 'g') {
|
||||
target->model = Target::Model::SG1000;
|
||||
} else {
|
||||
target->model = Target::Model::MasterSystem;
|
||||
}
|
||||
|
||||
target->media.cartridges = media.cartridges;
|
||||
|
||||
if(!target->media.empty())
|
||||
targets.push_back(std::move(target));
|
||||
|
||||
return targets;
|
||||
}
|
26
Analyser/Static/Sega/StaticAnalyser.hpp
Normal file
26
Analyser/Static/Sega/StaticAnalyser.hpp
Normal file
@ -0,0 +1,26 @@
|
||||
//
|
||||
// StaticAnalyser.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 20/09/2018.
|
||||
// Copyright © 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef StaticAnalyser_Sega_StaticAnalyser_hpp
|
||||
#define StaticAnalyser_Sega_StaticAnalyser_hpp
|
||||
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Sega {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* StaticAnalyser_hpp */
|
29
Analyser/Static/Sega/Target.hpp
Normal file
29
Analyser/Static/Sega/Target.hpp
Normal file
@ -0,0 +1,29 @@
|
||||
//
|
||||
// Target.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 23/09/2018.
|
||||
// Copyright © 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Analyser_Static_Sega_Target_h
|
||||
#define Analyser_Static_Sega_Target_h
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Sega {
|
||||
|
||||
struct Target: public ::Analyser::Static::Target {
|
||||
enum class Model {
|
||||
MasterSystem,
|
||||
SG1000
|
||||
};
|
||||
|
||||
Model model = Model::MasterSystem;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Analyser_Static_Sega_Target_h */
|
@ -23,6 +23,7 @@
|
||||
#include "DiskII/StaticAnalyser.hpp"
|
||||
#include "MSX/StaticAnalyser.hpp"
|
||||
#include "Oric/StaticAnalyser.hpp"
|
||||
#include "Sega/StaticAnalyser.hpp"
|
||||
#include "ZX8081/StaticAnalyser.hpp"
|
||||
|
||||
// Cartridges
|
||||
@ -129,6 +130,8 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
|
||||
result.cartridges,
|
||||
Cartridge::BinaryDump,
|
||||
TargetPlatform::AcornElectron | TargetPlatform::ColecoVision | TargetPlatform::MSX) // ROM
|
||||
Format("sg", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Sega) // SG
|
||||
Format("sms", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Sega) // SMS
|
||||
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)
|
||||
@ -170,6 +173,7 @@ TargetList Analyser::Static::GetTargets(const std::string &file_name) {
|
||||
if(potential_platforms & TargetPlatform::ColecoVision) Append(Coleco);
|
||||
if(potential_platforms & TargetPlatform::Commodore) Append(Commodore);
|
||||
if(potential_platforms & TargetPlatform::DiskII) Append(DiskII);
|
||||
if(potential_platforms & TargetPlatform::Sega) Append(Sega);
|
||||
if(potential_platforms & TargetPlatform::MSX) Append(MSX);
|
||||
if(potential_platforms & TargetPlatform::Oric) Append(Oric);
|
||||
if(potential_platforms & TargetPlatform::ZX8081) Append(ZX8081);
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -17,6 +17,7 @@
|
||||
#include <cstdint>
|
||||
|
||||
namespace TI {
|
||||
namespace TMS {
|
||||
|
||||
/*!
|
||||
Provides emulation of the TMS9918a, TMS9928 and TMS9929. Likely in the future to be the
|
||||
@ -29,12 +30,8 @@ namespace TI {
|
||||
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.
|
||||
*/
|
||||
class TMS9918: public TMS9918Base {
|
||||
class TMS9918: public Base {
|
||||
public:
|
||||
enum Personality {
|
||||
TMS9918A, // includes the 9928 and 9929; set TV standard and output device as desired.
|
||||
};
|
||||
|
||||
/*!
|
||||
Constructs an instance of the drive controller that behaves according to personality @c p.
|
||||
@param p The type of controller to emulate.
|
||||
@ -66,6 +63,13 @@ class TMS9918: public TMS9918Base {
|
||||
/*! Gets a register value. */
|
||||
uint8_t get_register(int address);
|
||||
|
||||
/*! Gets the current scan line; provided by the Master System only. */
|
||||
uint8_t get_current_line();
|
||||
|
||||
uint8_t get_latched_horizontal_counter();
|
||||
|
||||
void latch_horizontal_counter();
|
||||
|
||||
/*!
|
||||
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.
|
||||
@ -81,6 +85,7 @@ class TMS9918: public TMS9918Base {
|
||||
bool get_interrupt_line();
|
||||
};
|
||||
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* TMS9918_hpp */
|
||||
|
@ -12,90 +12,780 @@
|
||||
#include "../../../Outputs/CRT/CRT.hpp"
|
||||
#include "../../../ClockReceiver/ClockReceiver.hpp"
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
|
||||
namespace TI {
|
||||
namespace TMS {
|
||||
|
||||
enum Personality {
|
||||
TMS9918A, // includes the 9928 and 9929; set TV standard and output device as desired.
|
||||
V9938,
|
||||
V9958,
|
||||
SMSVDP,
|
||||
GGVDP,
|
||||
};
|
||||
|
||||
#define is_sega_vdp(x) x >= SMSVDP
|
||||
|
||||
class Base {
|
||||
public:
|
||||
static 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;
|
||||
}
|
||||
|
||||
class TMS9918Base {
|
||||
protected:
|
||||
TMS9918Base();
|
||||
// The default TMS palette.
|
||||
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)
|
||||
};
|
||||
|
||||
Base(Personality p);
|
||||
|
||||
Personality personality_;
|
||||
std::unique_ptr<Outputs::CRT::CRT> crt_;
|
||||
|
||||
uint8_t ram_[16384];
|
||||
// Holds the contents of this VDP's connected DRAM.
|
||||
std::vector<uint8_t> ram_;
|
||||
|
||||
// Holds the state of the DRAM/CRAM-access mechanism.
|
||||
uint16_t ram_pointer_ = 0;
|
||||
uint8_t read_ahead_buffer_ = 0;
|
||||
enum class MemoryAccess {
|
||||
Read, Write, None
|
||||
} queued_access_ = MemoryAccess::None;
|
||||
|
||||
// Holds the main status register.
|
||||
uint8_t status_ = 0;
|
||||
|
||||
bool write_phase_ = false;
|
||||
uint8_t low_write_ = 0;
|
||||
// Current state of programmer input.
|
||||
bool write_phase_ = false; // Determines whether the VDP is expecting the low or high byte of a write.
|
||||
uint8_t low_write_ = 0; // Buffers the low byte of a write.
|
||||
|
||||
// The various register flags.
|
||||
int next_screen_mode_ = 0, screen_mode_ = 0;
|
||||
bool next_blank_screen_ = true, blank_screen_ = true;
|
||||
// Various programmable flags.
|
||||
bool mode1_enable_ = false;
|
||||
bool mode2_enable_ = false;
|
||||
bool mode3_enable_ = false;
|
||||
bool blank_display_ = false;
|
||||
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;
|
||||
|
||||
size_t pattern_name_address_ = 0; // i.e. address of the tile map.
|
||||
size_t colour_table_address_ = 0; // address of the colour map (if applicable).
|
||||
size_t pattern_generator_table_address_ = 0; // address of the tile contents.
|
||||
size_t sprite_attribute_table_address_ = 0; // address of the sprite list.
|
||||
size_t sprite_generator_table_address_ = 0; // address of the sprite contents.
|
||||
|
||||
uint8_t text_colour_ = 0;
|
||||
uint8_t background_colour_ = 0;
|
||||
|
||||
HalfCycles half_cycles_into_frame_;
|
||||
int column_ = 0, row_ = 0, output_column_ = 0;
|
||||
// This implementation of this chip officially accepts a 3.58Mhz clock, but runs
|
||||
// internally at 5.37Mhz. The following two help to maintain a lossless conversion
|
||||
// from the one to the other.
|
||||
int cycles_error_ = 0;
|
||||
uint32_t *pixel_target_ = nullptr, *pixel_base_ = nullptr;
|
||||
HalfCycles half_cycles_before_internal_cycles(int internal_cycles);
|
||||
|
||||
// Internal mechanisms for position tracking.
|
||||
int latched_column_ = 0;
|
||||
|
||||
// A helper function to output the current border colour for
|
||||
// the number of cycles supplied.
|
||||
void output_border(int cycles);
|
||||
|
||||
// Vertical timing details.
|
||||
int frame_lines_ = 262;
|
||||
int first_vsync_line_ = 227;
|
||||
// A struct to contain timing information for the current mode.
|
||||
struct {
|
||||
/*
|
||||
Vertical layout:
|
||||
|
||||
Lines 0 to [pixel_lines]: standard data fetch and drawing will occur.
|
||||
... to [first_vsync_line]: refresh fetches will occur and border will be output.
|
||||
.. to [2.5 or 3 lines later]: vertical sync is output.
|
||||
... to [total lines - 1]: refresh fetches will occur and border will be output.
|
||||
... for one line: standard data fetch will occur, without drawing.
|
||||
*/
|
||||
int total_lines = 262;
|
||||
int pixel_lines = 192;
|
||||
int first_vsync_line = 227;
|
||||
|
||||
// Maximum number of sprite slots to populate;
|
||||
// if sprites beyond this number should be visible
|
||||
// then the appropriate status information will be set.
|
||||
int maximum_visible_sprites = 4;
|
||||
|
||||
// Set the position, in cycles, of the two interrupts,
|
||||
// within a line.
|
||||
struct {
|
||||
int column = 4;
|
||||
int row = 193;
|
||||
} end_of_frame_interrupt_position;
|
||||
int line_interrupt_position = -1;
|
||||
|
||||
// Enables or disabled the recognition of 0xd0 as a sprite
|
||||
// list terminator.
|
||||
bool allow_sprite_terminator = true;
|
||||
} mode_timing_;
|
||||
|
||||
uint8_t line_interrupt_target = 0xff;
|
||||
uint8_t line_interrupt_counter = 0;
|
||||
bool enable_line_interrupts_ = false;
|
||||
bool line_interrupt_pending_ = false;
|
||||
|
||||
// The screen mode is a necessary predecessor to picking the line mode,
|
||||
// which is the thing latched per line.
|
||||
enum class ScreenMode {
|
||||
Blank,
|
||||
Text,
|
||||
MultiColour,
|
||||
ColouredText,
|
||||
Graphics,
|
||||
SMSMode4
|
||||
} screen_mode_;
|
||||
|
||||
// Horizontal selections.
|
||||
enum class LineMode {
|
||||
Text = 0,
|
||||
Character = 1,
|
||||
Refresh = 2
|
||||
} line_mode_ = LineMode::Text;
|
||||
int first_pixel_column_, first_right_border_column_;
|
||||
Text,
|
||||
Character,
|
||||
Refresh,
|
||||
SMS
|
||||
};
|
||||
|
||||
uint8_t pattern_names_[40];
|
||||
uint8_t pattern_buffer_[40];
|
||||
uint8_t colour_buffer_[40];
|
||||
// Temporary buffers collect a representation of this line prior to pixel serialisation.
|
||||
struct LineBuffer {
|
||||
// The line mode describes the proper timing diagram for this line.
|
||||
LineMode line_mode = LineMode::Text;
|
||||
|
||||
struct SpriteSet {
|
||||
// Holds the horizontal scroll position to apply to this line;
|
||||
// of those VDPs currently implemented, affects the Master System only.
|
||||
uint8_t latched_horizontal_scroll = 0;
|
||||
|
||||
// The names array holds pattern names, as an offset into memory, and
|
||||
// potentially flags also.
|
||||
struct {
|
||||
size_t offset;
|
||||
uint8_t flags;
|
||||
} names[40];
|
||||
|
||||
// The patterns array holds tile patterns, corresponding 1:1 with names.
|
||||
// Four bytes per pattern is the maximum required by any
|
||||
// currently-implemented VDP.
|
||||
uint8_t patterns[40][4];
|
||||
|
||||
/*
|
||||
Horizontal layout (on a 342-cycle clock):
|
||||
|
||||
15 cycles right border
|
||||
58 cycles blanking & sync
|
||||
13 cycles left border
|
||||
|
||||
... i.e. to cycle 86, then:
|
||||
|
||||
border up to first_pixel_output_column;
|
||||
pixels up to next_border_column;
|
||||
border up to the end.
|
||||
|
||||
e.g. standard 256-pixel modes will want to set
|
||||
first_pixel_output_column = 86, next_border_column = 342.
|
||||
*/
|
||||
int first_pixel_output_column = 94;
|
||||
int next_border_column = 334;
|
||||
|
||||
// An active sprite is one that has been selected for composition onto
|
||||
// this line.
|
||||
struct ActiveSprite {
|
||||
int index = 0;
|
||||
int row = 0;
|
||||
int index = 0; // The original in-table index of this sprite.
|
||||
int row = 0; // The row of the sprite that should be drawn.
|
||||
int x = 0; // The sprite's x position on screen.
|
||||
|
||||
uint8_t info[4];
|
||||
uint8_t image[2];
|
||||
uint8_t image[4]; // Up to four bytes of image information.
|
||||
int shift_position = 0; // An offset representing how much of the image information has already been drawn.
|
||||
} active_sprites[8];
|
||||
|
||||
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 active_sprite_slot = 0; // A pointer to the slot into which a new active sprite will be deposited, if required.
|
||||
bool sprites_stopped = false; // A special TMS feature is that a sentinel value can be used to prevent any further sprites
|
||||
// being evaluated for display. This flag determines whether the sentinel has yet been reached.
|
||||
|
||||
int access_pointer_ = 0;
|
||||
void reset_sprite_collection();
|
||||
} line_buffers_[313];
|
||||
void posit_sprite(LineBuffer &buffer, int sprite_number, int sprite_y, int screen_row);
|
||||
|
||||
inline void test_sprite(int sprite_number, int screen_row);
|
||||
inline void get_sprite_contents(int start, int cycles, int screen_row);
|
||||
// There is a delay between reading into the line buffer and outputting from there to the screen. That delay
|
||||
// is observeable because reading time affects availability of memory accesses and therefore time in which
|
||||
// to update sprites and tiles, but writing time affects when the palette is used and when the collision flag
|
||||
// may end up being set. So the two processes are slightly decoupled. The end of reading one line may overlap
|
||||
// with the beginning of writing the next, hence the two separate line buffers.
|
||||
struct LineBufferPointer {
|
||||
int row, column;
|
||||
} read_pointer_, write_pointer_;
|
||||
|
||||
// Extra information that affects the Master System output mode.
|
||||
struct {
|
||||
// Programmer-set flags.
|
||||
bool vertical_scroll_lock = false;
|
||||
bool horizontal_scroll_lock = false;
|
||||
bool hide_left_column = false;
|
||||
bool shift_sprites_8px_left = false;
|
||||
bool mode4_enable = false;
|
||||
uint8_t horizontal_scroll = 0;
|
||||
uint8_t vertical_scroll = 0;
|
||||
|
||||
// The Master System's additional colour RAM.
|
||||
uint32_t colour_ram[32];
|
||||
bool cram_is_selected = false;
|
||||
|
||||
// Holds the vertical scroll position for this frame; this is latched
|
||||
// once and cannot dynamically be changed until the next frame.
|
||||
uint8_t latched_vertical_scroll = 0;
|
||||
} master_system_;
|
||||
|
||||
void set_current_screen_mode() {
|
||||
if(blank_display_) {
|
||||
screen_mode_ = ScreenMode::Blank;
|
||||
return;
|
||||
}
|
||||
|
||||
if(is_sega_vdp(personality_) && master_system_.mode4_enable) {
|
||||
screen_mode_ = ScreenMode::SMSMode4;
|
||||
mode_timing_.pixel_lines = 192;
|
||||
if(mode2_enable_ && mode1_enable_) mode_timing_.pixel_lines = 224;
|
||||
if(mode2_enable_ && mode3_enable_) mode_timing_.pixel_lines = 240;
|
||||
mode_timing_.maximum_visible_sprites = 8;
|
||||
return;
|
||||
}
|
||||
|
||||
mode_timing_.maximum_visible_sprites = 4;
|
||||
if(!mode1_enable_ && !mode2_enable_ && !mode3_enable_) {
|
||||
screen_mode_ = ScreenMode::ColouredText;
|
||||
return;
|
||||
}
|
||||
|
||||
if(mode1_enable_ && !mode2_enable_ && !mode3_enable_) {
|
||||
screen_mode_ = ScreenMode::Text;
|
||||
return;
|
||||
}
|
||||
|
||||
if(!mode1_enable_ && mode2_enable_ && !mode3_enable_) {
|
||||
screen_mode_ = ScreenMode::Graphics;
|
||||
return;
|
||||
}
|
||||
|
||||
if(!mode1_enable_ && !mode2_enable_ && mode3_enable_) {
|
||||
screen_mode_ = ScreenMode::MultiColour;
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: undocumented TMS modes.
|
||||
screen_mode_ = ScreenMode::Blank;
|
||||
}
|
||||
|
||||
void do_external_slot() {
|
||||
switch(queued_access_) {
|
||||
default: return;
|
||||
|
||||
case MemoryAccess::Write:
|
||||
if(master_system_.cram_is_selected) {
|
||||
master_system_.colour_ram[ram_pointer_ & 0x1f] = palette_pack(
|
||||
static_cast<uint8_t>(((read_ahead_buffer_ >> 0) & 3) * 255 / 3),
|
||||
static_cast<uint8_t>(((read_ahead_buffer_ >> 2) & 3) * 255 / 3),
|
||||
static_cast<uint8_t>(((read_ahead_buffer_ >> 4) & 3) * 255 / 3)
|
||||
);
|
||||
} else {
|
||||
ram_[ram_pointer_ & 16383] = read_ahead_buffer_;
|
||||
}
|
||||
break;
|
||||
case MemoryAccess::Read:
|
||||
read_ahead_buffer_ = ram_[ram_pointer_ & 16383];
|
||||
break;
|
||||
}
|
||||
++ram_pointer_;
|
||||
queued_access_ = MemoryAccess::None;
|
||||
}
|
||||
|
||||
/*
|
||||
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;\
|
||||
case n
|
||||
|
||||
#define external_slot(n) \
|
||||
slot(n): do_external_slot();
|
||||
|
||||
#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);
|
||||
|
||||
|
||||
/***********************************************
|
||||
TMS9918 Fetching Code
|
||||
************************************************/
|
||||
|
||||
template<bool use_end> void 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<bool use_end> void 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 | static_cast<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<bool use_end> void 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();
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
/***********************************************
|
||||
Master System Fetching Code
|
||||
************************************************/
|
||||
|
||||
template<bool use_end> void fetch_sms(int start, int end) {
|
||||
#define sprite_fetch(sprite) {\
|
||||
line_buffer.active_sprites[sprite].x = \
|
||||
ram_[\
|
||||
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_[\
|
||||
sprite_attribute_table_address_ & size_t(0x3f81 | (line_buffer.active_sprites[sprite].index << 1))\
|
||||
] & (sprites_16x16_ ? ~1 : ~0);\
|
||||
const size_t graphic_location = 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_[sprite_attribute_table_address_ & (sprite | 0x3f00)], write_pointer_.row); \
|
||||
posit_sprite(sprite_selection_buffer, sprite+1, ram_[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 = static_cast<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;
|
||||
|
||||
// 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) % 224;
|
||||
struct RowInfo {
|
||||
size_t pattern_address_base;
|
||||
size_t sub_row[2];
|
||||
};
|
||||
const RowInfo scrolled_row_info = {
|
||||
pattern_name_address_ & static_cast<size_t>(((scrolled_row & ~7) << 3) | 0x3800),
|
||||
{static_cast<size_t>((scrolled_row & 7) << 2), 28 ^ static_cast<size_t>((scrolled_row & 7) << 2)}
|
||||
};
|
||||
RowInfo row_info;
|
||||
if(master_system_.vertical_scroll_lock) {
|
||||
row_info.pattern_address_base = pattern_name_address_ & static_cast<size_t>(((write_pointer_.row & ~7) << 3) | 0x3800);
|
||||
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();
|
||||
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
|
||||
}
|
||||
|
||||
#undef external_slot
|
||||
#undef slot
|
||||
|
||||
uint32_t *pixel_target_ = nullptr, *pixel_origin_ = nullptr;
|
||||
void draw_tms_character(int start, int end);
|
||||
void draw_tms_text(int start, int end);
|
||||
void draw_sms(int start, int end);
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* TMS9918Base_hpp */
|
||||
|
@ -100,7 +100,7 @@ class Joystick: public Inputs::ConcreteJoystick {
|
||||
|
||||
private:
|
||||
uint8_t direction_ = 0xff;
|
||||
uint8_t keypad_ = 0xff;
|
||||
uint8_t keypad_ = 0x7f;
|
||||
};
|
||||
|
||||
class ConcreteMachine:
|
||||
@ -141,10 +141,19 @@ class ConcreteMachine:
|
||||
cartridge_address_limit_ = static_cast<uint16_t>(0x8000 + cartridge_.size() - 1);
|
||||
|
||||
if(cartridge_.size() > 32768) {
|
||||
// Ensure the cartrige is a multiple of 16kb in size, as that won't
|
||||
// be checked when paging.
|
||||
const size_t extension = (16384 - cartridge_.size() & 16383) % 16384;
|
||||
cartridge_.resize(cartridge_.size() + extension);
|
||||
|
||||
cartridge_pages_[0] = &cartridge_[cartridge_.size() - 16384];
|
||||
cartridge_pages_[1] = cartridge_.data();
|
||||
is_megacart_ = true;
|
||||
} else {
|
||||
// Ensure at least 32kb is allocated to the cartrige so that
|
||||
// reads are never out of bounds.
|
||||
cartridge_.resize(32768);
|
||||
|
||||
cartridge_pages_[0] = cartridge_.data();
|
||||
cartridge_pages_[1] = cartridge_.data() + 16384;
|
||||
is_megacart_ = false;
|
||||
@ -161,7 +170,7 @@ class ConcreteMachine:
|
||||
}
|
||||
|
||||
void setup_output(float aspect_ratio) override {
|
||||
vdp_.reset(new TI::TMS9918(TI::TMS9918::TMS9918A));
|
||||
vdp_.reset(new TI::TMS::TMS9918(TI::TMS::TMS9918A));
|
||||
get_crt()->set_video_signal(Outputs::CRT::VideoSignal::Composite);
|
||||
}
|
||||
|
||||
@ -197,131 +206,134 @@ class ConcreteMachine:
|
||||
time_since_vdp_update_ += length;
|
||||
time_since_sn76489_update_ += length;
|
||||
|
||||
uint16_t address = cycle.address ? *cycle.address : 0x0000;
|
||||
switch(cycle.operation) {
|
||||
case CPU::Z80::PartialMachineCycle::ReadOpcode:
|
||||
if(!address) pc_zero_accesses_++;
|
||||
case CPU::Z80::PartialMachineCycle::Read:
|
||||
if(address < 0x2000) {
|
||||
if(super_game_module_.replace_bios) {
|
||||
// Act only if necessary.
|
||||
if(cycle.is_terminal()) {
|
||||
uint16_t address = cycle.address ? *cycle.address : 0x0000;
|
||||
switch(cycle.operation) {
|
||||
case CPU::Z80::PartialMachineCycle::ReadOpcode:
|
||||
if(!address) pc_zero_accesses_++;
|
||||
case CPU::Z80::PartialMachineCycle::Read:
|
||||
if(address < 0x2000) {
|
||||
if(super_game_module_.replace_bios) {
|
||||
*cycle.value = super_game_module_.ram[address];
|
||||
} else {
|
||||
*cycle.value = bios_[address];
|
||||
}
|
||||
} else if(super_game_module_.replace_ram && address < 0x8000) {
|
||||
*cycle.value = super_game_module_.ram[address];
|
||||
} else if(address >= 0x6000 && address < 0x8000) {
|
||||
*cycle.value = ram_[address & 1023];
|
||||
} else if(address >= 0x8000 && address <= cartridge_address_limit_) {
|
||||
if(is_megacart_ && address >= 0xffc0) {
|
||||
page_megacart(address);
|
||||
}
|
||||
*cycle.value = cartridge_pages_[(address >> 14)&1][address&0x3fff];
|
||||
} else {
|
||||
*cycle.value = bios_[address];
|
||||
*cycle.value = 0xff;
|
||||
}
|
||||
} else if(super_game_module_.replace_ram && address < 0x8000) {
|
||||
*cycle.value = super_game_module_.ram[address];
|
||||
} else if(address >= 0x6000 && address < 0x8000) {
|
||||
*cycle.value = ram_[address & 1023];
|
||||
} else if(address >= 0x8000 && address <= cartridge_address_limit_) {
|
||||
if(is_megacart_ && address >= 0xffc0) {
|
||||
break;
|
||||
|
||||
case CPU::Z80::PartialMachineCycle::Write:
|
||||
if(super_game_module_.replace_bios && address < 0x2000) {
|
||||
super_game_module_.ram[address] = *cycle.value;
|
||||
} else if(super_game_module_.replace_ram && address >= 0x2000 && address < 0x8000) {
|
||||
super_game_module_.ram[address] = *cycle.value;
|
||||
} else if(address >= 0x6000 && address < 0x8000) {
|
||||
ram_[address & 1023] = *cycle.value;
|
||||
} else if(is_megacart_ && address >= 0xffc0) {
|
||||
page_megacart(address);
|
||||
}
|
||||
*cycle.value = cartridge_pages_[(address >> 14)&1][address&0x3fff];
|
||||
} else {
|
||||
*cycle.value = 0xff;
|
||||
}
|
||||
break;
|
||||
break;
|
||||
|
||||
case CPU::Z80::PartialMachineCycle::Write:
|
||||
if(super_game_module_.replace_bios && address < 0x2000) {
|
||||
super_game_module_.ram[address] = *cycle.value;
|
||||
} else if(super_game_module_.replace_ram && address >= 0x2000 && address < 0x8000) {
|
||||
super_game_module_.ram[address] = *cycle.value;
|
||||
} else if(address >= 0x6000 && address < 0x8000) {
|
||||
ram_[address & 1023] = *cycle.value;
|
||||
} else if(is_megacart_ && address >= 0xffc0) {
|
||||
page_megacart(address);
|
||||
}
|
||||
break;
|
||||
case CPU::Z80::PartialMachineCycle::Input:
|
||||
switch((address >> 5) & 7) {
|
||||
case 5:
|
||||
update_video();
|
||||
*cycle.value = vdp_->get_register(address);
|
||||
z80_.set_non_maskable_interrupt_line(vdp_->get_interrupt_line());
|
||||
time_until_interrupt_ = vdp_->get_time_until_interrupt();
|
||||
break;
|
||||
|
||||
case CPU::Z80::PartialMachineCycle::Input:
|
||||
switch((address >> 5) & 7) {
|
||||
case 5:
|
||||
update_video();
|
||||
*cycle.value = vdp_->get_register(address);
|
||||
z80_.set_non_maskable_interrupt_line(vdp_->get_interrupt_line());
|
||||
time_until_interrupt_ = vdp_->get_time_until_interrupt();
|
||||
break;
|
||||
case 7: {
|
||||
const std::size_t joystick_id = (address&2) >> 1;
|
||||
Joystick *joystick = static_cast<Joystick *>(joysticks_[joystick_id].get());
|
||||
if(joysticks_in_keypad_mode_) {
|
||||
*cycle.value = joystick->get_keypad_input();
|
||||
} else {
|
||||
*cycle.value = joystick->get_direction_input();
|
||||
}
|
||||
|
||||
case 7: {
|
||||
const std::size_t joystick_id = (address&2) >> 1;
|
||||
Joystick *joystick = static_cast<Joystick *>(joysticks_[joystick_id].get());
|
||||
if(joysticks_in_keypad_mode_) {
|
||||
*cycle.value = joystick->get_keypad_input();
|
||||
} else {
|
||||
*cycle.value = joystick->get_direction_input();
|
||||
}
|
||||
// Hitting exactly the recommended joypad input port is an indicator that
|
||||
// this really is a ColecoVision game. The BIOS won't do this when just waiting
|
||||
// to start a game (unlike accessing the VDP and SN).
|
||||
if((address&0xfc) == 0xfc) confidence_counter_.add_hit();
|
||||
} break;
|
||||
|
||||
// Hitting exactly the recommended joypad input port is an indicator that
|
||||
// this really is a ColecoVision game. The BIOS won't do this when just waiting
|
||||
// to start a game (unlike accessing the VDP and SN).
|
||||
if((address&0xfc) == 0xfc) confidence_counter_.add_hit();
|
||||
} break;
|
||||
default:
|
||||
switch(address&0xff) {
|
||||
default: *cycle.value = 0xff; break;
|
||||
case 0x52:
|
||||
// Read AY data.
|
||||
update_audio();
|
||||
ay_.set_control_lines(GI::AY38910::ControlLines(GI::AY38910::BC2 | GI::AY38910::BC1));
|
||||
*cycle.value = ay_.get_data_output();
|
||||
ay_.set_control_lines(GI::AY38910::ControlLines(0));
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
switch(address&0xff) {
|
||||
default: *cycle.value = 0xff; break;
|
||||
case 0x52:
|
||||
// Read AY data.
|
||||
update_audio();
|
||||
ay_.set_control_lines(GI::AY38910::ControlLines(GI::AY38910::BC2 | GI::AY38910::BC1));
|
||||
*cycle.value = ay_.get_data_output();
|
||||
ay_.set_control_lines(GI::AY38910::ControlLines(0));
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case CPU::Z80::PartialMachineCycle::Output: {
|
||||
const int eighth = (address >> 5) & 7;
|
||||
switch(eighth) {
|
||||
case 4: case 6:
|
||||
joysticks_in_keypad_mode_ = eighth == 4;
|
||||
break;
|
||||
|
||||
case CPU::Z80::PartialMachineCycle::Output: {
|
||||
const int eighth = (address >> 5) & 7;
|
||||
switch(eighth) {
|
||||
case 4: case 6:
|
||||
joysticks_in_keypad_mode_ = eighth == 4;
|
||||
break;
|
||||
case 5:
|
||||
update_video();
|
||||
vdp_->set_register(address, *cycle.value);
|
||||
z80_.set_non_maskable_interrupt_line(vdp_->get_interrupt_line());
|
||||
time_until_interrupt_ = vdp_->get_time_until_interrupt();
|
||||
break;
|
||||
|
||||
case 5:
|
||||
update_video();
|
||||
vdp_->set_register(address, *cycle.value);
|
||||
z80_.set_non_maskable_interrupt_line(vdp_->get_interrupt_line());
|
||||
time_until_interrupt_ = vdp_->get_time_until_interrupt();
|
||||
break;
|
||||
case 7:
|
||||
update_audio();
|
||||
sn76489_.set_register(*cycle.value);
|
||||
break;
|
||||
|
||||
case 7:
|
||||
update_audio();
|
||||
sn76489_.set_register(*cycle.value);
|
||||
break;
|
||||
default:
|
||||
// Catch Super Game Module accesses; it decodes more thoroughly.
|
||||
switch(address&0xff) {
|
||||
default: break;
|
||||
case 0x7f:
|
||||
super_game_module_.replace_bios = !((*cycle.value)&0x2);
|
||||
break;
|
||||
case 0x50:
|
||||
// Set AY address.
|
||||
update_audio();
|
||||
ay_.set_control_lines(GI::AY38910::BC1);
|
||||
ay_.set_data_input(*cycle.value);
|
||||
ay_.set_control_lines(GI::AY38910::ControlLines(0));
|
||||
break;
|
||||
case 0x51:
|
||||
// Set AY data.
|
||||
update_audio();
|
||||
ay_.set_control_lines(GI::AY38910::ControlLines(GI::AY38910::BC2 | GI::AY38910::BDIR));
|
||||
ay_.set_data_input(*cycle.value);
|
||||
ay_.set_control_lines(GI::AY38910::ControlLines(0));
|
||||
break;
|
||||
case 0x53:
|
||||
super_game_module_.replace_ram = !!((*cycle.value)&0x1);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
} break;
|
||||
|
||||
default:
|
||||
// Catch Super Game Module accesses; it decodes more thoroughly.
|
||||
switch(address&0xff) {
|
||||
default: break;
|
||||
case 0x7f:
|
||||
super_game_module_.replace_bios = !((*cycle.value)&0x2);
|
||||
break;
|
||||
case 0x50:
|
||||
// Set AY address.
|
||||
update_audio();
|
||||
ay_.set_control_lines(GI::AY38910::BC1);
|
||||
ay_.set_data_input(*cycle.value);
|
||||
ay_.set_control_lines(GI::AY38910::ControlLines(0));
|
||||
break;
|
||||
case 0x51:
|
||||
// Set AY data.
|
||||
update_audio();
|
||||
ay_.set_control_lines(GI::AY38910::ControlLines(GI::AY38910::BC2 | GI::AY38910::BDIR));
|
||||
ay_.set_data_input(*cycle.value);
|
||||
ay_.set_control_lines(GI::AY38910::ControlLines(0));
|
||||
break;
|
||||
case 0x53:
|
||||
super_game_module_.replace_ram = !!((*cycle.value)&0x1);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
} break;
|
||||
|
||||
default: break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
if(time_until_interrupt_ > 0) {
|
||||
@ -358,7 +370,7 @@ class ConcreteMachine:
|
||||
}
|
||||
|
||||
CPU::Z80::Processor<ConcreteMachine, false, false> z80_;
|
||||
std::unique_ptr<TI::TMS9918> vdp_;
|
||||
std::unique_ptr<TI::TMS::TMS9918> vdp_;
|
||||
|
||||
Concurrency::DeferringAsyncTaskQueue audio_queue_;
|
||||
TI::SN76489 sn76489_;
|
||||
|
@ -219,7 +219,7 @@ class ConcreteMachine:
|
||||
}
|
||||
|
||||
void setup_output(float aspect_ratio) override {
|
||||
vdp_.reset(new TI::TMS9918(TI::TMS9918::TMS9918A));
|
||||
vdp_.reset(new TI::TMS::TMS9918(TI::TMS::TMS9918A));
|
||||
}
|
||||
|
||||
void close_output() override {
|
||||
@ -361,183 +361,185 @@ class ConcreteMachine:
|
||||
memory_slots_[2].cycles_since_update += total_length;
|
||||
memory_slots_[3].cycles_since_update += total_length;
|
||||
|
||||
uint16_t address = cycle.address ? *cycle.address : 0x0000;
|
||||
switch(cycle.operation) {
|
||||
case CPU::Z80::PartialMachineCycle::ReadOpcode:
|
||||
if(use_fast_tape_) {
|
||||
if(address == 0x1a63) {
|
||||
// TAPION
|
||||
if(cycle.is_terminal()) {
|
||||
uint16_t address = cycle.address ? *cycle.address : 0x0000;
|
||||
switch(cycle.operation) {
|
||||
case CPU::Z80::PartialMachineCycle::ReadOpcode:
|
||||
if(use_fast_tape_) {
|
||||
if(address == 0x1a63) {
|
||||
// TAPION
|
||||
|
||||
// Enable the tape motor.
|
||||
i8255_.set_register(0xab, 0x8);
|
||||
// Enable the tape motor.
|
||||
i8255_.set_register(0xab, 0x8);
|
||||
|
||||
// Disable interrupts.
|
||||
z80_.set_value_of_register(CPU::Z80::Register::IFF1, 0);
|
||||
z80_.set_value_of_register(CPU::Z80::Register::IFF2, 0);
|
||||
// Disable interrupts.
|
||||
z80_.set_value_of_register(CPU::Z80::Register::IFF1, 0);
|
||||
z80_.set_value_of_register(CPU::Z80::Register::IFF2, 0);
|
||||
|
||||
// Use the parser to find a header, and if one is found then populate
|
||||
// LOWLIM and WINWID, and reset carry. Otherwise set carry.
|
||||
using Parser = Storage::Tape::MSX::Parser;
|
||||
std::unique_ptr<Parser::FileSpeed> new_speed = Parser::find_header(tape_player_);
|
||||
if(new_speed) {
|
||||
ram_[0xfca4] = new_speed->minimum_start_bit_duration;
|
||||
ram_[0xfca5] = new_speed->low_high_disrimination_duration;
|
||||
z80_.set_value_of_register(CPU::Z80::Register::Flags, 0);
|
||||
} else {
|
||||
z80_.set_value_of_register(CPU::Z80::Register::Flags, 1);
|
||||
// Use the parser to find a header, and if one is found then populate
|
||||
// LOWLIM and WINWID, and reset carry. Otherwise set carry.
|
||||
using Parser = Storage::Tape::MSX::Parser;
|
||||
std::unique_ptr<Parser::FileSpeed> new_speed = Parser::find_header(tape_player_);
|
||||
if(new_speed) {
|
||||
ram_[0xfca4] = new_speed->minimum_start_bit_duration;
|
||||
ram_[0xfca5] = new_speed->low_high_disrimination_duration;
|
||||
z80_.set_value_of_register(CPU::Z80::Register::Flags, 0);
|
||||
} else {
|
||||
z80_.set_value_of_register(CPU::Z80::Register::Flags, 1);
|
||||
}
|
||||
|
||||
// RET.
|
||||
*cycle.value = 0xc9;
|
||||
break;
|
||||
}
|
||||
|
||||
// RET.
|
||||
*cycle.value = 0xc9;
|
||||
break;
|
||||
}
|
||||
if(address == 0x1abc) {
|
||||
// TAPIN
|
||||
|
||||
if(address == 0x1abc) {
|
||||
// TAPIN
|
||||
// Grab the current values of LOWLIM and WINWID.
|
||||
using Parser = Storage::Tape::MSX::Parser;
|
||||
Parser::FileSpeed tape_speed;
|
||||
tape_speed.minimum_start_bit_duration = ram_[0xfca4];
|
||||
tape_speed.low_high_disrimination_duration = ram_[0xfca5];
|
||||
|
||||
// Grab the current values of LOWLIM and WINWID.
|
||||
using Parser = Storage::Tape::MSX::Parser;
|
||||
Parser::FileSpeed tape_speed;
|
||||
tape_speed.minimum_start_bit_duration = ram_[0xfca4];
|
||||
tape_speed.low_high_disrimination_duration = ram_[0xfca5];
|
||||
// Ask the tape parser to grab a byte.
|
||||
int next_byte = Parser::get_byte(tape_speed, tape_player_);
|
||||
|
||||
// Ask the tape parser to grab a byte.
|
||||
int next_byte = Parser::get_byte(tape_speed, tape_player_);
|
||||
// If a byte was found, return it with carry unset. Otherwise set carry to
|
||||
// indicate error.
|
||||
if(next_byte >= 0) {
|
||||
z80_.set_value_of_register(CPU::Z80::Register::A, static_cast<uint16_t>(next_byte));
|
||||
z80_.set_value_of_register(CPU::Z80::Register::Flags, 0);
|
||||
} else {
|
||||
z80_.set_value_of_register(CPU::Z80::Register::Flags, 1);
|
||||
}
|
||||
|
||||
// If a byte was found, return it with carry unset. Otherwise set carry to
|
||||
// indicate error.
|
||||
if(next_byte >= 0) {
|
||||
z80_.set_value_of_register(CPU::Z80::Register::A, static_cast<uint16_t>(next_byte));
|
||||
z80_.set_value_of_register(CPU::Z80::Register::Flags, 0);
|
||||
} else {
|
||||
z80_.set_value_of_register(CPU::Z80::Register::Flags, 1);
|
||||
// RET.
|
||||
*cycle.value = 0xc9;
|
||||
break;
|
||||
}
|
||||
|
||||
// RET.
|
||||
*cycle.value = 0xc9;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!address) {
|
||||
pc_zero_accesses_++;
|
||||
}
|
||||
if(read_pointers_[address >> 13] == unpopulated_) {
|
||||
performed_unmapped_access_ = true;
|
||||
}
|
||||
pc_address_ = address; // This is retained so as to be able to name the source of an access to cartridge handlers.
|
||||
case CPU::Z80::PartialMachineCycle::Read:
|
||||
if(read_pointers_[address >> 13]) {
|
||||
*cycle.value = read_pointers_[address >> 13][address & 8191];
|
||||
} else {
|
||||
if(!address) {
|
||||
pc_zero_accesses_++;
|
||||
}
|
||||
if(read_pointers_[address >> 13] == unpopulated_) {
|
||||
performed_unmapped_access_ = true;
|
||||
}
|
||||
pc_address_ = address; // This is retained so as to be able to name the source of an access to cartridge handlers.
|
||||
case CPU::Z80::PartialMachineCycle::Read:
|
||||
if(read_pointers_[address >> 13]) {
|
||||
*cycle.value = read_pointers_[address >> 13][address & 8191];
|
||||
} else {
|
||||
int slot_hit = (paged_memory_ >> ((address >> 14) * 2)) & 3;
|
||||
memory_slots_[slot_hit].handler->run_for(memory_slots_[slot_hit].cycles_since_update.flush());
|
||||
*cycle.value = memory_slots_[slot_hit].handler->read(address);
|
||||
}
|
||||
break;
|
||||
|
||||
case CPU::Z80::PartialMachineCycle::Write: {
|
||||
write_pointers_[address >> 13][address & 8191] = *cycle.value;
|
||||
|
||||
int slot_hit = (paged_memory_ >> ((address >> 14) * 2)) & 3;
|
||||
memory_slots_[slot_hit].handler->run_for(memory_slots_[slot_hit].cycles_since_update.flush());
|
||||
*cycle.value = memory_slots_[slot_hit].handler->read(address);
|
||||
}
|
||||
break;
|
||||
|
||||
case CPU::Z80::PartialMachineCycle::Write: {
|
||||
write_pointers_[address >> 13][address & 8191] = *cycle.value;
|
||||
|
||||
int slot_hit = (paged_memory_ >> ((address >> 14) * 2)) & 3;
|
||||
if(memory_slots_[slot_hit].handler) {
|
||||
update_audio();
|
||||
memory_slots_[slot_hit].handler->run_for(memory_slots_[slot_hit].cycles_since_update.flush());
|
||||
memory_slots_[slot_hit].handler->write(address, *cycle.value, read_pointers_[pc_address_ >> 13] != memory_slots_[0].read_pointers[pc_address_ >> 13]);
|
||||
}
|
||||
} 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:
|
||||
if(memory_slots_[slot_hit].handler) {
|
||||
update_audio();
|
||||
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:
|
||||
update_audio();
|
||||
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;
|
||||
|
||||
case CPU::Z80::PartialMachineCycle::Interrupt:
|
||||
*cycle.value = 0xff;
|
||||
|
||||
// Take this as a convenient moment to jump into the keyboard buffer, if desired.
|
||||
if(!input_text_.empty()) {
|
||||
// The following are KEYBUF per the Red Book; its address and its definition as DEFS 40.
|
||||
const int buffer_start = 0xfbf0;
|
||||
const int buffer_size = 40;
|
||||
|
||||
// Also from the Red Book: GETPNT is at F3FAH and PUTPNT is at F3F8H.
|
||||
int read_address = ram_[0xf3fa] | (ram_[0xf3fb] << 8);
|
||||
int write_address = ram_[0xf3f8] | (ram_[0xf3f9] << 8);
|
||||
|
||||
// Write until either the string is exhausted or the write_pointer is immediately
|
||||
// behind the read pointer; temporarily map write_address and read_address into
|
||||
// buffer-relative values.
|
||||
std::size_t characters_written = 0;
|
||||
write_address -= buffer_start;
|
||||
read_address -= buffer_start;
|
||||
while(characters_written < input_text_.size()) {
|
||||
const int next_write_address = (write_address + 1) % buffer_size;
|
||||
if(next_write_address == read_address) break;
|
||||
ram_[write_address + buffer_start] = static_cast<uint8_t>(input_text_[characters_written]);
|
||||
++characters_written;
|
||||
write_address = next_write_address;
|
||||
memory_slots_[slot_hit].handler->run_for(memory_slots_[slot_hit].cycles_since_update.flush());
|
||||
memory_slots_[slot_hit].handler->write(address, *cycle.value, read_pointers_[pc_address_ >> 13] != memory_slots_[0].read_pointers[pc_address_ >> 13]);
|
||||
}
|
||||
input_text_.erase(input_text_.begin(), input_text_.begin() + static_cast<std::string::difference_type>(characters_written));
|
||||
} break;
|
||||
|
||||
// Map the write address back into absolute terms and write it out again as PUTPNT.
|
||||
write_address += buffer_start;
|
||||
ram_[0xf3f8] = static_cast<uint8_t>(write_address);
|
||||
ram_[0xf3f9] = static_cast<uint8_t>(write_address >> 8);
|
||||
}
|
||||
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;
|
||||
|
||||
default: break;
|
||||
case 0xa2:
|
||||
update_audio();
|
||||
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:
|
||||
update_audio();
|
||||
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;
|
||||
|
||||
case CPU::Z80::PartialMachineCycle::Interrupt:
|
||||
*cycle.value = 0xff;
|
||||
|
||||
// Take this as a convenient moment to jump into the keyboard buffer, if desired.
|
||||
if(!input_text_.empty()) {
|
||||
// The following are KEYBUF per the Red Book; its address and its definition as DEFS 40.
|
||||
const int buffer_start = 0xfbf0;
|
||||
const int buffer_size = 40;
|
||||
|
||||
// Also from the Red Book: GETPNT is at F3FAH and PUTPNT is at F3F8H.
|
||||
int read_address = ram_[0xf3fa] | (ram_[0xf3fb] << 8);
|
||||
int write_address = ram_[0xf3f8] | (ram_[0xf3f9] << 8);
|
||||
|
||||
// Write until either the string is exhausted or the write_pointer is immediately
|
||||
// behind the read pointer; temporarily map write_address and read_address into
|
||||
// buffer-relative values.
|
||||
std::size_t characters_written = 0;
|
||||
write_address -= buffer_start;
|
||||
read_address -= buffer_start;
|
||||
while(characters_written < input_text_.size()) {
|
||||
const int next_write_address = (write_address + 1) % buffer_size;
|
||||
if(next_write_address == read_address) break;
|
||||
ram_[write_address + buffer_start] = static_cast<uint8_t>(input_text_[characters_written]);
|
||||
++characters_written;
|
||||
write_address = next_write_address;
|
||||
}
|
||||
input_text_.erase(input_text_.begin(), input_text_.begin() + static_cast<std::string::difference_type>(characters_written));
|
||||
|
||||
// Map the write address back into absolute terms and write it out again as PUTPNT.
|
||||
write_address += buffer_start;
|
||||
ram_[0xf3f8] = static_cast<uint8_t>(write_address);
|
||||
ram_[0xf3f9] = static_cast<uint8_t>(write_address >> 8);
|
||||
}
|
||||
break;
|
||||
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!tape_player_is_sleeping_)
|
||||
@ -683,7 +685,7 @@ class ConcreteMachine:
|
||||
};
|
||||
|
||||
CPU::Z80::Processor<ConcreteMachine, false, false> z80_;
|
||||
std::unique_ptr<TI::TMS9918> vdp_;
|
||||
std::unique_ptr<TI::TMS::TMS9918> vdp_;
|
||||
Intel::i8255::i8255<i8255PortHandler> i8255_;
|
||||
|
||||
Concurrency::DeferringAsyncTaskQueue audio_queue_;
|
||||
|
383
Machines/MasterSystem/MasterSystem.cpp
Normal file
383
Machines/MasterSystem/MasterSystem.cpp
Normal file
@ -0,0 +1,383 @@
|
||||
//
|
||||
// MasterSystem.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 20/09/2018.
|
||||
// Copyright © 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "MasterSystem.hpp"
|
||||
|
||||
#include "../../Processors/Z80/Z80.hpp"
|
||||
|
||||
#include "../../Components/9918/9918.hpp"
|
||||
#include "../../Components/SN76489/SN76489.hpp"
|
||||
|
||||
#include "../CRTMachine.hpp"
|
||||
#include "../JoystickMachine.hpp"
|
||||
|
||||
#include "../../ClockReceiver/ForceInline.hpp"
|
||||
|
||||
#include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
|
||||
#include "../../Outputs/Log.hpp"
|
||||
|
||||
#include "../../Analyser/Static/Sega/Target.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace {
|
||||
const int sn76489_divider = 2;
|
||||
}
|
||||
|
||||
namespace Sega {
|
||||
namespace MasterSystem {
|
||||
|
||||
class Joystick: public Inputs::ConcreteJoystick {
|
||||
public:
|
||||
Joystick() :
|
||||
ConcreteJoystick({
|
||||
Input(Input::Up),
|
||||
Input(Input::Down),
|
||||
Input(Input::Left),
|
||||
Input(Input::Right),
|
||||
|
||||
Input(Input::Fire, 0),
|
||||
Input(Input::Fire, 1)
|
||||
}) {}
|
||||
|
||||
void did_set_input(const Input &digital_input, bool is_active) override {
|
||||
switch(digital_input.type) {
|
||||
default: return;
|
||||
|
||||
case Input::Up: if(is_active) state_ &= ~0x01; else state_ |= 0x01; break;
|
||||
case Input::Down: if(is_active) state_ &= ~0x02; else state_ |= 0x02; break;
|
||||
case Input::Left: if(is_active) state_ &= ~0x04; else state_ |= 0x04; break;
|
||||
case Input::Right: if(is_active) state_ &= ~0x08; else state_ |= 0x08; break;
|
||||
case Input::Fire:
|
||||
switch(digital_input.info.control.index) {
|
||||
default: break;
|
||||
case 0: if(is_active) state_ &= ~0x10; else state_ |= 0x10; break;
|
||||
case 1: if(is_active) state_ &= ~0x20; else state_ |= 0x20; break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t get_state() {
|
||||
return state_;
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t state_ = 0xff;
|
||||
};
|
||||
|
||||
class ConcreteMachine:
|
||||
public Machine,
|
||||
public CPU::Z80::BusHandler,
|
||||
public CRTMachine::Machine,
|
||||
public JoystickMachine::Machine {
|
||||
|
||||
public:
|
||||
ConcreteMachine(const Analyser::Static::Sega::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) :
|
||||
model_(target.model),
|
||||
z80_(*this),
|
||||
sn76489_(
|
||||
(target.model == Analyser::Static::Sega::Target::Model::SG1000) ? TI::SN76489::Personality::SN76489 : TI::SN76489::Personality::SMS,
|
||||
audio_queue_,
|
||||
sn76489_divider),
|
||||
speaker_(sn76489_) {
|
||||
speaker_.set_input_rate(3579545.0f / static_cast<float>(sn76489_divider));
|
||||
set_clock_rate(3579545);
|
||||
|
||||
// Instantiate the joysticks.
|
||||
joysticks_.emplace_back(new Joystick);
|
||||
joysticks_.emplace_back(new Joystick);
|
||||
|
||||
// Clear the memory map.
|
||||
map(read_pointers_, nullptr, 0x10000, 0);
|
||||
map(write_pointers_, nullptr, 0x10000, 0);
|
||||
|
||||
// Take a copy of the cartridge and place it into memory.
|
||||
cartridge_ = target.media.cartridges[0]->get_segments()[0].data;
|
||||
if(cartridge_.size() < 48*1024) {
|
||||
std::size_t new_space = 48*1024 - cartridge_.size();
|
||||
cartridge_.resize(48*1024);
|
||||
memset(&cartridge_[48*1024 - new_space], 0xff, new_space);
|
||||
}
|
||||
page_cartridge();
|
||||
|
||||
// Establish the BIOS (if relevant) and RAM.
|
||||
if(target.model == Analyser::Static::Sega::Target::Model::MasterSystem) {
|
||||
const auto roms = rom_fetcher("MasterSystem", {"bios.sms"});
|
||||
if(!roms[0]) {
|
||||
throw ROMMachine::Error::MissingROMs;
|
||||
}
|
||||
|
||||
roms[0]->resize(8*1024);
|
||||
memcpy(&bios_, roms[0]->data(), roms[0]->size());
|
||||
map(read_pointers_, bios_, 8*1024, 0);
|
||||
|
||||
map(read_pointers_, ram_, 8*1024, 0xc000, 0x10000);
|
||||
map(write_pointers_, ram_, 8*1024, 0xc000, 0x10000);
|
||||
} else {
|
||||
map(read_pointers_, ram_, 1024, 0xc000, 0x10000);
|
||||
map(write_pointers_, ram_, 1024, 0xc000, 0x10000);
|
||||
}
|
||||
|
||||
speaker_.set_high_frequency_cutoff(8000);
|
||||
}
|
||||
|
||||
~ConcreteMachine() {
|
||||
audio_queue_.flush();
|
||||
}
|
||||
|
||||
void setup_output(float aspect_ratio) override {
|
||||
vdp_.reset(new TI::TMS::TMS9918(model_ == Analyser::Static::Sega::Target::Model::SG1000 ? TI::TMS::TMS9918A : TI::TMS::SMSVDP));
|
||||
get_crt()->set_video_signal(Outputs::CRT::VideoSignal::Composite);
|
||||
}
|
||||
|
||||
void close_output() override {
|
||||
vdp_.reset();
|
||||
}
|
||||
|
||||
Outputs::CRT::CRT *get_crt() override {
|
||||
return vdp_->get_crt();
|
||||
}
|
||||
|
||||
Outputs::Speaker::Speaker *get_speaker() override {
|
||||
return &speaker_;
|
||||
}
|
||||
|
||||
void run_for(const Cycles cycles) override {
|
||||
z80_.run_for(cycles);
|
||||
}
|
||||
|
||||
forceinline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) {
|
||||
time_since_vdp_update_ += cycle.length;
|
||||
time_since_sn76489_update_ += cycle.length;
|
||||
|
||||
if(cycle.is_terminal()) {
|
||||
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 >> 10] ? read_pointers_[address >> 10][address & 1023] : 0xff;
|
||||
break;
|
||||
|
||||
case CPU::Z80::PartialMachineCycle::Write:
|
||||
if(address >= 0xfffd && cartridge_.size() > 48*1024) {
|
||||
if(paging_registers_[address - 0xfffd] != *cycle.value) {
|
||||
paging_registers_[address - 0xfffd] = *cycle.value;
|
||||
page_cartridge();
|
||||
}
|
||||
}
|
||||
|
||||
if(write_pointers_[address >> 10]) write_pointers_[address >> 10][address & 1023] = *cycle.value;
|
||||
else LOG("Ignored write to ROM");
|
||||
break;
|
||||
|
||||
case CPU::Z80::PartialMachineCycle::Input:
|
||||
switch(address & 0xc1) {
|
||||
case 0x00:
|
||||
LOG("TODO: [input] memory control");
|
||||
*cycle.value = 0xff;
|
||||
break;
|
||||
case 0x01:
|
||||
LOG("TODO: [input] I/O port control");
|
||||
*cycle.value = 0xff;
|
||||
break;
|
||||
case 0x40:
|
||||
update_video();
|
||||
*cycle.value = vdp_->get_current_line();
|
||||
break;
|
||||
case 0x41:
|
||||
*cycle.value = vdp_->get_latched_horizontal_counter();
|
||||
break;
|
||||
case 0x80: case 0x81:
|
||||
update_video();
|
||||
*cycle.value = vdp_->get_register(address);
|
||||
z80_.set_interrupt_line(vdp_->get_interrupt_line());
|
||||
time_until_interrupt_ = vdp_->get_time_until_interrupt();
|
||||
break;
|
||||
case 0xc0: {
|
||||
Joystick *const joypad1 = static_cast<Joystick *>(joysticks_[0].get());
|
||||
Joystick *const joypad2 = static_cast<Joystick *>(joysticks_[1].get());
|
||||
*cycle.value = static_cast<uint8_t>(joypad1->get_state() | (joypad2->get_state() << 6));
|
||||
} break;
|
||||
case 0xc1: {
|
||||
Joystick *const joypad2 = static_cast<Joystick *>(joysticks_[1].get());
|
||||
|
||||
*cycle.value =
|
||||
(joypad2->get_state() >> 2) |
|
||||
0x30 |
|
||||
get_th_values();
|
||||
} break;
|
||||
|
||||
default:
|
||||
ERROR("[input] Clearly some sort of typo");
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case CPU::Z80::PartialMachineCycle::Output:
|
||||
switch(address & 0xc1) {
|
||||
case 0x00:
|
||||
if(model_ == Analyser::Static::Sega::Target::Model::MasterSystem) {
|
||||
// TODO: Obey the RAM enable.
|
||||
memory_control_ = *cycle.value;
|
||||
page_cartridge();
|
||||
}
|
||||
break;
|
||||
case 0x01: {
|
||||
// A programmer can force the TH lines to 0 here,
|
||||
// causing a phoney lightgun latch, so check for any
|
||||
// discontinuity in TH inputs.
|
||||
const auto previous_ths = get_th_values();
|
||||
io_port_control_ = *cycle.value;
|
||||
const auto new_ths = get_th_values();
|
||||
|
||||
// Latch if either TH has newly gone to 1.
|
||||
if((new_ths^previous_ths)&new_ths) {
|
||||
update_video();
|
||||
vdp_->latch_horizontal_counter();
|
||||
}
|
||||
} break;
|
||||
case 0x40: case 0x41:
|
||||
update_audio();
|
||||
sn76489_.set_register(*cycle.value);
|
||||
break;
|
||||
case 0x80: case 0x81:
|
||||
update_video();
|
||||
vdp_->set_register(address, *cycle.value);
|
||||
z80_.set_interrupt_line(vdp_->get_interrupt_line());
|
||||
time_until_interrupt_ = vdp_->get_time_until_interrupt();
|
||||
break;
|
||||
case 0xc0:
|
||||
LOG("TODO: [output] I/O port A/N; " << *cycle.value);
|
||||
break;
|
||||
case 0xc1:
|
||||
LOG("TODO: [output] I/O port B/misc");
|
||||
break;
|
||||
|
||||
default:
|
||||
ERROR("[output] Clearly some sort of typo");
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case CPU::Z80::PartialMachineCycle::Interrupt:
|
||||
*cycle.value = 0xff;
|
||||
break;
|
||||
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
if(time_until_interrupt_ > 0) {
|
||||
time_until_interrupt_ -= cycle.length;
|
||||
if(time_until_interrupt_ <= HalfCycles(0)) {
|
||||
z80_.set_interrupt_line(true, time_until_interrupt_);
|
||||
}
|
||||
}
|
||||
|
||||
return HalfCycles(0);
|
||||
}
|
||||
|
||||
void flush() {
|
||||
update_video();
|
||||
update_audio();
|
||||
audio_queue_.perform();
|
||||
}
|
||||
|
||||
std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override {
|
||||
return joysticks_;
|
||||
}
|
||||
|
||||
private:
|
||||
inline uint8_t get_th_values() {
|
||||
// Quick not on TH inputs here: if either is setup as an output, then the
|
||||
// currently output level is returned. Otherwise they're fixed at 1.
|
||||
return
|
||||
static_cast<uint8_t>(
|
||||
((io_port_control_ & 0x02) << 5) | ((io_port_control_&0x20) << 1) |
|
||||
((io_port_control_ & 0x08) << 4) | (io_port_control_&0x80)
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
inline void update_audio() {
|
||||
speaker_.run_for(audio_queue_, time_since_sn76489_update_.divide_cycles(Cycles(sn76489_divider)));
|
||||
}
|
||||
inline void update_video() {
|
||||
vdp_->run_for(time_since_vdp_update_.flush());
|
||||
}
|
||||
|
||||
Analyser::Static::Sega::Target::Model model_;
|
||||
CPU::Z80::Processor<ConcreteMachine, false, false> z80_;
|
||||
std::unique_ptr<TI::TMS::TMS9918> vdp_;
|
||||
|
||||
Concurrency::DeferringAsyncTaskQueue audio_queue_;
|
||||
TI::SN76489 sn76489_;
|
||||
Outputs::Speaker::LowpassSpeaker<TI::SN76489> speaker_;
|
||||
|
||||
std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
|
||||
|
||||
HalfCycles time_since_vdp_update_;
|
||||
HalfCycles time_since_sn76489_update_;
|
||||
HalfCycles time_until_interrupt_;
|
||||
|
||||
uint8_t ram_[8*1024];
|
||||
uint8_t bios_[8*1024];
|
||||
std::vector<uint8_t> cartridge_;
|
||||
|
||||
uint8_t io_port_control_ = 0x0f;
|
||||
|
||||
// The memory map has a 1kb granularity; this is determined by the SG1000's 1kb of RAM.
|
||||
const uint8_t *read_pointers_[64];
|
||||
uint8_t *write_pointers_[64];
|
||||
template <typename T> void map(T **target, uint8_t *source, size_t size, size_t start_address, size_t end_address = 0) {
|
||||
if(!end_address) end_address = start_address + size;
|
||||
for(auto address = start_address; address < end_address; address += 1024) {
|
||||
target[address >> 10] = source ? &source[(address - start_address) & (size - 1)] : nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t paging_registers_[3] = {0, 1, 2};
|
||||
uint8_t memory_control_ = 0;
|
||||
void page_cartridge() {
|
||||
// Either install the cartridge or don't.
|
||||
if(!(memory_control_ & 0x40)) {
|
||||
for(size_t c = 0; c < 3; ++c) {
|
||||
const size_t start_addr = (paging_registers_[c] * 0x4000) % cartridge_.size();
|
||||
map(
|
||||
read_pointers_,
|
||||
cartridge_.data() + start_addr,
|
||||
std::min(static_cast<size_t>(0x4000), cartridge_.size() - start_addr),
|
||||
c * 0x4000);
|
||||
}
|
||||
|
||||
// The first 1kb doesn't page though.
|
||||
map(read_pointers_, cartridge_.data(), 0x400, 0x0000);
|
||||
} else {
|
||||
map(read_pointers_, nullptr, 0xc000, 0x0000);
|
||||
}
|
||||
|
||||
// Throw the BIOS on top if this machine has one and it isn't disabled.
|
||||
if(model_ == Analyser::Static::Sega::Target::Model::MasterSystem && !(memory_control_ & 0x08)) {
|
||||
map(read_pointers_, bios_, 8*1024, 0);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
using namespace Sega::MasterSystem;
|
||||
|
||||
Machine *Machine::MasterSystem(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) {
|
||||
using Target = Analyser::Static::Sega::Target;
|
||||
const Target *const sega_target = dynamic_cast<const Target *>(target);
|
||||
return new ConcreteMachine(*sega_target, rom_fetcher);
|
||||
}
|
||||
|
||||
Machine::~Machine() {}
|
27
Machines/MasterSystem/MasterSystem.hpp
Normal file
27
Machines/MasterSystem/MasterSystem.hpp
Normal file
@ -0,0 +1,27 @@
|
||||
//
|
||||
// MasterSystem.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 20/09/2018.
|
||||
// Copyright © 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef MasterSystem_hpp
|
||||
#define MasterSystem_hpp
|
||||
|
||||
#include "../../Analyser/Static/StaticAnalyser.hpp"
|
||||
#include "../ROMMachine.hpp"
|
||||
|
||||
namespace Sega {
|
||||
namespace MasterSystem {
|
||||
|
||||
class Machine {
|
||||
public:
|
||||
virtual ~Machine();
|
||||
static Machine *MasterSystem(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher);
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* MasterSystem_hpp */
|
@ -14,6 +14,7 @@
|
||||
#include "../ColecoVision/ColecoVision.hpp"
|
||||
#include "../Commodore/Vic-20/Vic20.hpp"
|
||||
#include "../Electron/Electron.hpp"
|
||||
#include "../MasterSystem/MasterSystem.hpp"
|
||||
#include "../MSX/MSX.hpp"
|
||||
#include "../Oric/Oric.hpp"
|
||||
#include "../ZX8081/ZX8081.hpp"
|
||||
@ -36,6 +37,7 @@ namespace {
|
||||
Bind(Atari2600)
|
||||
BindD(Coleco::Vision, ColecoVision)
|
||||
Bind(Electron)
|
||||
BindD(Sega::MasterSystem, MasterSystem)
|
||||
Bind(MSX)
|
||||
Bind(Oric)
|
||||
BindD(Commodore::Vic20, Vic20)
|
||||
|
@ -242,6 +242,10 @@
|
||||
4B7A90ED20410A85008514A2 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7A90EC20410A85008514A2 /* StaticAnalyser.cpp */; };
|
||||
4B7BC7F51F58F27800D1B1B4 /* 6502AllRAM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B6A4C911F58F09E00E3F787 /* 6502AllRAM.cpp */; };
|
||||
4B7BC7F61F58F7D200D1B1B4 /* 6502Base.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B6A4C951F58F09E00E3F787 /* 6502Base.cpp */; };
|
||||
4B7F188E2154825E00388727 /* MasterSystem.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7F188C2154825D00388727 /* MasterSystem.cpp */; };
|
||||
4B7F188F2154825E00388727 /* MasterSystem.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7F188C2154825D00388727 /* MasterSystem.cpp */; };
|
||||
4B7F1897215486A200388727 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7F1896215486A100388727 /* StaticAnalyser.cpp */; };
|
||||
4B7F1898215486A200388727 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7F1896215486A100388727 /* StaticAnalyser.cpp */; };
|
||||
4B80AD001F85CACA00176895 /* BestEffortUpdater.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B80ACFE1F85CAC900176895 /* BestEffortUpdater.cpp */; };
|
||||
4B8334821F5D9FF70097E338 /* PartialMachineCycle.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8334811F5D9FF70097E338 /* PartialMachineCycle.cpp */; };
|
||||
4B8334841F5DA0360097E338 /* Z80Storage.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8334831F5DA0360097E338 /* Z80Storage.cpp */; };
|
||||
@ -311,6 +315,7 @@
|
||||
4B9BE401203A0C0600FFAE60 /* MultiSpeaker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B9BE3FE203A0C0600FFAE60 /* MultiSpeaker.cpp */; };
|
||||
4BA0F68E1EEA0E8400E9489E /* ZX8081.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BA0F68C1EEA0E8400E9489E /* ZX8081.cpp */; };
|
||||
4BA61EB01D91515900B3C876 /* NSData+StdVector.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BA61EAF1D91515900B3C876 /* NSData+StdVector.mm */; };
|
||||
4BA91E1D216D85BA00F79557 /* MasterSystemVDPTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BA91E1C216D85BA00F79557 /* MasterSystemVDPTests.mm */; };
|
||||
4BAD13441FF709C700FD114A /* MSX.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0E61051FF34737002A9DBD /* MSX.cpp */; };
|
||||
4BAE49582032881E004BE78E /* CSZX8081.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B14978E1EE4B4D200CE2596 /* CSZX8081.mm */; };
|
||||
4BAE495920328897004BE78E /* ZX8081OptionsPanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B95FA9C1F11893B0008E395 /* ZX8081OptionsPanel.swift */; };
|
||||
@ -941,6 +946,10 @@
|
||||
4B7A90E42041097C008514A2 /* ColecoVision.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ColecoVision.cpp; sourceTree = "<group>"; };
|
||||
4B7A90EB20410A85008514A2 /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = StaticAnalyser.hpp; sourceTree = "<group>"; };
|
||||
4B7A90EC20410A85008514A2 /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StaticAnalyser.cpp; sourceTree = "<group>"; };
|
||||
4B7F188C2154825D00388727 /* MasterSystem.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MasterSystem.cpp; sourceTree = "<group>"; };
|
||||
4B7F188D2154825D00388727 /* MasterSystem.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = MasterSystem.hpp; sourceTree = "<group>"; };
|
||||
4B7F1895215486A100388727 /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = StaticAnalyser.hpp; sourceTree = "<group>"; };
|
||||
4B7F1896215486A100388727 /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StaticAnalyser.cpp; sourceTree = "<group>"; };
|
||||
4B80ACFE1F85CAC900176895 /* BestEffortUpdater.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = BestEffortUpdater.cpp; path = ../../Concurrency/BestEffortUpdater.cpp; sourceTree = "<group>"; };
|
||||
4B80ACFF1F85CACA00176895 /* BestEffortUpdater.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = BestEffortUpdater.hpp; path = ../../Concurrency/BestEffortUpdater.hpp; sourceTree = "<group>"; };
|
||||
4B8334811F5D9FF70097E338 /* PartialMachineCycle.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PartialMachineCycle.cpp; sourceTree = "<group>"; };
|
||||
@ -1033,7 +1042,9 @@
|
||||
4BA141C12073100800A31EC9 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = "<group>"; };
|
||||
4BA61EAE1D91515900B3C876 /* NSData+StdVector.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSData+StdVector.h"; sourceTree = "<group>"; };
|
||||
4BA61EAF1D91515900B3C876 /* NSData+StdVector.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "NSData+StdVector.mm"; sourceTree = "<group>"; };
|
||||
4BA91E1C216D85BA00F79557 /* MasterSystemVDPTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = MasterSystemVDPTests.mm; sourceTree = "<group>"; };
|
||||
4BA9C3CF1D8164A9002DDB61 /* MediaTarget.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MediaTarget.hpp; sourceTree = "<group>"; };
|
||||
4BAA167B21582B1D008A3276 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = "<group>"; };
|
||||
4BAB62AC1D3272D200DF5BA0 /* Disk.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Disk.hpp; sourceTree = "<group>"; };
|
||||
4BAB62AE1D32730D00DF5BA0 /* Storage.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Storage.hpp; sourceTree = "<group>"; };
|
||||
4BAF2B4C2004580C00480230 /* DMK.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = DMK.cpp; sourceTree = "<group>"; };
|
||||
@ -2191,6 +2202,25 @@
|
||||
path = Coleco;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4B7F188B2154825D00388727 /* MasterSystem */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4B7F188C2154825D00388727 /* MasterSystem.cpp */,
|
||||
4B7F188D2154825D00388727 /* MasterSystem.hpp */,
|
||||
);
|
||||
path = MasterSystem;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4B7F1894215486A100388727 /* Sega */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4B7F1895215486A100388727 /* StaticAnalyser.hpp */,
|
||||
4B7F1896215486A100388727 /* StaticAnalyser.cpp */,
|
||||
4BAA167B21582B1D008A3276 /* Target.hpp */,
|
||||
);
|
||||
path = Sega;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4B8334881F5DB8470097E338 /* Implementation */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -2292,6 +2322,7 @@
|
||||
4BD67DC8209BE4D600AB2146 /* DiskII */,
|
||||
4B89450F201967B4007DE474 /* MSX */,
|
||||
4B8944F6201967B4007DE474 /* Oric */,
|
||||
4B7F1894215486A100388727 /* Sega */,
|
||||
4B894504201967B4007DE474 /* ZX8081 */,
|
||||
);
|
||||
path = Static;
|
||||
@ -2789,10 +2820,11 @@
|
||||
4BB73EB51B587A5100552FC2 /* Clock SignalTests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4B98A0601FFADCDE00ADF63B /* MSXStaticAnalyserTests.mm */,
|
||||
4B5073091DDFCFDF00C48FBD /* ArrayBuilderTests.mm */,
|
||||
4B924E981E74D22700B76AF1 /* AtariStaticAnalyserTests.mm */,
|
||||
4BB2A9AE1E13367E001A5C23 /* CRCTests.mm */,
|
||||
4BA91E1C216D85BA00F79557 /* MasterSystemVDPTests.mm */,
|
||||
4B98A0601FFADCDE00ADF63B /* MSXStaticAnalyserTests.mm */,
|
||||
4B121F9A1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm */,
|
||||
4BD4A8CF1E077FD20020D856 /* PCMTrackTests.mm */,
|
||||
4B2AF8681E513FC20027EE29 /* TIATests.mm */,
|
||||
@ -2832,11 +2864,11 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4B54C0BB1F8D8E790050900F /* KeyboardMachine.cpp */,
|
||||
4BA9C3CF1D8164A9002DDB61 /* MediaTarget.hpp */,
|
||||
4B046DC31CFE651500E9E45E /* CRTMachine.hpp */,
|
||||
4BBB709C2020109C002FE009 /* DynamicMachine.hpp */,
|
||||
4B7041271F92C26900735E45 /* JoystickMachine.hpp */,
|
||||
4B8E4ECD1DCE483D003716C3 /* KeyboardMachine.hpp */,
|
||||
4BA9C3CF1D8164A9002DDB61 /* MediaTarget.hpp */,
|
||||
4BDCC5F81FB27A5E001220C5 /* ROMMachine.hpp */,
|
||||
4B38F3491F2EC12000D9235D /* AmstradCPC */,
|
||||
4B15AA082082C799005E6C8D /* AppleII */,
|
||||
@ -2844,6 +2876,7 @@
|
||||
4B7A90E22041097C008514A2 /* ColecoVision */,
|
||||
4B4DC81D1D2C2425003C5BF8 /* Commodore */,
|
||||
4B2E2D9E1C3A070900138695 /* Electron */,
|
||||
4B7F188B2154825D00388727 /* MasterSystem */,
|
||||
4B79A4FC1FC8FF9800EEDAD5 /* MSX */,
|
||||
4BCF1FA51DADC3E10039D2E7 /* Oric */,
|
||||
4B2B3A461F9B8FA70062DABF /* Utility */,
|
||||
@ -3628,6 +3661,7 @@
|
||||
4B055AD51FAE9B0B0060FFFF /* Video.cpp in Sources */,
|
||||
4B055AE11FAE9B6F0060FFFF /* ArrayBuilder.cpp in Sources */,
|
||||
4B894521201967B4007DE474 /* StaticAnalyser.cpp in Sources */,
|
||||
4B7F188F2154825E00388727 /* MasterSystem.cpp in Sources */,
|
||||
4B055AA51FAE85EF0060FFFF /* Encoder.cpp in Sources */,
|
||||
4B894529201967B4007DE474 /* Disk.cpp in Sources */,
|
||||
4B055AEA1FAE9B990060FFFF /* 6502Storage.cpp in Sources */,
|
||||
@ -3641,6 +3675,7 @@
|
||||
4B894531201967B4007DE474 /* StaticAnalyser.cpp in Sources */,
|
||||
4BC891AE20F6EAB300EDE5B3 /* Rectangle.cpp in Sources */,
|
||||
4B894539201967B4007DE474 /* Tape.cpp in Sources */,
|
||||
4B7F1898215486A200388727 /* StaticAnalyser.cpp in Sources */,
|
||||
4B055AE51FAE9B6F0060FFFF /* IntermediateShader.cpp in Sources */,
|
||||
4B15A9FD208249BB005E6C8D /* StaticAnalyser.cpp in Sources */,
|
||||
4B055AD31FAE9B0B0060FFFF /* Microdisc.cpp in Sources */,
|
||||
@ -3864,6 +3899,7 @@
|
||||
4B12C0ED1FCFA98D005BFD93 /* Keyboard.cpp in Sources */,
|
||||
4BA0F68E1EEA0E8400E9489E /* ZX8081.cpp in Sources */,
|
||||
4BD468F71D8DF41D0084958B /* 1770.cpp in Sources */,
|
||||
4B7F1897215486A200388727 /* StaticAnalyser.cpp in Sources */,
|
||||
4BD3A30B1EE755C800B5B501 /* Video.cpp in Sources */,
|
||||
4BBF99141C8FBA6F0075DAFB /* TextureBuilder.cpp in Sources */,
|
||||
4B5FADBA1DE3151600AEC565 /* FileHolder.cpp in Sources */,
|
||||
@ -3895,6 +3931,7 @@
|
||||
4B6A4C991F58F09E00E3F787 /* 6502Base.cpp in Sources */,
|
||||
4B4518871F75E91A00926311 /* DigitalPhaseLockedLoop.cpp in Sources */,
|
||||
4B98A05E1FFAD3F600ADF63B /* CSROMFetcher.mm in Sources */,
|
||||
4B7F188E2154825E00388727 /* MasterSystem.cpp in Sources */,
|
||||
4B8805F41DCFD22A003085B1 /* Commodore.cpp in Sources */,
|
||||
4B3FCC40201EC24200960631 /* MultiMachine.cpp in Sources */,
|
||||
4B2E2D9A1C3A06EC00138695 /* Atari2600.cpp in Sources */,
|
||||
@ -3966,6 +4003,7 @@
|
||||
4BB73EB71B587A5100552FC2 /* AllSuiteATests.swift in Sources */,
|
||||
4B01A6881F22F0DB001FD6E3 /* Z80MemptrTests.swift in Sources */,
|
||||
4B121F9B1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm in Sources */,
|
||||
4BA91E1D216D85BA00F79557 /* MasterSystemVDPTests.mm in Sources */,
|
||||
4B98A0611FFADCDE00ADF63B /* MSXStaticAnalyserTests.mm in Sources */,
|
||||
4BEF6AAC1D35D1C400E73575 /* DPLLTests.swift in Sources */,
|
||||
4B3BA0CF1D318B44005DD7A7 /* MOS6522Bridge.mm in Sources */,
|
||||
|
@ -431,6 +431,50 @@
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>sms</string>
|
||||
</array>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>cartridge.png</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Master System Cartridge</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<false/>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>sg</string>
|
||||
</array>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>cartridge.png</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>SG1000 Cartridge</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<false/>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
|
82
OSBindings/Mac/Clock SignalTests/MasterSystemVDPTests.mm
Normal file
82
OSBindings/Mac/Clock SignalTests/MasterSystemVDPTests.mm
Normal file
@ -0,0 +1,82 @@
|
||||
//
|
||||
// MasterSystemVDPTests.m
|
||||
// Clock SignalTests
|
||||
//
|
||||
// Created by Thomas Harte on 09/10/2018.
|
||||
// Copyright © 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
#import <OpenGL/OpenGL.h>
|
||||
|
||||
#include "9918.hpp"
|
||||
|
||||
@interface MasterSystemVDPTests : XCTestCase
|
||||
@end
|
||||
|
||||
@implementation MasterSystemVDPTests {
|
||||
NSOpenGLContext *_openGLContext;
|
||||
}
|
||||
|
||||
- (void)setUp {
|
||||
[super setUp];
|
||||
|
||||
// Create a valid OpenGL context, so that a VDP can be constructed.
|
||||
NSOpenGLPixelFormatAttribute attributes[] =
|
||||
{
|
||||
NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion3_2Core,
|
||||
0
|
||||
};
|
||||
|
||||
NSOpenGLPixelFormat *pixelFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:attributes];
|
||||
_openGLContext = [[NSOpenGLContext alloc] initWithFormat:pixelFormat shareContext:nil];
|
||||
[_openGLContext makeCurrentContext];
|
||||
}
|
||||
|
||||
- (void)tearDown {
|
||||
// Put teardown code here. This method is called after the invocation of each test method in the class.
|
||||
_openGLContext = nil;
|
||||
|
||||
[super tearDown];
|
||||
}
|
||||
|
||||
- (void)testLineInterrupt {
|
||||
TI::TMS::TMS9918 vdp(TI::TMS::Personality::SMSVDP);
|
||||
|
||||
// Disable end-of-frame interrupts, enable line interrupts.
|
||||
vdp.set_register(1, 0x00);
|
||||
vdp.set_register(1, 0x81);
|
||||
|
||||
vdp.set_register(1, 0x10);
|
||||
vdp.set_register(1, 0x80);
|
||||
|
||||
// Set a line interrupt to occur in five lines.
|
||||
vdp.set_register(1, 5);
|
||||
vdp.set_register(1, 0x8a);
|
||||
|
||||
// Get time until interrupt.
|
||||
int time_until_interrupt = vdp.get_time_until_interrupt().as_int() - 1;
|
||||
|
||||
// Check interrupt flag isn't set prior to the reported time.
|
||||
vdp.run_for(HalfCycles(time_until_interrupt));
|
||||
NSAssert(!vdp.get_interrupt_line(), @"Interrupt line went active early [1]");
|
||||
|
||||
// Check interrupt flag is set at the reported time.
|
||||
vdp.run_for(HalfCycles(1));
|
||||
NSAssert(vdp.get_interrupt_line(), @"Interrupt line wasn't set when promised [1]");
|
||||
|
||||
// Read the status register to clear interrupt status.
|
||||
vdp.get_register(1);
|
||||
NSAssert(!vdp.get_interrupt_line(), @"Interrupt wasn't reset by status read");
|
||||
|
||||
// Check interrupt flag isn't set prior to the reported time.
|
||||
time_until_interrupt = vdp.get_time_until_interrupt().as_int() - 1;
|
||||
vdp.run_for(HalfCycles(time_until_interrupt));
|
||||
NSAssert(!vdp.get_interrupt_line(), @"Interrupt line went active early [2]");
|
||||
|
||||
// Check interrupt flag is set at the reported time.
|
||||
vdp.run_for(HalfCycles(1));
|
||||
NSAssert(vdp.get_interrupt_line(), @"Interrupt line wasn't set when promised [2]");
|
||||
}
|
||||
|
||||
@end
|
@ -25,6 +25,7 @@ SOURCES += glob.glob('../../Analyser/Static/Disassembler/*.cpp')
|
||||
SOURCES += glob.glob('../../Analyser/Static/DiskII/*.cpp')
|
||||
SOURCES += glob.glob('../../Analyser/Static/MSX/*.cpp')
|
||||
SOURCES += glob.glob('../../Analyser/Static/Oric/*.cpp')
|
||||
SOURCES += glob.glob('../../Analyser/Static/Sega/*.cpp')
|
||||
SOURCES += glob.glob('../../Analyser/Static/ZX8081/*.cpp')
|
||||
|
||||
SOURCES += glob.glob('../../Components/1770/*.cpp')
|
||||
@ -54,6 +55,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/MasterSystem/*.cpp')
|
||||
SOURCES += glob.glob('../../Machines/MSX/*.cpp')
|
||||
SOURCES += glob.glob('../../Machines/Oric/*.cpp')
|
||||
SOURCES += glob.glob('../../Machines/Utility/*.cpp')
|
||||
|
5
ROMImages/MasterSystem/readme.txt
Normal file
5
ROMImages/MasterSystem/readme.txt
Normal file
@ -0,0 +1,5 @@
|
||||
BIOS files would ordinarily go here; the copyright status of these is uncertain so they have not been included in this repository.
|
||||
|
||||
Expected files:
|
||||
|
||||
bios.sms — the EU/US Master System BIOS.
|
@ -24,10 +24,11 @@ enum Type: IntType {
|
||||
ColecoVision = 1 << 9,
|
||||
Commodore = 1 << 10,
|
||||
DiskII = 1 << 11,
|
||||
MSX = 1 << 12,
|
||||
Oric = 1 << 13,
|
||||
ZX80 = 1 << 14,
|
||||
ZX81 = 1 << 15,
|
||||
Sega = 1 << 12,
|
||||
MSX = 1 << 13,
|
||||
Oric = 1 << 14,
|
||||
ZX80 = 1 << 15,
|
||||
ZX81 = 1 << 16,
|
||||
|
||||
Acorn = AcornAtom | AcornElectron | BBCMaster | BBCModelA | BBCModelB,
|
||||
ZX8081 = ZX80 | ZX81,
|
||||
|
Loading…
Reference in New Issue
Block a user