1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-11-26 08:49:37 +00:00

Merge branch 'MSX2' of github.com:TomHarte/CLK into MSX2

This commit is contained in:
Thomas Harte 2023-02-01 22:25:12 -05:00
commit 8f5c7fcabc
6 changed files with 174 additions and 105 deletions

View File

@ -46,8 +46,8 @@ Base<personality>::Base() :
// at a random position.
fetch_pointer_.row = rand() % 262;
fetch_pointer_.column = rand() % (Timing<personality>::CyclesPerLine - output_lag);
output_pointer_.row = output_pointer_.row;
output_pointer_.column = output_pointer_.column + output_lag;
output_pointer_.row = fetch_pointer_.row;
output_pointer_.column = fetch_pointer_.column + output_lag;
}
template <Personality personality>
@ -490,7 +490,7 @@ void TMS9918<personality>::run_for(const HalfCycles cycles) {
this->asked_for_write_area_ = true;
this->pixel_origin_ = this->pixel_target_ = reinterpret_cast<uint32_t *>(
this->crt_.begin_data(line_buffer.pixel_count)
this->crt_.begin_data(size_t(line_buffer.pixel_count))
);
}
@ -509,7 +509,7 @@ void TMS9918<personality>::run_for(const HalfCycles cycles) {
if(end == line_buffer.next_border_column) {
const int length = line_buffer.next_border_column - line_buffer.first_pixel_output_column;
this->crt_.output_data(from_internal<personality, Clock::CRT>(length), line_buffer.pixel_count);
this->crt_.output_data(from_internal<personality, Clock::CRT>(length), size_t(line_buffer.pixel_count));
this->pixel_origin_ = this->pixel_target_ = nullptr;
this->asked_for_write_area_ = false;
}
@ -647,32 +647,11 @@ void Base<personality>::commit_register(int reg, uint8_t value) {
if(sprites_magnified_) sprite_height_ <<= 1;
break;
case 2:
pattern_name_address_ = size_t((value & 0xf) << 10) | 0x3ff;
break;
case 3:
colour_table_address_ =
(colour_table_address_ & ~0x3fc0) |
(value << 6) |
0x3f;
break;
case 4:
pattern_generator_table_address_ = size_t((value & 0x07) << 11) | 0x7ff;
// TODO: don't mask off so many bits for, at least, the Yamahas.
break;
case 5:
sprite_attribute_table_address_ =
(sprite_attribute_table_address_ & ~0x3d80) |
((value << 7) & 0x3f80) |
0x7f;
break;
case 6:
sprite_generator_table_address_ = size_t((value & 0x07) << 11) | 0x7ff;
break;
case 2: install_field<10>(pattern_name_address_, value); break;
case 3: install_field<6>(colour_table_address_, value); break;
case 4: install_field<11>(pattern_generator_table_address_, value); break;
case 5: install_field<7>(sprite_attribute_table_address_, value); break;
case 6: install_field<11>(sprite_generator_table_address_, value); break;
case 7:
text_colour_ = value >> 4;
@ -757,11 +736,6 @@ void Base<personality>::commit_register(int reg, uint8_t value) {
LOG("Screen mode: " << int(current_screen_mode()));
break;
case 2:
// Retain extra addressing bits.
pattern_name_address_ = size_t((value & 0x7f) << 10) | 0x3ff;
break;
case 8:
LOG("TODO: Yamaha VRAM organisation, sprite disable, etc; " << PADHEX(2) << +value);
// b7: "1 = input on colour bus, enable mouse; 1 = output on colour bus, disable mouse" [documentation clearly in error]
@ -783,19 +757,11 @@ void Base<personality>::commit_register(int reg, uint8_t value) {
// b0: 1 = [dot clock] DLCLK is input; 0 = DLCLK is output
break;
case 10:
colour_table_address_ =
(colour_table_address_ & ~0x1c000) |
((value << 14) & 0x1c000);
// b0b2: A14A16 of the colour table.
break;
case 10: install_field<14>(colour_table_address_, value); break;
case 11:
sprite_attribute_table_address_ =
(sprite_attribute_table_address_ & ~0x18000) |
((value << 15) & 0x18000);
// b0b1: A15A16 of the sprite table.
break;
case 11: install_field<15>(sprite_attribute_table_address_, value); break;
case 12:
LOG("TODO: Yamaha text and background blink colour; " << PADHEX(2) << +value);
@ -808,9 +774,7 @@ void Base<personality>::commit_register(int reg, uint8_t value) {
// b4b7: display time for even page.
break;
case 14:
ram_pointer_ = (ram_pointer_ & ~0x1c000) | ((value << 14) & 0x1c000);
break;
case 14: install_field<14>(ram_pointer_, value); break;
case 15:
Storage<personality>::selected_status_ = value & 0xf;
@ -865,15 +829,23 @@ void Base<personality>::commit_register(int reg, uint8_t value) {
case 44:
Storage<personality>::command_context_.colour = value;
Storage<personality>::command_context_.colour4bpp = (value & 0xf) | (value << 4);
Storage<personality>::command_context_.colour2bpp = (value & 0x3) | ((value & 0x3) << 2) | ((value & 0x3) << 4) | ((value & 0x3) << 6);
Storage<personality>::command_context_.colour4bpp = uint8_t(
(value & 0xf) |
(value << 4)
);
Storage<personality>::command_context_.colour2bpp = uint8_t(
(value & 0x3) |
((value & 0x3) << 2) |
((value & 0x3) << 4) |
((value & 0x3) << 6)
);
// Check whether a command was blocked on this.
if(
Storage<personality>::command_ &&
Storage<personality>::command_->access == Command::AccessType::WaitForColourReceipt
) {
Storage<personality>::command_->advance();
Storage<personality>::command_->advance(pixels_per_byte(this->screen_mode_));
Storage<personality>::update_command_step(fetch_pointer_.column);
}
break;
@ -917,7 +889,7 @@ void Base<personality>::commit_register(int reg, uint8_t value) {
case 0b1010: break; // TODO: lmcm. [logical move, VRAM to CPU]
case 0b1011: Begin(LogicalMoveFromCPU); break; // LMMC [logical move, CPU to VRAM]
case 0b1100: break; // TODO: hmmv. [high-speed move, VRAM to VDP]
case 0b1100: Begin(HighSpeedFill); break; // HMMV [high-speed move, VDP to VRAM]
case 0b1101: break; // TODO: hmmm. [high-speed move, VRAM to VRAM]
case 0b1110: break; // TODO: ymmm. [high-speed move, y only, VRAM to VRAM]
case 0b1111: break; // TODO: hmmc. [high-speed move, CPU to VRAM]
@ -950,13 +922,13 @@ void Base<personality>::write_register(uint8_t value) {
write_phase_ = true;
// The initial write should half update the access pointer.
ram_pointer_ = (ram_pointer_ & ~0xff) | low_write_;
install_field<0>(ram_pointer_, value);
return;
}
// The RAM pointer is always set on a second write, regardless of
// whether the caller is intending to enqueue a VDP operation.
ram_pointer_ = (ram_pointer_ & ~0x3f00) | ((value << 8) & 0x3f00);
install_field<8, 0x3f>(ram_pointer_, value);
write_phase_ = false;
if(value & 0x80) {

View File

@ -96,7 +96,7 @@ struct LineBuffer {
*/
int first_pixel_output_column = 94;
int next_border_column = 334;
size_t pixel_count = 256;
int pixel_count = 256;
// An active sprite is one that has been selected for composition onto
// this line.
@ -131,11 +131,15 @@ template <Personality personality, typename Enable = void> struct Storage {
};
template <> struct Storage<Personality::TMS9918A> {
using AddressT = uint16_t;
void begin_line(ScreenMode, bool, bool) {}
};
// Yamaha-specific storage.
template <Personality personality> struct Storage<personality, std::enable_if_t<is_yamaha_vdp(personality)>> {
using AddressT = uint32_t;
int selected_status_ = 0;
int indirect_register_ = 0;
@ -202,6 +206,7 @@ template <Personality personality> struct Storage<personality, std::enable_if_t<
None,
ReadPixel,
WritePixel,
WriteByte,
};
CommandStep next_command_step_ = CommandStep::None;
int minimum_command_column_ = 0;
@ -227,6 +232,9 @@ template <Personality personality> struct Storage<personality, std::enable_if_t<
// i.e. nothing to do until a colour is received.
next_command_step_ = CommandStep::None;
break;
case Command::AccessType::WriteByte:
next_command_step_ = CommandStep::WriteByte;
break;
}
}
@ -357,6 +365,8 @@ template <Personality personality> struct Storage<personality, std::enable_if_t<
// Master System-specific storage.
template <Personality personality> struct Storage<personality, std::enable_if_t<is_sega_vdp(personality)>> {
using AddressT = uint16_t;
// The SMS VDP has a programmer-set colour palette, with a dedicated patch of RAM. But the RAM is only exactly
// fast enough for the pixel clock. So when the programmer writes to it, that causes a one-pixel glitch; there
// isn't the bandwidth for the read both write to occur simultaneously. The following buffer therefore keeps
@ -371,8 +381,6 @@ template <Personality personality> struct Storage<personality, std::enable_if_t<
uint32_t colour_ram_[32];
bool cram_is_selected_ = false;
// Fields below affect only the Master System output mode.
// Programmer-set flags.
bool vertical_scroll_lock_ = false;
bool horizontal_scroll_lock_ = false;
@ -387,10 +395,10 @@ template <Personality personality> struct Storage<personality, std::enable_if_t<
uint8_t latched_vertical_scroll_ = 0;
// Various resource addresses with VDP-version-specific modifications
// built int.
size_t pattern_name_address_;
size_t sprite_attribute_table_address_;
size_t sprite_generator_table_address_;
// built in.
AddressT pattern_name_address_;
AddressT sprite_attribute_table_address_;
AddressT sprite_generator_table_address_;
void begin_line(ScreenMode, bool, bool) {}
};
@ -440,6 +448,18 @@ template <Personality personality> struct Base: public Storage<personality> {
Outputs::CRT::CRT crt_;
TVStandard tv_standard_ = TVStandard::NTSC;
using AddressT = typename Storage<personality>::AddressT;
/// Mutates @c target such that @c source & @c source_mask replaces the bits that currently start
/// at @c shift bits from least significant. Subsequently ensures @c target is constrained by the
/// applicable @c memory_mask.
template <int shift, uint8_t source_mask = 0xff> void install_field(AddressT &target, uint8_t source) {
constexpr auto mask = AddressT(~(source_mask << shift));
target = (
(target & mask) |
AddressT((source & source_mask) << shift)
) & memory_mask(personality);
}
// Personality-specific metrics and converters.
ClockConverter<personality> clock_converter_;
@ -448,7 +468,7 @@ template <Personality personality> struct Base: public Storage<personality> {
std::array<uint8_t, memory_size(personality)> ram_;
// State of the DRAM/CRAM-access mechanism.
size_t ram_pointer_ = 0;
AddressT ram_pointer_ = 0;
uint8_t read_ahead_buffer_ = 0;
MemoryAccess queued_access_ = MemoryAccess::None;
int minimum_access_column_ = 0;
@ -471,11 +491,11 @@ template <Personality personality> struct Base: public Storage<personality> {
int sprite_height_ = 8;
// Programmer-specified addresses.
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.
AddressT pattern_name_address_ = memory_mask(personality); // Address of the tile map.
AddressT colour_table_address_ = memory_mask(personality); // Address of the colour map (if applicable).
AddressT pattern_generator_table_address_ = memory_mask(personality); // Address of the tile contents.
AddressT sprite_attribute_table_address_ = memory_mask(personality); // Address of the sprite list.
AddressT sprite_generator_table_address_ = memory_mask(personality); // Address of the sprite contents.
// Default colours.
uint8_t text_colour_ = 0;
@ -596,29 +616,33 @@ template <Personality personality> struct Base: public Storage<personality> {
return ScreenMode::Blank;
}
uint32_t command_address() const {
AddressT command_address() const {
if constexpr (is_yamaha_vdp(personality)) {
switch(this->screen_mode_) {
default:
case ScreenMode::YamahaGraphics4: // 256 pixels @ 4bpp
return
return AddressT(
(Storage<personality>::command_->location.v[0] >> 1) +
(Storage<personality>::command_->location.v[1] << 7);
(Storage<personality>::command_->location.v[1] << 7)
);
case ScreenMode::YamahaGraphics5: // 512 pixels @ 2bpp
return
return AddressT(
(Storage<personality>::command_->location.v[0] >> 2) +
(Storage<personality>::command_->location.v[1] << 7);
(Storage<personality>::command_->location.v[1] << 7)
);
case ScreenMode::YamahaGraphics6: // 512 pixels @ 4bpp
return
return AddressT(
(Storage<personality>::command_->location.v[0] >> 1) +
(Storage<personality>::command_->location.v[1] << 8);
(Storage<personality>::command_->location.v[1] << 8)
);
case ScreenMode::YamahaGraphics7: // 256 pixels @ 8bpp
return
return AddressT(
(Storage<personality>::command_->location.v[0] >> 0) +
(Storage<personality>::command_->location.v[1] << 8);
(Storage<personality>::command_->location.v[1] << 8)
);
}
} else {
return 0;
@ -713,16 +737,22 @@ template <Personality personality> struct Base: public Storage<personality> {
ram_[address] = Storage<personality>::command_latch_;
Storage<personality>::command_->advance();
Storage<personality>::command_->advance(pixels_per_byte(this->screen_mode_));
Storage<personality>::update_command_step(access_column);
} break;
case CommandStep::WriteByte:
ram_[command_address()] = Storage<personality>::command_context_.colour;
Storage<personality>::command_->advance(pixels_per_byte(this->screen_mode_));
Storage<personality>::update_command_step(access_column);
break;
}
}
return;
}
size_t address = ram_pointer_;
AddressT address = ram_pointer_;
++ram_pointer_;
if constexpr (is_yamaha_vdp(personality)) {

View File

@ -39,6 +39,24 @@ enum class ScreenMode {
YamahaGraphics2 = Graphics,
};
constexpr int pixels_per_byte(ScreenMode mode) {
switch(mode) {
default:
case ScreenMode::Blank: return 0;
case ScreenMode::Text: return 6;
case ScreenMode::MultiColour: return 2;
case ScreenMode::ColouredText: return 8;
case ScreenMode::Graphics: return 8;
case ScreenMode::SMSMode4: return 2;
case ScreenMode::YamahaText80: return 6;
case ScreenMode::YamahaGraphics3: return 8;
case ScreenMode::YamahaGraphics4: return 2;
case ScreenMode::YamahaGraphics5: return 4;
case ScreenMode::YamahaGraphics6: return 2;
case ScreenMode::YamahaGraphics7: return 1;
}
}
enum class FetchMode {
Text,
Character,

View File

@ -469,7 +469,7 @@ template<bool use_end> void Base<personality>::fetch_sms(LineBuffer &line_buffer
template <Personality personality>
template<ScreenMode mode> void Base<personality>::fetch_yamaha([[maybe_unused]] LineBuffer &line_buffer, [[maybe_unused]] int y, int end) {
const int rotated_name_ = pattern_name_address_ >> 1;
const AddressT rotated_name_ = pattern_name_address_ >> 1;
const uint8_t *const ram2 = &ram_[65536];
while(Storage<personality>::next_event_->offset < end) {
@ -488,10 +488,10 @@ template<ScreenMode mode> void Base<personality>::fetch_yamaha([[maybe_unused]]
const int start = (y << 7) | column | 0x1'8000;
line_buffer.bitmap[column + 0] = ram_[pattern_name_address_ & (start + 0)];
line_buffer.bitmap[column + 1] = ram_[pattern_name_address_ & (start + 1)];
line_buffer.bitmap[column + 2] = ram_[pattern_name_address_ & (start + 2)];
line_buffer.bitmap[column + 3] = ram_[pattern_name_address_ & (start + 3)];
line_buffer.bitmap[column + 0] = ram_[pattern_name_address_ & AddressT(start + 0)];
line_buffer.bitmap[column + 1] = ram_[pattern_name_address_ & AddressT(start + 1)];
line_buffer.bitmap[column + 2] = ram_[pattern_name_address_ & AddressT(start + 2)];
line_buffer.bitmap[column + 3] = ram_[pattern_name_address_ & AddressT(start + 3)];
} break;
case ScreenMode::YamahaGraphics6:
@ -502,14 +502,14 @@ template<ScreenMode mode> void Base<personality>::fetch_yamaha([[maybe_unused]]
const int start = (y << 7) | column | 0x1'8000;
// Fetch from alternate banks.
line_buffer.bitmap[column + 0] = ram_[rotated_name_ & (start + 0)];
line_buffer.bitmap[column + 1] = ram2[rotated_name_ & (start + 0)];
line_buffer.bitmap[column + 2] = ram_[rotated_name_ & (start + 1)];
line_buffer.bitmap[column + 3] = ram2[rotated_name_ & (start + 1)];
line_buffer.bitmap[column + 4] = ram_[rotated_name_ & (start + 2)];
line_buffer.bitmap[column + 5] = ram2[rotated_name_ & (start + 2)];
line_buffer.bitmap[column + 6] = ram_[rotated_name_ & (start + 3)];
line_buffer.bitmap[column + 7] = ram2[rotated_name_ & (start + 3)];
line_buffer.bitmap[column + 0] = ram_[rotated_name_ & AddressT(start + 0)];
line_buffer.bitmap[column + 1] = ram2[rotated_name_ & AddressT(start + 0)];
line_buffer.bitmap[column + 2] = ram_[rotated_name_ & AddressT(start + 1)];
line_buffer.bitmap[column + 3] = ram2[rotated_name_ & AddressT(start + 1)];
line_buffer.bitmap[column + 4] = ram_[rotated_name_ & AddressT(start + 2)];
line_buffer.bitmap[column + 5] = ram2[rotated_name_ & AddressT(start + 2)];
line_buffer.bitmap[column + 6] = ram_[rotated_name_ & AddressT(start + 3)];
line_buffer.bitmap[column + 7] = ram2[rotated_name_ & AddressT(start + 3)];
} break;
default:

View File

@ -79,6 +79,9 @@ struct Command {
/// Blocks until the next CPU write to the colour register.
WaitForColourReceipt,
/// Writes an entire byte to the location containing the current @c location.
WriteByte,
};
AccessType access = AccessType::PlotPoint;
int cycles = 0;
@ -88,12 +91,19 @@ struct Command {
/// Current command parameters.
CommandContext &context;
Command(CommandContext &context) : context(context) {}
virtual ~Command() {}
/// @returns @c true if all output from this command is done; @c false otherwise.
virtual bool done() = 0;
/// Repopulates the fields above with the next action to take.
virtual void advance() = 0;
/// Repopulates the fields above with the next action to take, being provided with the
/// number of pixels per byte in the current screen mode.
virtual void advance(int pixels_per_byte) = 0;
protected:
template <int axis> void advance_axis(int offset = 1) {
context.destination.add<axis>(context.arguments & (0x4 << axis) ? -offset : offset);
}
};
// MARK: - Line drawing.
@ -127,7 +137,7 @@ struct Line: public Command {
return !context.size.v[0];
}
void advance() final {
void advance(int) final {
--context.size.v[0];
cycles = 88;
@ -140,9 +150,9 @@ struct Line: public Command {
// b3: 1 => y direction is up;
// 0 => y direction is down.
if(context.arguments & 0x1) {
location.add<1>(context.arguments & 0x8 ? -1 : 1);
advance_axis<1>();
} else {
location.add<0>(context.arguments & 0x4 ? -1 : 1);
advance_axis<0>();
}
position_ -= numerator_;
@ -151,11 +161,13 @@ struct Line: public Command {
cycles += 32;
if(context.arguments & 0x1) {
location.add<0>(context.arguments & 0x4 ? -1 : 1);
advance_axis<0>();
} else {
location.add<1>(context.arguments & 0x8 ? -1 : 1);
advance_axis<1>();
}
}
location = context.destination;
}
private:
@ -177,7 +189,7 @@ struct PointSet: public Command {
return done_;
}
void advance() final {
void advance(int) final {
done_ = true;
}
@ -199,7 +211,7 @@ struct LogicalMoveFromCPU: public Command {
location = context.destination;
}
void advance() final {
void advance(int) final {
switch(access) {
default: break;
@ -212,7 +224,7 @@ struct LogicalMoveFromCPU: public Command {
case AccessType::PlotPoint:
cycles = 0;
access = AccessType::WaitForColourReceipt;
context.destination.add<0>(context.arguments & 0x4 ? -1 : 1);
advance_axis<0>();
--context.size.v[0];
if(!context.size.v[0]) {
@ -220,7 +232,7 @@ struct LogicalMoveFromCPU: public Command {
context.size.v[0] = width_;
context.destination.v[0] = start_x_;
context.destination.add<1>(context.arguments & 0x8 ? -1 : 1);
advance_axis<1>();
--context.size.v[1];
}
break;
@ -235,6 +247,43 @@ struct LogicalMoveFromCPU: public Command {
int start_x_ = 0, width_ = 0;
};
struct HighSpeedFill: public Command {
HighSpeedFill(CommandContext &context) : Command(context) {
start_x_ = context.destination.v[0];
width_ = context.size.v[0];
cycles = 56;
access = AccessType::WriteByte;
location = context.destination;
}
bool done() final {
return true;
}
void advance(int pixels_per_byte) final {
cycles = 48;
// TODO: step at byte speed, not pixel speed.
advance_axis<0>(pixels_per_byte);
--context.size.v[0];
if(!context.size.v[0]) {
cycles += 56;
context.size.v[0] = width_;
context.destination.v[0] = start_x_;
advance_axis<1>();
--context.size.v[1];
}
location = context.destination;
}
private:
int start_x_ = 0, width_ = 0;
};
}
}
}

View File

@ -19,7 +19,7 @@
namespace CPU {
/// Provides access to all intermediate parts of a larger int.
template <typename Full, typename Half> union alignas(Full) RegisterPair {
template <typename Full, typename Half> union alignas(Full) alignas(Half) RegisterPair {
RegisterPair(Full v) : full(v) {}
RegisterPair() {}