// license:BSD-3-Clause
// copyright-holders:Curt Coder
/**********************************************************************
SMC CRT9007 CRT Video Processor and Controller (VPAC) emulation
**********************************************************************/
/*
TODO:
- cursor timer
- interrupts
- light pen
- frame timer
- non-DMA mode
- DMA mode
- cursor/blank skew
- sequential breaks
- interlaced mode
- smooth scroll
- page blank
- double height cursor
- row attributes
- pin configuration
- operation modes 0,4,7
- address modes 1,2,3
- light pen
- state saving
*/
#include "emu.h"
#include "crt9007.h"
#include "screen.h"
//#define VERBOSE 1
#include "logmacro.h"
//**************************************************************************
// DEVICE DEFINITIONS
//**************************************************************************
DEFINE_DEVICE_TYPE(CRT9007, crt9007_device, "crt9007", "SMC CRT9007 VPAC")
//**************************************************************************
// MACROS / CONSTANTS
//**************************************************************************
#define HAS_VALID_PARAMETERS \
(m_reg[0x00] && m_reg[0x01] && m_reg[0x07] && m_reg[0x08] && m_reg[0x09])
#define CHARACTERS_PER_HORIZONTAL_PERIOD \
m_reg[0x00]
#define CHARACTERS_PER_DATA_ROW \
(m_reg[0x01] + 1)
#define HORIZONTAL_DELAY \
m_reg[0x02]
#define HORIZONTAL_SYNC_WIDTH \
m_reg[0x03]
#define VERTICAL_SYNC_WIDTH \
m_reg[0x04]
#define VERTICAL_DELAY \
(m_reg[0x05] - 1)
#define PIN_CONFIGURATION \
(m_reg[0x06] >> 6)
#define CURSOR_SKEW \
((m_reg[0x06] >> 3) & 0x07)
#define BLANK_SKEW \
(m_reg[0x06] & 0x07)
#define VISIBLE_DATA_ROWS_PER_FRAME \
(m_reg[0x07] + 1)
#define SCAN_LINES_PER_DATA_ROW \
((m_reg[0x08] & 0x1f) + 1)
#define SCAN_LINES_PER_FRAME \
(((m_reg[0x08] << 3) & 0x0700) | m_reg[0x09])
#define DMA_BURST_COUNT \
((m_reg[0x0a] & 0x0f) + 1)
#define DMA_BURST_DELAY \
((((m_reg[0x0a] >> 4) & 0x07) + 1) % 8)
#define DMA_DISABLE \
BIT(m_reg[0x0a], 7)
#define SINGLE_HEIGHT_CURSOR \
BIT(m_reg[0x0b], 0)
#define OPERATION_MODE \
((m_reg[0x0b] >> 1) & 0x07)
#define INTERLACE_MODE \
((m_reg[0x0b] >> 4) & 0x03)
#define PAGE_BLANK \
BIT(m_reg[0x0b], 6)
#define TABLE_START \
(((m_reg[0x0d] << 8) & 0x3f00) | m_reg[0x0c])
#define ADDRESS_MODE \
((m_reg[0x0d] >> 6) & 0x03)
#define AUXILIARY_ADDRESS_1 \
(((m_reg[0x0f] << 8) & 0x3f00) | m_reg[0x0e])
#define ROW_ATTRIBUTES_1 \
((m_reg[0x0f] >> 6) & 0x03)
#define SEQUENTIAL_BREAK_1 \
m_reg[0x10]
#define SEQUENTIAL_BREAK_2 \
m_reg[0x12]
#define DATA_ROW_START \
m_reg[0x11]
#define DATA_ROW_END \
m_reg[0x12]
#define AUXILIARY_ADDRESS_2 \
(((m_reg[0x14] << 8) & 0x3f00) | m_reg[0x13])
#define ROW_ATTRIBUTES_2 \
((m_reg[0x14] >> 6) & 0x03)
#define SMOOTH_SCROLL_OFFSET \
((m_reg[0x17] >> 1) & 0x3f)
#define SMOOTH_SCROLL_OFFSET_OVERFLOW \
BIT(m_reg[0x17], 7)
#define VERTICAL_CURSOR \
m_reg[0x18]
#define HORIZONTAL_CURSOR \
m_reg[0x19]
#define INTERRUPT_ENABLE \
m_reg[0x1a]
#define FRAME_TIMER \
BIT(m_reg[0x1a], 0)
#define LIGHT_PEN_INTERRUPT \
BIT(m_reg[0x1a], 5)
#define VERTICAL_RETRACE_INTERRUPT \
BIT(m_reg[0x1a], 6)
#define VERTICAL_LIGHT_PEN \
m_reg[0x3b]
#define HORIZONTAL_LIGHT_PEN \
m_reg[0x3c]
// interlace
enum
{
NON_INTERLACED = 0,
ENHANCED_VIDEO_INTERFACE,
NORMAL_VIDEO_INTERFACE
};
// operation modes
enum
{
OPERATION_MODE_REPETITIVE_MEMORY_ADDRESSING = 0, // not implemented
OPERATION_MODE_DOUBLE_ROW_BUFFER = 1,
OPERATION_MODE_SINGLE_ROW_BUFFER = 4, // not implemented
OPERATION_MODE_ATTRIBUTE_ASSEMBLE = 7 // not implemented
};
// addressing modes
enum
{
ADDRESS_MODE_SEQUENTIAL_ADDRESSING = 0,
ADDRESS_MODE_SEQUENTIAL_ROLL_ADDRESSING, // not implemented
ADDRESS_MODE_CONTIGUOUS_ROW_TABLE, // not implemented
ADDRESS_MODE_LINKED_LIST_ROW_TABLE // not implemented
};
// interrupt enable register bits
const int IE_VERTICAL_RETRACE = 0x40;
//const int IE_LIGHT_PEN = 0x20;
//const int IE_FRAME_TIMER = 0x01;
// status register bits
const int STATUS_INTERRUPT_PENDING = 0x80;
//const int STATUS_VERTICAL_RETRACE = 0x40;
const int STATUS_LIGHT_PEN_UPDATE = 0x20;
//const int STATUS_ODD_EVEN = 0x04;
//const int STATUS_FRAME_TIMER_OCCURRED = 0x01;
//**************************************************************************
// GLOBAL VARIABLES
//**************************************************************************
// default address map
void crt9007_device::crt9007(address_map &map)
{
map(0x0000, 0x3fff).ram();
}
//**************************************************************************
// INLINE HELPERS
//**************************************************************************
//-------------------------------------------------
// readbyte - read a byte at the given address
//-------------------------------------------------
inline uint8_t crt9007_device::readbyte(offs_t address)
{
return space().read_byte(address);
}
//-------------------------------------------------
// trigger_interrupt -
//-------------------------------------------------
inline void crt9007_device::trigger_interrupt(int line)
{
if (INTERRUPT_ENABLE & line)
{
int status = m_status;
m_status |= STATUS_INTERRUPT_PENDING | line;
if (!(status & STATUS_INTERRUPT_PENDING))
{
LOG("CRT9007 INT 1\n");
m_write_int(ASSERT_LINE);
}
}
}
//-------------------------------------------------
// update_cblank_line -
//-------------------------------------------------
inline void crt9007_device::update_cblank_line()
{
int x = screen().hpos();
int y = screen().vpos();
// composite blank
int cblank = !(m_hs & m_vs);
if (m_cblank != cblank)
{
m_cblank = cblank;
LOG("CRT9007 y %03u x %04u : CBLANK %u\n", y, x, m_cblank);
m_write_cblank(m_cblank);
}
}
//-------------------------------------------------
// update_hsync_timer -
//-------------------------------------------------
inline void crt9007_device::update_hsync_timer(int state)
{
int y = screen().vpos();
int next_x = state ? m_hsync_start : m_hsync_end;
int next_y = state ? (y + 1) % SCAN_LINES_PER_FRAME : y;
attotime duration = screen().time_until_pos(next_y, next_x);
m_hsync_timer->adjust(duration, !state);
}
//-------------------------------------------------
// update_vsync_timer -
//-------------------------------------------------
inline void crt9007_device::update_vsync_timer(int state)
{
int next_y = state ? m_vsync_start : m_vsync_end;
attotime duration = screen().time_until_pos(next_y, 0);
m_vsync_timer->adjust(duration, !state);
}
//-------------------------------------------------
// update_vlt_timer -
//-------------------------------------------------
inline void crt9007_device::update_vlt_timer(int state)
{
// this signal is active during all visible scan lines and during the horizontal trace at vertical retrace
int y = screen().vpos();
int next_x = state ? m_vlt_end : m_vlt_start;
int next_y = state ? y : ((y == m_vlt_bottom) ? 0 : (y + 1));
attotime duration = screen().time_until_pos(next_y, next_x);
m_vlt_timer->adjust(duration, !state);
}
//-------------------------------------------------
// update_curs_timer -
//-------------------------------------------------
inline void crt9007_device::update_curs_timer(int state)
{
// this signal is active for 1 character time for all scanlines within the data row
// TODO
}
//-------------------------------------------------
// update_drb_timer -
//-------------------------------------------------
inline void crt9007_device::update_drb_timer(int state)
{
// this signal is active for 1 full scan line (VLT edge to edge) at the top scan line of each new row
// there is 1 extra DRB signal during the 1st scanline of the vertical retrace interval
int y = screen().vpos();
int next_x = m_vlt_end;
int next_y = y ? y + 1 : y;
if (state)
{
if (y == 0)
{
next_y = VERTICAL_DELAY - 1;
}
else if (y == m_drb_bottom)
{
next_x = 0;
next_y = 0;
}
else
{
next_y = y + SCAN_LINES_PER_DATA_ROW - 1;
}
}
attotime duration = screen().time_until_pos(next_y, next_x);
m_drb_timer->adjust(duration, !state);
}
//-------------------------------------------------
// update_dma_timer -
//-------------------------------------------------
inline void crt9007_device::update_dma_timer()
{
// TODO
}
//-------------------------------------------------
// recompute_parameters -
//-------------------------------------------------
inline void crt9007_device::recompute_parameters()
{
// check that necessary registers have been loaded
if (!HAS_VALID_PARAMETERS) return;
// screen dimensions
//int horiz_pix_total = CHARACTERS_PER_HORIZONTAL_PERIOD * m_hpixels_per_column;
//int vert_pix_total = SCAN_LINES_PER_FRAME;
// refresh rate
//attoseconds_t refresh = HZ_TO_ATTOSECONDS(clock()) * horiz_pix_total * vert_pix_total;
// horizontal sync
m_hsync_start = 0;
m_hsync_end = HORIZONTAL_SYNC_WIDTH * m_hpixels_per_column;
// visible line time
m_vlt_start = HORIZONTAL_DELAY * m_hpixels_per_column;
m_vlt_end = (HORIZONTAL_DELAY + CHARACTERS_PER_DATA_ROW) * m_hpixels_per_column;
m_vlt_bottom = VERTICAL_DELAY + (VISIBLE_DATA_ROWS_PER_FRAME * SCAN_LINES_PER_DATA_ROW) - 1;
// data row boundary
m_drb_bottom = VERTICAL_DELAY + (VISIBLE_DATA_ROWS_PER_FRAME * SCAN_LINES_PER_DATA_ROW) - SCAN_LINES_PER_DATA_ROW;
// vertical sync
m_vsync_start = 0;
m_vsync_end = VERTICAL_SYNC_WIDTH;
// visible area
//rectangle visarea;
//visarea.set(m_hsync_end, horiz_pix_total - 1, m_vsync_end, vert_pix_total - 1);
//LOG("CRT9007 Screen: %u x %u @ %f Hz\n", horiz_pix_total, vert_pix_total, 1 / ATTOSECONDS_TO_DOUBLE(refresh));
//LOG("CRT9007 Visible Area: (%u, %u) - (%u, %u)\n", visarea.min_x, visarea.min_y, visarea.max_x, visarea.max_y);
//screen().configure(horiz_pix_total, vert_pix_total, visarea, refresh);
m_hsync_timer->adjust(screen().time_until_pos(0, 0));
m_vsync_timer->adjust(screen().time_until_pos(0, 0));
m_vlt_timer->adjust(screen().time_until_pos(0, m_vlt_start), 1);
m_drb_timer->adjust(screen().time_until_pos(0, 0));
}
//**************************************************************************
// LIVE DEVICE
//**************************************************************************
//-------------------------------------------------
// crt9007_device - constructor
//-------------------------------------------------
crt9007_device::crt9007_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) :
device_t(mconfig, CRT9007, tag, owner, clock),
device_memory_interface(mconfig, *this),
device_video_interface(mconfig, *this),
m_space_config("videoram", ENDIANNESS_LITTLE, 8, 14, 0, address_map_constructor(), address_map_constructor(FUNC(crt9007_device::crt9007), this)),
m_write_int(*this),
m_write_dmar(*this),
m_write_hs(*this),
m_write_vs(*this),
m_write_vlt(*this),
m_write_curs(*this),
m_write_drb(*this),
m_write_wben(*this),
m_write_cblank(*this),
m_write_slg(*this),
m_write_sld(*this)
{
for (auto & elem : m_reg)
elem = 0;
}
//-------------------------------------------------
// device_start - device-specific startup
//-------------------------------------------------
void crt9007_device::device_start()
{
// allocate timers
m_hsync_timer = timer_alloc(TIMER_HSYNC);
m_vsync_timer = timer_alloc(TIMER_VSYNC);
m_vlt_timer = timer_alloc(TIMER_VLT);
m_curs_timer = timer_alloc(TIMER_CURS);
m_drb_timer = timer_alloc(TIMER_DRB);
m_dma_timer = timer_alloc(TIMER_DMA);
// resolve callbacks
m_write_int.resolve_safe();
m_write_dmar.resolve_safe();
m_write_hs.resolve_safe();
m_write_vs.resolve_safe();
m_write_vlt.resolve_safe();
m_write_curs.resolve_safe();
m_write_drb.resolve_safe();
m_write_wben.resolve_safe();
m_write_cblank.resolve_safe();
m_write_slg.resolve_safe();
m_write_sld.resolve_safe();
}
//-------------------------------------------------
// device_reset - device-specific reset
//-------------------------------------------------
void crt9007_device::device_reset()
{
m_disp = 0;
m_vs = 0;
m_cblank = 0;
// HS = 1
m_write_hs(1);
// VS = 1
m_write_vs(1);
// CBLANK = 1
m_write_cblank(0);
// CURS = 0
m_write_curs(0);
// VLT = 0
m_write_vlt(0);
// DRB = 1
m_write_drb(1);
// INT = 0
m_write_int(CLEAR_LINE);
// 28 (DMAR) = 0
m_write_dmar(CLEAR_LINE);
// 29 (WBEN) = 0
m_write_wben(1); // HACK
// 30 (SLG) = 0
m_write_slg(0);
// 31 (SLD) = 0
m_write_sld(0);
// 32 (LPSTB) = 0
}
//-------------------------------------------------
// device_clock_changed - handle clock change
//-------------------------------------------------
void crt9007_device::device_clock_changed()
{
recompute_parameters();
}
//-------------------------------------------------
// device_timer - handle timer events
//-------------------------------------------------
void crt9007_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr)
{
int x = screen().hpos();
int y = screen().vpos();
switch (id)
{
case TIMER_HSYNC:
m_hs = param;
LOG("CRT9007 y %03u x %04u : HS %u\n", y, x, m_hs);
m_write_hs(m_hs);
update_cblank_line();
update_hsync_timer(param);
break;
case TIMER_VSYNC:
m_vs = param;
LOG("CRT9007 y %03u x %04u : VS %u\n", y, x, m_vs);
m_write_vs(param);
if (m_vs)
{
// reset all other bits except Light Pen Update to logic 0
m_status &= STATUS_LIGHT_PEN_UPDATE;
}
else
{
trigger_interrupt(IE_VERTICAL_RETRACE);
update_cblank_line();
}
update_vsync_timer(param);
break;
case TIMER_VLT:
m_vlt = param;
LOG("CRT9007 y %03u x %04u : VLT %u\n", y, x, m_vlt);
m_write_vlt(param);
update_vlt_timer(param);
break;
case TIMER_CURS:
LOG("CRT9007 y %03u x %04u : CURS %u\n", y, x, param);
m_write_curs(param);
update_curs_timer(param);
break;
case TIMER_DRB:
m_drb = param;
LOG("CRT9007 y %03u x %04u : DRB %u\n", y, x, m_drb);
m_write_drb(param);
if (!m_drb && !DMA_DISABLE)
{
// start DMA burst sequence
m_dma_count = CHARACTERS_PER_DATA_ROW;
m_dma_burst = DMA_BURST_COUNT ? (DMA_BURST_COUNT * 4) : CHARACTERS_PER_DATA_ROW;
m_dma_delay = DMA_BURST_DELAY;
m_dmar = 1;
LOG("CRT9007 DMAR 1\n");
m_write_dmar(ASSERT_LINE);
}
update_drb_timer(param);
break;
case TIMER_DMA:
readbyte(AUXILIARY_ADDRESS_2);
update_dma_timer();
break;
}
}
//-------------------------------------------------
// memory_space_config - return a description of
// any address spaces owned by this device
//-------------------------------------------------
device_memory_interface::space_config_vector crt9007_device::memory_space_config() const
{
return space_config_vector {
std::make_pair(0, &m_space_config)
};
}
//-------------------------------------------------
// read - register read
//-------------------------------------------------
READ8_MEMBER( crt9007_device::read )
{
uint8_t data = 0;
switch (offset)
{
case 0x15:
LOG("CRT9007 Start\n");
m_disp = 1;
break;
case 0x16:
LOG("CRT9007 Reset\n");
device_reset();
break;
case 0x38:
data = VERTICAL_CURSOR;
break;
case 0x39:
data = HORIZONTAL_CURSOR;
break;
case 0x3a:
data = m_status;
// reset interrupt pending bit
m_status &= ~STATUS_INTERRUPT_PENDING;
LOG("CRT9007 INT 0\n");
m_write_int(CLEAR_LINE);
break;
case 0x3b:
data = VERTICAL_LIGHT_PEN;
break;
case 0x3c:
data = HORIZONTAL_LIGHT_PEN;
// reset light pen update bit
m_status &= ~STATUS_LIGHT_PEN_UPDATE;
break;
default:
logerror("CRT9007 Read from Invalid Register: %02x!\n", offset);
}
return data;
}
//-------------------------------------------------
// write - register write
//-------------------------------------------------
WRITE8_MEMBER( crt9007_device::write )
{
m_reg[offset] = data;
switch (offset)
{
case 0x00:
recompute_parameters();
LOG("CRT9007 Characters per Horizontal Period: %u\n", CHARACTERS_PER_HORIZONTAL_PERIOD);
break;
case 0x01:
recompute_parameters();
LOG("CRT9007 Characters per Data Row: %u\n", CHARACTERS_PER_DATA_ROW);
break;
case 0x02:
recompute_parameters();
LOG("CRT9007 Horizontal Delay: %u\n", HORIZONTAL_DELAY);
break;
case 0x03:
recompute_parameters();
LOG("CRT9007 Horizontal Sync Width: %u\n", HORIZONTAL_SYNC_WIDTH);
break;
case 0x04:
recompute_parameters();
LOG("CRT9007 Vertical Sync Width: %u\n", VERTICAL_SYNC_WIDTH);
break;
case 0x05:
recompute_parameters();
LOG("CRT9007 Vertical Delay: %u\n", VERTICAL_DELAY);
break;
case 0x06:
recompute_parameters();
LOG("CRT9007 Pin Configuration: %u\n", PIN_CONFIGURATION);
LOG("CRT9007 Cursor Skew: %u\n", CURSOR_SKEW);
LOG("CRT9007 Blank Skew: %u\n", BLANK_SKEW);
break;
case 0x07:
recompute_parameters();
LOG("CRT9007 Visible Data Rows per Frame: %u\n", VISIBLE_DATA_ROWS_PER_FRAME);
break;
case 0x08:
recompute_parameters();
LOG("CRT9007 Scan Lines per Data Row: %u\n", SCAN_LINES_PER_DATA_ROW);
break;
case 0x09:
recompute_parameters();
LOG("CRT9007 Scan Lines per Frame: %u\n", SCAN_LINES_PER_FRAME);
break;
case 0x0a:
LOG("CRT9007 DMA Burst Count: %u\n", DMA_BURST_COUNT);
LOG("CRT9007 DMA Burst Delay: %u\n", DMA_BURST_DELAY);
LOG("CRT9007 DMA Disable: %u\n", DMA_DISABLE);
break;
case 0x0b:
LOG("CRT9007 %s Height Cursor\n", SINGLE_HEIGHT_CURSOR ? "Single" : "Double");
LOG("CRT9007 Operation Mode: %u\n", OPERATION_MODE);
LOG("CRT9007 Interlace Mode: %u\n", INTERLACE_MODE);
LOG("CRT9007 %s Mechanism\n", PAGE_BLANK ? "Page Blank" : "Smooth Scroll");
break;
case 0x0c:
break;
case 0x0d:
LOG("CRT9007 Table Start Register: %04x\n", TABLE_START);
LOG("CRT9007 Address Mode: %u\n", ADDRESS_MODE);
break;
case 0x0e:
break;
case 0x0f:
LOG("CRT9007 Auxialiary Address Register 1: %04x\n", AUXILIARY_ADDRESS_1);
LOG("CRT9007 Row Attributes: %u\n", ROW_ATTRIBUTES_1);
break;
case 0x10:
LOG("CRT9007 Sequential Break Register 1: %u\n", SEQUENTIAL_BREAK_1);
break;
case 0x11:
LOG("CRT9007 Data Row Start Register: %u\n", DATA_ROW_START);
break;
case 0x12:
LOG("CRT9007 Data Row End/Sequential Break Register 2: %u\n", SEQUENTIAL_BREAK_2);
break;
case 0x13:
break;
case 0x14:
LOG("CRT9007 Auxiliary Address Register 2: %04x\n", AUXILIARY_ADDRESS_2);
LOG("CRT9007 Row Attributes: %u\n", ROW_ATTRIBUTES_2);
break;
case 0x15:
LOG("CRT9007 Start\n");
m_disp = 1;
break;
case 0x16:
LOG("CRT9007 Reset\n");
device_reset();
break;
case 0x17:
LOG("CRT9007 Smooth Scroll Offset: %u\n", SMOOTH_SCROLL_OFFSET);
LOG("CRT9007 Smooth Scroll Offset Overflow: %u\n", SMOOTH_SCROLL_OFFSET_OVERFLOW);
break;
case 0x18:
LOG("CRT9007 Vertical Cursor Register: %u\n", VERTICAL_CURSOR);
break;
case 0x19:
LOG("CRT9007 Horizontal Cursor Register: %u\n", HORIZONTAL_CURSOR);
break;
case 0x1a:
LOG("CRT9007 Frame Timer: %u\n", FRAME_TIMER);
LOG("CRT9007 Light Pen Interrupt: %u\n", LIGHT_PEN_INTERRUPT);
LOG("CRT9007 Vertical Retrace Interrupt: %u\n", VERTICAL_RETRACE_INTERRUPT);
break;
default:
logerror("CRT9007 Write to Invalid Register: %02x!\n", offset);
}
}
//-------------------------------------------------
// ack_w - DMA acknowledge
//-------------------------------------------------
WRITE_LINE_MEMBER( crt9007_device::ack_w )
{
LOG("CRT9007 ACK: %u\n", state);
if (m_dmar && !m_ack && state)
{
// start DMA transfer
m_dma_timer->adjust(attotime::from_hz(clock()));
}
m_ack = state;
}
//-------------------------------------------------
// lpstb_w - light pen strobe
//-------------------------------------------------
WRITE_LINE_MEMBER( crt9007_device::lpstb_w )
{
LOG("CRT9007 LPSTB: %u\n", state);
if (!m_lpstb && state)
{
// TODO latch current row/column position
}
m_lpstb = state;
}
//-------------------------------------------------
// set_character_width -
//-------------------------------------------------
void crt9007_device::set_character_width(int value)
{
m_hpixels_per_column = value;
recompute_parameters();
}