mirror of
https://github.com/TomHarte/CLK.git
synced 2024-07-06 01:28:57 +00:00
Takes a first shot at (inverted) Mac video output.
This commit is contained in:
parent
537b604fc9
commit
a99ebda513
@ -90,6 +90,16 @@ template <class T> class WrappedInt {
|
|||||||
return *static_cast<T *>(this);
|
return *static_cast<T *>(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
T &operator *=(const T &rhs) {
|
||||||
|
length_ *= rhs.length_;
|
||||||
|
return *static_cast<T *>(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
T &operator /=(const T &rhs) {
|
||||||
|
length_ /= rhs.length_;
|
||||||
|
return *static_cast<T *>(this);
|
||||||
|
}
|
||||||
|
|
||||||
T &operator %=(const T &rhs) {
|
T &operator %=(const T &rhs) {
|
||||||
length_ %= rhs.length_;
|
length_ %= rhs.length_;
|
||||||
return *static_cast<T *>(this);
|
return *static_cast<T *>(this);
|
||||||
@ -103,6 +113,9 @@ template <class T> class WrappedInt {
|
|||||||
constexpr T operator +(const T &rhs) const { return T(length_ + rhs.length_); }
|
constexpr T operator +(const T &rhs) const { return T(length_ + rhs.length_); }
|
||||||
constexpr T operator -(const T &rhs) const { return T(length_ - rhs.length_); }
|
constexpr T operator -(const T &rhs) const { return T(length_ - rhs.length_); }
|
||||||
|
|
||||||
|
constexpr T operator *(const T &rhs) const { return T(length_ * rhs.length_); }
|
||||||
|
constexpr T operator /(const T &rhs) const { return T(length_ / rhs.length_); }
|
||||||
|
|
||||||
constexpr T operator %(const T &rhs) const { return T(length_ % rhs.length_); }
|
constexpr T operator %(const T &rhs) const { return T(length_ % rhs.length_); }
|
||||||
constexpr T operator &(const T &rhs) const { return T(length_ & rhs.length_); }
|
constexpr T operator &(const T &rhs) const { return T(length_ & rhs.length_); }
|
||||||
|
|
||||||
|
@ -60,6 +60,8 @@ class ConcreteMachine:
|
|||||||
using Microcycle = CPU::MC68000::Microcycle;
|
using Microcycle = CPU::MC68000::Microcycle;
|
||||||
|
|
||||||
HalfCycles perform_bus_operation(const Microcycle &cycle, int is_supervisor) {
|
HalfCycles perform_bus_operation(const Microcycle &cycle, int is_supervisor) {
|
||||||
|
time_since_video_update_ += cycle.length;
|
||||||
|
|
||||||
// Assumption here: it's a divide by ten to derive the 6522 clock, i.e.
|
// Assumption here: it's a divide by ten to derive the 6522 clock, i.e.
|
||||||
// it runs off the 68000's E clock.
|
// it runs off the 68000's E clock.
|
||||||
via_clock_ += cycle.length;
|
via_clock_ += cycle.length;
|
||||||
@ -71,11 +73,12 @@ class ConcreteMachine:
|
|||||||
if(cycle.operation) {
|
if(cycle.operation) {
|
||||||
auto word_address = cycle.word_address();
|
auto word_address = cycle.word_address();
|
||||||
|
|
||||||
// Hardware devices begin at 0x800000.
|
// Hardware devices begin at 0x800000 and accesses to 'them' (i.e. at lest the 6522,
|
||||||
|
// and the other two are a guess) is via the synchronous bus.
|
||||||
mc68000_.set_is_peripheral_address(word_address >= 0x400000);
|
mc68000_.set_is_peripheral_address(word_address >= 0x400000);
|
||||||
if(word_address >= 0x400000) {
|
if(word_address >= 0x400000) {
|
||||||
if(cycle.data_select_active()) {
|
if(cycle.data_select_active()) {
|
||||||
printf("IO access to %06x: ", word_address << 1);
|
// printf("IO access to %06x: ", word_address << 1);
|
||||||
|
|
||||||
const int register_address = word_address >> 8;
|
const int register_address = word_address >> 8;
|
||||||
|
|
||||||
@ -83,7 +86,7 @@ class ConcreteMachine:
|
|||||||
case 0x77f0ff:
|
case 0x77f0ff:
|
||||||
// VIA accesses are via address 0xefe1fe + register*512,
|
// VIA accesses are via address 0xefe1fe + register*512,
|
||||||
// which at word precision is 0x77f0ff + register*256.
|
// which at word precision is 0x77f0ff + register*256.
|
||||||
printf("VIA");
|
// printf("VIA");
|
||||||
if(cycle.operation & Microcycle::Read) {
|
if(cycle.operation & Microcycle::Read) {
|
||||||
cycle.value->halves.low = via_.get_register(register_address);
|
cycle.value->halves.low = via_.get_register(register_address);
|
||||||
if(cycle.operation & Microcycle::SelectWord) cycle.value->halves.high = 0xff;
|
if(cycle.operation & Microcycle::SelectWord) cycle.value->halves.high = 0xff;
|
||||||
@ -94,11 +97,11 @@ class ConcreteMachine:
|
|||||||
|
|
||||||
case 0x6ff0ff:
|
case 0x6ff0ff:
|
||||||
// IWM
|
// IWM
|
||||||
printf("IWM %d", register_address & 0xf);
|
// printf("IWM %d", register_address & 0xf);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
printf("\n");
|
// printf("\n");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if(cycle.data_select_active()) {
|
if(cycle.data_select_active()) {
|
||||||
@ -136,6 +139,7 @@ class ConcreteMachine:
|
|||||||
break;
|
break;
|
||||||
case Microcycle::SelectWord:
|
case Microcycle::SelectWord:
|
||||||
memory_base[word_address] = cycle.value->full;
|
memory_base[word_address] = cycle.value->full;
|
||||||
|
printf("%04x -> %06x\n", cycle.value->full, word_address << 1);
|
||||||
break;
|
break;
|
||||||
case Microcycle::SelectByte:
|
case Microcycle::SelectByte:
|
||||||
memory_base[word_address] = uint16_t(
|
memory_base[word_address] = uint16_t(
|
||||||
@ -151,10 +155,6 @@ class ConcreteMachine:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Any access to the
|
|
||||||
|
|
||||||
// TODO: the entirety of dealing with this cycle.
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Normal memory map:
|
Normal memory map:
|
||||||
|
|
||||||
@ -164,19 +164,14 @@ class ConcreteMachine:
|
|||||||
BFFFF8+: SCC write operations
|
BFFFF8+: SCC write operations
|
||||||
DFE1FF+: IWM
|
DFE1FF+: IWM
|
||||||
EFE1FE+: VIA
|
EFE1FE+: VIA
|
||||||
|
|
||||||
Overlay mode:
|
|
||||||
|
|
||||||
ROM replaces RAM at 00000, while also being at 400000
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
return HalfCycles(0);
|
return HalfCycles(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
void flush() {
|
||||||
Notes to self: accesses to the VIA are via the 68000's
|
video_.run_for(time_since_video_update_.flush());
|
||||||
synchronous bus.
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
void set_rom_is_overlay(bool rom_is_overlay) {
|
void set_rom_is_overlay(bool rom_is_overlay) {
|
||||||
ROM_is_overlay_ = rom_is_overlay;
|
ROM_is_overlay_ = rom_is_overlay;
|
||||||
@ -258,6 +253,7 @@ class ConcreteMachine:
|
|||||||
MOS::MOS6522::MOS6522<VIAPortHandler> via_;
|
MOS::MOS6522::MOS6522<VIAPortHandler> via_;
|
||||||
VIAPortHandler via_port_handler_;
|
VIAPortHandler via_port_handler_;
|
||||||
HalfCycles via_clock_;
|
HalfCycles via_clock_;
|
||||||
|
HalfCycles time_since_video_update_;
|
||||||
|
|
||||||
bool ROM_is_overlay_ = true;
|
bool ROM_is_overlay_ = true;
|
||||||
};
|
};
|
||||||
|
@ -8,8 +8,18 @@
|
|||||||
|
|
||||||
#include "Video.hpp"
|
#include "Video.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
using namespace Apple::Macintosh;
|
using namespace Apple::Macintosh;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
const HalfCycles line_length(704);
|
||||||
|
const int number_of_lines = 370;
|
||||||
|
const HalfCycles frame_length(line_length * HalfCycles(number_of_lines));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// Re: CRT timings, see the Apple Guide to the Macintosh Hardware Family,
|
// Re: CRT timings, see the Apple Guide to the Macintosh Hardware Family,
|
||||||
// bottom of page 400:
|
// bottom of page 400:
|
||||||
//
|
//
|
||||||
@ -22,13 +32,116 @@ using namespace Apple::Macintosh;
|
|||||||
// During the vertical blanking interval, the turned-off beam ... traces out an additional 28 scan lines,"
|
// During the vertical blanking interval, the turned-off beam ... traces out an additional 28 scan lines,"
|
||||||
//
|
//
|
||||||
Video::Video(uint16_t *ram) :
|
Video::Video(uint16_t *ram) :
|
||||||
crt_(704, 1, 370, Outputs::Display::ColourSpace::YIQ, 1, 1, 6, false, Outputs::Display::InputDataType::Luminance1) {
|
crt_(704, 1, 370, Outputs::Display::ColourSpace::YIQ, 1, 1, 6, false, Outputs::Display::InputDataType::Luminance1),
|
||||||
|
ram_(ram) {
|
||||||
|
|
||||||
|
crt_.set_display_type(Outputs::Display::DisplayType::CompositeMonochrome);
|
||||||
|
crt_.set_visible_area(Outputs::Display::Rect(0.02f, 0.025f, 0.94f, 0.94f));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Video::set_scan_target(Outputs::Display::ScanTarget *scan_target) {
|
void Video::set_scan_target(Outputs::Display::ScanTarget *scan_target) {
|
||||||
crt_.set_scan_target(scan_target);
|
crt_.set_scan_target(scan_target);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Video::run_for(HalfCycles duration) {
|
||||||
|
const int sync_start = 36;
|
||||||
|
const int sync_end = 38;
|
||||||
|
|
||||||
|
// The number of HalfCycles is literally the number of pixel clocks to move through,
|
||||||
|
// since pixel output occurs at twice the processor clock. So divide by 16 to get
|
||||||
|
// the number of fetches.
|
||||||
|
while(duration > HalfCycles(0)) {
|
||||||
|
auto cycles_left_in_line = std::min(line_length - frame_position_%line_length, duration);
|
||||||
|
|
||||||
|
const int line = (frame_position_ / line_length).as_int();
|
||||||
|
const auto pixel_start = frame_position_ % line_length;
|
||||||
|
|
||||||
|
// Line timing, entirely invented as I can find exactly zero words of documentation:
|
||||||
|
//
|
||||||
|
// First 342 lines:
|
||||||
|
//
|
||||||
|
// First 32 words = pixels;
|
||||||
|
// next 5 words = right border;
|
||||||
|
// next 2 words = sync level;
|
||||||
|
// final 5 words = left border.
|
||||||
|
//
|
||||||
|
// Then 12 lines of border, 3 of sync, 11 more of border.
|
||||||
|
|
||||||
|
const int first_word = pixel_start.as_int() >> 4;
|
||||||
|
const int final_word = (pixel_start + cycles_left_in_line).as_int() >> 4;
|
||||||
|
|
||||||
|
if(first_word != final_word) {
|
||||||
|
if(line < 342) {
|
||||||
|
// If there are any pixels left to output, do so.
|
||||||
|
if(first_word < 32) {
|
||||||
|
const int final_pixel_word = std::min(final_word, 32);
|
||||||
|
|
||||||
|
if(!first_word) {
|
||||||
|
pixel_buffer_ = crt_.begin_data(512);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(pixel_buffer_) {
|
||||||
|
for(int c = first_word; c < final_pixel_word; ++c) {
|
||||||
|
uint16_t pixels = ram_[video_address_];
|
||||||
|
++video_address_;
|
||||||
|
|
||||||
|
pixel_buffer_[0] = pixels & 0x01;
|
||||||
|
pixel_buffer_[1] = pixels & 0x02;
|
||||||
|
pixel_buffer_[2] = pixels & 0x04;
|
||||||
|
pixel_buffer_[3] = pixels & 0x08;
|
||||||
|
pixel_buffer_[4] = pixels & 0x10;
|
||||||
|
pixel_buffer_[5] = pixels & 0x20;
|
||||||
|
pixel_buffer_[6] = pixels & 0x40;
|
||||||
|
pixel_buffer_[7] = pixels & 0x80;
|
||||||
|
|
||||||
|
pixels >>= 8;
|
||||||
|
pixel_buffer_[8] = pixels & 0x01;
|
||||||
|
pixel_buffer_[9] = pixels & 0x02;
|
||||||
|
pixel_buffer_[10] = pixels & 0x04;
|
||||||
|
pixel_buffer_[11] = pixels & 0x08;
|
||||||
|
pixel_buffer_[12] = pixels & 0x10;
|
||||||
|
pixel_buffer_[13] = pixels & 0x20;
|
||||||
|
pixel_buffer_[14] = pixels & 0x40;
|
||||||
|
pixel_buffer_[15] = pixels & 0x80;
|
||||||
|
|
||||||
|
pixel_buffer_ += 16;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(final_pixel_word == 32) {
|
||||||
|
crt_.output_data(512);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(first_word < sync_start && final_word >= sync_start) crt_.output_blank((sync_start - 32) * 16);
|
||||||
|
if(first_word < sync_end && final_word >= sync_end) crt_.output_sync((sync_end - sync_start) * 16);
|
||||||
|
if(final_word == 44) crt_.output_blank((44 - sync_end) * 16);
|
||||||
|
} else if(line >= 353 && line < 356) {
|
||||||
|
/* Output a sync line. */
|
||||||
|
if(final_word == 44) {
|
||||||
|
crt_.output_sync(sync_start * 16);
|
||||||
|
crt_.output_blank((sync_end - sync_start) * 16);
|
||||||
|
crt_.output_sync((44 - sync_end) * 16);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/* Output a blank line. */
|
||||||
|
if(final_word == 44) {
|
||||||
|
crt_.output_blank(sync_start * 16);
|
||||||
|
crt_.output_sync((sync_end - sync_start) * 16);
|
||||||
|
crt_.output_blank((44 - sync_end) * 16);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
duration -= cycles_left_in_line;
|
||||||
|
frame_position_ = frame_position_ + cycles_left_in_line;
|
||||||
|
if(frame_position_ == frame_length) {
|
||||||
|
frame_position_ = HalfCycles(0);
|
||||||
|
video_address_ = 0x1a700 >> 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Video: $1A700 and the alternate buffer starts at $12700; for a 512K Macintosh, add $60000 to these numbers.
|
Video: $1A700 and the alternate buffer starts at $12700; for a 512K Macintosh, add $60000 to these numbers.
|
||||||
*/
|
*/
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
#define Video_hpp
|
#define Video_hpp
|
||||||
|
|
||||||
#include "../../../Outputs/CRT/CRT.hpp"
|
#include "../../../Outputs/CRT/CRT.hpp"
|
||||||
|
#include "../../../ClockReceiver/ClockReceiver.hpp"
|
||||||
|
|
||||||
namespace Apple {
|
namespace Apple {
|
||||||
namespace Macintosh {
|
namespace Macintosh {
|
||||||
@ -18,9 +19,17 @@ class Video {
|
|||||||
public:
|
public:
|
||||||
Video(uint16_t *ram);
|
Video(uint16_t *ram);
|
||||||
void set_scan_target(Outputs::Display::ScanTarget *scan_target);
|
void set_scan_target(Outputs::Display::ScanTarget *scan_target);
|
||||||
|
void run_for(HalfCycles duration);
|
||||||
|
|
||||||
|
// TODO: feedback on blanks and syncs.
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Outputs::CRT::CRT crt_;
|
Outputs::CRT::CRT crt_;
|
||||||
|
|
||||||
|
HalfCycles frame_position_;
|
||||||
|
size_t video_address_;
|
||||||
|
uint16_t *ram_;
|
||||||
|
uint8_t *pixel_buffer_;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user