2021-07-22 22:43:07 +00:00
|
|
|
//
|
|
|
|
// Blitter.hpp
|
|
|
|
// Clock Signal
|
|
|
|
//
|
|
|
|
// Created by Thomas Harte on 22/07/2021.
|
|
|
|
// Copyright © 2021 Thomas Harte. All rights reserved.
|
|
|
|
//
|
|
|
|
|
|
|
|
#ifndef Blitter_hpp
|
|
|
|
#define Blitter_hpp
|
|
|
|
|
|
|
|
#include <cstddef>
|
|
|
|
#include <cstdint>
|
|
|
|
|
|
|
|
#include "../../ClockReceiver/ClockReceiver.hpp"
|
2021-09-15 00:51:32 +00:00
|
|
|
#include "DMADevice.hpp"
|
2021-07-22 22:43:07 +00:00
|
|
|
|
|
|
|
namespace Amiga {
|
|
|
|
|
2022-07-27 02:01:43 +00:00
|
|
|
/*!
|
|
|
|
Statefully provides the next access the Blitter should make.
|
|
|
|
|
|
|
|
TODO: determine the actual logic here, rather than
|
|
|
|
relying on tables.
|
|
|
|
*/
|
2022-07-26 21:05:05 +00:00
|
|
|
class BlitterSequencer {
|
|
|
|
public:
|
|
|
|
enum class Channel {
|
2022-07-27 02:01:43 +00:00
|
|
|
/// Tells the caller to calculate and load a new piece of output
|
2022-07-29 20:15:18 +00:00
|
|
|
/// into the output pipeline,
|
|
|
|
///
|
|
|
|
/// If any inputs are enabled then a two-stage output pipeline applies:
|
2022-07-27 02:01:43 +00:00
|
|
|
/// if anything is already in the pipeline then it should now be written.
|
|
|
|
/// Then the new value should be placed into the pipeline ready for the
|
|
|
|
/// next write slot.
|
|
|
|
///
|
|
|
|
/// If the pipeline is empty, this acts the same as @c None, indicating
|
|
|
|
/// an unused DMA slot.
|
|
|
|
Write,
|
|
|
|
/// The caller should read from channel C.
|
|
|
|
C,
|
|
|
|
/// The caller should read from channel B.
|
|
|
|
B,
|
|
|
|
/// The caller should read from channel A.
|
|
|
|
A,
|
|
|
|
/// Indicates an unused DMA slot.
|
|
|
|
None
|
2022-07-26 21:05:05 +00:00
|
|
|
};
|
|
|
|
|
2022-07-27 02:01:43 +00:00
|
|
|
/// Sets the current control value, which indicates which
|
|
|
|
/// channels are enabled.
|
2022-07-26 21:05:05 +00:00
|
|
|
void set_control(int control) {
|
2022-07-29 16:14:59 +00:00
|
|
|
control_ = control & 0xf;
|
|
|
|
index_ = 0; // TODO: this probably isn't accurate; case caught is a change
|
|
|
|
// of control values during a blit.
|
2022-07-26 21:05:05 +00:00
|
|
|
}
|
|
|
|
|
2022-07-27 02:01:43 +00:00
|
|
|
/// Indicates that blitting should conclude after this step, i.e.
|
|
|
|
/// whatever is being fetched now is part of the final set of input data;
|
|
|
|
/// this is safe to call following a fetch request on any channel.
|
2022-07-26 21:05:05 +00:00
|
|
|
void complete() {
|
2022-07-29 16:14:59 +00:00
|
|
|
next_phase_ =
|
2022-07-27 02:01:43 +00:00
|
|
|
(control_ == 0x9 || control_ == 0xb || control_ == 0xd) ?
|
|
|
|
Phase::PauseAndComplete : Phase::Complete;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Begins a blit operation.
|
|
|
|
void begin() {
|
2022-07-29 16:14:59 +00:00
|
|
|
phase_ = next_phase_ = Phase::Ongoing;
|
|
|
|
index_ = loop_ = 0;
|
2022-07-26 21:05:05 +00:00
|
|
|
}
|
|
|
|
|
2022-07-29 16:14:59 +00:00
|
|
|
/// Provides the next channel to fetch from, or that a write is required,
|
|
|
|
/// along with a count of complete channel iterations so far completed.
|
|
|
|
std::pair<Channel, int> next() {
|
2022-07-27 02:01:43 +00:00
|
|
|
switch(phase_) {
|
|
|
|
default: break;
|
|
|
|
|
|
|
|
case Phase::Complete:
|
2022-07-29 16:14:59 +00:00
|
|
|
return std::make_pair(Channel::Write, loop_);
|
2022-07-27 02:01:43 +00:00
|
|
|
|
|
|
|
case Phase::PauseAndComplete:
|
2022-07-29 16:14:59 +00:00
|
|
|
phase_ = Phase::Complete;
|
|
|
|
return std::make_pair(Channel::None, loop_);
|
2022-07-26 21:05:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Channel next = Channel::None;
|
|
|
|
|
|
|
|
switch(control_) {
|
|
|
|
default: break;
|
|
|
|
|
|
|
|
case 1: next = next_channel(pattern1); break;
|
|
|
|
case 2: next = next_channel(pattern2); break;
|
|
|
|
case 3: next = next_channel(pattern3); break;
|
|
|
|
case 4: next = next_channel(pattern4); break;
|
|
|
|
case 5: next = next_channel(pattern5); break;
|
|
|
|
case 6: next = next_channel(pattern6); break;
|
|
|
|
case 7: next = next_channel(pattern7); break;
|
|
|
|
case 8: next = next_channel(pattern8); break;
|
|
|
|
case 9: next = next_channel(pattern9); break;
|
|
|
|
case 10: next = next_channel(patternA); break;
|
|
|
|
case 11: next = next_channel(patternB); break;
|
|
|
|
case 12: next = next_channel(patternC); break;
|
|
|
|
case 13: next = next_channel(patternD); break;
|
|
|
|
case 14: next = next_channel(patternE); break;
|
|
|
|
case 15: next = next_channel(patternF); break;
|
|
|
|
}
|
|
|
|
|
2022-07-29 16:14:59 +00:00
|
|
|
return std::make_pair(next, loop_);
|
2022-07-26 21:05:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
static constexpr std::array<Channel, 2> pattern1 = { Channel::Write, Channel::None };
|
|
|
|
static constexpr std::array<Channel, 2> pattern2 = { Channel::C, Channel::None };
|
|
|
|
static constexpr std::array<Channel, 3> pattern3 = { Channel::C, Channel::Write, Channel::None };
|
|
|
|
static constexpr std::array<Channel, 3> pattern4 = { Channel::B, Channel::None, Channel::None };
|
|
|
|
static constexpr std::array<Channel, 3> pattern5 = { Channel::B, Channel::Write, Channel::None };
|
|
|
|
static constexpr std::array<Channel, 3> pattern6 = { Channel::B, Channel::C, Channel::None };
|
|
|
|
static constexpr std::array<Channel, 4> pattern7 = { Channel::B, Channel::C, Channel::Write, Channel::None };
|
|
|
|
static constexpr std::array<Channel, 2> pattern8 = { Channel::A, Channel::None };
|
|
|
|
static constexpr std::array<Channel, 2> pattern9 = { Channel::A, Channel::Write };
|
|
|
|
static constexpr std::array<Channel, 2> patternA = { Channel::A, Channel::C };
|
|
|
|
static constexpr std::array<Channel, 3> patternB = { Channel::A, Channel::C, Channel::Write };
|
|
|
|
static constexpr std::array<Channel, 3> patternC = { Channel::A, Channel::B, Channel::None };
|
|
|
|
static constexpr std::array<Channel, 3> patternD = { Channel::A, Channel::B, Channel::Write };
|
|
|
|
static constexpr std::array<Channel, 3> patternE = { Channel::A, Channel::B, Channel::C };
|
|
|
|
static constexpr std::array<Channel, 4> patternF = { Channel::A, Channel::B, Channel::C, Channel::Write };
|
|
|
|
template <typename ArrayT> Channel next_channel(const ArrayT &list) {
|
2022-07-29 16:14:59 +00:00
|
|
|
loop_ += index_ / list.size();
|
|
|
|
index_ %= list.size();
|
2022-07-26 21:05:05 +00:00
|
|
|
const Channel result = list[index_];
|
2022-07-29 16:14:59 +00:00
|
|
|
++index_;
|
|
|
|
if(index_ == list.size()) {
|
|
|
|
phase_ = next_phase_;
|
|
|
|
}
|
2022-07-26 21:05:05 +00:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2022-07-29 16:14:59 +00:00
|
|
|
// Current control flags, i.e. which channels are enabled.
|
2022-07-26 21:05:05 +00:00
|
|
|
int control_ = 0;
|
2022-07-29 16:14:59 +00:00
|
|
|
|
|
|
|
// Index into the pattern table for this blit.
|
2022-07-27 02:01:43 +00:00
|
|
|
size_t index_ = 0;
|
2022-07-29 16:14:59 +00:00
|
|
|
|
|
|
|
// Number of times the entire pattern table has been completed.
|
|
|
|
int loop_ = 0;
|
|
|
|
|
2022-07-27 02:01:43 +00:00
|
|
|
enum class Phase {
|
2022-07-29 16:14:59 +00:00
|
|
|
/// Return the next thing in the pattern table and advance.
|
|
|
|
/// If looping from the end of the pattern table to the start,
|
|
|
|
/// set phase_ to next_phase_.
|
|
|
|
Ongoing,
|
|
|
|
/// Return a Channel::None and advancce to phase_ = Phase::Complete.
|
|
|
|
PauseAndComplete,
|
|
|
|
/// Return Channel::Write indefinitely.
|
|
|
|
Complete
|
|
|
|
};
|
|
|
|
|
|
|
|
// Current sequencer pahse.
|
|
|
|
Phase phase_ = Phase::Complete;
|
|
|
|
// Phase to assume at the end of this iteration of the sequence table.
|
|
|
|
Phase next_phase_ = Phase::Complete;
|
2022-07-26 21:05:05 +00:00
|
|
|
};
|
|
|
|
|
2021-10-29 18:29:22 +00:00
|
|
|
class Blitter: public DMADevice<4, 4> {
|
2021-07-22 22:43:07 +00:00
|
|
|
public:
|
2021-09-15 00:51:32 +00:00
|
|
|
using DMADevice::DMADevice;
|
2021-07-22 22:43:07 +00:00
|
|
|
|
|
|
|
// Various setters; it's assumed that address decoding is handled externally.
|
|
|
|
//
|
|
|
|
// In all cases where a channel is identified numerically, it's taken that
|
|
|
|
// 0 = A, 1 = B, 2 = C, 3 = D.
|
|
|
|
void set_control(int index, uint16_t value);
|
|
|
|
void set_first_word_mask(uint16_t value);
|
|
|
|
void set_last_word_mask(uint16_t value);
|
2021-08-10 11:17:01 +00:00
|
|
|
|
2021-07-22 22:43:07 +00:00
|
|
|
void set_size(uint16_t value);
|
|
|
|
void set_minterms(uint16_t value);
|
2021-11-24 22:25:32 +00:00
|
|
|
// void set_vertical_size(uint16_t value);
|
|
|
|
// void set_horizontal_size(uint16_t value);
|
2021-07-22 22:43:07 +00:00
|
|
|
void set_data(int channel, uint16_t value);
|
|
|
|
|
|
|
|
uint16_t get_status();
|
|
|
|
|
2021-11-13 20:53:41 +00:00
|
|
|
bool advance_dma();
|
2021-07-22 22:43:07 +00:00
|
|
|
|
|
|
|
private:
|
2021-08-10 11:17:01 +00:00
|
|
|
int width_ = 0, height_ = 0;
|
2021-09-23 22:38:37 +00:00
|
|
|
int shifts_[2]{};
|
2021-09-26 23:18:12 +00:00
|
|
|
uint16_t a_mask_[2] = {0xffff, 0xffff};
|
|
|
|
|
2021-09-23 22:38:37 +00:00
|
|
|
bool line_mode_ = false;
|
2021-09-26 23:18:12 +00:00
|
|
|
bool one_dot_ = false;
|
|
|
|
int line_direction_ = 0;
|
|
|
|
int line_sign_ = 1;
|
|
|
|
|
2021-09-23 22:38:37 +00:00
|
|
|
uint32_t direction_ = 1;
|
2021-09-29 02:11:58 +00:00
|
|
|
bool inclusive_fill_ = false;
|
|
|
|
bool exclusive_fill_ = false;
|
|
|
|
bool fill_carry_ = false;
|
2021-09-26 23:18:12 +00:00
|
|
|
|
|
|
|
bool channel_enables_[4]{};
|
2021-09-23 22:38:37 +00:00
|
|
|
|
|
|
|
uint8_t minterms_ = 0;
|
2021-09-24 02:05:59 +00:00
|
|
|
uint32_t a32_ = 0, b32_ = 0;
|
2021-10-27 03:02:28 +00:00
|
|
|
uint16_t a_data_ = 0, b_data_ = 0, c_data_ = 0;
|
2021-09-21 03:08:26 +00:00
|
|
|
|
2021-10-29 01:12:46 +00:00
|
|
|
bool not_zero_flag_ = false;
|
2022-07-29 20:15:18 +00:00
|
|
|
|
|
|
|
BlitterSequencer sequencer_;
|
|
|
|
uint32_t write_address_ = 0xffff'ffff;
|
|
|
|
uint16_t write_value_ = 0;
|
|
|
|
enum WritePhase {
|
|
|
|
Starting, Full, Stopped
|
|
|
|
} write_phase_;
|
|
|
|
int y_, x_;
|
|
|
|
uint16_t transient_a_mask_;
|
|
|
|
bool stopping_;
|
2021-07-22 22:43:07 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endif /* Blitter_hpp */
|