1
0
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:
Thomas Harte 2017-12-14 18:20:13 -08:00 committed by GitHub
commit d66a33f249
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 162 additions and 109 deletions

View File

@ -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() {

View File

@ -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 */

View 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 */

View File

@ -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 = (

View File

@ -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')