mirror of
https://github.com/TomHarte/CLK.git
synced 2025-02-19 07:31:15 +00:00
Incompletely transitions towards more flexible clock ratios.
This commit is contained in:
parent
151f60958e
commit
5729ece7bb
@ -218,9 +218,7 @@ void TMS9918<personality>::run_for(const HalfCycles cycles) {
|
||||
// Convert 456 clocked half cycles per line to 342 internal cycles per line;
|
||||
// the internal clock is 1.5 times the nominal 3.579545 Mhz that I've advertised
|
||||
// for this part. So multiply by three quarters.
|
||||
int int_cycles = int(cycles.as_integral() * 3) + this->cycles_error_;
|
||||
this->cycles_error_ = int_cycles & 3;
|
||||
int_cycles >>= 2;
|
||||
int int_cycles = this->clock_converter_.to_internal(cycles.as<int>());
|
||||
if(!int_cycles) return;
|
||||
|
||||
// There are two intertwined processes here, 'writing' (which means writing to the
|
||||
@ -236,7 +234,10 @@ void TMS9918<personality>::run_for(const HalfCycles cycles) {
|
||||
|
||||
if(write_cycles_pool) {
|
||||
// Determine how much writing to do.
|
||||
const int write_cycles = std::min(342 - this->write_pointer_.column, write_cycles_pool);
|
||||
const int write_cycles = std::min(
|
||||
this->clock_converter_.CyclesPerLine - this->write_pointer_.column,
|
||||
write_cycles_pool
|
||||
);
|
||||
const int end_column = this->write_pointer_.column + write_cycles;
|
||||
LineBuffer &line_buffer = this->line_buffers_[this->write_pointer_.row];
|
||||
|
||||
@ -274,16 +275,16 @@ void TMS9918<personality>::run_for(const HalfCycles cycles) {
|
||||
// Perform memory accesses.
|
||||
// ------------------------
|
||||
#define fetch(function) \
|
||||
if(final_window != 171) { \
|
||||
if(final_window != this->clock_converter_.AccessWindowCyclesPerLine) { \
|
||||
function<true>(first_window, final_window);\
|
||||
} else {\
|
||||
function<false>(first_window, final_window);\
|
||||
}
|
||||
|
||||
// column_ and end_column are in 342-per-line cycles;
|
||||
// adjust them to a count of windows.
|
||||
const int first_window = this->write_pointer_.column >> 1;
|
||||
const int final_window = end_column >> 1;
|
||||
// Adjust column_ and end_column to the access-window clock before calling
|
||||
// the mode-applicable fetch function.
|
||||
const int first_window = this->clock_converter_.to_access_clock(this->write_pointer_.column);
|
||||
const int final_window = this->clock_converter_.to_access_clock(end_column);
|
||||
if(first_window != final_window) {
|
||||
switch(line_buffer.line_mode) {
|
||||
case LineMode::Text: fetch(this->template fetch_tms_text); break;
|
||||
@ -336,7 +337,7 @@ void TMS9918<personality>::run_for(const HalfCycles cycles) {
|
||||
this->write_pointer_.column = end_column;
|
||||
write_cycles_pool -= write_cycles;
|
||||
|
||||
if(this->write_pointer_.column == 342) {
|
||||
if(this->write_pointer_.column == this->clock_converter_.CyclesPerLine) {
|
||||
this->write_pointer_.column = 0;
|
||||
this->write_pointer_.row = (this->write_pointer_.row + 1) % this->mode_timing_.total_lines;
|
||||
LineBuffer &next_line_buffer = this->line_buffers_[this->write_pointer_.row];
|
||||
@ -345,7 +346,7 @@ void TMS9918<personality>::run_for(const HalfCycles cycles) {
|
||||
this->set_current_screen_mode();
|
||||
|
||||
// Based on the output mode, pick a line mode.
|
||||
next_line_buffer.first_pixel_output_column = 86;
|
||||
next_line_buffer.first_pixel_output_column = 86; // TODO: these should be a function of ClockConverter::CyclesPerLine.
|
||||
next_line_buffer.next_border_column = 342;
|
||||
this->mode_timing_.maximum_visible_sprites = 4;
|
||||
switch(this->screen_mode_) {
|
||||
@ -379,25 +380,31 @@ void TMS9918<personality>::run_for(const HalfCycles cycles) {
|
||||
|
||||
if(read_cycles_pool) {
|
||||
// Determine how much time has passed in the remainder of this line, and proceed.
|
||||
const int target_read_cycles = std::min(342 - this->read_pointer_.column, read_cycles_pool);
|
||||
const int target_read_cycles = std::min(
|
||||
this->clock_converter_.CyclesPerLine - this->read_pointer_.column,
|
||||
read_cycles_pool
|
||||
);
|
||||
int read_cycles_performed = 0;
|
||||
uint32_t next_cram_value = 0;
|
||||
|
||||
while(read_cycles_performed < target_read_cycles) {
|
||||
int read_cycles = target_read_cycles - read_cycles_performed;
|
||||
if(!read_cycles) continue;
|
||||
|
||||
const uint32_t cram_value = next_cram_value;
|
||||
next_cram_value = 0;
|
||||
int read_cycles = target_read_cycles - read_cycles_performed;
|
||||
if(!this->upcoming_cram_dots_.empty() && this->upcoming_cram_dots_.front().location.row == this->read_pointer_.row) {
|
||||
int time_until_dot = this->upcoming_cram_dots_.front().location.column - this->read_pointer_.column;
|
||||
if constexpr (is_sega_vdp(personality)) {
|
||||
if(!this->upcoming_cram_dots_.empty() && this->upcoming_cram_dots_.front().location.row == this->read_pointer_.row) {
|
||||
int time_until_dot = this->upcoming_cram_dots_.front().location.column - this->read_pointer_.column;
|
||||
|
||||
if(time_until_dot < read_cycles) {
|
||||
read_cycles = time_until_dot;
|
||||
next_cram_value = this->upcoming_cram_dots_.front().value;
|
||||
this->upcoming_cram_dots_.erase(this->upcoming_cram_dots_.begin());
|
||||
if(time_until_dot < read_cycles) {
|
||||
read_cycles = time_until_dot;
|
||||
next_cram_value = this->upcoming_cram_dots_.front().value;
|
||||
this->upcoming_cram_dots_.erase(this->upcoming_cram_dots_.begin());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!read_cycles) continue;
|
||||
read_cycles_performed += read_cycles;
|
||||
|
||||
const int end_column = this->read_pointer_.column + read_cycles;
|
||||
@ -418,6 +425,7 @@ void TMS9918<personality>::run_for(const HalfCycles cycles) {
|
||||
|
||||
#define border(left, right) intersect(left, right, this->output_border(end - start, cram_value))
|
||||
|
||||
// TODO: CRT clock might need to change?
|
||||
if(line_buffer.line_mode == LineMode::Refresh || this->read_pointer_.row > this->mode_timing_.pixel_lines) {
|
||||
if(this->read_pointer_.row >= this->mode_timing_.first_vsync_line && this->read_pointer_.row < this->mode_timing_.first_vsync_line+4) {
|
||||
// Vertical sync.
|
||||
@ -507,7 +515,7 @@ void TMS9918<personality>::run_for(const HalfCycles cycles) {
|
||||
}
|
||||
|
||||
read_cycles_pool -= target_read_cycles;
|
||||
if(this->read_pointer_.column == 342) {
|
||||
if(this->read_pointer_.column == this->clock_converter_.CyclesPerLine) {
|
||||
this->read_pointer_.column = 0;
|
||||
this->read_pointer_.row = (this->read_pointer_.row + 1) % this->mode_timing_.total_lines;
|
||||
}
|
||||
@ -750,11 +758,6 @@ uint8_t TMS9918<personality>::read(int address) {
|
||||
return result;
|
||||
}
|
||||
|
||||
template <Personality personality>
|
||||
HalfCycles Base<personality>::half_cycles_before_internal_cycles(int internal_cycles) const {
|
||||
return HalfCycles(((internal_cycles << 2) + (2 - cycles_error_)) / 3);
|
||||
}
|
||||
|
||||
template <Personality personality>
|
||||
HalfCycles TMS9918<personality>::get_next_sequence_point() const {
|
||||
if(!this->generate_interrupts_ && !this->enable_line_interrupts_) return HalfCycles::max();
|
||||
@ -769,7 +772,7 @@ HalfCycles TMS9918<personality>::get_next_sequence_point() const {
|
||||
) % frame_length;
|
||||
if(!time_until_frame_interrupt) time_until_frame_interrupt = frame_length;
|
||||
|
||||
if(!this->enable_line_interrupts_) return this->half_cycles_before_internal_cycles(time_until_frame_interrupt);
|
||||
if(!this->enable_line_interrupts_) return this->clock_converter_.half_cycles_before_internal_cycles(time_until_frame_interrupt);
|
||||
|
||||
// Calculate when the next line interrupt will occur.
|
||||
int next_line_interrupt_row = -1;
|
||||
@ -797,17 +800,17 @@ HalfCycles TMS9918<personality>::get_next_sequence_point() const {
|
||||
// the frame end interrupt or no interrupt pending as appropriate.
|
||||
if(next_line_interrupt_row == -1) {
|
||||
return this->generate_interrupts_ ?
|
||||
this->half_cycles_before_internal_cycles(time_until_frame_interrupt) :
|
||||
this->clock_converter_.half_cycles_before_internal_cycles(time_until_frame_interrupt) :
|
||||
HalfCycles::max();
|
||||
}
|
||||
|
||||
// Figure out the number of internal cycles until the next line interrupt, which is the amount
|
||||
// of time to the next tick over and then next_line_interrupt_row - row_ lines further.
|
||||
const int local_cycles_until_line_interrupt = cycles_to_next_interrupt_threshold + (next_line_interrupt_row - line_of_next_interrupt_threshold) * 342;
|
||||
if(!this->generate_interrupts_) return this->half_cycles_before_internal_cycles(local_cycles_until_line_interrupt);
|
||||
if(!this->generate_interrupts_) return this->clock_converter_.half_cycles_before_internal_cycles(local_cycles_until_line_interrupt);
|
||||
|
||||
// Return whichever interrupt is closer.
|
||||
return this->half_cycles_before_internal_cycles(std::min(local_cycles_until_line_interrupt, time_until_frame_interrupt));
|
||||
return this->clock_converter_.half_cycles_before_internal_cycles(std::min(local_cycles_until_line_interrupt, time_until_frame_interrupt));
|
||||
}
|
||||
|
||||
template <Personality personality>
|
||||
@ -825,7 +828,7 @@ HalfCycles TMS9918<personality>::get_time_until_line(int line) {
|
||||
line += this->mode_timing_.total_lines;
|
||||
}
|
||||
|
||||
return this->half_cycles_before_internal_cycles(cycles_to_next_interrupt_threshold + (line - line_of_next_interrupt_threshold)*342);
|
||||
return this->clock_converter_.half_cycles_before_internal_cycles(cycles_to_next_interrupt_threshold + (line - line_of_next_interrupt_threshold)*342);
|
||||
}
|
||||
|
||||
template <Personality personality>
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
#include "../../../Outputs/CRT/CRT.hpp"
|
||||
#include "../../../ClockReceiver/ClockReceiver.hpp"
|
||||
#include "ClockConverter.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
@ -199,11 +200,7 @@ template <Personality personality> struct Base {
|
||||
uint8_t text_colour_ = 0;
|
||||
uint8_t background_colour_ = 0;
|
||||
|
||||
// This implementation of this chip officially accepts a 3.58Mhz clock, but runs
|
||||
// internally at 5.37Mhz. The following two help to maintain a lossless conversion
|
||||
// from the one to the other.
|
||||
int cycles_error_ = 0;
|
||||
HalfCycles half_cycles_before_internal_cycles(int internal_cycles) const;
|
||||
ClockConverter<personality> clock_converter_;
|
||||
|
||||
// Internal mechanisms for position tracking.
|
||||
int latched_column_ = 0;
|
||||
|
64
Components/9918/Implementation/ClockConverter.hpp
Normal file
64
Components/9918/Implementation/ClockConverter.hpp
Normal file
@ -0,0 +1,64 @@
|
||||
//
|
||||
// ClockConverter.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 01/01/2023.
|
||||
// Copyright © 2023 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef ClockConverter_hpp
|
||||
#define ClockConverter_hpp
|
||||
|
||||
#include "../9918.hpp"
|
||||
|
||||
namespace TI {
|
||||
namespace TMS {
|
||||
|
||||
template <Personality personality> class ClockConverter {
|
||||
public:
|
||||
/*!
|
||||
Converts a number of **half-cycles** to an internal number
|
||||
of **cycles**.
|
||||
*/
|
||||
int to_internal(int source) {
|
||||
// Default behaviour is top apply a multiplication by 3/4.
|
||||
const int result = source * 3 + cycles_error_;
|
||||
cycles_error_ = result & 3;
|
||||
return result >> 2;
|
||||
}
|
||||
|
||||
/*!
|
||||
Provides the number of external cycles that will need to pass in order to advance
|
||||
_at least_ @c internal_cycles into the future.
|
||||
*/
|
||||
HalfCycles half_cycles_before_internal_cycles(int internal_cycles) const {
|
||||
return HalfCycles(
|
||||
((internal_cycles << 2) + (2 - cycles_error_)) / 3
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
Converts a position in internal cycles to its corresponding position
|
||||
on the access-window clock.
|
||||
*/
|
||||
static constexpr int to_access_clock(int source) {
|
||||
return source >> 1;
|
||||
}
|
||||
|
||||
/// The number of internal cycles in a single line.
|
||||
constexpr static int CyclesPerLine = 342;
|
||||
|
||||
/// Indicates the number of access-window cycles in a single line.
|
||||
constexpr static int AccessWindowCyclesPerLine = 171;
|
||||
|
||||
private:
|
||||
// This implementation of this chip officially accepts a 3.58Mhz clock, but runs
|
||||
// internally at 5.37Mhz. The following two help to maintain a lossless conversion
|
||||
// from the one to the other.
|
||||
int cycles_error_ = 0;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* ClockConverter_hpp */
|
@ -1317,6 +1317,7 @@
|
||||
4B3FE75C1F3CF68B00448EE4 /* CPM.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CPM.cpp; path = Parsers/CPM.cpp; sourceTree = "<group>"; };
|
||||
4B3FE75D1F3CF68B00448EE4 /* CPM.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = CPM.hpp; path = Parsers/CPM.hpp; sourceTree = "<group>"; };
|
||||
4B43983829620FB1006B0BFC /* 9918.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = 9918.cpp; sourceTree = "<group>"; };
|
||||
4B43983C29621024006B0BFC /* ClockConverter.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ClockConverter.hpp; sourceTree = "<group>"; };
|
||||
4B448E7F1F1C45A00009ABD6 /* TZX.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TZX.cpp; sourceTree = "<group>"; };
|
||||
4B448E801F1C45A00009ABD6 /* TZX.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TZX.hpp; sourceTree = "<group>"; };
|
||||
4B448E821F1C4C480009ABD6 /* PulseQueuedTape.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PulseQueuedTape.cpp; sourceTree = "<group>"; };
|
||||
@ -4734,6 +4735,7 @@
|
||||
children = (
|
||||
4B43983829620FB1006B0BFC /* 9918.cpp */,
|
||||
4BD388411FE34E010042B588 /* 9918Base.hpp */,
|
||||
4B43983C29621024006B0BFC /* ClockConverter.hpp */,
|
||||
);
|
||||
path = Implementation;
|
||||
sourceTree = "<group>";
|
||||
|
Loading…
x
Reference in New Issue
Block a user