2021-07-22 22:43:07 +00:00
|
|
|
|
//
|
|
|
|
|
// Blitter.cpp
|
|
|
|
|
// Clock Signal
|
|
|
|
|
//
|
|
|
|
|
// Created by Thomas Harte on 22/07/2021.
|
|
|
|
|
// Copyright © 2021 Thomas Harte. All rights reserved.
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
#include "Blitter.hpp"
|
|
|
|
|
|
2021-10-16 12:37:46 +00:00
|
|
|
|
#include "Minterms.hpp"
|
2024-01-20 03:14:24 +00:00
|
|
|
|
#include "../../Outputs/Log.hpp"
|
2021-09-23 22:13:51 +00:00
|
|
|
|
|
2021-10-26 14:21:51 +00:00
|
|
|
|
#include <cassert>
|
|
|
|
|
|
2021-07-22 22:43:07 +00:00
|
|
|
|
using namespace Amiga;
|
|
|
|
|
|
2021-12-19 22:16:46 +00:00
|
|
|
|
namespace {
|
|
|
|
|
|
2024-01-20 03:14:24 +00:00
|
|
|
|
Log::Logger<Log::Source::AmigaBlitter> logger;
|
|
|
|
|
|
2021-12-19 22:16:46 +00:00
|
|
|
|
/// @returns Either the final carry flag or the output nibble when using fill mode given that it either @c is_exclusive fill mode, or isn't;
|
|
|
|
|
/// and the specified initial @c carry and input @c nibble.
|
|
|
|
|
template <bool wants_carry> constexpr uint32_t fill_nibble(bool is_exclusive, uint8_t carry, uint8_t nibble) {
|
|
|
|
|
uint8_t fill_output = 0;
|
|
|
|
|
uint8_t bit = 0x01;
|
|
|
|
|
while(bit < 0x10) {
|
|
|
|
|
auto pre_toggle = nibble & bit, post_toggle = pre_toggle;
|
|
|
|
|
if(!is_exclusive) {
|
|
|
|
|
pre_toggle &= ~carry; // Accept bits that would transition to set immediately.
|
|
|
|
|
post_toggle &= carry; // Accept bits that would transition to clear after the fact.
|
|
|
|
|
} else {
|
|
|
|
|
post_toggle = 0; // Just do the pre-toggle.
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
carry ^= pre_toggle;
|
|
|
|
|
fill_output |= carry;
|
|
|
|
|
carry ^= post_toggle;
|
|
|
|
|
|
|
|
|
|
bit <<= 1;
|
|
|
|
|
carry <<= 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if constexpr (wants_carry) {
|
|
|
|
|
return carry >> 4;
|
|
|
|
|
} else {
|
|
|
|
|
return fill_output;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Lookup key for these tables is:
|
|
|
|
|
//
|
|
|
|
|
// b0–b3: input nibble
|
|
|
|
|
// b4: carry
|
|
|
|
|
// b5: is_exclusive
|
|
|
|
|
//
|
|
|
|
|
// i.e. it's in the range [0, 63].
|
|
|
|
|
//
|
|
|
|
|
// Tables below are indexed such that the higher-order bits select a table entry, lower-order bits select
|
|
|
|
|
// a bit or nibble from within the indexed item.
|
|
|
|
|
|
|
|
|
|
constexpr uint32_t fill_carries[] = {
|
|
|
|
|
(fill_nibble<true>(false, 0, 0x0) << 0x0) | (fill_nibble<true>(false, 0, 0x1) << 0x1) | (fill_nibble<true>(false, 0, 0x2) << 0x2) | (fill_nibble<true>(false, 0, 0x3) << 0x3) |
|
|
|
|
|
(fill_nibble<true>(false, 0, 0x4) << 0x4) | (fill_nibble<true>(false, 0, 0x5) << 0x5) | (fill_nibble<true>(false, 0, 0x6) << 0x6) | (fill_nibble<true>(false, 0, 0x7) << 0x7) |
|
|
|
|
|
(fill_nibble<true>(false, 0, 0x8) << 0x8) | (fill_nibble<true>(false, 0, 0x9) << 0x9) | (fill_nibble<true>(false, 0, 0xa) << 0xa) | (fill_nibble<true>(false, 0, 0xb) << 0xb) |
|
|
|
|
|
(fill_nibble<true>(false, 0, 0xc) << 0xc) | (fill_nibble<true>(false, 0, 0xd) << 0xd) | (fill_nibble<true>(false, 0, 0xe) << 0xe) | (fill_nibble<true>(false, 0, 0xf) << 0xf) |
|
|
|
|
|
|
|
|
|
|
(fill_nibble<true>(false, 1, 0x0) << 0x10) | (fill_nibble<true>(false, 1, 0x1) << 0x11) | (fill_nibble<true>(false, 1, 0x2) << 0x12) | (fill_nibble<true>(false, 1, 0x3) << 0x13) |
|
|
|
|
|
(fill_nibble<true>(false, 1, 0x4) << 0x14) | (fill_nibble<true>(false, 1, 0x5) << 0x15) | (fill_nibble<true>(false, 1, 0x6) << 0x16) | (fill_nibble<true>(false, 1, 0x7) << 0x17) |
|
|
|
|
|
(fill_nibble<true>(false, 1, 0x8) << 0x18) | (fill_nibble<true>(false, 1, 0x9) << 0x19) | (fill_nibble<true>(false, 1, 0xa) << 0x1a) | (fill_nibble<true>(false, 1, 0xb) << 0x1b) |
|
|
|
|
|
(fill_nibble<true>(false, 1, 0xc) << 0x1c) | (fill_nibble<true>(false, 1, 0xd) << 0x1d) | (fill_nibble<true>(false, 1, 0xe) << 0x1e) | (fill_nibble<true>(false, 1, 0xf) << 0x1f),
|
|
|
|
|
|
|
|
|
|
(fill_nibble<true>(true, 0, 0x0) << 0x0) | (fill_nibble<true>(true, 0, 0x1) << 0x1) | (fill_nibble<true>(true, 0, 0x2) << 0x2) | (fill_nibble<true>(true, 0, 0x3) << 0x3) |
|
|
|
|
|
(fill_nibble<true>(true, 0, 0x4) << 0x4) | (fill_nibble<true>(true, 0, 0x5) << 0x5) | (fill_nibble<true>(true, 0, 0x6) << 0x6) | (fill_nibble<true>(true, 0, 0x7) << 0x7) |
|
|
|
|
|
(fill_nibble<true>(true, 0, 0x8) << 0x8) | (fill_nibble<true>(true, 0, 0x9) << 0x9) | (fill_nibble<true>(true, 0, 0xa) << 0xa) | (fill_nibble<true>(true, 0, 0xb) << 0xb) |
|
|
|
|
|
(fill_nibble<true>(true, 0, 0xc) << 0xc) | (fill_nibble<true>(true, 0, 0xd) << 0xd) | (fill_nibble<true>(true, 0, 0xe) << 0xe) | (fill_nibble<true>(true, 0, 0xf) << 0xf) |
|
|
|
|
|
|
|
|
|
|
(fill_nibble<true>(true, 1, 0x0) << 0x10) | (fill_nibble<true>(true, 1, 0x1) << 0x11) | (fill_nibble<true>(true, 1, 0x2) << 0x12) | (fill_nibble<true>(true, 1, 0x3) << 0x13) |
|
|
|
|
|
(fill_nibble<true>(true, 1, 0x4) << 0x14) | (fill_nibble<true>(true, 1, 0x5) << 0x15) | (fill_nibble<true>(true, 1, 0x6) << 0x16) | (fill_nibble<true>(true, 1, 0x7) << 0x17) |
|
|
|
|
|
(fill_nibble<true>(true, 1, 0x8) << 0x18) | (fill_nibble<true>(true, 1, 0x9) << 0x19) | (fill_nibble<true>(true, 1, 0xa) << 0x1a) | (fill_nibble<true>(true, 1, 0xb) << 0x1b) |
|
|
|
|
|
(fill_nibble<true>(true, 1, 0xc) << 0x1c) | (fill_nibble<true>(true, 1, 0xd) << 0x1d) | (fill_nibble<true>(true, 1, 0xe) << 0x1e) | (fill_nibble<true>(true, 1, 0xf) << 0x1f),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
constexpr uint32_t fill_values[] = {
|
|
|
|
|
(fill_nibble<false>(false, 0, 0x0) << 0) | (fill_nibble<false>(false, 0, 0x1) << 4) | (fill_nibble<false>(false, 0, 0x2) << 8) | (fill_nibble<false>(false, 0, 0x3) << 12) |
|
|
|
|
|
(fill_nibble<false>(false, 0, 0x4) << 16) | (fill_nibble<false>(false, 0, 0x5) << 20) | (fill_nibble<false>(false, 0, 0x6) << 24) | (fill_nibble<false>(false, 0, 0x7) << 28),
|
|
|
|
|
|
|
|
|
|
(fill_nibble<false>(false, 0, 0x8) << 0) | (fill_nibble<false>(false, 0, 0x9) << 4) | (fill_nibble<false>(false, 0, 0xa) << 8) | (fill_nibble<false>(false, 0, 0xb) << 12) |
|
|
|
|
|
(fill_nibble<false>(false, 0, 0xc) << 16) | (fill_nibble<false>(false, 0, 0xd) << 20) | (fill_nibble<false>(false, 0, 0xe) << 24) | (fill_nibble<false>(false, 0, 0xf) << 28),
|
|
|
|
|
|
|
|
|
|
(fill_nibble<false>(false, 1, 0x0) << 0) | (fill_nibble<false>(false, 1, 0x1) << 4) | (fill_nibble<false>(false, 1, 0x2) << 8) | (fill_nibble<false>(false, 1, 0x3) << 12) |
|
|
|
|
|
(fill_nibble<false>(false, 1, 0x4) << 16) | (fill_nibble<false>(false, 1, 0x5) << 20) | (fill_nibble<false>(false, 1, 0x6) << 24) | (fill_nibble<false>(false, 1, 0x7) << 28),
|
|
|
|
|
|
|
|
|
|
(fill_nibble<false>(false, 1, 0x8) << 0) | (fill_nibble<false>(false, 1, 0x9) << 4) | (fill_nibble<false>(false, 1, 0xa) << 8) | (fill_nibble<false>(false, 1, 0xb) << 12) |
|
|
|
|
|
(fill_nibble<false>(false, 1, 0xc) << 16) | (fill_nibble<false>(false, 1, 0xd) << 20) | (fill_nibble<false>(false, 1, 0xe) << 24) | (fill_nibble<false>(false, 1, 0xf) << 28),
|
|
|
|
|
|
|
|
|
|
(fill_nibble<false>(true, 0, 0x0) << 0) | (fill_nibble<false>(true, 0, 0x1) << 4) | (fill_nibble<false>(true, 0, 0x2) << 8) | (fill_nibble<false>(true, 0, 0x3) << 12) |
|
|
|
|
|
(fill_nibble<false>(true, 0, 0x4) << 16) | (fill_nibble<false>(true, 0, 0x5) << 20) | (fill_nibble<false>(true, 0, 0x6) << 24) | (fill_nibble<false>(true, 0, 0x7) << 28),
|
|
|
|
|
|
|
|
|
|
(fill_nibble<false>(true, 0, 0x8) << 0) | (fill_nibble<false>(true, 0, 0x9) << 4) | (fill_nibble<false>(true, 0, 0xa) << 8) | (fill_nibble<false>(true, 0, 0xb) << 12) |
|
|
|
|
|
(fill_nibble<false>(true, 0, 0xc) << 16) | (fill_nibble<false>(true, 0, 0xd) << 20) | (fill_nibble<false>(true, 0, 0xe) << 24) | (fill_nibble<false>(true, 0, 0xf) << 28),
|
|
|
|
|
|
|
|
|
|
(fill_nibble<false>(true, 1, 0x0) << 0) | (fill_nibble<false>(true, 1, 0x1) << 4) | (fill_nibble<false>(true, 1, 0x2) << 8) | (fill_nibble<false>(true, 1, 0x3) << 12) |
|
|
|
|
|
(fill_nibble<false>(true, 1, 0x4) << 16) | (fill_nibble<false>(true, 1, 0x5) << 20) | (fill_nibble<false>(true, 1, 0x6) << 24) | (fill_nibble<false>(true, 1, 0x7) << 28),
|
|
|
|
|
|
|
|
|
|
(fill_nibble<false>(true, 1, 0x8) << 0) | (fill_nibble<false>(true, 1, 0x9) << 4) | (fill_nibble<false>(true, 1, 0xa) << 8) | (fill_nibble<false>(true, 1, 0xb) << 12) |
|
|
|
|
|
(fill_nibble<false>(true, 1, 0xc) << 16) | (fill_nibble<false>(true, 1, 0xd) << 20) | (fill_nibble<false>(true, 1, 0xe) << 24) | (fill_nibble<false>(true, 1, 0xf) << 28),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2022-08-06 13:51:20 +00:00
|
|
|
|
template <bool record_bus>
|
|
|
|
|
void Blitter<record_bus>::set_control(int index, uint16_t value) {
|
2021-09-21 03:08:26 +00:00
|
|
|
|
if(index) {
|
2021-09-26 22:16:00 +00:00
|
|
|
|
line_mode_ = (value & 0x0001);
|
2021-09-26 23:18:12 +00:00
|
|
|
|
one_dot_ = value & 0x0002;
|
|
|
|
|
line_direction_ = (value >> 2) & 7;
|
|
|
|
|
line_sign_ = (value & 0x0040) ? -1 : 1;
|
|
|
|
|
|
|
|
|
|
direction_ = one_dot_ ? uint32_t(-1) : uint32_t(1);
|
2021-09-29 02:11:58 +00:00
|
|
|
|
exclusive_fill_ = (value & 0x0010);
|
2021-11-07 22:25:09 +00:00
|
|
|
|
inclusive_fill_ = !exclusive_fill_ && (value & 0x0008); // Exclusive fill takes precedence. Probably? TODO: verify.
|
2021-09-29 02:11:58 +00:00
|
|
|
|
fill_carry_ = (value & 0x0004);
|
2021-09-21 03:08:26 +00:00
|
|
|
|
} else {
|
2021-09-23 22:30:35 +00:00
|
|
|
|
minterms_ = value & 0xff;
|
2022-07-29 20:15:18 +00:00
|
|
|
|
sequencer_.set_control(value >> 8);
|
2021-09-21 03:08:26 +00:00
|
|
|
|
}
|
|
|
|
|
shifts_[index] = value >> 12;
|
2024-01-20 03:14:24 +00:00
|
|
|
|
logger.info().append("Set control %d to %04x", index, value);
|
2021-07-22 22:43:07 +00:00
|
|
|
|
}
|
|
|
|
|
|
2022-08-06 13:51:20 +00:00
|
|
|
|
template <bool record_bus>
|
|
|
|
|
void Blitter<record_bus>::set_first_word_mask(uint16_t value) {
|
2024-01-20 03:14:24 +00:00
|
|
|
|
logger.info().append("Set first word mask: %04x", value);
|
2021-09-26 22:42:08 +00:00
|
|
|
|
a_mask_[0] = value;
|
2021-07-22 22:43:07 +00:00
|
|
|
|
}
|
|
|
|
|
|
2022-08-06 13:51:20 +00:00
|
|
|
|
template <bool record_bus>
|
|
|
|
|
void Blitter<record_bus>::set_last_word_mask(uint16_t value) {
|
2024-01-20 03:14:24 +00:00
|
|
|
|
logger.info().append("Set last word mask: %04x", value);
|
2021-09-26 22:42:08 +00:00
|
|
|
|
a_mask_[1] = value;
|
2021-07-22 22:43:07 +00:00
|
|
|
|
}
|
|
|
|
|
|
2022-08-06 13:51:20 +00:00
|
|
|
|
template <bool record_bus>
|
|
|
|
|
void Blitter<record_bus>::set_size(uint16_t value) {
|
2021-11-24 22:25:32 +00:00
|
|
|
|
// width_ = (width_ & ~0x3f) | (value & 0x3f);
|
|
|
|
|
// height_ = (height_ & ~0x3ff) | (value >> 6);
|
|
|
|
|
width_ = value & 0x3f;
|
|
|
|
|
if(!width_) width_ = 0x40;
|
|
|
|
|
height_ = value >> 6;
|
|
|
|
|
if(!height_) height_ = 1024;
|
2024-01-20 03:14:24 +00:00
|
|
|
|
logger.info().append("Set size to %d, %d", width_, height_);
|
2021-09-23 22:13:51 +00:00
|
|
|
|
|
|
|
|
|
// Current assumption: writing this register informs the
|
|
|
|
|
// blitter that it should treat itself as about to start a new line.
|
2021-07-22 22:43:07 +00:00
|
|
|
|
}
|
|
|
|
|
|
2022-08-06 13:51:20 +00:00
|
|
|
|
template <bool record_bus>
|
|
|
|
|
void Blitter<record_bus>::set_minterms(uint16_t value) {
|
2024-01-20 03:14:24 +00:00
|
|
|
|
logger.info().append("Set minterms: %02x", value & 0xff);
|
2021-08-10 11:17:01 +00:00
|
|
|
|
minterms_ = value & 0xff;
|
2021-07-22 22:43:07 +00:00
|
|
|
|
}
|
|
|
|
|
|
2022-08-06 13:51:20 +00:00
|
|
|
|
//template <bool record_bus>
|
|
|
|
|
//void Blitter<record_bus>::set_vertical_size([[maybe_unused]] uint16_t value) {
|
2024-01-20 03:17:35 +00:00
|
|
|
|
// logger.info().append("Set vertical size %04x", value);
|
2021-11-24 22:25:32 +00:00
|
|
|
|
// // TODO. This is ECS only, I think. Ditto set_horizontal_size.
|
|
|
|
|
//}
|
|
|
|
|
//
|
2022-08-06 13:51:20 +00:00
|
|
|
|
//template <bool record_bus>
|
|
|
|
|
//void Blitter<record_bus>::set_horizontal_size([[maybe_unused]] uint16_t value) {
|
2024-01-20 03:17:35 +00:00
|
|
|
|
// logger.info().append("Set horizontal size %04x", value);
|
2021-11-24 22:25:32 +00:00
|
|
|
|
//}
|
2021-07-22 22:43:07 +00:00
|
|
|
|
|
2022-08-06 13:51:20 +00:00
|
|
|
|
template <bool record_bus>
|
|
|
|
|
void Blitter<record_bus>::set_data(int channel, uint16_t value) {
|
2024-01-20 03:14:24 +00:00
|
|
|
|
logger.info().append("Set data %d to %04x", channel, value);
|
2021-09-24 02:05:59 +00:00
|
|
|
|
|
|
|
|
|
// Ugh, backed myself into a corner. TODO: clean.
|
|
|
|
|
switch(channel) {
|
2021-10-27 03:02:28 +00:00
|
|
|
|
case 0: a_data_ = value; break;
|
|
|
|
|
case 1: b_data_ = value; break;
|
|
|
|
|
case 2: c_data_ = value; break;
|
2021-09-24 02:05:59 +00:00
|
|
|
|
default: break;
|
|
|
|
|
}
|
2021-07-22 22:43:07 +00:00
|
|
|
|
}
|
|
|
|
|
|
2022-08-06 13:51:20 +00:00
|
|
|
|
template <bool record_bus>
|
|
|
|
|
uint16_t Blitter<record_bus>::get_status() {
|
2021-10-29 01:12:46 +00:00
|
|
|
|
const uint16_t result =
|
|
|
|
|
(not_zero_flag_ ? 0x0000 : 0x2000) | (height_ ? 0x4000 : 0x0000);
|
2024-01-20 03:14:24 +00:00
|
|
|
|
logger.info().append("Returned status of %04x", result);
|
2021-10-29 01:12:46 +00:00
|
|
|
|
return result;
|
2021-07-22 22:43:07 +00:00
|
|
|
|
}
|
|
|
|
|
|
2022-07-26 21:05:05 +00:00
|
|
|
|
// Due to the pipeline, writes are delayed by one slot — the first write will occur
|
|
|
|
|
// after the second set of inputs has been fetched, and every sequence with writes enabled
|
|
|
|
|
// will end with an additional write.
|
|
|
|
|
//
|
|
|
|
|
// USE Code
|
|
|
|
|
// in Active
|
|
|
|
|
// BLTCON0 Channels Cycle Sequence
|
|
|
|
|
// --------- -------- --------------
|
|
|
|
|
// F A B C D A0 B0 C0 - A1 B1 C1 D0 A2 B2 C2 D1 D2
|
|
|
|
|
// E A B C A0 B0 C0 A1 B1 C1 A2 B2 C2
|
|
|
|
|
// D A B D A0 B0 - A1 B1 D0 A2 B2 D1 - D2
|
|
|
|
|
// C A B A0 B0 - A1 B1 - A2 B2
|
|
|
|
|
// B A C D A0 C0 - A1 C1 D0 A2 C2 D1 - D2
|
|
|
|
|
// A A C A0 C0 A1 C1 A2 C2
|
|
|
|
|
// 9 A D A0 - A1 D0 A2 D1 - D2
|
|
|
|
|
// 8 A A0 - A1 - A2
|
|
|
|
|
// 7 B C D B0 C0 - - B1 C1 D0 - B2 C2 D1 - D2
|
|
|
|
|
// 6 B C B0 C0 - B1 C1 - B2 C2
|
|
|
|
|
// 5 B D B0 - - B1 D0 - B2 D1 - D2
|
|
|
|
|
// 4 B B0 - - B1 - - B2
|
|
|
|
|
// 3 C D C0 - - C1 D0 - C2 D1 - D2
|
|
|
|
|
// 2 C C0 - C1 - C2
|
|
|
|
|
// 1 D D0 - D1 - D2
|
|
|
|
|
// 0 none - - - -
|
|
|
|
|
//
|
|
|
|
|
//
|
|
|
|
|
// Table 6-2: Typical Blitter Cycle Sequence
|
|
|
|
|
|
2022-08-06 13:51:20 +00:00
|
|
|
|
template <bool record_bus>
|
|
|
|
|
void Blitter<record_bus>::add_modulos() {
|
2022-08-04 14:06:14 +00:00
|
|
|
|
pointer_[0] += modulos_[0] * sequencer_.channel_enabled<0>()* direction_;
|
|
|
|
|
pointer_[1] += modulos_[1] * sequencer_.channel_enabled<1>() * direction_;
|
|
|
|
|
pointer_[2] += modulos_[2] * sequencer_.channel_enabled<2>() * direction_;
|
|
|
|
|
pointer_[3] += modulos_[3] * sequencer_.channel_enabled<3>() * direction_;
|
2022-07-31 01:35:26 +00:00
|
|
|
|
}
|
|
|
|
|
|
2022-08-06 13:51:20 +00:00
|
|
|
|
template <bool record_bus>
|
2022-08-15 15:10:17 +00:00
|
|
|
|
template <bool complete_immediately>
|
2022-08-06 13:51:20 +00:00
|
|
|
|
bool Blitter<record_bus>::advance_dma() {
|
2021-09-16 23:56:28 +00:00
|
|
|
|
if(!height_) return false;
|
|
|
|
|
|
2022-08-15 15:10:17 +00:00
|
|
|
|
// TODO: eliminate @c complete_immediately and this workaround.
|
|
|
|
|
// See commentary in Chipset.cpp.
|
2022-08-19 20:38:42 +00:00
|
|
|
|
if constexpr (complete_immediately) {
|
|
|
|
|
|
|
|
|
|
// HACK! HACK!! HACK!!!
|
|
|
|
|
//
|
|
|
|
|
// This resolves an issue with loading the particular copy of Spindizzy Worlds
|
|
|
|
|
// I am testing against.
|
|
|
|
|
//
|
|
|
|
|
// TODO: DO NOT PUBLISH THIS.
|
|
|
|
|
//
|
|
|
|
|
// This is committed solely so that I can continue researching the real, underlying
|
|
|
|
|
// issue across machines. It would not be acceptable to me to ship this.
|
|
|
|
|
// (and the printf is another reminder-to-self)
|
|
|
|
|
if(width_ == 8 && height_ == 32) {
|
|
|
|
|
printf("Accelerating %d x %d\n", width_, height_);
|
|
|
|
|
|
|
|
|
|
while(get_status() & 0x4000) {
|
|
|
|
|
advance_dma<false>();
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-08-15 15:10:17 +00:00
|
|
|
|
|
2021-09-21 03:08:26 +00:00
|
|
|
|
if(line_mode_) {
|
2022-07-31 00:34:37 +00:00
|
|
|
|
not_zero_flag_ = false;
|
|
|
|
|
|
2021-10-26 14:21:51 +00:00
|
|
|
|
// As-yet unimplemented:
|
2021-10-29 01:12:46 +00:00
|
|
|
|
assert(b_data_ == 0xffff);
|
2021-10-26 14:21:51 +00:00
|
|
|
|
|
2021-09-21 03:08:26 +00:00
|
|
|
|
//
|
|
|
|
|
// Line mode.
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
// Bluffer's guide to line mode:
|
|
|
|
|
//
|
|
|
|
|
// In Bresenham terms, the following registers have been set up:
|
|
|
|
|
//
|
|
|
|
|
// [A modulo] = 4 * (dy - dx)
|
|
|
|
|
// [B modulo] = 4 * dy
|
|
|
|
|
// [A pointer] = 4 * dy - 2 * dx, with the sign flag in BLTCON1 indicating sign.
|
|
|
|
|
//
|
|
|
|
|
// [A data] = 0x8000
|
|
|
|
|
// [Both masks] = 0xffff
|
|
|
|
|
// [A shift] = x1 & 15
|
|
|
|
|
//
|
|
|
|
|
// [B data] = texture
|
|
|
|
|
// [B shift] = bit at which to start the line texture (0 = LSB)
|
|
|
|
|
//
|
|
|
|
|
// [C and D pointers] = word containing the first pixel of the line
|
|
|
|
|
// [C and D modulo] = width of the bitplane in bytes
|
|
|
|
|
//
|
|
|
|
|
// height = number of pixels
|
|
|
|
|
//
|
|
|
|
|
// If ONEDOT of BLTCON1 is set, plot only a single bit per horizontal row.
|
|
|
|
|
//
|
|
|
|
|
// BLTCON1 quadrants are (bits 2–4):
|
|
|
|
|
//
|
|
|
|
|
// 110 -> step in x, x positive, y negative
|
|
|
|
|
// 111 -> step in x, x negative, y negative
|
|
|
|
|
// 101 -> step in x, x negative, y positive
|
|
|
|
|
// 100 -> step in x, x positive, y positive
|
|
|
|
|
//
|
|
|
|
|
// 001 -> step in y, x positive, y negative
|
|
|
|
|
// 011 -> step in y, x negative, y negative
|
|
|
|
|
// 010 -> step in y, x negative, y positive
|
|
|
|
|
// 000 -> step in y, x positive, y positive
|
|
|
|
|
//
|
|
|
|
|
// So that's:
|
|
|
|
|
//
|
2021-09-26 23:18:12 +00:00
|
|
|
|
// * bit 4 = x [=1] or y [=0] major;
|
2021-09-21 03:08:26 +00:00
|
|
|
|
// * bit 3 = 1 => major variable negative; otherwise positive;
|
|
|
|
|
// * bit 2 = 1 => minor variable negative; otherwise positive.
|
|
|
|
|
|
2021-09-29 01:39:09 +00:00
|
|
|
|
//
|
|
|
|
|
// Implementation below is heavily based on the documentation found
|
|
|
|
|
// at https://github.com/niklasekstrom/blitter-subpixel-line/blob/master/Drawing%20lines%20using%20the%20Amiga%20blitter.pdf
|
2021-09-30 02:19:17 +00:00
|
|
|
|
//
|
2021-09-29 01:39:09 +00:00
|
|
|
|
|
2022-08-07 23:19:00 +00:00
|
|
|
|
//
|
|
|
|
|
// Caveat: I've no idea how the DMA access slots should be laid out for
|
|
|
|
|
// line drawing.
|
|
|
|
|
//
|
|
|
|
|
|
2022-08-07 23:15:03 +00:00
|
|
|
|
if(!busy_) {
|
|
|
|
|
error_ = int16_t(pointer_[0] << 1) >> 1; // TODO: what happens if line_sign_ doesn't agree with this?
|
|
|
|
|
draw_ = true;
|
|
|
|
|
busy_ = true;
|
2022-08-07 23:19:00 +00:00
|
|
|
|
has_c_data_ = false;
|
2022-08-07 23:15:03 +00:00
|
|
|
|
}
|
2022-08-06 13:51:20 +00:00
|
|
|
|
|
2022-08-07 23:19:00 +00:00
|
|
|
|
bool did_output = false;
|
2022-08-07 23:15:03 +00:00
|
|
|
|
if(draw_) {
|
|
|
|
|
// TODO: patterned lines. Unclear what to do with the bit that comes out of b.
|
|
|
|
|
// Probably extend it to a full word?
|
2022-08-07 23:19:00 +00:00
|
|
|
|
|
|
|
|
|
if(!has_c_data_) {
|
|
|
|
|
has_c_data_ = true;
|
|
|
|
|
c_data_ = ram_[pointer_[3] & ram_mask_];
|
|
|
|
|
if constexpr (record_bus) {
|
|
|
|
|
transactions_.emplace_back(Transaction::Type::ReadC, pointer_[3], c_data_);
|
|
|
|
|
}
|
|
|
|
|
return true;
|
2021-09-29 01:39:09 +00:00
|
|
|
|
}
|
|
|
|
|
|
2022-08-07 23:15:03 +00:00
|
|
|
|
const uint16_t output =
|
|
|
|
|
apply_minterm<uint16_t>(a_data_ >> shifts_[0], b_data_, c_data_, minterms_);
|
|
|
|
|
ram_[pointer_[3] & ram_mask_] = output;
|
|
|
|
|
not_zero_flag_ |= output;
|
|
|
|
|
draw_ &= !one_dot_;
|
2022-08-07 23:19:00 +00:00
|
|
|
|
has_c_data_ = false;
|
|
|
|
|
did_output = true;
|
2022-08-07 23:15:03 +00:00
|
|
|
|
if constexpr (record_bus) {
|
|
|
|
|
transactions_.emplace_back(Transaction::Type::WriteFromPipeline, pointer_[3], output);
|
2021-09-29 01:39:09 +00:00
|
|
|
|
}
|
2022-08-07 23:15:03 +00:00
|
|
|
|
}
|
2021-09-29 01:39:09 +00:00
|
|
|
|
|
2022-08-07 23:15:03 +00:00
|
|
|
|
constexpr int LEFT = 1 << 0;
|
|
|
|
|
constexpr int RIGHT = 1 << 1;
|
|
|
|
|
constexpr int UP = 1 << 2;
|
|
|
|
|
constexpr int DOWN = 1 << 3;
|
|
|
|
|
int step = (line_direction_ & 4) ?
|
|
|
|
|
((line_direction_ & 1) ? LEFT : RIGHT) :
|
|
|
|
|
((line_direction_ & 1) ? UP : DOWN);
|
|
|
|
|
|
|
|
|
|
if(error_ < 0) {
|
|
|
|
|
error_ += modulos_[1];
|
|
|
|
|
} else {
|
|
|
|
|
step |=
|
|
|
|
|
(line_direction_ & 4) ?
|
|
|
|
|
((line_direction_ & 2) ? UP : DOWN) :
|
|
|
|
|
((line_direction_ & 2) ? LEFT : RIGHT);
|
|
|
|
|
|
|
|
|
|
error_ += modulos_[0];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(step & LEFT) {
|
|
|
|
|
--shifts_[0];
|
|
|
|
|
if(shifts_[0] == -1) {
|
|
|
|
|
--pointer_[3];
|
2021-09-28 02:06:00 +00:00
|
|
|
|
}
|
2022-08-07 23:15:03 +00:00
|
|
|
|
} else if(step & RIGHT) {
|
|
|
|
|
++shifts_[0];
|
|
|
|
|
if(shifts_[0] == 16) {
|
|
|
|
|
++pointer_[3];
|
2021-09-29 01:39:09 +00:00
|
|
|
|
}
|
2021-09-26 23:18:12 +00:00
|
|
|
|
}
|
2022-08-07 23:15:03 +00:00
|
|
|
|
shifts_[0] &= 15;
|
|
|
|
|
|
|
|
|
|
if(step & UP) {
|
|
|
|
|
pointer_[3] -= modulos_[2];
|
|
|
|
|
draw_ = true;
|
|
|
|
|
} else if(step & DOWN) {
|
|
|
|
|
pointer_[3] += modulos_[2];
|
|
|
|
|
draw_ = true;
|
|
|
|
|
}
|
2022-07-31 00:34:37 +00:00
|
|
|
|
|
2022-08-07 23:15:03 +00:00
|
|
|
|
--height_;
|
|
|
|
|
if(!height_) {
|
|
|
|
|
busy_ = false;
|
|
|
|
|
posit_interrupt(InterruptFlag::Blitter);
|
|
|
|
|
}
|
2022-08-07 23:19:00 +00:00
|
|
|
|
|
|
|
|
|
return did_output;
|
2021-09-21 03:08:26 +00:00
|
|
|
|
} else {
|
|
|
|
|
// Copy mode.
|
2022-07-31 00:34:37 +00:00
|
|
|
|
if(!busy_) {
|
|
|
|
|
sequencer_.begin();
|
|
|
|
|
a32_ = 0;
|
|
|
|
|
b32_ = 0;
|
|
|
|
|
|
|
|
|
|
y_ = 0;
|
|
|
|
|
x_ = 0;
|
|
|
|
|
loop_index_ = -1;
|
|
|
|
|
write_phase_ = WritePhase::Starting;
|
2022-07-31 01:02:44 +00:00
|
|
|
|
not_zero_flag_ = false;
|
2022-07-31 00:34:37 +00:00
|
|
|
|
busy_ = true;
|
|
|
|
|
}
|
2021-09-29 02:11:58 +00:00
|
|
|
|
|
2022-07-31 00:34:37 +00:00
|
|
|
|
const auto next = sequencer_.next();
|
2022-07-29 20:15:18 +00:00
|
|
|
|
|
2022-07-31 00:34:37 +00:00
|
|
|
|
// If this is the start of a new iteration, check for end of line,
|
|
|
|
|
// or of blit, and pick an appropriate mask for A based on location.
|
|
|
|
|
if(next.second != loop_index_) {
|
|
|
|
|
transient_a_mask_ = x_ ? 0xffff : a_mask_[0];
|
|
|
|
|
|
2022-07-31 01:02:04 +00:00
|
|
|
|
// Check whether an entire row was completed in the previous iteration.
|
2022-07-31 01:35:26 +00:00
|
|
|
|
// If so then add modulos. Though this won't capture the move off the
|
|
|
|
|
// final line, so that's handled elsewhere.
|
2022-07-31 00:34:37 +00:00
|
|
|
|
if(!x_ && y_) {
|
2022-07-31 01:35:26 +00:00
|
|
|
|
add_modulos();
|
2022-07-29 20:15:18 +00:00
|
|
|
|
}
|
2021-10-27 03:02:28 +00:00
|
|
|
|
|
2022-07-31 00:34:37 +00:00
|
|
|
|
++x_;
|
|
|
|
|
if(x_ == width_) {
|
|
|
|
|
transient_a_mask_ &= a_mask_[1];
|
|
|
|
|
x_ = 0;
|
|
|
|
|
++y_;
|
|
|
|
|
if(y_ == height_) {
|
|
|
|
|
sequencer_.complete();
|
|
|
|
|
}
|
2022-07-29 20:15:18 +00:00
|
|
|
|
}
|
2022-07-31 00:34:37 +00:00
|
|
|
|
++loop_index_;
|
|
|
|
|
}
|
2021-10-29 01:12:46 +00:00
|
|
|
|
|
2022-07-31 00:34:37 +00:00
|
|
|
|
using Channel = BlitterSequencer::Channel;
|
|
|
|
|
switch(next.first) {
|
|
|
|
|
case Channel::A:
|
|
|
|
|
a_data_ = ram_[pointer_[0] & ram_mask_];
|
2022-08-06 13:51:20 +00:00
|
|
|
|
|
|
|
|
|
if constexpr (record_bus) {
|
|
|
|
|
transactions_.emplace_back(Transaction::Type::ReadA, pointer_[0], a_data_);
|
|
|
|
|
}
|
2022-07-31 00:34:37 +00:00
|
|
|
|
pointer_[0] += direction_;
|
|
|
|
|
return true;
|
|
|
|
|
case Channel::B:
|
|
|
|
|
b_data_ = ram_[pointer_[1] & ram_mask_];
|
2022-08-06 13:51:20 +00:00
|
|
|
|
|
|
|
|
|
if constexpr (record_bus) {
|
|
|
|
|
transactions_.emplace_back(Transaction::Type::ReadB, pointer_[1], b_data_);
|
|
|
|
|
}
|
2022-07-31 00:34:37 +00:00
|
|
|
|
pointer_[1] += direction_;
|
|
|
|
|
return true;
|
|
|
|
|
case Channel::C:
|
|
|
|
|
c_data_ = ram_[pointer_[2] & ram_mask_];
|
2022-08-06 13:51:20 +00:00
|
|
|
|
|
|
|
|
|
if constexpr (record_bus) {
|
|
|
|
|
transactions_.emplace_back(Transaction::Type::ReadC, pointer_[2], c_data_);
|
|
|
|
|
}
|
2022-07-31 00:34:37 +00:00
|
|
|
|
pointer_[2] += direction_;
|
|
|
|
|
return true;
|
|
|
|
|
case Channel::FlushPipeline:
|
2022-07-31 01:35:26 +00:00
|
|
|
|
add_modulos();
|
2022-07-31 00:34:37 +00:00
|
|
|
|
posit_interrupt(InterruptFlag::Blitter);
|
|
|
|
|
height_ = 0;
|
|
|
|
|
busy_ = false;
|
|
|
|
|
|
|
|
|
|
if(write_phase_ == WritePhase::Full) {
|
2022-08-06 13:51:20 +00:00
|
|
|
|
if constexpr (record_bus) {
|
|
|
|
|
transactions_.emplace_back(Transaction::Type::WriteFromPipeline, write_address_, write_value_);
|
|
|
|
|
}
|
2022-07-31 00:34:37 +00:00
|
|
|
|
ram_[write_address_ & ram_mask_] = write_value_;
|
2022-08-06 13:51:20 +00:00
|
|
|
|
write_phase_ = WritePhase::Starting;
|
2022-07-31 00:34:37 +00:00
|
|
|
|
}
|
|
|
|
|
return true;
|
2022-07-31 01:00:50 +00:00
|
|
|
|
|
2022-08-06 13:51:20 +00:00
|
|
|
|
case Channel::None:
|
|
|
|
|
if constexpr (record_bus) {
|
|
|
|
|
transactions_.emplace_back(Transaction::Type::SkippedSlot);
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
|
2022-07-31 01:00:50 +00:00
|
|
|
|
case Channel::Write: break;
|
2022-07-31 00:34:37 +00:00
|
|
|
|
}
|
2021-10-31 21:36:31 +00:00
|
|
|
|
|
2022-07-31 00:34:37 +00:00
|
|
|
|
a32_ = (a32_ << 16) | (a_data_ & transient_a_mask_);
|
|
|
|
|
b32_ = (b32_ << 16) | b_data_;
|
2022-07-29 20:15:18 +00:00
|
|
|
|
|
2022-07-31 00:34:37 +00:00
|
|
|
|
uint16_t a, b;
|
2021-09-23 22:38:37 +00:00
|
|
|
|
|
2022-07-31 00:34:37 +00:00
|
|
|
|
// The barrel shifter shifts to the right in ascending address mode,
|
|
|
|
|
// but to the left otherwise.
|
|
|
|
|
if(!one_dot_) {
|
|
|
|
|
a = uint16_t(a32_ >> shifts_[0]);
|
|
|
|
|
b = uint16_t(b32_ >> shifts_[1]);
|
|
|
|
|
} else {
|
|
|
|
|
// TODO: there must be a neater solution than this.
|
|
|
|
|
a = uint16_t(
|
|
|
|
|
(a32_ << shifts_[0]) |
|
|
|
|
|
(a32_ >> (32 - shifts_[0]))
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
b = uint16_t(
|
|
|
|
|
(b32_ << shifts_[1]) |
|
|
|
|
|
(b32_ >> (32 - shifts_[1]))
|
|
|
|
|
);
|
|
|
|
|
}
|
2022-07-29 20:15:18 +00:00
|
|
|
|
|
2022-07-31 00:34:37 +00:00
|
|
|
|
uint16_t output =
|
|
|
|
|
apply_minterm<uint16_t>(
|
|
|
|
|
a,
|
|
|
|
|
b,
|
|
|
|
|
c_data_,
|
|
|
|
|
minterms_);
|
|
|
|
|
|
|
|
|
|
if(exclusive_fill_ || inclusive_fill_) {
|
|
|
|
|
// Use the fill tables nibble-by-nibble to figure out the filled word.
|
|
|
|
|
uint16_t fill_output = 0;
|
|
|
|
|
int ongoing_carry = fill_carry_;
|
|
|
|
|
const int type_mask = exclusive_fill_ ? (1 << 5) : 0;
|
|
|
|
|
for(int c = 0; c < 16; c += 4) {
|
|
|
|
|
const int total_index = (output & 0xf) | (ongoing_carry << 4) | type_mask;
|
|
|
|
|
fill_output |= ((fill_values[total_index >> 3] >> ((total_index & 7) * 4)) & 0xf) << c;
|
|
|
|
|
ongoing_carry = (fill_carries[total_index >> 5] >> (total_index & 31)) & 1;
|
|
|
|
|
output >>= 4;
|
2021-09-23 22:13:51 +00:00
|
|
|
|
}
|
|
|
|
|
|
2022-07-31 00:34:37 +00:00
|
|
|
|
output = fill_output;
|
|
|
|
|
fill_carry_ = ongoing_carry;
|
|
|
|
|
}
|
2022-07-29 20:15:18 +00:00
|
|
|
|
|
2022-07-31 00:34:37 +00:00
|
|
|
|
not_zero_flag_ |= output;
|
2022-07-29 20:15:18 +00:00
|
|
|
|
|
2022-07-31 00:34:37 +00:00
|
|
|
|
switch(write_phase_) {
|
|
|
|
|
case WritePhase::Full:
|
2022-08-06 13:51:20 +00:00
|
|
|
|
if constexpr (record_bus) {
|
|
|
|
|
transactions_.emplace_back(Transaction::Type::WriteFromPipeline, write_address_, write_value_);
|
|
|
|
|
}
|
2022-07-31 00:34:37 +00:00
|
|
|
|
ram_[write_address_ & ram_mask_] = write_value_;
|
|
|
|
|
[[fallthrough]];
|
2022-07-29 20:15:18 +00:00
|
|
|
|
|
2022-07-31 00:34:37 +00:00
|
|
|
|
case WritePhase::Starting:
|
|
|
|
|
write_phase_ = WritePhase::Full;
|
|
|
|
|
write_address_ = pointer_[3];
|
|
|
|
|
write_value_ = output;
|
2022-08-06 13:51:20 +00:00
|
|
|
|
|
|
|
|
|
if constexpr (record_bus) {
|
|
|
|
|
transactions_.emplace_back(Transaction::Type::AddToPipeline, write_address_, write_value_);
|
|
|
|
|
}
|
2022-07-31 00:34:37 +00:00
|
|
|
|
pointer_[3] += direction_;
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
default: assert(false);
|
2021-09-23 22:13:51 +00:00
|
|
|
|
}
|
2021-09-21 03:08:26 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-09-16 23:56:28 +00:00
|
|
|
|
return true;
|
2021-07-22 22:43:07 +00:00
|
|
|
|
}
|
2022-08-06 13:51:20 +00:00
|
|
|
|
|
|
|
|
|
template <bool record_bus>
|
|
|
|
|
std::vector<typename Blitter<record_bus>::Transaction> Blitter<record_bus>::get_and_reset_transactions() {
|
|
|
|
|
std::vector<Transaction> result;
|
|
|
|
|
std::swap(result, transactions_);
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template class Amiga::Blitter<false>;
|
|
|
|
|
template class Amiga::Blitter<true>;
|
2022-08-15 15:10:17 +00:00
|
|
|
|
template bool Amiga::Blitter<true>::advance_dma<true>();
|
|
|
|
|
template bool Amiga::Blitter<true>::advance_dma<false>();
|
|
|
|
|
template bool Amiga::Blitter<false>::advance_dma<true>();
|
|
|
|
|
template bool Amiga::Blitter<false>::advance_dma<false>();
|