1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-07-05 10:28:58 +00:00

Merge pull request #1078 from TomHarte/SerialisedBlitter

Moves towards proper serialisation of the Amiga Blitter.
This commit is contained in:
Thomas Harte 2022-08-15 11:16:20 -04:00 committed by GitHub
commit e84e94ef61
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 849 additions and 2823438 deletions

View File

@ -111,7 +111,7 @@ bool Audio::advance_dma(int channel) {
}
void Audio::output() {
constexpr InterruptFlag interrupts[] = {
constexpr InterruptFlag::FlagT interrupts[] = {
InterruptFlag::AudioChannel0,
InterruptFlag::AudioChannel1,
InterruptFlag::AudioChannel2,

View File

@ -113,7 +113,8 @@ constexpr uint32_t fill_values[] = {
}
void Blitter::set_control(int index, uint16_t value) {
template <bool record_bus>
void Blitter<record_bus>::set_control(int index, uint16_t value) {
if(index) {
line_mode_ = (value & 0x0001);
one_dot_ = value & 0x0002;
@ -126,26 +127,26 @@ void Blitter::set_control(int index, uint16_t value) {
fill_carry_ = (value & 0x0004);
} else {
minterms_ = value & 0xff;
channel_enables_[3] = value & 0x100;
channel_enables_[2] = value & 0x200;
channel_enables_[1] = value & 0x400;
channel_enables_[0] = value & 0x800;
sequencer_.set_control(value >> 8);
}
shifts_[index] = value >> 12;
LOG("Set control " << index << " to " << PADHEX(4) << value);
}
void Blitter::set_first_word_mask(uint16_t value) {
template <bool record_bus>
void Blitter<record_bus>::set_first_word_mask(uint16_t value) {
LOG("Set first word mask: " << PADHEX(4) << value);
a_mask_[0] = value;
}
void Blitter::set_last_word_mask(uint16_t value) {
template <bool record_bus>
void Blitter<record_bus>::set_last_word_mask(uint16_t value) {
LOG("Set last word mask: " << PADHEX(4) << value);
a_mask_[1] = value;
}
void Blitter::set_size(uint16_t value) {
template <bool record_bus>
void Blitter<record_bus>::set_size(uint16_t value) {
// width_ = (width_ & ~0x3f) | (value & 0x3f);
// height_ = (height_ & ~0x3ff) | (value >> 6);
width_ = value & 0x3f;
@ -158,21 +159,25 @@ void Blitter::set_size(uint16_t value) {
// blitter that it should treat itself as about to start a new line.
}
void Blitter::set_minterms(uint16_t value) {
template <bool record_bus>
void Blitter<record_bus>::set_minterms(uint16_t value) {
LOG("Set minterms " << PADHEX(4) << value);
minterms_ = value & 0xff;
}
//void Blitter::set_vertical_size([[maybe_unused]] uint16_t value) {
//template <bool record_bus>
//void Blitter<record_bus>::set_vertical_size([[maybe_unused]] uint16_t value) {
// LOG("Set vertical size " << PADHEX(4) << value);
// // TODO. This is ECS only, I think. Ditto set_horizontal_size.
//}
//
//void Blitter::set_horizontal_size([[maybe_unused]] uint16_t value) {
//template <bool record_bus>
//void Blitter<record_bus>::set_horizontal_size([[maybe_unused]] uint16_t value) {
// LOG("Set horizontal size " << PADHEX(4) << value);
//}
void Blitter::set_data(int channel, uint16_t value) {
template <bool record_bus>
void Blitter<record_bus>::set_data(int channel, uint16_t value) {
LOG("Set data " << channel << " to " << PADHEX(4) << value);
// Ugh, backed myself into a corner. TODO: clean.
@ -184,18 +189,67 @@ void Blitter::set_data(int channel, uint16_t value) {
}
}
uint16_t Blitter::get_status() {
template <bool record_bus>
uint16_t Blitter<record_bus>::get_status() {
const uint16_t result =
(not_zero_flag_ ? 0x0000 : 0x2000) | (height_ ? 0x4000 : 0x0000);
LOG("Returned status of " << result);
return result;
}
bool Blitter::advance_dma() {
// 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
template <bool record_bus>
void Blitter<record_bus>::add_modulos() {
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_;
}
template <bool record_bus>
template <bool complete_immediately>
bool Blitter<record_bus>::advance_dma() {
if(!height_) return false;
not_zero_flag_ = false;
// TODO: eliminate @c complete_immediately and this workaround.
// See commentary in Chipset.cpp.
if constexpr (complete_immediately) {
while(get_status() & 0x4000) {
advance_dma<false>();
}
return true;
}
if(line_mode_) {
not_zero_flag_ = false;
// As-yet unimplemented:
assert(b_data_ == 0xffff);
@ -248,153 +302,266 @@ bool Blitter::advance_dma() {
// at https://github.com/niklasekstrom/blitter-subpixel-line/blob/master/Drawing%20lines%20using%20the%20Amiga%20blitter.pdf
//
int error = int16_t(pointer_[0] << 1) >> 1; // TODO: what happens if line_sign_ doesn't agree with this?
bool draw_ = true;
while(height_--) {
//
// Caveat: I've no idea how the DMA access slots should be laid out for
// line drawing.
//
if(draw_) {
// TODO: patterned lines. Unclear what to do with the bit that comes out of b.
// Probably extend it to a full word?
if(!busy_) {
error_ = int16_t(pointer_[0] << 1) >> 1; // TODO: what happens if line_sign_ doesn't agree with this?
draw_ = true;
busy_ = true;
has_c_data_ = false;
}
bool did_output = false;
if(draw_) {
// TODO: patterned lines. Unclear what to do with the bit that comes out of b.
// Probably extend it to a full word?
if(!has_c_data_) {
has_c_data_ = true;
c_data_ = ram_[pointer_[3] & ram_mask_];
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_;
}
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];
}
} else if(step & RIGHT) {
++shifts_[0];
if(shifts_[0] == 16) {
++pointer_[3];
if constexpr (record_bus) {
transactions_.emplace_back(Transaction::Type::ReadC, pointer_[3], c_data_);
}
return true;
}
shifts_[0] &= 15;
if(step & UP) {
pointer_[3] -= modulos_[2];
draw_ = true;
} else if(step & DOWN) {
pointer_[3] += modulos_[2];
draw_ = true;
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_;
has_c_data_ = false;
did_output = true;
if constexpr (record_bus) {
transactions_.emplace_back(Transaction::Type::WriteFromPipeline, pointer_[3], output);
}
}
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];
}
} else if(step & RIGHT) {
++shifts_[0];
if(shifts_[0] == 16) {
++pointer_[3];
}
}
shifts_[0] &= 15;
if(step & UP) {
pointer_[3] -= modulos_[2];
draw_ = true;
} else if(step & DOWN) {
pointer_[3] += modulos_[2];
draw_ = true;
}
--height_;
if(!height_) {
busy_ = false;
posit_interrupt(InterruptFlag::Blitter);
}
return did_output;
} else {
// Copy mode.
if(!busy_) {
sequencer_.begin();
a32_ = 0;
b32_ = 0;
// Quick hack: do the entire action atomically.
a32_ = 0;
b32_ = 0;
y_ = 0;
x_ = 0;
loop_index_ = -1;
write_phase_ = WritePhase::Starting;
not_zero_flag_ = false;
busy_ = true;
}
for(int y = 0; y < height_; y++) {
bool fill_carry = fill_carry_;
const auto next = sequencer_.next();
for(int x = 0; x < width_; x++) {
uint16_t a_mask = 0xffff;
if(x == 0) a_mask &= a_mask_[0];
if(x == width_ - 1) a_mask &= a_mask_[1];
// 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];
if(channel_enables_[0]) {
a_data_ = ram_[pointer_[0] & ram_mask_];
pointer_[0] += direction_;
}
a32_ = (a32_ << 16) | (a_data_ & a_mask);
if(channel_enables_[1]) {
b_data_ = ram_[pointer_[1] & ram_mask_];
pointer_[1] += direction_;
}
b32_ = (b32_ << 16) | b_data_;
if(channel_enables_[2]) {
c_data_ = ram_[pointer_[2] & ram_mask_];
pointer_[2] += direction_;
}
uint16_t a, b;
// 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]))
);
}
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;
}
output = fill_output;
fill_carry = ongoing_carry;
}
not_zero_flag_ |= output;
if(channel_enables_[3]) {
ram_[pointer_[3] & ram_mask_] = output;
pointer_[3] += direction_;
}
// Check whether an entire row was completed in the previous iteration.
// If so then add modulos. Though this won't capture the move off the
// final line, so that's handled elsewhere.
if(!x_ && y_) {
add_modulos();
}
pointer_[0] += modulos_[0] * channel_enables_[0] * direction_;
pointer_[1] += modulos_[1] * channel_enables_[1] * direction_;
pointer_[2] += modulos_[2] * channel_enables_[2] * direction_;
pointer_[3] += modulos_[3] * channel_enables_[3] * direction_;
++x_;
if(x_ == width_) {
transient_a_mask_ &= a_mask_[1];
x_ = 0;
++y_;
if(y_ == height_) {
sequencer_.complete();
}
}
++loop_index_;
}
using Channel = BlitterSequencer::Channel;
switch(next.first) {
case Channel::A:
a_data_ = ram_[pointer_[0] & ram_mask_];
if constexpr (record_bus) {
transactions_.emplace_back(Transaction::Type::ReadA, pointer_[0], a_data_);
}
pointer_[0] += direction_;
return true;
case Channel::B:
b_data_ = ram_[pointer_[1] & ram_mask_];
if constexpr (record_bus) {
transactions_.emplace_back(Transaction::Type::ReadB, pointer_[1], b_data_);
}
pointer_[1] += direction_;
return true;
case Channel::C:
c_data_ = ram_[pointer_[2] & ram_mask_];
if constexpr (record_bus) {
transactions_.emplace_back(Transaction::Type::ReadC, pointer_[2], c_data_);
}
pointer_[2] += direction_;
return true;
case Channel::FlushPipeline:
add_modulos();
posit_interrupt(InterruptFlag::Blitter);
height_ = 0;
busy_ = false;
if(write_phase_ == WritePhase::Full) {
if constexpr (record_bus) {
transactions_.emplace_back(Transaction::Type::WriteFromPipeline, write_address_, write_value_);
}
ram_[write_address_ & ram_mask_] = write_value_;
write_phase_ = WritePhase::Starting;
}
return true;
case Channel::None:
if constexpr (record_bus) {
transactions_.emplace_back(Transaction::Type::SkippedSlot);
}
return false;
case Channel::Write: break;
}
a32_ = (a32_ << 16) | (a_data_ & transient_a_mask_);
b32_ = (b32_ << 16) | b_data_;
uint16_t a, b;
// 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]))
);
}
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;
}
output = fill_output;
fill_carry_ = ongoing_carry;
}
not_zero_flag_ |= output;
switch(write_phase_) {
case WritePhase::Full:
if constexpr (record_bus) {
transactions_.emplace_back(Transaction::Type::WriteFromPipeline, write_address_, write_value_);
}
ram_[write_address_ & ram_mask_] = write_value_;
[[fallthrough]];
case WritePhase::Starting:
write_phase_ = WritePhase::Full;
write_address_ = pointer_[3];
write_value_ = output;
if constexpr (record_bus) {
transactions_.emplace_back(Transaction::Type::AddToPipeline, write_address_, write_value_);
}
pointer_[3] += direction_;
return true;
default: assert(false);
}
}
posit_interrupt(InterruptFlag::Blitter);
height_ = 0;
return true;
}
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>;
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>();

View File

@ -11,13 +11,168 @@
#include <cstddef>
#include <cstdint>
#include <string>
#include <vector>
#include "../../ClockReceiver/ClockReceiver.hpp"
#include "DMADevice.hpp"
namespace Amiga {
class Blitter: public DMADevice<4, 4> {
/*!
Statefully provides the next access the Blitter should make.
TODO: determine the actual logic here, rather than
relying on tables.
*/
class BlitterSequencer {
public:
enum class Channel {
/// Tells the caller to calculate and load a new piece of output
/// into the output pipeline.
///
/// If any inputs are enabled then a one-slot output pipeline applies:
/// output will rest in the pipeline for one write phase before being written.
Write,
/// Indicates that a write should occur if anything is in the pipeline, otherwise
/// no activity should occur.
FlushPipeline,
/// 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
};
/// Sets the current control value, which indicates which
/// channels are enabled.
void set_control(int control) {
control_ = control & 0xf;
index_ = 0; // TODO: this probably isn't accurate; case caught is a change
// of control values during a blit.
}
/// 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.
void complete() {
next_phase_ =
(control_ == 0x9 || control_ == 0xb || control_ == 0xd) ?
Phase::PauseAndComplete : Phase::Complete;
}
/// Begins a blit operation.
void begin() {
phase_ = next_phase_ = Phase::Ongoing;
index_ = loop_ = 0;
}
/// 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() {
switch(phase_) {
default: break;
case Phase::Complete:
return std::make_pair(Channel::FlushPipeline, loop_);
case Phase::PauseAndComplete:
phase_ = Phase::Complete;
return std::make_pair(Channel::None, loop_);
}
Channel next = Channel::None;
switch(control_) {
default: break;
case 0: next = next_channel(pattern0); 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;
}
return std::make_pair(next, loop_);
}
template <int channel> bool channel_enabled() {
return control_ & (8 >> channel);
}
private:
static constexpr std::array<Channel, 1> pattern0 = { Channel::None };
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) {
loop_ += index_ / list.size();
index_ %= list.size();
const Channel result = list[index_];
++index_;
if(index_ == list.size()) {
phase_ = next_phase_;
}
return result;
}
// Current control flags, i.e. which channels are enabled.
int control_ = 0;
// Index into the pattern table for this blit.
size_t index_ = 0;
// Number of times the entire pattern table has been completed.
int loop_ = 0;
enum class Phase {
/// 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;
};
/*!
If @c record_bus is @c true then all bus interactions will be recorded
and can subsequently be retrieved. This is included for testing purposes.
*/
template <bool record_bus = false> class Blitter: public DMADevice<4, 4> {
public:
using DMADevice::DMADevice;
@ -37,7 +192,42 @@ class Blitter: public DMADevice<4, 4> {
uint16_t get_status();
bool advance_dma();
template <bool complete_immediately> bool advance_dma();
struct Transaction {
enum class Type {
SkippedSlot,
ReadA,
ReadB,
ReadC,
AddToPipeline,
WriteFromPipeline
} type = Type::SkippedSlot;
uint32_t address = 0;
uint16_t value = 0;
Transaction() {}
Transaction(Type type) : type(type) {}
Transaction(Type type, uint32_t address, uint16_t value) : type(type), address(address), value(value) {}
std::string to_string() const {
std::string result;
switch(type) {
case Type::SkippedSlot: result = "SkippedSlot"; break;
case Type::ReadA: result = "ReadA"; break;
case Type::ReadB: result = "ReadB"; break;
case Type::ReadC: result = "ReadC"; break;
case Type::AddToPipeline: result = "AddToPipeline"; break;
case Type::WriteFromPipeline: result = "WriteFromPipeline"; break;
}
result += " address:" + std::to_string(address) + " value:" + std::to_string(value);
return result;
}
};
std::vector<Transaction> get_and_reset_transactions();
private:
int width_ = 0, height_ = 0;
@ -54,13 +244,29 @@ class Blitter: public DMADevice<4, 4> {
bool exclusive_fill_ = false;
bool fill_carry_ = false;
bool channel_enables_[4]{};
uint8_t minterms_ = 0;
uint32_t a32_ = 0, b32_ = 0;
uint16_t a_data_ = 0, b_data_ = 0, c_data_ = 0;
bool not_zero_flag_ = false;
BlitterSequencer sequencer_;
uint32_t write_address_ = 0xffff'ffff;
uint16_t write_value_ = 0;
enum WritePhase {
Starting, Full
} write_phase_;
int y_, x_;
uint16_t transient_a_mask_;
bool busy_ = false;
int loop_index_ = -1;
int error_ = 0;
bool draw_ = false;
bool has_c_data_ = false;
void add_modulos();
std::vector<Transaction> transactions_;
};
}

View File

@ -21,35 +21,6 @@
using namespace Amiga;
// TODO: I don't think the nonsense below, which was intended to allow a typed enum but also
// clean combination, really works. Rethink.
namespace {
template <typename EnumT, EnumT... T> struct Mask {
static constexpr uint16_t value = 0;
};
template <typename EnumT, EnumT F, EnumT... T> struct Mask<EnumT, F, T...> {
static constexpr uint16_t value = uint16_t(F) | Mask<EnumT, T...>::value;
};
template <InterruptFlag... Flags> struct InterruptMask: Mask<InterruptFlag, Flags...> {};
template <DMAFlag... Flags> struct DMAMask: Mask<DMAFlag, Flags...> {};
constexpr uint16_t AudioFlags[] = {
DMAMask<DMAFlag::AudioChannel0, DMAFlag::AllBelow>::value,
DMAMask<DMAFlag::AudioChannel1, DMAFlag::AllBelow>::value,
DMAMask<DMAFlag::AudioChannel2, DMAFlag::AllBelow>::value,
DMAMask<DMAFlag::AudioChannel3, DMAFlag::AllBelow>::value,
};
constexpr auto BlitterFlag = DMAMask<DMAFlag::Blitter, DMAFlag::AllBelow>::value;
constexpr auto BitplaneFlag = DMAMask<DMAFlag::Bitplane, DMAFlag::AllBelow>::value;
constexpr auto CopperFlag = DMAMask<DMAFlag::Copper, DMAFlag::AllBelow>::value;
constexpr auto DiskFlag = DMAMask<DMAFlag::Disk, DMAFlag::AllBelow>::value;
constexpr auto SpritesFlag = DMAMask<DMAFlag::Sprites, DMAFlag::AllBelow>::value;
}
#define DMA_CONSTRUCT *this, reinterpret_cast<uint16_t *>(map.chip_ram.data()), map.chip_ram.size() >> 1
Chipset::Chipset(MemoryMap &map, int input_clock_rate) :
@ -94,17 +65,17 @@ void Chipset::set_cia_interrupts(bool cia_a_interrupt, bool cia_b_interrupt) {
// If latched, is it only on a leading edge?
// interrupt_requests_ &= ~InterruptMask<InterruptFlag::IOPortsAndTimers, InterruptFlag::External>::value;
interrupt_requests_ |=
(cia_a_interrupt ? InterruptMask<InterruptFlag::IOPortsAndTimers>::value : 0) |
(cia_b_interrupt ? InterruptMask<InterruptFlag::External>::value : 0);
(cia_a_interrupt ? InterruptFlag::IOPortsAndTimers : 0) |
(cia_b_interrupt ? InterruptFlag::External : 0);
update_interrupts();
}
void Chipset::posit_interrupt(InterruptFlag flag) {
interrupt_requests_ |= uint16_t(flag);
void Chipset::posit_interrupt(InterruptFlag::FlagT flag) {
interrupt_requests_ |= flag;
update_interrupts();
}
void DMADeviceBase::posit_interrupt(InterruptFlag flag) {
void DMADeviceBase::posit_interrupt(InterruptFlag::FlagT flag) {
chipset_.posit_interrupt(flag);
}
@ -557,9 +528,10 @@ template <int cycle, bool stop_if_cpu> bool Chipset::perform_cycle() {
}
}
constexpr auto BitplaneEnabled = DMAFlag::AllBelow | DMAFlag::Bitplane;
if(
horizontal_fetch_ != HorizontalFetch::Stopped &&
(dma_control_ & BitplaneFlag) == BitplaneFlag &&
(dma_control_ & BitplaneEnabled) == BitplaneEnabled &&
fetch_vertical_ &&
bitplanes_.advance_dma(cycle - horizontal_offset_)
) {
@ -622,7 +594,8 @@ template <int cycle, bool stop_if_cpu> bool Chipset::perform_cycle() {
}
if constexpr (cycle >= 0x08 && cycle < 0x0e) {
if((dma_control_ & DiskFlag) == DiskFlag) {
constexpr auto DiskEnabled = DMAFlag::AllBelow | DMAFlag::Disk;
if((dma_control_ & DiskEnabled) == DiskEnabled) {
if(disk_.advance_dma()) {
return false;
}
@ -634,6 +607,13 @@ template <int cycle, bool stop_if_cpu> bool Chipset::perform_cycle() {
static_assert(channel >= 0 && channel < 4);
static_assert(cycle != 0x15 || channel == 3);
constexpr DMAFlag::FlagT AudioFlags[] = {
DMAFlag::AllBelow | DMAFlag::AudioChannel0,
DMAFlag::AllBelow | DMAFlag::AudioChannel1,
DMAFlag::AllBelow | DMAFlag::AudioChannel2,
DMAFlag::AllBelow | DMAFlag::AudioChannel3,
};
if((dma_control_ & AudioFlags[channel]) == AudioFlags[channel]) {
if(audio_.advance_dma(channel)) {
return false;
@ -642,7 +622,8 @@ template <int cycle, bool stop_if_cpu> bool Chipset::perform_cycle() {
}
if constexpr (cycle >= 0x16 && cycle < 0x36) {
if(y_ >= vertical_blank_height_ && (dma_control_ & SpritesFlag) == SpritesFlag) {
constexpr auto SpritesEnabled = DMAFlag::AllBelow | DMAFlag::Sprites;
if(y_ >= vertical_blank_height_ && (dma_control_ & SpritesEnabled) == SpritesEnabled) {
constexpr auto sprite_id = (cycle - 0x16) >> 2;
static_assert(sprite_id >= 0 && sprite_id < std::tuple_size<decltype(sprites_)>::value);
@ -654,20 +635,32 @@ template <int cycle, bool stop_if_cpu> bool Chipset::perform_cycle() {
} else {
// Bitplanes having been dealt with, specific even-cycle responsibility
// is just possibly to pass to the Copper.
//
// The Blitter and CPU are dealt with outside of the odd/even test.
if((dma_control_ & CopperFlag) == CopperFlag) {
constexpr auto CopperEnabled = DMAFlag::AllBelow | DMAFlag::Copper;
if((dma_control_ & CopperEnabled) == CopperEnabled) {
if(copper_.advance_dma(uint16_t(((y_ & 0xff) << 8) | cycle), blitter_.get_status())) {
return false;
}
} else {
copper_.stop();
}
// Picking between the Blitter and CPU occurs below, if applicable.
// But if the Blitter priority bit isn't set then don't even give it
// a look-in — nothing else having claimed this slot, leave it vacant
// for the CPU.
if(!(dma_control_ & DMAFlag::BlitterPriority)) {
return true;
}
}
// Down here: give first refusal to the Blitter, otherwise
// pass on to the CPU.
return (dma_control_ & BlitterFlag) != BlitterFlag || !blitter_.advance_dma();
// Give first refusal to the Blitter (if enabled), otherwise pass on to the CPU.
//
// TODO: determine why I see Blitter issues if I don't allow it to complete immediately.
// All tests pass without immediate completion, and immediate completion just runs the
// non-immediate version until the busy flag is disabled. So probably a scheduling or
// signalling issue out here.
constexpr auto BlitterEnabled = DMAFlag::AllBelow | DMAFlag::Blitter;
return (dma_control_ & BlitterEnabled) != BlitterEnabled || !blitter_.advance_dma<true>();
}
/// Performs all slots starting with @c first_slot and ending just before @c last_slot.
@ -764,7 +757,7 @@ template <bool stop_on_cpu> Chipset::Changes Chipset::run(HalfCycles length) {
if(y_ == short_field_height_ + is_long_field_) {
++vsyncs;
interrupt_requests_ |= InterruptMask<InterruptFlag::VerticalBlank>::value;
interrupt_requests_ |= InterruptFlag::VerticalBlank;
update_interrupts();
y_ = 0;
@ -832,17 +825,17 @@ void Chipset::update_interrupts() {
const uint16_t enabled_requests = interrupt_enable_ & interrupt_requests_ & 0x3fff;
if(enabled_requests && (interrupt_enable_ & 0x4000)) {
if(enabled_requests & InterruptMask<InterruptFlag::External>::value) {
if(enabled_requests & InterruptFlag::External) {
interrupt_level_ = 6;
} else if(enabled_requests & InterruptMask<InterruptFlag::SerialPortReceive, InterruptFlag::DiskSyncMatch>::value) {
} else if(enabled_requests & (InterruptFlag::SerialPortReceive | InterruptFlag::DiskSyncMatch)) {
interrupt_level_ = 5;
} else if(enabled_requests & InterruptMask<InterruptFlag::AudioChannel0, InterruptFlag::AudioChannel1, InterruptFlag::AudioChannel2, InterruptFlag::AudioChannel3>::value) {
} else if(enabled_requests & (InterruptFlag::AudioChannel0 | InterruptFlag::AudioChannel1 | InterruptFlag::AudioChannel2 | InterruptFlag::AudioChannel3)) {
interrupt_level_ = 4;
} else if(enabled_requests & InterruptMask<InterruptFlag::Copper, InterruptFlag::VerticalBlank, InterruptFlag::Blitter>::value) {
} else if(enabled_requests & (InterruptFlag::Copper | InterruptFlag::VerticalBlank | InterruptFlag::Blitter)) {
interrupt_level_ = 3;
} else if(enabled_requests & InterruptMask<InterruptFlag::IOPortsAndTimers>::value) {
} else if(enabled_requests & InterruptFlag::IOPortsAndTimers) {
interrupt_level_ = 2;
} else if(enabled_requests & InterruptMask<InterruptFlag::SerialPortTransmit, InterruptFlag::DiskBlock, InterruptFlag::Software>::value) {
} else if(enabled_requests & (InterruptFlag::SerialPortTransmit | InterruptFlag::DiskBlock | InterruptFlag::Software)) {
interrupt_level_ = 1;
}
}

View File

@ -125,7 +125,7 @@ class Chipset: private ClockingHint::Observer {
int interrupt_level_ = 0;
void update_interrupts();
void posit_interrupt(InterruptFlag);
void posit_interrupt(InterruptFlag::FlagT);
// MARK: - Scheduler.
@ -139,7 +139,7 @@ class Chipset: private ClockingHint::Observer {
// MARK: - DMA Control, Scheduler and Blitter.
uint16_t dma_control_ = 0;
Blitter blitter_;
Blitter<false> blitter_;
// MARK: - Sprites and collision flags.

View File

@ -21,10 +21,14 @@ using namespace Amiga;
namespace {
bool satisfies_raster(uint16_t position, uint16_t blitter_status, uint16_t *instruction) {
// Return immediately if: (i) wait-for-Blitter is not disabled; and (ii) the Blitter is busy.
if(!(instruction[1] & 0x8000) && (blitter_status & 0x4000)) {
return false;
}
// Otherwise, test the raster position against the instruction's value and mask.
const uint16_t mask = 0x8000 | (instruction[1] & 0x7ffe);
return
(position & mask) >= (instruction[0] & mask) &&
(!(blitter_status & 0x4000) || (instruction[1] & 0x8000));
return (position & mask) >= (instruction[0] & mask);
}
}

View File

@ -24,7 +24,7 @@ class DMADeviceBase {
DMADeviceBase(Chipset &chipset, uint16_t *ram, size_t word_size) :
chipset_(chipset), ram_(ram), ram_mask_(uint32_t(word_size - 1)) {}
void posit_interrupt(Amiga::InterruptFlag);
void posit_interrupt(Amiga::InterruptFlag::FlagT);
protected:
Chipset &chipset_;

View File

@ -11,39 +11,43 @@
namespace Amiga {
enum class InterruptFlag: uint16_t {
SerialPortTransmit = 1 << 0,
DiskBlock = 1 << 1,
Software = 1 << 2,
IOPortsAndTimers = 1 << 3, // i.e. CIA A.
Copper = 1 << 4,
VerticalBlank = 1 << 5,
Blitter = 1 << 6,
AudioChannel0 = 1 << 7,
AudioChannel1 = 1 << 8,
AudioChannel2 = 1 << 9,
AudioChannel3 = 1 << 10,
SerialPortReceive = 1 << 11,
DiskSyncMatch = 1 << 12,
External = 1 << 13, // i.e. CIA B.
};
namespace InterruptFlag {
using FlagT = uint16_t;
enum class DMAFlag: uint16_t {
AudioChannel0 = 1 << 0,
AudioChannel1 = 1 << 1,
AudioChannel2 = 1 << 2,
AudioChannel3 = 1 << 3,
Disk = 1 << 4,
Sprites = 1 << 5,
Blitter = 1 << 6,
Copper = 1 << 7,
Bitplane = 1 << 8,
AllBelow = 1 << 9,
BlitterPriority = 1 << 10,
BlitterZero = 1 << 13,
BlitterBusy = 1 << 14,
};
constexpr FlagT SerialPortTransmit = 1 << 0;
constexpr FlagT DiskBlock = 1 << 1;
constexpr FlagT Software = 1 << 2;
constexpr FlagT IOPortsAndTimers = 1 << 3; // i.e. CIA A.
constexpr FlagT Copper = 1 << 4;
constexpr FlagT VerticalBlank = 1 << 5;
constexpr FlagT Blitter = 1 << 6;
constexpr FlagT AudioChannel0 = 1 << 7;
constexpr FlagT AudioChannel1 = 1 << 8;
constexpr FlagT AudioChannel2 = 1 << 9;
constexpr FlagT AudioChannel3 = 1 << 10;
constexpr FlagT SerialPortReceive = 1 << 11;
constexpr FlagT DiskSyncMatch = 1 << 12;
constexpr FlagT External = 1 << 13; // i.e. CIA B.
}
};
namespace DMAFlag {
using FlagT = uint16_t;
constexpr FlagT AudioChannel0 = 1 << 0;
constexpr FlagT AudioChannel1 = 1 << 1;
constexpr FlagT AudioChannel2 = 1 << 2;
constexpr FlagT AudioChannel3 = 1 << 3;
constexpr FlagT Disk = 1 << 4;
constexpr FlagT Sprites = 1 << 5;
constexpr FlagT Blitter = 1 << 6;
constexpr FlagT Copper = 1 << 7;
constexpr FlagT Bitplane = 1 << 8;
constexpr FlagT AllBelow = 1 << 9;
constexpr FlagT BlitterPriority = 1 << 10;
constexpr FlagT BlitterZero = 1 << 13;
constexpr FlagT BlitterBusy = 1 << 14;
}
}
#endif /* Flags_hpp */

View File

@ -971,6 +971,7 @@
4BC6236E26F4235400F83DFE /* Copper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC6236C26F4235400F83DFE /* Copper.cpp */; };
4BC6236F26F426B400F83DFE /* FAT.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B477709268FBE4D005C2340 /* FAT.cpp */; };
4BC6237226F94BCB00F83DFE /* MintermTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BC6237126F94BCB00F83DFE /* MintermTests.mm */; };
4BC62FF228A149300036AE59 /* NSData+dataWithContentsOfGZippedFile.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BC62FF128A149300036AE59 /* NSData+dataWithContentsOfGZippedFile.m */; };
4BC751B21D157E61006C31D9 /* 6522Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BC751B11D157E61006C31D9 /* 6522Tests.swift */; };
4BC76E691C98E31700E6EF73 /* FIRFilter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC76E671C98E31700E6EF73 /* FIRFilter.cpp */; };
4BC890D3230F86020025A55A /* DirectAccessDevice.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC890D1230F86020025A55A /* DirectAccessDevice.cpp */; };
@ -2053,6 +2054,8 @@
4BC6236C26F4235400F83DFE /* Copper.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Copper.cpp; sourceTree = "<group>"; };
4BC6237026F94A5B00F83DFE /* Minterms.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Minterms.hpp; sourceTree = "<group>"; };
4BC6237126F94BCB00F83DFE /* MintermTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = MintermTests.mm; sourceTree = "<group>"; };
4BC62FF028A149300036AE59 /* NSData+dataWithContentsOfGZippedFile.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSData+dataWithContentsOfGZippedFile.h"; sourceTree = "<group>"; };
4BC62FF128A149300036AE59 /* NSData+dataWithContentsOfGZippedFile.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSData+dataWithContentsOfGZippedFile.m"; sourceTree = "<group>"; };
4BC751B11D157E61006C31D9 /* 6522Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = 6522Tests.swift; sourceTree = "<group>"; };
4BC76E671C98E31700E6EF73 /* FIRFilter.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FIRFilter.cpp; sourceTree = "<group>"; };
4BC76E681C98E31700E6EF73 /* FIRFilter.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = FIRFilter.hpp; sourceTree = "<group>"; };
@ -4283,6 +4286,8 @@
4BFCA12A1ECBE7C400AC40C1 /* ZexallTests.swift */,
4B3BA0C41D318B44005DD7A7 /* Bridges */,
4B1414631B588A1100E04248 /* Test Binaries */,
4BC62FF028A149300036AE59 /* NSData+dataWithContentsOfGZippedFile.h */,
4BC62FF128A149300036AE59 /* NSData+dataWithContentsOfGZippedFile.m */,
);
path = "Clock SignalTests";
sourceTree = "<group>";
@ -6123,6 +6128,7 @@
4B778F4F23A5F21C0000D260 /* StaticAnalyser.cpp in Sources */,
4B8DD3682633B2D400B3C866 /* SpectrumVideoContentionTests.mm in Sources */,
4B7752A928217E200073E2C5 /* 65816Storage.cpp in Sources */,
4BC62FF228A149300036AE59 /* NSData+dataWithContentsOfGZippedFile.m in Sources */,
4B7752AC28217E6E0073E2C5 /* StaticAnalyser.cpp in Sources */,
4B778F1223A5EC720000D260 /* CRT.cpp in Sources */,
4B778EF423A5DB3A0000D260 /* C1540.cpp in Sources */,

File diff suppressed because it is too large Load Diff

View File

@ -1,210 +0,0 @@
[
["bltafwm", 8191],
["bltalwm", 65528],
["bltcon1", 0],
["bltbdat", 65535],
["bltadat", 65535],
["bltamod", 0],
["bltdmod", 76],
["bltcmod", 76],
["bltapth", 0],
["bltaptl", 47616],
["bltcon0", 874],
["bltcpth", 1],
["bltcptl", 1492],
["bltdpth", 1],
["bltdptl", 1492],
["bltsize", 642],
["cread", 67028, 59391],
["write", 67028, 63488],
["cread", 67030, 65511],
["write", 67030, 31],
["cread", 67108, 58880],
["write", 67108, 63999],
["cread", 67110, 2023],
["write", 67110, 63519],
["cread", 67188, 58880],
["write", 67188, 63999],
["cread", 67190, 103],
["write", 67190, 65439],
["cread", 67268, 26119],
["write", 67268, 31224],
["cread", 67270, 65127],
["write", 67270, 415],
["cread", 67348, 26119],
["write", 67348, 31224],
["cread", 67350, 65127],
["write", 67350, 415],
["cread", 67428, 26119],
["write", 67428, 31224],
["cread", 67430, 65127],
["write", 67430, 415],
["cread", 67508, 26119],
["write", 67508, 31224],
["cread", 67510, 65127],
["write", 67510, 415],
["cread", 67588, 26599],
["write", 67588, 30744],
["cread", 67590, 65127],
["write", 67590, 415],
["cread", 67668, 26592],
["write", 67668, 30751],
["cread", 67670, 103],
["write", 67670, 65439],
["cread", 67748, 59391],
["write", 67748, 63488],
["cread", 67750, 65511],
["write", 67750, 31],
["bltapth", 0],
["bltaptl", 47616],
["bltcon0", 874],
["bltcpth", 1],
["bltcptl", 21972],
["bltdpth", 1],
["bltdptl", 21972],
["bltsize", 642],
["cread", 87508, 0],
["write", 87508, 8191],
["cread", 87510, 0],
["write", 87510, 65528],
["cread", 87588, 511],
["write", 87588, 7680],
["cread", 87590, 63488],
["write", 87590, 2040],
["cread", 87668, 480],
["write", 87668, 7711],
["cread", 87670, 0],
["write", 87670, 65528],
["cread", 87748, 33248],
["write", 87748, 40479],
["cread", 87750, 0],
["write", 87750, 65528],
["cread", 87828, 33248],
["write", 87828, 40479],
["cread", 87830, 0],
["write", 87830, 65528],
["cread", 87908, 33248],
["write", 87908, 40479],
["cread", 87910, 0],
["write", 87910, 65528],
["cread", 87988, 33248],
["write", 87988, 40479],
["cread", 87990, 0],
["write", 87990, 65528],
["cread", 88068, 32768],
["write", 88068, 40959],
["cread", 88070, 0],
["write", 88070, 65528],
["cread", 88148, 32768],
["write", 88148, 40959],
["cread", 88150, 0],
["write", 88150, 65528],
["cread", 88228, 0],
["write", 88228, 8191],
["cread", 88230, 0],
["write", 88230, 65528],
["bltafwm", 8191],
["bltalwm", 65528],
["bltcon1", 0],
["bltbdat", 65535],
["bltadat", 65535],
["bltamod", 0],
["bltdmod", 76],
["bltcmod", 76],
["bltapth", 0],
["bltaptl", 47616],
["bltcon0", 874],
["bltcpth", 1],
["bltcptl", 1492],
["bltdpth", 1],
["bltdptl", 1492],
["bltsize", 642],
["cread", 67028, 63488],
["write", 67028, 59391],
["cread", 67030, 31],
["write", 67030, 65511],
["cread", 67108, 63999],
["write", 67108, 58880],
["cread", 67110, 63519],
["write", 67110, 2023],
["cread", 67188, 63999],
["write", 67188, 58880],
["cread", 67190, 65439],
["write", 67190, 103],
["cread", 67268, 31224],
["write", 67268, 26119],
["cread", 67270, 415],
["write", 67270, 65127],
["cread", 67348, 31224],
["write", 67348, 26119],
["cread", 67350, 415],
["write", 67350, 65127],
["cread", 67428, 31224],
["write", 67428, 26119],
["cread", 67430, 415],
["write", 67430, 65127],
["cread", 67508, 31224],
["write", 67508, 26119],
["cread", 67510, 415],
["write", 67510, 65127],
["cread", 67588, 30744],
["write", 67588, 26599],
["cread", 67590, 415],
["write", 67590, 65127],
["cread", 67668, 30751],
["write", 67668, 26592],
["cread", 67670, 65439],
["write", 67670, 103],
["cread", 67748, 63488],
["write", 67748, 59391],
["cread", 67750, 31],
["write", 67750, 65511],
["bltapth", 0],
["bltaptl", 47616],
["bltcon0", 874],
["bltcpth", 1],
["bltcptl", 21972],
["bltdpth", 1],
["bltdptl", 21972],
["bltsize", 642],
["cread", 87508, 8191],
["write", 87508, 0],
["cread", 87510, 65528],
["write", 87510, 0],
["cread", 87588, 7680],
["write", 87588, 511],
["cread", 87590, 2040],
["write", 87590, 63488],
["cread", 87668, 7711],
["write", 87668, 480],
["cread", 87670, 65528],
["write", 87670, 0],
["cread", 87748, 40479],
["write", 87748, 33248],
["cread", 87750, 65528],
["write", 87750, 0],
["cread", 87828, 40479],
["write", 87828, 33248],
["cread", 87830, 65528],
["write", 87830, 0],
["cread", 87908, 40479],
["write", 87908, 33248],
["cread", 87910, 65528],
["write", 87910, 0],
["cread", 87988, 40479],
["write", 87988, 33248],
["cread", 87990, 65528],
["write", 87990, 0],
["cread", 88068, 40959],
["write", 88068, 32768],
["cread", 88070, 65528],
["write", 88070, 0],
["cread", 88148, 40959],
["write", 88148, 32768],
["cread", 88150, 65528],
["write", 88150, 0],
["cread", 88228, 8191],
["write", 88228, 0],
["cread", 88230, 65528],
["write", 88230, 0]
]

View File

@ -1,785 +0,0 @@
[
["bltafwm", 65535],
["bltalwm", 65535],
["bltbmod", 0],
["bltamod", 0],
["bltdmod", 0],
["bltcdat", 21845],
["bltapth", 0],
["bltaptl", 27983],
["bltbpth", 0],
["bltbptl", 28495],
["bltdpth", 0],
["bltdptl", 24127],
["bltcon0", 7640],
["bltcon1", 2],
["bltsize", 2056],
["aread", 27982, 43690],
["bread", 28494, 43690],
["write", 24126, 0],
["aread", 27980, 43690],
["bread", 28492, 43690],
["write", 24124, 0],
["aread", 27978, 43690],
["bread", 28490, 43690],
["write", 24122, 0],
["aread", 27976, 43690],
["bread", 28488, 43690],
["write", 24120, 0],
["aread", 27974, 43690],
["bread", 28486, 43690],
["write", 24118, 0],
["aread", 27972, 43690],
["bread", 28484, 43690],
["write", 24116, 0],
["aread", 27970, 43690],
["bread", 28482, 43690],
["write", 24114, 0],
["aread", 27968, 43690],
["bread", 28480, 43690],
["write", 24112, 0],
["aread", 27966, 43690],
["bread", 28478, 43690],
["write", 24110, 0],
["aread", 27964, 43690],
["bread", 28476, 43690],
["write", 24108, 0],
["aread", 27962, 43690],
["bread", 28474, 43690],
["write", 24106, 0],
["aread", 27960, 43690],
["bread", 28472, 43690],
["write", 24104, 0],
["aread", 27958, 43690],
["bread", 28470, 43690],
["write", 24102, 0],
["aread", 27956, 43690],
["bread", 28468, 43690],
["write", 24100, 0],
["aread", 27954, 43690],
["bread", 28466, 43690],
["write", 24098, 0],
["aread", 27952, 43690],
["bread", 28464, 43690],
["write", 24096, 0],
["aread", 27950, 43690],
["bread", 28462, 43690],
["write", 24094, 0],
["aread", 27948, 43690],
["bread", 28460, 43690],
["write", 24092, 0],
["aread", 27946, 43690],
["bread", 28458, 43690],
["write", 24090, 0],
["aread", 27944, 43690],
["bread", 28456, 43690],
["write", 24088, 0],
["aread", 27942, 43690],
["bread", 28454, 43690],
["write", 24086, 0],
["aread", 27940, 43690],
["bread", 28452, 43690],
["write", 24084, 0],
["aread", 27938, 43690],
["bread", 28450, 43690],
["write", 24082, 0],
["aread", 27936, 43690],
["bread", 28448, 43690],
["write", 24080, 0],
["aread", 27934, 43690],
["bread", 28446, 43690],
["write", 24078, 0],
["aread", 27932, 43690],
["bread", 28444, 43690],
["write", 24076, 0],
["aread", 27930, 43690],
["bread", 28442, 43690],
["write", 24074, 0],
["aread", 27928, 43690],
["bread", 28440, 43690],
["write", 24072, 0],
["aread", 27926, 43690],
["bread", 28438, 43690],
["write", 24070, 0],
["aread", 27924, 43690],
["bread", 28436, 43690],
["write", 24068, 0],
["aread", 27922, 43690],
["bread", 28434, 43690],
["write", 24066, 0],
["aread", 27920, 43690],
["bread", 28432, 43690],
["write", 24064, 0],
["aread", 27918, 43690],
["bread", 28430, 43690],
["write", 24062, 0],
["aread", 27916, 43690],
["bread", 28428, 43690],
["write", 24060, 0],
["aread", 27914, 43690],
["bread", 28426, 43690],
["write", 24058, 0],
["aread", 27912, 43690],
["bread", 28424, 43690],
["write", 24056, 0],
["aread", 27910, 43690],
["bread", 28422, 43690],
["write", 24054, 0],
["aread", 27908, 43690],
["bread", 28420, 43690],
["write", 24052, 0],
["aread", 27906, 43690],
["bread", 28418, 43690],
["write", 24050, 0],
["aread", 27904, 43690],
["bread", 28416, 43690],
["write", 24048, 0],
["aread", 27902, 43690],
["bread", 28414, 43690],
["write", 24046, 0],
["aread", 27900, 43690],
["bread", 28412, 43690],
["write", 24044, 0],
["aread", 27898, 43690],
["bread", 28410, 43690],
["write", 24042, 0],
["aread", 27896, 43690],
["bread", 28408, 43690],
["write", 24040, 0],
["aread", 27894, 43690],
["bread", 28406, 43690],
["write", 24038, 0],
["aread", 27892, 43690],
["bread", 28404, 43690],
["write", 24036, 0],
["aread", 27890, 43690],
["bread", 28402, 43690],
["write", 24034, 0],
["aread", 27888, 43690],
["bread", 28400, 43690],
["write", 24032, 0],
["aread", 27886, 43690],
["bread", 28398, 43690],
["write", 24030, 0],
["aread", 27884, 43690],
["bread", 28396, 43690],
["write", 24028, 0],
["aread", 27882, 43690],
["bread", 28394, 43690],
["write", 24026, 0],
["aread", 27880, 43690],
["bread", 28392, 43690],
["write", 24024, 0],
["aread", 27878, 43690],
["bread", 28390, 43690],
["write", 24022, 0],
["aread", 27876, 43690],
["bread", 28388, 43690],
["write", 24020, 0],
["aread", 27874, 43690],
["bread", 28386, 43690],
["write", 24018, 0],
["aread", 27872, 43690],
["bread", 28384, 43690],
["write", 24016, 0],
["aread", 27870, 43690],
["bread", 28382, 43690],
["write", 24014, 0],
["aread", 27868, 43690],
["bread", 28380, 43690],
["write", 24012, 0],
["aread", 27866, 43690],
["bread", 28378, 43690],
["write", 24010, 0],
["aread", 27864, 43690],
["bread", 28376, 43690],
["write", 24008, 0],
["aread", 27862, 43690],
["bread", 28374, 43690],
["write", 24006, 0],
["aread", 27860, 43690],
["bread", 28372, 43690],
["write", 24004, 0],
["aread", 27858, 43690],
["bread", 28370, 43690],
["write", 24002, 0],
["aread", 27856, 43690],
["bread", 28368, 43690],
["write", 24000, 0],
["aread", 27854, 43690],
["bread", 28366, 43690],
["write", 23998, 0],
["aread", 27852, 43690],
["bread", 28364, 43690],
["write", 23996, 0],
["aread", 27850, 43690],
["bread", 28362, 43690],
["write", 23994, 0],
["aread", 27848, 43690],
["bread", 28360, 43690],
["write", 23992, 0],
["aread", 27846, 43690],
["bread", 28358, 43690],
["write", 23990, 0],
["aread", 27844, 43690],
["bread", 28356, 43690],
["write", 23988, 0],
["aread", 27842, 43690],
["bread", 28354, 43690],
["write", 23986, 0],
["aread", 27840, 43690],
["bread", 28352, 43690],
["write", 23984, 0],
["aread", 27838, 43690],
["bread", 28350, 43690],
["write", 23982, 0],
["aread", 27836, 43690],
["bread", 28348, 43690],
["write", 23980, 0],
["aread", 27834, 43690],
["bread", 28346, 43690],
["write", 23978, 0],
["aread", 27832, 43690],
["bread", 28344, 43690],
["write", 23976, 0],
["aread", 27830, 43690],
["bread", 28342, 43690],
["write", 23974, 0],
["aread", 27828, 43690],
["bread", 28340, 43690],
["write", 23972, 0],
["aread", 27826, 43690],
["bread", 28338, 43690],
["write", 23970, 0],
["aread", 27824, 43690],
["bread", 28336, 43690],
["write", 23968, 0],
["aread", 27822, 43690],
["bread", 28334, 43690],
["write", 23966, 0],
["aread", 27820, 43690],
["bread", 28332, 43690],
["write", 23964, 0],
["aread", 27818, 43690],
["bread", 28330, 43690],
["write", 23962, 0],
["aread", 27816, 43690],
["bread", 28328, 43690],
["write", 23960, 0],
["aread", 27814, 43690],
["bread", 28326, 43690],
["write", 23958, 0],
["aread", 27812, 43690],
["bread", 28324, 43690],
["write", 23956, 0],
["aread", 27810, 43690],
["bread", 28322, 43690],
["write", 23954, 0],
["aread", 27808, 43690],
["bread", 28320, 43690],
["write", 23952, 0],
["aread", 27806, 43690],
["bread", 28318, 43690],
["write", 23950, 0],
["aread", 27804, 43690],
["bread", 28316, 43690],
["write", 23948, 0],
["aread", 27802, 43690],
["bread", 28314, 43690],
["write", 23946, 0],
["aread", 27800, 43690],
["bread", 28312, 43690],
["write", 23944, 0],
["aread", 27798, 43690],
["bread", 28310, 43690],
["write", 23942, 0],
["aread", 27796, 43690],
["bread", 28308, 43690],
["write", 23940, 0],
["aread", 27794, 43690],
["bread", 28306, 43690],
["write", 23938, 0],
["aread", 27792, 43690],
["bread", 28304, 43690],
["write", 23936, 0],
["aread", 27790, 43690],
["bread", 28302, 43690],
["write", 23934, 0],
["aread", 27788, 43690],
["bread", 28300, 43690],
["write", 23932, 0],
["aread", 27786, 43690],
["bread", 28298, 43690],
["write", 23930, 0],
["aread", 27784, 43690],
["bread", 28296, 43690],
["write", 23928, 0],
["aread", 27782, 43690],
["bread", 28294, 43690],
["write", 23926, 0],
["aread", 27780, 43690],
["bread", 28292, 43690],
["write", 23924, 0],
["aread", 27778, 43690],
["bread", 28290, 43690],
["write", 23922, 0],
["aread", 27776, 43690],
["bread", 28288, 43690],
["write", 23920, 0],
["aread", 27774, 43690],
["bread", 28286, 43690],
["write", 23918, 0],
["aread", 27772, 43690],
["bread", 28284, 43690],
["write", 23916, 0],
["aread", 27770, 43690],
["bread", 28282, 43690],
["write", 23914, 0],
["aread", 27768, 43690],
["bread", 28280, 43690],
["write", 23912, 0],
["aread", 27766, 43690],
["bread", 28278, 43690],
["write", 23910, 0],
["aread", 27764, 43690],
["bread", 28276, 43690],
["write", 23908, 0],
["aread", 27762, 43690],
["bread", 28274, 43690],
["write", 23906, 0],
["aread", 27760, 43690],
["bread", 28272, 43690],
["write", 23904, 0],
["aread", 27758, 43690],
["bread", 28270, 43690],
["write", 23902, 0],
["aread", 27756, 43690],
["bread", 28268, 43690],
["write", 23900, 0],
["aread", 27754, 43690],
["bread", 28266, 43690],
["write", 23898, 0],
["aread", 27752, 43690],
["bread", 28264, 43690],
["write", 23896, 0],
["aread", 27750, 43690],
["bread", 28262, 43690],
["write", 23894, 0],
["aread", 27748, 43690],
["bread", 28260, 43690],
["write", 23892, 0],
["aread", 27746, 43690],
["bread", 28258, 43690],
["write", 23890, 0],
["aread", 27744, 43690],
["bread", 28256, 43690],
["write", 23888, 0],
["aread", 27742, 43690],
["bread", 28254, 43690],
["write", 23886, 0],
["aread", 27740, 43690],
["bread", 28252, 43690],
["write", 23884, 0],
["aread", 27738, 43690],
["bread", 28250, 43690],
["write", 23882, 0],
["aread", 27736, 43690],
["bread", 28248, 43690],
["write", 23880, 0],
["aread", 27734, 43690],
["bread", 28246, 43690],
["write", 23878, 0],
["aread", 27732, 43690],
["bread", 28244, 43690],
["write", 23876, 0],
["aread", 27730, 43690],
["bread", 28242, 43690],
["write", 23874, 0],
["aread", 27728, 43690],
["bread", 28240, 43690],
["write", 23872, 0],
["aread", 27726, 43690],
["bread", 28238, 43690],
["write", 23870, 0],
["aread", 27724, 43690],
["bread", 28236, 43690],
["write", 23868, 0],
["aread", 27722, 43690],
["bread", 28234, 43690],
["write", 23866, 0],
["aread", 27720, 43690],
["bread", 28232, 43690],
["write", 23864, 0],
["aread", 27718, 43690],
["bread", 28230, 43690],
["write", 23862, 0],
["aread", 27716, 43690],
["bread", 28228, 43690],
["write", 23860, 0],
["aread", 27714, 43690],
["bread", 28226, 43690],
["write", 23858, 0],
["aread", 27712, 43690],
["bread", 28224, 43690],
["write", 23856, 0],
["aread", 27710, 43690],
["bread", 28222, 43690],
["write", 23854, 0],
["aread", 27708, 43690],
["bread", 28220, 43690],
["write", 23852, 0],
["aread", 27706, 43690],
["bread", 28218, 43690],
["write", 23850, 0],
["aread", 27704, 43690],
["bread", 28216, 43690],
["write", 23848, 0],
["aread", 27702, 43690],
["bread", 28214, 43690],
["write", 23846, 0],
["aread", 27700, 43690],
["bread", 28212, 43690],
["write", 23844, 0],
["aread", 27698, 43690],
["bread", 28210, 43690],
["write", 23842, 0],
["aread", 27696, 43690],
["bread", 28208, 43690],
["write", 23840, 0],
["aread", 27694, 43690],
["bread", 28206, 43690],
["write", 23838, 0],
["aread", 27692, 43690],
["bread", 28204, 43690],
["write", 23836, 0],
["aread", 27690, 43690],
["bread", 28202, 43690],
["write", 23834, 0],
["aread", 27688, 43690],
["bread", 28200, 43690],
["write", 23832, 0],
["aread", 27686, 43690],
["bread", 28198, 43690],
["write", 23830, 0],
["aread", 27684, 43690],
["bread", 28196, 43690],
["write", 23828, 0],
["aread", 27682, 43690],
["bread", 28194, 43690],
["write", 23826, 0],
["aread", 27680, 43690],
["bread", 28192, 43690],
["write", 23824, 0],
["aread", 27678, 43690],
["bread", 28190, 43690],
["write", 23822, 0],
["aread", 27676, 43690],
["bread", 28188, 43690],
["write", 23820, 0],
["aread", 27674, 43690],
["bread", 28186, 43690],
["write", 23818, 0],
["aread", 27672, 43690],
["bread", 28184, 43690],
["write", 23816, 0],
["aread", 27670, 43690],
["bread", 28182, 43690],
["write", 23814, 0],
["aread", 27668, 43690],
["bread", 28180, 43690],
["write", 23812, 0],
["aread", 27666, 43690],
["bread", 28178, 43690],
["write", 23810, 0],
["aread", 27664, 43690],
["bread", 28176, 43690],
["write", 23808, 0],
["aread", 27662, 43690],
["bread", 28174, 43690],
["write", 23806, 0],
["aread", 27660, 43690],
["bread", 28172, 43690],
["write", 23804, 0],
["aread", 27658, 43690],
["bread", 28170, 43690],
["write", 23802, 0],
["aread", 27656, 43690],
["bread", 28168, 43690],
["write", 23800, 0],
["aread", 27654, 43690],
["bread", 28166, 43690],
["write", 23798, 0],
["aread", 27652, 43690],
["bread", 28164, 43690],
["write", 23796, 0],
["aread", 27650, 43690],
["bread", 28162, 43690],
["write", 23794, 0],
["aread", 27648, 43690],
["bread", 28160, 43690],
["write", 23792, 0],
["aread", 27646, 43690],
["bread", 28158, 43690],
["write", 23790, 0],
["aread", 27644, 43690],
["bread", 28156, 43690],
["write", 23788, 0],
["aread", 27642, 43690],
["bread", 28154, 43690],
["write", 23786, 0],
["aread", 27640, 43690],
["bread", 28152, 43690],
["write", 23784, 0],
["aread", 27638, 43690],
["bread", 28150, 43690],
["write", 23782, 0],
["aread", 27636, 43690],
["bread", 28148, 43690],
["write", 23780, 0],
["aread", 27634, 43690],
["bread", 28146, 43690],
["write", 23778, 0],
["aread", 27632, 43690],
["bread", 28144, 43690],
["write", 23776, 0],
["aread", 27630, 43690],
["bread", 28142, 43690],
["write", 23774, 0],
["aread", 27628, 43690],
["bread", 28140, 43690],
["write", 23772, 0],
["aread", 27626, 43690],
["bread", 28138, 43690],
["write", 23770, 0],
["aread", 27624, 43690],
["bread", 28136, 43690],
["write", 23768, 0],
["aread", 27622, 43690],
["bread", 28134, 43690],
["write", 23766, 0],
["aread", 27620, 43690],
["bread", 28132, 43690],
["write", 23764, 0],
["aread", 27618, 43690],
["bread", 28130, 43690],
["write", 23762, 0],
["aread", 27616, 43690],
["bread", 28128, 43690],
["write", 23760, 0],
["aread", 27614, 43690],
["bread", 28126, 43690],
["write", 23758, 0],
["aread", 27612, 43690],
["bread", 28124, 43690],
["write", 23756, 0],
["aread", 27610, 43690],
["bread", 28122, 43690],
["write", 23754, 0],
["aread", 27608, 43690],
["bread", 28120, 43690],
["write", 23752, 0],
["aread", 27606, 43690],
["bread", 28118, 43690],
["write", 23750, 0],
["aread", 27604, 43690],
["bread", 28116, 43690],
["write", 23748, 0],
["aread", 27602, 43690],
["bread", 28114, 43690],
["write", 23746, 0],
["aread", 27600, 43690],
["bread", 28112, 43690],
["write", 23744, 0],
["aread", 27598, 43690],
["bread", 28110, 43690],
["write", 23742, 0],
["aread", 27596, 43690],
["bread", 28108, 43690],
["write", 23740, 0],
["aread", 27594, 43690],
["bread", 28106, 43690],
["write", 23738, 0],
["aread", 27592, 43690],
["bread", 28104, 43690],
["write", 23736, 0],
["aread", 27590, 43690],
["bread", 28102, 43690],
["write", 23734, 0],
["aread", 27588, 43690],
["bread", 28100, 43690],
["write", 23732, 0],
["aread", 27586, 43690],
["bread", 28098, 43690],
["write", 23730, 0],
["aread", 27584, 43690],
["bread", 28096, 43690],
["write", 23728, 0],
["aread", 27582, 43690],
["bread", 28094, 43690],
["write", 23726, 0],
["aread", 27580, 43690],
["bread", 28092, 43690],
["write", 23724, 0],
["aread", 27578, 43690],
["bread", 28090, 43690],
["write", 23722, 0],
["aread", 27576, 43690],
["bread", 28088, 43690],
["write", 23720, 0],
["aread", 27574, 43690],
["bread", 28086, 43690],
["write", 23718, 0],
["aread", 27572, 43690],
["bread", 28084, 43690],
["write", 23716, 0],
["aread", 27570, 43690],
["bread", 28082, 43690],
["write", 23714, 0],
["aread", 27568, 43690],
["bread", 28080, 43690],
["write", 23712, 0],
["aread", 27566, 43690],
["bread", 28078, 43690],
["write", 23710, 0],
["aread", 27564, 43690],
["bread", 28076, 43690],
["write", 23708, 0],
["aread", 27562, 43690],
["bread", 28074, 43690],
["write", 23706, 0],
["aread", 27560, 43690],
["bread", 28072, 43690],
["write", 23704, 0],
["aread", 27558, 43690],
["bread", 28070, 43690],
["write", 23702, 0],
["aread", 27556, 43690],
["bread", 28068, 43690],
["write", 23700, 0],
["aread", 27554, 43690],
["bread", 28066, 43690],
["write", 23698, 0],
["aread", 27552, 43690],
["bread", 28064, 43690],
["write", 23696, 0],
["aread", 27550, 43690],
["bread", 28062, 43690],
["write", 23694, 0],
["aread", 27548, 43690],
["bread", 28060, 43690],
["write", 23692, 0],
["aread", 27546, 43690],
["bread", 28058, 43690],
["write", 23690, 0],
["aread", 27544, 43690],
["bread", 28056, 43690],
["write", 23688, 0],
["aread", 27542, 43690],
["bread", 28054, 43690],
["write", 23686, 0],
["aread", 27540, 43690],
["bread", 28052, 43690],
["write", 23684, 0],
["aread", 27538, 43690],
["bread", 28050, 43690],
["write", 23682, 0],
["aread", 27536, 43690],
["bread", 28048, 43690],
["write", 23680, 0],
["aread", 27534, 43690],
["bread", 28046, 43690],
["write", 23678, 0],
["aread", 27532, 43690],
["bread", 28044, 43690],
["write", 23676, 0],
["aread", 27530, 43690],
["bread", 28042, 43690],
["write", 23674, 0],
["aread", 27528, 43690],
["bread", 28040, 43690],
["write", 23672, 0],
["aread", 27526, 43690],
["bread", 28038, 43690],
["write", 23670, 0],
["aread", 27524, 43690],
["bread", 28036, 43690],
["write", 23668, 0],
["aread", 27522, 43690],
["bread", 28034, 43690],
["write", 23666, 0],
["aread", 27520, 5290],
["bread", 28032, 20778],
["write", 23664, 30976],
["aread", 27518, 4753],
["bread", 28030, 18770],
["write", 23662, 24946],
["aread", 27516, 37137],
["bread", 28028, 19026],
["write", 23660, 25202],
["aread", 27514, 5268],
["bread", 28026, 17481],
["write", 23658, 27753],
["aread", 27512, 4373],
["bread", 28024, 20772],
["write", 23656, 29486],
["aread", 27510, 4757],
["bread", 28022, 17477],
["write", 23654, 25711],
["aread", 27508, 4693],
["bread", 28020, 19026],
["write", 23652, 24826],
["aread", 27506, 37461],
["bread", 28018, 21077],
["write", 23650, 28927],
["aread", 27504, 42258],
["bread", 28016, 17493],
["write", 23648, 20085],
["aread", 27502, 4778],
["bread", 28014, 21162],
["write", 23646, 28672],
["aread", 27500, 43689],
["bread", 28012, 43668],
["write", 23644, 22],
["aread", 27498, 37524],
["bread", 28010, 43594],
["write", 23642, 8296],
["aread", 27496, 4778],
["bread", 28008, 43594],
["write", 23640, 8256],
["aread", 27494, 37157],
["bread", 28006, 17706],
["write", 23638, 26378],
["aread", 27492, 42314],
["bread", 28004, 19114],
["write", 23636, 19072],
["aread", 27490, 21842],
["bread", 28002, 21802],
["write", 23634, 65440],
["aread", 27488, 42325],
["bread", 28000, 17572],
["write", 23632, 20142],
["aread", 27486, 10916],
["bread", 27998, 43666],
["write", 23630, 24],
["aread", 27484, 43349],
["bread", 27996, 18770],
["write", 23628, 17402],
["aread", 27482, 43282],
["bread", 27994, 43346],
["write", 23626, 880],
["aread", 27480, 43690],
["bread", 27992, 10922],
["write", 23624, 0],
["aread", 27478, 42276],
["bread", 27990, 42257],
["write", 23622, 3865],
["aread", 27476, 19090],
["bread", 27988, 19114],
["write", 23620, 49184],
["aread", 27474, 10538],
["bread", 27986, 20778],
["write", 23618, 21248],
["aread", 27472, 43685],
["bread", 27984, 17477],
["write", 23616, 17487]
]

File diff suppressed because it is too large Load Diff

View File

@ -9,6 +9,7 @@
#import <XCTest/XCTest.h>
#include "Blitter.hpp"
#include "NSData+dataWithContentsOfGZippedFile.h"
#include <unordered_map>
#include <vector>
@ -27,111 +28,51 @@ struct Chipset {
};
namespace {
using WriteVector = std::vector<std::pair<uint32_t, uint16_t>>;
}
@interface AmigaBlitterTests: XCTestCase
@end
@implementation AmigaBlitterTests
- (BOOL)verifyWrites:(WriteVector &)writes blitter:(Amiga::Blitter &)blitter ram:(uint16_t *)ram approximateLocation:(NSInteger)approximateLocation {
// Run for however much time the Blitter wants.
while(blitter.get_status() & 0x4000) {
blitter.advance_dma();
}
// Some blits will write the same address twice
// (e.g. by virtue of an appropriate modulo), but
// this unit test is currently able to verify the
// final result only. So count number of accesses per
// address up front in order only to count the
// final ones below.
std::unordered_map<int, int> access_counts;
for(const auto &write: writes) {
++access_counts[write.first];
}
for(const auto &write: writes) {
auto &count = access_counts[write.first];
--count;
if(count) continue;
XCTAssertEqual(ram[write.first >> 1], write.second, @"Didn't find %04x at address %08x; found %04x instead, somewhere before line %ld", write.second, write.first, ram[write.first >> 1], (long)approximateLocation);
// For now, indicate only the first failure.
if(ram[write.first >> 1] != write.second) {
return NO;
}
}
writes.clear();
return YES;
}
- (void)testCase:(NSString *)name {
- (void)testCase:(NSString *)name capturedAllBusActivity:(BOOL)capturedAllBusActivity {
uint16_t ram[256 * 1024]{};
Amiga::Chipset nonChipset;
Amiga::Blitter blitter(nonChipset, ram, 256 * 1024);
Amiga::Blitter<true> blitter(nonChipset, ram, 256 * 1024);
NSURL *const traceURL = [[NSBundle bundleForClass:[self class]] URLForResource:name withExtension:@"json" subdirectory:@"Amiga Blitter Tests"];
NSData *const traceData = [NSData dataWithContentsOfURL:traceURL];
NSArray *const trace = [NSJSONSerialization JSONObjectWithData:traceData options:0 error:nil];
NSString *const tracePath = [[NSBundle bundleForClass:[self class]]
pathForResource:name ofType:@"json.gz" inDirectory:@"Amiga Blitter Tests"];
NSData *const traceData = [NSData dataWithContentsOfGZippedFile:tracePath];
NSError *error;
NSArray *const trace = [NSJSONSerialization JSONObjectWithData:traceData options:0 error:&error];
XCTAssertNotNil(trace, @"JSON decoding failed with error %@", error);
// Step 1 in developing my version of the Blitter is to make sure that I understand
// the logic; as a result the first implementation is going to be a magical thing that
// completes all Blits in a single cycle.
//
// Therefore I've had to bodge my way around the trace's record of reads and writes by
// accumulating all writes into a blob and checking them en massse at the end of a blit
// (as detected by any register work in between memory accesses, since Kickstart 1.3
// doesn't do anything off-book).
enum class State {
AwaitingWrites,
LoggingWrites
} state = State::AwaitingWrites;
using TransactionType = Amiga::Blitter<true>::Transaction::Type;
WriteVector writes;
BOOL hasFailed = NO;
NSInteger arrayEntry = -1;
NSUInteger index = -1;
for(NSArray *const event in trace) {
++arrayEntry;
if(hasFailed) break;
NSString *const type = event[0];
const NSInteger param1 = [event[1] integerValue];
++index;
if([type isEqualToString:@"cread"] || [type isEqualToString:@"bread"] || [type isEqualToString:@"aread"]) {
XCTAssert(param1 < sizeof(ram) - 1);
ram[param1 >> 1] = [event[2] integerValue];
state = State::LoggingWrites;
continue;
}
if([type isEqualToString:@"write"]) {
const uint16_t value = uint16_t([event[2] integerValue]);
if(writes.empty() || writes.back().first != param1) {
writes.push_back(std::make_pair(uint32_t(param1), value));
} else {
writes.back().second = value;
//
// Register writes. Pass straight along.
//
if(![type isEqualToString:@"cread"] && ![type isEqualToString:@"bread"] && ![type isEqualToString:@"aread"] && ![type isEqualToString:@"write"]) {
// Ensure all blitting is completed between register writes; none of the tests
// in this test set are about illegal usage.
while(blitter.get_status() & 0x4000) {
blitter.advance_dma<false>();
}
state = State::LoggingWrites;
continue;
}
// Hackaround for testing my magical all-at-once Blitter is here.
if(state == State::LoggingWrites) {
if(![self verifyWrites:writes blitter:blitter ram:ram approximateLocation:arrayEntry]) {
hasFailed = YES;
break;
const auto transactions = blitter.get_and_reset_transactions();
if(capturedAllBusActivity) {
for(const auto &transaction: transactions) {
if(transaction.type != TransactionType::SkippedSlot && transaction.type != TransactionType::WriteFromPipeline) {
XCTAssert(false, "Unexpected transaction found at index %lu: %s", (unsigned long)index, transaction.to_string().c_str());
return;
}
}
}
state = State::AwaitingWrites;
}
// Hack ends here.
if([type isEqualToString:@"bltcon0"]) {
blitter.set_control(0, param1);
@ -220,55 +161,186 @@ using WriteVector = std::vector<std::pair<uint32_t, uint16_t>>;
continue;
}
NSLog(@"Unhandled type: %@", type);
XCTAssert(false);
break;
}
//
// Bus activity. Store as initial state, and translate for comparison.
//
Amiga::Blitter<true>::Transaction expected_transaction;
expected_transaction.address = uint32_t(param1 >> 1);
expected_transaction.value = uint16_t([event[2] integerValue]);
// Check the final set of writes.
if(!hasFailed) {
[self verifyWrites:writes blitter:blitter ram:ram approximateLocation:-1];
if([type isEqualToString:@"cread"] || [type isEqualToString:@"bread"] || [type isEqualToString:@"aread"]) {
XCTAssert(param1 < sizeof(ram) - 1);
ram[param1 >> 1] = [event[2] integerValue];
if([type isEqualToString:@"aread"]) expected_transaction.type = TransactionType::ReadA;
if([type isEqualToString:@"bread"]) expected_transaction.type = TransactionType::ReadB;
if([type isEqualToString:@"cread"]) expected_transaction.type = TransactionType::ReadC;
} else if([type isEqualToString:@"write"]) {
expected_transaction.type = TransactionType::AddToPipeline;
} else {
NSLog(@"Unhandled type: %@", type);
XCTAssert(false);
break;
}
// Loop until another [comparable] bus transaction appears, and test.
while(true) {
if(!(blitter.get_status() & 0x4000)) {
XCTAssert(false, @"Blitter terminated early at index %lu; waiting for %s", (unsigned long)index, expected_transaction.to_string().c_str());
return;
}
blitter.advance_dma<false>();
const auto transactions = blitter.get_and_reset_transactions();
if(transactions.empty()) {
continue;
}
bool did_compare = false;
for(const auto &transaction : transactions) {
// Skipped slots and data coming out of the pipeline aren't captured
// by the original test data.
switch(transaction.type) {
case TransactionType::SkippedSlot:
case TransactionType::WriteFromPipeline:
continue;
default: break;
}
XCTAssertEqual(transaction.type, expected_transaction.type, @"Type mismatch at index %lu: %s expected vs %s found", (unsigned long)index, expected_transaction.to_string().c_str(), transaction.to_string().c_str());
XCTAssertEqual(transaction.value, expected_transaction.value, @"Value mismatch at index %lu: %s expected vs %s found", (unsigned long)index, expected_transaction.to_string().c_str(), transaction.to_string().c_str());
XCTAssertEqual(transaction.address, expected_transaction.address, @"Address mismatch at index %lu: %s expected vs %s found", (unsigned long)index, expected_transaction.to_string().c_str(), transaction.to_string().c_str());
if(
transaction.type != expected_transaction.type ||
transaction.value != expected_transaction.value ||
transaction.address != expected_transaction.address) {
return;
}
did_compare = true;
}
if(did_compare) break;
}
}
}
- (void)testGadgetToggle {
[self testCase:@"gadget toggle"];
[self testCase:@"gadget toggle" capturedAllBusActivity:YES];
}
- (void)testIconHighlight {
[self testCase:@"icon highlight"];
[self testCase:@"icon highlight" capturedAllBusActivity:NO];
}
- (void)testKickstart13BootLogo {
[self testCase:@"kickstart13 boot logo"];
[self testCase:@"kickstart13 boot logo" capturedAllBusActivity:YES];
}
- (void)testSectorDecode {
[self testCase:@"sector decode"];
[self testCase:@"sector decode" capturedAllBusActivity:YES];
}
- (void)testWindowDrag {
[self testCase:@"window drag"];
[self testCase:@"window drag" capturedAllBusActivity:NO];
}
- (void)testWindowResize {
[self testCase:@"window resize"];
[self testCase:@"window resize" capturedAllBusActivity:NO];
}
- (void)testRAMDiskOpen {
[self testCase:@"RAM disk open"];
[self testCase:@"RAM disk open" capturedAllBusActivity:NO];
}
- (void)testSpots {
[self testCase:@"spots"];
[self testCase:@"spots" capturedAllBusActivity:YES];
}
- (void)testClock {
[self testCase:@"clock"];
[self testCase:@"clock" capturedAllBusActivity:NO];
}
- (void)testInclusiveFills {
[self testCase:@"inclusive fills"];
[self testCase:@"inclusive fills" capturedAllBusActivity:YES];
}
- (void)testAddamsFamilyIntro {
[self testCase:@"Addams Family Intro" capturedAllBusActivity:YES];
}
- (void)testSpindizzyWorlds {
[self testCase:@"Spindizzy Worlds" capturedAllBusActivity:YES];
}
- (void)testSequencer {
// These patterns are faithfully transcribed from the HRM's
// 'Pipeline Register' section, as captured online at
// http://www.amigadev.elowar.com/read/ADCD_2.1/Hardware_Manual_guide/node0127.html
NSArray<NSString *> *const patterns = @[
/* 0 */ @"- - - -",
/* 1 */ @"D0 - D1 - D2",
/* 2 */ @"C0 - C1 - C2",
/* 3 */ @"C0 - - C1 D0 - C2 D1 - D2",
/* 4 */ @"B0 - - B1 - - B2",
/* 5 */ @"B0 - - B1 D0 - B2 D1 - D2",
/* 6 */ @"B0 C0 - B1 C1 - B2 C2",
/* 7 */ @"B0 C0 - - B1 C1 D0 - B2 C2 D1 - D2",
/* 8 */ @"A0 - A1 - A2",
/* 9 */ @"A0 - A1 D0 A2 D1 - D2",
/* A */ @"A0 C0 A1 C1 A2 C2",
/* B */ @"A0 C0 - A1 C1 D0 A2 C2 D1 - D2",
/* C */ @"A0 B0 - A1 B1 - A2 B2",
/* D */ @"A0 B0 - A1 B1 D0 A2 B2 D1 - D2",
/* E */ @"A0 B0 C0 A1 B1 C1 A2 B2 C2",
/* F */ @"A0 B0 C0 - A1 B1 C1 D0 A2 B2 C2 D1 D2",
];
for(int c = 0; c < 16; c++) {
Amiga::BlitterSequencer sequencer;
sequencer.set_control(c);
sequencer.begin();
int writes = 0;
NSUInteger length = [[patterns[c] componentsSeparatedByString:@" "] count];
bool is_first_write = c > 1; // control = 1 is D only, in which case don't pipeline.
NSMutableArray<NSString *> *const components = [[NSMutableArray alloc] init];
while(length--) {
const auto next = sequencer.next();
using Channel = Amiga::BlitterSequencer::Channel;
switch(next.first) {
case Channel::None: [components addObject:@"-"]; break;
case Channel::A: [components addObject:[NSString stringWithFormat:@"A%d", next.second]]; break;
case Channel::B: [components addObject:[NSString stringWithFormat:@"B%d", next.second]]; break;
case Channel::C: [components addObject:[NSString stringWithFormat:@"C%d", next.second]]; break;
case Channel::Write:
case Channel::FlushPipeline:
if(is_first_write) {
is_first_write = false;
[components addObject:@"-"];
} else {
[components addObject:[NSString stringWithFormat:@"D%d", writes++]];
}
break;
default: break;
}
if(next.second == 2) {
sequencer.complete();
}
}
NSString *pattern = [components componentsJoinedByString:@" "];
XCTAssertEqualObjects(
pattern,
patterns[c],
@"Pattern didn't match for control value %x", c);
}
}
@end

View File

@ -0,0 +1,20 @@
//
// NSData+dataWithContentsOfGZippedFile.h
// Clock SignalTests
//
// Created by Thomas Harte on 08/08/2022.
// Copyright © 2022 Thomas Harte. All rights reserved.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface NSData (dataWithContentsOfGZippedFile)
/// Performs an in-memory decompression of the named file, returning it as ephemeral data.
+ (instancetype)dataWithContentsOfGZippedFile:(NSString *)path;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,35 @@
//
// NSData+dataWithContentsOfGZippedFile.m
// Clock SignalTests
//
// Created by Thomas Harte on 08/08/2022.
// Copyright © 2022 Thomas Harte. All rights reserved.
//
#import "NSData+dataWithContentsOfGZippedFile.h"
#include <zlib.h>
@implementation NSData (dataWithContentsOfGZippedFile)
+ (instancetype)dataWithContentsOfGZippedFile:(NSString *)path {
gzFile compressedFile = gzopen([path UTF8String], "rb");
if(!compressedFile) {
return nil;
}
NSMutableData *data = [[NSMutableData alloc] init];
uint8_t buffer[64 * 1024];
while(true) {
int length = gzread(compressedFile, buffer, sizeof(buffer));
if(!length) break;
[data appendBytes:buffer length:length];
if(length != sizeof(buffer)) break;
}
gzclose(compressedFile);
return data;
}
@end