diff --git a/Components/9918/Implementation/9918.cpp b/Components/9918/Implementation/9918.cpp index 0f771b1c3..c89bfecc4 100644 --- a/Components/9918/Implementation/9918.cpp +++ b/Components/9918/Implementation/9918.cpp @@ -46,8 +46,8 @@ Base::Base() : // at a random position. fetch_pointer_.row = rand() % 262; fetch_pointer_.column = rand() % (Timing::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 @@ -490,7 +490,7 @@ void TMS9918::run_for(const HalfCycles cycles) { this->asked_for_write_area_ = true; this->pixel_origin_ = this->pixel_target_ = reinterpret_cast( - this->crt_.begin_data(line_buffer.pixel_count) + this->crt_.begin_data(size_t(line_buffer.pixel_count)) ); } @@ -509,7 +509,7 @@ void TMS9918::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(length), line_buffer.pixel_count); + this->crt_.output_data(from_internal(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::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::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::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); - // b0–b2: A14–A16 of the colour table. - break; + // b0–b2: A14–A16 of the colour table. + case 10: install_field<14>(colour_table_address_, value); break; - case 11: - sprite_attribute_table_address_ = - (sprite_attribute_table_address_ & ~0x18000) | - ((value << 15) & 0x18000); - // b0–b1: A15–A16 of the sprite table. - break; + // b0–b1: A15–A16 of the sprite table. + 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::commit_register(int reg, uint8_t value) { // b4–b7: 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::selected_status_ = value & 0xf; @@ -865,15 +829,23 @@ void Base::commit_register(int reg, uint8_t value) { case 44: Storage::command_context_.colour = value; - Storage::command_context_.colour4bpp = (value & 0xf) | (value << 4); - Storage::command_context_.colour2bpp = (value & 0x3) | ((value & 0x3) << 2) | ((value & 0x3) << 4) | ((value & 0x3) << 6); + Storage::command_context_.colour4bpp = uint8_t( + (value & 0xf) | + (value << 4) + ); + Storage::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::command_ && Storage::command_->access == Command::AccessType::WaitForColourReceipt ) { - Storage::command_->advance(); + Storage::command_->advance(pixels_per_byte(this->screen_mode_)); Storage::update_command_step(fetch_pointer_.column); } break; @@ -917,7 +889,7 @@ void Base::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::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) { diff --git a/Components/9918/Implementation/9918Base.hpp b/Components/9918/Implementation/9918Base.hpp index 92a69c829..abb85a747 100644 --- a/Components/9918/Implementation/9918Base.hpp +++ b/Components/9918/Implementation/9918Base.hpp @@ -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 struct Storage { }; template <> struct Storage { + using AddressT = uint16_t; + void begin_line(ScreenMode, bool, bool) {} }; // Yamaha-specific storage. template struct Storage> { + using AddressT = uint32_t; + int selected_status_ = 0; int indirect_register_ = 0; @@ -202,6 +206,7 @@ template struct Storage struct Storage struct Storage struct Storage> { + 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 struct Storage struct Storage struct Base: public Storage { Outputs::CRT::CRT crt_; TVStandard tv_standard_ = TVStandard::NTSC; + using AddressT = typename Storage::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 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 clock_converter_; @@ -448,7 +468,7 @@ template struct Base: public Storage { std::array 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 struct Base: public Storage { 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 struct Base: public Storage { 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::command_->location.v[0] >> 1) + - (Storage::command_->location.v[1] << 7); + (Storage::command_->location.v[1] << 7) + ); case ScreenMode::YamahaGraphics5: // 512 pixels @ 2bpp - return + return AddressT( (Storage::command_->location.v[0] >> 2) + - (Storage::command_->location.v[1] << 7); + (Storage::command_->location.v[1] << 7) + ); case ScreenMode::YamahaGraphics6: // 512 pixels @ 4bpp - return + return AddressT( (Storage::command_->location.v[0] >> 1) + - (Storage::command_->location.v[1] << 8); + (Storage::command_->location.v[1] << 8) + ); case ScreenMode::YamahaGraphics7: // 256 pixels @ 8bpp - return + return AddressT( (Storage::command_->location.v[0] >> 0) + - (Storage::command_->location.v[1] << 8); + (Storage::command_->location.v[1] << 8) + ); } } else { return 0; @@ -713,16 +737,22 @@ template struct Base: public Storage { ram_[address] = Storage::command_latch_; - Storage::command_->advance(); + Storage::command_->advance(pixels_per_byte(this->screen_mode_)); Storage::update_command_step(access_column); } break; + + case CommandStep::WriteByte: + ram_[command_address()] = Storage::command_context_.colour; + Storage::command_->advance(pixels_per_byte(this->screen_mode_)); + Storage::update_command_step(access_column); + break; } } return; } - size_t address = ram_pointer_; + AddressT address = ram_pointer_; ++ram_pointer_; if constexpr (is_yamaha_vdp(personality)) { diff --git a/Components/9918/Implementation/AccessEnums.hpp b/Components/9918/Implementation/AccessEnums.hpp index 66a80ec1e..5aa912183 100644 --- a/Components/9918/Implementation/AccessEnums.hpp +++ b/Components/9918/Implementation/AccessEnums.hpp @@ -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, diff --git a/Components/9918/Implementation/Fetch.hpp b/Components/9918/Implementation/Fetch.hpp index 18b55f1c0..b2a3d1f8b 100644 --- a/Components/9918/Implementation/Fetch.hpp +++ b/Components/9918/Implementation/Fetch.hpp @@ -469,7 +469,7 @@ template void Base::fetch_sms(LineBuffer &line_buffer template template void Base::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::next_event_->offset < end) { @@ -488,10 +488,10 @@ template void Base::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 void Base::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: diff --git a/Components/9918/Implementation/YamahaCommands.hpp b/Components/9918/Implementation/YamahaCommands.hpp index 2ad52c83e..6fd3e7cdc 100644 --- a/Components/9918/Implementation/YamahaCommands.hpp +++ b/Components/9918/Implementation/YamahaCommands.hpp @@ -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 void advance_axis(int offset = 1) { + context.destination.add(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; +}; + } } } diff --git a/Numeric/RegisterSizes.hpp b/Numeric/RegisterSizes.hpp index c0a86b92f..669227e9c 100644 --- a/Numeric/RegisterSizes.hpp +++ b/Numeric/RegisterSizes.hpp @@ -19,7 +19,7 @@ namespace CPU { /// Provides access to all intermediate parts of a larger int. -template union alignas(Full) RegisterPair { +template union alignas(Full) alignas(Half) RegisterPair { RegisterPair(Full v) : full(v) {} RegisterPair() {}