2016-12-11 02:07:52 +00:00
|
|
|
|
//
|
|
|
|
|
// Video.hpp
|
|
|
|
|
// Clock Signal
|
|
|
|
|
//
|
|
|
|
|
// Created by Thomas Harte on 10/12/2016.
|
2018-05-13 19:19:52 +00:00
|
|
|
|
// Copyright 2016 Thomas Harte. All rights reserved.
|
2016-12-11 02:07:52 +00:00
|
|
|
|
//
|
|
|
|
|
|
2024-01-17 04:34:46 +00:00
|
|
|
|
#pragma once
|
2016-12-11 02:07:52 +00:00
|
|
|
|
|
2024-03-04 16:31:25 +00:00
|
|
|
|
#include "../../../Outputs/CRT/CRT.hpp"
|
|
|
|
|
#include "../../../ClockReceiver/ClockReceiver.hpp"
|
2016-12-11 02:07:52 +00:00
|
|
|
|
#include "Interrupts.hpp"
|
|
|
|
|
|
2018-11-04 01:54:25 +00:00
|
|
|
|
#include <vector>
|
|
|
|
|
|
2016-12-11 02:07:52 +00:00
|
|
|
|
namespace Electron {
|
|
|
|
|
|
2016-12-16 00:43:04 +00:00
|
|
|
|
/*!
|
|
|
|
|
Implements the Electron's video subsystem plus appropriate signalling.
|
|
|
|
|
|
|
|
|
|
The Electron has an interlaced fully-bitmapped display with six different output modes,
|
|
|
|
|
running either at 40 or 80 columns. Memory is shared between video and CPU; when the video
|
|
|
|
|
is accessing it the CPU may not.
|
|
|
|
|
*/
|
2017-07-27 11:40:02 +00:00
|
|
|
|
class VideoOutput {
|
2016-12-11 02:07:52 +00:00
|
|
|
|
public:
|
2016-12-16 00:43:04 +00:00
|
|
|
|
/*!
|
2018-11-15 02:52:57 +00:00
|
|
|
|
Instantiates a VideoOutput that will read its pixels from @c memory.
|
2018-11-04 03:40:39 +00:00
|
|
|
|
|
|
|
|
|
The pointer supplied should be to address 0 in the unexpanded Electron's memory map.
|
2016-12-16 00:43:04 +00:00
|
|
|
|
*/
|
2024-09-07 00:18:29 +00:00
|
|
|
|
VideoOutput(const uint8_t *memory);
|
2016-12-11 02:07:52 +00:00
|
|
|
|
|
2018-11-15 02:52:57 +00:00
|
|
|
|
/// Sets the destination for output.
|
|
|
|
|
void set_scan_target(Outputs::Display::ScanTarget *scan_target);
|
|
|
|
|
|
2020-01-21 02:45:10 +00:00
|
|
|
|
/// Gets the current scan status.
|
2020-01-22 03:28:25 +00:00
|
|
|
|
Outputs::Display::ScanStatus get_scaled_scan_status() const;
|
2020-01-21 02:45:10 +00:00
|
|
|
|
|
2018-11-30 04:44:21 +00:00
|
|
|
|
/// Sets the type of output.
|
|
|
|
|
void set_display_type(Outputs::Display::DisplayType);
|
|
|
|
|
|
2020-03-18 02:06:20 +00:00
|
|
|
|
/// Gets the type of output.
|
2020-05-21 03:34:26 +00:00
|
|
|
|
Outputs::Display::DisplayType get_display_type() const;
|
2020-03-18 02:06:20 +00:00
|
|
|
|
|
2024-09-07 00:18:29 +00:00
|
|
|
|
/// Produces the next @c cycles of video output.
|
|
|
|
|
///
|
|
|
|
|
/// @returns a bit mask of all interrupts triggered.
|
2024-09-07 00:21:46 +00:00
|
|
|
|
uint8_t run_for(const Cycles cycles);
|
2024-09-07 00:18:29 +00:00
|
|
|
|
|
|
|
|
|
/// @returns The number of 2Mhz cycles that will pass before completion of an attempted
|
|
|
|
|
/// IO [/1Mhz] access that is first signalled in the upcoming cycle.
|
|
|
|
|
Cycles io_delay() {
|
2024-09-09 00:16:43 +00:00
|
|
|
|
return 2 + ((h_count_ >> 3)&1);
|
2024-09-07 00:18:29 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// @returns The number of 2Mhz cycles that will pass before completion of an attempted
|
|
|
|
|
/// RAM access that is first signalled in the upcoming cycle.
|
|
|
|
|
Cycles ram_delay() {
|
2024-09-09 00:16:43 +00:00
|
|
|
|
if(!mode_40_ && !in_blank()) {
|
|
|
|
|
return 2 + ((h_active - h_count_) >> 3);
|
2024-09-07 00:18:29 +00:00
|
|
|
|
}
|
|
|
|
|
return io_delay();
|
|
|
|
|
}
|
|
|
|
|
|
2016-12-16 00:43:04 +00:00
|
|
|
|
/*!
|
|
|
|
|
Writes @c value to the register at @c address. May mutate the results of @c get_next_interrupt,
|
|
|
|
|
@c get_cycles_until_next_ram_availability and @c get_memory_access_range.
|
|
|
|
|
*/
|
2020-01-05 18:40:02 +00:00
|
|
|
|
void write(int address, uint8_t value);
|
2016-12-16 00:43:04 +00:00
|
|
|
|
|
|
|
|
|
/*!
|
2017-07-25 02:36:42 +00:00
|
|
|
|
@returns the number of cycles after (final cycle of last run_for batch + @c from_time)
|
2016-12-16 00:43:04 +00:00
|
|
|
|
before the video circuits will allow the CPU to access RAM.
|
|
|
|
|
*/
|
2016-12-12 13:01:10 +00:00
|
|
|
|
unsigned int get_cycles_until_next_ram_availability(int from_time);
|
2016-12-11 23:34:49 +00:00
|
|
|
|
|
2016-12-11 02:07:52 +00:00
|
|
|
|
private:
|
2024-09-07 00:18:29 +00:00
|
|
|
|
const uint8_t *ram_ = nullptr;
|
2016-12-11 02:07:52 +00:00
|
|
|
|
|
|
|
|
|
// CRT output
|
2024-09-07 00:36:27 +00:00
|
|
|
|
enum class OutputStage {
|
|
|
|
|
Sync, Blank, Pixels
|
|
|
|
|
};
|
2024-09-07 02:12:19 +00:00
|
|
|
|
OutputStage output_ = OutputStage::Blank;
|
2024-09-07 00:36:27 +00:00
|
|
|
|
int output_length_ = 0;
|
2024-09-07 01:01:30 +00:00
|
|
|
|
int screen_pitch_ = 0;
|
2024-09-07 00:36:27 +00:00
|
|
|
|
|
2017-09-01 02:29:24 +00:00
|
|
|
|
uint8_t *current_output_target_ = nullptr;
|
|
|
|
|
uint8_t *initial_output_target_ = nullptr;
|
2018-11-03 23:58:44 +00:00
|
|
|
|
int current_output_divider_ = 1;
|
2018-11-15 02:52:57 +00:00
|
|
|
|
Outputs::CRT::CRT crt_;
|
2016-12-15 23:07:46 +00:00
|
|
|
|
|
2024-09-07 01:36:05 +00:00
|
|
|
|
// Palettes.
|
2024-09-07 02:12:19 +00:00
|
|
|
|
uint8_t palette_[8]{};
|
|
|
|
|
uint8_t palette1bpp_[2]{};
|
|
|
|
|
uint8_t palette2bpp_[4]{};
|
|
|
|
|
uint8_t palette4bpp_[16]{};
|
2024-09-07 01:36:05 +00:00
|
|
|
|
|
|
|
|
|
template <int index, int source_bit, int target_bit>
|
|
|
|
|
uint8_t channel() {
|
|
|
|
|
if constexpr (source_bit < target_bit) {
|
|
|
|
|
return (palette_[index] << (target_bit - source_bit)) & (1 << target_bit);
|
|
|
|
|
} else {
|
|
|
|
|
return (palette_[index] >> (source_bit - target_bit)) & (1 << target_bit);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template <int r_index, int r_bit, int g_index, int g_bit, int b_index, int b_bit>
|
|
|
|
|
uint8_t palette_entry() {
|
|
|
|
|
return channel<r_index, r_bit, 2>() | channel<g_index, g_bit, 1>() | channel<b_index, b_bit, 0>();
|
|
|
|
|
}
|
2016-12-11 02:07:52 +00:00
|
|
|
|
|
2024-09-07 00:18:29 +00:00
|
|
|
|
// User-selected base address; constrained to a 64-byte boundary by the setter.
|
2024-09-09 00:16:43 +00:00
|
|
|
|
uint16_t screen_base_ = 0;
|
2024-09-07 00:18:29 +00:00
|
|
|
|
|
|
|
|
|
// Parameters implied by mode selection.
|
2024-09-09 00:16:43 +00:00
|
|
|
|
uint16_t mode_base_ = 0;
|
|
|
|
|
bool mode_40_ = true;
|
|
|
|
|
bool mode_text_ = false;
|
2024-09-07 00:18:29 +00:00
|
|
|
|
enum class Bpp {
|
2024-09-07 01:01:30 +00:00
|
|
|
|
One = 1, Two = 2, Four = 4
|
2024-09-09 00:16:43 +00:00
|
|
|
|
} mode_bpp_ = Bpp::One;
|
2024-09-07 00:18:29 +00:00
|
|
|
|
|
|
|
|
|
// Frame position.
|
2024-09-09 00:16:43 +00:00
|
|
|
|
int v_count_ = 0;
|
|
|
|
|
int h_count_ = 0;
|
|
|
|
|
bool field_ = true;
|
2024-09-07 00:18:29 +00:00
|
|
|
|
|
|
|
|
|
// Current working address.
|
2024-09-09 00:16:43 +00:00
|
|
|
|
uint16_t row_addr_ = 0; // Address, sans character row, adopted at the start of a row.
|
|
|
|
|
uint16_t byte_addr_ = 0; // Current working address, incremented as the raster moves across the line.
|
|
|
|
|
int char_row_ = 0; // Character row; 0–9 in text mode, 0–7 in graphics.
|
2024-09-07 00:18:29 +00:00
|
|
|
|
|
|
|
|
|
// Sync states.
|
2024-09-09 00:16:43 +00:00
|
|
|
|
bool vsync_int_ = false; // True => vsync active.
|
|
|
|
|
bool hsync_int_ = false; // True => hsync active.
|
2024-09-07 00:18:29 +00:00
|
|
|
|
|
|
|
|
|
// Horizontal timing parameters; all in terms of the 16Mhz pixel clock but conveniently all
|
|
|
|
|
// divisible by 8, so it's safe to count time with a 2Mhz input.
|
|
|
|
|
static constexpr int h_active = 640;
|
|
|
|
|
static constexpr int hsync_start = 768;
|
|
|
|
|
static constexpr int hsync_end = 832;
|
|
|
|
|
static constexpr int h_reset_addr = 1016;
|
|
|
|
|
static constexpr int h_total = 1024; // Minor digression from the FPGA original here;
|
|
|
|
|
// in this implementation the value is tested
|
|
|
|
|
// _after_ position increment rather than before/instead.
|
|
|
|
|
// So it needs to be one higher. Which is baked into
|
|
|
|
|
// the constant to emphasise the all-divisible-by-8 property.
|
|
|
|
|
static constexpr int h_half = h_total / 2;
|
|
|
|
|
|
|
|
|
|
// Vertical timing parameters; all in terms of lines. As per the horizontal parameters above,
|
|
|
|
|
// lines begin with their first visible pixel (or the equivalent position).
|
|
|
|
|
static constexpr int v_active_gph = 256;
|
|
|
|
|
static constexpr int v_active_txt = 250;
|
|
|
|
|
static constexpr int v_disp_gph = v_active_gph - 1;
|
|
|
|
|
static constexpr int v_disp_txt = v_active_txt - 1;
|
|
|
|
|
static constexpr int vsync_start = 274;
|
|
|
|
|
static constexpr int vsync_end = 276;
|
|
|
|
|
static constexpr int v_rtc = 99;
|
|
|
|
|
|
|
|
|
|
// Various signals that it was convenient to factor out.
|
|
|
|
|
int v_total() const {
|
2024-09-09 00:16:43 +00:00
|
|
|
|
return field_ ? 312 : 311;
|
2024-09-07 00:18:29 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool last_line() const {
|
2024-09-09 00:16:43 +00:00
|
|
|
|
return char_row_ == (mode_text_ ? 9 : 7);
|
2024-09-07 00:18:29 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool in_blank() const {
|
2024-09-09 00:16:43 +00:00
|
|
|
|
return h_count_ >= h_active || (mode_text_ && v_count_ >= v_active_txt) || (!mode_text_ && v_count_ >= v_active_gph) || char_row_ >= 8;
|
2024-09-07 00:18:29 +00:00
|
|
|
|
}};
|
2016-12-11 02:07:52 +00:00
|
|
|
|
}
|