1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-12-01 11:49:58 +00:00
CLK/Components/9918/Implementation/YamahaCommands.hpp

385 lines
9.9 KiB
C++
Raw Normal View History

2023-01-26 16:59:27 +00:00
//
// YamahaCommands.hpp
// Clock Signal
//
// Created by Thomas Harte on 26/01/2023.
// Copyright © 2023 Thomas Harte. All rights reserved.
//
#ifndef YamahaCommands_hpp
#define YamahaCommands_hpp
#include "AccessEnums.hpp"
2023-03-26 03:22:34 +00:00
namespace TI::TMS {
2023-01-26 16:59:27 +00:00
// MARK: - Generics.
2023-01-27 00:51:56 +00:00
struct Vector {
int v[2]{};
template <int offset, bool high> void set(uint8_t value) {
constexpr uint8_t mask = high ? (offset ? 0x3 : 0x1) : 0xff;
constexpr int shift = high ? 8 : 0;
2023-02-02 03:57:33 +00:00
v[offset] = (v[offset] & ~(mask << shift)) | ((value & mask) << shift);
2023-01-27 00:51:56 +00:00
}
2023-01-27 16:57:40 +00:00
template <int offset> void add(int amount) {
v[offset] += amount;
if constexpr (offset == 1) {
v[offset] &= 0x3ff;
} else {
v[offset] &= 0x1ff;
}
}
2023-01-27 00:51:56 +00:00
Vector & operator += (const Vector &rhs) {
2023-01-27 16:57:40 +00:00
add<0>(rhs.v[0]);
add<1>(rhs.v[1]);
2023-01-27 00:51:56 +00:00
return *this;
}
};
struct Colour {
void set(uint8_t value) {
colour = value;
colour4bpp = uint8_t((value & 0xf) | (value << 4));
colour2bpp = uint8_t((colour4bpp & 0x33) | ((colour4bpp & 0x33) << 2));
}
void reset() {
colour = 0x00;
colour4bpp = 0xff;
}
bool has_value() const {
return (colour & 0xf) == (colour4bpp & 0xf);
}
/// Colour as written by the CPU.
uint8_t colour = 0x00;
/// The low four bits of the CPU-written colour, repeated twice.
uint8_t colour4bpp = 0xff;
/// The low two bits of the CPU-written colour, repeated four times.
uint8_t colour2bpp = 0xff;
};
2023-01-26 16:59:27 +00:00
struct CommandContext {
2023-01-27 00:51:56 +00:00
Vector source;
Vector destination;
Vector size;
2023-01-26 16:59:27 +00:00
uint8_t arguments = 0;
Colour colour;
Colour latched_colour;
enum class LogicalOperation {
Copy = 0b0000,
And = 0b0001,
Or = 0b0010,
Xor = 0b0011,
Not = 0b0100,
};
LogicalOperation pixel_operation;
bool test_source;
2023-01-26 16:59:27 +00:00
};
struct ModeDescription {
int width = 256;
int pixels_per_byte = 4;
bool rotate_address = false;
2023-05-16 17:01:23 +00:00
int start_cycle = 0;
int end_cycle = 0;
};
2023-01-26 16:59:27 +00:00
struct Command {
// In net:
//
// This command is blocked until @c access has been performed, reading
// from or writing to @c value. It should not be performed until at least
// @c cycles have passed.
enum class AccessType {
/// Plots a single pixel of the current contextual colour at @c destination,
/// which occurs as a read, then a 24-cycle gap, then a write.
2023-01-29 02:30:45 +00:00
PlotPoint,
/// Blocks until the next CPU write to the colour register.
2023-01-29 18:22:56 +00:00
WaitForColourReceipt,
2023-01-31 18:35:39 +00:00
/// Writes an entire byte to the address containing the current @c destination.
2023-01-31 18:35:39 +00:00
WriteByte,
/// Copies a single pixel from @c source location to @c destination,
/// being a read, a 32-cycle gap, then a PlotPoint.
CopyPoint,
/// Copies a complete byte from @c source location to @c destination,
/// being a read, a 24-cycle gap, then a write.
CopyByte,
2023-03-14 02:51:01 +00:00
/// Copies a single pixel from @c source to the colour status register.
ReadPoint,
// ReadByte,
// WaitForColourSend,
};
AccessType access = AccessType::PlotPoint;
2023-01-26 16:59:27 +00:00
int cycles = 0;
2023-01-29 02:45:05 +00:00
bool is_cpu_transfer = false;
2023-03-09 04:12:02 +00:00
bool y_only = false;
2023-01-26 16:59:27 +00:00
/// Current command parameters.
CommandContext &context;
ModeDescription &mode_description;
Command(CommandContext &context, ModeDescription &mode_description) : context(context), mode_description(mode_description) {}
virtual ~Command() {}
2023-01-26 16:59:27 +00:00
/// @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, being provided with the
/// number of pixels per byte in the current screen mode.
virtual void advance() = 0;
2023-01-31 18:35:39 +00:00
protected:
template <int axis, bool include_source> void advance_axis(int offset = 1) {
context.destination.add<axis>(context.arguments & (0x4 << axis) ? -offset : offset);
if constexpr (include_source) {
context.source.add<axis>(context.arguments & (0x4 << axis) ? -offset : offset);
}
2023-01-31 18:35:39 +00:00
}
2023-01-26 16:59:27 +00:00
};
namespace Commands {
2023-02-03 02:55:00 +00:00
// MARK: - Line drawing.
2023-01-27 03:02:40 +00:00
/// Implements the LINE command, which is plain-old Bresenham.
///
/// Per Grauw timing is:
///
/// * 88 cycles between every pixel plot;
/// * plus an additional 32 cycles if a step along the minor axis is taken.
struct Line: public Command {
public:
Line(CommandContext &context, ModeDescription &mode_description) : Command(context, mode_description) {
2023-01-27 16:57:40 +00:00
// context.destination = start position;
// context.size.v[0] = long side dots;
// context.size.v[1] = short side dots;
// context.arguments => direction
2023-01-27 16:57:40 +00:00
position_ = context.size.v[1];
numerator_ = position_ << 1;
2023-01-27 16:57:40 +00:00
denominator_ = context.size.v[0] << 1;
2023-01-27 16:57:40 +00:00
cycles = 32;
access = AccessType::PlotPoint;
}
bool done() final {
2023-01-27 16:57:40 +00:00
return !context.size.v[0];
}
void advance() final {
2023-01-27 16:57:40 +00:00
--context.size.v[0];
cycles = 88;
2023-01-27 16:57:40 +00:00
// b0: 1 => long direction is y;
// 0 => long direction is x.
//
// b2: 1 => x direction is left;
// 0 => x direction is right.
//
// b3: 1 => y direction is up;
// 0 => y direction is down.
if(context.arguments & 0x1) {
advance_axis<1, false>();
2023-01-27 16:57:40 +00:00
} else {
advance_axis<0, false>();
2023-01-27 16:57:40 +00:00
}
position_ -= numerator_;
if(position_ < 0) {
position_ += denominator_;
2023-01-27 16:57:40 +00:00
cycles += 32;
if(context.arguments & 0x1) {
advance_axis<0, false>();
2023-01-27 16:57:40 +00:00
} else {
advance_axis<1, false>();
2023-01-27 16:57:40 +00:00
}
}
}
private:
int position_, numerator_, denominator_, duration_;
};
2023-02-03 02:55:00 +00:00
// MARK: - Single pixel manipulation.
2023-03-14 03:21:19 +00:00
/// Implements the PSET command, which plots a single pixel and POINT, which reads one.
2023-01-27 03:02:40 +00:00
///
2023-03-14 03:21:19 +00:00
/// No timings are documented, so this'll output or input as quickly as possible.
2023-03-14 02:51:01 +00:00
template <bool is_read> struct Point: public Command {
2023-01-27 02:52:41 +00:00
public:
Point(CommandContext &context, ModeDescription &mode_description) : Command(context, mode_description) {
cycles = 0; // TODO.
2023-03-14 02:51:01 +00:00
access = is_read ? AccessType::ReadPoint : AccessType::PlotPoint;
2023-01-27 02:52:41 +00:00
}
bool done() final {
2023-01-27 03:02:40 +00:00
return done_;
}
void advance() final {
2023-01-27 03:02:40 +00:00
done_ = true;
2023-01-27 02:52:41 +00:00
}
2023-01-27 03:02:40 +00:00
private:
bool done_ = false;
2023-01-27 02:52:41 +00:00
};
2023-02-03 02:55:00 +00:00
// MARK: - Rectangular base.
2023-02-03 02:49:05 +00:00
/// Useful base class for anything that does logical work in a rectangle.
template <bool logical, bool include_source> struct Rectangle: public Command {
2023-01-29 02:30:45 +00:00
public:
Rectangle(CommandContext &context, ModeDescription &mode_description) : Command(context, mode_description) {
if constexpr (include_source) {
start_x_[0] = context.source.v[0];
}
start_x_[1] = context.destination.v[0];
2023-01-29 02:30:45 +00:00
width_ = context.size.v[0];
2023-02-08 03:28:18 +00:00
if(!width_) {
// Width = 0 => maximal width for this mode.
2023-02-08 03:28:18 +00:00
// (aside: it's still unclear to me whether commands are
// automatically clipped to the display; I think so but
// don't want to spend any time on it until I'm certain)
2023-03-26 03:22:34 +00:00
// context.size.v[0] = width_ = mode_description.width;
2023-02-08 03:28:18 +00:00
}
2023-01-29 02:30:45 +00:00
}
2023-02-03 02:49:05 +00:00
/// Advances the current destination and, if @c include_source is @c true also the source;
/// @returns @c true if a new row was started; @c false otherwise.
bool advance_pixel() {
2023-02-03 02:49:05 +00:00
if constexpr (logical) {
advance_axis<0, include_source>();
--context.size.v[0];
if(context.size.v[0]) {
return false;
}
} else {
advance_axis<0, include_source>(mode_description.pixels_per_byte);
context.size.v[0] -= mode_description.pixels_per_byte;
2023-01-29 02:30:45 +00:00
if(context.size.v[0] & ~(mode_description.pixels_per_byte - 1)) {
2023-02-03 02:49:05 +00:00
return false;
}
}
2023-01-29 02:30:45 +00:00
2023-02-03 02:49:05 +00:00
context.size.v[0] = width_;
if constexpr (include_source) {
context.source.v[0] = start_x_[0];
}
context.destination.v[0] = start_x_[1];
2023-01-29 02:30:45 +00:00
2023-02-03 02:49:05 +00:00
advance_axis<1, include_source>();
--context.size.v[1];
2023-01-29 02:43:14 +00:00
2023-02-03 02:49:05 +00:00
return true;
2023-01-29 02:30:45 +00:00
}
bool done() final {
return !context.size.v[1] || !width_;
}
private:
int start_x_[2]{}, width_ = 0;
2023-01-29 02:30:45 +00:00
};
2023-02-05 02:29:44 +00:00
// MARK: - Rectangular moves to/from CPU.
2023-02-03 02:55:00 +00:00
2023-03-04 02:40:48 +00:00
template <bool logical> struct MoveFromCPU: public Rectangle<logical, false> {
MoveFromCPU(CommandContext &context, ModeDescription &mode_description) : Rectangle<logical, false>(context, mode_description) {
2023-03-04 02:40:48 +00:00
Command::is_cpu_transfer = true;
2023-01-31 18:35:39 +00:00
2023-02-03 02:49:05 +00:00
// This command is started with the first colour ready to transfer.
2023-03-04 02:40:48 +00:00
Command::cycles = 32;
Command::access = logical ? Command::AccessType::PlotPoint : Command::AccessType::WriteByte;
2023-01-31 18:35:39 +00:00
}
void advance() final {
2023-03-04 02:40:48 +00:00
switch(Command::access) {
2023-02-03 02:49:05 +00:00
default: break;
2023-03-04 02:40:48 +00:00
case Command::AccessType::WaitForColourReceipt:
Command::cycles = 32;
Command::access = logical ? Command::AccessType::PlotPoint : Command::AccessType::WriteByte;
2023-02-03 02:49:05 +00:00
break;
2023-03-04 02:40:48 +00:00
case Command::AccessType::WriteByte:
case Command::AccessType::PlotPoint:
Command::cycles = 0;
Command::access = Command::AccessType::WaitForColourReceipt;
if(Rectangle<logical, false>::advance_pixel()) {
2023-03-04 02:40:48 +00:00
Command::cycles = 64;
2023-02-03 02:49:05 +00:00
// TODO: I'm not sure this will be honoured per the outer wrapping.
}
break;
}
}
};
2023-02-05 02:29:44 +00:00
// MARK: - Rectangular moves within VRAM.
2023-03-09 03:36:06 +00:00
enum class MoveType {
Logical,
HighSpeed,
YOnly,
2023-02-05 02:29:44 +00:00
};
2023-03-09 03:36:06 +00:00
template <MoveType type> struct Move: public Rectangle<type == MoveType::Logical, true> {
static constexpr bool is_logical = type == MoveType::Logical;
static constexpr bool is_y_only = type == MoveType::YOnly;
2023-03-10 03:24:53 +00:00
using RectangleBase = Rectangle<is_logical, true>;
2023-03-09 03:36:06 +00:00
Move(CommandContext &context, ModeDescription &mode_description) : RectangleBase(context, mode_description) {
2023-03-09 03:36:06 +00:00
Command::access = is_logical ? Command::AccessType::CopyPoint : Command::AccessType::CopyByte;
Command::cycles = is_y_only ? 0 : 64;
2023-03-09 04:12:02 +00:00
Command::y_only = is_y_only;
2023-02-05 02:29:44 +00:00
}
void advance() final {
2023-03-09 03:36:06 +00:00
Command::cycles = is_y_only ? 40 : 64;
if(RectangleBase::advance_pixel()) {
2023-03-09 03:36:06 +00:00
Command::cycles += is_y_only ? 0 : 64;
2023-02-05 02:29:44 +00:00
}
}
};
// MARK: - Rectangular fills.
2023-02-03 02:55:00 +00:00
template <bool logical> struct Fill: public Rectangle<logical, false> {
using RectangleBase = Rectangle<logical, false>;
2023-01-31 18:35:39 +00:00
Fill(CommandContext &context, ModeDescription &mode_description) : RectangleBase(context, mode_description) {
Command::cycles = logical ? 64 : 56;
Command::access = logical ? Command::AccessType::PlotPoint : Command::AccessType::WriteByte;
2023-02-04 15:31:41 +00:00
}
void advance() final {
Command::cycles = logical ? 72 : 48;
if(RectangleBase::advance_pixel()) {
Command::cycles += logical ? 64 : 56;
2023-02-04 15:31:41 +00:00
}
}
};
2023-01-26 16:59:27 +00:00
}
}
#endif /* YamahaCommands_hpp */