mirror of
https://github.com/TomHarte/CLK.git
synced 2024-11-19 08:31:11 +00:00
Merge pull request #319 from TomHarte/TMSTests
Corrects a couple of lingering TMS issues and tidies it up
This commit is contained in:
commit
d66a33f249
@ -55,10 +55,14 @@ const uint8_t StatusSpriteCollision = 0x20;
|
||||
|
||||
}
|
||||
|
||||
TMS9918::TMS9918(Personality p) :
|
||||
TMS9918Base::TMS9918Base() :
|
||||
// 342 internal cycles are 228/227.5ths of a line, so 341.25 cycles should be a whole
|
||||
// line. Therefore multiply everything by four, but set line length to 1365 rather than 342*4 = 1368.
|
||||
crt_(new Outputs::CRT::CRT(1365, 4, Outputs::CRT::DisplayType::NTSC60, 4)) {
|
||||
crt_(new Outputs::CRT::CRT(1365, 4, Outputs::CRT::DisplayType::NTSC60, 4)) {}
|
||||
|
||||
TMS9918::TMS9918(Personality p) {
|
||||
// Unimaginatively, this class just passes RGB through to the shader. Investigation is needed
|
||||
// into whether there's a more natural form.
|
||||
crt_->set_rgb_sampling_function(
|
||||
"vec3 rgb_sample(usampler2D sampler, vec2 coordinate, vec2 icoordinate)"
|
||||
"{"
|
||||
@ -72,7 +76,7 @@ std::shared_ptr<Outputs::CRT::CRT> TMS9918::get_crt() {
|
||||
return crt_;
|
||||
}
|
||||
|
||||
void TMS9918::test_sprite(int sprite_number) {
|
||||
void TMS9918Base::test_sprite(int sprite_number) {
|
||||
if(!(status_ & StatusFifthSprite)) {
|
||||
status_ = static_cast<uint8_t>((status_ & ~31) | sprite_number);
|
||||
}
|
||||
@ -101,7 +105,7 @@ void TMS9918::test_sprite(int sprite_number) {
|
||||
sprite_sets_[active_sprite_set_].active_sprite_slot++;
|
||||
}
|
||||
|
||||
void TMS9918::get_sprite_contents(int field, int cycles_left, int screen_row) {
|
||||
void TMS9918Base::get_sprite_contents(int field, int cycles_left, int screen_row) {
|
||||
int sprite_id = field / 6;
|
||||
field %= 6;
|
||||
|
||||
@ -187,10 +191,11 @@ void TMS9918::run_for(const HalfCycles cycles) {
|
||||
|
||||
if(cycles_left >= time_until_access_slot) {
|
||||
if(queued_access_ == MemoryAccess::Write) {
|
||||
ram_[queued_address_] = read_ahead_buffer_;
|
||||
ram_[ram_pointer_ & 16383] = read_ahead_buffer_;
|
||||
} else {
|
||||
read_ahead_buffer_ = ram_[queued_address_];
|
||||
read_ahead_buffer_ = ram_[ram_pointer_ & 16383];
|
||||
}
|
||||
ram_pointer_++;
|
||||
queued_access_ = MemoryAccess::None;
|
||||
}
|
||||
}
|
||||
@ -445,7 +450,10 @@ void TMS9918::run_for(const HalfCycles cycles) {
|
||||
if(sprite_set.active_sprite_slot) {
|
||||
int sprite_pixels_left = pixels_left;
|
||||
const int shift_advance = sprites_magnified_ ? 1 : 2;
|
||||
// const uint32_t sprite_colour_selection_masks[2] = {0x00000000, 0xffffffff};
|
||||
|
||||
const uint32_t sprite_colour_selection_masks[2] = {0x00000000, 0xffffffff};
|
||||
const int colour_masks[16] = {0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
|
||||
|
||||
while(sprite_pixels_left--) {
|
||||
uint32_t sprite_colour = pixel_base_[output_column_ - first_pixel_column_];
|
||||
int sprite_mask = 0;
|
||||
@ -463,12 +471,8 @@ void TMS9918::run_for(const HalfCycles cycles) {
|
||||
sprite_mask |= mask;
|
||||
sprite.shift_position += shift_advance;
|
||||
|
||||
// TODO: can a non-conditional version be found like that commented out below, but
|
||||
// which accounts for colour 0 being invisible?
|
||||
// sprite_colour = (sprite_colour & sprite_colour_selection_masks[mask^1]) | (palette[sprite.info[3]&15] & sprite_colour_selection_masks[mask]);
|
||||
if((sprite.info[3]&15) && mask) {
|
||||
sprite_colour = palette[sprite.info[3]&15];
|
||||
}
|
||||
mask &= colour_masks[sprite.info[3]&15];
|
||||
sprite_colour = (sprite_colour & sprite_colour_selection_masks[mask^1]) | (palette[sprite.info[3]&15] & sprite_colour_selection_masks[mask]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -545,7 +549,7 @@ void TMS9918::run_for(const HalfCycles cycles) {
|
||||
}
|
||||
}
|
||||
|
||||
void TMS9918::output_border(int cycles) {
|
||||
void TMS9918Base::output_border(int cycles) {
|
||||
pixel_target_ = reinterpret_cast<uint32_t *>(crt_->allocate_write_area(1));
|
||||
if(pixel_target_) *pixel_target_ = palette[background_colour_];
|
||||
crt_->output_level(static_cast<unsigned int>(cycles) * 4);
|
||||
@ -556,13 +560,11 @@ void TMS9918::set_register(int address, uint8_t value) {
|
||||
// the value and return.
|
||||
if(!(address & 1)) {
|
||||
write_phase_ = false;
|
||||
read_ahead_buffer_ = value;
|
||||
|
||||
// Enqueue the write to occur at the next available slot.
|
||||
read_ahead_buffer_ = value;
|
||||
queued_access_ = MemoryAccess::Write;
|
||||
queued_address_ = ram_pointer_ & 16383;
|
||||
|
||||
ram_pointer_++;
|
||||
return;
|
||||
}
|
||||
|
||||
@ -580,7 +582,6 @@ void TMS9918::set_register(int address, uint8_t value) {
|
||||
switch(value & 7) {
|
||||
case 0:
|
||||
next_screen_mode_ = (next_screen_mode_ & 6) | ((low_write_ & 2) >> 1);
|
||||
// printf("NSM: %02x\n", next_screen_mode_);
|
||||
break;
|
||||
|
||||
case 1:
|
||||
@ -593,7 +594,6 @@ void TMS9918::set_register(int address, uint8_t value) {
|
||||
sprite_height_ = 8;
|
||||
if(sprites_16x16_) sprite_height_ <<= 1;
|
||||
if(sprites_magnified_) sprite_height_ <<= 1;
|
||||
// printf("NSM: %02x\n", next_screen_mode_);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
@ -626,8 +626,7 @@ void TMS9918::set_register(int address, uint8_t value) {
|
||||
ram_pointer_ = static_cast<uint16_t>(low_write_ | (value << 8));
|
||||
if(!(value & 0x40)) {
|
||||
// Officially a 'read' set, so perform lookahead.
|
||||
read_ahead_buffer_ = ram_[ram_pointer_ & 16383];
|
||||
ram_pointer_++;
|
||||
queued_access_ = MemoryAccess::Read;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -637,13 +636,9 @@ uint8_t TMS9918::get_register(int address) {
|
||||
|
||||
// Reads from address 0 read video RAM, via the read-ahead buffer.
|
||||
if(!(address & 1)) {
|
||||
uint8_t result = read_ahead_buffer_;
|
||||
|
||||
// Enqueue the write to occur at the next available slot.
|
||||
uint8_t result = read_ahead_buffer_;
|
||||
queued_access_ = MemoryAccess::Read;
|
||||
queued_address_ = ram_pointer_ & 16383;
|
||||
|
||||
ram_pointer_++;
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -659,7 +654,7 @@ uint8_t TMS9918::get_register(int address) {
|
||||
|
||||
const int half_cycles_per_frame = frame_lines_ * 228 * 2;
|
||||
int half_cycles_remaining = (192 * 228 * 2 + half_cycles_per_frame - half_cycles_into_frame_.as_int()) % half_cycles_per_frame;
|
||||
return HalfCycles(half_cycles_remaining);
|
||||
return HalfCycles(half_cycles_remaining ? half_cycles_remaining : half_cycles_per_frame);
|
||||
}
|
||||
|
||||
bool TMS9918::get_interrupt_line() {
|
||||
|
@ -6,20 +6,33 @@
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef _918_hpp
|
||||
#define _918_hpp
|
||||
#ifndef TMS9918_hpp
|
||||
#define TMS9918_hpp
|
||||
|
||||
#include "../../Outputs/CRT/CRT.hpp"
|
||||
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||
|
||||
#include "Implementation/9918Base.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace TI {
|
||||
|
||||
class TMS9918 {
|
||||
/*!
|
||||
Provides emulation of the TMS9918a, TMS9928 and TMS9929. Likely in the future to be the
|
||||
vessel for emulation of sufficiently close derivatives, such as the Master System VDP.
|
||||
|
||||
The TMS9918 and descendants are video display generators that own their own RAM, making it
|
||||
accessible through an implicitly-timed register interface, and (depending on model) can generate
|
||||
PAL and NTSC component and composite video.
|
||||
|
||||
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 {
|
||||
public:
|
||||
enum Personality {
|
||||
TMS9918A, // includes the 9928A; set TV standard as desired.
|
||||
TMS9918A, // includes the 9928 and 9929; set TV standard and output device as desired.
|
||||
};
|
||||
|
||||
/*!
|
||||
@ -29,10 +42,16 @@ class TMS9918 {
|
||||
TMS9918(Personality p);
|
||||
|
||||
enum TVStandard {
|
||||
PAL, NTSC
|
||||
/*! i.e. 50Hz output at around 312.5 lines/field */
|
||||
PAL,
|
||||
/*! i.e. 60Hz output at around 262.5 lines/field */
|
||||
NTSC
|
||||
};
|
||||
|
||||
/*! Sets the TV standard for this TMS, if that is hard-coded in hardware. */
|
||||
void set_tv_standard(TVStandard standard);
|
||||
|
||||
/*! Provides the CRT this TMS is connected to. */
|
||||
std::shared_ptr<Outputs::CRT::CRT> get_crt();
|
||||
|
||||
/*!
|
||||
@ -41,7 +60,10 @@ class TMS9918 {
|
||||
*/
|
||||
void run_for(const HalfCycles cycles);
|
||||
|
||||
/*! Sets a register value. */
|
||||
void set_register(int address, uint8_t value);
|
||||
|
||||
/*! Gets a register value. */
|
||||
uint8_t get_register(int address);
|
||||
|
||||
/*!
|
||||
@ -57,84 +79,8 @@ class TMS9918 {
|
||||
@returns @c true if the interrupt line is currently active; @c false otherwise.
|
||||
*/
|
||||
bool get_interrupt_line();
|
||||
|
||||
private:
|
||||
std::shared_ptr<Outputs::CRT::CRT> crt_;
|
||||
|
||||
uint8_t ram_[16384];
|
||||
|
||||
uint16_t ram_pointer_ = 0;
|
||||
uint8_t read_ahead_buffer_ = 0;
|
||||
enum class MemoryAccess {
|
||||
Read, Write, None
|
||||
} queued_access_ = MemoryAccess::None;
|
||||
uint16_t queued_address_;
|
||||
|
||||
uint8_t status_ = 0;
|
||||
|
||||
bool write_phase_ = false;
|
||||
uint8_t low_write_ = 0;
|
||||
|
||||
// The various register flags.
|
||||
int next_screen_mode_ = 0, screen_mode_ = 0;
|
||||
bool next_blank_screen_ = true, blank_screen_ = true;
|
||||
bool sprites_16x16_ = false;
|
||||
bool sprites_magnified_ = false;
|
||||
bool generate_interrupts_ = false;
|
||||
int sprite_height_ = 8;
|
||||
uint16_t pattern_name_address_ = 0;
|
||||
uint16_t colour_table_address_ = 0;
|
||||
uint16_t pattern_generator_table_address_ = 0;
|
||||
uint16_t sprite_attribute_table_address_ = 0;
|
||||
uint16_t sprite_generator_table_address_ = 0;
|
||||
|
||||
uint8_t text_colour_ = 0;
|
||||
uint8_t background_colour_ = 0;
|
||||
|
||||
HalfCycles half_cycles_into_frame_;
|
||||
int column_ = 0, row_ = 0, output_column_ = 0;
|
||||
int cycles_error_ = 0;
|
||||
uint32_t *pixel_target_ = nullptr, *pixel_base_ = nullptr;
|
||||
|
||||
void output_border(int cycles);
|
||||
|
||||
// Vertical timing details.
|
||||
int frame_lines_ = 262;
|
||||
int first_vsync_line_ = 227;
|
||||
|
||||
// Horizontal selections.
|
||||
enum class LineMode {
|
||||
Text = 0,
|
||||
Character = 1,
|
||||
Refresh = 2
|
||||
} line_mode_ = LineMode::Text;
|
||||
int first_pixel_column_, first_right_border_column_;
|
||||
|
||||
uint8_t pattern_names_[40];
|
||||
uint8_t pattern_buffer_[40];
|
||||
uint8_t colour_buffer_[40];
|
||||
|
||||
struct SpriteSet {
|
||||
struct ActiveSprite {
|
||||
int index = 0;
|
||||
int row = 0;
|
||||
|
||||
uint8_t info[4];
|
||||
uint8_t image[2];
|
||||
|
||||
int shift_position = 0;
|
||||
} active_sprites[4];
|
||||
int active_sprite_slot = 0;
|
||||
} sprite_sets_[2];
|
||||
int active_sprite_set_ = 0;
|
||||
bool sprites_stopped_ = false;
|
||||
|
||||
int access_pointer_ = 0;
|
||||
|
||||
inline void test_sprite(int sprite_number);
|
||||
inline void get_sprite_contents(int start, int cycles, int screen_row);
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
#endif /* _918_hpp */
|
||||
#endif /* TMS9918_hpp */
|
||||
|
101
Components/9918/Implementation/9918Base.hpp
Normal file
101
Components/9918/Implementation/9918Base.hpp
Normal file
@ -0,0 +1,101 @@
|
||||
//
|
||||
// 9918Base.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 14/12/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef TMS9918Base_hpp
|
||||
#define TMS9918Base_hpp
|
||||
|
||||
#include "../../../Outputs/CRT/CRT.hpp"
|
||||
#include "../../../ClockReceiver/ClockReceiver.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
|
||||
namespace TI {
|
||||
|
||||
class TMS9918Base {
|
||||
protected:
|
||||
TMS9918Base();
|
||||
|
||||
std::shared_ptr<Outputs::CRT::CRT> crt_;
|
||||
|
||||
uint8_t ram_[16384];
|
||||
|
||||
uint16_t ram_pointer_ = 0;
|
||||
uint8_t read_ahead_buffer_ = 0;
|
||||
enum class MemoryAccess {
|
||||
Read, Write, None
|
||||
} queued_access_ = MemoryAccess::None;
|
||||
|
||||
uint8_t status_ = 0;
|
||||
|
||||
bool write_phase_ = false;
|
||||
uint8_t low_write_ = 0;
|
||||
|
||||
// The various register flags.
|
||||
int next_screen_mode_ = 0, screen_mode_ = 0;
|
||||
bool next_blank_screen_ = true, blank_screen_ = true;
|
||||
bool sprites_16x16_ = false;
|
||||
bool sprites_magnified_ = false;
|
||||
bool generate_interrupts_ = false;
|
||||
int sprite_height_ = 8;
|
||||
uint16_t pattern_name_address_ = 0;
|
||||
uint16_t colour_table_address_ = 0;
|
||||
uint16_t pattern_generator_table_address_ = 0;
|
||||
uint16_t sprite_attribute_table_address_ = 0;
|
||||
uint16_t sprite_generator_table_address_ = 0;
|
||||
|
||||
uint8_t text_colour_ = 0;
|
||||
uint8_t background_colour_ = 0;
|
||||
|
||||
HalfCycles half_cycles_into_frame_;
|
||||
int column_ = 0, row_ = 0, output_column_ = 0;
|
||||
int cycles_error_ = 0;
|
||||
uint32_t *pixel_target_ = nullptr, *pixel_base_ = nullptr;
|
||||
|
||||
void output_border(int cycles);
|
||||
|
||||
// Vertical timing details.
|
||||
int frame_lines_ = 262;
|
||||
int first_vsync_line_ = 227;
|
||||
|
||||
// Horizontal selections.
|
||||
enum class LineMode {
|
||||
Text = 0,
|
||||
Character = 1,
|
||||
Refresh = 2
|
||||
} line_mode_ = LineMode::Text;
|
||||
int first_pixel_column_, first_right_border_column_;
|
||||
|
||||
uint8_t pattern_names_[40];
|
||||
uint8_t pattern_buffer_[40];
|
||||
uint8_t colour_buffer_[40];
|
||||
|
||||
struct SpriteSet {
|
||||
struct ActiveSprite {
|
||||
int index = 0;
|
||||
int row = 0;
|
||||
|
||||
uint8_t info[4];
|
||||
uint8_t image[2];
|
||||
|
||||
int shift_position = 0;
|
||||
} active_sprites[4];
|
||||
int active_sprite_slot = 0;
|
||||
} sprite_sets_[2];
|
||||
int active_sprite_set_ = 0;
|
||||
bool sprites_stopped_ = false;
|
||||
|
||||
int access_pointer_ = 0;
|
||||
|
||||
inline void test_sprite(int sprite_number);
|
||||
inline void get_sprite_contents(int start, int cycles, int screen_row);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* TMS9918Base_hpp */
|
@ -1234,6 +1234,7 @@
|
||||
4BCF1FAA1DADD41B0039D2E7 /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = StaticAnalyser.hpp; path = ../../StaticAnalyser/Oric/StaticAnalyser.hpp; sourceTree = "<group>"; };
|
||||
4BD14B0F1D74627C0088EAD6 /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = StaticAnalyser.cpp; path = ../../StaticAnalyser/Acorn/StaticAnalyser.cpp; sourceTree = "<group>"; };
|
||||
4BD14B101D74627C0088EAD6 /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = StaticAnalyser.hpp; path = ../../StaticAnalyser/Acorn/StaticAnalyser.hpp; sourceTree = "<group>"; };
|
||||
4BD388411FE34E010042B588 /* 9918Base.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = 9918Base.hpp; path = 9918/Implementation/9918Base.hpp; sourceTree = "<group>"; };
|
||||
4BD3A3091EE755C800B5B501 /* Video.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Video.cpp; path = ZX8081/Video.cpp; sourceTree = "<group>"; };
|
||||
4BD3A30A1EE755C800B5B501 /* Video.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Video.hpp; path = ZX8081/Video.hpp; sourceTree = "<group>"; };
|
||||
4BD468F51D8DF41D0084958B /* 1770.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = 1770.cpp; path = 1770/1770.cpp; sourceTree = "<group>"; };
|
||||
@ -1382,6 +1383,7 @@
|
||||
children = (
|
||||
4B0E04F91FC9FA3100F43484 /* 9918.cpp */,
|
||||
4B0E04F81FC9FA3000F43484 /* 9918.hpp */,
|
||||
4BD388431FE34E060042B588 /* Implementation */,
|
||||
);
|
||||
name = 9918;
|
||||
sourceTree = "<group>";
|
||||
@ -2615,6 +2617,14 @@
|
||||
name = Acorn;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4BD388431FE34E060042B588 /* Implementation */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4BD388411FE34E010042B588 /* 9918Base.hpp */,
|
||||
);
|
||||
name = Implementation;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4BD468F81D8DF4290084958B /* 1770 */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -15,6 +15,7 @@ SOURCES += glob.glob('../../Components/6522/Implementation/*.cpp')
|
||||
SOURCES += glob.glob('../../Components/6560/*.cpp')
|
||||
SOURCES += glob.glob('../../Components/8272/*.cpp')
|
||||
SOURCES += glob.glob('../../Components/9918/*.cpp')
|
||||
SOURCES += glob.glob('../../Components/9918/Implementation/*.cpp')
|
||||
SOURCES += glob.glob('../../Components/AY38910/*.cpp')
|
||||
|
||||
SOURCES += glob.glob('../../Concurrency/*.cpp')
|
||||
|
Loading…
Reference in New Issue
Block a user